/* * Copyright (C) Igor Sysoev */ #include #include #include #define ngx_http_script_exit (u_char *) &ngx_http_script_exit_code static uintptr_t ngx_http_script_exit_code = (uintptr_t) NULL; ngx_uint_t ngx_http_script_variables_count(ngx_str_t *value) { ngx_uint_t i, n; for (n = 0, i = 0; i < value->len; i++) { if (value->data[i] == '$') { n++; } } return n; } ngx_int_t ngx_http_script_compile(ngx_http_script_compile_t *sc) { u_char ch; size_t size; ngx_int_t index, *p; ngx_str_t name; uintptr_t *code; ngx_uint_t i, n, bracket; ngx_http_script_var_code_t *var_code; ngx_http_script_copy_code_t *copy; ngx_http_script_copy_capture_code_t *copy_capture; if (sc->flushes && *sc->flushes == NULL) { n = sc->variables ? sc->variables : 1; *sc->flushes = ngx_array_create(sc->cf->pool, n, sizeof(ngx_uint_t)); if (*sc->flushes == NULL) { return NGX_ERROR; } } if (*sc->lengths == NULL) { n = sc->variables * (2 * sizeof(ngx_http_script_copy_code_t) + sizeof(ngx_http_script_var_code_t)) + sizeof(uintptr_t); *sc->lengths = ngx_array_create(sc->cf->pool, n, 1); if (*sc->lengths == NULL) { return NGX_ERROR; } } if (*sc->values == NULL) { n = (sc->variables * (2 * sizeof(ngx_http_script_copy_code_t) + sizeof(ngx_http_script_var_code_t)) + sizeof(uintptr_t) + sc->source->len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); *sc->values = ngx_array_create(sc->cf->pool, n, 1); if (*sc->values == NULL) { return NGX_ERROR; } } sc->variables = 0; 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] >= '1' && sc->source->data[i] <= '9') { copy_capture = ngx_http_script_add_code(*sc->lengths, sizeof(ngx_http_script_copy_capture_code_t), NULL); if (copy_capture == NULL) { return NGX_ERROR; } copy_capture->code = (ngx_http_script_code_pt) ngx_http_script_copy_capture_len_code; copy_capture->n = 2 * (sc->source->data[i] - '0'); copy_capture = ngx_http_script_add_code(*sc->values, sizeof(ngx_http_script_copy_capture_code_t), &sc->main); if (copy_capture == NULL) { return NGX_ERROR; } copy_capture->code = ngx_http_script_copy_capture_code; copy_capture->n = sc->source->data[i] - '0'; if (sc->ncaptures < copy_capture->n) { sc->ncaptures = copy_capture->n; } copy_capture->n *= 2; i++; continue; } if (sc->source->data[i] == '{') { bracket = 1; if (++i == sc->source->len) { goto invalid_variable; } 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 ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '_') { continue; } break; } if (bracket) { ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0, "the closing bracket in \"%V\" " "variable is missing", &name); return NGX_ERROR; } if (name.len == 0) { goto invalid_variable; } sc->variables++; index = ngx_http_get_variable_index(sc->cf, &name); if (index == NGX_ERROR) { return NGX_ERROR; } if (sc->flushes) { p = ngx_array_push(*sc->flushes); if (p == NULL) { return NGX_ERROR; } *p = index; } var_code = ngx_http_script_add_code(*sc->lengths, sizeof(ngx_http_script_var_code_t), NULL); if (var_code == NULL) { return NGX_ERROR; } var_code->code = (ngx_http_script_code_pt) ngx_http_script_copy_var_len_code; var_code->index = (uintptr_t) index; var_code = ngx_http_script_add_code(*sc->values, sizeof(ngx_http_script_var_code_t), &sc->main); if (var_code == NULL) { return NGX_ERROR; } var_code->code = ngx_http_script_copy_var_code; var_code->index = (uintptr_t) index; continue; } if (sc->source->data[i] == '?' && sc->compile_args) { sc->args = 1; sc->compile_args = 0; code = ngx_http_script_add_code(*sc->values, sizeof(uintptr_t), &sc->main); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) ngx_http_script_start_args_code; i++; continue; } name.data = &sc->source->data[i]; while (i < sc->source->len && sc->source->data[i] != '$' && !(sc->source->data[i] == '?' && sc->compile_args)) { i++; name.len++; } sc->size += name.len; copy = ngx_http_script_add_code(*sc->lengths, sizeof(ngx_http_script_copy_code_t), NULL); if (copy == NULL) { return NGX_ERROR; } copy->code = (ngx_http_script_code_pt) ngx_http_script_copy_len_code; copy->len = name.len; size = (sizeof(ngx_http_script_copy_code_t) + name.len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1); copy = ngx_http_script_add_code(*sc->values, size, &sc->main); if (copy == NULL) { return NGX_ERROR; } copy->code = ngx_http_script_copy_code; copy->len = name.len; ngx_memcpy((u_char *) copy + sizeof(ngx_http_script_copy_code_t), name.data, name.len); } if (sc->complete_lengths) { code = ngx_http_script_add_code(*sc->lengths, sizeof(uintptr_t), NULL); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; } if (sc->complete_values) { code = ngx_http_script_add_code(*sc->values, sizeof(uintptr_t), &sc->main); if (code == NULL) { return NGX_ERROR; } *code = (uintptr_t) NULL; } return NGX_OK; invalid_variable: ngx_conf_log_error(NGX_LOG_EMERG, sc->cf, 0, "invalid variable name"); return NGX_ERROR; } u_char * ngx_http_script_run(ngx_http_request_t *r, ngx_str_t *value, void *code_lengths, size_t len, void *code_values) { ngx_http_script_code_pt code; ngx_http_script_len_code_pt lcode; ngx_http_script_engine_t e; ngx_memzero(&e, sizeof(ngx_http_script_engine_t)); e.ip = code_lengths; e.request = r; e.flushed = 1; while (*(uintptr_t *) e.ip) { lcode = *(ngx_http_script_len_code_pt *) e.ip; len += lcode(&e); } value->len = len; value->data = ngx_palloc(r->pool, len); if (value->data == NULL) { return NULL; } e.ip = code_values; e.pos = value->data; while (*(uintptr_t *) e.ip) { code = *(ngx_http_script_code_pt *) e.ip; code((ngx_http_script_engine_t *) &e); } return e.pos; } void ngx_http_script_flush_no_cachable_variables(ngx_http_request_t *r, ngx_array_t *indices) { ngx_uint_t n, *index; if (indices) { index = indices->elts; for (n = 0; n < indices->nelts; n++) { if (r->variables[index[n]].no_cachable) { r->variables[index[n]].valid = 0; r->variables[index[n]].not_found = 0; } } } } void * ngx_http_script_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); } void * ngx_http_script_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 (code) { if (elts != codes->elts) { p = code; *p += (u_char *) codes->elts - elts; } } return new; } size_t ngx_http_script_copy_len_code(ngx_http_script_engine_t *e) { ngx_http_script_copy_code_t *code; code = (ngx_http_script_copy_code_t *) e->ip; e->ip += sizeof(ngx_http_script_copy_code_t); return code->len; } void ngx_http_script_copy_code(ngx_http_script_engine_t *e) { ngx_http_script_copy_code_t *code; code = (ngx_http_script_copy_code_t *) e->ip; if (!e->skip) { e->pos = ngx_copy(e->pos, e->ip + sizeof(ngx_http_script_copy_code_t), code->len); } e->ip += sizeof(ngx_http_script_copy_code_t) + ((code->len + sizeof(uintptr_t) - 1) & ~(sizeof(uintptr_t) - 1)); if (e->log) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script copy: \"%V\"", &e->buf); } } size_t ngx_http_script_copy_var_len_code(ngx_http_script_engine_t *e) { ngx_http_variable_value_t *value; ngx_http_script_var_code_t *code; code = (ngx_http_script_var_code_t *) e->ip; e->ip += sizeof(ngx_http_script_var_code_t); if (e->flushed) { value = ngx_http_get_indexed_variable(e->request, code->index); } else { value = ngx_http_get_flushed_variable(e->request, code->index); } if (value && !value->not_found) { return value->len; } return 0; } void ngx_http_script_copy_var_code(ngx_http_script_engine_t *e) { ngx_http_variable_value_t *value; ngx_http_script_var_code_t *code; code = (ngx_http_script_var_code_t *) e->ip; e->ip += sizeof(ngx_http_script_var_code_t); if (!e->skip) { if (e->flushed) { value = ngx_http_get_indexed_variable(e->request, code->index); } else { value = ngx_http_get_flushed_variable(e->request, code->index); } if (value && !value->not_found) { e->pos = ngx_copy(e->pos, value->data, value->len); if (e->log) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script var: \"%V\"", &e->buf); } } } } size_t ngx_http_script_copy_capture_len_code(ngx_http_script_engine_t *e) { ngx_http_script_copy_capture_code_t *code; code = (ngx_http_script_copy_capture_code_t *) e->ip; e->ip += sizeof(ngx_http_script_copy_capture_code_t); if (code->n < e->ncaptures) { if ((e->args || e->quote) && (e->request->quoted_uri || e->request->plus_in_uri)) { return e->captures[code->n + 1] - e->captures[code->n] + ngx_escape_uri(NULL, &e->line.data[e->captures[code->n]], e->captures[code->n + 1] - e->captures[code->n], NGX_ESCAPE_ARGS); } else { return e->captures[code->n + 1] - e->captures[code->n]; } } return 0; } void ngx_http_script_copy_capture_code(ngx_http_script_engine_t *e) { ngx_http_script_copy_capture_code_t *code; code = (ngx_http_script_copy_capture_code_t *) e->ip; e->ip += sizeof(ngx_http_script_copy_capture_code_t); if (code->n < e->ncaptures) { 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_copy(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 script capture: \"%V\"", &e->buf); } void ngx_http_script_start_args_code(ngx_http_script_engine_t *e) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script args"); e->args = e->pos; e->ip += sizeof(uintptr_t); } #if (NGX_PCRE) void ngx_http_script_regex_start_code(ngx_http_script_engine_t *e) { size_t len; ngx_int_t rc; ngx_uint_t n; ngx_http_request_t *r; ngx_http_script_engine_t le; ngx_http_script_len_code_pt lcode; ngx_http_script_regex_code_t *code; code = (ngx_http_script_regex_code_t *) e->ip; r = e->request; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http script regex: \"%V\"", &code->name); if (code->uri) { e->line = r->uri; } else { e->sp--; e->line.len = e->sp->len; e->line.data = e->sp->data; } rc = ngx_regex_exec(code->regex, &e->line, e->captures, code->ncaptures); if (rc == NGX_REGEX_NO_MATCHED) { if (e->log) { ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, "\"%V\" does not match \"%V\"", &code->name, &e->line); } e->ncaptures = 0; if (code->test) { e->sp->len = 0; e->sp->data = (u_char *) ""; e->sp++; e->ip += sizeof(ngx_http_script_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_script_exit; e->status = NGX_HTTP_INTERNAL_SERVER_ERROR; return; } if (e->log) { ngx_log_error(NGX_LOG_NOTICE, r->connection->log, 0, "\"%V\" matches \"%V\"", &code->name, &e->line); } e->ncaptures = code->ncaptures; if (code->test) { e->sp->len = 1; e->sp->data = (u_char *) "1"; e->sp++; e->ip += sizeof(ngx_http_script_regex_code_t); return; } if (code->status) { e->status = code->status; if (!code->redirect) { e->ip = ngx_http_script_exit; return; } } if (code->uri) { r->internal = 1; r->valid_unparsed_uri = 0; if (code->break_cycle) { r->valid_location = 0; r->uri_changed = 0; } else { r->uri_changed = 1; } } if (code->lengths == NULL) { e->buf.len = code->size; if (code->uri) { 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]; } } else { ngx_memzero(&le, sizeof(ngx_http_script_engine_t)); le.ip = code->lengths->elts; le.request = r; le.captures = e->captures; le.ncaptures = e->ncaptures; len = 1; /* reserve 1 byte for possible "?" */ while (*(uintptr_t *) le.ip) { lcode = *(ngx_http_script_len_code_pt *) le.ip; len += lcode(&le); } e->buf.len = len; } if (code->add_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_script_exit; e->status = NGX_HTTP_INTERNAL_SERVER_ERROR; return; } e->quote = code->redirect; e->pos = e->buf.data; e->ip += sizeof(ngx_http_script_regex_code_t); } void ngx_http_script_regex_end_code(ngx_http_script_engine_t *e) { u_char *dst, *src; ngx_http_request_t *r; ngx_http_script_regex_end_code_t *code; code = (ngx_http_script_regex_end_code_t *) e->ip; r = e->request; e->quote = 0; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http script regex end"); if (code->redirect) { dst = e->buf.data; src = e->buf.data; ngx_unescape_uri(&dst, &src, e->pos - e->buf.data, NGX_UNESCAPE_URI); if (src < e->pos) { dst = ngx_copy(dst, src, e->pos - src); } e->pos = dst; if (code->add_args && r->args.len) { *e->pos++ = (u_char) (code->args ? '&' : '?'); e->pos = ngx_copy(e->pos, r->args.data, r->args.len); } e->buf.len = e->pos - e->buf.data; if (e->log) { 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_script_exit; e->status = NGX_HTTP_INTERNAL_SERVER_ERROR; return; } r->headers_out.location->hash = 1; 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_script_regex_end_code_t); return; } if (e->args) { e->buf.len = e->args - e->buf.data; if (code->add_args && r->args.len) { *e->pos++ = '&'; e->pos = ngx_copy(e->pos, r->args.data, r->args.len); } r->args.len = e->pos - e->args; r->args.data = e->args; e->args = NULL; } else { e->buf.len = e->pos - e->buf.data; } if (e->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 (r->uri.len == 0) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "the rewritten URI has a zero length"); e->ip = ngx_http_script_exit; e->status = NGX_HTTP_INTERNAL_SERVER_ERROR; return; } if (ngx_http_set_exten(r) != NGX_OK) { e->ip = ngx_http_script_exit; e->status = NGX_HTTP_INTERNAL_SERVER_ERROR; return; } } e->ip += sizeof(ngx_http_script_regex_end_code_t); } #endif void ngx_http_script_return_code(ngx_http_script_engine_t *e) { ngx_http_script_return_code_t *code; code = (ngx_http_script_return_code_t *) e->ip; e->status = code->status; e->ip += sizeof(ngx_http_script_return_code_t) - sizeof(uintptr_t); } void ngx_http_script_break_code(ngx_http_script_engine_t *e) { e->request->uri_changed = 0; e->ip = ngx_http_script_exit; } void ngx_http_script_if_code(ngx_http_script_engine_t *e) { ngx_http_script_if_code_t *code; code = (ngx_http_script_if_code_t *) e->ip; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script if"); e->sp--; if (e->sp->len && e->sp->data[0] != '0') { if (code->loc_conf) { e->request->loc_conf = code->loc_conf; ngx_http_update_location_config(e->request); } e->ip += sizeof(ngx_http_script_if_code_t); return; } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script if: false"); e->ip += code->next; } void ngx_http_script_equal_code(ngx_http_script_engine_t *e) { ngx_http_variable_value_t *val, *res; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script equal"); e->sp--; val = e->sp; res = e->sp - 1; e->ip += sizeof(uintptr_t); if (val->len == res->len && ngx_strncmp(val->data, res->data, res->len) == 0) { *res = ngx_http_variable_true_value; return; } ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script equal: no"); *res = ngx_http_variable_null_value; } void ngx_http_script_not_equal_code(ngx_http_script_engine_t *e) { ngx_http_variable_value_t *val, *res; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script not equal"); e->sp--; val = e->sp; res = e->sp - 1; e->ip += sizeof(uintptr_t); if (val->len == res->len && ngx_strncmp(val->data, res->data, res->len) == 0) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script not equal: no"); *res = ngx_http_variable_null_value; return; } *res = ngx_http_variable_true_value; } void ngx_http_script_complex_value_code(ngx_http_script_engine_t *e) { size_t len; ngx_http_script_engine_t le; ngx_http_script_len_code_pt lcode; ngx_http_script_complex_value_code_t *code; code = (ngx_http_script_complex_value_code_t *) e->ip; e->ip += sizeof(ngx_http_script_complex_value_code_t); ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script complex value"); ngx_memzero(&le, sizeof(ngx_http_script_engine_t)); le.ip = code->lengths->elts; le.request = e->request; le.captures = e->captures; le.ncaptures = e->ncaptures; for (len = 0; *(uintptr_t *) le.ip; len += lcode(&le)) { lcode = *(ngx_http_script_len_code_pt *) le.ip; } e->buf.len = len; e->buf.data = ngx_palloc(e->request->pool, len); if (e->buf.data == NULL) { e->ip = ngx_http_script_exit; e->status = NGX_HTTP_INTERNAL_SERVER_ERROR; return; } e->pos = e->buf.data; e->sp->len = e->buf.len; e->sp->data = e->buf.data; e->sp++; } void ngx_http_script_value_code(ngx_http_script_engine_t *e) { ngx_http_script_value_code_t *code; code = (ngx_http_script_value_code_t *) e->ip; e->ip += sizeof(ngx_http_script_value_code_t); e->sp->len = code->text_len; e->sp->data = (u_char *) code->text_data; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script value: \"%V\"", e->sp); e->sp++; } void ngx_http_script_set_var_code(ngx_http_script_engine_t *e) { ngx_http_request_t *r; ngx_http_script_var_code_t *code; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script set var"); code = (ngx_http_script_var_code_t *) e->ip; e->ip += sizeof(ngx_http_script_var_code_t); r = e->request; e->sp--; r->variables[code->index].len = e->sp->len; r->variables[code->index].valid = 1; r->variables[code->index].no_cachable = 0; r->variables[code->index].not_found = 0; r->variables[code->index].data = e->sp->data; } void ngx_http_script_var_code(ngx_http_script_engine_t *e) { ngx_http_variable_value_t *value; ngx_http_script_var_code_t *code; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script var"); code = (ngx_http_script_var_code_t *) e->ip; e->ip += sizeof(ngx_http_script_var_code_t); value = ngx_http_get_flushed_variable(e->request, code->index); if (value && !value->not_found) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, e->request->connection->log, 0, "http script var: \"%V\"", value); *e->sp = *value; e->sp++; return; } *e->sp = ngx_http_variable_null_value; e->sp++; } void ngx_http_script_nop_code(ngx_http_script_engine_t *e) { e->ip += sizeof(uintptr_t); }