/*
 * Copyright 1999-2006 University of Chicago
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "globus_i_xio_http.h"

#ifndef GLOBUS_DONT_DOCUMENT_INTERNAL
/**
 * @defgroup globus_i_xio_http_handle Internal Handle Implementation
 * @brief Internal Handle Implementation
 */
#endif

static
void
globus_l_xio_http_write_eof_callback(
    globus_xio_operation_t              op,
    globus_result_t                     result,
    globus_size_t                       nbytes,
    void *                              user_arg);

/**
 * Allocate and initialize an HTTP handle
 * @ingroup globus_i_xio_http_handle
 *
 * Creates a new handle with values derived from the attr and target. This
 * is called internally by the HTTP driver's open implementation.
 *
 * @param http_handle
 *     Pointer to an uninitialized handle.
 * @param attr
 *     HTTP attributes associated with open operation.
 * @param target
 *     Target which is being opened.
 *
 * This function may return GLOBUS_SUCCESS, or an error generated by
 * globus_i_xio_http_response_init(), globus_i_xio_http_request_copy(),
 * or globus_i_xio_http_target_copy().
 */
globus_result_t
globus_i_xio_http_handle_init(
    globus_i_xio_http_handle_t *        http_handle,
    globus_i_xio_http_attr_t *          attr,
    globus_i_xio_http_target_t *        target)
{
    globus_result_t                     result;
    int                                 rc;
    GlobusXIOName(globus_i_xio_http_handle_init);

    rc = globus_mutex_init(&http_handle->mutex, NULL);

    if (rc != 0)
    {
        result = GlobusXIOErrorMemory("mutex");
        goto error_exit;
    }

    if (target->is_client && attr != NULL)
    {
        result = globus_i_xio_http_request_copy(
                &http_handle->request_info,
                &attr->request);
    }
    else
    {
        result = globus_i_xio_http_request_init(&http_handle->request_info);
    }

    if (target->is_client)
    {
        http_handle->parse_state = GLOBUS_XIO_HTTP_STATUS_LINE;
        http_handle->send_state = GLOBUS_XIO_HTTP_PRE_REQUEST_LINE;
    }
    else
    {
        http_handle->parse_state = GLOBUS_XIO_HTTP_PRE_REQUEST_LINE;
        http_handle->send_state = GLOBUS_XIO_HTTP_STATUS_LINE;
    }

    if (result != GLOBUS_SUCCESS)
    {
        goto free_mutex_exit;
    }

    result = globus_i_xio_http_response_init(&http_handle->response_info);
    if (result != GLOBUS_SUCCESS)
    {
        goto free_request_exit;
    }

    result = globus_i_xio_http_target_copy(&http_handle->target_info, target);
    if (result != GLOBUS_SUCCESS)
    {
        goto free_response_exit;
    }
    http_handle->header_iovec = NULL;
    http_handle->header_iovcnt = 0;
    http_handle->read_buffer.iov_base = NULL;
    http_handle->read_buffer.iov_len = 0;
    http_handle->close_operation = NULL;
    http_handle->response_read_operation = NULL;
    http_handle->read_operation.iov = NULL;
    http_handle->read_operation.iovcnt = 0;
    http_handle->read_operation.operation = NULL;
    http_handle->read_operation.driver_handle = NULL;
    http_handle->read_operation.nbytes = 0;
    http_handle->write_operation.iov = NULL;
    http_handle->write_operation.iovcnt = 0;
    http_handle->write_operation.operation = NULL;
    http_handle->write_operation.driver_handle = NULL;
    http_handle->write_operation.nbytes = 0;
    http_handle->user_close = GLOBUS_FALSE;
    http_handle->read_response = GLOBUS_FALSE;

    return GLOBUS_SUCCESS;

free_response_exit:
    globus_i_xio_http_response_destroy(&http_handle->response_info);
free_request_exit:
    globus_i_xio_http_request_destroy(&http_handle->request_info);
free_mutex_exit:
    globus_mutex_destroy(&http_handle->mutex);
error_exit:
    return result;
}
/* globus_i_xio_http_handle_init() */

