
/*
 * Copyright (C) Yichun Zhang (agentzh)
 */


#ifndef DDEBUG
#define DDEBUG 0
#endif
#include "ddebug.h"


#include "ngx_http_echo_util.h"
#include "ngx_http_echo_sleep.h"


ngx_uint_t  ngx_http_echo_content_length_hash = 0;


ngx_http_echo_ctx_t *
ngx_http_echo_create_ctx(ngx_http_request_t *r)
{
    ngx_http_echo_ctx_t         *ctx;

    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_echo_ctx_t));
    if (ctx == NULL) {
        return NULL;
    }

    ctx->sleep.handler   = ngx_http_echo_sleep_event_handler;
    ctx->sleep.data      = r;
    ctx->sleep.log       = r->connection->log;

    return ctx;
}


ngx_int_t
ngx_http_echo_eval_cmd_args(ngx_http_request_t *r,
    ngx_http_echo_cmd_t *cmd, ngx_array_t *computed_args,
    ngx_array_t *opts)
{
    unsigned                         expecting_opts = 1;
    ngx_uint_t                       i;
    ngx_array_t                     *args = cmd->args;
    ngx_str_t                       *arg, *raw, *opt;
    ngx_http_echo_arg_template_t    *value;

    value = args->elts;

    for (i = 0; i < args->nelts; i++) {
        raw = &value[i].raw_value;

        if (value[i].lengths == NULL && raw->len > 0) {
            if (expecting_opts) {
                if (raw->len == 1 || raw->data[0] != '-') {
                    expecting_opts = 0;

                } else if (raw->data[1] == '-') {
                    expecting_opts = 0;
                    continue;

                } else {
                    opt = ngx_array_push(opts);
                    if (opt == NULL) {
                        return NGX_HTTP_INTERNAL_SERVER_ERROR;
                    }

                    opt->len = raw->len - 1;
                    opt->data = raw->data + 1;

                    dd("pushing opt: %.*s", (int) opt->len, opt->data);

                    continue;
                }
            }

        } else {
            expecting_opts = 0;
        }

        arg = ngx_array_push(computed_args);
        if (arg == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        if (value[i].lengths == NULL) { /* does not contain vars */
            dd("Using raw value \"%.*s\"", (int) raw->len, raw->data);
            *arg = *raw;

        } else {
            if (ngx_http_script_run(r, arg, value[i].lengths->elts,
                                    0, value[i].values->elts)
                == NULL)
            {
                return NGX_HTTP_INTERNAL_SERVER_ERROR;
            }
        }

        dd("pushed arg: %.*s", (int) arg->len, arg->data);
    }

    return NGX_OK;
}


ngx_int_t
ngx_http_echo_send_chain_link(ngx_http_request_t *r,
    ngx_http_echo_ctx_t *ctx, ngx_chain_t *in)
{
    ngx_int_t        rc;

    rc = ngx_http_echo_send_header_if_needed(r, ctx);

    if (rc == NGX_ERROR || rc > NGX_OK || r->header_only) {
        return rc;
    }

    if (in == NULL) {

#if defined(nginx_version) && nginx_version <= 8004

        /* earlier versions of nginx does not allow subrequests
            to send last_buf themselves */
        if (r != r->main) {
            return NGX_OK;
        }

#endif

        rc = ngx_http_send_special(r, NGX_HTTP_LAST);
        if (rc >= NGX_HTTP_SPECIAL_RESPONSE) {
            return rc;
        }

        return NGX_OK;
    }

    /* FIXME we should udpate chains to recycle chain links and bufs */
    return ngx_http_output_filter(r, in);
}


ngx_int_t
ngx_http_echo_send_header_if_needed(ngx_http_request_t *r,
    ngx_http_echo_ctx_t *ctx)
{
    ngx_int_t                    rc;
    ngx_http_echo_loc_conf_t    *elcf;

    if (!r->header_sent && !ctx->header_sent) {
        elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);

        r->headers_out.status = (ngx_uint_t) elcf->status;

        if (ngx_http_set_content_type(r) != NGX_OK) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        ngx_http_clear_content_length(r);
        ngx_http_clear_accept_ranges(r);

        rc = ngx_http_send_header(r);
        ctx->header_sent = 1;
        return rc;
    }

    return NGX_OK;
}


ssize_t
ngx_http_echo_atosz(u_char *line, size_t n)
{
    ssize_t  value;

    if (n == 0) {
        return NGX_ERROR;
    }

    for (value = 0; n--; line++) {
        if (*line == '_') { /* we ignore undercores */
            continue;
        }

        if (*line < '0' || *line > '9') {
            return NGX_ERROR;
        }

        value = value * 10 + (*line - '0');
    }

    if (value < 0) {
        return NGX_ERROR;
    }

    return value;
}


/* Modified from the ngx_strlcasestrn function in ngx_string.h
 * Copyright (C) by Igor Sysoev */
u_char *
ngx_http_echo_strlstrn(u_char *s1, u_char *last, u_char *s2, size_t n)
{
    ngx_uint_t  c1, c2;

    c2 = (ngx_uint_t) *s2++;
    last -= n;

    do {
        do {
            if (s1 >= last) {
                return NULL;
            }

            c1 = (ngx_uint_t) *s1++;

        } while (c1 != c2);

    } while (ngx_strncmp(s1, s2, n) != 0);

    return --s1;
}


ngx_int_t
ngx_http_echo_post_request_at_head(ngx_http_request_t *r,
    ngx_http_posted_request_t *pr)
{
    dd_enter();

    if (pr == NULL) {
        pr = ngx_palloc(r->pool, sizeof(ngx_http_posted_request_t));
        if (pr == NULL) {
            return NGX_ERROR;
        }
    }

    pr->request = r;
    pr->next = r->main->posted_requests;
    r->main->posted_requests = pr;

    return NGX_OK;
}


u_char *
ngx_http_echo_rebase_path(ngx_pool_t *pool, u_char *src, size_t osize,
    size_t *nsize)
{
    u_char            *p, *dst;

    if (osize == 0) {
        return NULL;
    }

    if (src[0] == '/') {
        /* being an absolute path already, just add a trailing '\0' */
        *nsize = osize;

        dst = ngx_palloc(pool, *nsize + 1);
        if (dst == NULL) {
            *nsize = 0;
            return NULL;
        }

        p = ngx_copy(dst, src, osize);
        *p = '\0';

        return dst;
    }

    *nsize = ngx_cycle->prefix.len + osize;

    dst = ngx_palloc(pool, *nsize + 1);
    if (dst == NULL) {
        *nsize = 0;
        return NULL;
    }

    p = ngx_copy(dst, ngx_cycle->prefix.data, ngx_cycle->prefix.len);
    p = ngx_copy(p, src, osize);

    *p = '\0';

    return dst;
}


ngx_int_t
ngx_http_echo_flush_postponed_outputs(ngx_http_request_t *r)
{
    if (r == r->connection->data && r->postponed) {
        /* notify the downstream postpone filter to flush the postponed
         * outputs of the current request */
        return ngx_http_output_filter(r, NULL);
    }

    /* do nothing */
    return NGX_OK;
}
