/* 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 */ #include "apr.h" #include "apr_strings.h" #include "apr_hash.h" #include "apr_user.h" #include "apr_lib.h" #include "apr_global_mutex.h" #include "apr_dbm.h" #include "apr_dbd.h" #include "apr_version.h" #if !APR_VERSION_AT_LEAST(2,0,0) #include "apu_version.h" #endif #include "mod_dbd.h" #if APR_HAS_THREADS #include "apr_thread_mutex.h" #endif #define APR_WANT_MEMFUNC #define APR_WANT_STRFUNC #define APR_WANT_IOVEC #include "apr_want.h" #if APR_HAVE_STDARG_H #include #endif #if APR_HAVE_STDLIB_H #include #endif #if APR_HAVE_NETINET_IN_H #include #endif #include "ap_config.h" #include "httpd.h" #include "http_config.h" #include "http_request.h" #include "http_core.h" #include "http_log.h" #include "http_protocol.h" #include "http_ssl.h" #include "http_vhost.h" #include "util_mutex.h" #include "mod_rewrite.h" #include "ap_expr.h" #if APR_CHARSET_EBCDIC #include "util_charset.h" #endif #include "test_char.h" static ap_dbd_t *(*dbd_acquire)(request_rec*) = NULL; static void (*dbd_prepare)(server_rec*, const char*, const char*) = NULL; static const char* really_last_key = "rewrite_really_last"; /* * 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 #ifndef REWRITELOG_DISABLED #ifdef AP_HAVE_C99 #define rewritelog(...) do_rewritelog(__LINE__, __VA_ARGS__) #else #define rewritelog do_rewritelog #endif #define REWRITELOG_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD ) #define REWRITELOG_FLAGS ( APR_WRITE | APR_APPEND | APR_CREATE ) #else /* !REWRITELOG_DISABLED */ #ifdef AP_HAVE_C99 #define rewritelog(...) #else #define rewritelog do_rewritelog #endif #endif /* REWRITELOG_DISABLED */ /* remembered mime-type for [T=...] */ #define REWRITE_FORCED_MIMETYPE_NOTEVAR "rewrite-forced-mimetype" #define REWRITE_FORCED_HANDLER_NOTEVAR "rewrite-forced-handler" #define ENVVAR_SCRIPT_URL "SCRIPT_URL" #define REDIRECT_ENVVAR_SCRIPT_URL "REDIRECT_" ENVVAR_SCRIPT_URL #define ENVVAR_SCRIPT_URI "SCRIPT_URI" #define CONDFLAG_NONE (1<<0) #define CONDFLAG_NOCASE (1<<1) #define CONDFLAG_NOTMATCH (1<<2) #define CONDFLAG_ORNEXT (1<<3) #define CONDFLAG_NOVARY (1<<4) #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_ESCAPENOPLUS (1<<18) #define RULEFLAG_QSLAST (1<<19) #define RULEFLAG_QSNONE (1<<20) /* programattic only */ #define RULEFLAG_ESCAPECTLS (1<<21) #define RULEFLAG_UNSAFE_PREFIX_STAT (1<<22) #define RULEFLAG_UNSAFE_ALLOW3F (1<<23) #define RULEFLAG_UNC (1<<24) /* 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) #define MAPTYPE_TXT (1<<0) #define MAPTYPE_DBM (1<<1) #define MAPTYPE_PRG (1<<2) #define MAPTYPE_INT (1<<3) #define MAPTYPE_RND (1<<4) #define MAPTYPE_DBD (1<<5) #define MAPTYPE_DBD_CACHE (1<<6) #define ENGINE_DISABLED (1<<0) #define ENGINE_ENABLED (1<<1) #define OPTION_NONE (1<<0) #define OPTION_INHERIT (1<<1) #define OPTION_INHERIT_BEFORE (1<<2) #define OPTION_NOSLASH (1<<3) #define OPTION_ANYURI (1<<4) #define OPTION_MERGEBASE (1<<5) #define OPTION_INHERIT_DOWN (1<<6) #define OPTION_INHERIT_DOWN_BEFORE (1<<7) #define OPTION_IGNORE_INHERIT (1<<8) #define OPTION_IGNORE_CONTEXT_INFO (1<<9) #define OPTION_LEGACY_PREFIX_DOCROOT (1<<10) #define OPTION_LONGOPT (1<<11) #define OPTION_UNSAFE_PREFIX_STAT (1<<12) #ifndef RAND_MAX #define RAND_MAX 32767 #endif /* max line length (incl.\n) in text rewrite maps */ #ifndef REWRITE_MAX_TXT_MAP_LINE #define REWRITE_MAX_TXT_MAP_LINE 1024 #endif /* buffer length for prg rewrite maps */ #ifndef REWRITE_PRG_MAP_BUF #define REWRITE_PRG_MAP_BUF 1024 #endif /* for better readbility */ #define LEFT_CURLY '{' #define RIGHT_CURLY '}' /* * 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))) #ifndef REWRITE_MAX_ROUNDS #define REWRITE_MAX_ROUNDS 10000 #endif /* * +-------------------------------------------------------+ * | | * | Types and Structures * | | * +-------------------------------------------------------+ */ typedef struct { const char *datafile; /* filename for map data files */ const char *dbmtype; /* dbm type for dbm map data files */ const char *checkfile; /* filename to check for map existence */ const char *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 */ const char *dbdq; /* SQL SELECT statement for rewritemap */ const char *checkfile2; /* filename to check for map existence NULL if only one file */ const char *user; /* run RewriteMap program as this user */ const char *group; /* run RewriteMap program as this group */ } rewritemap_entry; /* special pattern types for RewriteCond */ typedef enum { CONDPAT_REGEX = 0, CONDPAT_FILE_EXISTS, CONDPAT_FILE_SIZE, CONDPAT_FILE_LINK, CONDPAT_FILE_DIR, CONDPAT_FILE_XBIT, CONDPAT_LU_URL, CONDPAT_LU_FILE, 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, CONDPAT_AP_EXPR } pattern_type; typedef enum { 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; typedef struct { 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 */ typedef struct data_item { struct data_item *next; char *data; } data_item; typedef struct { 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 */ const char *escapes; /* specific backref escapes */ const char *noescapes; /* specific backref chars not to escape */ } rewriterule_entry; typedef struct { 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 */ unsigned int state_set:1; unsigned int options_set:1; } rewrite_server_conf; typedef struct { 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 */ const char *baseurl; /* the base-URL where it applies */ unsigned int state_set:1; unsigned int options_set:1; unsigned int baseurl_set:1; } rewrite_perdir_conf; /* the (per-child) cache structures. */ typedef struct cache { apr_pool_t *pool; apr_hash_t *maps; #if APR_HAS_THREADS apr_thread_mutex_t *lock; #endif } cache; /* 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. */ typedef struct { apr_time_t mtime; apr_pool_t *pool; apr_hash_t *entries; } cachedmap; /* the regex structure for the * substitution of backreferences */ typedef struct backrefinfo { const char *source; ap_regmatch_t regmatch[AP_MAX_REG_MATCH]; } backrefinfo; /* single linked list used for * variable expansion */ typedef struct result_list { struct result_list *next; apr_size_t len; const char *string; } result_list; /* context structure for variable lookup and expansion */ typedef struct { request_rec *r; const char *uri; const char *vary_this; const char *vary; char *perdir; backrefinfo briRR; backrefinfo briRC; apr_pool_t *temp_pool; } rewrite_ctx; /* * +-------------------------------------------------------+ * | | * | static module data * | | * +-------------------------------------------------------+ */ /* the global module structure */ module AP_MODULE_DECLARE_DATA rewrite_module; /* rewritemap int: handler function registry */ static apr_hash_t *mapfunc_hash; /* the cache */ static cache *cachep; /* whether proxy module is available or not */ static int proxy_available; /* Locks/Mutexes */ static int rewrite_lock_needed = 0; static apr_global_mutex_t *rewrite_mapr_lock_acquire = NULL; static const char *rewritemap_mutex_type = "rewrite-map"; /* Optional functions imported from mod_ssl when loaded: */ static char *escape_backref(apr_pool_t *p, const char *path, const char *escapeme, const char *noescapeme, int flags); /* * +-------------------------------------------------------+ * | | * | rewriting logfile support * | | * +-------------------------------------------------------+ */ /* 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. */ #if !defined(REWRITELOG_DISABLED) || !defined(AP_HAVE_C99) #ifdef AP_HAVE_C99 static void do_rewritelog(int line, request_rec *r, int level, char *perdir, const char *fmt, ...) __attribute__((format(printf,5,6))); #else static void do_rewritelog(request_rec *r, int level, char *perdir, const char *fmt, ...) __attribute__((format(printf,4,5))); #endif static void do_rewritelog( #ifdef AP_HAVE_C99 int line, #endif request_rec *r, int level, char *perdir, const char *fmt, ...) { #ifndef REWRITELOG_DISABLED char *logline, *text; const char *rhost, *rname; int redir; request_rec *req; va_list ap; if (!APLOG_R_IS_LEVEL(r, APLOG_DEBUG + level)) return; rhost = ap_get_useragent_host(r, REMOTE_NOLOOKUP, NULL); rname = ap_get_remote_logname(r); for (redir=0, req=r; req->prev; req = req->prev) { ++redir; } va_start(ap, fmt); text = apr_pvsprintf(r->pool, fmt, ap); va_end(ap); logline = apr_psprintf(r->pool, "%s %s %s [%s/sid#%pp][rid#%pp/%s%s%s] " "%s%s%s%s", rhost ? rhost : "UNKNOWN-HOST", rname ? rname : "-", r->user ? (*r->user ? r->user : "\"\"") : "-", ap_get_server_name(r), (void *)(r->server), (void *)r, r->main ? "subreq" : "initial", redir ? "/redir#" : "", redir ? apr_itoa(r->pool, redir) : "", perdir ? "[perdir " : "", perdir ? perdir : "", perdir ? "] ": "", text); AP_REWRITE_LOG((uintptr_t)r, level, r->main ? 0 : 1, (char *)ap_get_server_name(r), logline); /* Intentional no APLOGNO */ #ifdef AP_HAVE_C99 ap_log_rerror(__FILE__, line, APLOG_MODULE_INDEX, APLOG_DEBUG + level, 0, r, "%s", logline); #else ap_log_rerror(APLOG_MARK, APLOG_DEBUG + level, 0, r, "%s", logline); #endif #endif /* !REWRITELOG_DISABLED */ } #endif /* !defined(REWRITELOG_DISABLED) || !defined(AP_HAVE_C99) */ /* * +-------------------------------------------------------+ * | | * | 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. */ static unsigned is_absolute_uri(char *uri, int *supportsqs) { int dummy, *sqs; sqs = (supportsqs ? supportsqs : &dummy); *sqs = 0; /* fast exit */ if (*uri == '/' || strlen(uri) <= 5) { return 0; } switch (*uri++) { case 'a': case 'A': if (!ap_cstr_casecmpn(uri, "jp://", 5)) { /* ajp:// */ *sqs = 1; return 6; } break; case 'b': case 'B': if (!ap_cstr_casecmpn(uri, "alancer://", 10)) { /* balancer:// */ *sqs = 1; return 11; } break; case 'f': case 'F': if (!ap_cstr_casecmpn(uri, "tp://", 5)) { /* ftp:// */ return 6; } if (!ap_cstr_casecmpn(uri, "cgi://", 6)) { /* fcgi:// */ *sqs = 1; return 7; } break; case 'g': case 'G': if (!ap_cstr_casecmpn(uri, "opher://", 8)) { /* gopher:// */ return 9; } break; case 'h': case 'H': if (!ap_cstr_casecmpn(uri, "ttp://", 6)) { /* http:// */ *sqs = 1; return 7; } else if (!ap_cstr_casecmpn(uri, "ttps://", 7)) { /* https:// */ *sqs = 1; return 8; } else if (!ap_cstr_casecmpn(uri, "2://", 4)) { /* h2:// */ *sqs = 1; return 5; } else if (!ap_cstr_casecmpn(uri, "2c://", 5)) { /* h2c:// */ *sqs = 1; return 6; } break; case 'l': case 'L': if (!ap_cstr_casecmpn(uri, "dap://", 6)) { /* ldap:// */ return 7; } break; case 'm': case 'M': if (!ap_cstr_casecmpn(uri, "ailto:", 6)) { /* mailto: */ *sqs = 1; return 7; } break; case 'n': case 'N': if (!ap_cstr_casecmpn(uri, "ews:", 4)) { /* news: */ return 5; } else if (!ap_cstr_casecmpn(uri, "ntp://", 6)) { /* nntp:// */ return 7; } break; case 's': case 'S': if (!ap_cstr_casecmpn(uri, "cgi://", 6)) { /* scgi:// */ *sqs = 1; return 7; } break; case 'w': case 'W': if (!ap_cstr_casecmpn(uri, "s://", 4)) { /* ws:// */ *sqs = 1; return 5; } else if (!ap_cstr_casecmpn(uri, "ss://", 5)) { /* wss:// */ *sqs = 1; return 6; } break; case 'u': case 'U': if (!ap_cstr_casecmpn(uri, "nix:", 4)) { /* unix: */ *sqs = 1; return (uri[4] == '/' && uri[5] == '/') ? 7 : 5; } } return 0; } static int is_absolute_path(const char *path) { #ifndef CASE_BLIND_FILESYSTEM return (path[0] == '/'); #else return ((AP_IS_SLASH(path[0]) && path[1] == path[0]) || (apr_isalpha(path[0]) && path[1] == ':' && AP_IS_SLASH(path[2]))); #endif } static const char c2x_table[] = "0123456789abcdef"; static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix, unsigned char *where) { #if APR_CHARSET_EBCDIC what = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)what); #endif /*APR_CHARSET_EBCDIC*/ *where++ = prefix; *where++ = c2x_table[what >> 4]; *where++ = c2x_table[what & 0xf]; return where; } /* * Escapes a backreference in a similar way as php's urlencode does. * Based on ap_os_escape_path in server/util.c */ static char *escape_backref(apr_pool_t *p, const char *path, const char *escapeme, const char *noescapeme, int flags) { char *copy = apr_palloc(p, 3 * strlen(path) + 1); const unsigned char *s = (const unsigned char *)path; unsigned char *d = (unsigned char *)copy; int noplus = (flags & RULEFLAG_ESCAPENOPLUS) != 0; int ctls = (flags & RULEFLAG_ESCAPECTLS) != 0; unsigned char c; while ((c = *s)) { if (((ctls ? !TEST_CHAR(c, T_VCHAR_OBSTEXT) : !escapeme) || (escapeme && ap_strchr_c(escapeme, c))) && (!noescapeme || !ap_strchr_c(noescapeme, c))) { if (apr_isalnum(c) || c == '_') { *d++ = c; } else if (c == ' ' && !noplus) { *d++ = '+'; } else { d = c2x(c, '%', d); } } else { *d++ = c; } ++s; } *d = '\0'; return copy; } /* * escape absolute uri, which may or may not be path oriented. * So let's handle them differently. */ static char *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); } /* remember the hostname stuff */ scheme = cp - 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; } return apr_pstrcat(p, apr_pstrndup(p, uri, scheme), ap_escape_uri(p, token[0]), (c >= 1) ? "?" : NULL, (c >= 1) ? ap_escape_uri(p, token[1]) : NULL, (c >= 2) ? "?" : NULL, (c >= 2) ? ap_escape_uri(p, token[2]) : NULL, (c >= 3) ? "?" : NULL, (c >= 3) ? ap_escape_uri(p, token[3]) : NULL, (c >= 4) ? "?" : NULL, (c >= 4) ? ap_escape_uri(p, token[4]) : NULL, NULL); } } /* 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 */ static void 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; } if (qsdiscard) { r->args = NULL; /* Discard query string */ rewritelog(r, 2, NULL, "discarding query string"); } q = qslast ? ap_strrchr(r->filename + skip, '?') : ap_strchr(r->filename + skip, '?'); if (q != NULL) { char *olduri; apr_size_t len; olduri = apr_pstrdup(r->pool, r->filename); *q++ = '\0'; if (qsappend) { if (*q) { r->args = apr_pstrcat(r->pool, q, "&" , r->args, NULL); } } else { r->args = apr_pstrdup(r->pool, q); } if (r->args) { len = strlen(r->args); if (!len) { r->args = NULL; } else if (r->args[len-1] == '&') { r->args[len-1] = '\0'; } } rewritelog(r, 3, NULL, "split uri=%s -> uri=%s, args=%s", olduri, r->filename, r->args ? r->args : ""); } } /* * strip 'http[s]://ourhost/' from URI */ static void reduce_uri(request_rec *r) { char *cp; apr_size_t l; cp = (char *)ap_http_scheme(r); l = strlen(cp); if ( strlen(r->filename) > l+3 && ap_cstr_casecmpn(r->filename, cp, l) == 0 && r->filename[l] == ':' && r->filename[l+1] == '/' && r->filename[l+2] == '/' ) { unsigned short port; char *portp, *host, *url, *scratch; scratch = apr_pstrdup(r->pool, r->filename); /* our scratchpad */ /* 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 = "/"; } } else if (*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 */ static void fully_qualify_uri(request_rec *r) { if (r->method_number == M_CONNECT) { return; } else if (!is_absolute_uri(r->filename, NULL)) { const char *thisserver; char *thisport; int port; thisserver = ap_get_server_name_for_url(r); port = ap_get_server_port(r); thisport = ap_is_default_port(port, r) ? "" : apr_psprintf(r->pool, ":%u", port); r->filename = apr_psprintf(r->pool, "%s://%s%s%s%s", ap_http_scheme(r), thisserver, thisport, (*r->filename == '/') ? "" : "/", r->filename); } return; } static int startsWith(request_rec *r, const char *haystack, const char *needle) { int rc = (ap_strstr_c(haystack, needle) == haystack); rewritelog(r, 5, NULL, "prefix_stat startsWith(%s, %s) %d", haystack, needle, rc); return rc; } /* * stat() only the first segment of a path, and only if it matches the output of the last matching rule */ static int prefix_stat(request_rec *r, const char *path, apr_pool_t *pool, rewriterule_entry *lastsub) { const char *curpath = path; const char *root; const char *slash; char *statpath; apr_status_t rv; rv = apr_filepath_root(&root, &curpath, APR_FILEPATH_TRUENAME, pool); if (rv != APR_SUCCESS) { return 0; } /* let's recognize slashes only, the mod_rewrite semantics are opaque * enough. */ if ((slash = ap_strchr_c(curpath, '/')) != NULL) { rv = apr_filepath_merge(&statpath, root, apr_pstrndup(pool, curpath, (apr_size_t)(slash - curpath)), APR_FILEPATH_NOTABOVEROOT | APR_FILEPATH_NOTRELATIVE, pool); } else { rv = apr_filepath_merge(&statpath, root, curpath, APR_FILEPATH_NOTABOVEROOT | APR_FILEPATH_NOTRELATIVE, pool); } if (rv == APR_SUCCESS) { apr_finfo_t sb; 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 { const char *docroot = ap_document_root(r); const char *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) */ static char *subst_prefix_path(request_rec *r, char *input, const char *match, const char *subst) { apr_size_t len = strlen(match); if (len && match[len - 1] == '/') { --len; } if (!strncmp(input, match, len) && input[len++] == '/') { apr_size_t slen, outlen; char *output; rewritelog(r, 5, NULL, "strip matching prefix: %s -> %s", input, input+len); slen = strlen(subst); if (slen && subst[slen - 1] != '/') { ++slen; } outlen = strlen(input) + slen - len; output = apr_palloc(r->pool, outlen + 1); /* don't forget the \0 */ memcpy(output, subst, slen); if (slen && !output[slen-1]) { output[slen-1] = '/'; } memcpy(output+slen, input+len, outlen - slen); output[outlen] = '\0'; rewritelog(r, 4, NULL, "add subst prefix: %s -> %s", input+len, output); return output; } /* prefix didn't match */ return input; } /* * +-------------------------------------------------------+ * | | * | caching support * | | * +-------------------------------------------------------+ */ static void set_cache_value(const char *name, apr_time_t t, char *key, char *val) { cachedmap *map; if (cachep) { #if APR_HAS_THREADS apr_thread_mutex_lock(cachep->lock); #endif map = apr_hash_get(cachep->maps, name, APR_HASH_KEY_STRING); if (!map) { apr_pool_t *p; if (apr_pool_create(&p, cachep->pool) != APR_SUCCESS) { #if APR_HAS_THREADS apr_thread_mutex_unlock(cachep->lock); #endif return; } apr_pool_tag(p, "rewrite_cachedmap"); map = apr_palloc(cachep->pool, sizeof(cachedmap)); map->pool = p; map->entries = apr_hash_make(map->pool); map->mtime = t; apr_hash_set(cachep->maps, name, APR_HASH_KEY_STRING, map); } else if (map->mtime != t) { apr_pool_clear(map->pool); map->entries = apr_hash_make(map->pool); map->mtime = t; } /* 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 APR_HAS_THREADS apr_thread_mutex_unlock(cachep->lock); #endif } return; } static char *get_cache_value(const char *name, apr_time_t t, char *key, apr_pool_t *p) { cachedmap *map; char *val = NULL; if (cachep) { #if APR_HAS_THREADS apr_thread_mutex_lock(cachep->lock); #endif map = apr_hash_get(cachep->maps, name, APR_HASH_KEY_STRING); 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 APR_HAS_THREADS apr_thread_mutex_unlock(cachep->lock); #endif } return val; } static int init_cache(apr_pool_t *p) { cachep = apr_palloc(p, sizeof(cache)); if (apr_pool_create(&cachep->pool, p) != APR_SUCCESS) { cachep = NULL; /* turns off cache */ return 0; } apr_pool_tag(cachep->pool, "rewrite_cachep"); cachep->maps = apr_hash_make(cachep->pool); #if APR_HAS_THREADS (void)apr_thread_mutex_create(&(cachep->lock), APR_THREAD_MUTEX_DEFAULT, p); #endif return 1; } /* * +-------------------------------------------------------+ * | | * | Map Functions * | | * +-------------------------------------------------------+ */ /* * General Note: key is already a fresh string, created (expanded) just * for the purpose to be passed in here. So one can modify key itself. */ static char *rewrite_mapfunc_toupper(request_rec *r, char *key) { ap_str_toupper(key); return key; } static char *rewrite_mapfunc_tolower(request_rec *r, char *key) { ap_str_tolower(key); return key; } static char *rewrite_mapfunc_escape(request_rec *r, char *key) { return ap_escape_uri(r->pool, key); } static char *rewrite_mapfunc_unescape(request_rec *r, char *key) { ap_unescape_url(key); return key; } static char *select_random_value_part(request_rec *r, char *value) { char *p = value; unsigned n = 0; /* count number of distinct values */ while ((p = ap_strchr(p, '|')) != NULL) { ++n; ++p; } if (n > 0) { n = ap_random_pick(0, n); /* extract the n'th part from the whole string */ while ((p = ap_strchr(value, '|')) != NULL) { if (!n--) { *p = '\0'; break; } value = p + 1; } } return value; } /* child process code */ static void rewrite_child_errfn(apr_pool_t *p, apr_status_t err, const char *desc) { ap_log_error(APLOG_MARK, APLOG_ERR, err, NULL, APLOGNO(00653) "%s", desc); } static apr_status_t rewritemap_program_child(apr_pool_t *p, const char *progname, char **argv, const char *user, const char *group, apr_file_t **fpout, apr_file_t **fpin) { apr_status_t rc; apr_procattr_t *procattr; apr_proc_t *procnew; if ( APR_SUCCESS == (rc=apr_procattr_create(&procattr, p)) && APR_SUCCESS == (rc=apr_procattr_io_set(procattr, APR_FULL_BLOCK, APR_FULL_BLOCK, APR_NO_PIPE)) && APR_SUCCESS == (rc=apr_procattr_dir_set(procattr, ap_make_dirstr_parent(p, argv[0]))) && (!user || APR_SUCCESS == (rc=apr_procattr_user_set(procattr, user, ""))) && (!group || APR_SUCCESS == (rc=apr_procattr_group_set(procattr, group))) && APR_SUCCESS == (rc=apr_procattr_cmdtype_set(procattr, APR_PROGRAM)) && APR_SUCCESS == (rc=apr_procattr_child_errfn_set(procattr, rewrite_child_errfn)) && APR_SUCCESS == (rc=apr_procattr_error_check_set(procattr, 1))) { procnew = apr_pcalloc(p, sizeof(*procnew)); rc = apr_proc_create(procnew, argv[0], (const char **)argv, NULL, procattr, p); if (rc == APR_SUCCESS) { apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT); if (fpin) { (*fpin) = procnew->in; } if (fpout) { (*fpout) = procnew->out; } } } return (rc); } static apr_status_t run_rewritemap_programs(server_rec *s, apr_pool_t *p) { rewrite_server_conf *conf; apr_hash_index_t *hi; apr_status_t rc; conf = ap_get_module_config(s->module_config, &rewrite_module); /* If the engine isn't turned on, * don't even try to do anything. */ if (conf->state == ENGINE_DISABLED) { return APR_SUCCESS; } for (hi = apr_hash_first(p, conf->rewritemaps); hi; hi = apr_hash_next(hi)){ apr_file_t *fpin = NULL; apr_file_t *fpout = NULL; rewritemap_entry *map; void *val; apr_hash_this(hi, NULL, NULL, &val); map = val; if (map->type != MAPTYPE_PRG) { continue; } if (!(map->argv[0]) || !*(map->argv[0]) || map->fpin || map->fpout) { continue; } rc = rewritemap_program_child(p, map->argv[0], map->argv, map->user, map->group, &fpout, &fpin); if (rc != APR_SUCCESS || fpin == NULL || fpout == NULL) { ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, APLOGNO(00654) "mod_rewrite: could not start RewriteMap " "program %s", map->checkfile); return rc; } map->fpin = fpin; map->fpout = fpout; } return APR_SUCCESS; } /* * +-------------------------------------------------------+ * | | * | Lookup functions * | | * +-------------------------------------------------------+ */ static char *lookup_map_txtfile(request_rec *r, const char *file, char *key) { apr_file_t *fp = NULL; char line[REWRITE_MAX_TXT_MAP_LINE + 1]; /* +1 for \0 */ char *value, *keylast; apr_status_t rv; 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); return value; } static char *lookup_map_dbmfile(request_rec *r, const char *file, const char *dbmtype, char *key) { #if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7) const apr_dbm_driver_t *driver; const apu_err_t *err; #endif apr_dbm_t *dbmfp = NULL; apr_datum_t dbmkey; apr_datum_t dbmval; char *value; apr_status_t rv; #if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7) if ((rv = apr_dbm_get_driver(&driver, dbmtype, &err, r->pool)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10287) "mod_rewrite: can't load DBM library '%s': %s", err->reason, err->msg); return NULL; } if ((rv = apr_dbm_open2(&dbmfp, driver, file, APR_DBM_READONLY, APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00656) "mod_rewrite: can't open DBM RewriteMap %s", file); return NULL; } #else if ((rv = apr_dbm_open_ex(&dbmfp, dbmtype, file, APR_DBM_READONLY, APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00656) "mod_rewrite: can't open DBM RewriteMap %s", file); return NULL; } #endif dbmkey.dptr = key; dbmkey.dsize = strlen(key); if (apr_dbm_fetch(dbmfp, dbmkey, &dbmval) == APR_SUCCESS && dbmval.dptr) { value = apr_pstrmemdup(r->pool, dbmval.dptr, dbmval.dsize); } else { value = NULL; } apr_dbm_close(dbmfp); return value; } static char *lookup_map_dbd(request_rec *r, char *key, const char *label) { apr_status_t rv; apr_dbd_prepared_t *stmt; const char *errmsg; apr_dbd_results_t *res = NULL; apr_dbd_row_t *row = NULL; char *ret = NULL; int n = 0; ap_dbd_t *db = dbd_acquire(r); if (db == NULL) { ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02963) "rewritemap: No db handle available! " "Check your database access"); return NULL; } stmt = apr_hash_get(db->prepared, label, APR_HASH_KEY_STRING); rv = apr_dbd_pvselect(db->driver, r->pool, db->handle, &res, stmt, 0, key, NULL); if (rv != 0) { errmsg = apr_dbd_error(db->driver, db->handle, rv); ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00657) "rewritemap: error %s querying for %s", errmsg, key); return NULL; } while ((rv = apr_dbd_get_row(db->driver, r->pool, res, &row, -1)) == 0) { ++n; if (ret == NULL) { ret = apr_pstrdup(r->pool, apr_dbd_get_entry(db->driver, row, 0)); } else { /* randomise crudely amongst multiple results */ if ((double)rand() < (double)RAND_MAX/(double)n) { ret = apr_pstrdup(r->pool, apr_dbd_get_entry(db->driver, row, 0)); } } } if (rv != -1) { errmsg = apr_dbd_error(db->driver, db->handle, rv); ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00658) "rewritemap: error %s looking up %s", errmsg, key); } switch (n) { case 0: return NULL; case 1: return ret; default: /* what's a fair rewritelog level for this? */ rewritelog(r, 3, NULL, "Multiple values found for %s", key); return ret; } } static char *lookup_map_program(request_rec *r, apr_file_t *fpin, apr_file_t *fpout, char *key) { char *buf; char c; apr_size_t i, nbytes, combined_len = 0; apr_status_t rv; const char *eol = APR_EOL_STR; apr_size_t eolc = 0; int found_nl = 0; result_list *buflist = NULL, *curbuf = NULL; #ifndef NO_WRITEV struct iovec iova[2]; apr_size_t niov; #endif /* 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? */ } } /* write out the request key */ #ifdef NO_WRITEV nbytes = strlen(key); /* XXX: error handling */ apr_file_write_full(fpin, key, nbytes, NULL); nbytes = 1; apr_file_write_full(fpin, "\n", nbytes, NULL); #else iova[0].iov_base = key; iova[0].iov_len = strlen(key); iova[1].iov_base = "\n"; iova[1].iov_len = 1; niov = 2; /* XXX: error handling */ apr_file_writev_full(fpin, iova, niov, &nbytes); #endif buf = apr_palloc(r->pool, REWRITE_PRG_MAP_BUF + 1); /* 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 */ else if (eolc) { eolc = 0; } /* catch binary mode, e.g. on Win32 */ else if (c == '\n') { ++found_nl; break; } buf[i++] = c; apr_file_read(fpout, &c, &nbytes); } /* 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)); } else if (i) { curbuf->next = apr_palloc(r->pool, sizeof(*buflist)); curbuf = curbuf->next; } curbuf->next = NULL; if (i) { curbuf->string = buf; curbuf->len = i; combined_len += i; buf = apr_palloc(r->pool, REWRITE_PRG_MAP_BUF); } if (nbytes == 1 && !found_nl) { continue; } } break; } while (1); /* concat the stuff */ if (buflist) { char *p; 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; } return buf; } /* * generic map lookup */ static char *lookup_map(request_rec *r, char *name, char *key) { rewrite_server_conf *conf; rewritemap_entry *s; char *value; apr_finfo_t st; apr_status_t rv; /* get map configuration */ conf = ap_get_module_config(r->server->module_config, &rewrite_module); s = apr_hash_get(conf->rewritemaps, name, APR_HASH_KEY_STRING); /* map doesn't exist */ if (!s) { return NULL; } switch (s->type) { /* * Text file map (perhaps random) */ case MAPTYPE_RND: case MAPTYPE_TXT: rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00661) "mod_rewrite: can't access text RewriteMap file %s", s->checkfile); return NULL; } value = get_cache_value(s->cachename, st.mtime, key, r->pool); if (!value) { rewritelog(r, 6, NULL, "cache lookup FAILED, forcing new map lookup"); value = lookup_map_txtfile(r, s->datafile, key); if (!value) { rewritelog(r, 5, NULL, "map lookup FAILED: map=%s[txt] key=%s", name, key); set_cache_value(s->cachename, st.mtime, key, ""); return NULL; } rewritelog(r, 5, NULL, "map lookup OK: map=%s[txt] key=%s -> val=%s", name, key, value); set_cache_value(s->cachename, st.mtime, key, value); } else { rewritelog(r, 5, NULL, "cache lookup OK: map=%s[txt] key=%s -> val=%s", name, key, value); } if (s->type == MAPTYPE_RND && *value) { value = select_random_value_part(r, value); rewritelog(r, 5, NULL, "randomly chosen the subvalue `%s'",value); } return *value ? value : NULL; /* * DBM file map */ case MAPTYPE_DBM: rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00662) "mod_rewrite: can't access DBM RewriteMap file %s", s->checkfile); } else if(s->checkfile2 != NULL) { apr_finfo_t st2; rv = apr_stat(&st2, s->checkfile2, APR_FINFO_MIN, r->pool); if (rv != APR_SUCCESS) { ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00663) "mod_rewrite: can't access DBM RewriteMap " "file %s", s->checkfile2); } else if(st2.mtime > st.mtime) { st.mtime = st2.mtime; } } if(rv != APR_SUCCESS) { return NULL; } value = get_cache_value(s->cachename, st.mtime, key, r->pool); if (!value) { rewritelog(r, 6, NULL, "cache lookup FAILED, forcing new map lookup"); value = lookup_map_dbmfile(r, s->datafile, s->dbmtype, key); if (!value) { rewritelog(r, 5, NULL, "map lookup FAILED: map=%s[dbm] key=%s", name, key); set_cache_value(s->cachename, st.mtime, key, ""); return NULL; } rewritelog(r, 5, NULL, "map lookup OK: map=%s[dbm] key=%s -> " "val=%s", name, key, value); set_cache_value(s->cachename, st.mtime, key, value); return value; } rewritelog(r, 5, NULL, "cache lookup OK: map=%s[dbm] key=%s -> val=%s", name, key, value); return *value ? value : NULL; /* * SQL map without cache */ case MAPTYPE_DBD: value = lookup_map_dbd(r, key, s->dbdq); if (!value) { rewritelog(r, 5, NULL, "SQL map lookup FAILED: map %s key=%s", name, key); return NULL; } rewritelog(r, 5, NULL, "SQL map lookup OK: map %s key=%s, val=%s", name, key, value); return value; /* * SQL map with cache */ case MAPTYPE_DBD_CACHE: value = get_cache_value(s->cachename, 0, key, r->pool); if (!value) { rewritelog(r, 6, NULL, "cache lookup FAILED, forcing new map lookup"); value = lookup_map_dbd(r, key, s->dbdq); if (!value) { rewritelog(r, 5, NULL, "SQL map lookup FAILED: map %s key=%s", name, key); set_cache_value(s->cachename, 0, key, ""); return NULL; } rewritelog(r, 5, NULL, "SQL map lookup OK: map %s key=%s, val=%s", name, key, value); set_cache_value(s->cachename, 0, key, value); return value; } rewritelog(r, 5, NULL, "cache lookup OK: map=%s[SQL] key=%s, val=%s", name, key, value); return *value ? value : NULL; /* * Program file map */ case MAPTYPE_PRG: value = lookup_map_program(r, s->fpin, s->fpout, key); if (!value) { rewritelog(r, 5, NULL, "map lookup FAILED: map=%s key=%s", name, key); return NULL; } rewritelog(r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s", name, key, value); return value; /* * Internal Map */ case MAPTYPE_INT: value = s->func(r, key); if (!value) { rewritelog(r, 5, NULL, "map lookup FAILED: map=%s key=%s", name, key); return NULL; } rewritelog(r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s", name, key, value); return value; } return NULL; } /* * lookup a HTTP header and set VARY note */ static const char *lookup_header(const char *name, rewrite_ctx *ctx) { const char *val = apr_table_get(ctx->r->headers_in, name); /* Skip the 'Vary: Host' header combination * as indicated in rfc7231 section-7.1.4 */ if (val && strcasecmp(name, "Host") != 0) { ctx->vary_this = ctx->vary_this ? apr_pstrcat(ctx->r->pool, ctx->vary_this, ", ", name, NULL) : apr_pstrdup(ctx->r->pool, name); } return val; } /* * lookahead helper function * Determine the correct URI path in perdir context */ static APR_INLINE const char *la_u(rewrite_ctx *ctx) { rewrite_perdir_conf *conf; if (*ctx->uri == '/') { return ctx->uri; } conf = ap_get_module_config(ctx->r->per_dir_config, &rewrite_module); return apr_pstrcat(ctx->r->pool, conf->baseurl ? conf->baseurl : conf->directory, ctx->uri, NULL); } /* * generic variable lookup */ static char *lookup_variable(char *var, rewrite_ctx *ctx) { const char *result; request_rec *r = ctx->r; apr_size_t varlen = strlen(var); /* fast exit */ if (varlen < 4) { return ""; } result = 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); } } else if (var[4] && !strncasecmp(var, "SSL", 3)) { result = ap_ssl_var_lookup(r->pool, r->server, r->connection, r, var + 4); } } else if (var[4] == ':') { if (var[5]) { request_rec *rr; const char *path; if (!strncasecmp(var, "HTTP", 4)) { result = lookup_header(var+5, ctx); } else if (!strncasecmp(var, "LA-U", 4)) { if (ctx->uri && subreq_ok(r)) { path = ctx->perdir ? la_u(ctx) : ctx->uri; rr = ap_sub_req_lookup_uri(path, r, NULL); ctx->r = rr; result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx)); ctx->r = r; ap_destroy_sub_req(rr); rewritelog(r, 5, ctx->perdir, "lookahead: path=%s var=%s " "-> val=%s", path, var+5, result); return (char *)result; } } else if (!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; conf = ap_get_module_config(r->per_dir_config, &rewrite_module); path = apr_pstrcat(r->pool, conf->directory, path, NULL); } rr = ap_sub_req_lookup_file(path, r, NULL); } ctx->r = rr; result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx)); ctx->r = r; ap_destroy_sub_req(rr); rewritelog(r, 5, ctx->perdir, "lookahead: path=%s var=%s " "-> val=%s", path, var+5, result); return (char *)result; } } } } /* well, do it the hard way */ else { apr_time_exp_t tm; /* can't do this above, because of the getenv call */ ap_str_toupper(var); switch (varlen) { case 4: if (!strcmp(var, "TIME")) { apr_time_exp_lt(&tm, apr_time_now()); result = apr_psprintf(r->pool, "%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); rewritelog(r, 1, ctx->perdir, "RESULT='%s'", result); return (char *)result; } else if (!strcmp(var, "IPV6")) { int flag = FALSE; #if APR_HAVE_IPV6 apr_sockaddr_t *addr = r->useragent_addr; flag = (addr->family == AF_INET6 && !IN6_IS_ADDR_V4MAPPED((struct in6_addr *)addr->ipaddr_ptr)); rewritelog(r, 1, ctx->perdir, "IPV6='%s'", flag ? "on" : "off"); #else rewritelog(r, 1, ctx->perdir, "IPV6='off' (IPv6 is not enabled)"); #endif result = (flag ? "on" : "off"); } break; case 5: if (!strcmp(var, "HTTPS")) { int flag = ap_ssl_conn_is_ssl(r->connection); return apr_pstrdup(r->pool, flag ? "on" : "off"); } break; case 8: switch (var[6]) { case 'A': if (!strcmp(var, "TIME_DAY")) { apr_time_exp_lt(&tm, apr_time_now()); return apr_psprintf(r->pool, "%02d", tm.tm_mday); } break; case 'E': if (!strcmp(var, "TIME_SEC")) { apr_time_exp_lt(&tm, apr_time_now()); return apr_psprintf(r->pool, "%02d", tm.tm_sec); } break; case 'I': if (!strcmp(var, "TIME_MIN")) { apr_time_exp_lt(&tm, apr_time_now()); return apr_psprintf(r->pool, "%02d", tm.tm_min); } break; case 'O': if (!strcmp(var, "TIME_MON")) { apr_time_exp_lt(&tm, apr_time_now()); return apr_psprintf(r->pool, "%02d", tm.tm_mon+1); } break; } break; case 9: switch (var[7]) { case 'A': if (var[8] == 'Y' && !strcmp(var, "TIME_WDAY")) { apr_time_exp_lt(&tm, apr_time_now()); return apr_psprintf(r->pool, "%d", tm.tm_wday); } else if (!strcmp(var, "TIME_YEAR")) { apr_time_exp_lt(&tm, apr_time_now()); return apr_psprintf(r->pool, "%04d", tm.tm_year+1900); } break; case 'E': if (!strcmp(var, "IS_SUBREQ")) { result = (r->main ? "true" : "false"); } break; case 'F': if (!strcmp(var, "PATH_INFO")) { result = r->path_info; } break; case 'P': if (!strcmp(var, "AUTH_TYPE")) { result = r->ap_auth_type; } break; case 'S': if (!strcmp(var, "HTTP_HOST")) { result = lookup_header("Host", ctx); } break; case 'U': if (!strcmp(var, "TIME_HOUR")) { apr_time_exp_lt(&tm, apr_time_now()); return apr_psprintf(r->pool, "%02d", tm.tm_hour); } break; } break; case 11: switch (var[8]) { case 'A': if (!strcmp(var, "SERVER_NAME")) { result = ap_get_server_name_for_url(r); } break; case 'D': if (*var == 'R' && !strcmp(var, "REMOTE_ADDR")) { result = r->useragent_ip; } else if (!strcmp(var, "SERVER_ADDR")) { result = r->connection->local_ip; } break; case 'E': if (*var == 'H' && !strcmp(var, "HTTP_ACCEPT")) { result = lookup_header("Accept", ctx); } else if (!strcmp(var, "THE_REQUEST")) { result = r->the_request; } break; case 'I': if (!strcmp(var, "API_VERSION")) { return apr_psprintf(r->pool, "%d:%d", MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR); } break; case 'K': if (!strcmp(var, "HTTP_COOKIE")) { result = lookup_header("Cookie", ctx); } break; case 'O': if (*var == 'S' && !strcmp(var, "SERVER_PORT")) { return apr_psprintf(r->pool, "%u", ap_get_server_port(r)); } else if (var[7] == 'H' && !strcmp(var, "REMOTE_HOST")) { result = ap_get_useragent_host(r, REMOTE_NAME, NULL); } else if (!strcmp(var, "REMOTE_PORT")) { return apr_itoa(r->pool, r->useragent_addr->port); } break; case 'S': if (*var == 'R' && !strcmp(var, "REMOTE_USER")) { result = r->user; } else if (!strcmp(var, "SCRIPT_USER")) { result = ""; 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 = ""; 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); } else if (*var == 'C' && !strcmp(var, "CONTEXT_PREFIX")) { result = ap_context_prefix(r); } else if (var[8] == 'M' && !strcmp(var, "REQUEST_METHOD")) { result = r->method; } else if (!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; } else if (!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); } else if (!strcmp(var, "CONTEXT_DOCUMENT_ROOT")) { result = ap_context_document_root(r); } break; } } return apr_pstrdup(r->pool, result ? result : ""); } /* * +-------------------------------------------------------+ * | | * | Expansion functions * | | * +-------------------------------------------------------+ */ /* * Bracketed expression handling * s points after the opening bracket */ static APR_INLINE char *find_closing_curly(char *s) { unsigned depth; for (depth = 1; *s; ++s) { if (*s == RIGHT_CURLY && --depth == 0) { return s; } else if (*s == LEFT_CURLY) { ++depth; } } return NULL; } static APR_INLINE char *find_char_in_curlies(char *s, int c) { unsigned depth; for (depth = 1; *s; ++s) { if (*s == c && depth == 1) { return s; } else if (*s == RIGHT_CURLY && --depth == 0) { return NULL; } else if (*s == LEFT_CURLY) { ++depth; } } return NULL; } /* 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. */ static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry, int *unsafe_qmark, apr_pool_t *pool) { #define EXPAND_SPECIALS "\\$%" result_list *result, *current; result_list sresult[SMALL_EXPANSION]; unsigned spc = 0; apr_size_t span, inputlen, outlen; char *p, *c; 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++]); p = input + span; current->next = NULL; current->string = input; current->len = span; outlen = span; /* loop for specials */ do { int expanded = 0; /* prepare next entry */ if (current->len) { current->next = (spc < SMALL_EXPANSION) ? &(sresult[spc++]) : (result_list *)apr_palloc(pool, sizeof(result_list)); current = current->next; current->next = NULL; current->len = 0; } /* escaped character */ if (*p == '\\') { current->len = 1; ++outlen; if (!p[1]) { current->string = p; break; } else { current->string = ++p; ++p; } } /* variable or map lookup */ else if (p[1] == '{') { char *endp; endp = find_closing_curly(p+2); if (!endp) { current->len = 2; current->string = p; outlen += 2; p += 2; } /* variable lookup */ else if (*p == '%') { p = lookup_variable(apr_pstrmemdup(pool, p+2, endp-p-2), ctx); span = strlen(p); current->len = span; current->string = p; outlen += span; expanded = 1; p = endp + 1; } /* map lookup */ else { /* *p == '$' */ char *key; /* * 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. */ key = find_char_in_curlies(p+2, ':'); if (!key) { current->len = 2; current->string = p; outlen += 2; p += 2; } else { char *map, *dflt; map = apr_pstrmemdup(pool, p+2, endp-p-2); key = map + (key-p-2); *key++ = '\0'; dflt = find_char_in_curlies(key, '|'); if (dflt) { *dflt++ = '\0'; } /* reuse of key variable as result */ key = lookup_map(ctx->r, map, do_expand(key, ctx, entry, NULL, pool)); if (!key && dflt && *dflt) { key = do_expand(dflt, ctx, entry, NULL, pool); } if (key && *key) { span = strlen(key); current->len = span; current->string = key; outlen += span; } expanded = 1; p = endp + 1; } } } /* backreference */ else if (apr_isdigit(p[1])) { int n = p[1] - '0'; backrefinfo *bri = (*p == '$') ? &ctx->briRR : &ctx->briRC; /* see ap_pregsub() in server/util.c */ if (bri->source && n < AP_MAX_REG_MATCH && bri->regmatch[n].rm_eo > bri->regmatch[n].rm_so) { span = bri->regmatch[n].rm_eo - bri->regmatch[n].rm_so; if (entry && (entry->flags & RULEFLAG_ESCAPEBACKREF)) { /* escape the backreference */ char *tmp2, *tmp; tmp = apr_pstrmemdup(pool, bri->source + bri->regmatch[n].rm_so, span); tmp2 = escape_backref(pool, tmp, entry->escapes, entry->noescapes, entry->flags); rewritelog(ctx->r, 5, ctx->perdir, "escaping backreference '%s' to '%s'", tmp, tmp2); current->len = span = strlen(tmp2); current->string = tmp2; } else { current->len = span; current->string = bri->source + bri->regmatch[n].rm_so; } outlen += span; expanded = 1; } p += 2; } /* 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; } current->len = span; current->string = p; p += span; outlen += span; } } while (p < input+inputlen); /* 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 */ static void do_expand_env(data_item *env, rewrite_ctx *ctx) { char *name, *val; while (env) { name = do_expand(env->data, ctx, NULL, NULL, ctx->r->pool); 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 = ""; } apr_table_set(ctx->r->subprocess_env, name, val); rewritelog(ctx->r, 5, NULL, "setting env variable '%s' to '%s'", name, val); } env = env->next; } return; } /* * perform all the expansions on the cookies * * TODO: use cached time similar to how logging does it */ static void 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 */ const char *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; expires = apr_strtok(NULL, sep, &tok_cntx); path = expires ? apr_strtok(NULL, sep, &tok_cntx) : NULL; secure = path ? apr_strtok(NULL, sep, &tok_cntx) : NULL; httponly = secure ? apr_strtok(NULL, sep, &tok_cntx) : NULL; samesite = httponly ? apr_strtok(NULL, sep, &tok_cntx) : NULL; if (expires) { apr_time_exp_t tms; long exp_min; exp_min = atol(expires); if (exp_min) { apr_time_exp_gmt(&tms, r->request_time + apr_time_from_sec((60 * exp_min))); exp_time = apr_psprintf(r->pool, "%s, %.2d-%s-%.4d " "%.2d:%.2d:%.2d GMT", apr_day_snames[tms.tm_wday], tms.tm_mday, apr_month_snames[tms.tm_mon], tms.tm_year+1900, tms.tm_hour, tms.tm_min, tms.tm_sec); } } cookie = apr_pstrcat(rmain->pool, var, "=", val, "; path=", path ? path : "/", "; domain=", domain, expires ? (exp_time ? "; expires=" : "") : NULL, expires ? (exp_time ? exp_time : "") : NULL, (secure && (!ap_cstr_casecmp(secure, "true") || !strcmp(secure, "1") || !ap_cstr_casecmp(secure, "secure"))) ? "; secure" : NULL, (httponly && (!ap_cstr_casecmp(httponly, "true") || !strcmp(httponly, "1") || !ap_cstr_casecmp(httponly, "HttpOnly"))) ? "; HttpOnly" : NULL, NULL); if (samesite && strcmp(samesite, "0") && ap_cstr_casecmp(samesite,"false")) { cookie = apr_pstrcat(rmain->pool, cookie, "; SameSite=", samesite, NULL); } apr_table_addn(rmain->err_headers_out, "Set-Cookie", cookie); apr_pool_userdata_set("set", notename, NULL, rmain->pool); rewritelog(rmain, 5, NULL, "setting cookie '%s'", cookie); } else { rewritelog(rmain, 5, NULL, "skipping already set cookie '%s'", var); } } return; } static void do_expand_cookie(data_item *cookie, rewrite_ctx *ctx) { while (cookie) { add_cookie(ctx->r, do_expand(cookie->data, ctx, NULL, NULL, ctx->r->pool)); cookie = cookie->next; } return; } #if APR_HAS_USER /* * Expand tilde-paths (/~user) through Unix /etc/passwd * database information (or other OS-specific database) */ static char *expand_tildepaths(request_rec *r, char *uri) { if (uri && *uri == '/' && uri[1] == '~') { char *p, *user; p = user = uri + 2; while (*p && *p != '/') { ++p; } if (p > user) { char *homedir; user = apr_pstrmemdup(r->pool, user, p-user); if (apr_uid_homepath_get(&homedir, user, r->pool) == APR_SUCCESS) { if (*p) { /* reuse of user variable */ user = homedir + strlen(homedir) - 1; if (user >= homedir && *user == '/') { *user = '\0'; } return apr_pstrcat(r->pool, homedir, p, NULL); } else { return homedir; } } } } return uri; } #endif /* if APR_HAS_USER */ /* * +-------------------------------------------------------+ * | | * | rewriting lockfile support * | | * +-------------------------------------------------------+ */ static apr_status_t rewritelock_create(server_rec *s, apr_pool_t *p) { apr_status_t rc; /* create the lockfile */ rc = ap_global_mutex_create(&rewrite_mapr_lock_acquire, NULL, rewritemap_mutex_type, NULL, s, p, 0); if (rc != APR_SUCCESS) { return rc; } return APR_SUCCESS; } static apr_status_t rewritelock_remove(void *data) { /* destroy the rewritelock */ if (rewrite_mapr_lock_acquire) { apr_global_mutex_destroy(rewrite_mapr_lock_acquire); rewrite_mapr_lock_acquire = NULL; } return APR_SUCCESS; } /* * +-------------------------------------------------------+ * | | * | configuration directive handling * | | * +-------------------------------------------------------+ */ /* * 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* */ static char *parseargline(apr_pool_t *p, char *str, char **a1, char **a2, char **a2_end, char **a3) { char quote; while (apr_isspace(*str)) { ++str; } /* * determine first argument */ quote = (*str == '"' || *str == '\'') ? *str++ : '\0'; *a1 = str; for (; *str; ++str) { if ((apr_isspace(*str) && !quote) || (*str == quote)) { break; } if (*str == '\\' && apr_isspace(str[1])) { ++str; continue; } } if (!*str) { return "bad argument line: at least two arguments required"; } *str++ = '\0'; while (apr_isspace(*str)) { ++str; } /* * determine second argument */ quote = (*str == '"' || *str == '\'') ? *str++ : '\0'; *a2 = str; for (; *str; ++str) { if ((apr_isspace(*str) && !quote) || (*str == quote)) { break; } if (*str == '\\' && apr_isspace(str[1])) { ++str; continue; } } if (!*str) { *a3 = NULL; /* 3rd argument is optional */ *a2_end = str; return NULL; } *a2_end = str; *str++ = '\0'; while (apr_isspace(*str)) { ++str; } if (!*str) { *a3 = NULL; /* 3rd argument is still optional */ return NULL; } /* * determine third argument */ quote = (*str == '"' || *str == '\'') ? *str++ : '\0'; *a3 = str; for (; *str; ++str) { if ((apr_isspace(*str) && !quote) || (*str == quote)) { break; } if (*str == '\\' && apr_isspace(str[1])) { ++str; continue; } } *str = '\0'; if (**a3 != '[') { return apr_psprintf(p, "bad flag delimiters: third argument must begin " "with '[' but found '%c' - too many arguments or rogue " "whitespace?", **a3); } else if ((*a3)[strlen(*a3)-1] != ']') { return apr_psprintf(p, "bad flag delimiters: third argument must end " "with ']' but found '%c' - unintended whitespace within the " "flags definition?", (*a3)[strlen(*a3)-1]); } return NULL; } static void *config_server_create(apr_pool_t *p, server_rec *s) { rewrite_server_conf *a; a = (rewrite_server_conf *)apr_pcalloc(p, sizeof(rewrite_server_conf)); a->state = ENGINE_DISABLED; a->options = OPTION_NONE; a->rewritemaps = apr_hash_make(p); a->rewriteconds = apr_array_make(p, 2, sizeof(rewritecond_entry)); a->rewriterules = apr_array_make(p, 2, sizeof(rewriterule_entry)); a->server = s; return (void *)a; } static void *config_server_merge(apr_pool_t *p, void *basev, void *overridesv) { rewrite_server_conf *a, *base, *overrides; a = (rewrite_server_conf *)apr_pcalloc(p, sizeof(rewrite_server_conf)); base = (rewrite_server_conf *)basev; overrides = (rewrite_server_conf *)overridesv; a->state = (overrides->state_set == 0) ? base->state : overrides->state; a->state_set = overrides->state_set || base->state_set; a->options = (overrides->options_set == 0) ? base->options : overrides->options; a->options_set = overrides->options_set || base->options_set; a->server = overrides->server; if (a->options & OPTION_INHERIT || (base->options & OPTION_INHERIT_DOWN && !(a->options & OPTION_IGNORE_INHERIT))) { /* * local directives override * and anything else is inherited */ a->rewritemaps = apr_hash_overlay(p, overrides->rewritemaps, base->rewritemaps); a->rewriteconds = apr_array_append(p, overrides->rewriteconds, base->rewriteconds); a->rewriterules = apr_array_append(p, overrides->rewriterules, base->rewriterules); } else if (a->options & OPTION_INHERIT_BEFORE || (base->options & OPTION_INHERIT_DOWN_BEFORE && !(a->options & OPTION_IGNORE_INHERIT))) { /* * local directives override * and anything else is inherited (preserving order) */ a->rewritemaps = apr_hash_overlay(p, base->rewritemaps, overrides->rewritemaps); a->rewriteconds = apr_array_append(p, base->rewriteconds, overrides->rewriteconds); a->rewriterules = apr_array_append(p, base->rewriterules, overrides->rewriterules); } else { /* * local directives override * and anything else gets defaults */ a->rewritemaps = overrides->rewritemaps; a->rewriteconds = overrides->rewriteconds; a->rewriterules = overrides->rewriterules; } return (void *)a; } static void *config_perdir_create(apr_pool_t *p, char *path) { rewrite_perdir_conf *a; a = (rewrite_perdir_conf *)apr_pcalloc(p, sizeof(rewrite_perdir_conf)); a->state = ENGINE_DISABLED; a->options = OPTION_NONE; a->baseurl = NULL; a->rewriteconds = apr_array_make(p, 2, sizeof(rewritecond_entry)); a->rewriterules = apr_array_make(p, 2, sizeof(rewriterule_entry)); if (path == NULL) { a->directory = NULL; } else { /* make sure it has a trailing slash */ if (path[strlen(path)-1] == '/') { a->directory = apr_pstrdup(p, path); } else { a->directory = apr_pstrcat(p, path, "/", NULL); } } return (void *)a; } static void *config_perdir_merge(apr_pool_t *p, void *basev, void *overridesv) { rewrite_perdir_conf *a, *base, *overrides; a = (rewrite_perdir_conf *)apr_pcalloc(p, sizeof(rewrite_perdir_conf)); base = (rewrite_perdir_conf *)basev; overrides = (rewrite_perdir_conf *)overridesv; a->state = (overrides->state_set == 0) ? base->state : overrides->state; a->state_set = overrides->state_set || base->state_set; a->options = (overrides->options_set == 0) ? base->options : overrides->options; a->options_set = overrides->options_set || base->options_set; if (a->options & OPTION_MERGEBASE) { a->baseurl = (overrides->baseurl_set == 0) ? base->baseurl : overrides->baseurl; a->baseurl_set = overrides->baseurl_set || base->baseurl_set; } else { a->baseurl = overrides->baseurl; } a->directory = overrides->directory; if (a->options & OPTION_INHERIT || (base->options & OPTION_INHERIT_DOWN && !(a->options & OPTION_IGNORE_INHERIT))) { a->rewriteconds = apr_array_append(p, overrides->rewriteconds, base->rewriteconds); a->rewriterules = apr_array_append(p, overrides->rewriterules, base->rewriterules); } else if (a->options & OPTION_INHERIT_BEFORE || (base->options & OPTION_INHERIT_DOWN_BEFORE && !(a->options & OPTION_IGNORE_INHERIT))) { a->rewriteconds = apr_array_append(p, base->rewriteconds, overrides->rewriteconds); a->rewriterules = apr_array_append(p, base->rewriterules, overrides->rewriterules); } else { a->rewriteconds = overrides->rewriteconds; a->rewriterules = overrides->rewriterules; } return (void *)a; } static const char *cmd_rewriteengine(cmd_parms *cmd, void *in_dconf, int flag) { rewrite_perdir_conf *dconf = in_dconf; rewrite_server_conf *sconf; sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); /* server command? set both global scope and base directory scope */ if (cmd->path == NULL) { sconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED); sconf->state_set = 1; dconf->state = sconf->state; dconf->state_set = 1; } /* directory command? set directory scope only */ else { dconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED); dconf->state_set = 1; } return NULL; } static const char *cmd_rewriteoptions(cmd_parms *cmd, void *in_dconf, const char *option) { int options = 0; while (*option) { char *w = ap_getword_conf(cmd->temp_pool, &option); if (!strcasecmp(w, "inherit")) { options |= OPTION_INHERIT; } else if (!strcasecmp(w, "inheritbefore")) { options |= OPTION_INHERIT_BEFORE; } else if (!strcasecmp(w, "inheritdown")) { options |= OPTION_INHERIT_DOWN; } else if(!strcasecmp(w, "inheritdownbefore")) { options |= OPTION_INHERIT_DOWN_BEFORE; } else if (!strcasecmp(w, "ignoreinherit")) { options |= OPTION_IGNORE_INHERIT; } else if (!strcasecmp(w, "allownoslash")) { options |= OPTION_NOSLASH; } else if (!strncasecmp(w, "MaxRedirects=", 13)) { ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00664) "RewriteOptions: MaxRedirects option has been " "removed in favor of the global " "LimitInternalRecursion directive and will be " "ignored."); } else if (!strcasecmp(w, "allowanyuri")) { options |= OPTION_ANYURI; } else if (!strcasecmp(w, "mergebase")) { options |= OPTION_MERGEBASE; } else if (!strcasecmp(w, "ignorecontextinfo")) { options |= OPTION_IGNORE_CONTEXT_INFO; } else if (!strcasecmp(w, "legacyprefixdocroot")) { options |= OPTION_LEGACY_PREFIX_DOCROOT; } else if (!strcasecmp(w, "LongURLOptimization")) { options |= OPTION_LONGOPT; } else if (!strcasecmp(w, "UnsafePrefixStat")) { options |= OPTION_UNSAFE_PREFIX_STAT; } else { return apr_pstrcat(cmd->pool, "RewriteOptions: unknown option '", w, "'", NULL); } } /* 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); sconf->options |= options; sconf->options_set = 1; dconf->options |= options; dconf->options_set = 1; } /* directory command? set directory scope only */ else { /* is per-directory command */ rewrite_perdir_conf *dconf = in_dconf; dconf->options |= options; dconf->options_set = 1; } return NULL; } static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, const char *a1, const char *a2, const char *a3) { rewrite_server_conf *sconf; rewritemap_entry *newmap; apr_finfo_t st; const char *fname; sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); newmap = apr_pcalloc(cmd->pool, sizeof(rewritemap_entry)); if (strncasecmp(a2, "txt:", 4) == 0) { if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) { return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ", a2+4, NULL); } newmap->type = MAPTYPE_TXT; newmap->datafile = fname; newmap->checkfile = fname; newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s", (void *)cmd->server, a1); } else if (strncasecmp(a2, "rnd:", 4) == 0) { if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) { return apr_pstrcat(cmd->pool, "RewriteMap: bad path to rnd map: ", a2+4, NULL); } newmap->type = MAPTYPE_RND; newmap->datafile = fname; newmap->checkfile = fname; newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s", (void *)cmd->server, a1); } else if (strncasecmp(a2, "dbm", 3) == 0) { apr_status_t rv; newmap->type = MAPTYPE_DBM; fname = NULL; newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s", (void *)cmd->server, a1); if (a2[3] == ':') { newmap->dbmtype = "default"; fname = a2+4; } else if (a2[3] == '=') { const char *colon = ap_strchr_c(a2 + 4, ':'); if (colon) { newmap->dbmtype = apr_pstrndup(cmd->pool, a2 + 4, colon - (a2 + 3) - 1); fname = colon + 1; } } if (!fname) { return apr_pstrcat(cmd->pool, "RewriteMap: bad map:", a2, NULL); } if ((newmap->datafile = ap_server_root_relative(cmd->pool, fname)) == NULL) { return apr_pstrcat(cmd->pool, "RewriteMap: bad path to dbm map: ", fname, NULL); } rv = apr_dbm_get_usednames_ex(cmd->pool, newmap->dbmtype, newmap->datafile, &newmap->checkfile, &newmap->checkfile2); if (rv != APR_SUCCESS) { return apr_pstrcat(cmd->pool, "RewriteMap: dbm type ", newmap->dbmtype, " is invalid", NULL); } } else if ((strncasecmp(a2, "dbd:", 4) == 0) || (strncasecmp(a2, "fastdbd:", 8) == 0)) { if (dbd_prepare == NULL) { return "RewriteMap types dbd and fastdbd require mod_dbd!"; } if ((a2[0] == 'd') || (a2[0] == 'D')) { newmap->type = MAPTYPE_DBD; fname = a2+4; } else { newmap->type = MAPTYPE_DBD_CACHE; fname = a2+8; newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s", (void *)cmd->server, a1); } newmap->dbdq = a1; dbd_prepare(cmd->server, fname, newmap->dbdq); } else if (strncasecmp(a2, "prg:", 4) == 0) { apr_tokenize_to_argv(a2 + 4, &newmap->argv, cmd->pool); fname = newmap->argv[0]; if ((newmap->argv[0] = ap_server_root_relative(cmd->pool, fname)) == NULL) { return apr_pstrcat(cmd->pool, "RewriteMap: bad path to prg map: ", fname, NULL); } newmap->type = MAPTYPE_PRG; newmap->checkfile = newmap->argv[0]; rewrite_lock_needed = 1; if (a3) { char *tok_cntx; newmap->user = apr_strtok(apr_pstrdup(cmd->pool, a3), ":", &tok_cntx); newmap->group = apr_strtok(NULL, ":", &tok_cntx); } } else if (strncasecmp(a2, "int:", 4) == 0) { newmap->type = MAPTYPE_INT; newmap->func = (char *(*)(request_rec *,char *)) apr_hash_get(mapfunc_hash, a2+4, strlen(a2+4)); if (newmap->func == NULL) { return apr_pstrcat(cmd->pool, "RewriteMap: internal map not found:", a2+4, NULL); } } else { if ((fname = ap_server_root_relative(cmd->pool, a2)) == NULL) { return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ", a2, NULL); } newmap->type = MAPTYPE_TXT; newmap->datafile = fname; newmap->checkfile = fname; newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s", (void *)cmd->server, a1); } if (newmap->checkfile && (apr_stat(&st, newmap->checkfile, APR_FINFO_MIN, cmd->pool) != APR_SUCCESS)) { return apr_pstrcat(cmd->pool, "RewriteMap: file for map ", a1, " not found:", newmap->checkfile, NULL); } apr_hash_set(sconf->rewritemaps, a1, APR_HASH_KEY_STRING, newmap); return NULL; } static const char *cmd_rewritebase(cmd_parms *cmd, void *in_dconf, const char *a1) { rewrite_perdir_conf *dconf = in_dconf; 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 */ static const char *cmd_parseflagfield(apr_pool_t *p, void *cfg, char *key, const char *(*parse)(apr_pool_t *, void *, char *, char *)) { char *val, *nextp, *endp; const char *err; endp = key + strlen(key) - 1; /* This should have been checked before, but just in case... */ 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; } /* strip trailing spaces */ endp = nextp - 1; while (apr_isspace(*endp)) { --endp; } *++endp = '\0'; /* split key and val */ val = ap_strchr(key, '='); if (val) { *val++ = '\0'; } else { val = endp; } err = parse(p, cfg, key, val); if (err) { return err; } key = nextp + 1; } return NULL; } static const char *cmd_rewritecond_setflag(apr_pool_t *p, void *_cfg, char *key, char *val) { rewritecond_entry *cfg = _cfg; if ( strcasecmp(key, "nocase") == 0 || strcasecmp(key, "NC") == 0 ) { cfg->flags |= CONDFLAG_NOCASE; } else if ( strcasecmp(key, "ornext") == 0 || strcasecmp(key, "OR") == 0 ) { cfg->flags |= CONDFLAG_ORNEXT; } else if ( strcasecmp(key, "novary") == 0 || strcasecmp(key, "NV") == 0 ) { cfg->flags |= CONDFLAG_NOVARY; } else { return apr_pstrcat(p, "unknown flag '", key, "'", NULL); } return NULL; } static const char *cmd_rewritecond(cmd_parms *cmd, void *in_dconf, const char *in_str) { rewrite_perdir_conf *dconf = in_dconf; char *str = apr_pstrdup(cmd->pool, in_str); rewrite_server_conf *sconf; rewritecond_entry *newcond; ap_regex_t *regexp; char *a1 = NULL, *a2 = NULL, *a2_end, *a3 = NULL; const char *err; sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); /* 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 ((err = parseargline(cmd->pool, str, &a1, &a2, &a2_end, &a3))) { return apr_psprintf(cmd->pool, "RewriteCond: %s " "(TestString=%s, CondPattern=%s, flags=%s)", err, a1, a2, a3); } /* 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; } /* determine the pattern type */ newcond->ptype = CONDPAT_REGEX; if (strcasecmp(a1, "expr") == 0) { newcond->ptype = CONDPAT_AP_EXPR; } else if (*a2 && a2[1]) { if (*a2 == '-') { if (!a2[2]) { switch (a2[1]) { case 'f': newcond->ptype = CONDPAT_FILE_EXISTS; break; case 's': newcond->ptype = CONDPAT_FILE_SIZE; break; case 'd': newcond->ptype = CONDPAT_FILE_DIR; break; case 'x': newcond->ptype = CONDPAT_FILE_XBIT; break; case 'h': newcond->ptype = CONDPAT_FILE_LINK; break; case 'L': newcond->ptype = CONDPAT_FILE_LINK; break; case 'l': newcond->ptype = CONDPAT_FILE_LINK; break; case 'U': newcond->ptype = CONDPAT_LU_URL; break; case 'F': newcond->ptype = CONDPAT_LU_FILE; break; } } else if (a2[3]) { switch (a2[1]) { case 'l': if (a2[2] == 't') { a2 += 3; newcond->ptype = CONDPAT_INT_LT; } else if (a2[2] == 'e') { a2 += 3; newcond->ptype = CONDPAT_INT_LE; } break; case 'g': if (a2[2] == 't') { a2 += 3; newcond->ptype = CONDPAT_INT_GT; } else if (a2[2] == 'e') { a2 += 3; newcond->ptype = CONDPAT_INT_GE; } break; case 'e': if (a2[2] == 'q') { a2 += 3; newcond->ptype = CONDPAT_INT_EQ; } break; case 'n': if (a2[2] == 'e') { /* Inversion, ensure !-ne == -eq */ a2 += 3; newcond->ptype = CONDPAT_INT_EQ; newcond->flags ^= CONDFLAG_NOTMATCH; } break; } } } else { switch (*a2) { case '>': if (*++a2 == '=') ++a2, newcond->ptype = CONDPAT_STR_GE; else newcond->ptype = CONDPAT_STR_GT; break; case '<': if (*++a2 == '=') ++a2, newcond->ptype = CONDPAT_STR_LE; else newcond->ptype = CONDPAT_STR_LT; break; case '=': newcond->ptype = CONDPAT_STR_EQ; /* "" represents an empty string */ if (*++a2 == '"' && a2[1] == '"' && !a2[2]) a2 += 2; break; } } } if ((newcond->ptype != CONDPAT_REGEX) && (newcond->ptype < CONDPAT_STR_LT || newcond->ptype > CONDPAT_STR_GE) && (newcond->flags & CONDFLAG_NOCASE)) { ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00665) "RewriteCond: NoCase option for non-regex pattern '%s' " "is not supported and will be ignored. (%s:%d)", a2, cmd->directive->filename, cmd->directive->line_num); newcond->flags &= ~CONDFLAG_NOCASE; } newcond->pskip = a2 - newcond->pattern; newcond->pattern += newcond->pskip; if (newcond->ptype == CONDPAT_REGEX) { regexp = ap_pregcomp(cmd->pool, a2, AP_REG_EXTENDED | ((newcond->flags & CONDFLAG_NOCASE) ? AP_REG_ICASE : 0)); if (!regexp) { return apr_pstrcat(cmd->pool, "RewriteCond: cannot compile regular " "expression '", a2, "'", NULL); } newcond->regexp = regexp; } else if (newcond->ptype == CONDPAT_AP_EXPR) { unsigned int flags = newcond->flags & CONDFLAG_NOVARY ? AP_EXPR_FLAG_DONT_VARY : 0; newcond->expr = ap_expr_parse_cmd(cmd, a2, flags, &err, NULL); if (err) return apr_psprintf(cmd->pool, "RewriteCond: cannot compile " "expression \"%s\": %s", a2, err); } return NULL; } static const char *cmd_rewriterule_setflag(apr_pool_t *p, void *_cfg, char *key, char *val) { rewriterule_entry *cfg = _cfg; int error = 0; switch (*key++) { case 'b': case 'B': if (!*key || !strcasecmp(key, "ackrefescaping")) { cfg->flags |= RULEFLAG_ESCAPEBACKREF; if (val && *val) { cfg->escapes = val; } } else if (!strcasecmp(key, "NE")) { if (val && *val) { cfg->noescapes = val; } else { return "flag 'BNE' wants a list of characters (i.e. [BNE=...])"; } } else if (!strcasecmp(key, "NP") || !strcasecmp(key, "ackrefernoplus")) { cfg->flags |= RULEFLAG_ESCAPENOPLUS; } else if (!strcasecmp(key, "CTLS")) { cfg->flags |= RULEFLAG_ESCAPECTLS|RULEFLAG_ESCAPEBACKREF; } else { ++error; } break; case 'c': case 'C': if (!*key || !strcasecmp(key, "hain")) { /* chain */ cfg->flags |= RULEFLAG_CHAIN; } else if (((*key == 'O' || *key == 'o') && !key[1]) || !strcasecmp(key, "ookie")) { /* cookie */ data_item *cp = cfg->cookie; if (!cp) { cp = cfg->cookie = apr_palloc(p, sizeof(*cp)); } else { while (cp->next) { cp = cp->next; } cp->next = apr_palloc(p, sizeof(*cp)); cp = cp->next; } cp->next = NULL; cp->data = val; } else { ++error; } break; case 'd': case 'D': if (!*key || !strcasecmp(key, "PI") || !strcasecmp(key,"iscardpath")) { cfg->flags |= (RULEFLAG_DISCARDPATHINFO); } break; case 'e': case 'E': if (!*key || !strcasecmp(key, "nv")) { /* env */ data_item *cp = cfg->env; if (!cp) { cp = cfg->env = apr_palloc(p, sizeof(*cp)); } else { while (cp->next) { cp = cp->next; } cp->next = apr_palloc(p, sizeof(*cp)); cp = cp->next; } cp->next = NULL; cp->data = val; } else if (!strcasecmp(key, "nd")) { /* end */ cfg->flags |= RULEFLAG_END; } else { ++error; } break; case 'f': case 'F': if (!*key || !strcasecmp(key, "orbidden")) { /* forbidden */ cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB); cfg->forced_responsecode = HTTP_FORBIDDEN; } else { ++error; } break; case 'g': case 'G': if (!*key || !strcasecmp(key, "one")) { /* gone */ cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB); cfg->forced_responsecode = HTTP_GONE; } else { ++error; } break; case 'h': case 'H': if (!*key || !strcasecmp(key, "andler")) { /* handler */ cfg->forced_handler = val; } else { ++error; } break; case 'l': case 'L': if (!*key || !strcasecmp(key, "ast")) { /* last */ cfg->flags |= RULEFLAG_LASTRULE; } else { ++error; } break; case 'n': case 'N': if (((*key == 'E' || *key == 'e') && !key[1]) || !strcasecmp(key, "oescape")) { /* noescape */ cfg->flags |= RULEFLAG_NOESCAPE; } else if (!*key || !strcasecmp(key, "ext")) { /* next */ cfg->flags |= RULEFLAG_NEWROUND; if (val && *val) { cfg->maxrounds = atoi(val); } } else if (((*key == 'S' || *key == 's') && !key[1]) || !strcasecmp(key, "osubreq")) { /* nosubreq */ cfg->flags |= RULEFLAG_IGNOREONSUBREQ; } else if (((*key == 'C' || *key == 'c') && !key[1]) || !strcasecmp(key, "ocase")) { /* nocase */ cfg->flags |= RULEFLAG_NOCASE; } else { ++error; } break; case 'p': case 'P': if (!*key || !strcasecmp(key, "roxy")) { /* proxy */ cfg->flags |= RULEFLAG_PROXY; } else if (((*key == 'T' || *key == 't') && !key[1]) || !strcasecmp(key, "assthrough")) { /* passthrough */ cfg->flags |= RULEFLAG_PASSTHROUGH; } else { ++error; } break; case 'q': case 'Q': if ( !strcasecmp(key, "SA") || !strcasecmp(key, "sappend")) { /* qsappend */ cfg->flags |= RULEFLAG_QSAPPEND; } else if ( !strcasecmp(key, "SD") || !strcasecmp(key, "sdiscard") ) { /* qsdiscard */ cfg->flags |= RULEFLAG_QSDISCARD; } else if ( !strcasecmp(key, "SL") || !strcasecmp(key, "slast") ) { /* qslast */ cfg->flags |= RULEFLAG_QSLAST; } else { ++error; } break; case 'r': case 'R': if (!*key || !strcasecmp(key, "edirect")) { /* redirect */ int status = 0; cfg->flags |= RULEFLAG_FORCEREDIRECT; if (*val) { if (strcasecmp(val, "permanent") == 0) { status = HTTP_MOVED_PERMANENTLY; } else if (strcasecmp(val, "temp") == 0) { status = HTTP_MOVED_TEMPORARILY; } else if (strcasecmp(val, "seeother") == 0) { status = HTTP_SEE_OTHER; } else if (apr_isdigit(*val)) { status = atoi(val); if (status != HTTP_INTERNAL_SERVER_ERROR) { int idx = ap_index_of_response(HTTP_INTERNAL_SERVER_ERROR); if (ap_index_of_response(status) == idx) { return apr_psprintf(p, "invalid HTTP " "response code '%s' for " "flag 'R'", val); } } if (!ap_is_HTTP_REDIRECT(status)) { cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB); } } cfg->forced_responsecode = status; } } else { ++error; } break; case 's': case 'S': if (!*key || !strcasecmp(key, "kip")) { /* skip */ cfg->skip = atoi(val); } else { ++error; } break; case 't': case 'T': if (!*key || !strcasecmp(key, "ype")) { /* type */ cfg->forced_mimetype = val; } else { ++error; } break; case 'u': case 'U': if (!strcasecmp(key, "nsafePrefixStat")){ cfg->flags |= (RULEFLAG_UNSAFE_PREFIX_STAT); } else if(!strcasecmp(key, "nsafeAllow3F")) { cfg->flags |= RULEFLAG_UNSAFE_ALLOW3F; } else if(!strcasecmp(key, "NC")) { cfg->flags |= RULEFLAG_UNC; } else { ++error; } break; default: ++error; break; } if (error) { return apr_pstrcat(p, "unknown flag '", --key, "'", NULL); } return NULL; } static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf, const char *in_str) { rewrite_perdir_conf *dconf = in_dconf; char *str = apr_pstrdup(cmd->pool, in_str); rewrite_server_conf *sconf; rewriterule_entry *newrule; ap_regex_t *regexp; char *a1 = NULL, *a2 = NULL, *a2_end, *a3 = NULL; const char *err; sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); /* 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 ((err = parseargline(cmd->pool, str, &a1, &a2, &a2_end, &a3))) { return apr_psprintf(cmd->pool, "RewriteRule: %s " "(pattern='%s', substitution='%s', flags='%s')", err, a1, a2, a3); } newrule->forced_mimetype = NULL; newrule->forced_handler = NULL; newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY; newrule->flags = RULEFLAG_NONE; newrule->env = NULL; newrule->cookie = NULL; newrule->skip = 0; newrule->maxrounds = REWRITE_MAX_ROUNDS; newrule->escapes = newrule->noescapes = NULL; /* arg3: optional flags field */ if (a3 != NULL) { if ((err = cmd_parseflagfield(cmd->pool, newrule, a3, cmd_rewriterule_setflag)) != NULL) { return apr_pstrcat(cmd->pool, "RewriteRule: ", err, NULL); } } /* arg1: the pattern * try to compile the regexp to test if is ok */ if (*a1 == '!') { newrule->flags |= RULEFLAG_NOTMATCH; ++a1; } regexp = ap_pregcomp(cmd->pool, a1, AP_REG_EXTENDED | ((newrule->flags & RULEFLAG_NOCASE) ? AP_REG_ICASE : 0)); if (!regexp) { return apr_pstrcat(cmd->pool, "RewriteRule: cannot compile regular expression '", a1, "'", NULL); } newrule->pattern = a1; newrule->regexp = regexp; /* 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'; } } else if (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)); } return NULL; } /* * +-------------------------------------------------------+ * | | * | the rewriting engine * | | * +-------------------------------------------------------+ */ /* Lexicographic Compare */ static APR_INLINE int compare_lexicography(char *a, char *b) { apr_size_t i, lena, lenb; lena = strlen(a); lenb = strlen(b); if (lena == lenb) { for (i = 0; i < lena; ++i) { if (a[i] != b[i]) { return ((unsigned char)a[i] > (unsigned char)b[i]) ? 1 : -1; } } return 0; } return ((lena > lenb) ? 1 : -1); } /* * Apply a single rewriteCond */ static int apply_rewrite_cond(rewritecond_entry *p, rewrite_ctx *ctx, apr_pool_t *pool) { char *input = NULL; apr_finfo_t sb; request_rec *rsub, *r = ctx->r; ap_regmatch_t regmatch[AP_MAX_REG_MATCH]; int rc = 0; int basis; if (p->ptype != CONDPAT_AP_EXPR) input = do_expand(p->input, ctx, NULL, NULL, pool); switch (p->ptype) { case CONDPAT_FILE_EXISTS: if ( apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS && sb.filetype == APR_REG) { rc = 1; } break; case CONDPAT_FILE_SIZE: if ( apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS && sb.filetype == APR_REG && sb.size > 0) { rc = 1; } break; case CONDPAT_FILE_LINK: #if !defined(OS2) if ( apr_stat(&sb, input, APR_FINFO_MIN | APR_FINFO_LINK, r->pool) == APR_SUCCESS && sb.filetype == APR_LNK) { rc = 1; } #endif break; case CONDPAT_FILE_DIR: if ( apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS && sb.filetype == APR_DIR) { rc = 1; } break; case CONDPAT_FILE_XBIT: if ( apr_stat(&sb, input, APR_FINFO_PROT, r->pool) == APR_SUCCESS && (sb.protection & (APR_UEXECUTE | APR_GEXECUTE | APR_WEXECUTE))) { rc = 1; } break; case CONDPAT_LU_URL: if (*input && subreq_ok(r)) { rsub = ap_sub_req_lookup_uri(input, r, NULL); if (rsub->status < 400) { rc = 1; } rewritelog(r, 5, NULL, "RewriteCond URI (-U check: " "path=%s -> status=%d", input, rsub->status); ap_destroy_sub_req(rsub); } break; case CONDPAT_LU_FILE: if (*input && subreq_ok(r)) { rsub = ap_sub_req_lookup_file(input, r, NULL); if (rsub->status < 300 && /* double-check that file exists since default result is 200 */ apr_stat(&sb, rsub->filename, APR_FINFO_MIN, r->pool) == APR_SUCCESS) { rc = 1; } rewritelog(r, 5, NULL, "RewriteCond file (-F check: path=%s " "-> file=%s status=%d", input, rsub->filename, rsub->status); ap_destroy_sub_req(rsub); } break; case CONDPAT_STR_GE: basis = 0; goto test_str_g; case CONDPAT_STR_GT: basis = 1; test_str_g: if (p->flags & CONDFLAG_NOCASE) { rc = (strcasecmp(input, p->pattern) >= basis) ? 1 : 0; } else { rc = (compare_lexicography(input, p->pattern) >= basis) ? 1 : 0; } break; case CONDPAT_STR_LE: basis = 0; goto test_str_l; case CONDPAT_STR_LT: basis = -1; test_str_l: if (p->flags & CONDFLAG_NOCASE) { rc = (strcasecmp(input, p->pattern) <= basis) ? 1 : 0; } else { rc = (compare_lexicography(input, p->pattern) <= basis) ? 1 : 0; } break; 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; case CONDPAT_AP_EXPR: { const char *err, *source; rc = ap_expr_exec_re(r, p->expr, AP_MAX_REG_MATCH, regmatch, &source, &err); if (rc < 0 || err) { rewritelog(r, 1, ctx->perdir, "RewriteCond: expr='%s' evaluation failed: %s", p->pattern - p->pskip, err); rc = 0; } /* update briRC backref info */ if (rc && !(p->flags & CONDFLAG_NOTMATCH)) { ctx->briRC.source = source; memcpy(ctx->briRC.regmatch, regmatch, sizeof(regmatch)); } } break; default: /* it is really a regexp pattern, so apply it */ rc = !ap_regexec(p->regexp, input, AP_MAX_REG_MATCH, regmatch, 0); /* update briRC backref info */ if (rc && !(p->flags & CONDFLAG_NOTMATCH)) { ctx->briRC.source = input; memcpy(ctx->briRC.regmatch, regmatch, sizeof(regmatch)); } break; } if (p->flags & CONDFLAG_NOTMATCH) { rc = !rc; } rewritelog(r, 4, ctx->perdir, "RewriteCond: input='%s' pattern='%s'%s " "=> %s", input, p->pattern - p->pskip, (p->flags & CONDFLAG_NOCASE) ? " [NC]" : "", rc ? "matched" : "not-matched"); return rc; } /* check for forced type and handler */ static APR_INLINE void force_type_handler(rewriterule_entry *p, rewrite_ctx *ctx) { char *expanded; if (p->forced_mimetype) { expanded = do_expand(p->forced_mimetype, ctx, p, NULL, ctx->r->pool); if (*expanded) { ap_str_tolower(expanded); rewritelog(ctx->r, 2, ctx->perdir, "remember %s to have MIME-type " "'%s'", ctx->r->filename, expanded); apr_table_setn(ctx->r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR, expanded); } } if (p->forced_handler) { expanded = do_expand(p->forced_handler, ctx, p, NULL, ctx->r->pool); if (*expanded) { ap_str_tolower(expanded); rewritelog(ctx->r, 2, ctx->perdir, "remember %s to have " "Content-handler '%s'", ctx->r->filename, expanded); apr_table_setn(ctx->r->notes, REWRITE_FORCED_HANDLER_NOTEVAR, expanded); } } } /* * Apply a single RewriteRule */ static rule_return_type apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) { ap_regmatch_t regmatch[AP_MAX_REG_MATCH]; apr_array_header_t *rewriteconds; rewritecond_entry *conds; int i, rc; char *newuri = NULL; request_rec *r = ctx->r; int is_proxyreq = 0; int prefix_added = 0; ctx->uri = r->filename; if (ctx->perdir) { apr_size_t dirlen = strlen(ctx->perdir); /* * Proxy request? */ is_proxyreq = ( r->proxyreq && r->filename && !strncmp(r->filename, "proxy:", 6)); /* 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)) { rewritelog(r, 3, ctx->perdir, "strip per-dir prefix: %s -> %s", ctx->uri, ctx->uri + dirlen); ctx->uri = ctx->uri + 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); rc = !ap_regexec(p->regexp, ctx->uri, AP_MAX_REG_MATCH, regmatch, 0); if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) || (!rc && (p->flags & RULEFLAG_NOTMATCH)) ) ) { return RULE_RC_NOMATCH; } /* It matched, wow! Now it's time to prepare the context structure for * further processing */ ctx->vary_this = NULL; ctx->briRC.source = NULL; if (p->flags & RULEFLAG_NOTMATCH) { ctx->briRR.source = NULL; } else { ctx->briRR.source = apr_pstrdup(r->pool, ctx->uri); memcpy(ctx->briRR.regmatch, regmatch, sizeof(regmatch)); } /* 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, ctx->temp_pool ? ctx->temp_pool : r->pool); /* * 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]; } } } else if (!rc) { if (ctx->temp_pool) { apr_pool_clear(ctx->temp_pool); } 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 (p->flags & RULEFLAG_UNSAFE_ALLOW3F) { newuri = do_expand(p->output, ctx, p, NULL, ctx->r->pool); } else { newuri = do_expand(p->output, ctx, p, &unsafe_qmark, ctx->r->pool); } rewritelog(r, 2, ctx->perdir, "rewrite '%s' -> '%s'", ctx->uri, newuri); 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=] */ do_expand_env(p->env, ctx); do_expand_cookie(p->cookie, ctx); /* non-substitution rules ('RewriteRule -') 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; } else if (!(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); newuri = apr_pstrcat(r->pool, "/", newuri, NULL); prefix_added = 1; } } /* Now adjust API's knowledge about r->filename and r->args */ r->filename = newuri; if (ctx->perdir && (p->flags & RULEFLAG_DISCARDPATHINFO)) { r->path_info = NULL; } splitout_queryargs(r, p->flags); /* 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); r->filename = apr_pstrcat(r->pool, "proxy:", r->filename, NULL); return RULE_RC_MATCH; } /* 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); r->status = p->forced_responsecode; return RULE_RC_MATCH; } /* Special Rewriting Feature: Self-Reduction * We reduce the URL by stripping a possible * http[s]://[:] 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 .. ://...') 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); r->status = p->forced_responsecode; return RULE_RC_MATCH; } 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 */ static int 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; rewrite_perdir_conf *dconf = (rewrite_perdir_conf *) ap_get_module_config(r->per_dir_config, &rewrite_module); ctx = apr_palloc(r->pool, sizeof(*ctx)); ctx->perdir = perdir; ctx->r = r; *lastsub = NULL; if (dconf->options & OPTION_LONGOPT) { apr_pool_create(&(ctx->temp_pool), r->pool); apr_pool_tag(ctx->temp_pool, "rewrite_ctx_ptemp"); } else { ctx->temp_pool = NULL; } /* * 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); r->status = HTTP_INTERNAL_SERVER_ERROR; return ACTION_STATUS; } goto loop; } /* * 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; } /* * +-------------------------------------------------------+ * | | * | Module Initialization Hooks * | | * +-------------------------------------------------------+ */ static int pre_config(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptemp) { APR_OPTIONAL_FN_TYPE(ap_register_rewrite_mapfunc) *map_pfn_register; rewrite_lock_needed = 0; ap_mutex_register(pconf, rewritemap_mutex_type, NULL, APR_LOCK_DEFAULT, 0); /* register int: rewritemap handlers */ map_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_rewrite_mapfunc); if (map_pfn_register) { map_pfn_register("tolower", rewrite_mapfunc_tolower); map_pfn_register("toupper", rewrite_mapfunc_toupper); map_pfn_register("escape", rewrite_mapfunc_escape); map_pfn_register("unescape", rewrite_mapfunc_unescape); } dbd_acquire = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_acquire); dbd_prepare = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_prepare); return OK; } static int post_config(apr_pool_t *p, apr_pool_t *plog, apr_pool_t *ptemp, server_rec *s) { apr_status_t rv; /* check if proxy module is available */ proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL); if (rewrite_lock_needed) { rv = rewritelock_create(s, p); if (rv != APR_SUCCESS) { return HTTP_INTERNAL_SERVER_ERROR; } apr_pool_cleanup_register(p, (void *)s, rewritelock_remove, apr_pool_cleanup_null); } /* 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; } static void 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"); } } /* * +-------------------------------------------------------+ * | | * | runtime hooks * | | * +-------------------------------------------------------+ */ /* * URI-to-filename hook * [deals with RewriteRules in server context] */ static int hook_uri2file(request_rec *r) { rewrite_perdir_conf *dconf; rewrite_server_conf *conf; const char *saved_rulestatus; const char *var; const char *thisserver; char *thisport; const char *thisurl; unsigned int port; int rulestatus; void *skipdata; char *ofilename; const char *oargs; rewriterule_entry *lastsub = NULL; /* * retrieve the config structures */ conf = ap_get_module_config(r->server->module_config, &rewrite_module); dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config, &rewrite_module); /* * 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. * also, we'll restore original r->filename if we decline this * request. */ ofilename = r->filename; 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 r->filename was not initially set or if it's a pre_trans reverse * "proxy:" scheme, we start with the requested URI. */ if (r->filename == NULL || (r->proxyreq == PROXYREQ_REVERSE && strncmp(r->filename, "proxy:", 6) == 0)) { r->filename = apr_pstrdup(r->pool, r->uri); rewritelog(r, 2, NULL, "init rewrite engine with requested uri " "%s. Original filename = %s", r->filename, (ofilename) ? ofilename : "n/a"); } 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); rulestatus = atoi(saved_rulestatus); } if (rulestatus) { apr_size_t flen = r->filename ? strlen(r->filename) : 0; unsigned skip_absolute = flen ? is_absolute_uri(r->filename, NULL) : 0; int to_proxyreq = (flen > 6 && strncmp(r->filename, "proxy:", 6) == 0); int will_escape = skip_absolute && (rulestatus != ACTION_NOESCAPE); 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 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; r->status = HTTP_OK; return n; } else if (ACTION_STATUS_SET == rulestatus) { return r->status; } /* If a pre_trans reverse "proxy:" filename gets rewritten to * a non-proxy one this is not a proxy request anymore. */ if (r->proxyreq == PROXYREQ_REVERSE && !to_proxyreq) { if (r->handler && strcmp(r->handler, "proxy-server") == 0) { r->handler = NULL; } r->proxyreq = PROXYREQ_NONE; } 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; } else if (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))); r->filename = apr_pstrcat(r->pool, r->filename, "?", noescape ? r->args : (escaped_args = ap_escape_uri(r->pool, r->args)), NULL); rewritelog(r, 1, NULL, "%s %s to query string for redirect %s", noescape ? "copying" : "escaping", r->args , noescape ? "" : escaped_args); } /* 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; } else if (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 */ const char *uri_reduced = NULL; /* expand "/~user" prefix */ #if APR_HAS_USER r->filename = expand_tildepaths(r, r->filename); #endif rewritelog(r, 2, NULL, "local path result: %s", r->filename); /* 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(!(conf->options & OPTION_LEGACY_PREFIX_DOCROOT)) { uri_reduced = apr_table_get(r->notes, "mod_rewrite_uri_reduced"); } if (!prefix_stat(r, r->filename, r->pool, conf->options & OPTION_UNSAFE_PREFIX_STAT ? NULL : lastsub) || uri_reduced != NULL) { int res; char *tmp = r->uri; r->uri = r->filename; res = ap_core_translate(r); r->uri = tmp; if (res != OK) { rewritelog(r, 1, NULL, "prefixing with document_root of %s" " FAILED", r->filename); return res; } rewritelog(r, 2, NULL, "prefixed with document_root to %s", r->filename); } rewritelog(r, 1, NULL, "go-ahead with %s [OK]", r->filename); return OK; } } else { rewritelog(r, 1, NULL, "pass through %s, filename %s", r->filename, (ofilename) ? ofilename : "n/a"); r->filename = ofilename; return DECLINED; } } /* * Fixup hook * [RewriteRules in directory context] */ static int hook_fixup(request_rec *r) { rewrite_perdir_conf *dconf; char *cp; char *cp2; const char *ccp; apr_size_t l; int rulestatus; int n; char *ofilename, *oargs; int is_proxyreq; void *skipdata; rewriterule_entry *lastsub; dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config, &rewrite_module); /* 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; } /* * Proxy request? */ is_proxyreq = ( r->proxyreq && r->filename && !strncmp(r->filename, "proxy:", 6)); /* * .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->filename == NULL) { r->filename = apr_pstrdup(r->pool, r->uri); rewritelog(r, 2, dconf->directory, "init rewrite engine with" " requested uri %s", r->filename); } /* * now apply the rules ... */ rulestatus = apply_rewrite_list(r, dconf->rewriterules, dconf->directory, &lastsub); if (rulestatus) { unsigned skip_absolute = is_absolute_uri(r->filename, NULL); int to_proxyreq = 0; int will_escape = 0; l = strlen(r->filename); to_proxyreq = l > 6 && strncmp(r->filename, "proxy:", 6) == 0; will_escape = skip_absolute && (rulestatus != ACTION_NOESCAPE); 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; r->status = HTTP_OK; return n; } else if (ACTION_STATUS_SET == rulestatus) { return 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; } else if (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))); r->filename = apr_pstrcat(r->pool, r->filename, "?", noescape ? r->args : (escaped_args = ap_escape_uri(r->pool, r->args)), NULL); rewritelog(r, 1, dconf->directory, "%s %s to query string for redirect %s", noescape ? "copying" : "escaping", r->args , noescape ? "" : escaped_args); } /* 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 { const char *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); } rewritelog(r, 2, NULL, "local path result: %s", r->filename); /* 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); r->filename = apr_pstrdup(r->pool, 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) { const char *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); } } } apr_table_setn(r->notes, "redirect-keeps-vary", ""); /* now initiate the internal redirect */ rewritelog(r, 1, dconf->directory, "internal redirect with %s " "[INTERNAL REDIRECT]", r->filename); r->filename = apr_pstrcat(r->pool, "redirect:", r->filename, NULL); r->handler = REWRITE_REDIRECT_HANDLER_NAME; return OK; } } else { rewritelog(r, 1, dconf->directory, "pass through %s, filename %s", r->filename, (ofilename) ? ofilename : "n/a"); r->filename = ofilename; return DECLINED; } } /* * MIME-type hook * [T=...,H=...] execution */ static int hook_mimetype(request_rec *r) { const char *t; /* 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 */ static int 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); /* and return gracefully */ return OK; } /* * +-------------------------------------------------------+ * | | * | Module paraphernalia * | | * +-------------------------------------------------------+ */ static const 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 } }; static void ap_register_rewrite_mapfunc(char *name, rewrite_mapfunc_t *func) { apr_hash_set(mapfunc_hash, name, strlen(name), (const void *)func); } static void register_hooks(apr_pool_t *p) { static const char * const aszModProxy[] = { "mod_proxy.c", 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); ap_hook_handler(handler_redirect, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_pre_config(pre_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_child_init(init_child, NULL, NULL, APR_HOOK_MIDDLE); /* 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); } /* the main config structure */ AP_DECLARE_MODULE(rewrite) = { STANDARD20_MODULE_STUFF, config_perdir_create, /* create per-dir config structures */ config_perdir_merge, /* merge per-dir config structures */ config_server_create, /* create per-server config structures */ config_server_merge, /* merge per-server config structures */ command_table, /* table of config file commands */ register_hooks /* register hooks */ }; /*EOF*/