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


#include "ngx_http_memc_handler.h"
#include "ngx_http_memc_module.h"
#include "ngx_http_memc_request.h"
#include "ngx_http_memc_response.h"
#include "ngx_http_memc_util.h"


static ngx_int_t ngx_http_memc_flags_as_http_time_variable(
    ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data);


static ngx_http_variable_t ngx_http_memc_variables[] = {

    { ngx_string("memc_flags_as_http_time"), NULL,
      ngx_http_memc_flags_as_http_time_variable, 0,
      0, 0 },

    { ngx_null_string, NULL, NULL, 0, 0, 0 }
};


static ngx_str_t  ngx_http_memc_key = ngx_string("memc_key");
static ngx_str_t  ngx_http_memc_cmd = ngx_string("memc_cmd");
static ngx_str_t  ngx_http_memc_value = ngx_string("memc_value");
static ngx_str_t  ngx_http_memc_flags = ngx_string("memc_flags");
static ngx_str_t  ngx_http_memc_exptime = ngx_string("memc_exptime");


static ngx_int_t ngx_http_memc_add_more_variables(ngx_conf_t *cf);
static ngx_int_t ngx_http_memc_variable_not_found(ngx_http_request_t *r,
        ngx_http_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_http_memc_add_variable(ngx_conf_t *cf, ngx_str_t *name);
static ngx_flag_t ngx_http_memc_in_cmds_allowed(ngx_http_memc_loc_conf_t *mlcf,
        ngx_http_memc_cmd_t memc_cmd);
static ngx_int_t ngx_http_memc_reinit_request(ngx_http_request_t *r);
static void ngx_http_memc_abort_request(ngx_http_request_t *r);
static void ngx_http_memc_finalize_request(ngx_http_request_t *r,
    ngx_int_t rc);
static ngx_flag_t ngx_http_memc_valid_uint32_str(u_char *data, size_t len);
static ngx_flag_t ngx_http_memc_valid_uint64_str(u_char *data, size_t len);


ngx_int_t
ngx_http_memc_handler(ngx_http_request_t *r)
{
    ngx_int_t                       rc;
    ngx_http_upstream_t            *u;
    ngx_http_memc_ctx_t            *ctx;
    ngx_http_memc_loc_conf_t       *mlcf;
    ngx_http_memc_main_conf_t      *mmcf;
    ngx_str_t                       target;
    ngx_url_t                       url;

    ngx_http_variable_value_t      *cmd_vv;
    ngx_http_variable_value_t      *key_vv;
    ngx_http_variable_value_t      *value_vv;
    ngx_http_variable_value_t      *flags_vv;
    ngx_http_variable_value_t      *exptime_vv;

    ngx_http_memc_cmd_t             memc_cmd;
    ngx_flag_t                      is_storage_cmd = 0;

    dd("memc handler");

    mmcf = ngx_http_get_module_main_conf(r, ngx_http_memc_module);

    key_vv = ngx_http_get_indexed_variable(r, mmcf->key_index);
    if (key_vv == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    cmd_vv = ngx_http_get_indexed_variable(r, mmcf->cmd_index);
    if (cmd_vv == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (cmd_vv->not_found || cmd_vv->len == 0) {
        dd("variable $memc_cmd not found");
        cmd_vv->not_found = 0;
        cmd_vv->valid = 1;
        cmd_vv->no_cacheable = 0;

        if (r->method & (NGX_HTTP_GET|NGX_HTTP_HEAD)) {
            cmd_vv->len = sizeof("get") - 1;
            cmd_vv->data = (u_char*) "get";
            memc_cmd = ngx_http_memc_cmd_get;

        } else if (r->method == NGX_HTTP_POST) {
            cmd_vv->len = sizeof("add") - 1;
            cmd_vv->data = (u_char*) "add";
            memc_cmd = ngx_http_memc_cmd_add;
            is_storage_cmd = 1;

        } else if (r->method == NGX_HTTP_PUT) {
            cmd_vv->len = sizeof("set") - 1;
            cmd_vv->data = (u_char*) "set";
            memc_cmd = ngx_http_memc_cmd_set;
            is_storage_cmd = 1;

        } else if (r->method == NGX_HTTP_DELETE) {
            cmd_vv->len = sizeof("delete") - 1;
            cmd_vv->data = (u_char*) "delete";
            memc_cmd = ngx_http_memc_cmd_delete;

        } else {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "ngx_memc: $memc_cmd variable not found for HTTP "
                          "%V requests", &r->method_name);

            return NGX_HTTP_BAD_REQUEST;
        }

    } else {

        memc_cmd = ngx_http_memc_parse_cmd(cmd_vv->data, cmd_vv->len,
                                           &is_storage_cmd);

        if (memc_cmd == ngx_http_memc_cmd_unknown) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "ngx_memc: unknown $memc_cmd \"%v\"", cmd_vv);
            return NGX_HTTP_BAD_REQUEST;
        }
    }

    mlcf = ngx_http_get_module_loc_conf(r, ngx_http_memc_module);

    dd("XXX connect timeout %d", (int) mlcf->upstream.connect_timeout);

    if (!ngx_http_memc_in_cmds_allowed(mlcf, memc_cmd)) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "ngx_memc: memcached command \"%v\" not allowed",
                      cmd_vv);

        return NGX_HTTP_FORBIDDEN;
    }

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

