/* Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License.
*/
/* _ _ _ * _ __ ___ ___ __| | _ __ _____ ___ __(_) |_ ___ * | '_ ` _ \ / _ \ / _` | | '__/ _ \ \ /\ / / '__| | __/ _ \ * | | | | | | (_) | (_| | | | | __/\ V V /| | | | || __/ * |_| |_| |_|\___/ \__,_|___|_| \___| \_/\_/ |_| |_|\__\___| * |_____| * * URL Rewriting Module * * This module uses a rule-based rewriting engine (based on a * regular-expression parser) to rewrite requested URLs on the fly. * * It supports an unlimited number of additional rule conditions (which can * operate on a lot of variables, even on HTTP headers) for granular * matching and even external database lookups (either via plain text * tables, DBM hash files or even external processes) for advanced URL * substitution. * * It operates on the full URLs (including the PATH_INFO part) both in * per-server context (httpd.conf) and per-dir context (.htaccess) and even * can generate QUERY_STRING parts on result. The rewriting result finally * can lead to internal subprocessing, external request redirection or even * to internal proxy throughput. * * This module was originally written in April 1996 and * gifted exclusively to the The Apache Software Foundation in July 1997 by * * Ralf S. Engelschall * rse engelschall.com * www.engelschall.com
*/
/* * in order to improve performance on running production systems, you * may strip all rewritelog code entirely from mod_rewrite by using the * -DREWRITELOG_DISABLED compiler option. * * DO NOT USE THIS OPTION FOR PUBLIC BINARY RELEASES. Otherwise YOU are * responsible for answering all the mod_rewrite questions out there.
*/ /* If logging is limited to APLOG_DEBUG or lower, disable rewrite log, too */ #ifdef APLOG_MAX_LOGLEVEL #if APLOG_MAX_LOGLEVEL < APLOG_TRACE1 #ifndef REWRITELOG_DISABLED #define REWRITELOG_DISABLED #endif #endif #endif
/* return code of the rewrite rule * the result may be escaped - or not
*/ #define ACTION_NORMAL (1<<0) #define ACTION_NOESCAPE (1<<1) #define ACTION_STATUS (1<<2) #define ACTION_STATUS_SET (1<<3)
/* * expansion result items on the stack to save some cycles * * (5 == about 2 variables like "foo%{var}bar%{var}baz")
*/ #define SMALL_EXPANSION 5
/* * check that a subrequest won't cause infinite recursion * * either not in a subrequest, or in a subrequest * and URIs aren't NULL and sub/main URIs differ
*/ #define subreq_ok(r) (!r->main || \
(r->main->uri && r->uri && strcmp(r->main->uri, r->uri)))
typedefstruct { constchar *datafile; /* filename for map data files */ constchar *dbmtype; /* dbm type for dbm map data files */ constchar *checkfile; /* filename to check for map existence */ constchar *cachename; /* for cached maps (txt/rnd/dbm) */ int type; /* the type of the map */
apr_file_t *fpin; /* in file pointer for program maps */
apr_file_t *fpout; /* out file pointer for program maps */
apr_file_t *fperr; /* err file pointer for program maps */ char *(*func)(request_rec *, /* function pointer for internal maps */ char *); char **argv; /* argv of the external rewrite map */ constchar *dbdq; /* SQL SELECT statement for rewritemap */ constchar *checkfile2; /* filename to check for map existence
NULL if only one file */ constchar *user; /* run RewriteMap program as this user */ constchar *group; /* run RewriteMap program as this group */
} rewritemap_entry;
typedefenum {
RULE_RC_NOMATCH = 0, /* the rule didn't match */
RULE_RC_MATCH = 1, /* a matching rule w/ substitution */
RULE_RC_NOSUB = 2, /* a matching rule w/ no substitution */
RULE_RC_STATUS_SET = 3 /* a matching rule that has set an HTTP error
to be returned in r->status */
} rule_return_type;
typedefenum {
COND_RC_NOMATCH = 0, /* the cond didn't match */
COND_RC_MATCH = 1, /* the cond matched */
COND_RC_STATUS_SET = 3 /* The condition eval set a final r->status */
} cond_return_type;
typedefstruct { char *input; /* Input string of RewriteCond */ char *pattern; /* the RegExp pattern string */
ap_regex_t *regexp; /* the precompiled regexp */
ap_expr_info_t *expr; /* the compiled ap_expr */ int flags; /* Flags which control the match */
pattern_type ptype; /* pattern type */ int pskip; /* back-index to display pattern */
} rewritecond_entry;
/* single linked list for env vars and cookies */ typedefstruct data_item { struct data_item *next; char *data;
} data_item;
typedefstruct {
apr_array_header_t *rewriteconds;/* the corresponding RewriteCond entries */ char *pattern; /* the RegExp pattern string */
ap_regex_t *regexp; /* the RegExp pattern compilation */ char *output; /* the Substitution string */ int flags; /* Flags which control the substitution */ char *forced_mimetype; /* forced MIME type of substitution */ char *forced_handler; /* forced content handler of subst. */ int forced_responsecode; /* forced HTTP response status */
data_item *env; /* added environment variables */
data_item *cookie; /* added cookies */ int skip; /* number of next rules to skip */ int maxrounds; /* limit on number of loops with N flag */ constchar *escapes; /* specific backref escapes */ constchar *noescapes; /* specific backref chars not to escape */
} rewriterule_entry;
typedefstruct { int state; /* the RewriteEngine state */ int options; /* the RewriteOption state */
apr_hash_t *rewritemaps; /* the RewriteMap entries */
apr_array_header_t *rewriteconds; /* the RewriteCond entries (temp.) */
apr_array_header_t *rewriterules; /* the RewriteRule entries */
server_rec *server; /* the corresponding server indicator */ unsignedint state_set:1; unsignedint options_set:1;
} rewrite_server_conf;
typedefstruct { int state; /* the RewriteEngine state */ int options; /* the RewriteOption state */
apr_array_header_t *rewriteconds; /* the RewriteCond entries (temp.) */
apr_array_header_t *rewriterules; /* the RewriteRule entries */ char *directory; /* the directory where it applies */ constchar *baseurl; /* the base-URL where it applies */ unsignedint state_set:1; unsignedint options_set:1; unsignedint baseurl_set:1;
} rewrite_perdir_conf;
/* cached maps contain an mtime for the whole map and live in a subpool * of the cachep->pool. That makes it easy to forget them if necessary.
*/ typedefstruct {
apr_time_t mtime;
apr_pool_t *pool;
apr_hash_t *entries;
} cachedmap;
/* the regex structure for the * substitution of backreferences
*/ typedefstruct backrefinfo { constchar *source;
ap_regmatch_t regmatch[AP_MAX_REG_MATCH];
} backrefinfo;
/* single linked list used for * variable expansion
*/ typedefstruct result_list { struct result_list *next;
apr_size_t len; constchar *string;
} result_list;
/* Slightly complicated definitions follow here to support * compile-time choice of enabled/disabled of rewritelog both with or * without C99 support, where varargs macros are only available with * C99. * * do_rewritelog is defined for (rewritelog enabled) or (rewritelog * disabled AND no C99 support) but is an noop for the latter case and * should get optimized away. * * For the (rewritelog disabled) case the function is defined * differently for C99/non-C99. For C99, the rewritelog() macro passes * __LINE__, allowing accurate logging of line numbers. For non-C99 * the line number used for rewritelog() tracing is always
* constant. */
/* * +-------------------------------------------------------+ * | | * | URI and path functions * | | * +-------------------------------------------------------+
*/
/* return number of chars of the scheme (incl. '://') * if the URI is absolute (includes a scheme etc.) * otherwise 0. * If supportqs is not NULL, we return a whether or not * the scheme supports a query string or not. * * NOTE: If you add new schemes here, please have a * look at escape_absolute_uri and splitout_queryargs. * Not every scheme takes query strings and some schemes * may be handled in a special way. * * XXX: we may consider a scheme registry, perhaps with * appropriate escape callbacks to allow other modules * to extend mod_rewrite at runtime.
*/ staticunsigned is_absolute_uri(char *uri, int *supportsqs)
{ int dummy, *sqs;
/* * escape absolute uri, which may or may not be path oriented. * So let's handle them differently.
*/ staticchar *escape_absolute_uri(apr_pool_t *p, char *uri, unsigned scheme)
{ char *cp;
/* be safe. * NULL should indicate elsewhere, that something's wrong
*/ if (!scheme || strlen(uri) < scheme) { return NULL;
}
cp = uri + scheme;
/* scheme with authority part? */ if (cp[-1] == '/') { /* skip host part */ while (*cp && *cp != '/') {
++cp;
}
/* nothing after the hostpart. ready! */ if (!*cp || !*++cp) { return apr_pstrdup(p, uri);
}
/* special thing for ldap. * The parts are separated by question marks. From RFC 2255: * ldapurl = scheme "://" [hostport] ["/" * [dn ["?" [attributes] ["?" [scope] * ["?" [filter] ["?" extensions]]]]]]
*/ if (!ap_cstr_casecmpn(uri, "ldap", 4)) { char *token[5]; int c = 0;
token[0] = cp = apr_pstrdup(p, cp); while (*cp && c < 4) { if (*cp == '?') {
token[++c] = cp + 1;
*cp = '\0';
}
++cp;
}
/* Nothing special here. Apply normal escaping. */ return apr_pstrcat(p, apr_pstrndup(p, uri, scheme),
ap_escape_uri(p, cp), NULL);
}
/* * split out a QUERY_STRING part from * the current URI string
*/ staticvoid splitout_queryargs(request_rec *r, int flags)
{ char *q; int split, skip; int qsappend = flags & RULEFLAG_QSAPPEND; int qsdiscard = flags & RULEFLAG_QSDISCARD; int qslast = flags & RULEFLAG_QSLAST;
if (flags & RULEFLAG_QSNONE) {
rewritelog(r, 2, NULL, "discarding query string, no parse from substitution");
r->args = NULL; return;
}
/* don't touch, unless it's a scheme for which a query string makes sense. * See RFC 1738 and RFC 2368.
*/ if ((skip = is_absolute_uri(r->filename, &split))
&& !split) {
r->args = NULL; /* forget the query that's still flying around */ return;
}
/* cut the hostname and port out of the URI */
cp = host = scratch + l + 3; /* 3 == strlen("://") */ while (*cp && *cp != '/' && *cp != ':') {
++cp;
}
if (*cp == ':') { /* additional port given */
*cp++ = '\0';
portp = cp; while (*cp && *cp != '/') {
++cp;
}
*cp = '\0';
port = atoi(portp);
url = r->filename + (cp - scratch); if (!*url) {
url = "/";
}
} elseif (*cp == '/') { /* default port */
*cp = '\0';
port = ap_default_port(r);
url = r->filename + (cp - scratch);
} else {
port = ap_default_port(r);
url = "/";
}
/* now check whether we could reduce it to a local path... */ if (ap_matches_request_vhost(r, host, port)) {
rewrite_server_conf *conf =
ap_get_module_config(r->server->module_config, &rewrite_module);
rewritelog(r, 3, NULL, "reduce %s -> %s", r->filename, url);
r->filename = apr_pstrdup(r->pool, url);
/* remember that the uri was reduced */ if(!(conf->options & OPTION_LEGACY_PREFIX_DOCROOT)) {
apr_table_setn(r->notes, "mod_rewrite_uri_reduced", "true");
}
}
}
return;
}
/* * add 'http[s]://ourhost[:ourport]/' to URI * if URI is still not fully qualified
*/ staticvoid fully_qualify_uri(request_rec *r)
{ if (r->method_number == M_CONNECT) { return;
} elseif (!is_absolute_uri(r->filename, NULL)) { constchar *thisserver; char *thisport; int port;
if (apr_stat(&sb, statpath, APR_FINFO_MIN, pool) == APR_SUCCESS) { if (!lastsub) {
rewritelog(r, 3, NULL, "prefix_stat no lastsub subst prefix %s", statpath); return 1;
}
rewritelog(r, 3, NULL, "prefix_stat compare statpath %s and lastsub output %s STATOK %d ",
statpath, lastsub->output, lastsub->flags & RULEFLAG_UNSAFE_PREFIX_STAT); if (lastsub->flags & RULEFLAG_UNSAFE_PREFIX_STAT) { return 1;
} else { constchar *docroot = ap_document_root(r); constchar *context_docroot = ap_context_document_root(r); /* * As an example, path (r->filename) is /var/foo/bar/baz.html * even if the flag is not set, we can accept a rule that * began with a literal /var (stapath), or if the entire path * starts with the docroot or context document root
*/ if (startsWith(r, lastsub->output, statpath) ||
startsWith(r, path, docroot) ||
((docroot != context_docroot) &&
startsWith(r, path, context_docroot))) { return 1;
}
}
}
}
/* prefix will be added */ return 0;
}
/* * substitute the prefix path 'match' in 'input' with 'subst' (RewriteBase)
*/ staticchar *subst_prefix_path(request_rec *r, char *input, constchar *match, constchar *subst)
{
apr_size_t len = strlen(match);
/* Now we should have a valid map->entries hash, where we * can store our value. * * We need to copy the key and the value into OUR pool, * so that we don't leave it during the r->pool cleanup.
*/
apr_hash_set(map->entries,
apr_pstrdup(map->pool, key), APR_HASH_KEY_STRING,
apr_pstrdup(map->pool, val));
if (map) { /* if this map is outdated, forget it. */ if (map->mtime != t) {
apr_pool_clear(map->pool);
map->entries = apr_hash_make(map->pool);
map->mtime = t;
} else {
val = apr_hash_get(map->entries, key, APR_HASH_KEY_STRING); if (val) { /* copy the cached value into the supplied pool, * where it belongs (r->pool usually)
*/
val = apr_pstrdup(p, val);
}
}
}
if ((rv = apr_file_open(&fp, file, APR_READ|APR_BUFFERED, APR_OS_DEFAULT,
r->pool)) != APR_SUCCESS)
{
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00655) "mod_rewrite: can't open text RewriteMap file %s", file); return NULL;
}
keylast = key + strlen(key);
value = NULL; while (apr_file_gets(line, sizeof(line), fp) == APR_SUCCESS) { char *p, *c;
/* ignore comments and lines starting with whitespaces */ if (*line == '#' || apr_isspace(*line)) { continue;
}
p = line;
c = key; while (c < keylast && *p == *c && !apr_isspace(*p)) {
++p;
++c;
}
/* key doesn't match - ignore. */ if (c != keylast || !apr_isspace(*p)) { continue;
}
/* jump to the value */ while (apr_isspace(*p)) {
++p;
}
/* no value? ignore */ if (!*p) { continue;
}
/* extract the value and return. */
c = p; while (*p && !apr_isspace(*p)) {
++p;
}
value = apr_pstrmemdup(r->pool, c, p - c); break;
}
apr_file_close(fp);
/* when `RewriteEngine off' was used in the per-server * context then the rewritemap-programs were not spawned. * In this case using such a map (usually in per-dir context) * is useless because it is not available. * * newlines in the key leave bytes in the pipe and cause * bad things to happen (next map lookup will use the chars * after the \n instead of the new key etc etc - in other words, * the Rewritemap falls out of sync with the requests).
*/ if (fpin == NULL || fpout == NULL || ap_strchr(key, '\n')) { return NULL;
}
/* take the lock */ if (rewrite_mapr_lock_acquire) {
rv = apr_global_mutex_lock(rewrite_mapr_lock_acquire); if (rv != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00659) "apr_global_mutex_lock(rewrite_mapr_lock_acquire) " "failed"); return NULL; /* Maybe this should be fatal? */
}
}
/* read in the response value */
nbytes = 1;
apr_file_read(fpout, &c, &nbytes); do {
i = 0; while (nbytes == 1 && (i < REWRITE_PRG_MAP_BUF)) { if (c == eol[eolc]) { if (!eol[++eolc]) { /* remove eol from the buffer */
--eolc; if (i < eolc) {
curbuf->len -= eolc-i;
i = 0;
} else {
i -= eolc;
}
++found_nl; break;
}
}
/* only partial (invalid) eol sequence -> reset the counter */ elseif (eolc) {
eolc = 0;
}
/* catch binary mode, e.g. on Win32 */ elseif (c == '\n') {
++found_nl; break;
}
/* well, if there wasn't a newline yet, we need to read further */ if (buflist || (nbytes == 1 && !found_nl)) { if (!buflist) {
curbuf = buflist = apr_palloc(r->pool, sizeof(*buflist));
} elseif (i) {
curbuf->next = apr_palloc(r->pool, sizeof(*buflist));
curbuf = curbuf->next;
p = buf = apr_palloc(r->pool, combined_len + 1); /* \0 */ while (buflist) { if (buflist->len) {
memcpy(p, buflist->string, buflist->len);
p += buflist->len;
}
buflist = buflist->next;
}
*p = '\0';
i = combined_len;
} else {
buf[i] = '\0';
}
/* give the lock back */ if (rewrite_mapr_lock_acquire) {
rv = apr_global_mutex_unlock(rewrite_mapr_lock_acquire); if (rv != APR_SUCCESS) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00660) "apr_global_mutex_unlock(rewrite_mapr_lock_acquire) " "failed"); return NULL; /* Maybe this should be fatal? */
}
}
/* catch the "failed" case */ if (i == 4 && !strcasecmp(buf, "NULL")) { return NULL;
}
/* fast tests for variable length variables (sic) first */ if (var[3] == ':') { if (var[4] && !strncasecmp(var, "ENV", 3)) {
var += 4;
result = apr_table_get(r->notes, var);
if (!result) {
result = apr_table_get(r->subprocess_env, var);
} if (!result) {
result = getenv(var);
}
} elseif (var[4] && !strncasecmp(var, "SSL", 3)) {
result = ap_ssl_var_lookup(r->pool, r->server, r->connection, r,
var + 4);
}
} elseif (var[4] == ':') { if (var[5]) {
request_rec *rr; constchar *path;
return (char *)result;
}
} elseif (!strncasecmp(var, "LA-F", 4)) { if (ctx->uri && subreq_ok(r)) {
path = ctx->uri; if (ctx->perdir && *path == '/') { /* sigh, the user wants a file based subrequest, but * we can't do one, since we don't know what the file * path is! In this case behave like LA-U.
*/
rr = ap_sub_req_lookup_uri(path, r, NULL);
} else { if (ctx->perdir) {
rewrite_perdir_conf *conf;
case'S': if (*var == 'R' && !strcmp(var, "REMOTE_USER")) {
result = r->user;
} elseif (!strcmp(var, "SCRIPT_USER")) {
result = "<unknown>"; if (r->finfo.valid & APR_FINFO_USER) {
apr_uid_name_get((char **)&result, r->finfo.user,
r->pool);
}
} break;
case'U': if (!strcmp(var, "REQUEST_URI")) {
result = r->uri;
} break;
} break;
case 12: switch (var[3]) { case'I': if (!strcmp(var, "SCRIPT_GROUP")) {
result = "<unknown>"; if (r->finfo.valid & APR_FINFO_GROUP) {
apr_gid_name_get((char **)&result, r->finfo.group,
r->pool);
}
} break;
case'O': if (!strcmp(var, "REMOTE_IDENT")) {
result = ap_get_remote_logname(r);
} break;
case'P': if (!strcmp(var, "HTTP_REFERER")) {
result = lookup_header("Referer", ctx);
} break;
case'R': if (!strcmp(var, "QUERY_STRING")) {
result = r->args;
} break;
case'V': if (!strcmp(var, "SERVER_ADMIN")) {
result = r->server->server_admin;
} break;
} break;
case 13: if (!strcmp(var, "DOCUMENT_ROOT")) {
result = ap_document_root(r);
} break;
case 14: if (*var == 'H' && !strcmp(var, "HTTP_FORWARDED")) {
result = lookup_header("Forwarded", ctx);
} elseif (*var == 'C' && !strcmp(var, "CONTEXT_PREFIX")) {
result = ap_context_prefix(r);
} elseif (var[8] == 'M' && !strcmp(var, "REQUEST_METHOD")) {
result = r->method;
} elseif (!strcmp(var, "REQUEST_SCHEME")) {
result = ap_http_scheme(r);
} break;
case 15: switch (var[7]) { case'E': if (!strcmp(var, "HTTP_USER_AGENT")) {
result = lookup_header("User-Agent", ctx);
} break;
case'F': if (!strcmp(var, "SCRIPT_FILENAME")) {
result = r->filename; /* same as request_filename (16) */
} break;
case'P': if (!strcmp(var, "SERVER_PROTOCOL")) {
result = r->protocol;
} break;
case'S': if (!strcmp(var, "SERVER_SOFTWARE")) {
result = ap_get_server_banner();
} break;
} break;
case 16: if (*var == 'C' && !strcmp(var, "CONN_REMOTE_ADDR")) {
result = r->connection->client_ip;
} elseif (!strcmp(var, "REQUEST_FILENAME")) {
result = r->filename; /* same as script_filename (15) */
} break;
case 21: if (!strcmp(var, "HTTP_PROXY_CONNECTION")) {
result = lookup_header("Proxy-Connection", ctx);
} elseif (!strcmp(var, "CONTEXT_DOCUMENT_ROOT")) {
result = ap_context_document_root(r);
} break;
}
}
return apr_pstrdup(r->pool, result ? result : "");
}
/* perform all the expansions on the input string * putting the result into a new string * * for security reasons this expansion must be performed in a * single pass, otherwise an attacker can arrange for the result * of an earlier expansion to include expansion specifiers that * are interpreted by a later expansion, producing results that * were not intended by the administrator. * * unsafe_qmark if not NULL will be set to 1 or 0 if a question mark * is found respectively in a literal or in a lookup/expansion (whether * it's the first or last qmark depends on [QSL]). Should be initialized * to -1 and remains so if no qmark is found.
*/ staticchar *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry, int *unsafe_qmark)
{ #define EXPAND_SPECIALS "\\$%"
result_list *result, *current;
result_list sresult[SMALL_EXPANSION]; unsigned spc = 0;
apr_size_t span, inputlen, outlen; char *p, *c;
apr_pool_t *pool = ctx->r->pool;
inputlen = strlen(input); if (!unsafe_qmark) {
span = strcspn(input, EXPAND_SPECIALS);
} else {
span = strcspn(input, EXPAND_SPECIALS "?"); if (input[span] == '?') { /* this qmark is not from an expansion thus safe */
*unsafe_qmark = 0;
/* keep tracking only if interested in the last qmark */ if (!entry || !(entry->flags & RULEFLAG_QSLAST)) {
unsafe_qmark = NULL;
}
/* find the next real special char, any (last) qmark up to * there is safe too
*/
span += strcspn(input + span, EXPAND_SPECIALS);
}
}
/* fast path (no specials) */ if (span >= inputlen) { return apr_pstrmemdup(pool, input, inputlen);
}
/* well, actually something to do */
result = current = &(sresult[spc++]);
/* * To make rewrite maps useful, the lookup key and * default values must be expanded, so we make * recursive calls to do the work. For security * reasons we must never expand a string that includes * verbatim data from the network. The recursion here * isn't a problem because the result of expansion is * only passed to lookup_map() so it cannot be * re-expanded, only re-looked-up. Another way of * looking at it is that the recursion is entirely * driven by the syntax of the nested curly brackets.
*/
/* not for us, just copy it */ else {
current->len = 1;
current->string = p++;
++outlen;
}
if (unsafe_qmark && expanded && current->len
&& memchr(current->string, '?', current->len)) { /* this qmark is from an expansion thus unsafe */
*unsafe_qmark = 1;
/* keep tracking only if interested in the last qmark */ if (!entry || !(entry->flags & RULEFLAG_QSLAST)) {
unsafe_qmark = NULL;
}
}
/* check the remainder */ if (!unsafe_qmark) {
span = strcspn(p, EXPAND_SPECIALS);
} else {
span = strcspn(p, EXPAND_SPECIALS "?"); if (p[span] == '?') { /* this qmark is not from an expansion thus safe */
*unsafe_qmark = 0;
/* keep tracking only if interested in the last qmark */ if (!entry || !(entry->flags & RULEFLAG_QSLAST)) {
unsafe_qmark = NULL;
}
/* find the next real special char, any (last) qmark up to * there is safe too
*/
span += strcspn(p + span, EXPAND_SPECIALS);
}
} if (span > 0) { if (current->len) {
current->next = (spc < SMALL_EXPANSION)
? &(sresult[spc++])
: (result_list *)apr_palloc(pool, sizeof(result_list));
current = current->next;
current->next = NULL;
}
/* assemble result */
c = p = apr_palloc(pool, outlen + 1); /* don't forget the \0 */ do { if (result->len) {
ap_assert(c+result->len <= p+outlen); /* XXX: can be removed after * extensive testing and * review
*/
memcpy(c, result->string, result->len);
c += result->len;
}
result = result->next;
} while (result);
p[outlen] = '\0';
return p;
}
/* * perform all the expansions on the environment variables
*/ staticvoid do_expand_env(data_item *env, rewrite_ctx *ctx)
{ char *name, *val;
while (env) {
name = do_expand(env->data, ctx, NULL, NULL); if (*name == '!') {
name++;
apr_table_unset(ctx->r->subprocess_env, name);
rewritelog(ctx->r, 5, NULL, "unsetting env variable '%s'", name);
} else { if ((val = ap_strchr(name, ':')) != NULL) {
*val++ = '\0';
} else {
val = "";
}
/* * perform all the expansions on the cookies * * TODO: use cached time similar to how logging does it
*/ staticvoid add_cookie(request_rec *r, char *s)
{ char *var; char *val; char *domain; char *expires; char *path; char *secure; char *httponly; char *samesite;
char *tok_cntx; char *cookie; /* long-standing default, but can't use ':' in a cookie */ constchar *sep = ":";
/* opt-in to ; separator if first character is a ; */ if (s && *s == ';') {
sep = ";";
s++;
}
var = apr_strtok(s, sep, &tok_cntx);
val = apr_strtok(NULL, sep, &tok_cntx);
domain = apr_strtok(NULL, sep, &tok_cntx);
if (var && val && domain) {
request_rec *rmain = r; char *notename; void *data;
while (rmain->main) {
rmain = rmain->main;
}
notename = apr_pstrcat(rmain->pool, var, "_rewrite", NULL);
apr_pool_userdata_get(&data, notename, rmain->pool); if (!data) { char *exp_time = NULL;
/* * own command line parser for RewriteRule and RewriteCond, * which doesn't have the '\\' problem. * (returns true on error) * * XXX: what an inclined parser. Seems we have to leave it so * for backwards compat. *sigh*
*/ staticint parseargline(char *str, char **a1, char **a2, char **a2_end, char **a3)
{ char quote;
/* server command? set both global scope and base directory scope */ if (cmd->path == NULL) { /* is server command */
rewrite_perdir_conf *dconf = in_dconf;
rewrite_server_conf *sconf =
ap_get_module_config(cmd->server->module_config,
&rewrite_module);
if (cmd->path == NULL || dconf == NULL) { return"RewriteBase: only valid in per-directory config files";
} if (a1[0] == '\0') { return"RewriteBase: empty URL not allowed";
} if (a1[0] != '/') { return"RewriteBase: argument is not a valid URL";
}
dconf->baseurl = a1;
dconf->baseurl_set = 1;
return NULL;
}
/* * generic lexer for RewriteRule and RewriteCond flags. * The parser will be passed in as a function pointer * and called if a flag was found
*/ staticconstchar *cmd_parseflagfield(apr_pool_t *p, void *cfg, char *key, constchar *(*parse)(apr_pool_t *, void *, char *, char *))
{ char *val, *nextp, *endp; constchar *err;
endp = key + strlen(key) - 1; if (*key != '[' || *endp != ']') { return"bad flag delimiters";
}
*endp = ','; /* for simpler parsing */
++key;
while (*key) { /* skip leading spaces */ while (apr_isspace(*key)) {
++key;
}
if (!*key || (nextp = ap_strchr(key, ',')) == NULL) { /* NULL should not * happen, but ...
*/ break;
}
/* make a new entry in the internal temporary rewrite rule list */ if (cmd->path == NULL) { /* is server command */
newcond = apr_array_push(sconf->rewriteconds);
} else { /* is per-directory command */
newcond = apr_array_push(dconf->rewriteconds);
}
/* parse the argument line ourself * a1 .. a3 are substrings of str, which is a fresh copy * of the argument line. So we can use a1 .. a3 without * copying them again.
*/ if (parseargline(str, &a1, &a2, &a2_end, &a3)) { return apr_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str, "'", NULL);
}
/* arg1: the input string */
newcond->input = a1;
/* arg3: optional flags field * (this has to be parsed first, because we need to * know if the regex should be compiled with ICASE!)
*/
newcond->flags = CONDFLAG_NONE; if (a3 != NULL) { if ((err = cmd_parseflagfield(cmd->pool, newcond, a3,
cmd_rewritecond_setflag)) != NULL) { return apr_pstrcat(cmd->pool, "RewriteCond: ", err, NULL);
}
}
/* arg2: the pattern */
newcond->pattern = a2; if (*a2 == '!') {
newcond->flags |= CONDFLAG_NOTMATCH;
++a2;
}
/* make a new entry in the internal rewrite rule list */ if (cmd->path == NULL) { /* is server command */
newrule = apr_array_push(sconf->rewriterules);
} else { /* is per-directory command */
newrule = apr_array_push(dconf->rewriterules);
}
/* parse the argument line ourself */ if (parseargline(str, &a1, &a2, &a2_end, &a3)) { return apr_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str, "'", NULL);
}
/* arg2: the output string */
newrule->output = a2; if (*a2 == '-' && !a2[1]) {
newrule->flags |= RULEFLAG_NOSUB;
}
if (*(a2_end-1) == '?') { /* a literal ? at the end of the unsubstituted rewrite rule */ if (newrule->flags & RULEFLAG_QSAPPEND) { /* with QSA, splitout_queryargs will safely handle it if RULEFLAG_QSLAST is set */
newrule->flags |= RULEFLAG_QSLAST;
} else { /* avoid getting a query string via inadvertent capture */
newrule->flags |= RULEFLAG_QSNONE; /* trailing ? has done its job, but splitout_queryargs will not chop it off */
*(a2_end-1) = '\0';
}
} elseif (newrule->flags & RULEFLAG_QSDISCARD) { if (NULL == ap_strchr(newrule->output, '?')) {
newrule->flags |= RULEFLAG_QSNONE;
}
}
/* now, if the server or per-dir config holds an * array of RewriteCond entries, we take it for us * and clear the array
*/ if (cmd->path == NULL) { /* is server command */
newrule->rewriteconds = sconf->rewriteconds;
sconf->rewriteconds = apr_array_make(cmd->pool, 2, sizeof(rewritecond_entry));
} else { /* is per-directory command */
newrule->rewriteconds = dconf->rewriteconds;
dconf->rewriteconds = apr_array_make(cmd->pool, 2, sizeof(rewritecond_entry));
}
case CONDPAT_STR_EQ: /* Note: the only type where the operator is dropped from p->pattern */ if (p->flags & CONDFLAG_NOCASE) {
rc = !strcasecmp(input, p->pattern);
} else {
rc = !strcmp(input, p->pattern);
} break;
case CONDPAT_INT_GE: rc = (atoi(input) >= atoi(p->pattern)); break; case CONDPAT_INT_GT: rc = (atoi(input) > atoi(p->pattern)); break;
case CONDPAT_INT_LE: rc = (atoi(input) <= atoi(p->pattern)); break; case CONDPAT_INT_LT: rc = (atoi(input) < atoi(p->pattern)); break;
case CONDPAT_INT_EQ: rc = (atoi(input) == atoi(p->pattern)); break;
/* Since we want to match against the (so called) full URL, we have * to re-add the PATH_INFO postfix
*/ if (r->path_info && *r->path_info) {
rewritelog(r, 3, ctx->perdir, "add path info postfix: %s -> %s%s",
ctx->uri, ctx->uri, r->path_info);
ctx->uri = apr_pstrcat(r->pool, ctx->uri, r->path_info, NULL);
}
/* Additionally we strip the physical path from the url to match * it independent from the underlying filesystem.
*/ if (!is_proxyreq && strlen(ctx->uri) >= dirlen &&
!strncmp(ctx->uri, ctx->perdir, dirlen)) {
/* Try to match the URI against the RewriteRule pattern * and exit immediately if it didn't apply.
*/
rewritelog(r, 3, ctx->perdir, "applying pattern '%s' to uri '%s'",
p->pattern, ctx->uri);
/* Ok, we already know the pattern has matched, but we now * additionally have to check for all existing preconditions * (RewriteCond) which have to be also true. We do this at * this very late stage to avoid unnecessary checks which * would slow down the rewriting engine.
*/
rewriteconds = p->rewriteconds;
conds = (rewritecond_entry *)rewriteconds->elts;
for (i = 0; i < rewriteconds->nelts; ++i) {
rewritecond_entry *c = &conds[i];
rc = apply_rewrite_cond(c, ctx);
/* Error while evaluating cond, r->status set */ if (COND_RC_STATUS_SET == rc) { return RULE_RC_STATUS_SET;
}
/* * Reset vary_this if the novary flag is set for this condition.
*/ if (c->flags & CONDFLAG_NOVARY) {
ctx->vary_this = NULL;
} if (c->flags & CONDFLAG_ORNEXT) { if (!rc) { /* One condition is false, but another can be still true. */
ctx->vary_this = NULL; continue;
} else { /* skip the rest of the chained OR conditions */ while ( i < rewriteconds->nelts
&& c->flags & CONDFLAG_ORNEXT) {
c = &conds[++i];
}
}
} elseif (!rc) { return RULE_RC_NOMATCH;
}
/* If some HTTP header was involved in the condition, remember it * for later use
*/ if (ctx->vary_this) {
ctx->vary = ctx->vary
? apr_pstrcat(r->pool, ctx->vary, ", ", ctx->vary_this,
NULL)
: ctx->vary_this;
ctx->vary_this = NULL;
}
}
/* expand the result */ if (!(p->flags & RULEFLAG_NOSUB)) { int unsafe_qmark = -1;
if (unsafe_qmark > 0) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10508) "Unsafe URL with %%3f URL rewritten without " "UnsafeAllow3F");
r->status = HTTP_FORBIDDEN; return RULE_RC_STATUS_SET;
}
}
/* expand [E=var:val] and [CO=<cookie>] */
do_expand_env(p->env, ctx);
do_expand_cookie(p->cookie, ctx);
/* non-substitution rules ('RewriteRule <pat> -') end here. */ if (p->flags & RULEFLAG_NOSUB) {
force_type_handler(p, ctx);
if (p->flags & RULEFLAG_STATUS) {
rewritelog(r, 2, ctx->perdir, "forcing responsecode %d for %s",
p->forced_responsecode, r->filename);
r->status = p->forced_responsecode;
}
return RULE_RC_NOSUB;
}
/* Add the previously stripped per-directory location prefix, unless * (1) it's an absolute URL path and * (2) it's a full qualified URL
*/ if (!is_proxyreq
&& !is_absolute_path(newuri)
&& !AP_IS_SLASH(*newuri)
&& !is_absolute_uri(newuri, NULL)) { if (ctx->perdir) {
rewritelog(r, 3, ctx->perdir, "add per-dir prefix: %s -> %s%s",
newuri, ctx->perdir, newuri);
newuri = apr_pstrcat(r->pool, ctx->perdir, newuri, NULL);
prefix_added = 1;
} elseif (!(p->flags & (RULEFLAG_PROXY | RULEFLAG_FORCEREDIRECT))) { /* Not an absolute URI-path and the scheme (if any) is unknown, * and it won't be passed to fully_qualify_uri() below either, * so add an implicit '/' prefix. This avoids potentially a common * rule like "RewriteRule ^/some/path(.*) $1" that is given a path * like "/some/pathscheme:..." to produce the fully qualified URL * "scheme:..." which could be misinterpreted later.
*/
rewritelog(r, 3, ctx->perdir, "add root prefix: %s -> /%s",
newuri, newuri);
/* If this rule is forced for proxy throughput * (`RewriteRule ... ... [P]') then emulate mod_proxy's * URL-to-filename handler to be sure mod_proxy is triggered * for this URL later in the Apache API. But make sure it is * a fully-qualified URL. (If not it is qualified with * ourself).
*/ if (p->flags & RULEFLAG_PROXY) {
fully_qualify_uri(r);
rewritelog(r, 2, ctx->perdir, "forcing proxy-throughput with %s",
r->filename);
/* If this rule is explicitly forced for HTTP redirection * (`RewriteRule .. .. [R]') then force an external HTTP * redirect. But make sure it is a fully-qualified URL. (If * not it is qualified with ourself).
*/ if (p->flags & RULEFLAG_FORCEREDIRECT) {
fully_qualify_uri(r);
rewritelog(r, 2, ctx->perdir, "explicitly forcing redirect with %s",
r->filename);
/* Special Rewriting Feature: Self-Reduction * We reduce the URL by stripping a possible * http[s]://<ourhost>[:<port>] prefix, i.e. a prefix which * corresponds to ourself. This is to simplify rewrite maps * and to avoid recursion, etc. When this prefix is not a * coincidence then the user has to use [R] explicitly (see * above).
*/
reduce_uri(r);
/* If this rule is still implicitly forced for HTTP * redirection (`RewriteRule .. <scheme>://...') then * directly force an external HTTP redirect.
*/ if (is_absolute_uri(r->filename, NULL)) {
rewritelog(r, 2, ctx->perdir, "implicitly forcing redirect (rc=%d " "with %s", p->forced_responsecode, r->filename);
if (!((p->flags & RULEFLAG_UNC) || prefix_added)) { /* merge leading slashes, unless they were literals in the sub */ if (!AP_IS_SLASH(p->output[0]) || !AP_IS_SLASH(p->output[1])) { while (AP_IS_SLASH(r->filename[0]) &&
AP_IS_SLASH(r->filename[1])) {
r->filename++;
}
}
}
/* Finally remember the forced mime-type */
force_type_handler(p, ctx);
/* Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_) * But now we're done for this particular rule.
*/ return RULE_RC_MATCH;
}
/* * Apply a complete rule set, * i.e. a list of rewrite rules
*/ staticint apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules, char *perdir, rewriterule_entry **lastsub)
{
rewriterule_entry *entries;
rewriterule_entry *p; int i; int changed;
rule_return_type rc; int s;
rewrite_ctx *ctx; int round = 1;
/* * Iterate over all existing rules
*/
entries = (rewriterule_entry *)rewriterules->elts;
changed = 0;
loop: for (i = 0; i < rewriterules->nelts; i++) {
p = &entries[i];
/* * Ignore this rule on subrequests if we are explicitly * asked to do so or this is a proxy-throughput or a * forced redirect rule.
*/ if (r->main != NULL &&
(p->flags & RULEFLAG_IGNOREONSUBREQ ||
p->flags & RULEFLAG_FORCEREDIRECT )) { continue;
}
/* * Apply the current rule.
*/
ctx->vary = NULL;
rc = apply_rewrite_rule(p, ctx);
if (rc != RULE_RC_NOMATCH) {
if (!(p->flags & RULEFLAG_NOSUB)) {
rewritelog(r, 2, perdir, "setting lastsub to rule with output %s", p->output);
*lastsub = p;
}
/* Catch looping rules with pathinfo growing unbounded */ if ( strlen( r->filename ) > 2*r->server->limit_req_line ) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, "RewriteRule '%s' and URI '%s' " "exceeded maximum length (%d)",
p->pattern, r->uri, 2*r->server->limit_req_line );
r->status = HTTP_INTERNAL_SERVER_ERROR; return ACTION_STATUS;
}
/* Regardless of what we do next, we've found a match. Check to see * if any of the request header fields were involved, and add them * to the Vary field of the response.
*/ if (ctx->vary) {
apr_table_merge(r->headers_out, "Vary", ctx->vary);
}
/* Error while evaluating rule, r->status set */ if (RULE_RC_STATUS_SET == rc) { return ACTION_STATUS_SET;
}
/* * The rule sets the response code (implies match-only)
*/ if (p->flags & RULEFLAG_STATUS) { return ACTION_STATUS;
}
/* * Indicate a change if this was not a match-only rule.
*/ if (rc != RULE_RC_NOSUB) {
changed = ((p->flags & RULEFLAG_NOESCAPE)
? ACTION_NOESCAPE : ACTION_NORMAL);
}
/* * Pass-Through Feature (`RewriteRule .. .. [PT]'): * Because the Apache 1.x API is very limited we * need this hack to pass the rewritten URL to other * modules like mod_alias, mod_userdir, etc.
*/ if (p->flags & RULEFLAG_PASSTHROUGH) {
rewritelog(r, 2, perdir, "forcing '%s' to get passed through " "to next API URI-to-filename handler", r->filename);
r->filename = apr_pstrcat(r->pool, "passthrough:",
r->filename, NULL);
changed = ACTION_NORMAL; break;
}
if (p->flags & RULEFLAG_END) {
rewritelog(r, 8, perdir, "Rule has END flag, no further rewriting for this request");
apr_pool_userdata_set("1", really_last_key, apr_pool_cleanup_null, r->pool); break;
} /* * Stop processing also on proxy pass-through and * last-rule and new-round flags.
*/ if (p->flags & (RULEFLAG_PROXY | RULEFLAG_LASTRULE)) { break;
}
/* * On "new-round" flag we just start from the top of * the rewriting ruleset again.
*/ if (p->flags & RULEFLAG_NEWROUND) { if (++round >= p->maxrounds) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02596) "RewriteRule '%s' and URI '%s' exceeded " "maximum number of rounds (%d) via the [N] flag",
p->pattern, r->uri, p->maxrounds);
/* * If we are forced to skip N next rules, do it now.
*/ if (p->skip > 0) {
s = p->skip; while ( i < rewriterules->nelts
&& s > 0) {
i++;
s--;
}
}
} else { /* * If current rule is chained with next rule(s), * skip all this next rule(s)
*/ while ( i < rewriterules->nelts
&& p->flags & RULEFLAG_CHAIN) {
i++;
p = &entries[i];
}
}
} return changed;
}
/* if we are not doing the initial config, step through the servers and * open the RewriteMap prg:xxx programs,
*/ if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_CONFIG) { for (; s; s = s->next) { if (run_rewritemap_programs(s, p) != APR_SUCCESS) { return HTTP_INTERNAL_SERVER_ERROR;
}
}
}
return OK;
}
staticvoid init_child(apr_pool_t *p, server_rec *s)
{
apr_status_t rv = 0; /* get a rid of gcc warning (REWRITELOG_DISABLED) */
if (rewrite_mapr_lock_acquire) {
rv = apr_global_mutex_child_init(&rewrite_mapr_lock_acquire,
apr_global_mutex_lockfile(rewrite_mapr_lock_acquire), p); if (rv != APR_SUCCESS) {
ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00666) "mod_rewrite: could not init rewrite_mapr_lock_acquire" " in child");
}
}
/* create the lookup cache */ if (!init_cache(p)) {
ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00667) "mod_rewrite: could not init map cache in child");
}
}
/* * only do something under runtime if the engine is really enabled, * else return immediately!
*/ if (!dconf || dconf->state == ENGINE_DISABLED) { return DECLINED;
}
/* * check for the ugly API case of a virtual host section where no * mod_rewrite directives exists. In this situation we became no chance * by the API to setup our default per-server config so we have to * on-the-fly assume we have the default config. But because the default * config has a disabled rewriting engine we are lucky because can * just stop operating now.
*/ if (conf->server != r->server) { return DECLINED;
}
/* END flag was used as a RewriteRule flag on this request */
apr_pool_userdata_get(&skipdata, really_last_key, r->pool); if (skipdata != NULL) {
rewritelog(r, 8, NULL, "Declining, no further rewriting due to END flag"); return DECLINED;
}
/* Unless the anyuri option is set, ensure that the input to the * first rule really is a URL-path, avoiding security issues with
* poorly configured rules. See CVE-2011-3368, CVE-2011-4317. */ if ((dconf->options & OPTION_ANYURI) == 0
&& ((r->unparsed_uri[0] == '*' && r->unparsed_uri[1] == '\0')
|| !r->uri || r->uri[0] != '/')) {
rewritelog(r, 8, NULL, "Declining, request-URI '%s' is not a URL-path. " "Consult the manual entry for the RewriteOptions directive " "for options and caveats about matching other strings.",
r->uri); return DECLINED;
}
/* * remember the original query string for later check, since we don't * want to apply URL-escaping when no substitution has changed it.
*/
oargs = r->args;
/* * add the SCRIPT_URL variable to the env. this is a bit complicated * due to the fact that apache uses subrequests and internal redirects
*/
if (r->main == NULL) {
var = apr_table_get(r->subprocess_env, REDIRECT_ENVVAR_SCRIPT_URL); if (var == NULL) {
apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, r->uri);
} else {
apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
}
} else {
var = apr_table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL);
apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
}
/* * create the SCRIPT_URI variable for the env
*/
/* add the canonical URI of this URL */
thisserver = ap_get_server_name_for_url(r);
port = ap_get_server_port(r); if (ap_is_default_port(port, r)) {
thisport = "";
} else {
thisport = apr_psprintf(r->pool, ":%u", port);
}
thisurl = apr_table_get(r->subprocess_env, ENVVAR_SCRIPT_URL);
/* set the variable */
var = apr_pstrcat(r->pool, ap_http_scheme(r), "://", thisserver, thisport,
thisurl, NULL);
apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URI, var);
if (!(saved_rulestatus = apr_table_get(r->notes,"mod_rewrite_rewritten"))) { /* if filename was not initially set, * we start with the requested URI
*/ if (r->filename == NULL) {
r->filename = apr_pstrdup(r->pool, r->uri);
rewritelog(r, 2, NULL, "init rewrite engine with requested uri %s",
r->filename);
} else {
rewritelog(r, 2, NULL, "init rewrite engine with passed filename " "%s. Original uri = %s", r->filename, r->uri);
}
/* * now apply the rules ...
*/
rulestatus = apply_rewrite_list(r, conf->rewriterules, NULL, &lastsub);
apr_table_setn(r->notes, "mod_rewrite_rewritten",
apr_psprintf(r->pool,"%d",rulestatus));
} else {
rewritelog(r, 2, NULL, "uri already rewritten. Status %s, Uri %s, " "r->filename %s", saved_rulestatus, r->uri, r->filename);
if (r->args
&& !will_escape
&& *(ap_scan_vchar_obstext(r->args))) { /* * We have a raw control character or a ' ' in r->args. * Correct encoding was missed. * Correct encoding was missed and we're not going to escape * it before returning.
*/
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10410) "Rewritten query string contains control " "characters or spaces"); return HTTP_FORBIDDEN;
}
if (ACTION_STATUS == rulestatus) { int n = r->status;
if (to_proxyreq) { /* it should be go on as an internal proxy request */
/* check if the proxy module is enabled, so * we can actually use it!
*/ if (!proxy_available) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00669) "attempt to make remote request from mod_rewrite " "without proxy enabled: %s", r->filename); return HTTP_FORBIDDEN;
}
if (rulestatus == ACTION_NOESCAPE) {
apr_table_setn(r->notes, "proxy-nocanon", "1");
}
/* make sure the QUERY_STRING and * PATH_INFO parts get incorporated
*/ if (r->path_info != NULL) {
r->filename = apr_pstrcat(r->pool, r->filename,
r->path_info, NULL);
} if ((r->args != NULL)
&& ((r->proxyreq == PROXYREQ_PROXY)
|| apr_table_get(r->notes, "proxy-nocanon"))) { /* see proxy_http:proxy_http_canon() */
r->filename = apr_pstrcat(r->pool, r->filename, "?", r->args, NULL);
}
/* now make sure the request gets handled by the proxy handler */ if (PROXYREQ_NONE == r->proxyreq) {
r->proxyreq = PROXYREQ_REVERSE;
}
r->handler = "proxy-server";
rewritelog(r, 1, NULL, "go-ahead with proxy request %s [OK]",
r->filename); return OK;
} elseif (skip_absolute > 0) { int n;
/* it was finally rewritten to a remote URL */
if (rulestatus != ACTION_NOESCAPE) {
rewritelog(r, 1, NULL, "escaping %s for redirect",
r->filename);
r->filename = escape_absolute_uri(r->pool, r->filename, skip_absolute);
}
/* append the QUERY_STRING part */ if (r->args) { char *escaped_args = NULL; int noescape = (rulestatus == ACTION_NOESCAPE ||
(oargs && !strcmp(r->args, oargs)));
/* determine HTTP redirect response code */ if (ap_is_HTTP_REDIRECT(r->status)) {
n = r->status;
r->status = HTTP_OK; /* make Apache kernel happy */
} else {
n = HTTP_MOVED_TEMPORARILY;
}
/* now do the redirection */
apr_table_setn(r->headers_out, "Location", r->filename);
rewritelog(r, 1, NULL, "redirect to %s [REDIRECT/%d]", r->filename,
n);
return n;
} elseif (flen > 12 && strncmp(r->filename, "passthrough:", 12) == 0) { /* * Hack because of underpowered API: passing the current * rewritten filename through to other URL-to-filename handlers * just as it were the requested URL. This is to enable * post-processing by mod_alias, etc. which always act on * r->uri! The difference here is: We do not try to * add the document root
*/
r->uri = apr_pstrdup(r->pool, r->filename+12); return DECLINED;
} else { /* it was finally rewritten to a local path */ constchar *uri_reduced = NULL;
/* the filename must be either an absolute local path or an * absolute local URL.
*/ if ( *r->filename != '/'
&& !ap_os_is_path_absolute(r->pool, r->filename)) { return HTTP_BAD_REQUEST;
}
/* We have r->filename as a path in a server-context rewrite without * the PT flag. The historical behavior is to treat it as a verbatim * filesystem path iff the first component of the path exists and is * readable by httpd. Otherwise, it is interpreted as DocumentRoot * relative. * * NOTICE: * We cannot leave out the prefix_stat because * - If we always prefix with document_root * then no absolute path can could ever be used in * a substitution. e.g. emulating an Alias. * - If we never prefix with document_root * then the files under document_root have to * be references directly and document_root * gets never used and will be a dummy parameter - * this is also bad. * - Later addition: This part is questionable. * If we had never prefixed, users would just * need %{DOCUMENT_ROOT} in substitutions or the * [PT] flag. * * BUT: * Under real Unix systems this is no perf problem, * because we only do stat() on the first directory * and this gets cached by the kernel for along time!
*/
/* if there is no per-dir config we return immediately */ if (dconf == NULL) { return DECLINED;
}
/* * only do something under runtime if the engine is really enabled, * for this directory, else return immediately!
*/ if (dconf->state == ENGINE_DISABLED) { return DECLINED;
}
/* if there are no real (i.e. no RewriteRule directives!)
per-dir config of us, we return also immediately */ if (dconf->directory == NULL) { return DECLINED;
}
/* * .htaccess file is called before really entering the directory, i.e.: * URL: http://localhost/foo and .htaccess is located in foo directory * Ignore such attempts, allowing mod_dir to direct the client to the * canonical URL. This can be controlled with the AllowNoSlash option.
*/ if (!is_proxyreq && !(dconf->options & OPTION_NOSLASH)) {
l = strlen(dconf->directory) - 1; if (r->filename && strlen(r->filename) == l &&
(dconf->directory)[l] == '/' &&
!strncmp(r->filename, dconf->directory, l)) { return DECLINED;
}
}
/* END flag was used as a RewriteRule flag on this request */
apr_pool_userdata_get(&skipdata, really_last_key, r->pool); if (skipdata != NULL) {
rewritelog(r, 8, dconf->directory, "Declining, no further rewriting due to END flag"); return DECLINED;
}
/* * Do the Options check after engine check, so * the user is able to explicitly turn RewriteEngine Off.
*/ if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) { /* FollowSymLinks is mandatory! */
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00670) "Options FollowSymLinks and SymLinksIfOwnerMatch are both off, " "so the RewriteRule directive is also forbidden " "due to its similar ability to circumvent directory restrictions : " "%s", r->filename); return HTTP_FORBIDDEN;
}
/* * remember the current filename before rewriting for later check * to prevent deadlooping because of internal redirects * on final URL/filename which can be equal to the initial one. * also, we'll restore original r->filename if we decline this * request
*/
ofilename = r->filename;
oargs = r->args;
if (r->args
&& !will_escape
&& *(ap_scan_vchar_obstext(r->args))) { /* * We have a raw control character or a ' ' in r->args. * Correct encoding was missed.
*/
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10411) "Rewritten query string contains control " "characters or spaces"); return HTTP_FORBIDDEN;
}
if (ACTION_STATUS == rulestatus) { int n = r->status;
if (to_proxyreq) { /* it should go on as an internal proxy request */
/* check if the proxy module is enabled, so * we can actually use it!
*/ if (!proxy_available) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10160) "attempt to make remote request from mod_rewrite " "without proxy enabled: %s", r->filename); return HTTP_FORBIDDEN;
}
if (rulestatus == ACTION_NOESCAPE) {
apr_table_setn(r->notes, "proxy-nocanon", "1");
}
/* make sure the QUERY_STRING gets incorporated in the case * [NE] was specified on the Proxy rule. We are preventing * mod_proxy canon handler from incorporating r->args as well * as escaping the URL. * (r->path_info was already appended by the * rewriting engine because of the per-dir context!)
*/ if ((r->args != NULL) && apr_table_get(r->notes, "proxy-nocanon")) {
r->filename = apr_pstrcat(r->pool, r->filename, "?", r->args, NULL);
}
/* now make sure the request gets handled by the proxy handler */ if (PROXYREQ_NONE == r->proxyreq) {
r->proxyreq = PROXYREQ_REVERSE;
}
r->handler = "proxy-server";
rewritelog(r, 1, dconf->directory, "go-ahead with proxy request " "%s [OK]", r->filename); return OK;
} elseif (skip_absolute > 0) { /* it was finally rewritten to a remote URL */
/* because we are in a per-dir context * first try to replace the directory with its base-URL * if there is a base-URL available
*/ if (dconf->baseurl != NULL) { /* skip 'scheme://' */
cp = r->filename + skip_absolute;
if ((cp = ap_strchr(cp, '/')) != NULL && *(++cp)) {
rewritelog(r, 2, dconf->directory, "trying to replace prefix %s with %s",
dconf->directory, dconf->baseurl);
/* I think, that hack needs an explanation: * well, here is it: * mod_rewrite was written for unix systems, were * absolute file-system paths start with a slash. * URL-paths _also_ start with slashes, so they * can be easily compared with system paths. * * the following assumes, that the actual url-path * may be prefixed by the current directory path and * tries to replace the system path with the RewriteBase * URL. * That assumption is true if we use a RewriteRule like * * RewriteRule ^foo bar [R] * * (see apply_rewrite_rule function) * However on systems that don't have a / as system * root this will never match, so we skip the / after the * hostname and compare/substitute only the stuff after it. * * (note that cp was already increased to the right value)
*/
cp2 = subst_prefix_path(r, cp, (*dconf->directory == '/')
? dconf->directory + 1
: dconf->directory,
dconf->baseurl + 1); if (strcmp(cp2, cp) != 0) {
*cp = '\0';
r->filename = apr_pstrcat(r->pool, r->filename,
cp2, NULL);
}
}
}
/* now prepare the redirect... */ if (rulestatus != ACTION_NOESCAPE) {
rewritelog(r, 1, dconf->directory, "escaping %s for redirect",
r->filename);
r->filename = escape_absolute_uri(r->pool, r->filename, skip_absolute);
}
/* append the QUERY_STRING part */ if (r->args) { char *escaped_args = NULL; int noescape = (rulestatus == ACTION_NOESCAPE ||
(oargs && !strcmp(r->args, oargs)));
/* determine HTTP redirect response code */ if (ap_is_HTTP_REDIRECT(r->status)) {
n = r->status;
r->status = HTTP_OK; /* make Apache kernel happy */
} else {
n = HTTP_MOVED_TEMPORARILY;
}
/* now do the redirection */
apr_table_setn(r->headers_out, "Location", r->filename);
rewritelog(r, 1, dconf->directory, "redirect to %s [REDIRECT/%d]",
r->filename, n); return n;
} else { constchar *tmpfilename = NULL; /* it was finally rewritten to a local path */
/* if someone used the PASSTHROUGH flag in per-dir * context we just ignore it. It is only useful * in per-server context
*/ if (l > 12 && strncmp(r->filename, "passthrough:", 12) == 0) {
r->filename = apr_pstrdup(r->pool, r->filename+12);
}
/* the filename must be either an absolute local path or an * absolute local URL.
*/ if ( *r->filename != '/'
&& !ap_os_is_path_absolute(r->pool, r->filename)) { return HTTP_BAD_REQUEST;
}
/* Check for deadlooping: * At this point we KNOW that at least one rewriting * rule was applied, but when the resulting URL is * the same as the initial URL, we are not allowed to * use the following internal redirection stuff because * this would lead to a deadloop.
*/ if (ofilename != NULL && strcmp(r->filename, ofilename) == 0) {
rewritelog(r, 1, dconf->directory, "initial URL equal rewritten" " URL: %s [IGNORING REWRITE]", r->filename); return OK;
}
tmpfilename = r->filename;
/* if there is a valid base-URL then substitute * the per-dir prefix with this base-URL if the * current filename still is inside this per-dir * context. If not then treat the result as a * plain URL
*/ if (dconf->baseurl != NULL) {
rewritelog(r, 2, dconf->directory, "trying to replace prefix " "%s with %s", dconf->directory, dconf->baseurl);
r->filename = subst_prefix_path(r, r->filename,
dconf->directory,
dconf->baseurl);
} else { /* if no explicit base-URL exists we assume * that the directory prefix is also a valid URL * for this webserver and only try to remove the * document_root if it is prefix
*/ if ((ccp = ap_document_root(r)) != NULL) { /* strip trailing slash */
l = strlen(ccp); if (ccp[l-1] == '/') {
--l;
} if (!strncmp(r->filename, ccp, l) &&
r->filename[l] == '/') {
rewritelog(r, 2,dconf->directory, "strip document_root" " prefix: %s -> %s", r->filename,
r->filename+l);
/* No base URL, or r->filename wasn't still under dconf->directory * or, r->filename wasn't still under the document root. * If there's a context document root AND a context prefix, and * the context document root is a prefix of r->filename, replace. * This allows a relative substitution on a path found by mod_userdir * or mod_alias without baking in a RewriteBase.
*/ if (tmpfilename == r->filename &&
!(dconf->options & OPTION_IGNORE_CONTEXT_INFO)) { if ((ccp = ap_context_document_root(r)) != NULL) { constchar *prefix = ap_context_prefix(r); if (prefix != NULL) {
rewritelog(r, 2, dconf->directory, "trying to replace " "context docroot %s with context prefix %s",
ccp, prefix);
r->filename = subst_prefix_path(r, r->filename,
ccp, prefix);
}
}
}
/* type */
t = apr_table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR); if (t && *t) {
rewritelog(r, 1, NULL, "force filename %s to have MIME-type '%s'",
r->filename, t);
ap_set_content_type_ex(r, t, 1);
}
/* handler */
t = apr_table_get(r->notes, REWRITE_FORCED_HANDLER_NOTEVAR); if (t && *t) {
rewritelog(r, 1, NULL, "force filename %s to have the " "Content-handler '%s'", r->filename, t);
r->handler = t;
}
return OK;
}
/* * "content" handler for internal redirects
*/ staticint handler_redirect(request_rec *r)
{ if (strcmp(r->handler, REWRITE_REDIRECT_HANDLER_NAME)) { return DECLINED;
}
/* just make sure that we are really meant! */ if (strncmp(r->filename, "redirect:", 9) != 0) { return DECLINED;
}
/* now do the internal redirect */
ap_internal_redirect(apr_pstrcat(r->pool, r->filename+9,
r->args ? "?" : NULL, r->args, NULL), r);
staticconst command_rec command_table[] = {
AP_INIT_FLAG( "RewriteEngine", cmd_rewriteengine, NULL, OR_FILEINFO, "On or Off to enable or disable (default) the whole " "rewriting engine"),
AP_INIT_ITERATE( "RewriteOptions", cmd_rewriteoptions, NULL, OR_FILEINFO, "List of option strings to set"),
AP_INIT_TAKE1( "RewriteBase", cmd_rewritebase, NULL, OR_FILEINFO, "the base URL of the per-directory context"),
AP_INIT_RAW_ARGS("RewriteCond", cmd_rewritecond, NULL, OR_FILEINFO, "an input string and a to be applied regexp-pattern"),
AP_INIT_RAW_ARGS("RewriteRule", cmd_rewriterule, NULL, OR_FILEINFO, "an URL-applied regexp-pattern and a substitution URL"),
AP_INIT_TAKE23( "RewriteMap", cmd_rewritemap, NULL, RSRC_CONF, "a mapname and a filename and options"),
{ NULL }
};
/* make the hashtable before registering the function, so that * other modules are prevented from accessing uninitialized memory.
*/
mapfunc_hash = apr_hash_make(p);
APR_REGISTER_OPTIONAL_FN(ap_register_rewrite_mapfunc);
/* allow to change the uri before mod_proxy takes over it */
ap_hook_translate_name(hook_uri2file, NULL, aszModProxy, APR_HOOK_FIRST); /* fixup before mod_proxy so that a [P] URL gets fixed up there */
ap_hook_fixups(hook_fixup, NULL, aszModProxy, APR_HOOK_FIRST);
ap_hook_fixups(hook_mimetype, NULL, NULL, APR_HOOK_LAST);
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.127Bemerkung:
(vorverarbeitet am 2026-04-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.