/**
 * 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);
    }
}
/**
 * Destroy an HTTP attribute
 * @ingroup globus_i_xio_http_attr
 *
 * Frees all storage associated with an HTTP attribute. No further
 * handle controls may be called on this attribute. This is called by the XIO
 * driver via globus_xio_attr_destroy().
 *
 * @param driver_attr
 *     Void pointer to a #globus_i_xio_http_attr_t structure to be destroyed.
 *
 * @return This function always returns GLOBUS_SUCCESS.
 *
 * @see globus_i_xio_http_attr_init()
 */
globus_result_t
globus_i_xio_http_attr_destroy(
    void *                              driver_attr)
{
    globus_i_xio_http_attr_t *          attr = driver_attr;
    GlobusXIOName(globus_i_xio_http_attr_destroy);

    globus_i_xio_http_request_destroy(&attr->request);
    globus_i_xio_http_response_destroy(&attr->response);
    globus_libc_free(attr);

    return GLOBUS_SUCCESS;
}
/** Copy an HTTP attribute
 * @ingroup globus_i_xio_http_attr
 *
 * Copies all values associated with the @a src http attribute to
 * a newly allocated attribute in @a dst. If this function returns a
 * failure, then the @a dst should be considered uninitiailized.  This is
 * called by the XIO driver via globus_xio_attr_copy().
 *
 * @param dst
 *     Void ** which will be set to point to a newly allocated attribute
 *     with equivalent values to those in @a src.
 * @param src
 *     Void * pointing to a #globus_i_xio_http_attr_t which contains the
 *     attributes we want to copy.
 *
 * @return
 *     This function returns GLOBUS_SUCCESS or GLOBUS_XIO_ERROR_MEMORY itself.
 *     Other errors generated by globus_i_xio_http_request_copy() may be
 *     returned as well.
 *
 * @retval GLOBUS_SUCCESS
 *     Attribute successfully copied.
 * @retval GLOBUS_XIO_ERROR_MEMORY
 *     Attribute copy failed due to memory constraints.
 */
globus_result_t
globus_i_xio_http_attr_copy(
    void **                             dst,
    void *                              src)
{
    globus_result_t                     result;
    globus_i_xio_http_attr_t *          http_dst;
    globus_i_xio_http_attr_t *          http_src = src;
    GlobusXIOName(globus_i_xio_http_attr_copy);

    /*
     * Don't use globus_i_xio_http_request_init() here or the call to
     * globus_i_xio_http_request_copy() below will leak.
     */
    http_dst = globus_libc_malloc(sizeof(globus_i_xio_http_attr_t));
    if (http_dst == NULL)
    {
        result = GlobusXIOErrorMemory(dst);
        goto error_exit;
    }

    /* Copy request attrs */
    result = globus_i_xio_http_request_copy(
            &http_dst->request,
            &http_src->request);
    if (result != GLOBUS_SUCCESS)
    {
        goto free_http_dst_exit;
    }

    /* Copy response attrs */
    result = globus_i_xio_http_response_copy(
            &http_dst->response,
            &http_src->response);
    if (result != GLOBUS_SUCCESS)
    {
        goto free_http_dst_request_exit;
    }
    http_dst->delay_write_header = http_src->delay_write_header;

    *dst = http_dst;

    return GLOBUS_SUCCESS;
free_http_dst_request_exit:
    globus_i_xio_http_request_destroy(&http_dst->request);
free_http_dst_exit:
    globus_libc_free(http_dst);
error_exit:
    return result;
}
/**
 * Allocate and initialize an HTTP attribute
 * @ingroup globus_i_xio_http_attr
 *
 * Creates a new attribute with default values. This is called by the XIO
 * driver via globus_xio_attr_init().
 *
 * @param out_attr
 *     Pointer value will be set to point to a 
 *     newly allocated and initilized #globus_i_xio_http_attr_t
 *     structure.
 *
 * @retval GLOBUS_SUCCESS
 *     Attribute successfully initialized.
 * @retval GLOBUS_XIO_ERROR_MEMORY
 *     Initialization failed due to memory constraints.
 *
 * @see globus_i_xio_http_attr_destroy()
 */
