view src/http/modules/ngx_http_rewrite_module.c @ 54:bcb5fce0b038 NGINX_0_1_27

nginx 0.1.27 *) Feature: the "blocked" parameter of the "valid_referers" directive. *) Change: the errors while handling the request header now logged at "info" level. The server name and the "Host" and "Referer" header lines also logged. *) Change: the "Host" header line is also logged in error log. *) Feature: the proxy_pass_unparsed_uri directive. The special handling of the "://" symbols in URI, appeared in 0.1.11 version, now is canceled. *) Bugfix: nginx could not be built on FreeBSD and Linux, if the --without-ngx_http_auth_basic_module configuration parameter was used.
author Igor Sysoev <http://sysoev.ru>
date Mon, 28 Mar 2005 00:00:00 +0400
parents 72eb30262aac
children 3050baa54a26
line wrap: on
line source


/*
 * Copyright (C) Igor Sysoev
 */


#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>


typedef struct ngx_http_rewrite_engine_s  ngx_http_rewrite_engine_t;

typedef void (*ngx_http_rewrite_code_pt) (ngx_http_rewrite_engine_t *e);


typedef struct {
    ngx_str_t                     name;
    ngx_uint_t                    wildcard;
} ngx_http_rewrite_referer_t;


typedef struct {
    ngx_str_t                    *name;
    ngx_http_variable_value_t    *value;
} ngx_http_rewrite_variable_t;


typedef struct {
    ngx_array_t                  *codes;        /* uintptr_t */
    ngx_array_t                  *referers;     /* ngx_http_rewrite_referer_t */

    ngx_uint_t                    max_captures;
    ngx_uint_t                    stack_size;

    ngx_flag_t                    log;

    ngx_flag_t                    no_referer;
    ngx_flag_t                    blocked_referer;
} ngx_http_rewrite_loc_conf_t;


typedef struct {
    ngx_http_rewrite_code_pt      code;
    ngx_regex_t                  *regex;
    uintptr_t                     size;
    uintptr_t                     ncaptures;
    uintptr_t                     status;
    uintptr_t                     next;

    uintptr_t                     test:1;
    uintptr_t                     uri:1;

    /* add the r->args to the new arguments */
    uintptr_t                     args:1;

    uintptr_t                     redirect:1;
    uintptr_t                     break_cycle:1;

    ngx_str_t                     name;
} ngx_http_rewrite_regex_code_t;


typedef struct {
    ngx_http_rewrite_code_pt      code;

    uintptr_t                     uri:1;

    /* add the r->args to the new arguments */
    uintptr_t                     args:1;

    uintptr_t                     redirect:1;
} ngx_http_rewrite_regex_end_code_t;

typedef struct {
    ngx_http_rewrite_code_pt      code;
    uintptr_t                     n;
} ngx_http_rewrite_copy_capture_code_t;


typedef struct {
    ngx_http_rewrite_code_pt      code;
    uintptr_t                     len;
} ngx_http_rewrite_copy_code_t;


typedef struct {
    ngx_http_rewrite_code_pt      code;
    uintptr_t                     status;
    uintptr_t                     null;
} ngx_http_rewrite_return_code_t;


typedef struct {
    ngx_http_rewrite_code_pt      code;
    uintptr_t                     next;
    void                        **loc_conf;
} ngx_http_rewrite_if_code_t;


typedef struct {
    ngx_http_rewrite_code_pt      code;
    uintptr_t                     value;
    uintptr_t                     text_len;
    uintptr_t                     text_data;
} ngx_http_rewrite_value_code_t;


typedef struct {
    ngx_http_rewrite_code_pt      code;
    uintptr_t                     index;
} ngx_http_rewrite_var_code_t;


struct ngx_http_rewrite_engine_s {
    u_char                       *ip;
    ngx_http_variable_value_t    *sp;

    ngx_str_t                     buf;
    ngx_str_t                    *line;

    u_char                       *pos;

    /* the start of the rewritten arguments */
    u_char                       *args;

    unsigned                      quote:1;

    ngx_int_t                     status;

    int                          *captures;

    ngx_http_request_t           *request;
    ngx_http_rewrite_loc_conf_t  *conf;
};


static ngx_int_t ngx_http_rewrite_init(ngx_cycle_t *cycle);
static void *ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_rewrite_merge_loc_conf(ngx_conf_t *cf,
    void *parent, void *child);
static char *ngx_http_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static char *ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd,
    void *conf);
static char *ngx_http_rewrite_if(ngx_conf_t *cf, ngx_command_t *cmd,
    void *conf);
static char * ngx_http_rewrite_if_condition(ngx_conf_t *cf,
    ngx_http_rewrite_loc_conf_t *lcf);
static char *ngx_http_rewrite_variable(ngx_conf_t *cf,
    ngx_http_rewrite_loc_conf_t *lcf, ngx_str_t *value);
static char *ngx_http_rewrite_valid_referers(ngx_conf_t *cf,
    ngx_command_t *cmd, void *conf);
static char * ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd,
    void *conf);
static void *ngx_http_rewrite_start_code(ngx_pool_t *pool,
    ngx_array_t **codes, size_t size);
static void *ngx_http_rewrite_add_code(ngx_array_t *codes, size_t size,
    void *code);


static ngx_command_t  ngx_http_rewrite_commands[] = {

    { ngx_string("rewrite"),
      NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                       |NGX_CONF_TAKE23,
      ngx_http_rewrite,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("return"),
      NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                       |NGX_CONF_TAKE1,
      ngx_http_rewrite_return,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("if"),
      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_1MORE,
      ngx_http_rewrite_if,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("valid_referers"),
      NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
      ngx_http_rewrite_valid_referers,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("set"),
      NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                       |NGX_CONF_TAKE2,
      ngx_http_rewrite_set,
      NGX_HTTP_LOC_CONF_OFFSET,
      0,
      NULL },

    { ngx_string("rewrite_log"),
      NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                       |NGX_CONF_TAKE1,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_rewrite_loc_conf_t, log),
      NULL },

      ngx_null_command
};


