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


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


#include "ngx_http_lua_script.h"


static void * ngx_http_lua_script_add_code(ngx_array_t *codes, size_t size);
static size_t ngx_http_lua_script_copy_len_code(
    ngx_http_lua_script_engine_t *e);
static void ngx_http_lua_script_copy_code(ngx_http_lua_script_engine_t *e);
static ngx_int_t ngx_http_lua_script_add_copy_code(
    ngx_http_lua_script_compile_t *sc, ngx_str_t *value, ngx_uint_t last);
static ngx_int_t ngx_http_lua_script_compile(ngx_http_lua_script_compile_t *sc);
static ngx_int_t ngx_http_lua_script_add_capture_code(
    ngx_http_lua_script_compile_t *sc, ngx_uint_t n);
static size_t ngx_http_lua_script_copy_capture_len_code(
    ngx_http_lua_script_engine_t *e);
static void ngx_http_lua_script_copy_capture_code(
    ngx_http_lua_script_engine_t *e);
static ngx_int_t ngx_http_lua_script_done(ngx_http_lua_script_compile_t *sc);
static ngx_int_t ngx_http_lua_script_init_arrays(
    ngx_http_lua_script_compile_t *sc);


ngx_int_t
ngx_http_lua_compile_complex_value(ngx_http_lua_compile_complex_value_t *ccv)
{
    ngx_str_t                  *v;
    ngx_uint_t                  i, n, nv;
    ngx_array_t                 lengths, values, *pl, *pv;

    ngx_http_lua_script_compile_t   sc;

    v = ccv->value;

    nv = 0;

    for (i = 0; i < v->len; i++) {
        if (v->data[i] == '$') {
            nv++;
        }
    }

    ccv->complex_value->value = *v;
    ccv->complex_value->lengths = NULL;
    ccv->complex_value->values = NULL;

    if (nv == 0) {
        return NGX_OK;
    }

    n = nv * (2 * sizeof(ngx_http_lua_script_copy_code_t)
                  + sizeof(ngx_http_lua_script_capture_code_t))
        + sizeof(uintptr_t);

    if (ngx_array_init(&lengths, ccv->pool, n, 1) != NGX_OK) {
        return NGX_ERROR;
    }

    n = (nv * (2 * sizeof(ngx_http_lua_script_copy_code_t)
                   + sizeof(ngx_http_lua_script_capture_code_t))
                + sizeof(uintptr_t)
                + sizeof(uintptr_t) - 1)
            & ~(sizeof(uintptr_t) - 1);

    if (ngx_array_init(&values, ccv->pool, n, 1) != NGX_OK) {
        return NGX_ERROR;
    }

    pl = &lengths;
    pv = &values;

    ngx_memzero(&sc, sizeof(ngx_http_lua_script_compile_t));

    sc.pool = ccv->pool;
    sc.log = ccv->log;
    sc.source = v;
    sc.lengths = &pl;
    sc.values = &pv;
    sc.complete_lengths = 1;
    sc.complete_values = 1;

    if (ngx_http_lua_script_compile(&sc) != NGX_OK) {
        ngx_array_destroy(&lengths);
        ngx_array_destroy(&values);
        return NGX_ERROR;
    }

    ccv->complex_value->lengths = lengths.elts;
    ccv->complex_value->values = values.elts;

    return NGX_OK;
}


ngx_int_t
ngx_http_lua_complex_value(ngx_http_request_t *r, ngx_str_t *subj,
    size_t offset, ngx_int_t count, int *cap,
    ngx_http_lua_complex_value_t *val, luaL_Buffer *luabuf)
{
    size_t                            len;
    u_char                           *p;
    ngx_http_lua_script_code_pt       code;
    ngx_http_lua_script_len_code_pt   lcode;
    ngx_http_lua_script_engine_t      e;

    if (val->lengths == NULL) {
        luaL_addlstring(luabuf, (char *) &subj->data[offset], cap[0] - offset);
        luaL_addlstring(luabuf, (char *) val->value.data, val->value.len);

        return NGX_OK;
    }

    ngx_memzero(&e, sizeof(ngx_http_lua_script_engine_t));

    e.log = r->connection->log;
    e.ncaptures = count * 2;
    e.captures = cap;
    e.captures_data = subj->data;

    e.ip = val->lengths;

    len = 0;

    while (*(uintptr_t *) e.ip) {
        lcode = *(ngx_http_lua_script_len_code_pt *) e.ip;
        len += lcode(&e);
    }

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

    e.ip = val->values;
    e.pos = p;

    while (*(uintptr_t *) e.ip) {
        code = *(ngx_http_lua_script_code_pt *) e.ip;
        code((ngx_http_lua_script_engine_t *) &e);
    }

    luaL_addlstring(luabuf, (char *) &subj->data[offset], cap[0] - offset);
    luaL_addlstring(luabuf, (char *) p, len);

    ngx_pfree(r->pool, p);

    return NGX_OK;
}


