diff options
Diffstat (limited to 'src/http/modules/ngx_http_rewrite_module.c')
-rw-r--r-- | src/http/modules/ngx_http_rewrite_module.c | 1689 |
1 files changed, 1689 insertions, 0 deletions
diff --git a/src/http/modules/ngx_http_rewrite_module.c b/src/http/modules/ngx_http_rewrite_module.c new file mode 100644 index 000000000..d0313a01f --- /dev/null +++ b/src/http/modules/ngx_http_rewrite_module.c @@ -0,0 +1,1689 @@ + +/* + * 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_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; + } + + 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)) + { + 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; + + 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); + } + + if (conf->no_referer == NGX_CONF_UNSET) { + conf->no_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), + ®ex); + 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), + ®ex); + 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, + ®ex); + 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), + ®ex); + 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), + ®ex); + 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, "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; +} |