ngx_http_module_t  ngx_http_rewrite_module_ctx = {
    NULL,                                  /* pre conf */

    NULL,                                  /* create main configuration */
    NULL,                                  /* init main configuration */

    NULL,                                  /* create server configuration */
    NULL,                                  /* merge server configuration */

    ngx_http_rewrite_create_loc_conf,      /* create location configration */
    ngx_http_rewrite_merge_loc_conf        /* merge location configration */
};


ngx_module_t  ngx_http_rewrite_module = {
    NGX_MODULE,
    &ngx_http_rewrite_module_ctx,          /* module context */ 
    ngx_http_rewrite_commands,             /* module directives */
    NGX_HTTP_MODULE,                       /* module type */
    ngx_http_rewrite_init,                 /* init module */
    NULL                                   /* init process */
};


#define ngx_http_rewrite_exit  (u_char *) &ngx_http_rewrite_exit_code

uintptr_t ngx_http_rewrite_exit_code = (uintptr_t) NULL;

static ngx_http_variable_value_t  ngx_http_rewrite_null_value =
                                                        { 0, ngx_string("") };


static ngx_int_t
ngx_http_rewrite_handler(ngx_http_request_t *r)
{
    ngx_http_rewrite_code_pt      code;
    ngx_http_rewrite_engine_t    *e;
    ngx_http_rewrite_loc_conf_t  *cf;

    cf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module);

    if (cf->codes == NULL) {
        return NGX_DECLINED;
    }

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

    e->sp = ngx_palloc(r->pool,
                       cf->stack_size * sizeof(ngx_http_variable_value_t));
    if (e->sp == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (cf->max_captures) {
        e->captures = ngx_palloc(r->pool, cf->max_captures * sizeof(int));
        if (e->captures == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

    } else {
        e->captures = NULL;
    }

    e->ip = cf->codes->elts;
    e->buf.len = 0;
    e->buf.data = NULL;
    e->line = NULL;
    e->pos = NULL;
    e->args = NULL;
    e->quote = 1;
    e->status = NGX_DECLINED;
    e->request = r;
    e->conf = cf;

    while (*(uintptr_t *) e->ip) {
        code = *(ngx_http_rewrite_code_pt *) e->ip;
        code(e);
    }

    return e->status;
}


static void
ngx_http_rewrite_regex_start_code(ngx_http_rewrite_engine_t *e)
{
    ngx_int_t                       rc;
    ngx_uint_t                      n;
    ngx_http_request_t             *r;
    ngx_http_rewrite_regex_code_t  *code;

    code = (ngx_http_rewrite_regex_code_t *) e->ip;

    r = e->request;

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http rewrite regex: \"%V\"", &code->name);

    if (code->uri) {
        e->line = &r->uri;
    } else {
        e->sp--;
        e->line = &e->sp->text;
    }

    rc = ngx_regex_exec(code->regex, e->line, e->captures, code->ncaptures);

    if (rc == NGX_REGEX_NO_MATCHED) {
        if (e->conf->log) {
            ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
                          "\"%V\" does not match \"%V\"", &code->name, e->line);
        }

        if (code->test) {
            e->sp->value = 0;
            e->sp->text.len = 0;
            e->sp->text.data = (u_char *) "";
            e->sp++;

            e->ip += sizeof(ngx_http_rewrite_regex_code_t);
            return;
        }

        e->ip += code->next;
        return;
    }

    if (rc < 0) {
        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                      ngx_regex_exec_n " failed: %d on \"%V\" using \"%V\"",
                      rc, e->line, &code->name);

        e->ip = ngx_http_rewrite_exit;
        e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
        return;
    }

    if (e->conf->log) {
        ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
                      "\"%V\" matches \"%V\"", &code->name, e->line);
    }

    if (code->test) {
        e->sp->value = 1;
        e->sp->text.len = 1;
        e->sp->text.data = (u_char *) "1";
        e->sp++;

        e->ip += sizeof(ngx_http_rewrite_regex_code_t);
        return;
    }

    if (code->status) {
        e->status = code->status;

        if (!code->redirect) {
            e->ip = ngx_http_rewrite_exit;
            return;
        }
    }

    e->buf.len = code->size;

    if (code->uri) {
        if (!code->break_cycle) {
            r->uri_changed = 1;
            r->valid_unparsed_uri = 1;
        }

        if (rc && (r->quoted_uri || r->plus_in_uri)) {
            e->buf.len += 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len,
                                             NGX_ESCAPE_ARGS);
        }
    }

    for (n = 1; n < (ngx_uint_t) rc; n++) {
        e->buf.len += e->captures[2 * n + 1] - e->captures[2 * n];
    }

    if (code->args && r->args.len) {
        e->buf.len += r->args.len + 1;
    }

    e->buf.data = ngx_palloc(r->pool, e->buf.len);
    if (e->buf.data == NULL) {
        e->ip = ngx_http_rewrite_exit;
        e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
        return;
    }

    e->quote = code->redirect;

    e->pos = e->buf.data;

    e->ip += sizeof(ngx_http_rewrite_regex_code_t);
}