globus_result_t
globus_i_xio_http_handle_reinit(
    globus_i_xio_http_handle_t *        http_handle,
    globus_i_xio_http_attr_t *          http_attr,
    globus_i_xio_http_target_t *        http_target)
{
    globus_result_t                     result;
    GlobusXIOName(globus_i_xio_http_handle_reinit);

    if (http_target && http_target->is_client && http_attr != NULL)
    {
        globus_i_xio_http_request_destroy(&http_handle->request_info);

        result = globus_i_xio_http_request_copy(
                &http_handle->request_info,
                &http_attr->request);
    }
    else
    {
        globus_i_xio_http_request_destroy(&http_handle->request_info);
        result = globus_i_xio_http_request_init(&http_handle->request_info);
    }

    if (http_target && http_target->is_client)
    {
        http_handle->send_state = GLOBUS_XIO_HTTP_PRE_REQUEST_LINE;
    }
    else
    {
        http_handle->send_state = GLOBUS_XIO_HTTP_STATUS_LINE;
    }

    if (result != GLOBUS_SUCCESS)
    {
        goto free_mutex_exit;
    }

    globus_i_xio_http_response_destroy(&http_handle->response_info);
    result = globus_i_xio_http_response_init(&http_handle->response_info);
    if (result != GLOBUS_SUCCESS)
    {
        goto free_request_exit;
    }

    globus_i_xio_http_target_destroy_internal(&http_handle->target_info);
    if (http_target)
    {
        result = globus_i_xio_http_target_copy(
                &http_handle->target_info,
                http_target);
        if (result != GLOBUS_SUCCESS)
        {
            goto free_response_exit;
        }
    }
    http_handle->header_iovec = NULL;
    http_handle->header_iovcnt = 0;
    http_handle->close_operation = NULL;
    http_handle->read_operation.iov = NULL;
    http_handle->read_operation.iovcnt = 0;
    http_handle->read_operation.operation = NULL;
    http_handle->read_operation.driver_handle = NULL;
    http_handle->read_operation.nbytes = 0;
    http_handle->write_operation.iov = NULL;
    http_handle->write_operation.iovcnt = 0;
    http_handle->write_operation.operation = NULL;
    http_handle->write_operation.driver_handle = NULL;
    http_handle->write_operation.nbytes = 0;
    http_handle->user_close = GLOBUS_FALSE;
    http_handle->read_response = GLOBUS_FALSE;

    return GLOBUS_SUCCESS;

free_response_exit:
    globus_i_xio_http_response_destroy(&http_handle->response_info);
free_request_exit:
    globus_i_xio_http_request_destroy(&http_handle->request_info);
free_mutex_exit:
    globus_mutex_destroy(&http_handle->mutex);
    return result;
}
/**
 * Destroy an HTTP handle
 * @ingroup globus_i_xio_http_handle
 *
 * Frees all storage associated with an HTTP handle. The handle must
 * not be used after this function returns.
 *
 * @param http_handle
 *     Handle to be destroyed.
 *
 * @return void
 */
void
globus_i_xio_http_handle_destroy(
    globus_i_xio_http_handle_t *        http_handle)
{
    int                                 i;

    globus_mutex_destroy(&http_handle->mutex);
    globus_i_xio_http_request_destroy(&http_handle->request_info);
    globus_i_xio_http_response_destroy(&http_handle->response_info);
    globus_i_xio_http_target_destroy_internal(&http_handle->target_info);

    if (http_handle->header_iovec != NULL)
    {
        for (i = 0; i < http_handle->header_iovcnt; i++)
        {
            globus_libc_free(http_handle->header_iovec[i].iov_base);
        }
        globus_libc_free(http_handle->header_iovec);
    }
    if (http_handle->read_buffer.iov_base != NULL)
    {
        globus_libc_free(http_handle->read_buffer.iov_base);
    }
    if (http_handle->close_operation != NULL)
    {
        globus_xio_driver_operation_destroy(http_handle->close_operation);
    }
}
/* globus_i_xio_http_handle_destroy() */