globus_result_t
globus_i_xio_http_attr_init(
    void **                             out_attr)
{
    globus_result_t                     res;
    globus_i_xio_http_attr_t *          attr;
    GlobusXIOName(globus_i_xio_http_attr_init);

    attr = globus_libc_malloc(sizeof(globus_i_xio_http_attr_t));
    if (attr == NULL)
    {
        res = GlobusXIOErrorMemory("attr");

        goto error_exit;
    }

    res = globus_i_xio_http_request_init(&attr->request);

    if (res != GLOBUS_SUCCESS)
    {
        goto free_attr_exit;
    }
    res = globus_i_xio_http_response_init(&attr->response);

    if (res != GLOBUS_SUCCESS)
    {
        goto free_request_exit;
    }
    attr->delay_write_header = GLOBUS_FALSE;

    *out_attr = attr;
    return GLOBUS_SUCCESS;

free_request_exit:
    globus_i_xio_http_request_destroy(&attr->request);
free_attr_exit:
    globus_libc_free(attr);
error_exit:
    return res;
}
void
globus_i_xio_http_server_read_request_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_bool_t                       done;
    globus_result_t                     eof_result = GLOBUS_SUCCESS;
    globus_i_xio_http_attr_t *          descriptor;
    globus_bool_t                       registered_again = GLOBUS_FALSE;
    GlobusXIOName(globus_i_xio_http_server_read_request_callback);

    globus_mutex_lock(&http_handle->mutex);

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

    /* Haven't parsed request and headers yet */
    http_handle->read_buffer_valid += nbytes;

    result = globus_l_xio_http_server_parse_request(http_handle, &done);
    if (result == GLOBUS_SUCCESS && !done)
    {
        goto reregister_read;
    }
    else if (result != GLOBUS_SUCCESS)
    {
        goto error_exit;
    }

    /* Determine whether we should expect an entity along with the
     * request
     */
    if ((http_handle->request_info.http_version == GLOBUS_XIO_HTTP_VERSION_1_1)
            && (http_handle->request_info.headers.transfer_encoding
            == GLOBUS_XIO_HTTP_TRANSFER_ENCODING_CHUNKED))
    {
        http_handle->parse_state = GLOBUS_XIO_HTTP_CHUNK_LINE;
    }
    else if (GLOBUS_I_XIO_HTTP_HEADER_IS_CONTENT_LENGTH_SET(
                &http_handle->request_info.headers))
    {
        http_handle->parse_state = GLOBUS_XIO_HTTP_IDENTITY_BODY;
    }

    if (GLOBUS_I_XIO_HTTP_HEADER_IS_CONNECTION_CLOSE(
                &http_handle->request_info.headers))
    {
        http_handle->response_info.headers.flags |=
                GLOBUS_I_XIO_HTTP_HEADER_CONNECTION_CLOSE;
    }

    http_handle->send_state = GLOBUS_XIO_HTTP_STATUS_LINE;

    descriptor = globus_xio_operation_get_data_descriptor(op, GLOBUS_TRUE);
    if (descriptor == NULL)
    {
        result = GlobusXIOErrorMemory("descriptor");
        
        goto error_exit;
    }
    globus_i_xio_http_request_destroy(&descriptor->request);
    result = globus_i_xio_http_request_copy(
            &descriptor->request,
            &http_handle->request_info);
    if (result != GLOBUS_SUCCESS)
    {
        goto error_exit;
    }

    result = globus_i_xio_http_parse_residue(http_handle, &registered_again);

    if ((http_handle->read_operation.wait_for <= 0 && !registered_again) ||
        result != GLOBUS_SUCCESS)
    {
        if (http_handle->response_info.headers.transfer_encoding !=
                GLOBUS_XIO_HTTP_TRANSFER_ENCODING_CHUNKED &&
            GLOBUS_I_XIO_HTTP_HEADER_IS_CONTENT_LENGTH_SET(
                    &http_handle->response_info.headers) &&
            http_handle->response_info.headers.content_length == 0)
        {
            /* Synthesize EOF if we've read all of the entity content */
            result = GlobusXIOErrorEOF();
        }
        /*
         * Either we've read enough, hit end of chunk, no entity was present,
         * or pass to transport failed. Call finished_read
         */
        nbytes = http_handle->read_operation.nbytes;
        globus_libc_free(http_handle->read_operation.iov);
        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;

        globus_mutex_unlock(&http_handle->mutex);
        
        globus_xio_driver_finished_read(op, result, nbytes);

        return;
    }
    else if (registered_again)
    {
        globus_mutex_unlock(&http_handle->mutex);
        return;
    }

    /* FALLSTHROUGH */
reregister_read:
    globus_assert(op == http_handle->read_operation.operation);
    if (eof_result != GLOBUS_SUCCESS)
    {
        /* Header block wasn't complete before eof */
        result = eof_result;
        goto error_exit;
    }
    result = globus_i_xio_http_clean_read_buffer(http_handle);

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

    result = globus_xio_driver_pass_read(
            op,
            &http_handle->read_iovec,
            1,
            1,
            globus_i_xio_http_server_read_request_callback,
            http_handle);

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

    globus_mutex_unlock(&http_handle->mutex);
    return;