static void
ngx_http_rewrite_regex_end_code(ngx_http_rewrite_engine_t *e)
{
    ngx_http_request_t                 *r;
    ngx_http_rewrite_regex_end_code_t  *code;

    code = (ngx_http_rewrite_regex_end_code_t *) e->ip;

    r = e->request;

    e->quote = 0;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http rewrite regex end");

    if (e->args) {
        e->buf.len = e->args - e->buf.data;

        if (code->args && r->args.len) {
            *e->pos++ = '&';
            e->pos = ngx_cpymem(e->pos, r->args.data, r->args.len);
        }

        r->args.len = e->pos - e->args;
        r->args.data = e->args;

        e->args = NULL;

    } else {
        if (code->args && r->args.len) {
            *e->pos++ = '?';
            e->pos = ngx_cpymem(e->pos, r->args.data, r->args.len);
        }

        e->buf.len = e->pos - e->buf.data;
    }

    if (!code->redirect) {
        if (e->conf->log) {
            ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
                          "rewritten data: \"%V\", args: \"%V\"",
                          &e->buf, &r->args);
        }

        if (code->uri) {
            r->uri = e->buf;

            if (ngx_http_set_exten(r) != NGX_OK) {
                e->ip = ngx_http_rewrite_exit;
                e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
                return;
            }
        }

        e->ip += sizeof(ngx_http_rewrite_regex_end_code_t);
        return;
    }

    ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0,
                  "rewritten redirect: \"%V\"", &e->buf);

    r->headers_out.location = ngx_list_push(&r->headers_out.headers);
    if (r->headers_out.location == NULL) {
        e->ip = ngx_http_rewrite_exit;
        e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
        return;
    }

    if (e->buf.data[0] != '/') {
        r->headers_out.location->key.len = sizeof("Location") - 1;
        r->headers_out.location->key.data = (u_char *) "Location";
    }

    r->headers_out.location->value = e->buf;

    e->ip += sizeof(ngx_http_rewrite_regex_end_code_t);
}


static void
ngx_http_rewrite_copy_capture_code(ngx_http_rewrite_engine_t *e)
{
    ngx_http_rewrite_copy_capture_code_t  *code;

    code = (ngx_http_rewrite_copy_capture_code_t *) e->ip;

    e->ip += sizeof(ngx_http_rewrite_copy_capture_code_t);

    if ((e->args || e->quote)
        && (e->request->quoted_uri || e->request->plus_in_uri))
    {
        e->pos = (u_char *) ngx_escape_uri(e->pos,
                                &e->line->data[e->captures[code->n]],
                                e->captures[code->n + 1] - e->captures[code->n],
                                NGX_ESCAPE_ARGS);
    } else {
        e->pos = ngx_cpymem(e->pos, &e->line->data[e->captures[code->n]],
                        e->captures[code->n + 1] - e->captures[code->n]);
    }

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
                   "http rewrite capture: \"%V\"", &e->buf);
}


static void
ngx_http_rewrite_copy_code(ngx_http_rewrite_engine_t *e)
{
    ngx_http_rewrite_copy_code_t  *code;

    code = (ngx_http_rewrite_copy_code_t *) e->ip;

    e->pos = ngx_cpymem(e->pos, e->ip + sizeof(ngx_http_rewrite_copy_code_t),
                        code->len);

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

    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
                   "http rewrite copy: \"%V\"", &e->buf);
}


static void
ngx_http_rewrite_start_args_code(ngx_http_rewrite_engine_t *e)
{
    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
                   "http rewrite args");

    e->args = e->pos;
    e->ip += sizeof(uintptr_t);
}


static void
ngx_http_rewrite_return_code(ngx_http_rewrite_engine_t *e)
{
    ngx_http_rewrite_return_code_t  *code;

    code = (ngx_http_rewrite_return_code_t *) e->ip;

    e->status = code->status;

    e->ip += sizeof(ngx_http_rewrite_return_code_t) - sizeof(uintptr_t);
}


static void
ngx_http_rewrite_if_code(ngx_http_rewrite_engine_t *e)
{
    ngx_http_rewrite_if_code_t  *code;

    code = (ngx_http_rewrite_if_code_t *) e->ip;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
                   "http rewrite if");

    e->sp--;

    if (e->sp->value) {
        if (code->loc_conf) {
            e->request->loc_conf = code->loc_conf;
        }

        e->ip += sizeof(ngx_http_rewrite_if_code_t);
        return;
    }

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
                   "http rewrite if false");

    e->ip += code->next;
}


static void
ngx_http_rewrite_value_code(ngx_http_rewrite_engine_t *e)
{
    ngx_http_rewrite_value_code_t  *code;

    code = (ngx_http_rewrite_value_code_t *) e->ip;

    e->ip += sizeof(ngx_http_rewrite_value_code_t);

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
                   "http rewrite value");

    e->sp->value = (ngx_uint_t) code->value;
    e->sp->text.len = (size_t) code->text_len;
    e->sp->text.data = (u_char *) code->text_data;
    e->sp++;
}


static void
ngx_http_rewrite_set_var_code(ngx_http_rewrite_engine_t *e)
{
    ngx_http_request_t           *r;
    ngx_http_variable_value_t    *value;
    ngx_http_core_main_conf_t    *cmcf;
    ngx_http_rewrite_var_code_t  *code;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
                   "http rewrite set var");

    code = (ngx_http_rewrite_var_code_t *) e->ip;

    e->ip += sizeof(ngx_http_rewrite_var_code_t);

    r = e->request;

    if (r->variables == NULL) {
        cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

        r->variables = ngx_pcalloc(r->pool, cmcf->variables.nelts
                                        * sizeof(ngx_http_variable_value_t *));
        if (r->variables == NULL) {
            e->ip = ngx_http_rewrite_exit;
            e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
            return;
        }
    }

    value = ngx_palloc(r->pool, sizeof(ngx_http_variable_value_t));
    if (value == NULL) {
        e->ip = ngx_http_rewrite_exit;
        e->status = NGX_HTTP_INTERNAL_SERVER_ERROR;
        return;
    }

    e->sp--;

    *value = *e->sp;

    r->variables[code->index] = value;
}