/**
 * Modify the state of an HTTP handle
 * @ingroup globus_i_xio_http_handle
 *
 * Modify the state of an HTTP handle. This is called by the XIO driver via
 * globus_xio_handle_cntl().
 *
 * @param handle
 *     Void pointer to a #globus_i_xio_http_handle_t structure containing
 *     information an HTTP request/response transaction.
 * @param cmd
 *     Integer value indicating what command will be executed on the handle.
 *     Valid command values are in the set defined by
 *     #globus_xio_http_handle_cmd_t.
 * @param ap
 *     Variable-length argument list containing any cmd-specific parameters.
 *
 * @retval GLOBUS_SUCCESS
 *     The command was sucessfully executed.
 * @retval GLOBUS_XIO_ERROR_MEMORY
 *     The command failed due to memory constraints.
 * @retval GLOBUS_XIO_ERROR_PARAMETER
 *     Invalid @a cmd parameter or invlaid value of cmd-specific parameters 
 *     in @a ap
 */
globus_result_t
globus_i_xio_http_handle_cntl(
    void *                              handle,
    int                                 cmd,
    va_list                             ap)
{
    char *                              in_header_name;
    char *                              in_header_value;
    globus_result_t                     result = GLOBUS_SUCCESS;
    globus_i_xio_http_handle_t *        http_handle = handle;
    char *                              in_str;
    char *                              in_str2;
    char *                              save_str;
    int                                 in_int;
    GlobusXIOName(globus_i_xio_http_handle_cntl);

    globus_mutex_lock(&http_handle->mutex);

    switch (cmd)
    {
        case GLOBUS_XIO_HTTP_HANDLE_SET_RESPONSE_HEADER:
            if (http_handle->target_info.is_client)
            {
                result = GlobusXIOErrorParameter("handle");
                break;
            }

            if (http_handle->send_state != GLOBUS_XIO_HTTP_STATUS_LINE)
            {
                result = GlobusXIOErrorParameter("handle");
                break;
            }
            in_str = va_arg(ap, char *);
            in_str2 = va_arg(ap, char *);

            result = globus_i_xio_http_header_info_set_header(
                    &http_handle->response_info.headers,
                    in_str,
                    in_str2,
                    GLOBUS_FALSE);

            break;

        case GLOBUS_XIO_HTTP_HANDLE_SET_RESPONSE_STATUS_CODE:
            if (http_handle->target_info.is_client)
            {
                result = GlobusXIOErrorParameter("handle");
                break;
            }

            if (http_handle->send_state != GLOBUS_XIO_HTTP_STATUS_LINE)
            {
                result = GlobusXIOErrorParameter("handle");
                break;
            }
            in_int = va_arg(ap, int);

            if (in_int < 100 || in_int > 599)
            {
                result = GlobusXIOErrorParameter("status_code");
                break;
            }

            http_handle->response_info.status_code = in_int;

            break;

        case GLOBUS_XIO_HTTP_HANDLE_SET_RESPONSE_REASON_PHRASE:
            if (http_handle->target_info.is_client)
            {
                result = GlobusXIOErrorParameter("handle");
                break;
            }

            if (http_handle->send_state != GLOBUS_XIO_HTTP_STATUS_LINE)
            {
                result = GlobusXIOErrorParameter("handle");
                break;
            }
            in_str = va_arg(ap, char *);

            if (in_str == NULL)
            {
                result = GlobusXIOErrorParameter("reason_phrase");
            }
            save_str = http_handle->response_info.reason_phrase;

            http_handle->response_info.reason_phrase =
                    globus_libc_strdup(in_str);

            if (http_handle->response_info.reason_phrase == NULL)
            {
                result = GlobusXIOErrorMemory("reason_phrase");
                break;
            }

            if (save_str)
            {
                globus_libc_free(save_str);
            }
            break;
        case GLOBUS_XIO_HTTP_HANDLE_SET_RESPONSE_HTTP_VERSION:
            if (http_handle->target_info.is_client)
            {
                result = GlobusXIOErrorParameter("handle");
                break;
            }

            if (http_handle->send_state != GLOBUS_XIO_HTTP_STATUS_LINE)
            {
                result = GlobusXIOErrorParameter("handle");
                break;
            }

            http_handle->response_info.http_version =
                    va_arg(ap, globus_xio_http_version_t);
            break;
        case GLOBUS_XIO_HTTP_HANDLE_SET_END_OF_ENTITY:
            result = globus_i_xio_http_set_end_of_entity(http_handle);

            break;

        case GLOBUS_XIO_HTTP_HANDLE_SET_REQUEST_HEADER:
            in_header_name = va_arg(ap, char *);
            if (in_header_name == NULL)
            {
                result = GlobusXIOErrorParameter("name");
                break;
            }

            in_header_value = va_arg(ap, char *);
            if (in_header_value == NULL)
            {
                result = GlobusXIOErrorParameter("value");
                break;
            }

            result = globus_i_xio_http_header_info_set_header(
                    &http_handle->request_info.headers,
                    in_header_name,
                    in_header_value,
                    GLOBUS_FALSE);
            break;

        default:
            result = GlobusXIOErrorParameter("cmd");
            break;
    }
    globus_mutex_unlock(&http_handle->mutex);
    return result;
}
/* globus_i_xio_http_handle_cntl() */

