Added mod_rewrite for nginx module

This commit is contained in:
alexey
2026-03-23 01:15:59 +03:00
commit 0d2c6277e1
124 changed files with 11079 additions and 0 deletions

View 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

View 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;
}

View 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_ */

View 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;
}

View 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);

View 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_ */

View 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;
}

File diff suppressed because it is too large Load Diff

View 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;
}