static void
ngx_http_rewrite_var_code(ngx_http_rewrite_engine_t *e)
{
    ngx_http_variable_value_t    *value;
    ngx_http_rewrite_var_code_t  *code;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
                   "http rewrite var");

    code = (ngx_http_rewrite_var_code_t *) e->ip;

    e->ip += sizeof(ngx_http_rewrite_var_code_t);

    value = ngx_http_get_indexed_variable(e->request, code->index);

    if (value == NULL || value == NGX_HTTP_VARIABLE_NOT_FOUND) {
        e->sp->value = 0;
        e->sp->text.len = 0;
        e->sp->text.data = (u_char *) "";
        e->sp++;

        return;
    }

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0,
                   "http rewrite var: %ui, \"%V\"", value->value, &value->text);

    *e->sp = *value;
    e->sp++;
}


static void
ngx_http_rewrite_invalid_referer_code(ngx_http_rewrite_engine_t *e)
{
    u_char                       *ref;
    size_t                        len;
    ngx_uint_t                    i, n;
    ngx_http_request_t           *r;
    ngx_http_rewrite_referer_t   *refs;
    ngx_http_rewrite_loc_conf_t  *cf;

    r = e->request;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "http rewrite invalid referer");

    cf = ngx_http_get_module_loc_conf(r, ngx_http_rewrite_module);

    e->ip += sizeof(uintptr_t);

    if (cf->referers == NULL) {
        e->sp->value = 0;
        e->sp->text.len = 0;
        e->sp->text.data = (u_char *) "";
        e->sp++;

        return;
    }

    if (r->headers_in.referer == NULL) {
        if (cf->no_referer) {
            e->sp->value = 0;
            e->sp->text.len = 0;
            e->sp->text.data = (u_char *) "";
            e->sp++;

            return;

        } else {
            e->sp->value = 1;
            e->sp->text.len = 1;
            e->sp->text.data = (u_char *) "1";
            e->sp++;

            return;
        }
    }

    len = r->headers_in.referer->value.len;
    ref = r->headers_in.referer->value.data;

    if (len < sizeof("http://i.ru") - 1
        || (ngx_strncasecmp(ref, "http://", 7) != 0))
    {
        if (cf->blocked_referer) {
            e->sp->value = 0;
            e->sp->text.len = 0;
            e->sp->text.data = (u_char *) "0";
            e->sp++;

            return;

        } else {
            e->sp->value = 1;
            e->sp->text.len = 1;
            e->sp->text.data = (u_char *) "1";
            e->sp++;

            return;
        }
    }

    len -= 7;
    ref += 7;

    refs = cf->referers->elts;
    for (i = 0; i < cf->referers->nelts; i++ ){

        if (refs[i].name.len > len) {
            continue;
        }

        if (refs[i].wildcard) {
            for (n = 0; n < len; n++) {
                if (ref[n] == '/' || ref[n] == ':') {
                    break;
                }

                if (ref[n] != '.') {
                    continue;
                }

                if (ngx_strncmp(&ref[n], refs[i].name.data,
                                refs[i].name.len) == 0)
                {
                    e->sp->value = 0;
                    e->sp->text.len = 0;
                    e->sp->text.data = (u_char *) "";
                    e->sp++;

                    return;
                }
            }

        } else {
            if (ngx_strncasecmp(refs[i].name.data, ref, refs[i].name.len) == 0)
            {
                e->sp->value = 0;
                e->sp->text.len = 0;
                e->sp->text.data = (u_char *) "";
                e->sp++;

                return;
            }
        }
    }

    e->sp->value = 1;
    e->sp->text.len = 1;
    e->sp->text.data = (u_char *) "1";
    e->sp++;
}


static void
ngx_http_rewrite_nop_code(ngx_http_rewrite_engine_t *e)
{
    e->ip += sizeof(uintptr_t);
}


static ngx_http_variable_value_t *
ngx_http_rewrite_var(ngx_http_request_t *r, uintptr_t data)
{
    ngx_http_variable_t        *var;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    var = cmcf->variables.elts;

    /*
     * the ngx_http_rewrite_module sets variables directly in r->variables,
     * and they should be handle by ngx_http_get_indexed_variable(),
     * so the handler is called only if the variable is not initialized
     */

    ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                  "using uninitialized \"%V\" variable", &var[data].name);

    return &ngx_http_rewrite_null_value;
}


static ngx_int_t
ngx_http_rewrite_init(ngx_cycle_t *cycle)
{   
    ngx_http_handler_pt        *h;
    ngx_http_core_main_conf_t  *cmcf;
    
    cmcf = ngx_http_cycle_get_module_main_conf(cycle, ngx_http_core_module);

    h = ngx_array_push(&cmcf->phases[NGX_HTTP_REWRITE_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }
    
    *h = ngx_http_rewrite_handler;
    
    return NGX_OK;
}   


static void *
ngx_http_rewrite_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_rewrite_loc_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_rewrite_loc_conf_t));
    if (conf == NULL) {
        return NGX_CONF_ERROR;
    }

    conf->stack_size = NGX_CONF_UNSET_UINT;
    conf->log = NGX_CONF_UNSET;
    conf->no_referer = NGX_CONF_UNSET;
    conf->blocked_referer = NGX_CONF_UNSET;

    return conf;
}