error_exit:
    globus_libc_free(http_handle->read_operation.iov);
    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;
    globus_mutex_unlock(&http_handle->mutex);

    globus_xio_driver_finished_read(op, result, 0);
}
/**
 * Parse an HTTP request
 * @ingroup globus_i_xio_http_server
 *
 * Parses the HTTP request line and then uses globus_i_xio_http_header_parse()
 * to parse the header bock .If the entire request header section is reqad, the
 * boolean pointed to by @a done will be modified to be GLOBUS_TRUE
 *
 * Called with mutex locked.
 *
 * @param http_handle
 * @param done
 *
 * @return
 *     This function returns GLOBUS_SUCCESS, GLOBUS_XIO_HTTP_ERROR_PARSE,  or
 *     GLOBUS_XIO_ERROR_MEMORY. Other errors may be generated from
 *     globus_i_xio_http_header_parse()
 *
 * @retval GLOBUS_SUCCESS
 *     No parsing errors occurred while parsing the status line or headers.
 *     Parsing may still be incomplete, depending on the final value of @a
 *     done.
 * @retval <driver>::GLOBUS_XIO_HTTP_ERROR_PARSE
 *     Parse error reading the HTTP request line
 * @retval GLOBUS_XIO_ERROR_MEMORY
 *     Parsing failed because of memory constraints.
 */
static
globus_result_t
globus_l_xio_http_server_parse_request(
    globus_i_xio_http_handle_t *        http_handle,
    globus_bool_t *                     done)
{
    globus_result_t                     result;
    char *                              eol;
    char *                              current_offset;
    int                                 parsed;
    int                                 rc;
    int                                 http_major;
    int                                 http_minor;
    GlobusXIOName(globus_l_xio_http_server_parse_request);

    if (http_handle->parse_state == GLOBUS_XIO_HTTP_REQUEST_LINE)
    {
        /*
         * Make sure any old request info has been freed so we don't leak here
         * when reusing a handle (or have old headers around)
         */
        globus_i_xio_http_request_destroy(&http_handle->request_info);
        result = globus_i_xio_http_request_init(&http_handle->request_info);

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

        /* Parse the request line:
         *
         * Method SP Request-URI SP HTTP-Version CRLF
         */
        current_offset = ((char *) (http_handle->read_buffer.iov_base))
                + http_handle->read_buffer_offset;

        eol = globus_i_xio_http_find_eol(
                current_offset,
                http_handle->read_buffer_valid);
        if (eol == NULL)
        {
            *done = GLOBUS_FALSE;

            return GLOBUS_SUCCESS;
        }
        *eol = '\0';

        rc = sscanf(current_offset, "%*s %n", &parsed);

        if (rc < 0)
        {
            result = GlobusXIOHttpErrorParse("Method", current_offset);

            goto error_exit;
        }

        http_handle->request_info.method = globus_libc_malloc(parsed+1);
        if (http_handle->request_info.method == NULL)
        {
            result = GlobusXIOErrorMemory("method");

            goto error_exit;
        }

        rc = sscanf(current_offset, "%s ", http_handle->request_info.method);
        globus_assert(rc == 1);

        current_offset += parsed;
        
        rc = sscanf(current_offset, "%*s %n", &parsed);
        if (rc < 0)
        {
            result = GlobusXIOHttpErrorParse("Request-URI", current_offset);

            goto error_exit;
        }

        http_handle->request_info.uri = globus_libc_malloc(parsed+1);
        if (http_handle->request_info.uri == NULL)
        {
            result = GlobusXIOErrorMemory("uri");

            goto error_exit;
        }
        rc = sscanf(current_offset, "%s ", http_handle->request_info.uri);
        globus_assert(rc == 1);

        current_offset += parsed;

        rc = sscanf(current_offset, "HTTP/%d.%d", &http_major, &http_minor);

        if (rc < 2)
        {
            result = GlobusXIOHttpErrorParse("Http-Version", current_offset);

            goto error_exit;
        }

        http_handle->request_info.http_version =
            globus_i_xio_http_guess_version(http_major, http_minor);

        /* Set current offset to end of CRLF at the end of this line */
        current_offset = eol+2;

        parsed = current_offset - ((char *) http_handle->read_buffer.iov_base
                + http_handle->read_buffer_offset);
        http_handle->read_buffer_valid -= parsed;
        http_handle->read_buffer_offset += parsed;
        http_handle->parse_state = GLOBUS_XIO_HTTP_HEADERS;
    }
    return globus_i_xio_http_header_parse(http_handle, done);

error_exit:
    parsed = current_offset - ((char *) http_handle->read_buffer.iov_base
                + http_handle->read_buffer_offset);

    /* Chop of what we managed to parse from the buffer */
    http_handle->read_buffer_valid -= parsed;
    http_handle->read_buffer_offset += parsed;

error_exit_init:
    return result;
}
/**
 * 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_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;
}