#if defined(nginx_version) && \
    ((nginx_version >= 7063 && nginx_version < 8000) \
     || nginx_version >= 8007)

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

    u = r->upstream;

#else /* 0.7.x < 0.7.63, 0.8.x < 0.8.7 */

    u = ngx_pcalloc(r->pool, sizeof(ngx_http_upstream_t));
    if (u == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    u->peer.log = r->connection->log;
    u->peer.log_error = NGX_ERROR_ERR;
#  if (NGX_THREADS)
    u->peer.lock = &r->connection->lock;
#  endif

    r->upstream = u;

#endif

    if (mlcf->complex_target) {
        /* variables used in the memc_pass directive */
        if (ngx_http_complex_value(r, mlcf->complex_target, &target)
            != NGX_OK)
        {
            dd("failed to compile");
            return NGX_ERROR;
        }

        if (target.len == 0) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "memc: handler: empty \"memc_pass\" target");
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        url.host = target;
        url.port = 0;
        url.no_resolve = 1;

        mlcf->upstream.upstream = ngx_http_memc_upstream_add(r, &url);

        if (mlcf->upstream.upstream == NULL) {
            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                          "memc: upstream \"%V\" not found", &target);
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }
    }

    u->schema.len = sizeof("memcached://") - 1;
    u->schema.data = (u_char *) "memcached://";

    u->output.tag = (ngx_buf_tag_t) &ngx_http_memc_module;

    u->conf = &mlcf->upstream;

    ctx = ngx_palloc(r->pool, sizeof(ngx_http_memc_ctx_t));
    if (ctx == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    ctx->body_length = 0;
    ctx->memc_key_vv = key_vv;
    ctx->memc_value_vv = NULL;
    ctx->memc_flags_vv = NULL;

    ctx->parser_state = NGX_ERROR;

    ctx->rest = NGX_HTTP_MEMC_END;
    ctx->request = r;

    ctx->cmd_str.data = cmd_vv->data;
    ctx->cmd_str.len  = cmd_vv->len;

    ctx->cmd = memc_cmd;

    ctx->is_storage_cmd = is_storage_cmd;

    ngx_http_set_ctx(r, ctx, ngx_http_memc_module);

    if (is_storage_cmd) {
        u->create_request = ngx_http_memc_create_storage_cmd_request;
        u->process_header = ngx_http_memc_process_simple_header;

        u->input_filter_init = ngx_http_memc_empty_filter_init;
        u->input_filter = ngx_http_memc_empty_filter;

    } else if (memc_cmd == ngx_http_memc_cmd_get) {
        u->create_request = ngx_http_memc_create_get_cmd_request;
        u->process_header = ngx_http_memc_process_get_cmd_header;

        u->input_filter_init = ngx_http_memc_get_cmd_filter_init;
        u->input_filter = ngx_http_memc_get_cmd_filter;

    } else if (memc_cmd == ngx_http_memc_cmd_flush_all) {
        u->create_request = ngx_http_memc_create_flush_all_cmd_request;
        u->process_header = ngx_http_memc_process_simple_header;

        u->input_filter_init = ngx_http_memc_empty_filter_init;
        u->input_filter = ngx_http_memc_empty_filter;

    } else if (memc_cmd == ngx_http_memc_cmd_version
            || memc_cmd == ngx_http_memc_cmd_stats)
    {
        u->create_request = ngx_http_memc_create_noarg_cmd_request;
        u->process_header = ngx_http_memc_process_simple_header;

        u->input_filter_init = ngx_http_memc_empty_filter_init;
        u->input_filter = ngx_http_memc_empty_filter;

    } else if (memc_cmd == ngx_http_memc_cmd_delete) {
        u->create_request = ngx_http_memc_create_delete_cmd_request;
        u->process_header = ngx_http_memc_process_simple_header;

        u->input_filter_init = ngx_http_memc_empty_filter_init;
        u->input_filter = ngx_http_memc_empty_filter;

    } else if (memc_cmd == ngx_http_memc_cmd_incr
            || memc_cmd == ngx_http_memc_cmd_decr) {
        u->create_request = ngx_http_memc_create_incr_decr_cmd_request;
        u->process_header = ngx_http_memc_process_simple_header;

        u->input_filter_init = ngx_http_memc_empty_filter_init;
        u->input_filter = ngx_http_memc_empty_filter;

    } else {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
            "assertion failed: command \"%v\" does not have proper "
            "handlers.", cmd_vv);

        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    u->reinit_request = ngx_http_memc_reinit_request;
    u->abort_request = ngx_http_memc_abort_request;
    u->finalize_request = ngx_http_memc_finalize_request;

    u->input_filter_ctx = ctx;

    if (is_storage_cmd
            || memc_cmd == ngx_http_memc_cmd_flush_all
            || memc_cmd == ngx_http_memc_cmd_delete)
    {
        exptime_vv = ngx_http_get_indexed_variable(r, mmcf->exptime_index);

        if (exptime_vv == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        ctx->memc_exptime_vv = exptime_vv;

        if (!exptime_vv->not_found
            && exptime_vv->len
            && !ngx_http_memc_valid_uint32_str(exptime_vv->data,
                                               exptime_vv->len))
        {
            return NGX_HTTP_BAD_REQUEST;
        }
    }

    if (is_storage_cmd || memc_cmd == ngx_http_memc_cmd_get) {
        flags_vv = ngx_http_get_indexed_variable(r, mmcf->flags_index);
        if (flags_vv == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        ctx->memc_flags_vv = flags_vv;

        if (is_storage_cmd
            && !flags_vv->not_found
            && flags_vv->len
            && !ngx_http_memc_valid_uint32_str(flags_vv->data, flags_vv->len))
        {
            return NGX_HTTP_BAD_REQUEST;
        }
    }

    if (is_storage_cmd
        || memc_cmd == ngx_http_memc_cmd_incr
        || memc_cmd == ngx_http_memc_cmd_decr)
    {
        value_vv = ngx_http_get_indexed_variable(r, mmcf->value_index);
        if (value_vv == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        if (memc_cmd == ngx_http_memc_cmd_incr
            || memc_cmd == ngx_http_memc_cmd_decr)
        {
            if (value_vv->not_found || value_vv->len == 0) {
                ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                              "the \"$memc_value\" variable is required for "
                              "command \"%V\"", &ctx->cmd_str);

                return NGX_HTTP_BAD_REQUEST;
            }

            if (!ngx_http_memc_valid_uint64_str(value_vv->data,
                                                value_vv->len))
            {
                return NGX_HTTP_BAD_REQUEST;
            }
        }

        ctx->memc_value_vv = value_vv;
    }

    rc = ngx_http_read_client_request_body(r, ngx_http_upstream_init);

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

    return NGX_DONE;
}


static ngx_int_t
ngx_http_memc_reinit_request(ngx_http_request_t *r)
{
    return NGX_OK;
}


static void
ngx_http_memc_abort_request(ngx_http_request_t *r)
{
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "abort http memcached request");
    return;
}


static void
ngx_http_memc_finalize_request(ngx_http_request_t *r, ngx_int_t rc)
{
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "finalize http memcached request");
    return;
}