static char *
ngx_http_rewrite_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
    ngx_http_rewrite_loc_conf_t *prev = parent;
    ngx_http_rewrite_loc_conf_t *conf = child;

    uintptr_t                      *code, *last;
    ngx_http_rewrite_regex_code_t  *regex;

    ngx_conf_merge_value(conf->log, prev->log, 0);
    ngx_conf_merge_unsigned_value(conf->stack_size, prev->stack_size, 10);

    if (conf->referers == NULL) {
        conf->referers = prev->referers;
        ngx_conf_merge_value(conf->no_referer, prev->no_referer, 0);
        ngx_conf_merge_value(conf->blocked_referer, prev->blocked_referer, 0);
    }

    if (conf->no_referer == NGX_CONF_UNSET) {
        conf->no_referer = 0;
    }

    if (conf->blocked_referer == NGX_CONF_UNSET) {
        conf->blocked_referer = 0;
    }

    if (conf->codes == NULL) {
        return NGX_CONF_OK;
    }

    if (conf->codes == prev->codes) {
        return NGX_CONF_OK;
    }

    code = conf->codes->elts;
    last = (uintptr_t *) ((u_char *) code + conf->codes->nelts);

    while (code < last) {
        if (*code == (uintptr_t) NULL) {
            return NGX_CONF_OK;
        }

        if (*code == (uintptr_t) &ngx_http_rewrite_regex_start_code) {
            regex = (ngx_http_rewrite_regex_code_t *) code;
            if (conf->max_captures < regex->ncaptures) {
                conf->max_captures = regex->ncaptures;
            }
            code = (uintptr_t *) ((u_char *) code + regex->next);
            continue;
        }

        if (*code == (uintptr_t) &ngx_http_rewrite_if_code) {
            code += sizeof(ngx_http_rewrite_if_code_t) / sizeof(uintptr_t);
            continue;
        }

        if (*code == (uintptr_t) &ngx_http_rewrite_return_code) {
            code += sizeof(ngx_http_rewrite_return_code_t) / sizeof(uintptr_t);
            continue;
        }

        if (*code == (uintptr_t) &ngx_http_rewrite_set_var_code) {
            code += sizeof(ngx_http_rewrite_var_code_t) / sizeof(uintptr_t);
            continue;
        }

        if (*code == (uintptr_t) &ngx_http_rewrite_var_code) {
            code += sizeof(ngx_http_rewrite_var_code_t) / sizeof(uintptr_t);
            continue;
        }

        if (*code == (uintptr_t) &ngx_http_rewrite_value_code) {
            code += sizeof(ngx_http_rewrite_value_code_t) / sizeof(uintptr_t);
            continue;
        }

        if (*code == (uintptr_t) &ngx_http_rewrite_invalid_referer_code) {
            code++;
            continue;
        }

        if (*code == (uintptr_t) &ngx_http_rewrite_nop_code) {
            code++;
            continue;
        }

#if (NGX_DEBUG)
        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                      "unknown rewrite code: %p", *code);
        return NGX_CONF_ERROR;
#endif
    }

    code = ngx_array_push_n(conf->codes, sizeof(uintptr_t));
    if (code == NULL) {
        return NGX_CONF_ERROR;
    }

    *code = (uintptr_t) NULL;

    return NGX_CONF_OK;
}