/**
 * Called with mutex locked
 */
globus_result_t
globus_i_xio_http_set_end_of_entity(
    globus_i_xio_http_handle_t *        http_handle)
{
    globus_result_t                     result = GLOBUS_SUCCESS;
    globus_i_xio_http_header_info_t *   headers;
    static globus_xio_iovec_t           end_of_body_iovec = {
        .iov_base = "0\r\n\r\n", 
        .iov_len = 5
    };
    GlobusXIOName(globus_i_xio_http_set_end_of_entity);

    if (http_handle->write_operation.operation != NULL)
    {
        result = GlobusXIOErrorAlreadyRegistered();

        goto error_exit;
    }
    else if (http_handle->target_info.is_client)
    {
        headers = &http_handle->request_info.headers;
    }
    else
    {
        headers = &http_handle->response_info.headers;
    }
    
    if (http_handle->send_state == GLOBUS_XIO_HTTP_STATUS_LINE)
    {
        /* To send an empty response from server */
        result = globus_i_xio_http_server_write_response(
                http_handle,
                NULL,
                0,
                NULL);
    }
    else if (http_handle->send_state == GLOBUS_XIO_HTTP_CHUNK_BODY)
    {
        result = globus_xio_driver_operation_create(
                &http_handle->write_operation.operation,
                http_handle->handle);

        if (result != GLOBUS_SUCCESS)
        {
            goto error_exit;
        }

        result = globus_xio_driver_pass_write(
                http_handle->write_operation.operation,
                &end_of_body_iovec,
                1,
                5,
                globus_l_xio_http_write_eof_callback,
                http_handle);

        if (result != GLOBUS_SUCCESS)
        {
            globus_xio_driver_operation_destroy(
                    http_handle->write_operation.operation);
        }
        else
        {
            http_handle->send_state = GLOBUS_XIO_HTTP_EOF;
        }
    }
    else if (http_handle->send_state == GLOBUS_XIO_HTTP_IDENTITY_BODY)
    {
        http_handle->send_state = GLOBUS_XIO_HTTP_EOF;
    }

error_exit:
    return result;
}

static
void
globus_l_xio_http_write_eof_callback(
    globus_xio_operation_t              op,
    globus_result_t                     result,
    globus_size_t                       nbytes,
    void *                              user_arg)
{
    globus_i_xio_http_handle_t *        http_handle = user_arg;

    globus_mutex_lock(&http_handle->mutex);

    /* How should error be handled here? */
    globus_xio_driver_operation_destroy(op);

    http_handle->write_operation.operation = NULL;
    http_handle->write_operation.driver_handle = NULL;

    if (http_handle->close_operation != NULL)
    {
        result = globus_i_xio_http_close_internal(http_handle);
    }

    globus_mutex_unlock(&http_handle->mutex);

    return;
}
/* globus_l_xio_http_write_eof_callback() */