static ngx_int_t
ngx_http_lua_script_compile(ngx_http_lua_script_compile_t *sc)
{
    u_char       ch;
    ngx_str_t    name;
    ngx_uint_t   i, bracket;
    unsigned     num_var;
    ngx_uint_t   n = 0;

    if (ngx_http_lua_script_init_arrays(sc) != NGX_OK) {
        return NGX_ERROR;
    }

    for (i = 0; i < sc->source->len; /* void */ ) {

        name.len = 0;

        if (sc->source->data[i] == '$') {

            if (++i == sc->source->len) {
                goto invalid_variable;
            }

            if (sc->source->data[i] == '$') {
                name.data = &sc->source->data[i];
                i++;
                name.len++;

                if (ngx_http_lua_script_add_copy_code(sc, &name,
                                                      (i == sc->source->len))
                    != NGX_OK)
                {
                    return NGX_ERROR;
                }

                continue;
            }

            if (sc->source->data[i] >= '0' && sc->source->data[i] <= '9') {
                num_var = 1;
                n = 0;

            } else {
                num_var = 0;
            }

            if (sc->source->data[i] == '{') {
                bracket = 1;

                if (++i == sc->source->len) {
                    goto invalid_variable;
                }

                if (sc->source->data[i] >= '0' && sc->source->data[i] <= '9') {
                    num_var = 1;
                    n = 0;
                }

                name.data = &sc->source->data[i];

            } else {
                bracket = 0;
                name.data = &sc->source->data[i];
            }

            for ( /* void */ ; i < sc->source->len; i++, name.len++) {
                ch = sc->source->data[i];

                if (ch == '}' && bracket) {
                    i++;
                    bracket = 0;
                    break;
                }

                if (num_var) {
                    if (ch >= '0' && ch <= '9') {
                        n = n * 10 + (ch - '0');
                        continue;
                    }

                    break;
                }

                /* not a number variable like $1, $2, etc */

                if ((ch >= 'A' && ch <= 'Z')
                    || (ch >= 'a' && ch <= 'z')
                    || (ch >= '0' && ch <= '9')
                    || ch == '_')
                {
                    continue;
                }

                break;
            }

            if (bracket) {
                ngx_log_error(NGX_LOG_ERR, sc->log, 0,
                              "the closing bracket in \"%V\" "
                              "variable is missing", &name);
                return NGX_ERROR;
            }

            if (name.len == 0) {
                goto invalid_variable;
            }

            if (!num_var) {
                ngx_log_error(NGX_LOG_ERR, sc->log, 0,
                              "attempt to use named capturing variable "
                              "\"%V\" (named captures not supported yet)",
                              &name);

                return NGX_ERROR;
            }

            sc->variables++;

            if (ngx_http_lua_script_add_capture_code(sc, n) != NGX_OK) {
                return NGX_ERROR;
            }

            continue;
        }

        name.data = &sc->source->data[i];

        while (i < sc->source->len) {

            if (sc->source->data[i] == '$') {
                break;
            }

            i++;
            name.len++;
        }

        if (ngx_http_lua_script_add_copy_code(sc, &name, (i == sc->source->len))
            != NGX_OK)
        {
            return NGX_ERROR;
        }
    }

    return ngx_http_lua_script_done(sc);

invalid_variable:

    ngx_log_error(NGX_LOG_ERR, sc->log, 0,
                  "lua script: invalid capturing variable name found in \"%V\"",
                  sc->source);

    return NGX_ERROR;
}


static ngx_int_t
ngx_http_lua_script_add_copy_code(ngx_http_lua_script_compile_t *sc,
    ngx_str_t *value, ngx_uint_t last)
{
    size_t                            size, len;
    ngx_http_lua_script_copy_code_t  *code;

    len = value->len;

    code = ngx_http_lua_script_add_code(*sc->lengths,
                                    sizeof(ngx_http_lua_script_copy_code_t));
    if (code == NULL) {
        return NGX_ERROR;
    }

    code->code = (ngx_http_lua_script_code_pt)
                 ngx_http_lua_script_copy_len_code;
    code->len = len;

    size = (sizeof(ngx_http_lua_script_copy_code_t) + len +
            sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1);

    code = ngx_http_lua_script_add_code(*sc->values, size);
    if (code == NULL) {
        return NGX_ERROR;
    }

    code->code = ngx_http_lua_script_copy_code;
    code->len = len;

    ngx_memcpy((u_char *) code + sizeof(ngx_http_lua_script_copy_code_t),
               value->data, value->len);

    return NGX_OK;
}


static size_t
ngx_http_lua_script_copy_len_code(ngx_http_lua_script_engine_t *e)
{
    ngx_http_lua_script_copy_code_t  *code;

    code = (ngx_http_lua_script_copy_code_t *) e->ip;

    e->ip += sizeof(ngx_http_lua_script_copy_code_t);

    return code->len;
}