static char *
ngx_http_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_rewrite_loc_conf_t *lcf = conf;
    
    u_char                                *data;
    size_t                                 len, size;
    ngx_str_t                             *value, err;
    ngx_int_t                              n;
    ngx_uint_t                             i, last;
    ngx_http_rewrite_code_pt              *code;
    ngx_http_rewrite_copy_code_t          *copy;
    ngx_http_rewrite_regex_code_t         *regex;
    ngx_http_rewrite_regex_end_code_t     *regex_end;
    ngx_http_rewrite_copy_capture_code_t  *copy_capture;
    u_char                                 errstr[NGX_MAX_CONF_ERRSTR];

    regex = ngx_http_rewrite_start_code(cf->pool, &lcf->codes,
                                        sizeof(ngx_http_rewrite_regex_code_t));
    if (regex == NULL) {
        return NGX_CONF_ERROR;
    }

    value = cf->args->elts;

    err.len = NGX_MAX_CONF_ERRSTR;
    err.data = errstr;

    /* TODO: NGX_REGEX_CASELESS */

    regex->regex = ngx_regex_compile(&value[1], 0, cf->pool, &err);

    if (regex->regex == NULL) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s", err.data);
        return NGX_CONF_ERROR;
    }

    regex->code = ngx_http_rewrite_regex_start_code;
    regex->size = 0;
    regex->ncaptures = 0;
    regex->status = 0;
    regex->test = 0;
    regex->uri = 1;
    regex->args = 1;
    regex->redirect = 0;
    regex->name = value[1];

    last = 0;

    if (ngx_strncmp(value[2].data, "http://", sizeof("http://") - 1) == 0) {
        regex->status = NGX_HTTP_MOVED_TEMPORARILY;
        regex->redirect = 1;
        last = 1;
    }

    if (cf->args->nelts == 4) {
        if (ngx_strcmp(value[3].data, "last") == 0) {
            last = 1;

        } else if (ngx_strcmp(value[3].data, "break") == 0) {
            regex->break_cycle = 1;
            last = 1;

        } else if (ngx_strcmp(value[3].data, "redirect") == 0) {
            regex->status = NGX_HTTP_MOVED_TEMPORARILY;
            regex->redirect = 1;
            last = 1;

        } else if (ngx_strcmp(value[3].data, "permanent") == 0) {
            regex->status = NGX_HTTP_MOVED_PERMANENTLY;
            regex->redirect = 1;
            last = 1;

        } else {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "invalid parameter \"%V\"", &value[3]);
            return NGX_CONF_ERROR;
        }
    }

    i = 0;

    while (i < value[2].len) {

        data = &value[2].data[i];

        if (value[2].data[i] == '$' && i < value[2].len
            && value[2].data[i + 1] >= '1' && value[2].data[i + 1] <= '9')
        {

            /* the "$1" - "$9" captures */

            copy_capture = ngx_http_rewrite_add_code(lcf->codes,
                                  sizeof(ngx_http_rewrite_copy_capture_code_t),
                                  &regex);
            if (copy_capture == NULL) {
                return NGX_CONF_ERROR;
            }

            i++;

            copy_capture->code = ngx_http_rewrite_copy_capture_code;
            copy_capture->n = value[2].data[i] - '0';

            if (regex->ncaptures < copy_capture->n) {
                regex->ncaptures = copy_capture->n;
            }

            copy_capture->n *= 2;

            i++;

            continue;
        }

        if (value[2].data[i] == '?') {

            /* the arguments */

            if (i == value[2].len - 1) {
                /* the last "?" drops the original arguments */
                regex->args = 0;
                break;
            }

            if (!regex->redirect) {
                code = ngx_http_rewrite_add_code(lcf->codes, sizeof(uintptr_t),
                                                 &regex);
                if (code == NULL) {
                    return NGX_CONF_ERROR;
                }

                *code = ngx_http_rewrite_start_args_code;

                i++;

                continue;
            }
        }

        i++;

        /* the substituion strings */

        while (i < value[2].len && value[2].data[i] != '$') {

            if (value[2].data[i] == '?') {

                if (i == value[2].len - 1) {
                    /*
                     * the last "?" drops the original arguments,
                     * and it should not be copied to a substituion
                     */
                    regex->args = 0;
                    break;
                }

                if (!regex->redirect) {
                    break;
                }
            }

            i++;
        }

        len = &value[2].data[i] - data;

        if (len == 0) {
            continue;
        }

        regex->size += len;

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

        copy = ngx_http_rewrite_add_code(lcf->codes,
                                   sizeof(ngx_http_rewrite_copy_code_t) + size,
                                   &regex);
        if (copy == NULL) {
            return NGX_CONF_ERROR;
        }

        copy->code = ngx_http_rewrite_copy_code;
        copy->len = len;

        ngx_memcpy((u_char *) copy + sizeof(ngx_http_rewrite_copy_code_t),
                   data, len);
    }

    n = ngx_regex_capture_count(regex->regex);

    if (n < 0) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           ngx_regex_capture_count_n " failed for "
                           "pattern \"%V\"", &value[1]);
        return NGX_CONF_ERROR;
    }

    if (regex->ncaptures > (ngx_uint_t) n) {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "pattern \"%V\" has less captures "
                           "than referrenced in substitution \"%V\"",
                           &value[1], &value[2]);
        return NGX_CONF_ERROR;
    }

    if (regex->ncaptures < (ngx_uint_t) n) {
        regex->ncaptures = (ngx_uint_t) n;
    }

    if (regex->ncaptures) {
        regex->ncaptures = (regex->ncaptures + 1) * 3;
    }

    regex_end = ngx_http_rewrite_add_code(lcf->codes,
                                     sizeof(ngx_http_rewrite_regex_end_code_t),
                                     &regex);
    if (regex_end == NULL) {
        return NGX_CONF_ERROR;
    }

    regex_end->code = ngx_http_rewrite_regex_end_code;
    regex_end->uri = regex->uri;
    regex_end->args = regex->args;
    regex_end->redirect = regex->redirect;

    if (last) {
        code = ngx_http_rewrite_add_code(lcf->codes, sizeof(uintptr_t),
                                         &regex);
        if (code == NULL) {
            return NGX_CONF_ERROR;
        }

        *code = (uintptr_t) NULL;
    }

    regex->next = (u_char *) lcf->codes->elts + lcf->codes->nelts
                                              - (u_char *) regex;

    return NGX_CONF_OK;
}


static char *
ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_rewrite_loc_conf_t *lcf = conf;

    ngx_str_t                       *value;
    ngx_http_rewrite_return_code_t  *ret;

    ret = ngx_http_rewrite_start_code(cf->pool, &lcf->codes,
                                      sizeof(ngx_http_rewrite_return_code_t));
    if (ret == NULL) {
        return NGX_CONF_ERROR;
    }

    value = cf->args->elts;

    ret->code = ngx_http_rewrite_return_code;
    ret->null = (uintptr_t) NULL;

    ret->status = ngx_atoi(value[1].data, value[1].len);

    if (ret->status == (uintptr_t) NGX_ERROR) {
        return NGX_CONF_ERROR;
    }

    return NGX_CONF_OK;
}


