/*
 * reqpool - request objects
 *
 * Copyright(c) 2020 Glenn Strauss gstrauss()gluelogic.com  All rights reserved
 * License: BSD 3-clause (same as lighttpd)
 */
#include "first.h"
#include "reqpool.h"

#include <stdlib.h>
#include <string.h>

#include "base.h"
#include "buffer.h"
#include "chunk.h"
#include "plugins.h"
#include "plugin_config.h"
#include "request.h"
#include "response.h"

#ifdef HAVE_PCRE2_H
#define PCRE2_CODE_UNIT_WIDTH 8
#include <pcre2.h>
#endif


static const request_config *request_config_defaults;


void
request_config_set_defaults (const request_config *config_defaults)
{
    request_config_defaults = config_defaults;
}


__attribute_noinline__
void
request_config_reset (request_st * const r)
{
    memcpy(&r->conf, request_config_defaults, sizeof(request_config));
}


static void
request_set_con (request_st * const r, connection * const con)
{
    r->con = con;
    r->dst_addr = &con->dst_addr;
    r->dst_addr_buf = &con->dst_addr_buf;
    /*r->tmp_buf = con->srv->tmp_buf;*/
}


void
request_init_data (request_st * const r, connection * const con, server * const srv)
{
    request_set_con(r, con);

    r->http_method = HTTP_METHOD_UNSET;
    r->http_version = HTTP_VERSION_UNSET;
  #if 0 /*(already zeroed by calloc())*/
    r->resp_fn_step = 0;
    r->resp_header_len = 0;
    r->loops_per_request = 0;
  #endif
    r->tmp_buf = srv->tmp_buf;
    r->resp_body_scratchpad = -1;
    r->server_name = &r->uri.authority;

    /* init plugin-specific per-request structures */
    r->plugin_ctx = ck_calloc(srv->plugins.used + 1, sizeof(void *));
    r->cond_cache = ck_calloc(srv->config_context->used, sizeof(cond_cache_t));

  #ifdef HAVE_PCRE
    if (srv->config_captures) {
        r->cond_captures = srv->config_captures;
        r->cond_match = ck_calloc(srv->config_captures, sizeof(cond_match_t *));
        r->cond_match_data = ck_calloc(srv->config_captures,
                                       sizeof(cond_match_t));
    }
  #endif

    chunkqueue_init(&r->write_queue);
    chunkqueue_init(&r->read_queue);
    chunkqueue_init(&r->reqbody_queue);

    request_config_reset(r);
}


__attribute_cold__
__attribute_noinline__
__attribute_returns_nonnull__
static request_st *
request_init (connection * const con)
{
    request_st * const r = ck_calloc(1, sizeof(request_st));
    request_init_data(r, con, con->srv);
    return r;
}


void
request_reset (request_st * const r)
{
    plugins_call_handle_request_reset(r);

    http_response_reset(r);

    r->resp_fn_step = 0;
    r->loops_per_request = 0;
    r->keep_alive = 0;

    memset(&r->x, 0, sizeof(r->x));
    /* clear initial members of r->x union */
    /*r->x.h1.bytes_written_ckpt = 0;*/
    /*r->x.h1.bytes_read_ckpt = 0;*/
    /*r->x.h1.te_chunked = 0;*/
    /*r->x.h2.state = 0;*/ /* H2_STATE_IDLE */
    /*r->x.h2.id = 0;*/

    r->http_method = HTTP_METHOD_UNSET;
    r->http_version = HTTP_VERSION_UNSET;

    /*con->proto_default_port = 80;*//*set to default in connection_accepted()*/

    r->http_host = NULL;
    r->reqbody_length = 0;
    r->resp_body_scratchpad = -1;
    r->rqst_htags = 0;

    r->error_handler_saved_status = 0;
    /*r->error_handler_saved_method = HTTP_METHOD_UNSET;*/
    /*(error_handler_saved_method value is not valid
     * unless error_handler_saved_status is set)*/
    r->h2_connect_ext = 0;

    buffer_clear(&r->uri.scheme);

    if (r->rqst_header_len <= BUFFER_MAX_REUSE_SIZE) {
        r->rqst_headers.used = 0;
        /* (Note: total header size not recalculated on HANDLER_COMEBACK
         *  even if other request headers changed during processing)
         * (While this might delay release of larger buffers, it is not
         *  expected to be the general case.  For those systems where it
         *  is a typical case, the larger buffers are likely to be reused) */
        buffer_clear(&r->target);
        buffer_clear(&r->pathinfo);
        /*buffer_clear(&r->target_orig);*/  /* reset later; used by mod_status*/
        /*buffer_clear(&r->uri.path);*/     /* reset later; used by mod_status*/
        /*buffer_clear(&r->uri.query);*/    /* reset later; used by mod_status*/
        /*buffer_clear(&r->uri.authority);*//* reset later; used by mod_status*/
        /*buffer_clear(&r->server_name_buf);*//* reset when used */
    }
    else {
        buffer_reset(&r->target);
        buffer_reset(&r->pathinfo);
        /*buffer_reset(&r->target_orig);*/  /* reset later; used by mod_status*/
        /*buffer_reset(&r->uri.path);*/     /* reset later; used by mod_status*/
        /*buffer_reset(&r->uri.query);*/    /* reset later; used by mod_status*/
        /*buffer_clear(&r->uri.authority);*//* reset later; used by mod_status*/
        /*buffer_clear(&r->server_name_buf);*//* reset when used */
        array_reset_data_strings(&r->rqst_headers);
    }
    r->rqst_header_len = 0;
    if (0 != r->env.used)
        array_reset_data_strings(&r->env);

    chunkqueue_reset(&r->reqbody_queue);
    /* r->read_queue, r->write_queue are shared with con for HTTP/1.1
     * but are different than con->read_queue, con->write_queue for HTTP/2
     * For HTTP/1.1, when &r->read_queue == con->read_queue, r->read_queue
     * is not cleared between requests since it might contain subsequent
     * requests.  (see also request_release()) */

    /* The cond_cache gets reset in response.c */
    /* config_cond_cache_reset(r); */

    request_config_reset(r);
}