static void
ngx_http_lua_script_copy_code(ngx_http_lua_script_engine_t *e)
{
    u_char                           *p;
    ngx_http_lua_script_copy_code_t  *code;

    code = (ngx_http_lua_script_copy_code_t *) e->ip;

    p = e->pos;

    if (!e->skip) {
        e->pos = ngx_copy(p, e->ip + sizeof(ngx_http_lua_script_copy_code_t),
                          code->len);
    }

    e->ip += sizeof(ngx_http_lua_script_copy_code_t)
          + ((code->len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1));

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, e->log, 0,
                   "lua script copy: \"%*s\"", e->pos - p, p);
}


static ngx_int_t
ngx_http_lua_script_add_capture_code(ngx_http_lua_script_compile_t *sc,
    ngx_uint_t n)
{
    ngx_http_lua_script_capture_code_t  *code;

    code = ngx_http_lua_script_add_code(*sc->lengths,
                                  sizeof(ngx_http_lua_script_capture_code_t));
    if (code == NULL) {
        return NGX_ERROR;
    }

    code->code = (ngx_http_lua_script_code_pt)
                 ngx_http_lua_script_copy_capture_len_code;
    code->n = 2 * n;

    code = ngx_http_lua_script_add_code(*sc->values,
                                  sizeof(ngx_http_lua_script_capture_code_t));
    if (code == NULL) {
        return NGX_ERROR;
    }

    code->code = ngx_http_lua_script_copy_capture_code;
    code->n = 2 * n;

    return NGX_OK;
}


static size_t
ngx_http_lua_script_copy_capture_len_code(ngx_http_lua_script_engine_t *e)
{
    int                                  *cap;
    ngx_uint_t                            n;
    ngx_http_lua_script_capture_code_t   *code;

    code = (ngx_http_lua_script_capture_code_t *) e->ip;

    e->ip += sizeof(ngx_http_lua_script_capture_code_t);

    n = code->n;

    if (n < e->ncaptures) {
        cap = e->captures;
        return cap[n + 1] - cap[n];
    }

    return 0;
}


static void
ngx_http_lua_script_copy_capture_code(ngx_http_lua_script_engine_t *e)
{
    int                                  *cap;
    u_char                               *p, *pos;
    ngx_uint_t                            n;
    ngx_http_lua_script_capture_code_t   *code;

    code = (ngx_http_lua_script_capture_code_t *) e->ip;

    e->ip += sizeof(ngx_http_lua_script_capture_code_t);

    n = code->n;

    pos = e->pos;

    if (n < e->ncaptures) {

        cap = e->captures;
        p = e->captures_data;

        e->pos = ngx_copy(pos, &p[cap[n]], cap[n + 1] - cap[n]);
    }

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, e->log, 0,
                   "lua script capture: \"%*s\"", e->pos - pos, pos);
}


static ngx_int_t
ngx_http_lua_script_init_arrays(ngx_http_lua_script_compile_t *sc)
{
    ngx_uint_t   n;

    if (*sc->lengths == NULL) {
        n = sc->variables * (2 * sizeof(ngx_http_lua_script_copy_code_t)
                             + sizeof(ngx_http_lua_script_capture_code_t))
            + sizeof(uintptr_t);

        *sc->lengths = ngx_array_create(sc->pool, n, 1);
        if (*sc->lengths == NULL) {
            return NGX_ERROR;
        }
    }

    if (*sc->values == NULL) {
        n = (sc->variables * (2 * sizeof(ngx_http_lua_script_copy_code_t)
                              + sizeof(ngx_http_lua_script_capture_code_t))
                + sizeof(uintptr_t)
                + sizeof(uintptr_t) - 1)
            & ~(sizeof(uintptr_t) - 1);

        *sc->values = ngx_array_create(sc->pool, n, 1);
        if (*sc->values == NULL) {
            return NGX_ERROR;
        }
    }

    sc->variables = 0;

    return NGX_OK;
}


static ngx_int_t
ngx_http_lua_script_done(ngx_http_lua_script_compile_t *sc)
{
    uintptr_t   *code;

    if (sc->complete_lengths) {
        code = ngx_http_lua_script_add_code(*sc->lengths, sizeof(uintptr_t));
        if (code == NULL) {
            return NGX_ERROR;
        }

        *code = (uintptr_t) NULL;
    }

    if (sc->complete_values) {
        code = ngx_http_lua_script_add_code(*sc->values, sizeof(uintptr_t));
        if (code == NULL) {
            return NGX_ERROR;
        }

        *code = (uintptr_t) NULL;
    }

    return NGX_OK;
}


static void *
ngx_http_lua_script_add_code(ngx_array_t *codes, size_t size)
{
    return ngx_array_push_n(codes, size);
}

/* vi:set ft=c ts=4 sw=4 et fdm=marker: */