static char *
ngx_http_rewrite_if(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_rewrite_loc_conf_t *lcf = conf;

    void                         *mconf;
    char                         *rv;
    u_char                       *elts;
    ngx_uint_t                    i;
    ngx_conf_t                    save;
    ngx_http_module_t            *module;
    ngx_http_conf_ctx_t          *ctx, *pctx;
    ngx_http_core_loc_conf_t     *clcf, *pclcf, **clcfp;
    ngx_http_rewrite_if_code_t   *if_code;
    ngx_http_rewrite_loc_conf_t  *nlcf;

    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_http_conf_ctx_t));
    if (ctx == NULL) {
        return NGX_CONF_ERROR;
    }

    pctx = cf->ctx;
    ctx->main_conf = pctx->main_conf;
    ctx->srv_conf = pctx->srv_conf; 

    ctx->loc_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_http_max_module);
    if (ctx->loc_conf == NULL) {
        return NGX_CONF_ERROR;
    }

    for (i = 0; ngx_modules[i]; i++) {
        if (ngx_modules[i]->type != NGX_HTTP_MODULE) {
            continue;
        }

        module = ngx_modules[i]->ctx;
    
        if (module->create_loc_conf) {

            mconf = module->create_loc_conf(cf);
            if (mconf == NULL) {
                 return NGX_CONF_ERROR;
            }

            ctx->loc_conf[ngx_modules[i]->ctx_index] = mconf;
        }
    }

    pclcf = pctx->loc_conf[ngx_http_core_module.ctx_index];

    clcf = ctx->loc_conf[ngx_http_core_module.ctx_index];
    clcf->loc_conf = ctx->loc_conf;
    clcf->name = pclcf->name;
    clcf->noname = 1;

    if (pclcf->locations.elts == NULL) {
        if (ngx_array_init(&pclcf->locations, cf->pool, 4, sizeof(void *))
                                                                  == NGX_ERROR)
        {
            return NGX_CONF_ERROR;
        }
    }

    clcfp = ngx_array_push(&pclcf->locations);
    if (clcfp == NULL) {
        return NGX_CONF_ERROR;
    }

    *clcfp = clcf;


    if (ngx_http_rewrite_if_condition(cf, lcf) != NGX_CONF_OK) {
        return NGX_CONF_ERROR;
    }

    if_code = ngx_array_push_n(lcf->codes, sizeof(ngx_http_rewrite_if_code_t));
    if (if_code == NULL) {
        return NULL;
    }

    if_code->code = ngx_http_rewrite_if_code;

    elts = lcf->codes->elts;


    /* the inside directives must compile to the same code array */

    nlcf = ctx->loc_conf[ngx_http_rewrite_module.ctx_index];
    nlcf->codes = lcf->codes;


    save = *cf;
    cf->ctx = ctx;

    if (pclcf->name.len == 0) {
        if_code->loc_conf = NULL;
        cf->cmd_type = NGX_HTTP_SIF_CONF;

    } else {
        if_code->loc_conf = ctx->loc_conf;
        cf->cmd_type = NGX_HTTP_LIF_CONF;
    }

    rv = ngx_conf_parse(cf, NULL);

    *cf = save;

    if (rv != NGX_CONF_OK) {
        return rv;
    }


    if (elts != lcf->codes->elts) {
        if_code = (ngx_http_rewrite_if_code_t *)
                   ((u_char *) if_code + ((u_char *) lcf->codes->elts - elts));
    }

    if_code->next = (u_char *) lcf->codes->elts + lcf->codes->nelts
                                                - (u_char *) if_code;

    return NGX_CONF_OK;
}


static char *
ngx_http_rewrite_if_condition(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf)
{
    ngx_str_t                      *value, err;
    ngx_uint_t                      cur, last;
    ngx_http_rewrite_regex_code_t  *regex;
    u_char                          errstr[NGX_MAX_CONF_ERRSTR];

    value = cf->args->elts;
    last = cf->args->nelts - 1;

    if (value[1].len < 1 || value[1].data[0] != '(') {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "invalid condition \"%V\"", &value[1]);
        return NGX_CONF_ERROR;
    }

    if (value[1].len == 1) {
        cur = 2;

    } else {
        cur = 1;
        value[1].len--;
        value[1].data++;
    }

    if (value[last].len < 1 || value[last].data[value[last].len - 1] != ')') {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "invalid condition \"%V\"", &value[last]);
        return NGX_CONF_ERROR;
    }

    if (value[last].len == 1) {
        last--;

    } else {
        value[last].len--;
        value[last].data[value[last].len] = '\0';
    }

    if (value[cur].len > 1 && value[cur].data[0] == '$') {

        if (cur != last && cur + 2 != last) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                       "invalid condition \"%V\"", &value[cur]);
            return NGX_CONF_ERROR;
        }

        if (ngx_http_rewrite_variable(cf, lcf, &value[cur])!= NGX_CONF_OK) {
            return NGX_CONF_ERROR;
        }

        if (cur == last) {
            return NGX_CONF_OK;
        }

        cur++;

        if ((value[cur].len == 1 && value[cur].data[0] != '~')
            || (value[cur].len == 2
                && value[cur].data[0] != '~' && value[cur].data[1] != '*'))
        {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "unexpected \"%V\" in condition", &value[cur]);
            return NGX_CONF_ERROR;
        }

        regex = ngx_http_rewrite_start_code(cf->pool, &lcf->codes,
                                        sizeof(ngx_http_rewrite_regex_code_t));
        if (regex == NULL) {
            return NGX_CONF_ERROR;
        }

        err.len = NGX_MAX_CONF_ERRSTR;
        err.data = errstr;

        regex->regex = ngx_regex_compile(&value[last],
                                (value[cur].len == 2) ? NGX_REGEX_CASELESS : 0,
                                cf->pool, &err);

        if (regex->regex == NULL) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "%s", err.data);
            return NGX_CONF_ERROR;
        }

        regex->code = ngx_http_rewrite_regex_start_code;
        regex->size = 0;
        regex->ncaptures = 0;
        regex->status = 0;
        regex->next = sizeof(ngx_http_rewrite_regex_code_t);
        regex->test = 1;
        regex->uri = 0;
        regex->args = 0;
        regex->redirect = 0;
        regex->name = value[last];

        return NGX_CONF_OK;
    }

    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                       "invalid condition \"%V\"", &value[cur]);

    return NGX_CONF_ERROR;
}