#if 0 /* DEBUG_DEV */
__attribute_cold__
static void request_plugin_ctx_check(request_st * const r, server * const srv) {
    /* plugins should have cleaned themselves up */
    for (uint32_t i = 0, used = srv->plugins.used; i < used; ++i) {
        plugin_data_base *pd = ((plugin_data_base **)(srv->plugins.ptr))[i];
        if (NULL == r->plugin_ctx[pd->id]
            && NULL == r->con->plugin_ctx[pd->id]) continue;
        log_error(r->conf.errh, __FILE__, __LINE__,
          "missing cleanup in %s", pd->self->name);
        r->plugin_ctx[pd->id] = NULL;
        r->con->plugin_ctx[pd->id] = NULL;
    }
}
#endif


void
request_reset_ex (request_st * const r)
{
  #if 0 /* DEBUG_DEV */
    /* plugins should have cleaned themselves up (id range: [1,used]) */
    connection * const con = r->con;
    server * const srv = con->srv;
    for (uint32_t i = 1; i <= srv->plugins.used; ++i) {
        if (NULL != r->plugin_ctx[i] || NULL != con->plugin_ctx[i]) {
            request_plugin_ctx_check(r, srv);
            break;
        }
    }
  #endif

    r->server_name = &r->uri.authority;
    buffer_clear(&r->uri.authority);
    buffer_reset(&r->uri.path);
    buffer_reset(&r->uri.query);
    buffer_reset(&r->physical.path);
    buffer_reset(&r->physical.rel_path);
    buffer_reset(&r->target_orig);
    buffer_reset(&r->target);       /*(see comments in request_reset())*/
    buffer_reset(&r->pathinfo);     /*(see comments in request_reset())*/

    /* preserve; callers must handle changes */
    /*r->state = CON_STATE_CONNECT;*/
}


void
request_free_data (request_st * const r)
{
    chunkqueue_reset(&r->reqbody_queue);
    chunkqueue_reset(&r->write_queue);
    chunkqueue_reset(&r->read_queue);
    array_free_data(&r->rqst_headers);
    array_free_data(&r->resp_headers);
    array_free_data(&r->env);

    free(r->target.ptr);
    free(r->target_orig.ptr);

    free(r->uri.scheme.ptr);
    free(r->uri.authority.ptr);
    free(r->uri.path.ptr);
    free(r->uri.query.ptr);

    free(r->physical.doc_root.ptr);
    free(r->physical.path.ptr);
    free(r->physical.basedir.ptr);
    free(r->physical.rel_path.ptr);

    free(r->pathinfo.ptr);
    free(r->server_name_buf.ptr);

    free(r->plugin_ctx);
    free(r->cond_cache);
  #ifdef HAVE_PCRE
    if (r->cond_match_data) {
        for (int i = 0, used = r->cond_captures; i < used; ++i) {
          #ifdef HAVE_PCRE2_H
            if (r->cond_match_data[i].match_data)
                pcre2_match_data_free(r->cond_match_data[i].match_data);
          #else /* HAVE_PCRE_H */
            if (r->cond_match_data[i].matches)
                free(r->cond_match_data[i].matches);
          #endif
        }
        free(r->cond_match_data);
        free(r->cond_match);
    }
  #endif

    /* note: r is not zeroed here and r is not freed here */
}


/* linked list of (request_st *) cached for reuse */
static request_st *reqpool;


void
request_pool_free (void)
{
    while (reqpool) {
        request_st * const r = reqpool;
        reqpool = (request_st *)r->con; /*(reuse r->con as next ptr)*/
        request_free_data(r);
        free(r);
    }
}


static void
request_pool_push (request_st * const r)
{
    r->con = (connection *)reqpool; /*(reuse r->con as next ptr)*/
    reqpool = r;
}


static request_st *
request_pool_pop (void)
{
    /*assert(reqpool);*//*(caller should check non-NULL)*/
    request_st * const r = reqpool;
    reqpool = (request_st *)r->con; /*(reuse r->con as next ptr)*/
    return r;
}


void
request_release (request_st * const r)
{
    /* (For HTTP/1.1, r == &con->request, and so request_release() not called)
     * r->read_queue, r->write_queue are shared with con for HTTP/1.1
     * but are different than con->read_queue, con->write_queue for HTTP/2
     * For HTTP/1.1, when &r->read_queue == con->read_queue, r->read_queue
     * is not cleared between requests since it might contain subsequent
     * requests.  (see also request_reset()) */
    chunkqueue_reset(&r->read_queue);

    /*(r->cond_cache and r->cond_match are re-init in h2_init_stream())*/

    request_reset(r);
    request_reset_ex(r);
    r->state = CON_STATE_CONNECT;

    request_pool_push(r);
}


request_st *
request_acquire (connection * const con)
{
    if (!reqpool)
        return request_init(con);

    request_st * const r = request_pool_pop();
    request_set_con(r, con);
    return r;
}
