/*
 * mod_echo - test/debugging module to echo request back to client as response
 *
 * Copyright(c) 2021 Glenn Strauss gstrauss()gluelogic.com  All rights reserved
 * License: BSD 3-clause (same as lighttpd)
 *
 * Note: module is hard-coded to handle requests to the exact uri-path: "/echo"
 *
 * Note: testing GET requests with Content-Length requires lighttpd.conf:
 *   server.http-parseopts += ("method-get-body" => "enable")
 */
#include "first.h"

#include <stdlib.h>

#include "base.h"
#include "fdevent.h"
#include "http_chunk.h"
#include "plugin.h"
#include "request.h"
#include "response.h"

typedef struct {
    PLUGIN_DATA;
} plugin_data;

INIT_FUNC(mod_echo_init);
REQUEST_FUNC(mod_echo_handle_uri_clean);
REQUEST_FUNC(mod_echo_handle_subrequest);

static const plugin mod_echo_plugin = {
  .name                         = "echo",
  .version                      = LIGHTTPD_VERSION_ID,
  .init                         = mod_echo_init,
  .handle_uri_clean             = mod_echo_handle_uri_clean,
  .handle_subrequest            = mod_echo_handle_subrequest
};

INIT_FUNC(mod_echo_init) {
    plugin_data * const pd = ck_calloc(1, sizeof(plugin_data));
    pd->self = &mod_echo_plugin;
    return pd;
}

__attribute_cold__
__declspec_dllexport__
int mod_echo_plugin_init(plugin *p);
int mod_echo_plugin_init(plugin *p) {
    memcpy(p, &mod_echo_plugin, sizeof(plugin));
    return 0;
}

#if 1 /*(would be simpler if not supporting streaming w/ bufmin)*/

static handler_t mod_echo_request_body(request_st * const r) {
    chunkqueue * const cq = &r->reqbody_queue;
    chunkqueue_remove_finished_chunks(cq); /* unnecessary? */
    off_t cqlen = chunkqueue_length(cq);
    if ((r->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)
        && r->resp_body_started) {
        if (chunkqueue_length(&r->write_queue) > 65536 - 4096) {
            /* wait for more data to be sent to client */
            return HANDLER_WAIT_FOR_EVENT;
        }
        else {
            if (cqlen > 65536) {
                cqlen = 65536;
                joblist_append(r->con);
            }
        }
    }

    if (0 != http_chunk_transfer_cqlen(r, cq, (size_t)cqlen))
        return HANDLER_ERROR;

    if (cq->bytes_out == (off_t)r->reqbody_length) {
        /* sent all request body input */
        http_response_backend_done(r);
        return HANDLER_FINISHED;
    }

    cqlen = chunkqueue_length(cq);
    if (cq->bytes_in != (off_t)r->reqbody_length && cqlen < 65536 - 16384) {
        /*(r->conf.stream_request_body & FDEVENT_STREAM_REQUEST)*/
        if (!(r->conf.stream_request_body & FDEVENT_STREAM_REQUEST_POLLIN)) {
            r->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLIN;
            if (r->http_version <= HTTP_VERSION_1_1)
                r->con->is_readable = 1; /*trigger optimistic client read */
        }
    }
    return HANDLER_WAIT_FOR_EVENT;
}

REQUEST_FUNC(mod_echo_handle_subrequest) {
    UNUSED(p_d);

    handler_t rc = mod_echo_request_body(r);
    if (rc != HANDLER_WAIT_FOR_EVENT) return rc;

    chunkqueue * const cq = &r->reqbody_queue;
    if (cq->bytes_in != (off_t)r->reqbody_length) {
        /*(64k - 4k to attempt to avoid temporary files
         * in conjunction with FDEVENT_STREAM_REQUEST_BUFMIN)*/
        if (chunkqueue_length(cq) > 65536 - 4096
            && (r->conf.stream_request_body & FDEVENT_STREAM_REQUEST_BUFMIN)) {
            r->conf.stream_request_body &= ~FDEVENT_STREAM_REQUEST_POLLIN;
            return HANDLER_WAIT_FOR_EVENT;
        }
        else {
            rc = r->con->reqbody_read(r);
            if (rc != HANDLER_GO_ON) return rc;

            if (-1 == r->reqbody_length
                && !(r->conf.stream_request_body & FDEVENT_STREAM_REQUEST))
                return HANDLER_WAIT_FOR_EVENT;
        }
    }

    return mod_echo_request_body(r);
}

#else /*(would be simpler if not supporting streaming w/ bufmin (above))*/

REQUEST_FUNC(mod_echo_handle_subrequest) {
    UNUSED(p_d);

    handler_t rc = r->con->reqbody_read(r);
    if (rc != HANDLER_GO_ON) return rc;

    chunkqueue * const cq = &r->reqbody_queue;
    if (0 != http_chunk_transfer_cqlen(r, cq, chunkqueue_length(cq)))
        return HANDLER_ERROR;

    if (cq->bytes_out == (off_t)r->reqbody_length) {
        http_response_backend_done(r);
        return HANDLER_FINISHED;
    }
    return HANDLER_WAIT_FOR_EVENT;
}

#endif

REQUEST_FUNC(mod_echo_handle_uri_clean) {
    plugin_data *p = p_d;
    if (NULL == r->handler_module
        && buffer_eq_slen(&r->uri.path, CONST_STR_LEN("/echo"))) {
        r->handler_module = p->self;
        r->resp_body_started = 1;
        /* XXX: future: might echo request headers here */
        if (0 == r->reqbody_length) {
            r->resp_body_finished = 1;
            return HANDLER_FINISHED;
        }
    }
    return HANDLER_GO_ON;
}