static char *
ngx_http_rewrite_variable(ngx_conf_t *cf, ngx_http_rewrite_loc_conf_t *lcf,
    ngx_str_t *value)
{
    ngx_http_variable_t          *var;
    ngx_http_rewrite_code_pt     *code;
    ngx_http_rewrite_var_code_t  *var_code;

    value->len--;
    value->data++;

    if (value->len == sizeof("invalid_referer") - 1
        && ngx_strncmp(value->data, "invalid_referer",
                       sizeof("invalid_referer") - 1) == 0)
    {
        code = ngx_http_rewrite_start_code(cf->pool, &lcf->codes,
                                           sizeof(ngx_http_rewrite_code_pt));
        if (code == NULL) {
            return NGX_CONF_ERROR;
        }

        *code = ngx_http_rewrite_invalid_referer_code;

    } else {
        var = ngx_http_add_variable(cf, value, 0);

        if (var == NULL) {
            return NGX_CONF_ERROR;
        }

        var_code = ngx_http_rewrite_start_code(cf->pool, &lcf->codes,
                                           sizeof(ngx_http_rewrite_var_code_t));
        if (var_code == NULL) {
            return NGX_CONF_ERROR;
        }

        var_code->code = ngx_http_rewrite_var_code;
        var_code->index = var->index;
    }

    return NGX_CONF_OK;
}


static char *
ngx_http_rewrite_valid_referers(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_rewrite_loc_conf_t *lcf = conf;

    ngx_uint_t                   i, server_names;
    ngx_str_t                   *value;
    ngx_http_server_name_t      *sn;
    ngx_http_core_srv_conf_t    *cscf;
    ngx_http_rewrite_referer_t  *ref;

    cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module);

    if (lcf->referers == NULL) {
        lcf->referers = ngx_array_create(cf->pool,
                                    cf->args->nelts + cscf->server_names.nelts,
                                    sizeof(ngx_http_rewrite_referer_t));
        if (lcf->referers == NULL) {
            return NGX_CONF_ERROR;
        }
    }

    value = cf->args->elts;
    server_names = 0;

    for (i = 1; i < cf->args->nelts; i++) {
        if (value[i].len == 0) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "invalid referer \"%V\"", &value[i]);
            return NGX_CONF_ERROR;
        }

        if (ngx_strcmp(value[i].data, "none") == 0) {
            lcf->no_referer = 1;
            continue;
        }

        if (ngx_strcmp(value[i].data, "blocked") == 0) {
            lcf->blocked_referer = 1;
            continue;
        }

        if (ngx_strcmp(value[i].data, "server_names") == 0) {
            server_names = 1;
            continue;
        }

        ref = ngx_array_push(lcf->referers);
        if (ref == NULL) {
            return NGX_CONF_ERROR;
        }

        if (value[i].data[0] != '*') {
            ref->name = value[i];
            ref->wildcard = 0;
            continue;
        }

        if (value[i].data[1] != '.') {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "invalid wildcard referer \"%V\"", &value[i]);
            return NGX_CONF_ERROR;
        }

        ref->name.len = value[i].len - 1;
        ref->name.data = value[i].data + 1;
        ref->wildcard = 1;
    }

    if (!server_names) {
        return NGX_CONF_OK;
    }

    sn = cscf->server_names.elts;
    for (i = 0; i < cscf->server_names.nelts; i++) {
        ref = ngx_array_push(lcf->referers);
        if (ref == NULL) {
            return NGX_CONF_ERROR;
        }

        ref->name.len = sn[i].name.len + 1;
        ref->name.data = ngx_palloc(cf->pool, ref->name.len);
        if (ref->name.data == NULL) {
            return NGX_CONF_ERROR;
        }

        ngx_memcpy(ref->name.data, sn[i].name.data, sn[i].name.len);
        ref->name.data[sn[i].name.len] = '/';
        ref->wildcard = sn[i].wildcard;
    }

    return NGX_CONF_OK;
}


static char *
ngx_http_rewrite_set(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    ngx_http_rewrite_loc_conf_t *lcf = conf;

    ngx_int_t                       n;
    ngx_str_t                      *value;
    ngx_http_variable_t            *v;
    ngx_http_rewrite_var_code_t    *var;
    ngx_http_rewrite_value_code_t  *val;

    value = cf->args->elts;

    if (value[1].data[0] != '$') {
        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "invalid variable name \"%V\"", &value[1]);
        return NGX_CONF_ERROR;
    }

    value[1].len--;
    value[1].data++;

    v = ngx_http_add_variable(cf, &value[1], 1);
    if (v == NULL) {
        return NGX_CONF_ERROR;
    }

    v->handler = ngx_http_rewrite_var;
    v->data = v->index;

    val = ngx_http_rewrite_start_code(cf->pool, &lcf->codes,
                                      sizeof(ngx_http_rewrite_value_code_t));
    if (val == NULL) {
        return NGX_CONF_ERROR;
    }

    n = ngx_atoi(value[2].data, value[2].len);

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

    val->code = ngx_http_rewrite_value_code;
    val->value = (uintptr_t) n;
    val->text_len = (uintptr_t) value[2].len;
    val->text_data = (uintptr_t) value[2].data;

    var = ngx_http_rewrite_start_code(cf->pool, &lcf->codes,
                                      sizeof(ngx_http_rewrite_var_code_t));
    if (var == NULL) {
        return NGX_CONF_ERROR;
    }

    var->code = ngx_http_rewrite_set_var_code;
    var->index = (uintptr_t) v->index;

    return NGX_CONF_OK;
}


static void *
ngx_http_rewrite_start_code(ngx_pool_t *pool, ngx_array_t **codes, size_t size)
{
    if (*codes == NULL) {
        *codes = ngx_array_create(pool, 256, 1);
        if (*codes == NULL) {
            return NULL;
        }
    }

    return ngx_array_push_n(*codes, size);
}


static void *
ngx_http_rewrite_add_code(ngx_array_t *codes, size_t size, void *code)
{
    u_char  *elts, **p;
    void    *new;

    elts = codes->elts;

    new = ngx_array_push_n(codes, size);
    if (new == NULL) {
        return NGX_CONF_ERROR;
    }

    if (elts != codes->elts) {
        p = code;
        *p += (u_char *) codes->elts - elts;
    }

    return new;
}