static ngx_flag_t
ngx_http_memc_in_cmds_allowed(ngx_http_memc_loc_conf_t *mlcf,
    ngx_http_memc_cmd_t memc_cmd)
{
    ngx_uint_t                   i;
    ngx_http_memc_cmd_t         *value;

    if (mlcf->cmds_allowed == NULL || mlcf->cmds_allowed->nelts == 0) {
        /* by default, all the memcached commands supported are allowed. */
        return 1;
    }

    value = mlcf->cmds_allowed->elts;

    for (i = 0; i < mlcf->cmds_allowed->nelts; i++) {
        if (memc_cmd == value[i]) {
            return 1;
        }
    }

    return 0;
}


static ngx_flag_t
ngx_http_memc_valid_uint32_str(u_char *data, size_t len)
{
    u_char              *p, *last;

    if (len > NGX_UINT32_LEN) {
        return 0;
    }

    last = data + len;
    for (p = data; p != last; p++) {
        if (*p < '0' || *p > '9') {
            return 0;
        }
    }

    return 1;
}


static ngx_flag_t
ngx_http_memc_valid_uint64_str(u_char *data, size_t len)
{
    u_char              *p, *last;

    if (len > NGX_UINT64_LEN) {
        return 0;
    }

    last = data + len;
    for (p = data; p != last; p++) {
        if (*p < '0' || *p > '9') {
            return 0;
        }
    }

    return 1;
}


