Added mod_rewrite for nginx module
This commit is contained in:
23
modules/mod_rewrite/config
Normal file
23
modules/mod_rewrite/config
Normal file
@@ -0,0 +1,23 @@
|
||||
ngx_addon_name=ngx_http_apache_rewrite_module
|
||||
|
||||
if test -n "$ngx_module_link"; then
|
||||
ngx_module_type=HTTP
|
||||
ngx_module_name=ngx_http_apache_rewrite_module
|
||||
ngx_module_srcs="$ngx_addon_dir/ngx_http_apache_rewrite_module.c \
|
||||
$ngx_addon_dir/ngx_http_apache_rewrite_engine.c \
|
||||
$ngx_addon_dir/ngx_http_apache_rewrite_expand.c \
|
||||
$ngx_addon_dir/ngx_http_apache_rewrite_variable.c \
|
||||
$ngx_addon_dir/ngx_http_apache_rewrite_map.c \
|
||||
$ngx_addon_dir/ngx_http_apache_rewrite_fastcgi.c"
|
||||
ngx_module_libs=
|
||||
. auto/module
|
||||
else
|
||||
HTTP_MODULES="$HTTP_MODULES ngx_http_apache_rewrite_module"
|
||||
NGX_ADDON_SRCS="$NGX_ADDON_SRCS \
|
||||
$ngx_addon_dir/ngx_http_apache_rewrite_module.c \
|
||||
$ngx_addon_dir/ngx_http_apache_rewrite_engine.c \
|
||||
$ngx_addon_dir/ngx_http_apache_rewrite_expand.c \
|
||||
$ngx_addon_dir/ngx_http_apache_rewrite_variable.c \
|
||||
$ngx_addon_dir/ngx_http_apache_rewrite_map.c \
|
||||
$ngx_addon_dir/ngx_http_apache_rewrite_fastcgi.c"
|
||||
fi
|
||||
857
modules/mod_rewrite/ngx_http_apache_rewrite_engine.c
Normal file
857
modules/mod_rewrite/ngx_http_apache_rewrite_engine.c
Normal file
@@ -0,0 +1,857 @@
|
||||
/*
|
||||
* Copyright 2026 Alexey Berezhok
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* ngx_http_apache_rewrite_engine.c
|
||||
*
|
||||
* Core rewrite engine for nginx Apache mod_rewrite module.
|
||||
* Ports apply_rewrite_list, apply_rewrite_rule, apply_rewrite_cond
|
||||
* from Apache mod_rewrite.c.
|
||||
*/
|
||||
|
||||
#include "ngx_http_apache_rewrite_engine.h"
|
||||
|
||||
|
||||
/*
|
||||
* Check if a URI is absolute (has scheme://)
|
||||
*/
|
||||
ngx_int_t
|
||||
ngx_rewrite_is_absolute_uri(ngx_str_t *uri)
|
||||
{
|
||||
u_char *p, *end;
|
||||
|
||||
if (uri->len < 4) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
p = uri->data;
|
||||
end = p + uri->len;
|
||||
|
||||
/* Must start with alpha */
|
||||
if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z'))) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (p++; p < end; p++) {
|
||||
if (*p == ':') {
|
||||
if (p + 2 < end && p[1] == '/' && p[2] == '/') {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (!((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z')
|
||||
|| (*p >= '0' && *p <= '9') || *p == '+' || *p == '-'
|
||||
|| *p == '.'))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Split query string out of a rewritten URI.
|
||||
* Handles QSA (append), QSD (discard) flags.
|
||||
*/
|
||||
void
|
||||
ngx_rewrite_splitout_queryargs(ngx_http_request_t *r,
|
||||
ngx_str_t *uri, ngx_int_t flags, ngx_str_t *args)
|
||||
{
|
||||
u_char *q;
|
||||
u_char *search_start;
|
||||
u_char *search_end;
|
||||
|
||||
if (flags & RULEFLAG_QSDISCARD) {
|
||||
ngx_str_null(args);
|
||||
}
|
||||
|
||||
search_start = uri->data;
|
||||
search_end = uri->data + uri->len;
|
||||
|
||||
/* Find the question mark */
|
||||
if (flags & RULEFLAG_QSLAST) {
|
||||
/* Search from end */
|
||||
q = NULL;
|
||||
for (u_char *s = search_end - 1; s >= search_start; s--) {
|
||||
if (*s == '?') {
|
||||
q = s;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* Search from start */
|
||||
q = ngx_strlchr(search_start, search_end, '?');
|
||||
}
|
||||
|
||||
if (q == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Split: uri becomes everything before '?', args is after */
|
||||
if (flags & RULEFLAG_QSAPPEND) {
|
||||
/* Append new query string to existing */
|
||||
if ((size_t)(search_end - q - 1) > 0) {
|
||||
if (args->len > 0) {
|
||||
u_char *combined;
|
||||
size_t new_len = (search_end - q - 1) + 1 + args->len;
|
||||
|
||||
combined = ngx_pnalloc(r->pool, new_len);
|
||||
if (combined) {
|
||||
u_char *cp = combined;
|
||||
cp = ngx_cpymem(cp, q + 1, search_end - q - 1);
|
||||
*cp++ = '&';
|
||||
cp = ngx_cpymem(cp, args->data, args->len);
|
||||
args->data = combined;
|
||||
args->len = new_len;
|
||||
}
|
||||
} else {
|
||||
args->data = q + 1;
|
||||
args->len = search_end - q - 1;
|
||||
}
|
||||
}
|
||||
} else if (!(flags & RULEFLAG_QSDISCARD)) {
|
||||
args->data = q + 1;
|
||||
args->len = search_end - q - 1;
|
||||
}
|
||||
|
||||
uri->len = q - uri->data;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* Apply a single RewriteCond.
|
||||
* Port of Apache apply_rewrite_cond() (mod_rewrite.c:4120-4319).
|
||||
*/
|
||||
ngx_rewrite_cond_rc_e
|
||||
ngx_rewrite_apply_cond(ngx_rewrite_cond_t *cond, ngx_rewrite_ctx_t *ctx)
|
||||
{
|
||||
ngx_str_t input;
|
||||
ngx_int_t rc = COND_RC_NOMATCH;
|
||||
ngx_int_t n;
|
||||
int ovector[NGX_REWRITE_MAX_CAPTURES * 2];
|
||||
ngx_file_info_t fi;
|
||||
|
||||
/* Expand the condition input string */
|
||||
input = ngx_rewrite_expand(&cond->input, ctx, NULL, ctx->r->pool);
|
||||
|
||||
switch (cond->ptype) {
|
||||
|
||||
case CONDPAT_FILE_EXISTS:
|
||||
if (input.len > 0) {
|
||||
u_char buf[NGX_MAX_PATH];
|
||||
ngx_cpystrn(buf, input.data, ngx_min(input.len + 1, NGX_MAX_PATH));
|
||||
if (ngx_file_info(buf, &fi) == NGX_FILE_ERROR) {
|
||||
rc = COND_RC_NOMATCH;
|
||||
} else if (ngx_is_file(&fi)) {
|
||||
rc = COND_RC_MATCH;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CONDPAT_FILE_SIZE:
|
||||
if (input.len > 0) {
|
||||
u_char buf[NGX_MAX_PATH];
|
||||
ngx_cpystrn(buf, input.data, ngx_min(input.len + 1, NGX_MAX_PATH));
|
||||
if (ngx_file_info(buf, &fi) != NGX_FILE_ERROR
|
||||
&& ngx_is_file(&fi) && ngx_file_size(&fi) > 0)
|
||||
{
|
||||
rc = COND_RC_MATCH;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CONDPAT_FILE_DIR:
|
||||
if (input.len > 0) {
|
||||
u_char buf[NGX_MAX_PATH];
|
||||
ngx_cpystrn(buf, input.data, ngx_min(input.len + 1, NGX_MAX_PATH));
|
||||
if (ngx_file_info(buf, &fi) != NGX_FILE_ERROR
|
||||
&& ngx_is_dir(&fi))
|
||||
{
|
||||
rc = COND_RC_MATCH;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CONDPAT_FILE_LINK:
|
||||
if (input.len > 0) {
|
||||
u_char buf[NGX_MAX_PATH];
|
||||
ngx_cpystrn(buf, input.data, ngx_min(input.len + 1, NGX_MAX_PATH));
|
||||
if (ngx_file_info(buf, &fi) != NGX_FILE_ERROR
|
||||
&& ngx_is_link(&fi))
|
||||
{
|
||||
rc = COND_RC_MATCH;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CONDPAT_FILE_XBIT:
|
||||
if (input.len > 0) {
|
||||
u_char buf[NGX_MAX_PATH];
|
||||
ngx_cpystrn(buf, input.data, ngx_min(input.len + 1, NGX_MAX_PATH));
|
||||
if (ngx_file_info(buf, &fi) != NGX_FILE_ERROR
|
||||
&& (ngx_file_access(&fi) & 0111))
|
||||
{
|
||||
rc = COND_RC_MATCH;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case CONDPAT_STR_LT:
|
||||
rc = (ngx_memn2cmp(input.data, cond->pattern.data,
|
||||
input.len, cond->pattern.len) < 0)
|
||||
? COND_RC_MATCH : COND_RC_NOMATCH;
|
||||
break;
|
||||
|
||||
case CONDPAT_STR_LE:
|
||||
rc = (ngx_memn2cmp(input.data, cond->pattern.data,
|
||||
input.len, cond->pattern.len) <= 0)
|
||||
? COND_RC_MATCH : COND_RC_NOMATCH;
|
||||
break;
|
||||
|
||||
case CONDPAT_STR_EQ:
|
||||
if (cond->flags & CONDFLAG_NOCASE) {
|
||||
rc = (input.len == cond->pattern.len
|
||||
&& ngx_strncasecmp(input.data, cond->pattern.data,
|
||||
input.len) == 0)
|
||||
? COND_RC_MATCH : COND_RC_NOMATCH;
|
||||
} else {
|
||||
rc = (input.len == cond->pattern.len
|
||||
&& ngx_memcmp(input.data, cond->pattern.data,
|
||||
input.len) == 0)
|
||||
? COND_RC_MATCH : COND_RC_NOMATCH;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONDPAT_STR_GT:
|
||||
rc = (ngx_memn2cmp(input.data, cond->pattern.data,
|
||||
input.len, cond->pattern.len) > 0)
|
||||
? COND_RC_MATCH : COND_RC_NOMATCH;
|
||||
break;
|
||||
|
||||
case CONDPAT_STR_GE:
|
||||
rc = (ngx_memn2cmp(input.data, cond->pattern.data,
|
||||
input.len, cond->pattern.len) >= 0)
|
||||
? COND_RC_MATCH : COND_RC_NOMATCH;
|
||||
break;
|
||||
|
||||
case CONDPAT_INT_LT:
|
||||
case CONDPAT_INT_LE:
|
||||
case CONDPAT_INT_EQ:
|
||||
case CONDPAT_INT_GT:
|
||||
case CONDPAT_INT_GE:
|
||||
{
|
||||
ngx_int_t a, b;
|
||||
u_char *tmp;
|
||||
|
||||
/* null-terminate for ngx_atoi */
|
||||
tmp = ngx_pnalloc(ctx->r->pool, input.len + 1);
|
||||
if (tmp) {
|
||||
ngx_memcpy(tmp, input.data, input.len);
|
||||
tmp[input.len] = '\0';
|
||||
a = ngx_atoi(input.data, input.len);
|
||||
} else {
|
||||
a = 0;
|
||||
}
|
||||
b = ngx_atoi(cond->pattern.data, cond->pattern.len);
|
||||
|
||||
if (a == NGX_ERROR) a = 0;
|
||||
if (b == NGX_ERROR) b = 0;
|
||||
|
||||
switch (cond->ptype) {
|
||||
case CONDPAT_INT_LT: rc = (a < b); break;
|
||||
case CONDPAT_INT_LE: rc = (a <= b); break;
|
||||
case CONDPAT_INT_EQ: rc = (a == b); break;
|
||||
case CONDPAT_INT_GT: rc = (a > b); break;
|
||||
case CONDPAT_INT_GE: rc = (a >= b); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/* CONDPAT_REGEX — regular expression match */
|
||||
if (cond->regex == NULL) {
|
||||
break;
|
||||
}
|
||||
|
||||
n = ngx_regex_exec(cond->regex, &input,
|
||||
ovector, NGX_REWRITE_MAX_CAPTURES * 2);
|
||||
|
||||
if (n >= 0) {
|
||||
rc = COND_RC_MATCH;
|
||||
|
||||
/* Store condition backrefs (%N) */
|
||||
if (!(cond->flags & CONDFLAG_NOTMATCH)) {
|
||||
ctx->briRC.source = input;
|
||||
ngx_memcpy(ctx->briRC.ovector, ovector, sizeof(ovector));
|
||||
ctx->briRC.ncaptures = (n > 0) ? n : NGX_REWRITE_MAX_CAPTURES;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
/* Handle negation */
|
||||
if (cond->flags & CONDFLAG_NOTMATCH) {
|
||||
rc = !rc;
|
||||
}
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_HTTP, ctx->r->connection->log, 0,
|
||||
"mod_rewrite: RewriteCond input=\"%V\" pattern=\"%V\" "
|
||||
"%s => %s",
|
||||
&input, &cond->pattern,
|
||||
(cond->flags & CONDFLAG_NOCASE) ? "[NC]" : "",
|
||||
rc ? "matched" : "not-matched");
|
||||
|
||||
return rc ? COND_RC_MATCH : COND_RC_NOMATCH;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Apply a single RewriteRule.
|
||||
* Port of Apache apply_rewrite_rule() (mod_rewrite.c:4359-4641).
|
||||
*/
|
||||
ngx_rewrite_rule_rc_e
|
||||
ngx_rewrite_apply_rule(ngx_rewrite_rule_t *rule, ngx_rewrite_ctx_t *ctx, ngx_str_t baseurl, ngx_int_t baseurl_set)
|
||||
{
|
||||
int ovector[NGX_REWRITE_MAX_CAPTURES * 2];
|
||||
ngx_int_t n, rc;
|
||||
ngx_rewrite_cond_t *conds;
|
||||
ngx_uint_t i;
|
||||
ngx_str_t newuri;
|
||||
ngx_http_request_t *r = ctx->r;
|
||||
|
||||
/* Match the URI against the rule pattern */
|
||||
ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
||||
"mod_rewrite: applying pattern \"%V\" to uri \"%V\"",
|
||||
&rule->pattern, &ctx->uri);
|
||||
|
||||
if (rule->regex == NULL) {
|
||||
return RULE_RC_NOMATCH;
|
||||
}
|
||||
|
||||
n = ngx_regex_exec(rule->regex, &ctx->match_uri,
|
||||
ovector, NGX_REWRITE_MAX_CAPTURES * 2);
|
||||
|
||||
rc = (n >= 0) ? 1 : 0;
|
||||
|
||||
/* Check match/notmatch */
|
||||
if (!((rc && !(rule->flags & RULEFLAG_NOTMATCH))
|
||||
|| (!rc && (rule->flags & RULEFLAG_NOTMATCH))))
|
||||
{
|
||||
return RULE_RC_NOMATCH;
|
||||
}
|
||||
|
||||
/* Rule matched — prepare backref context */
|
||||
ngx_str_null(&ctx->briRC.source);
|
||||
ctx->briRC.ncaptures = 0;
|
||||
|
||||
if (rule->flags & RULEFLAG_NOTMATCH) {
|
||||
ngx_str_null(&ctx->briRR.source);
|
||||
ctx->briRR.ncaptures = 0;
|
||||
} else {
|
||||
ctx->briRR.source.data = ngx_pnalloc(r->pool, ctx->uri.len);
|
||||
if (ctx->briRR.source.data) {
|
||||
ngx_memcpy(ctx->briRR.source.data, ctx->match_uri.data, ctx->match_uri.len);
|
||||
ctx->briRR.source.len = ctx->match_uri.len;
|
||||
}
|
||||
ngx_memcpy(ctx->briRR.ovector, ovector, sizeof(ovector));
|
||||
ctx->briRR.ncaptures = (n > 0) ? n : NGX_REWRITE_MAX_CAPTURES;
|
||||
}
|
||||
|
||||
/* Evaluate conditions (AND/OR logic) */
|
||||
if (rule->conditions && rule->conditions->nelts > 0) {
|
||||
conds = rule->conditions->elts;
|
||||
|
||||
for (i = 0; i < rule->conditions->nelts; i++) {
|
||||
ngx_rewrite_cond_t *c = &conds[i];
|
||||
|
||||
rc = ngx_rewrite_apply_cond(c, ctx);
|
||||
|
||||
if (c->flags & CONDFLAG_ORNEXT) {
|
||||
if (!rc) {
|
||||
/* OR: this one failed, try next */
|
||||
continue;
|
||||
} else {
|
||||
/* OR: this one matched, skip remaining OR chain */
|
||||
while (i < rule->conditions->nelts
|
||||
&& conds[i].flags & CONDFLAG_ORNEXT)
|
||||
{
|
||||
i++;
|
||||
}
|
||||
}
|
||||
} else if (!rc) {
|
||||
/* AND: condition failed — rule does not match */
|
||||
return RULE_RC_NOMATCH;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Handle environment variables (E=var:val) */
|
||||
if (rule->env) {
|
||||
ngx_rewrite_data_item_t *env = rule->env;
|
||||
while (env) {
|
||||
ngx_str_t expanded;
|
||||
u_char *colon;
|
||||
|
||||
expanded = ngx_rewrite_expand(&env->data, ctx, rule, r->pool);
|
||||
|
||||
if (expanded.len > 0 && expanded.data[0] == '!') {
|
||||
/* Unset: not directly supported in nginx, skip */
|
||||
} else {
|
||||
colon = ngx_strlchr(expanded.data,
|
||||
expanded.data + expanded.len, ':');
|
||||
if (colon) {
|
||||
ngx_str_t vname, vval;
|
||||
(void)vval;
|
||||
|
||||
vname.data = expanded.data;
|
||||
vname.len = colon - expanded.data;
|
||||
vval.data = colon + 1;
|
||||
vval.len = expanded.len - vname.len - 1;
|
||||
|
||||
/* Store in context for FastCGI integration */
|
||||
if (ctx->env_vars == NULL) {
|
||||
ctx->env_vars = ngx_palloc(r->pool,
|
||||
sizeof(ngx_rewrite_data_item_t));
|
||||
ctx->env_vars->next = NULL;
|
||||
ctx->env_vars->data.data = ngx_pnalloc(r->pool, expanded.len);
|
||||
if (ctx->env_vars->data.data) {
|
||||
ngx_memcpy(ctx->env_vars->data.data, expanded.data, expanded.len);
|
||||
ctx->env_vars->data.len = expanded.len;
|
||||
} else {
|
||||
/* Memory allocation failed - continue without tracking */
|
||||
}
|
||||
} else {
|
||||
/* Append to list */
|
||||
ngx_rewrite_data_item_t *new_env = ngx_palloc(r->pool,
|
||||
sizeof(ngx_rewrite_data_item_t));
|
||||
if (new_env) {
|
||||
new_env->data.data = ngx_pnalloc(r->pool, expanded.len);
|
||||
if (new_env->data.data) {
|
||||
ngx_memcpy(new_env->data.data, expanded.data, expanded.len);
|
||||
new_env->data.len = expanded.len;
|
||||
} else {
|
||||
/* Memory allocation failed */
|
||||
new_env = NULL;
|
||||
}
|
||||
new_env->next = ctx->env_vars;
|
||||
if (new_env) {
|
||||
ctx->env_vars = new_env;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
||||
"mod_rewrite: [E=...] set \"%*s\" => \"%*s\"",
|
||||
vname.len, vname.data, vval.len, vval.data);
|
||||
}
|
||||
}
|
||||
|
||||
env = env->next;
|
||||
}
|
||||
}
|
||||
|
||||
/* No-substitution rule (pattern '-') */
|
||||
if (rule->flags & RULEFLAG_NOSUB) {
|
||||
if (rule->flags & RULEFLAG_STATUS) {
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
||||
"mod_rewrite: forcing status %d (nosub)",
|
||||
rule->forced_responsecode);
|
||||
}
|
||||
return RULE_RC_NOSUB;
|
||||
}
|
||||
|
||||
/* Expand the substitution */
|
||||
newuri = ngx_rewrite_expand(&rule->output, ctx, rule, r->pool);
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
||||
"mod_rewrite: rewrite \"%V\" -> \"%V\" (flags=0x%xd, code=%d)",
|
||||
&ctx->uri, &newuri, rule->flags, rule->forced_responsecode);
|
||||
|
||||
/* Split out query string */
|
||||
{
|
||||
ngx_str_t new_args = r->args;
|
||||
ngx_rewrite_splitout_queryargs(r, &newuri, rule->flags, &new_args);
|
||||
r->args = new_args;
|
||||
}
|
||||
|
||||
/* Check for absolute URI → redirect */
|
||||
if (rule->flags & RULEFLAG_FORCEREDIRECT) {
|
||||
/* Explicit redirect [R] or [R=NNN] */
|
||||
ngx_int_t code = rule->forced_responsecode;
|
||||
if (code == 0) {
|
||||
code = NGX_HTTP_MOVED_TEMPORARILY;
|
||||
}
|
||||
|
||||
/* Fully qualify if needed */
|
||||
if (!ngx_rewrite_is_absolute_uri(&newuri)) {
|
||||
ngx_str_t scheme, host;
|
||||
ngx_uint_t port;
|
||||
u_char *p;
|
||||
size_t len;
|
||||
|
||||
#if (NGX_SSL)
|
||||
if (r->connection->ssl) {
|
||||
ngx_str_set(&scheme, "https");
|
||||
port = 443;
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
ngx_str_set(&scheme, "http");
|
||||
port = 80;
|
||||
}
|
||||
|
||||
ngx_http_core_loc_conf_t *clcf;
|
||||
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
|
||||
/* Use r->headers_in.server if available (has been sanitized - no port) */
|
||||
if (clcf->server_name_in_redirect || r->headers_in.server.len > 0) {
|
||||
if (clcf->server_name_in_redirect) {
|
||||
ngx_http_core_srv_conf_t *cscf;
|
||||
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
|
||||
host = cscf->server_name;
|
||||
} else {
|
||||
host = r->headers_in.server;
|
||||
}
|
||||
} else {
|
||||
ngx_http_core_srv_conf_t *cscf;
|
||||
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
|
||||
host = cscf->server_name;
|
||||
}
|
||||
|
||||
{
|
||||
ngx_uint_t actual_port;
|
||||
actual_port = ngx_inet_get_port(r->connection->local_sockaddr);
|
||||
if (actual_port == port) {
|
||||
port = 0;
|
||||
} else {
|
||||
port = actual_port;
|
||||
}
|
||||
}
|
||||
|
||||
len = scheme.len + 3 + host.len + newuri.len + 3;
|
||||
if (port) {
|
||||
len += 6; /* :NNNNN */
|
||||
}
|
||||
if (baseurl_set) {
|
||||
len += baseurl.len;
|
||||
}
|
||||
|
||||
p = ngx_pnalloc(r->pool, len);
|
||||
if (p) {
|
||||
u_char *start = p;
|
||||
p = ngx_cpymem(p, scheme.data, scheme.len);
|
||||
*p++ = ':'; *p++ = '/'; *p++ = '/';
|
||||
p = ngx_cpymem(p, host.data, host.len);
|
||||
if (port) {
|
||||
p = ngx_sprintf(p, ":%ui", port);
|
||||
}
|
||||
if (baseurl_set && baseurl.len > 0) {
|
||||
if (baseurl.data[0] == '/')
|
||||
p = ngx_cpymem(p, baseurl.data, baseurl.len);
|
||||
else {
|
||||
*p++ = '/';
|
||||
p = ngx_cpymem(p, baseurl.data, baseurl.len);
|
||||
}
|
||||
}
|
||||
if (*(p-1)!='/' && newuri.data[0]!= '/') {
|
||||
*p++ = '/';
|
||||
}
|
||||
p = ngx_cpymem(p, newuri.data, newuri.len);
|
||||
newuri.data = start;
|
||||
newuri.len = p - start;
|
||||
}
|
||||
}
|
||||
|
||||
ctx->redirect_url = newuri;
|
||||
ctx->redirect_code = code;
|
||||
ctx->uri = newuri;
|
||||
return RULE_RC_MATCH;
|
||||
}
|
||||
|
||||
/* Check for implicit redirect (absolute URI in substitution) */
|
||||
if (ngx_rewrite_is_absolute_uri(&newuri)) {
|
||||
ngx_int_t code = rule->forced_responsecode;
|
||||
if (code == 0) {
|
||||
code = NGX_HTTP_MOVED_TEMPORARILY;
|
||||
}
|
||||
|
||||
ctx->redirect_url = newuri;
|
||||
ctx->redirect_code = code;
|
||||
ctx->uri = newuri;
|
||||
return RULE_RC_MATCH;
|
||||
}
|
||||
|
||||
/* Ensure URI starts with '/' */
|
||||
if (newuri.len == 0 || newuri.data[0] != '/') {
|
||||
if (ctx->htaccess.data){
|
||||
u_char *p = ngx_pcalloc(r->pool, ctx->htaccess.len + newuri.len + 4);
|
||||
if (p){
|
||||
*p = '/';
|
||||
u_char *p1 = p + 1;
|
||||
if (ctx->htaccess.len > 0){
|
||||
ngx_memcpy(p1, ctx->htaccess.data, ctx->htaccess.len);
|
||||
p1 = p1 + ctx->htaccess.len;
|
||||
if (ctx->htaccess.data[ctx->htaccess.len-1] != '/') {
|
||||
*p1 = '/';
|
||||
p1++;
|
||||
}
|
||||
}
|
||||
if (newuri.len > 0) {
|
||||
ngx_memcpy(p1, newuri.data, newuri.len);
|
||||
p1 = p1 + newuri.len;
|
||||
}
|
||||
newuri.data = p;
|
||||
newuri.len = p1 - p;
|
||||
}
|
||||
} else {
|
||||
u_char *p = ngx_pcalloc(r->pool, newuri.len + 1);
|
||||
if (p) {
|
||||
*p = '/';
|
||||
if (newuri.len > 0) {
|
||||
ngx_memcpy(p + 1, newuri.data, newuri.len);
|
||||
}
|
||||
newuri.data = p;
|
||||
newuri.len++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ctx->uri = newuri;
|
||||
ngx_str_null(&ctx->redirect_url);
|
||||
ctx->redirect_code = 0;
|
||||
|
||||
return RULE_RC_MATCH;
|
||||
}
|
||||
|
||||
static void
|
||||
ngx_rewrite_save_skip_flag(void *cln)
|
||||
{
|
||||
/* cleanup handler - не используется, просто метка */
|
||||
}
|
||||
|
||||
ngx_int_t
|
||||
ngx_rewrite_set_skip_after_redirect(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_pool_cleanup_t *cln;
|
||||
ngx_rewrite_pool_ctx_t *pctx = NULL;
|
||||
for (cln = r->pool->cleanup; cln; cln = cln->next) {
|
||||
if (cln->handler == ngx_rewrite_save_skip_flag) {
|
||||
pctx = cln->data;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!pctx) {
|
||||
cln = ngx_pool_cleanup_add(r->pool, sizeof(ngx_rewrite_pool_ctx_t));
|
||||
if (cln == NULL) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
pctx = cln->data;
|
||||
}
|
||||
|
||||
/* Сохраняем флаг и текущий URI */
|
||||
pctx->skip_rewrite_after_redirect = 1;
|
||||
|
||||
cln->handler = ngx_rewrite_save_skip_flag; /* просто метка */
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Apply a complete list of rewrite rules.
|
||||
* Port of Apache apply_rewrite_list() (mod_rewrite.c:4647-4817).
|
||||
*
|
||||
* Returns: 0 = no change, ACTION_NORMAL/ACTION_NOESCAPE = changed,
|
||||
* ACTION_STATUS = status set.
|
||||
*/
|
||||
ngx_int_t
|
||||
ngx_rewrite_apply_list(ngx_http_request_t *r, ngx_array_t *rules,
|
||||
ngx_str_t *htaccess, ngx_str_t baseurl, ngx_int_t baseurl_set)
|
||||
{
|
||||
ngx_rewrite_rule_t *entries;
|
||||
ngx_rewrite_rule_t *p;
|
||||
ngx_uint_t i;
|
||||
ngx_int_t changed = 0;
|
||||
ngx_rewrite_rule_rc_e rc;
|
||||
ngx_int_t round = 1;
|
||||
ngx_rewrite_ctx_t *ctx;
|
||||
ngx_pool_cleanup_t *cln;
|
||||
ngx_rewrite_pool_ctx_t *pctx;
|
||||
|
||||
if (r->internal) {
|
||||
for (cln = r->pool->cleanup; cln; cln = cln->next) {
|
||||
if (cln->handler == ngx_rewrite_save_skip_flag) {
|
||||
pctx = cln->data;
|
||||
if (pctx && pctx->skip_rewrite_after_redirect) {
|
||||
pctx->skip_rewrite_after_redirect = 0;
|
||||
/* Skip rewriting on redirected URI */
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rules == NULL || rules->nelts == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ctx = ngx_http_get_module_ctx(r, ngx_http_apache_rewrite_module);
|
||||
if (ctx == NULL) {
|
||||
ctx = ngx_pcalloc(r->pool, sizeof(ngx_rewrite_ctx_t));
|
||||
if (ctx == NULL) {
|
||||
return NGX_HTTP_INTERNAL_SERVER_ERROR;
|
||||
}
|
||||
ngx_http_set_ctx(r, ctx, ngx_http_apache_rewrite_module);
|
||||
}
|
||||
|
||||
/* Check END flag from previous invocation */
|
||||
if (ctx->end) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ctx->r = r;
|
||||
ctx->uri = r->uri;
|
||||
ctx->match_uri = r->uri;
|
||||
ctx->uri_changed = 0;
|
||||
|
||||
ctx->htaccess = ((htaccess) ? (*htaccess) : (ngx_str_t)ngx_null_string);
|
||||
|
||||
if (ctx->htaccess.data && ctx->match_uri.len > 1 && ctx->match_uri.data[0] == '/') {
|
||||
ngx_str_t tmp_str = ngx_null_string;
|
||||
// Allocate buffer for relative URI (strip htaccess directory prefix)
|
||||
if (r->uri.len > (size_t)(ctx->htaccess.len + 1)) {
|
||||
tmp_str.data = ngx_pnalloc(r->pool, r->uri.len - ctx->htaccess.len + 1);
|
||||
if (tmp_str.data != NULL) {
|
||||
// Strip the htaccess directory prefix to get relative URI for pattern matching
|
||||
// Example: /wordpress/wp-json/ -> /wp-json/ when htaccess=/wordpress
|
||||
ngx_memcpy(tmp_str.data, r->uri.data + ctx->htaccess.len + 1,
|
||||
r->uri.len - ctx->htaccess.len - 1);
|
||||
tmp_str.len = r->uri.len - (ctx->htaccess.len + 1);
|
||||
ctx->match_uri = tmp_str;
|
||||
} else {
|
||||
// Fallback: use original URI on allocation failure
|
||||
ctx->match_uri = r->uri;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Use original URI for matching when htaccess not applicable
|
||||
ctx->match_uri = r->uri;
|
||||
}
|
||||
ngx_str_null(&ctx->redirect_url);
|
||||
ctx->redirect_code = 0;
|
||||
|
||||
entries = rules->elts;
|
||||
|
||||
loop:
|
||||
for (i = 0; i < rules->nelts; i++) {
|
||||
p = &entries[i];
|
||||
|
||||
/* Skip on subrequests if NS flag or redirect */
|
||||
if (r->main != r
|
||||
&& (p->flags & (RULEFLAG_IGNOREONSUBREQ | RULEFLAG_FORCEREDIRECT)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Apply this rule */
|
||||
rc = ngx_rewrite_apply_rule(p, ctx, baseurl, baseurl_set);
|
||||
|
||||
if (rc != RULE_RC_NOMATCH) {
|
||||
/* Check for status set */
|
||||
if (rc == RULE_RC_STATUS_SET) {
|
||||
return ACTION_STATUS_SET;
|
||||
}
|
||||
|
||||
/* Status flag [F], [G], or [R=NNN] with nosub */
|
||||
if (p->flags & RULEFLAG_STATUS) {
|
||||
ctx->status_code = p->forced_responsecode;
|
||||
return ACTION_STATUS;
|
||||
}
|
||||
|
||||
/* Track changes for non-nosub rules */
|
||||
if (rc != RULE_RC_NOSUB) {
|
||||
changed = (p->flags & RULEFLAG_NOESCAPE)
|
||||
? ACTION_NOESCAPE : ACTION_NORMAL;
|
||||
}
|
||||
|
||||
/* PassThrough [PT] — let other handlers process */
|
||||
if (p->flags & RULEFLAG_PASSTHROUGH) {
|
||||
r->uri = ctx->uri;
|
||||
ctx->uri_changed = 1;
|
||||
changed = ACTION_NORMAL;
|
||||
break;
|
||||
}
|
||||
|
||||
/* END flag */
|
||||
if (p->flags & RULEFLAG_END) {
|
||||
ctx->end = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* Last rule [L] or proxy [P] */
|
||||
if (p->flags & (RULEFLAG_LASTRULE | RULEFLAG_PROXY)) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* New round [N] */
|
||||
if (p->flags & RULEFLAG_NEWROUND) {
|
||||
ngx_int_t maxrounds = p->maxrounds;
|
||||
if (maxrounds == 0) {
|
||||
maxrounds = NGX_REWRITE_MAX_ROUNDS;
|
||||
}
|
||||
|
||||
if (++round >= maxrounds) {
|
||||
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
|
||||
"mod_rewrite: RewriteRule \"%V\" and URI \"%V\" "
|
||||
"exceeded maximum number of rounds (%d) "
|
||||
"via the [N] flag",
|
||||
&p->pattern, &r->uri, maxrounds);
|
||||
return ACTION_STATUS;
|
||||
}
|
||||
|
||||
/* Update r->uri for next round */
|
||||
r->uri = ctx->uri;
|
||||
ctx->uri_changed = 1;
|
||||
goto loop;
|
||||
}
|
||||
|
||||
/* Skip [S=N] */
|
||||
if (p->skip > 0) {
|
||||
ngx_int_t s = p->skip;
|
||||
while (i < rules->nelts && s > 0) {
|
||||
i++;
|
||||
s--;
|
||||
}
|
||||
}
|
||||
|
||||
/* Chain [C] — continue to next rule */
|
||||
} else {
|
||||
/* No match: if chained, skip rest of chain */
|
||||
while (i < rules->nelts && (p->flags & RULEFLAG_CHAIN)) {
|
||||
i++;
|
||||
if (i < rules->nelts) {
|
||||
p = &entries[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Update r->uri if changed */
|
||||
if (changed && ctx->uri.len > 0 && ctx->redirect_url.len == 0) {
|
||||
r->uri = ctx->uri;
|
||||
ctx->uri_changed = 1;
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
332
modules/mod_rewrite/ngx_http_apache_rewrite_engine.h
Normal file
332
modules/mod_rewrite/ngx_http_apache_rewrite_engine.h
Normal file
@@ -0,0 +1,332 @@
|
||||
/*
|
||||
* Copyright 2026 Alexey Berezhok
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* ngx_http_apache_rewrite_engine.h
|
||||
*
|
||||
* Shared header for the nginx Apache mod_rewrite compatibility module.
|
||||
* Contains all data structures, constants, and function prototypes.
|
||||
*/
|
||||
|
||||
#ifndef _NGX_HTTP_APACHE_REWRITE_ENGINE_H_
|
||||
#define _NGX_HTTP_APACHE_REWRITE_ENGINE_H_
|
||||
|
||||
#include <ngx_config.h>
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_http.h>
|
||||
|
||||
/*
|
||||
* Maximum regex captures (matches AP_MAX_REG_MATCH = 10)
|
||||
*/
|
||||
#define NGX_REWRITE_MAX_CAPTURES 10
|
||||
|
||||
/*
|
||||
* Maximum rounds for [N] flag loop prevention
|
||||
*/
|
||||
#define NGX_REWRITE_MAX_ROUNDS 10000
|
||||
|
||||
/*
|
||||
* RewriteEngine state
|
||||
*/
|
||||
#define ENGINE_DISABLED (1 << 0)
|
||||
#define ENGINE_ENABLED (1 << 1)
|
||||
|
||||
/*
|
||||
* RewriteOptions flags
|
||||
*/
|
||||
#define OPTION_NONE (1 << 0)
|
||||
#define OPTION_INHERIT (1 << 1)
|
||||
#define OPTION_INHERIT_BEFORE (1 << 2)
|
||||
|
||||
/*
|
||||
* RewriteRule flags
|
||||
*/
|
||||
#define RULEFLAG_NONE (1 << 0)
|
||||
#define RULEFLAG_FORCEREDIRECT (1 << 1)
|
||||
#define RULEFLAG_LASTRULE (1 << 2)
|
||||
#define RULEFLAG_NEWROUND (1 << 3)
|
||||
#define RULEFLAG_CHAIN (1 << 4)
|
||||
#define RULEFLAG_IGNOREONSUBREQ (1 << 5)
|
||||
#define RULEFLAG_NOTMATCH (1 << 6)
|
||||
#define RULEFLAG_PROXY (1 << 7)
|
||||
#define RULEFLAG_PASSTHROUGH (1 << 8)
|
||||
#define RULEFLAG_QSAPPEND (1 << 9)
|
||||
#define RULEFLAG_NOCASE (1 << 10)
|
||||
#define RULEFLAG_NOESCAPE (1 << 11)
|
||||
#define RULEFLAG_NOSUB (1 << 12)
|
||||
#define RULEFLAG_STATUS (1 << 13)
|
||||
#define RULEFLAG_ESCAPEBACKREF (1 << 14)
|
||||
#define RULEFLAG_DISCARDPATHINFO (1 << 15)
|
||||
#define RULEFLAG_QSDISCARD (1 << 16)
|
||||
#define RULEFLAG_END (1 << 17)
|
||||
#define RULEFLAG_QSLAST (1 << 19)
|
||||
|
||||
/*
|
||||
* Return codes for apply_rewrite_list
|
||||
*/
|
||||
#define ACTION_NORMAL (1 << 0)
|
||||
#define ACTION_NOESCAPE (1 << 1)
|
||||
#define ACTION_STATUS (1 << 2)
|
||||
#define ACTION_STATUS_SET (1 << 3)
|
||||
|
||||
/*
|
||||
* RewriteCond flags
|
||||
*/
|
||||
#define CONDFLAG_NONE (1 << 0)
|
||||
#define CONDFLAG_NOCASE (1 << 1)
|
||||
#define CONDFLAG_NOTMATCH (1 << 2)
|
||||
#define CONDFLAG_ORNEXT (1 << 3)
|
||||
|
||||
/*
|
||||
* Condition pattern types
|
||||
*/
|
||||
typedef enum {
|
||||
CONDPAT_REGEX = 0,
|
||||
CONDPAT_FILE_EXISTS,
|
||||
CONDPAT_FILE_SIZE,
|
||||
CONDPAT_FILE_LINK,
|
||||
CONDPAT_FILE_DIR,
|
||||
CONDPAT_FILE_XBIT,
|
||||
CONDPAT_STR_LT,
|
||||
CONDPAT_STR_LE,
|
||||
CONDPAT_STR_EQ,
|
||||
CONDPAT_STR_GT,
|
||||
CONDPAT_STR_GE,
|
||||
CONDPAT_INT_LT,
|
||||
CONDPAT_INT_LE,
|
||||
CONDPAT_INT_EQ,
|
||||
CONDPAT_INT_GT,
|
||||
CONDPAT_INT_GE
|
||||
} ngx_rewrite_condpat_type_e;
|
||||
|
||||
/*
|
||||
* Rule return types
|
||||
*/
|
||||
typedef enum {
|
||||
RULE_RC_NOMATCH = 0,
|
||||
RULE_RC_MATCH = 1,
|
||||
RULE_RC_NOSUB = 2,
|
||||
RULE_RC_STATUS_SET = 3
|
||||
} ngx_rewrite_rule_rc_e;
|
||||
|
||||
/*
|
||||
* Condition return types
|
||||
*/
|
||||
typedef enum {
|
||||
COND_RC_NOMATCH = 0,
|
||||
COND_RC_MATCH = 1
|
||||
} ngx_rewrite_cond_rc_e;
|
||||
|
||||
/*
|
||||
* Map types
|
||||
*/
|
||||
#define MAPTYPE_TXT (1 << 0)
|
||||
#define MAPTYPE_INT (1 << 3)
|
||||
#define MAPTYPE_RND (1 << 4)
|
||||
#define MAPTYPE_PRG (1 << 2)
|
||||
|
||||
/*
|
||||
* Backreference info — stores match source + capture offsets
|
||||
*/
|
||||
typedef struct {
|
||||
ngx_str_t source;
|
||||
int ovector[NGX_REWRITE_MAX_CAPTURES * 2];
|
||||
ngx_int_t ncaptures;
|
||||
} ngx_rewrite_backref_t;
|
||||
|
||||
/*
|
||||
* Single linked list for env vars (E=var:val)
|
||||
*/
|
||||
typedef struct ngx_rewrite_data_item_s ngx_rewrite_data_item_t;
|
||||
struct ngx_rewrite_data_item_s {
|
||||
ngx_rewrite_data_item_t *next;
|
||||
ngx_str_t data;
|
||||
};
|
||||
|
||||
/*
|
||||
* RewriteCond entry
|
||||
*/
|
||||
typedef struct {
|
||||
ngx_str_t input; /* input string */
|
||||
ngx_str_t pattern; /* pattern string */
|
||||
ngx_regex_t *regex; /* compiled regex (if regex type) */
|
||||
ngx_int_t flags; /* CONDFLAG_* */
|
||||
ngx_rewrite_condpat_type_e ptype; /* pattern type */
|
||||
} ngx_rewrite_cond_t;
|
||||
|
||||
/*
|
||||
* RewriteRule entry
|
||||
*/
|
||||
typedef struct {
|
||||
ngx_str_t pattern; /* regex pattern string */
|
||||
ngx_regex_t *regex; /* compiled regex */
|
||||
ngx_str_t output; /* substitution string */
|
||||
ngx_int_t flags; /* RULEFLAG_* */
|
||||
ngx_array_t *conditions; /* array of ngx_rewrite_cond_t */
|
||||
ngx_int_t skip; /* S=N skip count */
|
||||
ngx_int_t maxrounds; /* N=limit */
|
||||
ngx_int_t forced_responsecode; /* R=NNN */
|
||||
ngx_rewrite_data_item_t *env; /* E=var:val list */
|
||||
ngx_str_t forced_mimetype; /* T=type */
|
||||
} ngx_rewrite_rule_t;
|
||||
|
||||
/*
|
||||
* htaccess cache linked list entry
|
||||
*/
|
||||
typedef struct htaccess_entry_s {
|
||||
u_char *file_path; /* full path to .htaccess file (string) */
|
||||
time_t mtime; /* last modification time */
|
||||
ngx_array_t *rules; /* array of ngx_rewrite_rule_t */
|
||||
ngx_str_t baseurl;
|
||||
ngx_int_t options;
|
||||
ngx_int_t state;
|
||||
unsigned state_set:1;
|
||||
unsigned options_set:1;
|
||||
unsigned baseurl_set:1;
|
||||
ngx_str_t fallback_path; /* RewriteFallBack path from .htaccess */
|
||||
struct htaccess_entry_s *next; /* linked list next pointer */
|
||||
} htaccess_entry_t;
|
||||
|
||||
/*
|
||||
* Map entry
|
||||
*/
|
||||
typedef struct {
|
||||
ngx_str_t name;
|
||||
ngx_int_t type; /* MAPTYPE_* */
|
||||
ngx_str_t source; /* filename or function name for int */
|
||||
ngx_str_t (*func)(ngx_pool_t *pool, ngx_str_t key);
|
||||
} ngx_rewrite_map_entry_t;
|
||||
|
||||
/*
|
||||
* Per-server configuration
|
||||
*/
|
||||
typedef struct {
|
||||
ngx_int_t state; /* ENGINE_DISABLED | ENGINE_ENABLED */
|
||||
ngx_int_t options;
|
||||
ngx_array_t *rules; /* array of ngx_rewrite_rule_t */
|
||||
ngx_array_t *pending_conds;/* temp: conditions before next rule */
|
||||
ngx_array_t *maps; /* array of ngx_rewrite_map_entry_t */
|
||||
unsigned state_set:1;
|
||||
unsigned options_set:1;
|
||||
ngx_int_t htaccess_enable; // 0|1: enable htaccess parsing
|
||||
ngx_str_t htaccess_name; // file name (default ".htaccess")
|
||||
unsigned htaccess_enable_set:1;
|
||||
unsigned htaccess_name_set:1;
|
||||
|
||||
/* Fallback to index.php with query string after try_files miss */
|
||||
ngx_int_t fallback_to_index; // 0|1: enable fallback mechanism
|
||||
unsigned fallback_to_index_set:1;
|
||||
} ngx_http_apache_rewrite_srv_conf_t;
|
||||
|
||||
/*
|
||||
* Per-location configuration
|
||||
*/
|
||||
typedef struct {
|
||||
ngx_int_t state; /* ENGINE_DISABLED | ENGINE_ENABLED */
|
||||
ngx_int_t options;
|
||||
ngx_array_t *rules; /* array of ngx_rewrite_rule_t */
|
||||
ngx_array_t *pending_conds;/* temp: conditions before next rule */
|
||||
ngx_str_t baseurl; /* RewriteBase */
|
||||
unsigned state_set:1;
|
||||
unsigned options_set:1;
|
||||
unsigned baseurl_set:1;
|
||||
ngx_str_t fallback_path; /* RewriteFallBack path from .htaccess */
|
||||
} ngx_http_apache_rewrite_loc_conf_t;
|
||||
|
||||
/*
|
||||
* Per-request context (stored via ngx_http_set_ctx / ngx_http_get_module_ctx)
|
||||
*/
|
||||
typedef struct {
|
||||
ngx_http_request_t *r;
|
||||
ngx_str_t uri; /* current URI being matched */
|
||||
ngx_str_t match_uri;
|
||||
ngx_str_t htaccess;
|
||||
ngx_rewrite_backref_t briRR; /* rule backrefs ($N) */
|
||||
ngx_rewrite_backref_t briRC; /* condition backrefs (%N) */
|
||||
unsigned end:1; /* END flag set — stop all rewriting */
|
||||
ngx_str_t redirect_url; /* for external redirects */
|
||||
ngx_int_t redirect_code;
|
||||
ngx_int_t status_code; /* forced status from [F], [G], etc. */
|
||||
htaccess_entry_t *htaccess_cache_head; /* head of linked list of cached entries */
|
||||
|
||||
/* Fallback path - from RewriteFallBack in .htaccess or default /index.php */
|
||||
ngx_str_t fallback_path;
|
||||
|
||||
/* Environment variables set by [E=VAR:VAL] flags */
|
||||
/* These persist across request phases for FastCGI integration */
|
||||
ngx_rewrite_data_item_t *env_vars;
|
||||
ngx_int_t uri_changed;
|
||||
} ngx_rewrite_ctx_t;
|
||||
|
||||
/* modules/mod_rewrite/ngx_http_apache_rewrite_pool_ctx.h */
|
||||
typedef struct {
|
||||
ngx_flag_t skip_rewrite_after_redirect; /* флаг пропуска rewrite */
|
||||
} ngx_rewrite_pool_ctx_t;
|
||||
|
||||
/*
|
||||
* htaccess pool cleanup context - stores cached entries when request ctx is cleared
|
||||
*/
|
||||
typedef struct {
|
||||
htaccess_entry_t *htaccess_cache_entries_head; /* head of linked list of cached entries (saved in pool) */
|
||||
} ngx_htaccess_pool_cleanup_ctx_t;
|
||||
|
||||
/*
|
||||
* Module extern
|
||||
*/
|
||||
extern ngx_module_t ngx_http_apache_rewrite_module;
|
||||
|
||||
|
||||
/* --- Engine functions (ngx_http_apache_rewrite_engine.c) --- */
|
||||
|
||||
ngx_int_t ngx_rewrite_apply_list(ngx_http_request_t *r,
|
||||
ngx_array_t *rules, ngx_str_t *htaccess, ngx_str_t baseurl, ngx_int_t baseurl_set);
|
||||
|
||||
ngx_rewrite_rule_rc_e ngx_rewrite_apply_rule(ngx_rewrite_rule_t *rule,
|
||||
ngx_rewrite_ctx_t *ctx, ngx_str_t baseurl, ngx_int_t baseurl_set);
|
||||
|
||||
ngx_rewrite_cond_rc_e ngx_rewrite_apply_cond(ngx_rewrite_cond_t *cond,
|
||||
ngx_rewrite_ctx_t *ctx);
|
||||
|
||||
void ngx_rewrite_splitout_queryargs(ngx_http_request_t *r,
|
||||
ngx_str_t *uri, ngx_int_t flags, ngx_str_t *args);
|
||||
|
||||
ngx_int_t ngx_rewrite_is_absolute_uri(ngx_str_t *uri);
|
||||
|
||||
|
||||
/* --- Expand functions (ngx_http_apache_rewrite_expand.c) --- */
|
||||
|
||||
ngx_str_t ngx_rewrite_expand(ngx_str_t *input, ngx_rewrite_ctx_t *ctx,
|
||||
ngx_rewrite_rule_t *rule, ngx_pool_t *pool);
|
||||
|
||||
|
||||
/* --- Variable functions (ngx_http_apache_rewrite_variable.c) --- */
|
||||
|
||||
ngx_str_t ngx_rewrite_lookup_variable(ngx_str_t *var,
|
||||
ngx_rewrite_ctx_t *ctx);
|
||||
|
||||
|
||||
/* --- Map functions (ngx_http_apache_rewrite_map.c) --- */
|
||||
|
||||
ngx_str_t ngx_rewrite_lookup_map(ngx_http_request_t *r,
|
||||
ngx_http_apache_rewrite_srv_conf_t *sconf,
|
||||
ngx_str_t *mapname, ngx_str_t *key);
|
||||
|
||||
ngx_str_t ngx_rewrite_map_tolower(ngx_pool_t *pool, ngx_str_t key);
|
||||
ngx_str_t ngx_rewrite_map_toupper(ngx_pool_t *pool, ngx_str_t key);
|
||||
ngx_str_t ngx_rewrite_map_escape(ngx_pool_t *pool, ngx_str_t key);
|
||||
ngx_str_t ngx_rewrite_map_unescape(ngx_pool_t *pool, ngx_str_t key);
|
||||
|
||||
/* reuest cache functions */
|
||||
|
||||
ngx_int_t
|
||||
ngx_rewrite_set_skip_after_redirect(ngx_http_request_t *r);
|
||||
|
||||
|
||||
#endif /* _NGX_HTTP_APACHE_REWRITE_ENGINE_H_ */
|
||||
337
modules/mod_rewrite/ngx_http_apache_rewrite_expand.c
Normal file
337
modules/mod_rewrite/ngx_http_apache_rewrite_expand.c
Normal file
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* Copyright 2026 Alexey Berezhok
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* ngx_http_apache_rewrite_expand.c
|
||||
*
|
||||
* String expansion for Apache mod_rewrite substitutions.
|
||||
* Handles: \\x (escape), $N (rule backrefs), %N (cond backrefs),
|
||||
* %{VARNAME} (variables), ${MAP:KEY|DEFAULT} (map lookups).
|
||||
*/
|
||||
|
||||
#include "ngx_http_apache_rewrite_engine.h"
|
||||
|
||||
|
||||
/*
|
||||
* Result fragment linked list for efficient string building.
|
||||
*/
|
||||
typedef struct ngx_rewrite_result_s ngx_rewrite_result_t;
|
||||
|
||||
struct ngx_rewrite_result_s {
|
||||
ngx_rewrite_result_t *next;
|
||||
u_char *data;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
#define SMALL_EXPANSION 5
|
||||
|
||||
|
||||
/*
|
||||
* Find closing curly brace with nesting support.
|
||||
*/
|
||||
static u_char *
|
||||
ngx_rewrite_find_closing_curly(u_char *s, u_char *end)
|
||||
{
|
||||
ngx_uint_t depth = 1;
|
||||
|
||||
for (; s < end; s++) {
|
||||
if (*s == '}') {
|
||||
if (--depth == 0) {
|
||||
return s;
|
||||
}
|
||||
} else if (*s == '{') {
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Find a character inside curly braces (at depth 1).
|
||||
*/
|
||||
static u_char *
|
||||
ngx_rewrite_find_char_in_curlies(u_char *s, u_char *end, u_char c)
|
||||
{
|
||||
ngx_uint_t depth = 1;
|
||||
|
||||
for (; s < end; s++) {
|
||||
if (*s == c && depth == 1) {
|
||||
return s;
|
||||
}
|
||||
if (*s == '}') {
|
||||
if (--depth == 0) {
|
||||
return NULL;
|
||||
}
|
||||
} else if (*s == '{') {
|
||||
depth++;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Expand a substitution string.
|
||||
*
|
||||
* Port of Apache do_expand() (mod_rewrite.c:2417-2677).
|
||||
* Single-pass expansion for security.
|
||||
*/
|
||||
ngx_str_t
|
||||
ngx_rewrite_expand(ngx_str_t *input, ngx_rewrite_ctx_t *ctx,
|
||||
ngx_rewrite_rule_t *rule, ngx_pool_t *pool)
|
||||
{
|
||||
ngx_str_t result_str;
|
||||
ngx_rewrite_result_t sresult[SMALL_EXPANSION];
|
||||
ngx_rewrite_result_t *result, *current;
|
||||
ngx_uint_t spc = 0;
|
||||
u_char *p, *inp_end, *c;
|
||||
size_t span, outlen;
|
||||
ngx_http_apache_rewrite_srv_conf_t *sconf;
|
||||
|
||||
if (input->len == 0) {
|
||||
ngx_str_null(&result_str);
|
||||
return result_str;
|
||||
}
|
||||
|
||||
inp_end = input->data + input->len;
|
||||
|
||||
/* Find first special character */
|
||||
for (p = input->data; p < inp_end; p++) {
|
||||
if (*p == '\\' || *p == '$' || *p == '%') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
span = p - input->data;
|
||||
|
||||
/* Fast path: no specials */
|
||||
if (p >= inp_end) {
|
||||
result_str.data = ngx_pnalloc(pool, input->len);
|
||||
if (result_str.data == NULL) {
|
||||
ngx_str_null(&result_str);
|
||||
return result_str;
|
||||
}
|
||||
ngx_memcpy(result_str.data, input->data, input->len);
|
||||
result_str.len = input->len;
|
||||
return result_str;
|
||||
}
|
||||
|
||||
/* Initialize result list */
|
||||
result = current = &sresult[spc++];
|
||||
current->next = NULL;
|
||||
current->data = input->data;
|
||||
current->len = span;
|
||||
outlen = span;
|
||||
|
||||
/* Process specials */
|
||||
do {
|
||||
/* Advance to next node if current has content */
|
||||
if (current->len) {
|
||||
if (spc < SMALL_EXPANSION) {
|
||||
current->next = &sresult[spc++];
|
||||
} else {
|
||||
current->next = ngx_palloc(pool,
|
||||
sizeof(ngx_rewrite_result_t));
|
||||
if (current->next == NULL) {
|
||||
ngx_str_null(&result_str);
|
||||
return result_str;
|
||||
}
|
||||
}
|
||||
current = current->next;
|
||||
current->next = NULL;
|
||||
current->len = 0;
|
||||
}
|
||||
|
||||
/* Escaped character: \x → literal x */
|
||||
if (*p == '\\') {
|
||||
current->len = 1;
|
||||
outlen++;
|
||||
p++;
|
||||
if (p >= inp_end) {
|
||||
current->data = p - 1;
|
||||
break;
|
||||
}
|
||||
current->data = p;
|
||||
p++;
|
||||
}
|
||||
|
||||
/* Variable or map lookup: %{...} or ${...} */
|
||||
else if (p + 1 < inp_end && p[1] == '{') {
|
||||
u_char *endp;
|
||||
|
||||
endp = ngx_rewrite_find_closing_curly(p + 2, inp_end);
|
||||
if (endp == NULL) {
|
||||
/* No closing brace, copy literally */
|
||||
current->len = 2;
|
||||
current->data = p;
|
||||
outlen += 2;
|
||||
p += 2;
|
||||
}
|
||||
/* %{VARNAME} — variable lookup */
|
||||
else if (*p == '%') {
|
||||
ngx_str_t vname, val;
|
||||
|
||||
vname.data = p + 2;
|
||||
vname.len = endp - (p + 2);
|
||||
val = ngx_rewrite_lookup_variable(&vname, ctx);
|
||||
|
||||
current->len = val.len;
|
||||
current->data = val.data;
|
||||
outlen += val.len;
|
||||
p = endp + 1;
|
||||
}
|
||||
/* ${MAP:KEY|DEFAULT} — map lookup */
|
||||
else { /* *p == '$' */
|
||||
u_char *key_start;
|
||||
|
||||
key_start = ngx_rewrite_find_char_in_curlies(p + 2, endp, ':');
|
||||
if (key_start == NULL) {
|
||||
/* No colon, copy literally */
|
||||
current->len = 2;
|
||||
current->data = p;
|
||||
outlen += 2;
|
||||
p += 2;
|
||||
} else {
|
||||
ngx_str_t map_name, map_key, map_dflt, map_result;
|
||||
ngx_str_t key_expanded;
|
||||
u_char *dflt_start;
|
||||
|
||||
map_name.data = p + 2;
|
||||
map_name.len = key_start - (p + 2);
|
||||
|
||||
key_start++; /* skip ':' */
|
||||
|
||||
/* Find default separator '|' */
|
||||
dflt_start = ngx_rewrite_find_char_in_curlies(
|
||||
key_start, endp, '|');
|
||||
|
||||
if (dflt_start) {
|
||||
map_key.data = key_start;
|
||||
map_key.len = dflt_start - key_start;
|
||||
map_dflt.data = dflt_start + 1;
|
||||
map_dflt.len = endp - (dflt_start + 1);
|
||||
} else {
|
||||
map_key.data = key_start;
|
||||
map_key.len = endp - key_start;
|
||||
ngx_str_null(&map_dflt);
|
||||
}
|
||||
|
||||
/* Recursively expand the key */
|
||||
key_expanded = ngx_rewrite_expand(&map_key, ctx,
|
||||
rule, pool);
|
||||
|
||||
sconf = ngx_http_get_module_srv_conf(ctx->r,
|
||||
ngx_http_apache_rewrite_module);
|
||||
|
||||
map_result = ngx_rewrite_lookup_map(ctx->r, sconf,
|
||||
&map_name, &key_expanded);
|
||||
|
||||
if (map_result.len == 0 && map_dflt.len > 0) {
|
||||
map_result = ngx_rewrite_expand(&map_dflt, ctx,
|
||||
rule, pool);
|
||||
}
|
||||
|
||||
current->len = map_result.len;
|
||||
current->data = map_result.data;
|
||||
outlen += map_result.len;
|
||||
p = endp + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Backreference: $N or %N */
|
||||
else if (p + 1 < inp_end
|
||||
&& p[1] >= '0' && p[1] <= '9')
|
||||
{
|
||||
ngx_int_t n = p[1] - '0';
|
||||
ngx_rewrite_backref_t *bri;
|
||||
|
||||
if (*p == '$') {
|
||||
bri = &ctx->briRR;
|
||||
} else {
|
||||
bri = &ctx->briRC;
|
||||
}
|
||||
|
||||
if (bri->source.data
|
||||
&& n < bri->ncaptures
|
||||
&& bri->ovector[n * 2] >= 0
|
||||
&& bri->ovector[n * 2 + 1] > bri->ovector[n * 2])
|
||||
{
|
||||
span = bri->ovector[n * 2 + 1] - bri->ovector[n * 2];
|
||||
current->len = span;
|
||||
current->data = bri->source.data + bri->ovector[n * 2];
|
||||
outlen += span;
|
||||
}
|
||||
|
||||
p += 2;
|
||||
}
|
||||
|
||||
/* Not a special, copy literally */
|
||||
else {
|
||||
current->len = 1;
|
||||
current->data = p;
|
||||
outlen++;
|
||||
p++;
|
||||
}
|
||||
|
||||
/* Find next stretch of non-special characters */
|
||||
if (p < inp_end) {
|
||||
u_char *start = p;
|
||||
while (p < inp_end && *p != '\\' && *p != '$' && *p != '%') {
|
||||
p++;
|
||||
}
|
||||
span = p - start;
|
||||
if (span > 0) {
|
||||
if (current->len) {
|
||||
if (spc < SMALL_EXPANSION) {
|
||||
current->next = &sresult[spc++];
|
||||
} else {
|
||||
current->next = ngx_palloc(pool,
|
||||
sizeof(ngx_rewrite_result_t));
|
||||
if (current->next == NULL) {
|
||||
ngx_str_null(&result_str);
|
||||
return result_str;
|
||||
}
|
||||
}
|
||||
current = current->next;
|
||||
current->next = NULL;
|
||||
}
|
||||
current->data = start;
|
||||
current->len = span;
|
||||
outlen += span;
|
||||
}
|
||||
}
|
||||
|
||||
} while (p < inp_end);
|
||||
|
||||
/* Assemble result */
|
||||
c = ngx_pnalloc(pool, outlen);
|
||||
if (c == NULL) {
|
||||
ngx_str_null(&result_str);
|
||||
return result_str;
|
||||
}
|
||||
|
||||
result_str.data = c;
|
||||
result_str.len = outlen;
|
||||
|
||||
current = result;
|
||||
while (current) {
|
||||
if (current->len) {
|
||||
ngx_memcpy(c, current->data, current->len);
|
||||
c += current->len;
|
||||
}
|
||||
current = current->next;
|
||||
}
|
||||
|
||||
return result_str;
|
||||
}
|
||||
387
modules/mod_rewrite/ngx_http_apache_rewrite_fastcgi.c
Normal file
387
modules/mod_rewrite/ngx_http_apache_rewrite_fastcgi.c
Normal file
@@ -0,0 +1,387 @@
|
||||
/*
|
||||
* Copyright 2026 Alexey Berezhok
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* ngx_http_apache_rewrite_fastcgi.c
|
||||
*
|
||||
* FastCGI integration for Apache mod_rewrite module.
|
||||
* Passes environment variables set by [E=VAR:VAL] flags in RewriteRule
|
||||
* to PHP/FastCGI contexts via nginx variables.
|
||||
*
|
||||
* This file provides:
|
||||
* - ngx_rewrite_get_env_var() - lookup function for env vars
|
||||
* - ngx_rewrite_create_env_variable() - register custom variable handler
|
||||
* - Automatic passing mechanism to FastCGI params (auto mode)
|
||||
*/
|
||||
|
||||
#include "ngx_http_apache_rewrite_engine.h"
|
||||
|
||||
|
||||
/* ============================================================
|
||||
* Environment Variable Storage in Context
|
||||
* ============================================================ */
|
||||
struct ngx_rewrite_env_ctx_s {
|
||||
ngx_str_t env_value; /* Final value for this request context */
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
* Custom variable handler for E=VAR:VAL variables.
|
||||
* Returns the value from ctx->env_vars list.
|
||||
*/
|
||||
static ngx_int_t __attribute__((unused))
|
||||
ngx_rewrite_env_variable_handler(ngx_http_request_t *r,
|
||||
ngx_http_variable_value_t *v, void *data)
|
||||
{
|
||||
ngx_rewrite_ctx_t *ctx;
|
||||
|
||||
v->valid = 1;
|
||||
v->no_cacheable = 0;
|
||||
|
||||
ctx = ngx_http_get_module_ctx(r, ngx_http_apache_rewrite_module);
|
||||
|
||||
if (!ctx || !ctx->env_vars) {
|
||||
v->not_found = 1;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
/* Lookup by name */
|
||||
u_char *colon;
|
||||
const char *var_name = (const char *)v->data;
|
||||
size_t var_name_len = v->len;
|
||||
|
||||
ngx_rewrite_data_item_t *env = ctx->env_vars;
|
||||
while (env) {
|
||||
if (!env->data.data || env->data.len == 0) {
|
||||
env = env->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Check format VAR:VAL */
|
||||
colon = ngx_strlchr(env->data.data,
|
||||
(u_char *)(env->data.data + env->data.len), ':');
|
||||
|
||||
if (!colon) {
|
||||
env = env->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t name_len = colon - env->data.data;
|
||||
u_char *var_name_lowered = ngx_pnalloc(r->pool, var_name_len);
|
||||
for (size_t i = 0; i < var_name_len; i++) {
|
||||
var_name_lowered[i] = ngx_tolower(var_name[i]);
|
||||
}
|
||||
|
||||
/* Compare names case-insensitively */
|
||||
if ((name_len == var_name_len &&
|
||||
ngx_strncmp((char *)colon + 1, (char *)var_name_lowered, name_len) == 0) ||
|
||||
strcmp((const char *)(env->data.data), var_name) == 0) {
|
||||
|
||||
/* Found matching env var */
|
||||
u_char *p = v->data;
|
||||
while (*p != ':' && p < v->data + v->len) {
|
||||
p++;
|
||||
}
|
||||
|
||||
if (p < v->data + v->len) {
|
||||
/* Extract value after colon */
|
||||
size_t value_len = env->data.len - name_len - 1;
|
||||
const u_char *value_start = colon + 1;
|
||||
u_char *value_copy = ngx_pnalloc(r->pool, value_len);
|
||||
if (!value_copy) {
|
||||
v->not_found = 1;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_memcpy(value_copy, value_start, value_len);
|
||||
|
||||
v->data = (u_char *)value_copy;
|
||||
v->len = value_len;
|
||||
return NGX_OK;
|
||||
}
|
||||
}
|
||||
|
||||
env = env->next;
|
||||
}
|
||||
|
||||
/* Not found */
|
||||
v->not_found = 1;
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Register a custom variable from E=VAR:VAL format.
|
||||
* Creates nginx variable $var_name and links to handler.
|
||||
*/
|
||||
ngx_int_t
|
||||
ngx_rewrite_create_env_variable(ngx_http_request_t *r,
|
||||
const char *name, size_t name_len, const u_char *value, size_t value_len)
|
||||
{
|
||||
ngx_str_t vname;
|
||||
ngx_uint_t key;
|
||||
|
||||
/* Create nginx variable name: $var_name */
|
||||
vname.len = name_len + 1; /* +1 for '$' prefix */
|
||||
vname.data = (u_char *)ngx_palloc(r->pool, vname.len + 1);
|
||||
if (!vname.data) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
vname.data[0] = '$';
|
||||
ngx_memcpy(vname.data + 1, name, name_len);
|
||||
/* Remove underscores for nginx var format (HTTP_AUTH -> http-auth) */
|
||||
for (size_t i = 1; i < vname.len; i++) {
|
||||
if (vname.data[i] == '_') {
|
||||
vname.data[i] = '-';
|
||||
} else {
|
||||
vname.data[i] = ngx_tolower(vname.data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* Calculate hash */
|
||||
key = ngx_hash_strlow(vname.data, vname.data, vname.len);
|
||||
|
||||
/* Lookup/create variable */
|
||||
ngx_http_variable_value_t *vv;
|
||||
vv = ngx_http_get_variable(r, &vname, key);
|
||||
|
||||
if (!vv) {
|
||||
/* Variable doesn't exist - this shouldn't happen normally */
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/* Set the value */
|
||||
vv->data = (u_char *)value;
|
||||
vv->len = value_len;
|
||||
vv->valid = 1;
|
||||
vv->no_cacheable = 0;
|
||||
vv->not_found = 0;
|
||||
|
||||
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
||||
"mod_rewrite-fastcgi: Created variable \"%V\"", &vname);
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
/* ============================================================
|
||||
* Lookup Functions for Downstream Modules (e.g., FastCGI)
|
||||
* ============================================================ */
|
||||
|
||||
/**
|
||||
* Get an environment variable set by mod_rewrite [E=...] flag.
|
||||
* Returns the value or null if not found/invalid.
|
||||
*/
|
||||
ngx_str_t
|
||||
ngx_rewrite_get_env_var(ngx_http_request_t *r, const char *name, size_t name_len)
|
||||
{
|
||||
ngx_uint_t key;
|
||||
ngx_str_t vname;
|
||||
ngx_http_variable_value_t *vv;
|
||||
|
||||
/* Lookup by name - handle underscores to dashes */
|
||||
u_char *lowered = ngx_pnalloc(r->pool, name_len + 1);
|
||||
for (size_t i = 0; i < name_len; i++) {
|
||||
lowered[i] = ngx_tolower(name[i]);
|
||||
}
|
||||
|
||||
vname.data = lowered;
|
||||
vname.len = name_len;
|
||||
key = ngx_hash_strlow(vname.data, vname.data, vname.len);
|
||||
|
||||
vv = ngx_http_get_variable(r, &vname, key);
|
||||
|
||||
if (!vv || vv->not_found) {
|
||||
/* Variable not found */
|
||||
return (ngx_str_t){ 0, NULL };
|
||||
}
|
||||
|
||||
/* Return a copy since we can't guarantee the data persists */
|
||||
ngx_str_t result;
|
||||
result.len = vv->len;
|
||||
result.data = ngx_pnalloc(r->pool, vv->len);
|
||||
if (!result.data) {
|
||||
return (ngx_str_t){ 0, NULL };
|
||||
}
|
||||
|
||||
ngx_memcpy(result.data, vv->data, vv->len);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert ALL environment variables from mod_rewrite to HTTP headers.
|
||||
* This is called in CONTENT_PHASE handler to pass E vars to FastCGI/PHP.
|
||||
*
|
||||
* Apache PHP sees $_SERVER['HTTP_*'] for all HTTP headers automatically.
|
||||
* So we convert E=VAR:VAL -> HTTP header r->headers_in.VAR
|
||||
*/
|
||||
ngx_int_t
|
||||
ngx_rewrite_add_env_as_headers(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_rewrite_ctx_t *ctx;
|
||||
|
||||
ctx = ngx_http_get_module_ctx(r, ngx_http_apache_rewrite_module);
|
||||
|
||||
if (!ctx || !ctx->env_vars) {
|
||||
/* No env vars from mod_rewrite - nothing to convert */
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
||||
"mod_rewrite-http-headers: Converting E-vars from ctx");
|
||||
|
||||
/* Iterate over all environment variables */
|
||||
ngx_rewrite_data_item_t *env = ctx->env_vars;
|
||||
|
||||
while (env) {
|
||||
u_char *colon;
|
||||
|
||||
if (!env->data.data || env->data.len == 0) {
|
||||
env = env->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Split VAR:VAL */
|
||||
colon = ngx_strlchr(env->data.data,
|
||||
(u_char *)(env->data.data + env->data.len), ':');
|
||||
|
||||
if (!colon) {
|
||||
/* No colon - invalid format, skip */
|
||||
env = env->next;
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t var_name_len = colon - env->data.data;
|
||||
const u_char *value_start = colon + 1;
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
||||
"mod_rewrite-http-headers: E-var \"%*s\" => \"%*s\"",
|
||||
var_name_len, env->data.data,
|
||||
env->data.len - var_name_len - 1, value_start);
|
||||
|
||||
/* Convert header names for PHP access via $_SERVER['HTTP_*']
|
||||
* Apache behavior: E=VAR sets environment variable VAR.
|
||||
* FastCGI/PHP sees it as $_SERVER[VAR].
|
||||
* For HTTP_* headers (like HTTP_AUTHORIZATION), we need to set them correctly. */
|
||||
|
||||
u_char *header_name;
|
||||
size_t header_len;
|
||||
|
||||
/* Check if the var already starts with "HTTP_" - don't double-prefix */
|
||||
if (var_name_len >= 5 && ngx_strncasecmp(env->data.data, (u_char *)"HTTP_", 5) == 0) {
|
||||
/* Already HTTP_* format - use as-is for $_SERVER[HTTP_*] */
|
||||
header_name = ngx_pnalloc(r->pool, var_name_len + 1);
|
||||
if (!header_name) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
/* Copy VAR name and convert to uppercase with underscores */
|
||||
for (size_t j = 0; j < var_name_len; j++) {
|
||||
char ch = ((char *)env->data.data)[j];
|
||||
if (ch == '-') {
|
||||
header_name[j] = '_';
|
||||
} else {
|
||||
header_name[j] = ngx_toupper(ch);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set without HTTP_ prefix since already has it */
|
||||
header_len = var_name_len;
|
||||
} else {
|
||||
/* Need to add HTTP_ prefix for non-HTTP vars */
|
||||
header_name = ngx_pnalloc(r->pool, var_name_len + 6);
|
||||
if (!header_name) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
size_t i = 0;
|
||||
header_name[i++] = 'H';
|
||||
header_name[i++] = 'T';
|
||||
header_name[i++] = 'T';
|
||||
header_name[i++] = 'P';
|
||||
header_name[i++] = '_';
|
||||
|
||||
/* Copy VAR name and convert to uppercase with underscores */
|
||||
for (size_t j = 0; j < var_name_len; j++) {
|
||||
char ch = ((char *)env->data.data)[j];
|
||||
if (ch == '-') {
|
||||
header_name[i++] = '_';
|
||||
} else {
|
||||
header_name[i] = ngx_toupper(ch);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
header_len = i;
|
||||
}
|
||||
|
||||
/* Create HTTP_* header entry */
|
||||
u_char *header_value_copy = ngx_pnalloc(r->pool, env->data.len - var_name_len - 1 + 1);
|
||||
if (!header_value_copy) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
ngx_memcpy(header_value_copy, value_start, env->data.len - var_name_len - 1);
|
||||
header_value_copy[env->data.len - var_name_len - 1] = '\0';
|
||||
|
||||
/* Add header to r->headers_in */
|
||||
ngx_table_elt_t *h = ngx_list_push(&r->headers_in.headers);
|
||||
if (!h) {
|
||||
return NGX_ERROR;
|
||||
}
|
||||
|
||||
h->hash = 1;
|
||||
h->key.len = header_len;
|
||||
h->key.data = header_name;
|
||||
h->value.len = env->data.len - var_name_len - 1;
|
||||
h->value.data = header_value_copy;
|
||||
|
||||
ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
||||
"mod_rewrite-http-headers: Added HTTP header \"%*s\" => \"%*s\"",
|
||||
header_len, header_name, h->value.len, h->value.data);
|
||||
|
||||
env = env->next;
|
||||
}
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
/* ============================================================
|
||||
* Module Hooks (Registered via postconfiguration)
|
||||
* ============================================================ */
|
||||
|
||||
/**
|
||||
* Register handlers for fastcgi phase integration.
|
||||
* This is called during module initialization.
|
||||
*/
|
||||
ngx_int_t
|
||||
ngx_http_apache_rewrite_fastcgi_register(ngx_http_request_t *r)
|
||||
{
|
||||
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
|
||||
"mod_rewrite-fastcgi: Auto-integration mode enabled");
|
||||
|
||||
return NGX_OK;
|
||||
}
|
||||
|
||||
|
||||
/* ============================================================
|
||||
* Public API Export Header
|
||||
* ============================================================ */
|
||||
|
||||
/* These functions can be called from other modules or nginx config directives */
|
||||
extern ngx_str_t ngx_rewrite_get_env_var(ngx_http_request_t *r, const char *name, size_t name_len);
|
||||
extern ngx_int_t ngx_rewrite_create_env_variable(ngx_http_request_t *r,
|
||||
const char *name, size_t name_len, const u_char *value, size_t value_len);
|
||||
|
||||
extern ngx_int_t ngx_rewrite_add_env_as_headers(ngx_http_request_t *r);
|
||||
90
modules/mod_rewrite/ngx_http_apache_rewrite_fastcgi.h
Normal file
90
modules/mod_rewrite/ngx_http_apache_rewrite_fastcgi.h
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Copyright 2026 Alexey Berezhok
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* ngx_http_apache_rewrite_fastcgi.h
|
||||
*
|
||||
* FastCGI integration header for Apache mod_rewrite compatibility module.
|
||||
*/
|
||||
|
||||
#ifndef _NGX_HTTP_APACHE_REWRITE_FASTCGI_H_
|
||||
#define _NGX_HTTP_APACHE_REWRITE_FASTCGI_H_
|
||||
|
||||
#include <ngx_core.h>
|
||||
#include <ngx_http.h>
|
||||
|
||||
|
||||
/* ============================================================
|
||||
* Public API Functions
|
||||
* ============================================================ */
|
||||
|
||||
/**
|
||||
* Lookup an environment variable set by [E=VAR:VAL] flag.
|
||||
* Returns the value or ngx_null_string if not found.
|
||||
*
|
||||
* @param r nginx request
|
||||
* @param name variable name (e.g., "HTTP_AUTHORIZATION")
|
||||
* @param name_len length of name
|
||||
* @return ngx_str_t containing the value, or ngx_null_string on failure
|
||||
*/
|
||||
extern ngx_str_t ngx_rewrite_get_env_var(ngx_http_request_t *r, const char *name, size_t name_len);
|
||||
|
||||
|
||||
/**
|
||||
* Create/register a custom variable for FastCGI.
|
||||
* Sets up nginx variable $var_name with given value.
|
||||
*
|
||||
* @param r nginx request
|
||||
* @param name variable name (without prefix)
|
||||
* @param name_len length of name
|
||||
* @param value value string to set
|
||||
* @param value_len length of value
|
||||
* @return NGX_OK on success, NGX_ERROR on failure
|
||||
*/
|
||||
extern ngx_int_t ngx_rewrite_create_env_variable(ngx_http_request_t *r,
|
||||
const char *name, size_t name_len, const u_char *value, size_t value_len);
|
||||
|
||||
|
||||
/**
|
||||
* Automatically add ALL [E=...] environment variables as HTTP headers.
|
||||
* Called in CONTENT_PHASE to pass vars without manual nginx config.
|
||||
* PHP/FastCGI will see these as $_SERVER['HTTP_*'] automatically.
|
||||
*
|
||||
* @param r nginx request containing ctx->env_vars
|
||||
* @return NGX_OK on success, NGX_ERROR on failure
|
||||
*/
|
||||
|
||||
extern ngx_int_t ngx_rewrite_add_env_as_headers(ngx_http_request_t *r);
|
||||
|
||||
|
||||
/**
|
||||
* Register module hooks for FastCGI integration.
|
||||
* Called during postconfiguration.
|
||||
*
|
||||
* @param cf nginx configuration context
|
||||
* @return NGX_OK on success, NGX_ERROR on failure
|
||||
*/
|
||||
extern ngx_int_t ngx_http_apache_rewrite_fastcgi_register(ngx_http_request_t *r);
|
||||
|
||||
|
||||
/* ============================================================
|
||||
* Compile-time Options
|
||||
* ============================================================ */
|
||||
|
||||
/* Enable FastCGI integration - defines NGX_FASTCGI_INTEGRATION_ENABLED in module.c */
|
||||
#define NGX_FASTCGI_INTEGRATION_ENABLED 1
|
||||
|
||||
/* Mark ngx_http_fastcgi_module.h as available (defined by nginx core) */
|
||||
#ifdef NGX_HTTP_FASTCGI_MODULE
|
||||
#define NGX_FASTCGI_MODULE_PRESENT 1
|
||||
#endif
|
||||
|
||||
|
||||
#endif /* _NGX_HTTP_APACHE_REWRITE_FASTCGI_H_ */
|
||||
178
modules/mod_rewrite/ngx_http_apache_rewrite_map.c
Normal file
178
modules/mod_rewrite/ngx_http_apache_rewrite_map.c
Normal file
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright 2026 Alexey Berezhok
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* ngx_http_apache_rewrite_map.c
|
||||
*
|
||||
* RewriteMap implementations for nginx Apache rewrite module.
|
||||
* Phase 1: internal functions (tolower, toupper, escape, unescape).
|
||||
* Phase 2 will add txt, rnd, prg maps (if needed).
|
||||
*/
|
||||
|
||||
#include "ngx_http_apache_rewrite_engine.h"
|
||||
|
||||
|
||||
/*
|
||||
* int:tolower — convert string to lowercase
|
||||
*/
|
||||
ngx_str_t
|
||||
ngx_rewrite_map_tolower(ngx_pool_t *pool, ngx_str_t key)
|
||||
{
|
||||
ngx_str_t result;
|
||||
u_char *p;
|
||||
|
||||
result.data = ngx_pnalloc(pool, key.len);
|
||||
if (result.data == NULL) {
|
||||
ngx_str_null(&result);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.len = key.len;
|
||||
for (p = result.data; key.len--; ) {
|
||||
*p++ = ngx_tolower(*key.data++);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* int:toupper — convert string to uppercase
|
||||
*/
|
||||
ngx_str_t
|
||||
ngx_rewrite_map_toupper(ngx_pool_t *pool, ngx_str_t key)
|
||||
{
|
||||
ngx_str_t result;
|
||||
u_char *p;
|
||||
ngx_uint_t i;
|
||||
|
||||
result.data = ngx_pnalloc(pool, key.len);
|
||||
if (result.data == NULL) {
|
||||
ngx_str_null(&result);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.len = key.len;
|
||||
p = result.data;
|
||||
for (i = 0; i < key.len; i++) {
|
||||
u_char ch = key.data[i];
|
||||
if (ch >= 'a' && ch <= 'z') {
|
||||
ch -= 32;
|
||||
}
|
||||
*p++ = ch;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* int:escape — percent-encode string (URI-safe encoding)
|
||||
*/
|
||||
ngx_str_t
|
||||
ngx_rewrite_map_escape(ngx_pool_t *pool, ngx_str_t key)
|
||||
{
|
||||
ngx_str_t result;
|
||||
uintptr_t escaped_len;
|
||||
|
||||
escaped_len = ngx_escape_uri(NULL, key.data, key.len,
|
||||
NGX_ESCAPE_ARGS);
|
||||
result.len = key.len + 2 * escaped_len;
|
||||
result.data = ngx_pnalloc(pool, result.len);
|
||||
if (result.data == NULL) {
|
||||
ngx_str_null(&result);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (escaped_len) {
|
||||
ngx_escape_uri(result.data, key.data, key.len, NGX_ESCAPE_ARGS);
|
||||
} else {
|
||||
ngx_memcpy(result.data, key.data, key.len);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* int:unescape — percent-decode string
|
||||
*/
|
||||
ngx_str_t
|
||||
ngx_rewrite_map_unescape(ngx_pool_t *pool, ngx_str_t key)
|
||||
{
|
||||
ngx_str_t result;
|
||||
u_char *dst;
|
||||
|
||||
result.data = ngx_pnalloc(pool, key.len + 1);
|
||||
if (result.data == NULL) {
|
||||
ngx_str_null(&result);
|
||||
return result;
|
||||
}
|
||||
|
||||
dst = result.data;
|
||||
ngx_memcpy(dst, key.data, key.len);
|
||||
dst[key.len] = '\0';
|
||||
|
||||
ngx_unescape_uri(&dst, &result.data, key.len, 0);
|
||||
/* After unescape, dst points past written data, result.data is start */
|
||||
/* Actually ngx_unescape_uri modifies both pointers. Re-do properly: */
|
||||
result.data = ngx_pnalloc(pool, key.len + 1);
|
||||
if (result.data == NULL) {
|
||||
ngx_str_null(&result);
|
||||
return result;
|
||||
}
|
||||
|
||||
ngx_memcpy(result.data, key.data, key.len);
|
||||
result.data[key.len] = '\0';
|
||||
|
||||
dst = result.data;
|
||||
{
|
||||
u_char *src = result.data;
|
||||
ngx_unescape_uri(&dst, &src, key.len, 0);
|
||||
}
|
||||
result.len = dst - result.data;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Lookup a named map and return the value for the given key.
|
||||
* In Phase 1, only internal (int:) maps are supported.
|
||||
*/
|
||||
ngx_str_t
|
||||
ngx_rewrite_lookup_map(ngx_http_request_t *r,
|
||||
ngx_http_apache_rewrite_srv_conf_t *sconf,
|
||||
ngx_str_t *mapname, ngx_str_t *key)
|
||||
{
|
||||
ngx_str_t result = ngx_null_string;
|
||||
ngx_rewrite_map_entry_t *maps;
|
||||
ngx_uint_t i;
|
||||
|
||||
if (sconf == NULL || sconf->maps == NULL || sconf->maps->nelts == 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
maps = sconf->maps->elts;
|
||||
for (i = 0; i < sconf->maps->nelts; i++) {
|
||||
if (maps[i].name.len == mapname->len
|
||||
&& ngx_strncasecmp(maps[i].name.data, mapname->data,
|
||||
mapname->len) == 0)
|
||||
{
|
||||
if (maps[i].type == MAPTYPE_INT && maps[i].func) {
|
||||
result = maps[i].func(r->pool, *key);
|
||||
}
|
||||
/* txt, rnd, prg maps — Phase 2 */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
2676
modules/mod_rewrite/ngx_http_apache_rewrite_module.c
Normal file
2676
modules/mod_rewrite/ngx_http_apache_rewrite_module.c
Normal file
File diff suppressed because it is too large
Load Diff
562
modules/mod_rewrite/ngx_http_apache_rewrite_variable.c
Normal file
562
modules/mod_rewrite/ngx_http_apache_rewrite_variable.c
Normal file
@@ -0,0 +1,562 @@
|
||||
/*
|
||||
* Copyright 2026 Alexey Berezhok
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*/
|
||||
|
||||
/*
|
||||
* ngx_http_apache_rewrite_variable.c
|
||||
*
|
||||
* Apache mod_rewrite variable lookup for nginx.
|
||||
* Maps %{VARNAME} to nginx request fields.
|
||||
*/
|
||||
|
||||
#include "ngx_http_apache_rewrite_engine.h"
|
||||
|
||||
|
||||
/*
|
||||
* Lookup an HTTP request header by name (case-insensitive).
|
||||
* Converts underscores to dashes for matching.
|
||||
*/
|
||||
static ngx_str_t
|
||||
ngx_rewrite_lookup_header(ngx_http_request_t *r, ngx_str_t *name)
|
||||
{
|
||||
ngx_list_part_t *part;
|
||||
ngx_table_elt_t *h;
|
||||
ngx_uint_t i;
|
||||
u_char lowered[256];
|
||||
ngx_uint_t len;
|
||||
ngx_str_t empty = ngx_null_string;
|
||||
|
||||
/* Convert header name: underscores to dashes, lowercase */
|
||||
len = name->len;
|
||||
if (len > sizeof(lowered) - 1) {
|
||||
len = sizeof(lowered) - 1;
|
||||
}
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
u_char ch = name->data[i];
|
||||
if (ch == '_') {
|
||||
ch = '-';
|
||||
}
|
||||
lowered[i] = ngx_tolower(ch);
|
||||
}
|
||||
|
||||
part = &r->headers_in.headers.part;
|
||||
h = part->elts;
|
||||
|
||||
for (i = 0; /* void */ ; i++) {
|
||||
if (i >= part->nelts) {
|
||||
if (part->next == NULL) {
|
||||
break;
|
||||
}
|
||||
part = part->next;
|
||||
h = part->elts;
|
||||
i = 0;
|
||||
}
|
||||
|
||||
if (h[i].key.len == len
|
||||
&& ngx_strncasecmp(h[i].key.data, lowered, len) == 0)
|
||||
{
|
||||
return h[i].value;
|
||||
}
|
||||
}
|
||||
|
||||
return empty;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Build THE_REQUEST string: "METHOD URI PROTOCOL"
|
||||
*/
|
||||
static ngx_str_t
|
||||
ngx_rewrite_build_the_request(ngx_http_request_t *r, ngx_pool_t *pool)
|
||||
{
|
||||
ngx_str_t result;
|
||||
u_char *p;
|
||||
size_t len;
|
||||
|
||||
len = r->method_name.len + 1 + r->unparsed_uri.len + 1
|
||||
+ sizeof("HTTP/1.1") - 1;
|
||||
|
||||
p = ngx_pnalloc(pool, len);
|
||||
if (p == NULL) {
|
||||
ngx_str_null(&result);
|
||||
return result;
|
||||
}
|
||||
|
||||
result.data = p;
|
||||
p = ngx_cpymem(p, r->method_name.data, r->method_name.len);
|
||||
*p++ = ' ';
|
||||
p = ngx_cpymem(p, r->unparsed_uri.data, r->unparsed_uri.len);
|
||||
*p++ = ' ';
|
||||
|
||||
if (r->http_version == NGX_HTTP_VERSION_20) {
|
||||
p = ngx_cpymem(p, "HTTP/2.0", sizeof("HTTP/2.0") - 1);
|
||||
} else if (r->http_version == NGX_HTTP_VERSION_11) {
|
||||
p = ngx_cpymem(p, "HTTP/1.1", sizeof("HTTP/1.1") - 1);
|
||||
} else {
|
||||
p = ngx_cpymem(p, "HTTP/1.0", sizeof("HTTP/1.0") - 1);
|
||||
}
|
||||
|
||||
result.len = p - result.data;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Main variable lookup function.
|
||||
* Maps Apache mod_rewrite variable names to nginx equivalents.
|
||||
*/
|
||||
ngx_str_t
|
||||
ngx_rewrite_lookup_variable(ngx_str_t *var, ngx_rewrite_ctx_t *ctx)
|
||||
{
|
||||
ngx_http_request_t *r = ctx->r;
|
||||
ngx_pool_t *pool = r->pool;
|
||||
ngx_str_t result = ngx_null_string;
|
||||
ngx_http_core_srv_conf_t *cscf;
|
||||
ngx_http_core_loc_conf_t *clcf;
|
||||
u_char *p;
|
||||
struct tm tm;
|
||||
time_t now;
|
||||
ngx_str_t hdr_name;
|
||||
|
||||
if (var->len == 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/* ENV:varname — look up nginx variable */
|
||||
if (var->len > 4
|
||||
&& ngx_strncasecmp(var->data, (u_char *) "ENV:", 4) == 0)
|
||||
{
|
||||
ngx_str_t vname;
|
||||
ngx_uint_t key;
|
||||
ngx_http_variable_value_t *vv;
|
||||
|
||||
vname.data = var->data + 4;
|
||||
vname.len = var->len - 4;
|
||||
key = ngx_hash_strlow(vname.data, vname.data, vname.len);
|
||||
|
||||
vv = ngx_http_get_variable(r, &vname, key);
|
||||
if (vv && !vv->not_found && vv->len > 0) {
|
||||
result.data = vv->data;
|
||||
result.len = vv->len;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* HTTP:header — generic header lookup */
|
||||
if (var->len > 5
|
||||
&& ngx_strncasecmp(var->data, (u_char *) "HTTP:", 5) == 0)
|
||||
{
|
||||
hdr_name.data = var->data + 5;
|
||||
hdr_name.len = var->len - 5;
|
||||
return ngx_rewrite_lookup_header(r, &hdr_name);
|
||||
}
|
||||
|
||||
/* Fixed variable names — use length switch like Apache */
|
||||
switch (var->len) {
|
||||
|
||||
case 4:
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "TIME", 4) == 0) {
|
||||
now = ngx_time();
|
||||
ngx_localtime(now, &tm);
|
||||
p = ngx_pnalloc(pool, 15);
|
||||
if (p) {
|
||||
result.len = ngx_sprintf(p, "%04d%02d%02d%02d%02d%02d",
|
||||
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
||||
tm.tm_hour, tm.tm_min, tm.tm_sec) - p;
|
||||
result.data = p;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
|
||||
case 5:
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "HTTPS", 5) == 0) {
|
||||
#if (NGX_SSL)
|
||||
if (r->connection->ssl) {
|
||||
ngx_str_set(&result, "on");
|
||||
} else {
|
||||
ngx_str_set(&result, "off");
|
||||
}
|
||||
#else
|
||||
ngx_str_set(&result, "off");
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
|
||||
case 8:
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "TIME_DAY", 8) == 0) {
|
||||
now = ngx_time();
|
||||
ngx_localtime(now, &tm);
|
||||
p = ngx_pnalloc(pool, 3);
|
||||
if (p) {
|
||||
result.len = ngx_sprintf(p, "%02d", tm.tm_mday) - p;
|
||||
result.data = p;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "TIME_SEC", 8) == 0) {
|
||||
now = ngx_time();
|
||||
ngx_localtime(now, &tm);
|
||||
p = ngx_pnalloc(pool, 3);
|
||||
if (p) {
|
||||
result.len = ngx_sprintf(p, "%02d", tm.tm_sec) - p;
|
||||
result.data = p;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "TIME_MIN", 8) == 0) {
|
||||
now = ngx_time();
|
||||
ngx_localtime(now, &tm);
|
||||
p = ngx_pnalloc(pool, 3);
|
||||
if (p) {
|
||||
result.len = ngx_sprintf(p, "%02d", tm.tm_min) - p;
|
||||
result.data = p;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "TIME_MON", 8) == 0) {
|
||||
now = ngx_time();
|
||||
ngx_localtime(now, &tm);
|
||||
p = ngx_pnalloc(pool, 3);
|
||||
if (p) {
|
||||
result.len = ngx_sprintf(p, "%02d", tm.tm_mon + 1) - p;
|
||||
result.data = p;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
|
||||
case 9:
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "HTTP_HOST", 9) == 0) {
|
||||
if (r->headers_in.host) {
|
||||
result = r->headers_in.host->value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "IS_SUBREQ", 9) == 0) {
|
||||
if (r->main != r) {
|
||||
ngx_str_set(&result, "true");
|
||||
} else {
|
||||
ngx_str_set(&result, "false");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "PATH_INFO", 9) == 0) {
|
||||
/* In nginx rewrite phase there's no path_info yet */
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "TIME_HOUR", 9) == 0) {
|
||||
now = ngx_time();
|
||||
ngx_localtime(now, &tm);
|
||||
p = ngx_pnalloc(pool, 3);
|
||||
if (p) {
|
||||
result.len = ngx_sprintf(p, "%02d", tm.tm_hour) - p;
|
||||
result.data = p;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "TIME_WDAY", 9) == 0) {
|
||||
now = ngx_time();
|
||||
ngx_localtime(now, &tm);
|
||||
p = ngx_pnalloc(pool, 2);
|
||||
if (p) {
|
||||
result.len = ngx_sprintf(p, "%d", tm.tm_wday) - p;
|
||||
result.data = p;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "TIME_YEAR", 9) == 0) {
|
||||
now = ngx_time();
|
||||
ngx_localtime(now, &tm);
|
||||
p = ngx_pnalloc(pool, 5);
|
||||
if (p) {
|
||||
result.len = ngx_sprintf(p, "%04d",
|
||||
tm.tm_year + 1900) - p;
|
||||
result.data = p;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
|
||||
case 10:
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "SERVER_URL", 10) == 0) {
|
||||
/* Not commonly used, return empty */
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
|
||||
case 11:
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "SERVER_NAME", 11) == 0) {
|
||||
cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module);
|
||||
if (cscf) {
|
||||
result = cscf->server_name;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "REMOTE_ADDR", 11) == 0) {
|
||||
result = r->connection->addr_text;
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "SERVER_ADDR", 11) == 0) {
|
||||
ngx_str_t addr;
|
||||
u_char sa[NGX_SOCKADDR_STRLEN];
|
||||
|
||||
addr.data = sa;
|
||||
addr.len = NGX_SOCKADDR_STRLEN;
|
||||
|
||||
if (ngx_connection_local_sockaddr(r->connection, &addr, 0)
|
||||
== NGX_OK)
|
||||
{
|
||||
result.data = ngx_pnalloc(pool, addr.len);
|
||||
if (result.data) {
|
||||
ngx_memcpy(result.data, addr.data, addr.len);
|
||||
result.len = addr.len;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "THE_REQUEST", 11) == 0) {
|
||||
return ngx_rewrite_build_the_request(r, pool);
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "HTTP_ACCEPT", 11) == 0) {
|
||||
hdr_name.data = (u_char *) "Accept";
|
||||
hdr_name.len = 6;
|
||||
return ngx_rewrite_lookup_header(r, &hdr_name);
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "HTTP_COOKIE", 11) == 0) {
|
||||
hdr_name.data = (u_char *) "Cookie";
|
||||
hdr_name.len = 6;
|
||||
return ngx_rewrite_lookup_header(r, &hdr_name);
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "SERVER_PORT", 11) == 0) {
|
||||
/* Extract port from local sockaddr */
|
||||
ngx_uint_t port;
|
||||
struct sockaddr *sa;
|
||||
|
||||
sa = r->connection->local_sockaddr;
|
||||
port = ngx_inet_get_port(sa);
|
||||
p = ngx_pnalloc(pool, 6);
|
||||
if (p) {
|
||||
result.len = ngx_sprintf(p, "%ui", port) - p;
|
||||
result.data = p;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "REMOTE_PORT", 11) == 0) {
|
||||
ngx_uint_t port;
|
||||
struct sockaddr *sa;
|
||||
|
||||
sa = r->connection->sockaddr;
|
||||
port = ngx_inet_get_port(sa);
|
||||
p = ngx_pnalloc(pool, 6);
|
||||
if (p) {
|
||||
result.len = ngx_sprintf(p, "%ui", port) - p;
|
||||
result.data = p;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "REMOTE_HOST", 11) == 0) {
|
||||
/* In nginx, REMOTE_HOST = REMOTE_ADDR (no reverse DNS) */
|
||||
result = r->connection->addr_text;
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "REQUEST_URI", 11) == 0) {
|
||||
result = r->unparsed_uri;
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
|
||||
case 12:
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "QUERY_STRING", 12) == 0) {
|
||||
result = r->args;
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "HTTP_REFERER", 12) == 0) {
|
||||
if (r->headers_in.referer) {
|
||||
result = r->headers_in.referer->value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "REMOTE_IDENT", 12) == 0) {
|
||||
/* Not available in nginx */
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
|
||||
case 13:
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "DOCUMENT_ROOT", 13) == 0) {
|
||||
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
|
||||
if (clcf) {
|
||||
result = clcf->root;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
|
||||
case 14:
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "REQUEST_METHOD", 14) == 0) {
|
||||
result = r->method_name;
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "HTTP_FORWARDED", 14) == 0) {
|
||||
hdr_name.data = (u_char *) "Forwarded";
|
||||
hdr_name.len = 9;
|
||||
return ngx_rewrite_lookup_header(r, &hdr_name);
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "REQUEST_SCHEME", 14) == 0) {
|
||||
#if (NGX_SSL)
|
||||
if (r->connection->ssl) {
|
||||
ngx_str_set(&result, "https");
|
||||
} else {
|
||||
ngx_str_set(&result, "http");
|
||||
}
|
||||
#else
|
||||
ngx_str_set(&result, "http");
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
|
||||
case 15:
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "HTTP_USER_AGENT", 15) == 0)
|
||||
{
|
||||
if (r->headers_in.user_agent) {
|
||||
result = r->headers_in.user_agent->value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "SERVER_PROTOCOL", 15) == 0)
|
||||
{
|
||||
result = r->http_protocol;
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data, (u_char *) "SERVER_SOFTWARE", 15) == 0)
|
||||
{
|
||||
ngx_str_set(&result, NGINX_VER);
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data,
|
||||
(u_char *) "SCRIPT_FILENAME", 15) == 0)
|
||||
{
|
||||
size_t root;
|
||||
ngx_str_t r_path;
|
||||
u_char *last;
|
||||
|
||||
// Вызываем функцию для построения полного пути
|
||||
last = ngx_http_map_uri_to_path(r, &r_path, &root, 0);
|
||||
if (last == NULL) {
|
||||
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
|
||||
if (r->uri.len > 1 && clcf != NULL) {
|
||||
if (r->uri.data[0] == '/') {
|
||||
ngx_str_t tmp_str = ngx_null_string;
|
||||
tmp_str.data = ngx_pnalloc(r->pool, r->uri.len);
|
||||
if (tmp_str.data){
|
||||
ngx_memzero(tmp_str.data, r->uri.len);
|
||||
ngx_memcpy(tmp_str.data, r->uri.data+1, r->uri.len-1);
|
||||
tmp_str.len = r->uri.len-1;
|
||||
ngx_str_t full_path = ngx_null_string;
|
||||
full_path.data = ngx_pnalloc(r->pool, tmp_str.len + clcf->root.len + 1);
|
||||
ngx_memcpy(full_path.data, clcf->root.data, clcf->root.len);
|
||||
ngx_memcpy(full_path.data + clcf->root.len, tmp_str.data, tmp_str.len);
|
||||
full_path.len = tmp_str.len + clcf->root.len + 1;
|
||||
result = full_path;
|
||||
} else {
|
||||
result = r->uri;
|
||||
}
|
||||
} else {
|
||||
ngx_str_t full_path = ngx_null_string;
|
||||
full_path.data = ngx_pnalloc(r->pool, r->uri.len + clcf->root.len + 1);
|
||||
ngx_memcpy(full_path.data, clcf->root.data, clcf->root.len);
|
||||
ngx_memcpy(full_path.data + clcf->root.len, r->uri.data, r->uri.len);
|
||||
full_path.len =r->uri.len + clcf->root.len + 1;
|
||||
result = full_path;
|
||||
}
|
||||
} else {
|
||||
result = r->uri;
|
||||
}
|
||||
} else {
|
||||
r_path.len = last - r_path.data;
|
||||
r_path.data = r_path.data;
|
||||
result = r_path;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
|
||||
case 16:
|
||||
if (ngx_strncasecmp(var->data,
|
||||
(u_char *) "REQUEST_FILENAME", 16) == 0)
|
||||
{
|
||||
size_t root;
|
||||
ngx_str_t r_path;
|
||||
u_char *last;
|
||||
|
||||
last = ngx_http_map_uri_to_path(r, &r_path, &root, 0);
|
||||
if (last == NULL) {
|
||||
clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
|
||||
if (r->uri.len > 1 && clcf != NULL) {
|
||||
if (r->uri.data[0] == '/') {
|
||||
ngx_str_t tmp_str = ngx_null_string;
|
||||
tmp_str.data = ngx_pnalloc(r->pool, r->uri.len);
|
||||
if (tmp_str.data){
|
||||
ngx_memzero(tmp_str.data, r->uri.len);
|
||||
ngx_memcpy(tmp_str.data, r->uri.data+1, r->uri.len-1);
|
||||
tmp_str.len = r->uri.len-1;
|
||||
ngx_str_t full_path = ngx_null_string;
|
||||
full_path.data = ngx_pnalloc(r->pool, tmp_str.len + clcf->root.len + 1);
|
||||
ngx_memcpy(full_path.data, clcf->root.data, clcf->root.len);
|
||||
ngx_memcpy(full_path.data + clcf->root.len, tmp_str.data, tmp_str.len);
|
||||
full_path.len = tmp_str.len + clcf->root.len + 1;
|
||||
result = full_path;
|
||||
} else {
|
||||
result = r->uri;
|
||||
}
|
||||
} else {
|
||||
ngx_str_t full_path = ngx_null_string;
|
||||
full_path.data = ngx_pnalloc(r->pool, r->uri.len + clcf->root.len + 1);
|
||||
ngx_memcpy(full_path.data, clcf->root.data, clcf->root.len);
|
||||
ngx_memcpy(full_path.data + clcf->root.len, r->uri.data, r->uri.len);
|
||||
full_path.len =r->uri.len + clcf->root.len + 1;
|
||||
result = full_path;
|
||||
}
|
||||
} else {
|
||||
result = r->uri;
|
||||
}
|
||||
} else {
|
||||
r_path.len = last - r_path.data;
|
||||
r_path.data = r_path.data;
|
||||
result = r_path;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
if (ngx_strncasecmp(var->data,
|
||||
(u_char *) "CONN_REMOTE_ADDR", 16) == 0)
|
||||
{
|
||||
result = r->connection->addr_text;
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
|
||||
case 21:
|
||||
if (ngx_strncasecmp(var->data,
|
||||
(u_char *) "HTTP_PROXY_CONNECTION", 21) == 0)
|
||||
{
|
||||
hdr_name.data = (u_char *) "Proxy-Connection";
|
||||
hdr_name.len = 16;
|
||||
return ngx_rewrite_lookup_header(r, &hdr_name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
Reference in New Issue
Block a user