ngx_int_t
ngx_http_memc_init(ngx_conf_t *cf)
{
    ngx_http_memc_main_conf_t       *mmcf;

    mmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_memc_module);

    if (!mmcf->module_used) {
        return NGX_OK;
    }

    mmcf->key_index = ngx_http_memc_add_variable(cf, &ngx_http_memc_key);
    if (mmcf->key_index == NGX_ERROR) {
        return NGX_ERROR;
    }

    mmcf->cmd_index = ngx_http_memc_add_variable(cf, &ngx_http_memc_cmd);
    if (mmcf->cmd_index == NGX_ERROR) {
        return NGX_ERROR;
    }

    mmcf->flags_index = ngx_http_memc_add_variable(cf, &ngx_http_memc_flags);
    if (mmcf->flags_index == NGX_ERROR) {
        return NGX_ERROR;
    }

    mmcf->exptime_index = ngx_http_memc_add_variable(cf,
                                                     &ngx_http_memc_exptime);
    if (mmcf->exptime_index == NGX_ERROR) {
        return NGX_ERROR;
    }

    mmcf->value_index = ngx_http_memc_add_variable(cf, &ngx_http_memc_value);
    if (mmcf->value_index == NGX_ERROR) {
        return NGX_ERROR;
    }

    return ngx_http_memc_add_more_variables(cf);
}


static ngx_int_t
ngx_http_memc_add_variable(ngx_conf_t *cf, ngx_str_t *name)
{
    ngx_http_variable_t         *v;

    v = ngx_http_add_variable(cf, name, NGX_HTTP_VAR_CHANGEABLE);
    if (v == NULL) {
        return NGX_ERROR;
    }

    v->get_handler = ngx_http_memc_variable_not_found;
    return ngx_http_get_variable_index(cf, name);
}


static ngx_int_t
ngx_http_memc_variable_not_found(ngx_http_request_t *r,
    ngx_http_variable_value_t *v, uintptr_t data)
{
    v->not_found = 1;
    return NGX_OK;
}


static ngx_int_t
ngx_http_memc_add_more_variables(ngx_conf_t *cf)
{
    ngx_http_variable_t *var, *v;
    for (v = ngx_http_memc_variables; v->name.len; v++) {
        var = ngx_http_add_variable(cf, &v->name, v->flags);
        if (var == NULL) {
            return NGX_ERROR;
        }
        var->get_handler = v->get_handler;
        var->data = v->data;
    }
    return NGX_OK;
}


static ngx_int_t
ngx_http_memc_flags_as_http_time_variable(
    ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data)
{
    u_char                      *p;
    size_t                       len;
    time_t                       flags_time = 0;
    ngx_http_memc_ctx_t         *ctx;

    ngx_http_variable_value_t   *flags_vv;

    ctx = ngx_http_get_module_ctx(r, ngx_http_memc_module);

    flags_vv = ctx->memc_flags_vv;
    if (flags_vv == NULL) {
        goto not_found;
    }

    if (flags_vv->not_found || flags_vv->len == 0) {
        goto not_found;
    }

    flags_time = ngx_atotm(flags_vv->data, flags_vv->len);
    if (flags_time == NGX_ERROR) {
        return NGX_ERROR;
    }

    len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1;

    p = ngx_pnalloc(r->pool, len);
    if (p == NULL) {
        return NGX_ERROR;
    }

    ngx_http_time(p, flags_time);

    v->len = len;
    v->data = p;

    v->valid = 1;
    v->not_found = 0;
    v->no_cacheable = 0;

    return NGX_OK;

not_found:

    v->len = 0;
    v->data = NULL;
    v->valid = 1;
    v->not_found = 1;

    return NGX_OK;
}
