1 /* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements. See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
18 * _ __ ___ ___ __| | _ __ _____ ___ __(_) |_ ___
19 * | '_ ` _ \ / _ \ / _` | | '__/ _ \ \ /\ / / '__| | __/ _ \
20 * | | | | | | (_) | (_| | | | | __/\ V V /| | | | || __/
21 * |_| |_| |_|\___/ \__,_|___|_| \___| \_/\_/ |_| |_|\__\___|
24 * URL Rewriting Module
26 * This module uses a rule-based rewriting engine (based on a
27 * regular-expression parser) to rewrite requested URLs on the fly.
29 * It supports an unlimited number of additional rule conditions (which can
30 * operate on a lot of variables, even on HTTP headers) for granular
31 * matching and even external database lookups (either via plain text
32 * tables, DBM hash files or even external processes) for advanced URL
35 * It operates on the full URLs (including the PATH_INFO part) both in
36 * per-server context (httpd.conf) and per-dir context (.htaccess) and even
37 * can generate QUERY_STRING parts on result. The rewriting result finally
38 * can lead to internal subprocessing, external request redirection or even
39 * to internal proxy throughput.
41 * This module was originally written in April 1996 and
42 * gifted exclusively to the The Apache Software Foundation in July 1997 by
50 #include "apr_strings.h"
54 #include "apr_signal.h"
55 #include "apr_global_mutex.h"
61 #include "apr_thread_mutex.h"
64 #define APR_WANT_MEMFUNC
65 #define APR_WANT_STRFUNC
66 #define APR_WANT_IOVEC
69 /* XXX: Do we really need these headers? */
73 #if APR_HAVE_SYS_TYPES_H
74 #include <sys/types.h>
85 #if APR_HAVE_NETINET_IN_H
86 #include <netinet/in.h>
89 #include "ap_config.h"
91 #include "http_config.h"
92 #include "http_request.h"
93 #include "http_core.h"
95 #include "http_protocol.h"
96 #include "http_vhost.h"
97 #include "util_mutex.h"
101 #include "mod_rewrite.h"
104 #if APR_CHARSET_EBCDIC
105 #include "util_charset.h"
108 static ap_dbd_t *(*dbd_acquire)(request_rec*) = NULL;
109 static void (*dbd_prepare)(server_rec*, const char*, const char*) = NULL;
110 static const char* really_last_key = "rewrite_really_last";
113 * in order to improve performance on running production systems, you
114 * may strip all rewritelog code entirely from mod_rewrite by using the
115 * -DREWRITELOG_DISABLED compiler option.
117 * DO NOT USE THIS OPTION FOR PUBLIC BINARY RELEASES. Otherwise YOU are
118 * responsible for answering all the mod_rewrite questions out there.
120 /* If logging is limited to APLOG_DEBUG or lower, disable rewrite log, too */
121 #ifdef APLOG_MAX_LOGLEVEL
122 #if APLOG_MAX_LOGLEVEL < APLOG_TRACE1
123 #ifndef REWRITELOG_DISABLED
124 #define REWRITELOG_DISABLED
129 #ifndef REWRITELOG_DISABLED
131 #define rewritelog(x) do_rewritelog x
132 #define REWRITELOG_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD )
133 #define REWRITELOG_FLAGS ( APR_WRITE | APR_APPEND | APR_CREATE )
135 #else /* !REWRITELOG_DISABLED */
137 #define rewritelog(x)
139 #endif /* REWRITELOG_DISABLED */
141 /* remembered mime-type for [T=...] */
142 #define REWRITE_FORCED_MIMETYPE_NOTEVAR "rewrite-forced-mimetype"
143 #define REWRITE_FORCED_HANDLER_NOTEVAR "rewrite-forced-handler"
145 #define ENVVAR_SCRIPT_URL "SCRIPT_URL"
146 #define REDIRECT_ENVVAR_SCRIPT_URL "REDIRECT_" ENVVAR_SCRIPT_URL
147 #define ENVVAR_SCRIPT_URI "SCRIPT_URI"
149 #define CONDFLAG_NONE (1<<0)
150 #define CONDFLAG_NOCASE (1<<1)
151 #define CONDFLAG_NOTMATCH (1<<2)
152 #define CONDFLAG_ORNEXT (1<<3)
153 #define CONDFLAG_NOVARY (1<<4)
155 #define RULEFLAG_NONE (1<<0)
156 #define RULEFLAG_FORCEREDIRECT (1<<1)
157 #define RULEFLAG_LASTRULE (1<<2)
158 #define RULEFLAG_NEWROUND (1<<3)
159 #define RULEFLAG_CHAIN (1<<4)
160 #define RULEFLAG_IGNOREONSUBREQ (1<<5)
161 #define RULEFLAG_NOTMATCH (1<<6)
162 #define RULEFLAG_PROXY (1<<7)
163 #define RULEFLAG_PASSTHROUGH (1<<8)
164 #define RULEFLAG_QSAPPEND (1<<9)
165 #define RULEFLAG_NOCASE (1<<10)
166 #define RULEFLAG_NOESCAPE (1<<11)
167 #define RULEFLAG_NOSUB (1<<12)
168 #define RULEFLAG_STATUS (1<<13)
169 #define RULEFLAG_ESCAPEBACKREF (1<<14)
170 #define RULEFLAG_DISCARDPATHINFO (1<<15)
171 #define RULEFLAG_QSDISCARD (1<<16)
172 #define RULEFLAG_END (1<<17)
173 #define RULEFLAG_ESCAPENOPLUS (1<<18)
175 /* return code of the rewrite rule
176 * the result may be escaped - or not
178 #define ACTION_NORMAL (1<<0)
179 #define ACTION_NOESCAPE (1<<1)
180 #define ACTION_STATUS (1<<2)
183 #define MAPTYPE_TXT (1<<0)
184 #define MAPTYPE_DBM (1<<1)
185 #define MAPTYPE_PRG (1<<2)
186 #define MAPTYPE_INT (1<<3)
187 #define MAPTYPE_RND (1<<4)
188 #define MAPTYPE_DBD (1<<5)
189 #define MAPTYPE_DBD_CACHE (1<<6)
191 #define ENGINE_DISABLED (1<<0)
192 #define ENGINE_ENABLED (1<<1)
194 #define OPTION_NONE (1<<0)
195 #define OPTION_INHERIT (1<<1)
196 #define OPTION_INHERIT_BEFORE (1<<2)
197 #define OPTION_NOSLASH (1<<3)
198 #define OPTION_ANYURI (1<<4)
199 #define OPTION_MERGEBASE (1<<5)
200 #define OPTION_INHERIT_DOWN (1<<6)
201 #define OPTION_INHERIT_DOWN_BEFORE (1<<7)
202 #define OPTION_IGNORE_INHERIT (1<<8)
203 #define OPTION_IGNORE_CONTEXT_INFO (1<<9)
206 #define RAND_MAX 32767
209 /* max cookie size in rfc 2109 */
210 /* XXX: not used at all. We should do a check somewhere and/or cut the cookie */
211 #define MAX_COOKIE_LEN 4096
213 /* max line length (incl.\n) in text rewrite maps */
214 #ifndef REWRITE_MAX_TXT_MAP_LINE
215 #define REWRITE_MAX_TXT_MAP_LINE 1024
218 /* buffer length for prg rewrite maps */
219 #ifndef REWRITE_PRG_MAP_BUF
220 #define REWRITE_PRG_MAP_BUF 1024
223 /* for better readbility */
224 #define LEFT_CURLY '{'
225 #define RIGHT_CURLY '}'
228 * expansion result items on the stack to save some cycles
230 * (5 == about 2 variables like "foo%{var}bar%{var}baz")
232 #define SMALL_EXPANSION 5
235 * check that a subrequest won't cause infinite recursion
237 * either not in a subrequest, or in a subrequest
238 * and URIs aren't NULL and sub/main URIs differ
240 #define subreq_ok(r) (!r->main || \
241 (r->main->uri && r->uri && strcmp(r->main->uri, r->uri)))
243 #ifndef REWRITE_MAX_ROUNDS
244 #define REWRITE_MAX_ROUNDS 10000
248 * +-------------------------------------------------------+
250 * | Types and Structures
252 * +-------------------------------------------------------+
256 const char *datafile; /* filename for map data files */
257 const char *dbmtype; /* dbm type for dbm map data files */
258 const char *checkfile; /* filename to check for map existence */
259 const char *cachename; /* for cached maps (txt/rnd/dbm) */
260 int type; /* the type of the map */
261 apr_file_t *fpin; /* in file pointer for program maps */
262 apr_file_t *fpout; /* out file pointer for program maps */
263 apr_file_t *fperr; /* err file pointer for program maps */
264 char *(*func)(request_rec *, /* function pointer for internal maps */
266 char **argv; /* argv of the external rewrite map */
267 const char *dbdq; /* SQL SELECT statement for rewritemap */
268 const char *checkfile2; /* filename to check for map existence
269 NULL if only one file */
270 const char *user; /* run RewriteMap program as this user */
271 const char *group; /* run RewriteMap program as this group */
274 /* special pattern types for RewriteCond */
298 char *input; /* Input string of RewriteCond */
299 char *pattern; /* the RegExp pattern string */
300 ap_regex_t *regexp; /* the precompiled regexp */
301 ap_expr_info_t *expr; /* the compiled ap_expr */
302 int flags; /* Flags which control the match */
303 pattern_type ptype; /* pattern type */
304 int pskip; /* back-index to display pattern */
307 /* single linked list for env vars and cookies */
308 typedef struct data_item {
309 struct data_item *next;
314 apr_array_header_t *rewriteconds;/* the corresponding RewriteCond entries */
315 char *pattern; /* the RegExp pattern string */
316 ap_regex_t *regexp; /* the RegExp pattern compilation */
317 char *output; /* the Substitution string */
318 int flags; /* Flags which control the substitution */
319 char *forced_mimetype; /* forced MIME type of substitution */
320 char *forced_handler; /* forced content handler of subst. */
321 int forced_responsecode; /* forced HTTP response status */
322 data_item *env; /* added environment variables */
323 data_item *cookie; /* added cookies */
324 int skip; /* number of next rules to skip */
325 int maxrounds; /* limit on number of loops with N flag */
326 char *escapes; /* specific backref escapes */
330 int state; /* the RewriteEngine state */
331 int options; /* the RewriteOption state */
332 apr_hash_t *rewritemaps; /* the RewriteMap entries */
333 apr_array_header_t *rewriteconds; /* the RewriteCond entries (temp.) */
334 apr_array_header_t *rewriterules; /* the RewriteRule entries */
335 server_rec *server; /* the corresponding server indicator */
336 unsigned int state_set:1;
337 unsigned int options_set:1;
338 } rewrite_server_conf;
341 int state; /* the RewriteEngine state */
342 int options; /* the RewriteOption state */
343 apr_array_header_t *rewriteconds; /* the RewriteCond entries (temp.) */
344 apr_array_header_t *rewriterules; /* the RewriteRule entries */
345 char *directory; /* the directory where it applies */
346 const char *baseurl; /* the base-URL where it applies */
347 unsigned int state_set:1;
348 unsigned int options_set:1;
349 unsigned int baseurl_set:1;
350 } rewrite_perdir_conf;
352 /* the (per-child) cache structures.
354 typedef struct cache {
358 apr_thread_mutex_t *lock;
362 /* cached maps contain an mtime for the whole map and live in a subpool
363 * of the cachep->pool. That makes it easy to forget them if necessary.
371 /* the regex structure for the
372 * substitution of backreferences
374 typedef struct backrefinfo {
376 ap_regmatch_t regmatch[AP_MAX_REG_MATCH];
379 /* single linked list used for
382 typedef struct result_list {
383 struct result_list *next;
388 /* context structure for variable lookup and expansion
393 const char *vary_this;
401 * +-------------------------------------------------------+
403 * | static module data
405 * +-------------------------------------------------------+
408 /* the global module structure */
409 module AP_MODULE_DECLARE_DATA rewrite_module;
411 /* rewritemap int: handler function registry */
412 static apr_hash_t *mapfunc_hash;
415 static cache *cachep;
417 /* whether proxy module is available or not */
418 static int proxy_available;
421 static apr_global_mutex_t *rewrite_mapr_lock_acquire = NULL;
422 static const char *rewritemap_mutex_type = "rewrite-map";
424 /* Optional functions imported from mod_ssl when loaded: */
425 static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *rewrite_ssl_lookup = NULL;
426 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *rewrite_is_https = NULL;
427 static char *escape_backref(apr_pool_t *p, const char *path, const char *escapeme, int noplus);
430 * +-------------------------------------------------------+
432 * | rewriting logfile support
434 * +-------------------------------------------------------+
437 #ifndef REWRITELOG_DISABLED
438 static void do_rewritelog(request_rec *r, int level, char *perdir,
439 const char *fmt, ...)
440 __attribute__((format(printf,4,5)));
442 static void do_rewritelog(request_rec *r, int level, char *perdir,
443 const char *fmt, ...)
445 char *logline, *text;
446 const char *rhost, *rname;
451 if (!APLOG_R_IS_LEVEL(r, APLOG_DEBUG + level))
454 rhost = ap_get_useragent_host(r, REMOTE_NOLOOKUP, NULL);
455 rname = ap_get_remote_logname(r);
457 for (redir=0, req=r; req->prev; req = req->prev) {
462 text = apr_pvsprintf(r->pool, fmt, ap);
465 logline = apr_psprintf(r->pool, "%s %s %s [%s/sid#%pp][rid#%pp/%s%s%s] "
467 rhost ? rhost : "UNKNOWN-HOST",
469 r->user ? (*r->user ? r->user : "\"\"") : "-",
470 ap_get_server_name(r),
473 r->main ? "subreq" : "initial",
474 redir ? "/redir#" : "",
475 redir ? apr_itoa(r->pool, redir) : "",
476 perdir ? "[perdir " : "",
477 perdir ? perdir : "",
481 AP_REWRITE_LOG((uintptr_t)r, level, r->main ? 0 : 1, (char *)ap_get_server_name(r), logline);
483 /* Intentional no APLOGNO */
484 ap_log_rerror(APLOG_MARK, APLOG_DEBUG + level, 0, r, "%s", logline);
488 #endif /* !REWRITELOG_DISABLED */
492 * +-------------------------------------------------------+
494 * | URI and path functions
496 * +-------------------------------------------------------+
499 /* return number of chars of the scheme (incl. '://')
500 * if the URI is absolute (includes a scheme etc.)
502 * If supportqs is not NULL, we return a whether or not
503 * the scheme supports a query string or not.
505 * NOTE: If you add new schemes here, please have a
506 * look at escape_absolute_uri and splitout_queryargs.
507 * Not every scheme takes query strings and some schemes
508 * may be handled in a special way.
510 * XXX: we may consider a scheme registry, perhaps with
511 * appropriate escape callbacks to allow other modules
512 * to extend mod_rewrite at runtime.
514 static unsigned is_absolute_uri(char *uri, int *supportsqs)
518 sqs = (supportsqs ? supportsqs : &dummy);
521 if (*uri == '/' || strlen(uri) <= 5) {
528 if (!ap_casecmpstrn(uri, "jp://", 5)) { /* ajp:// */
536 if (!ap_casecmpstrn(uri, "alancer://", 10)) { /* balancer:// */
544 if (!ap_casecmpstrn(uri, "tp://", 5)) { /* ftp:// */
547 if (!ap_casecmpstrn(uri, "cgi://", 6)) { /* fcgi:// */
555 if (!ap_casecmpstrn(uri, "opher://", 8)) { /* gopher:// */
562 if (!ap_casecmpstrn(uri, "ttp://", 6)) { /* http:// */
566 else if (!ap_casecmpstrn(uri, "ttps://", 7)) { /* https:// */
574 if (!ap_casecmpstrn(uri, "dap://", 6)) { /* ldap:// */
581 if (!ap_casecmpstrn(uri, "ailto:", 6)) { /* mailto: */
589 if (!ap_casecmpstrn(uri, "ews:", 4)) { /* news: */
592 else if (!ap_casecmpstrn(uri, "ntp://", 6)) { /* nntp:// */
599 if (!ap_casecmpstrn(uri, "cgi://", 6)) { /* scgi:// */
607 if (!ap_casecmpstrn(uri, "s://", 4)) { /* ws:// */
611 else if (!ap_casecmpstrn(uri, "ss://", 5)) { /* wss:// */
621 static const char c2x_table[] = "0123456789abcdef";
623 static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix,
624 unsigned char *where)
626 #if APR_CHARSET_EBCDIC
627 what = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)what);
628 #endif /*APR_CHARSET_EBCDIC*/
630 *where++ = c2x_table[what >> 4];
631 *where++ = c2x_table[what & 0xf];
636 * Escapes a backreference in a similar way as php's urlencode does.
637 * Based on ap_os_escape_path in server/util.c
639 static char *escape_backref(apr_pool_t *p, const char *path, const char *escapeme, int noplus) {
640 char *copy = apr_palloc(p, 3 * strlen(path) + 3);
641 const unsigned char *s = (const unsigned char *)path;
642 unsigned char *d = (unsigned char *)copy;
647 if (apr_isalnum(c) || c == '_') {
650 else if (c == ' ' && !noplus) {
658 const char *esc = escapeme;
661 if (c == ' ' && !noplus) {
682 * escape absolute uri, which may or may not be path oriented.
683 * So let's handle them differently.
685 static char *escape_absolute_uri(apr_pool_t *p, char *uri, unsigned scheme)
690 * NULL should indicate elsewhere, that something's wrong
692 if (!scheme || strlen(uri) < scheme) {
698 /* scheme with authority part? */
701 while (*cp && *cp != '/') {
705 /* nothing after the hostpart. ready! */
706 if (!*cp || !*++cp) {
707 return apr_pstrdup(p, uri);
710 /* remember the hostname stuff */
713 /* special thing for ldap.
714 * The parts are separated by question marks. From RFC 2255:
715 * ldapurl = scheme "://" [hostport] ["/"
716 * [dn ["?" [attributes] ["?" [scope]
717 * ["?" [filter] ["?" extensions]]]]]]
719 if (!ap_casecmpstrn(uri, "ldap", 4)) {
723 token[0] = cp = apr_pstrdup(p, cp);
724 while (*cp && c < 4) {
732 return apr_pstrcat(p, apr_pstrndup(p, uri, scheme),
733 ap_escape_uri(p, token[0]),
734 (c >= 1) ? "?" : NULL,
735 (c >= 1) ? ap_escape_uri(p, token[1]) : NULL,
736 (c >= 2) ? "?" : NULL,
737 (c >= 2) ? ap_escape_uri(p, token[2]) : NULL,
738 (c >= 3) ? "?" : NULL,
739 (c >= 3) ? ap_escape_uri(p, token[3]) : NULL,
740 (c >= 4) ? "?" : NULL,
741 (c >= 4) ? ap_escape_uri(p, token[4]) : NULL,
746 /* Nothing special here. Apply normal escaping. */
747 return apr_pstrcat(p, apr_pstrndup(p, uri, scheme),
748 ap_escape_uri(p, cp), NULL);
752 * split out a QUERY_STRING part from
753 * the current URI string
755 static void splitout_queryargs(request_rec *r, int qsappend, int qsdiscard)
760 /* don't touch, unless it's a scheme for which a query string makes sense.
761 * See RFC 1738 and RFC 2368.
763 if (is_absolute_uri(r->filename, &split)
765 r->args = NULL; /* forget the query that's still flying around */
770 r->args = NULL; /* Discard query string */
771 rewritelog((r, 2, NULL, "discarding query string"));
774 q = ap_strchr(r->filename, '?');
779 olduri = apr_pstrdup(r->pool, r->filename);
782 r->args = apr_pstrcat(r->pool, q, "&", r->args, NULL);
785 r->args = apr_pstrdup(r->pool, q);
788 len = strlen(r->args);
792 else if (r->args[len-1] == '&') {
793 r->args[len-1] = '\0';
796 rewritelog((r, 3, NULL, "split uri=%s -> uri=%s, args=%s", olduri,
797 r->filename, r->args ? r->args : "<none>"));
804 * strip 'http[s]://ourhost/' from URI
806 static void reduce_uri(request_rec *r)
811 cp = (char *)ap_http_scheme(r);
813 if ( strlen(r->filename) > l+3
814 && ap_casecmpstrn(r->filename, cp, l) == 0
815 && r->filename[l] == ':'
816 && r->filename[l+1] == '/'
817 && r->filename[l+2] == '/' ) {
820 char *portp, *host, *url, *scratch;
822 scratch = apr_pstrdup(r->pool, r->filename); /* our scratchpad */
824 /* cut the hostname and port out of the URI */
825 cp = host = scratch + l + 3; /* 3 == strlen("://") */
826 while (*cp && *cp != '/' && *cp != ':') {
830 if (*cp == ':') { /* additional port given */
833 while (*cp && *cp != '/') {
839 url = r->filename + (cp - scratch);
844 else if (*cp == '/') { /* default port */
847 port = ap_default_port(r);
848 url = r->filename + (cp - scratch);
851 port = ap_default_port(r);
855 /* now check whether we could reduce it to a local path... */
856 if (ap_matches_request_vhost(r, host, port)) {
857 rewritelog((r, 3, NULL, "reduce %s -> %s", r->filename, url));
858 r->filename = apr_pstrdup(r->pool, url);
866 * add 'http[s]://ourhost[:ourport]/' to URI
867 * if URI is still not fully qualified
869 static void fully_qualify_uri(request_rec *r)
871 if (r->method_number == M_CONNECT) {
874 else if (!is_absolute_uri(r->filename, NULL)) {
875 const char *thisserver;
879 thisserver = ap_get_server_name_for_url(r);
880 port = ap_get_server_port(r);
881 thisport = ap_is_default_port(port, r)
883 : apr_psprintf(r->pool, ":%u", port);
885 r->filename = apr_psprintf(r->pool, "%s://%s%s%s%s",
886 ap_http_scheme(r), thisserver, thisport,
887 (*r->filename == '/') ? "" : "/",
895 * stat() only the first segment of a path
897 static int prefix_stat(const char *path, apr_pool_t *pool)
899 const char *curpath = path;
905 rv = apr_filepath_root(&root, &curpath, APR_FILEPATH_TRUENAME, pool);
907 if (rv != APR_SUCCESS) {
911 /* let's recognize slashes only, the mod_rewrite semantics are opaque
914 if ((slash = ap_strchr_c(curpath, '/')) != NULL) {
915 rv = apr_filepath_merge(&statpath, root,
916 apr_pstrndup(pool, curpath,
917 (apr_size_t)(slash - curpath)),
918 APR_FILEPATH_NOTABOVEROOT |
919 APR_FILEPATH_NOTRELATIVE, pool);
922 rv = apr_filepath_merge(&statpath, root, curpath,
923 APR_FILEPATH_NOTABOVEROOT |
924 APR_FILEPATH_NOTRELATIVE, pool);
927 if (rv == APR_SUCCESS) {
930 if (apr_stat(&sb, statpath, APR_FINFO_MIN, pool) == APR_SUCCESS) {
939 * substitute the prefix path 'match' in 'input' with 'subst' (RewriteBase)
941 static char *subst_prefix_path(request_rec *r, char *input, const char *match,
944 apr_size_t len = strlen(match);
946 if (len && match[len - 1] == '/') {
950 if (!strncmp(input, match, len) && input[len++] == '/') {
951 apr_size_t slen, outlen;
954 rewritelog((r, 5, NULL, "strip matching prefix: %s -> %s", input,
957 slen = strlen(subst);
958 if (slen && subst[slen - 1] != '/') {
962 outlen = strlen(input) + slen - len;
963 output = apr_palloc(r->pool, outlen + 1); /* don't forget the \0 */
965 memcpy(output, subst, slen);
966 if (slen && !output[slen-1]) {
967 output[slen-1] = '/';
969 memcpy(output+slen, input+len, outlen - slen);
970 output[outlen] = '\0';
972 rewritelog((r, 4, NULL, "add subst prefix: %s -> %s", input+len,
978 /* prefix didn't match */
984 * +-------------------------------------------------------+
988 * +-------------------------------------------------------+
991 static void set_cache_value(const char *name, apr_time_t t, char *key,
998 apr_thread_mutex_lock(cachep->lock);
1000 map = apr_hash_get(cachep->maps, name, APR_HASH_KEY_STRING);
1005 if (apr_pool_create(&p, cachep->pool) != APR_SUCCESS) {
1007 apr_thread_mutex_unlock(cachep->lock);
1012 map = apr_palloc(cachep->pool, sizeof(cachedmap));
1014 map->entries = apr_hash_make(map->pool);
1017 apr_hash_set(cachep->maps, name, APR_HASH_KEY_STRING, map);
1019 else if (map->mtime != t) {
1020 apr_pool_clear(map->pool);
1021 map->entries = apr_hash_make(map->pool);
1025 /* Now we should have a valid map->entries hash, where we
1026 * can store our value.
1028 * We need to copy the key and the value into OUR pool,
1029 * so that we don't leave it during the r->pool cleanup.
1031 apr_hash_set(map->entries,
1032 apr_pstrdup(map->pool, key), APR_HASH_KEY_STRING,
1033 apr_pstrdup(map->pool, val));
1036 apr_thread_mutex_unlock(cachep->lock);
1043 static char *get_cache_value(const char *name, apr_time_t t, char *key,
1051 apr_thread_mutex_lock(cachep->lock);
1053 map = apr_hash_get(cachep->maps, name, APR_HASH_KEY_STRING);
1056 /* if this map is outdated, forget it. */
1057 if (map->mtime != t) {
1058 apr_pool_clear(map->pool);
1059 map->entries = apr_hash_make(map->pool);
1063 val = apr_hash_get(map->entries, key, APR_HASH_KEY_STRING);
1065 /* copy the cached value into the supplied pool,
1066 * where it belongs (r->pool usually)
1068 val = apr_pstrdup(p, val);
1074 apr_thread_mutex_unlock(cachep->lock);
1081 static int init_cache(apr_pool_t *p)
1083 cachep = apr_palloc(p, sizeof(cache));
1084 if (apr_pool_create(&cachep->pool, p) != APR_SUCCESS) {
1085 cachep = NULL; /* turns off cache */
1089 cachep->maps = apr_hash_make(cachep->pool);
1091 (void)apr_thread_mutex_create(&(cachep->lock), APR_THREAD_MUTEX_DEFAULT, p);
1099 * +-------------------------------------------------------+
1103 * +-------------------------------------------------------+
1107 * General Note: key is already a fresh string, created (expanded) just
1108 * for the purpose to be passed in here. So one can modify key itself.
1111 static char *rewrite_mapfunc_toupper(request_rec *r, char *key)
1113 ap_str_toupper(key);
1118 static char *rewrite_mapfunc_tolower(request_rec *r, char *key)
1120 ap_str_tolower(key);
1125 static char *rewrite_mapfunc_escape(request_rec *r, char *key)
1127 return ap_escape_uri(r->pool, key);
1130 static char *rewrite_mapfunc_unescape(request_rec *r, char *key)
1132 ap_unescape_url(key);
1137 static char *select_random_value_part(request_rec *r, char *value)
1142 /* count number of distinct values */
1143 while ((p = ap_strchr(p, '|')) != NULL) {
1149 n = ap_random_pick(1, n);
1151 /* extract it from the whole string */
1152 while (--n && (value = ap_strchr(value, '|')) != NULL) {
1156 if (value) { /* should not be NULL, but ... */
1157 p = ap_strchr(value, '|');
1167 /* child process code */
1168 static void rewrite_child_errfn(apr_pool_t *p, apr_status_t err,
1171 ap_log_error(APLOG_MARK, APLOG_ERR, err, NULL, APLOGNO(00653) "%s", desc);
1174 static apr_status_t rewritemap_program_child(apr_pool_t *p,
1175 const char *progname, char **argv,
1176 const char *user, const char *group,
1181 apr_procattr_t *procattr;
1182 apr_proc_t *procnew;
1184 if ( APR_SUCCESS == (rc=apr_procattr_create(&procattr, p))
1185 && APR_SUCCESS == (rc=apr_procattr_io_set(procattr, APR_FULL_BLOCK,
1186 APR_FULL_BLOCK, APR_NO_PIPE))
1187 && APR_SUCCESS == (rc=apr_procattr_dir_set(procattr,
1188 ap_make_dirstr_parent(p, argv[0])))
1189 && (!user || APR_SUCCESS == (rc=apr_procattr_user_set(procattr, user, "")))
1190 && (!group || APR_SUCCESS == (rc=apr_procattr_group_set(procattr, group)))
1191 && APR_SUCCESS == (rc=apr_procattr_cmdtype_set(procattr, APR_PROGRAM))
1192 && APR_SUCCESS == (rc=apr_procattr_child_errfn_set(procattr,
1193 rewrite_child_errfn))
1194 && APR_SUCCESS == (rc=apr_procattr_error_check_set(procattr, 1))) {
1196 procnew = apr_pcalloc(p, sizeof(*procnew));
1197 rc = apr_proc_create(procnew, argv[0], (const char **)argv, NULL,
1200 if (rc == APR_SUCCESS) {
1201 apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);
1204 (*fpin) = procnew->in;
1208 (*fpout) = procnew->out;
1216 static apr_status_t run_rewritemap_programs(server_rec *s, apr_pool_t *p)
1218 rewrite_server_conf *conf;
1219 apr_hash_index_t *hi;
1222 conf = ap_get_module_config(s->module_config, &rewrite_module);
1224 /* If the engine isn't turned on,
1225 * don't even try to do anything.
1227 if (conf->state == ENGINE_DISABLED) {
1231 for (hi = apr_hash_first(p, conf->rewritemaps); hi; hi = apr_hash_next(hi)){
1232 apr_file_t *fpin = NULL;
1233 apr_file_t *fpout = NULL;
1234 rewritemap_entry *map;
1237 apr_hash_this(hi, NULL, NULL, &val);
1240 if (map->type != MAPTYPE_PRG) {
1243 if (!(map->argv[0]) || !*(map->argv[0]) || map->fpin || map->fpout) {
1247 rc = rewritemap_program_child(p, map->argv[0], map->argv,
1248 map->user, map->group,
1250 if (rc != APR_SUCCESS || fpin == NULL || fpout == NULL) {
1251 ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, APLOGNO(00654)
1252 "mod_rewrite: could not start RewriteMap "
1253 "program %s", map->checkfile);
1265 * +-------------------------------------------------------+
1267 * | Lookup functions
1269 * +-------------------------------------------------------+
1272 static char *lookup_map_txtfile(request_rec *r, const char *file, char *key)
1274 apr_file_t *fp = NULL;
1275 char line[REWRITE_MAX_TXT_MAP_LINE + 1]; /* +1 for \0 */
1276 char *value, *keylast;
1279 if ((rv = apr_file_open(&fp, file, APR_READ|APR_BUFFERED, APR_OS_DEFAULT,
1280 r->pool)) != APR_SUCCESS)
1282 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00655)
1283 "mod_rewrite: can't open text RewriteMap file %s", file);
1287 keylast = key + strlen(key);
1289 while (apr_file_gets(line, sizeof(line), fp) == APR_SUCCESS) {
1292 /* ignore comments and lines starting with whitespaces */
1293 if (*line == '#' || apr_isspace(*line)) {
1299 while (c < keylast && *p == *c && !apr_isspace(*p)) {
1304 /* key doesn't match - ignore. */
1305 if (c != keylast || !apr_isspace(*p)) {
1309 /* jump to the value */
1310 while (apr_isspace(*p)) {
1314 /* no value? ignore */
1319 /* extract the value and return. */
1321 while (*p && !apr_isspace(*p)) {
1324 value = apr_pstrmemdup(r->pool, c, p - c);
1332 static char *lookup_map_dbmfile(request_rec *r, const char *file,
1333 const char *dbmtype, char *key)
1335 apr_dbm_t *dbmfp = NULL;
1341 if ((rv = apr_dbm_open_ex(&dbmfp, dbmtype, file, APR_DBM_READONLY,
1342 APR_OS_DEFAULT, r->pool)) != APR_SUCCESS)
1344 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00656)
1345 "mod_rewrite: can't open DBM RewriteMap %s", file);
1350 dbmkey.dsize = strlen(key);
1352 if (apr_dbm_fetch(dbmfp, dbmkey, &dbmval) == APR_SUCCESS && dbmval.dptr) {
1353 value = apr_pstrmemdup(r->pool, dbmval.dptr, dbmval.dsize);
1359 apr_dbm_close(dbmfp);
1363 static char *lookup_map_dbd(request_rec *r, char *key, const char *label)
1366 apr_dbd_prepared_t *stmt;
1368 apr_dbd_results_t *res = NULL;
1369 apr_dbd_row_t *row = NULL;
1372 ap_dbd_t *db = dbd_acquire(r);
1375 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02963)
1376 "rewritemap: No db handle available! "
1377 "Check your database access");
1381 stmt = apr_hash_get(db->prepared, label, APR_HASH_KEY_STRING);
1383 rv = apr_dbd_pvselect(db->driver, r->pool, db->handle, &res,
1384 stmt, 0, key, NULL);
1386 errmsg = apr_dbd_error(db->driver, db->handle, rv);
1387 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00657)
1388 "rewritemap: error %s querying for %s", errmsg, key);
1391 while ((rv = apr_dbd_get_row(db->driver, r->pool, res, &row, -1)) == 0) {
1394 ret = apr_pstrdup(r->pool,
1395 apr_dbd_get_entry(db->driver, row, 0));
1398 /* randomise crudely amongst multiple results */
1399 if ((double)rand() < (double)RAND_MAX/(double)n) {
1400 ret = apr_pstrdup(r->pool,
1401 apr_dbd_get_entry(db->driver, row, 0));
1406 errmsg = apr_dbd_error(db->driver, db->handle, rv);
1407 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00658)
1408 "rewritemap: error %s looking up %s", errmsg, key);
1416 /* what's a fair rewritelog level for this? */
1417 rewritelog((r, 3, NULL, "Multiple values found for %s", key));
1422 static char *lookup_map_program(request_rec *r, apr_file_t *fpin,
1423 apr_file_t *fpout, char *key)
1427 apr_size_t i, nbytes, combined_len = 0;
1429 const char *eol = APR_EOL_STR;
1430 apr_size_t eolc = 0;
1432 result_list *buflist = NULL, *curbuf = NULL;
1435 struct iovec iova[2];
1439 /* when `RewriteEngine off' was used in the per-server
1440 * context then the rewritemap-programs were not spawned.
1441 * In this case using such a map (usually in per-dir context)
1442 * is useless because it is not available.
1444 * newlines in the key leave bytes in the pipe and cause
1445 * bad things to happen (next map lookup will use the chars
1446 * after the \n instead of the new key etc etc - in other words,
1447 * the Rewritemap falls out of sync with the requests).
1449 if (fpin == NULL || fpout == NULL || ap_strchr(key, '\n')) {
1454 if (rewrite_mapr_lock_acquire) {
1455 rv = apr_global_mutex_lock(rewrite_mapr_lock_acquire);
1456 if (rv != APR_SUCCESS) {
1457 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00659)
1458 "apr_global_mutex_lock(rewrite_mapr_lock_acquire) "
1460 return NULL; /* Maybe this should be fatal? */
1464 /* write out the request key */
1466 nbytes = strlen(key);
1467 /* XXX: error handling */
1468 apr_file_write_full(fpin, key, nbytes, NULL);
1470 apr_file_write_full(fpin, "\n", nbytes, NULL);
1472 iova[0].iov_base = key;
1473 iova[0].iov_len = strlen(key);
1474 iova[1].iov_base = "\n";
1475 iova[1].iov_len = 1;
1478 /* XXX: error handling */
1479 apr_file_writev_full(fpin, iova, niov, &nbytes);
1482 buf = apr_palloc(r->pool, REWRITE_PRG_MAP_BUF + 1);
1484 /* read in the response value */
1486 apr_file_read(fpout, &c, &nbytes);
1489 while (nbytes == 1 && (i < REWRITE_PRG_MAP_BUF)) {
1490 if (c == eol[eolc]) {
1492 /* remove eol from the buffer */
1495 curbuf->len -= eolc-i;
1506 /* only partial (invalid) eol sequence -> reset the counter */
1511 /* catch binary mode, e.g. on Win32 */
1512 else if (c == '\n') {
1518 apr_file_read(fpout, &c, &nbytes);
1521 /* well, if there wasn't a newline yet, we need to read further */
1522 if (buflist || (nbytes == 1 && !found_nl)) {
1524 curbuf = buflist = apr_palloc(r->pool, sizeof(*buflist));
1527 curbuf->next = apr_palloc(r->pool, sizeof(*buflist));
1528 curbuf = curbuf->next;
1531 curbuf->next = NULL;
1534 curbuf->string = buf;
1537 buf = apr_palloc(r->pool, REWRITE_PRG_MAP_BUF);
1540 if (nbytes == 1 && !found_nl) {
1548 /* concat the stuff */
1552 p = buf = apr_palloc(r->pool, combined_len + 1); /* \0 */
1555 memcpy(p, buflist->string, buflist->len);
1558 buflist = buflist->next;
1567 /* give the lock back */
1568 if (rewrite_mapr_lock_acquire) {
1569 rv = apr_global_mutex_unlock(rewrite_mapr_lock_acquire);
1570 if (rv != APR_SUCCESS) {
1571 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00660)
1572 "apr_global_mutex_unlock(rewrite_mapr_lock_acquire) "
1574 return NULL; /* Maybe this should be fatal? */
1578 /* catch the "failed" case */
1579 if (i == 4 && !strcasecmp(buf, "NULL")) {
1587 * generic map lookup
1589 static char *lookup_map(request_rec *r, char *name, char *key)
1591 rewrite_server_conf *conf;
1592 rewritemap_entry *s;
1597 /* get map configuration */
1598 conf = ap_get_module_config(r->server->module_config, &rewrite_module);
1599 s = apr_hash_get(conf->rewritemaps, name, APR_HASH_KEY_STRING);
1601 /* map doesn't exist */
1608 * Text file map (perhaps random)
1612 rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool);
1613 if (rv != APR_SUCCESS) {
1614 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00661)
1615 "mod_rewrite: can't access text RewriteMap file %s",
1620 value = get_cache_value(s->cachename, st.mtime, key, r->pool);
1622 rewritelog((r, 6, NULL,
1623 "cache lookup FAILED, forcing new map lookup"));
1625 value = lookup_map_txtfile(r, s->datafile, key);
1627 rewritelog((r, 5, NULL, "map lookup FAILED: map=%s[txt] key=%s",
1629 set_cache_value(s->cachename, st.mtime, key, "");
1633 rewritelog((r, 5, NULL,"map lookup OK: map=%s[txt] key=%s -> val=%s",
1635 set_cache_value(s->cachename, st.mtime, key, value);
1638 rewritelog((r,5,NULL,"cache lookup OK: map=%s[txt] key=%s -> val=%s",
1642 if (s->type == MAPTYPE_RND && *value) {
1643 value = select_random_value_part(r, value);
1644 rewritelog((r, 5, NULL, "randomly chosen the subvalue `%s'",value));
1647 return *value ? value : NULL;
1653 rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool);
1654 if (rv != APR_SUCCESS) {
1655 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00662)
1656 "mod_rewrite: can't access DBM RewriteMap file %s",
1659 else if(s->checkfile2 != NULL) {
1662 rv = apr_stat(&st2, s->checkfile2, APR_FINFO_MIN, r->pool);
1663 if (rv != APR_SUCCESS) {
1664 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00663)
1665 "mod_rewrite: can't access DBM RewriteMap "
1666 "file %s", s->checkfile2);
1668 else if(st2.mtime > st.mtime) {
1669 st.mtime = st2.mtime;
1672 if(rv != APR_SUCCESS) {
1676 value = get_cache_value(s->cachename, st.mtime, key, r->pool);
1678 rewritelog((r, 6, NULL,
1679 "cache lookup FAILED, forcing new map lookup"));
1681 value = lookup_map_dbmfile(r, s->datafile, s->dbmtype, key);
1683 rewritelog((r, 5, NULL, "map lookup FAILED: map=%s[dbm] key=%s",
1685 set_cache_value(s->cachename, st.mtime, key, "");
1689 rewritelog((r, 5, NULL, "map lookup OK: map=%s[dbm] key=%s -> "
1690 "val=%s", name, key, value));
1692 set_cache_value(s->cachename, st.mtime, key, value);
1696 rewritelog((r, 5, NULL, "cache lookup OK: map=%s[dbm] key=%s -> val=%s",
1698 return *value ? value : NULL;
1701 * SQL map without cache
1704 value = lookup_map_dbd(r, key, s->dbdq);
1706 rewritelog((r, 5, NULL, "SQL map lookup FAILED: map %s key=%s",
1711 rewritelog((r, 5, NULL, "SQL map lookup OK: map %s key=%s, val=%s",
1717 * SQL map with cache
1719 case MAPTYPE_DBD_CACHE:
1720 value = get_cache_value(s->cachename, 0, key, r->pool);
1722 rewritelog((r, 6, NULL,
1723 "cache lookup FAILED, forcing new map lookup"));
1725 value = lookup_map_dbd(r, key, s->dbdq);
1727 rewritelog((r, 5, NULL, "SQL map lookup FAILED: map %s key=%s",
1729 set_cache_value(s->cachename, 0, key, "");
1733 rewritelog((r, 5, NULL, "SQL map lookup OK: map %s key=%s, val=%s",
1736 set_cache_value(s->cachename, 0, key, value);
1740 rewritelog((r, 5, NULL, "cache lookup OK: map=%s[SQL] key=%s, val=%s",
1742 return *value ? value : NULL;
1748 value = lookup_map_program(r, s->fpin, s->fpout, key);
1750 rewritelog((r, 5,NULL,"map lookup FAILED: map=%s key=%s", name,
1755 rewritelog((r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s",
1763 value = s->func(r, key);
1765 rewritelog((r, 5,NULL,"map lookup FAILED: map=%s key=%s", name,
1770 rewritelog((r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s",
1779 * lookup a HTTP header and set VARY note
1781 static const char *lookup_header(const char *name, rewrite_ctx *ctx)
1783 const char *val = apr_table_get(ctx->r->headers_in, name);
1786 ctx->vary_this = ctx->vary_this
1787 ? apr_pstrcat(ctx->r->pool, ctx->vary_this, ", ",
1789 : apr_pstrdup(ctx->r->pool, name);
1796 * lookahead helper function
1797 * Determine the correct URI path in perdir context
1799 static APR_INLINE const char *la_u(rewrite_ctx *ctx)
1801 rewrite_perdir_conf *conf;
1803 if (*ctx->uri == '/') {
1807 conf = ap_get_module_config(ctx->r->per_dir_config, &rewrite_module);
1809 return apr_pstrcat(ctx->r->pool, conf->baseurl
1810 ? conf->baseurl : conf->directory,
1815 * generic variable lookup
1817 static char *lookup_variable(char *var, rewrite_ctx *ctx)
1820 request_rec *r = ctx->r;
1821 apr_size_t varlen = strlen(var);
1830 /* fast tests for variable length variables (sic) first */
1831 if (var[3] == ':') {
1832 if (var[4] && !strncasecmp(var, "ENV", 3)) {
1834 result = apr_table_get(r->notes, var);
1837 result = apr_table_get(r->subprocess_env, var);
1840 result = getenv(var);
1843 else if (var[4] && !strncasecmp(var, "SSL", 3) && rewrite_ssl_lookup) {
1844 result = rewrite_ssl_lookup(r->pool, r->server, r->connection, r,
1848 else if (var[4] == ':') {
1853 if (!strncasecmp(var, "HTTP", 4)) {
1854 result = lookup_header(var+5, ctx);
1856 else if (!strncasecmp(var, "LA-U", 4)) {
1857 if (ctx->uri && subreq_ok(r)) {
1858 path = ctx->perdir ? la_u(ctx) : ctx->uri;
1859 rr = ap_sub_req_lookup_uri(path, r, NULL);
1861 result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx));
1863 ap_destroy_sub_req(rr);
1865 rewritelog((r, 5, ctx->perdir, "lookahead: path=%s var=%s "
1866 "-> val=%s", path, var+5, result));
1868 return (char *)result;
1871 else if (!strncasecmp(var, "LA-F", 4)) {
1872 if (ctx->uri && subreq_ok(r)) {
1874 if (ctx->perdir && *path == '/') {
1875 /* sigh, the user wants a file based subrequest, but
1876 * we can't do one, since we don't know what the file
1877 * path is! In this case behave like LA-U.
1879 rr = ap_sub_req_lookup_uri(path, r, NULL);
1883 rewrite_perdir_conf *conf;
1885 conf = ap_get_module_config(r->per_dir_config,
1888 path = apr_pstrcat(r->pool, conf->directory, path,
1892 rr = ap_sub_req_lookup_file(path, r, NULL);
1896 result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx));
1898 ap_destroy_sub_req(rr);
1900 rewritelog((r, 5, ctx->perdir, "lookahead: path=%s var=%s "
1901 "-> val=%s", path, var+5, result));
1903 return (char *)result;
1909 /* well, do it the hard way */
1913 /* can't do this above, because of the getenv call */
1914 ap_str_toupper(var);
1918 if (!strcmp(var, "TIME")) {
1919 apr_time_exp_lt(&tm, apr_time_now());
1920 result = apr_psprintf(r->pool, "%04d%02d%02d%02d%02d%02d",
1921 tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
1922 tm.tm_hour, tm.tm_min, tm.tm_sec);
1923 rewritelog((r, 1, ctx->perdir, "RESULT='%s'", result));
1924 return (char *)result;
1926 else if (!strcmp(var, "IPV6")) {
1929 apr_sockaddr_t *addr = r->useragent_addr;
1930 flag = (addr->family == AF_INET6 &&
1931 !IN6_IS_ADDR_V4MAPPED((struct in6_addr *)addr->ipaddr_ptr));
1932 rewritelog((r, 1, ctx->perdir, "IPV6='%s'", flag ? "on" : "off"));
1934 rewritelog((r, 1, ctx->perdir, "IPV6='off' (IPv6 is not enabled)"));
1936 result = (flag ? "on" : "off");
1941 if (!strcmp(var, "HTTPS")) {
1942 int flag = rewrite_is_https && rewrite_is_https(r->connection);
1943 return apr_pstrdup(r->pool, flag ? "on" : "off");
1950 if (!strcmp(var, "TIME_DAY")) {
1951 apr_time_exp_lt(&tm, apr_time_now());
1952 return apr_psprintf(r->pool, "%02d", tm.tm_mday);
1957 if (!strcmp(var, "TIME_SEC")) {
1958 apr_time_exp_lt(&tm, apr_time_now());
1959 return apr_psprintf(r->pool, "%02d", tm.tm_sec);
1964 if (!strcmp(var, "TIME_MIN")) {
1965 apr_time_exp_lt(&tm, apr_time_now());
1966 return apr_psprintf(r->pool, "%02d", tm.tm_min);
1971 if (!strcmp(var, "TIME_MON")) {
1972 apr_time_exp_lt(&tm, apr_time_now());
1973 return apr_psprintf(r->pool, "%02d", tm.tm_mon+1);
1982 if (var[8] == 'Y' && !strcmp(var, "TIME_WDAY")) {
1983 apr_time_exp_lt(&tm, apr_time_now());
1984 return apr_psprintf(r->pool, "%d", tm.tm_wday);
1986 else if (!strcmp(var, "TIME_YEAR")) {
1987 apr_time_exp_lt(&tm, apr_time_now());
1988 return apr_psprintf(r->pool, "%04d", tm.tm_year+1900);
1993 if (!strcmp(var, "IS_SUBREQ")) {
1994 result = (r->main ? "true" : "false");
1999 if (!strcmp(var, "PATH_INFO")) {
2000 result = r->path_info;
2005 if (!strcmp(var, "AUTH_TYPE")) {
2006 result = r->ap_auth_type;
2011 if (!strcmp(var, "HTTP_HOST")) {
2012 result = lookup_header("Host", ctx);
2017 if (!strcmp(var, "TIME_HOUR")) {
2018 apr_time_exp_lt(&tm, apr_time_now());
2019 return apr_psprintf(r->pool, "%02d", tm.tm_hour);
2028 if (!strcmp(var, "SERVER_NAME")) {
2029 result = ap_get_server_name_for_url(r);
2034 if (*var == 'R' && !strcmp(var, "REMOTE_ADDR")) {
2035 result = r->useragent_ip;
2037 else if (!strcmp(var, "SERVER_ADDR")) {
2038 result = r->connection->local_ip;
2043 if (*var == 'H' && !strcmp(var, "HTTP_ACCEPT")) {
2044 result = lookup_header("Accept", ctx);
2046 else if (!strcmp(var, "THE_REQUEST")) {
2047 result = r->the_request;
2052 if (!strcmp(var, "API_VERSION")) {
2053 return apr_psprintf(r->pool, "%d:%d",
2054 MODULE_MAGIC_NUMBER_MAJOR,
2055 MODULE_MAGIC_NUMBER_MINOR);
2060 if (!strcmp(var, "HTTP_COOKIE")) {
2061 result = lookup_header("Cookie", ctx);
2066 if (*var == 'S' && !strcmp(var, "SERVER_PORT")) {
2067 return apr_psprintf(r->pool, "%u", ap_get_server_port(r));
2069 else if (var[7] == 'H' && !strcmp(var, "REMOTE_HOST")) {
2070 result = ap_get_remote_host(r->connection,r->per_dir_config,
2073 else if (!strcmp(var, "REMOTE_PORT")) {
2074 return apr_itoa(r->pool, r->useragent_addr->port);
2079 if (*var == 'R' && !strcmp(var, "REMOTE_USER")) {
2082 else if (!strcmp(var, "SCRIPT_USER")) {
2083 result = "<unknown>";
2084 if (r->finfo.valid & APR_FINFO_USER) {
2085 apr_uid_name_get((char **)&result, r->finfo.user,
2092 if (!strcmp(var, "REQUEST_URI")) {
2102 if (!strcmp(var, "SCRIPT_GROUP")) {
2103 result = "<unknown>";
2104 if (r->finfo.valid & APR_FINFO_GROUP) {
2105 apr_gid_name_get((char **)&result, r->finfo.group,
2112 if (!strcmp(var, "REMOTE_IDENT")) {
2113 result = ap_get_remote_logname(r);
2118 if (!strcmp(var, "HTTP_REFERER")) {
2119 result = lookup_header("Referer", ctx);
2124 if (!strcmp(var, "QUERY_STRING")) {
2130 if (!strcmp(var, "SERVER_ADMIN")) {
2131 result = r->server->server_admin;
2138 if (!strcmp(var, "DOCUMENT_ROOT")) {
2139 result = ap_document_root(r);
2144 if (*var == 'H' && !strcmp(var, "HTTP_FORWARDED")) {
2145 result = lookup_header("Forwarded", ctx);
2147 else if (*var == 'C' && !strcmp(var, "CONTEXT_PREFIX")) {
2148 result = ap_context_prefix(r);
2150 else if (var[8] == 'M' && !strcmp(var, "REQUEST_METHOD")) {
2153 else if (!strcmp(var, "REQUEST_SCHEME")) {
2154 result = ap_http_scheme(r);
2161 if (!strcmp(var, "HTTP_USER_AGENT")) {
2162 result = lookup_header("User-Agent", ctx);
2167 if (!strcmp(var, "SCRIPT_FILENAME")) {
2168 result = r->filename; /* same as request_filename (16) */
2173 if (!strcmp(var, "SERVER_PROTOCOL")) {
2174 result = r->protocol;
2179 if (!strcmp(var, "SERVER_SOFTWARE")) {
2180 result = ap_get_server_banner();
2187 if (*var == 'C' && !strcmp(var, "CONN_REMOTE_ADDR")) {
2188 result = r->connection->client_ip;
2190 else if (!strcmp(var, "REQUEST_FILENAME")) {
2191 result = r->filename; /* same as script_filename (15) */
2196 if (!strcmp(var, "HTTP_PROXY_CONNECTION")) {
2197 result = lookup_header("Proxy-Connection", ctx);
2199 else if (!strcmp(var, "CONTEXT_DOCUMENT_ROOT")) {
2200 result = ap_context_document_root(r);
2206 return apr_pstrdup(r->pool, result ? result : "");
2211 * +-------------------------------------------------------+
2213 * | Expansion functions
2215 * +-------------------------------------------------------+
2219 * Bracketed expression handling
2220 * s points after the opening bracket
2222 static APR_INLINE char *find_closing_curly(char *s)
2226 for (depth = 1; *s; ++s) {
2227 if (*s == RIGHT_CURLY && --depth == 0) {
2230 else if (*s == LEFT_CURLY) {
2238 static APR_INLINE char *find_char_in_curlies(char *s, int c)
2242 for (depth = 1; *s; ++s) {
2243 if (*s == c && depth == 1) {
2246 else if (*s == RIGHT_CURLY && --depth == 0) {
2249 else if (*s == LEFT_CURLY) {
2257 /* perform all the expansions on the input string
2258 * putting the result into a new string
2260 * for security reasons this expansion must be performed in a
2261 * single pass, otherwise an attacker can arrange for the result
2262 * of an earlier expansion to include expansion specifiers that
2263 * are interpreted by a later expansion, producing results that
2264 * were not intended by the administrator.
2266 static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry)
2268 result_list *result, *current;
2269 result_list sresult[SMALL_EXPANSION];
2271 apr_size_t span, inputlen, outlen;
2273 apr_pool_t *pool = ctx->r->pool;
2275 span = strcspn(input, "\\$%");
2276 inputlen = strlen(input);
2279 if (inputlen == span) {
2280 return apr_pstrmemdup(pool, input, inputlen);
2283 /* well, actually something to do */
2284 result = current = &(sresult[spc++]);
2287 current->next = NULL;
2288 current->string = input;
2289 current->len = span;
2292 /* loop for specials */
2294 /* prepare next entry */
2296 current->next = (spc < SMALL_EXPANSION)
2298 : (result_list *)apr_palloc(pool,
2299 sizeof(result_list));
2300 current = current->next;
2301 current->next = NULL;
2305 /* escaped character */
2310 current->string = p;
2314 current->string = ++p;
2319 /* variable or map lookup */
2320 else if (p[1] == '{') {
2323 endp = find_closing_curly(p+2);
2326 current->string = p;
2331 /* variable lookup */
2332 else if (*p == '%') {
2333 p = lookup_variable(apr_pstrmemdup(pool, p+2, endp-p-2), ctx);
2336 current->len = span;
2337 current->string = p;
2343 else { /* *p == '$' */
2347 * To make rewrite maps useful, the lookup key and
2348 * default values must be expanded, so we make
2349 * recursive calls to do the work. For security
2350 * reasons we must never expand a string that includes
2351 * verbatim data from the network. The recursion here
2352 * isn't a problem because the result of expansion is
2353 * only passed to lookup_map() so it cannot be
2354 * re-expanded, only re-looked-up. Another way of
2355 * looking at it is that the recursion is entirely
2356 * driven by the syntax of the nested curly brackets.
2359 key = find_char_in_curlies(p+2, ':');
2362 current->string = p;
2369 map = apr_pstrmemdup(pool, p+2, endp-p-2);
2370 key = map + (key-p-2);
2372 dflt = find_char_in_curlies(key, '|');
2377 /* reuse of key variable as result */
2378 key = lookup_map(ctx->r, map, do_expand(key, ctx, entry));
2380 if (!key && dflt && *dflt) {
2381 key = do_expand(dflt, ctx, entry);
2386 current->len = span;
2387 current->string = key;
2397 else if (apr_isdigit(p[1])) {
2399 backrefinfo *bri = (*p == '$') ? &ctx->briRR : &ctx->briRC;
2401 /* see ap_pregsub() in server/util.c */
2402 if (bri->source && n < AP_MAX_REG_MATCH
2403 && bri->regmatch[n].rm_eo > bri->regmatch[n].rm_so) {
2404 span = bri->regmatch[n].rm_eo - bri->regmatch[n].rm_so;
2405 if (entry && (entry->flags & RULEFLAG_ESCAPEBACKREF)) {
2406 /* escape the backreference */
2408 tmp = apr_pstrmemdup(pool, bri->source + bri->regmatch[n].rm_so, span);
2409 tmp2 = escape_backref(pool, tmp, entry->escapes, entry->flags & RULEFLAG_ESCAPENOPLUS);
2410 rewritelog((ctx->r, 5, ctx->perdir, "escaping backreference '%s' to '%s'",
2413 current->len = span = strlen(tmp2);
2414 current->string = tmp2;
2416 current->len = span;
2417 current->string = bri->source + bri->regmatch[n].rm_so;
2426 /* not for us, just copy it */
2429 current->string = p++;
2433 /* check the remainder */
2434 if (*p && (span = strcspn(p, "\\$%")) > 0) {
2436 current->next = (spc < SMALL_EXPANSION)
2438 : (result_list *)apr_palloc(pool,
2439 sizeof(result_list));
2440 current = current->next;
2441 current->next = NULL;
2444 current->len = span;
2445 current->string = p;
2450 } while (p < input+inputlen);
2452 /* assemble result */
2453 c = p = apr_palloc(pool, outlen + 1); /* don't forget the \0 */
2456 ap_assert(c+result->len <= p+outlen); /* XXX: can be removed after
2457 * extensive testing and
2460 memcpy(c, result->string, result->len);
2463 result = result->next;
2472 * perform all the expansions on the environment variables
2474 static void do_expand_env(data_item *env, rewrite_ctx *ctx)
2479 name = do_expand(env->data, ctx, NULL);
2482 apr_table_unset(ctx->r->subprocess_env, name);
2483 rewritelog((ctx->r, 5, NULL, "unsetting env variable '%s'", name));
2486 if ((val = ap_strchr(name, ':')) != NULL) {
2492 apr_table_set(ctx->r->subprocess_env, name, val);
2493 rewritelog((ctx->r, 5, NULL, "setting env variable '%s' to '%s'",
2504 * perform all the expansions on the cookies
2506 * TODO: use cached time similar to how logging does it
2508 static void add_cookie(request_rec *r, char *s)
2520 /* long-standing default, but can't use ':' in a cookie */
2521 const char *sep = ":";
2523 /* opt-in to ; separator if first character is a ; */
2524 if (s && *s == ';') {
2529 var = apr_strtok(s, sep, &tok_cntx);
2530 val = apr_strtok(NULL, sep, &tok_cntx);
2531 domain = apr_strtok(NULL, sep, &tok_cntx);
2533 if (var && val && domain) {
2534 request_rec *rmain = r;
2538 while (rmain->main) {
2539 rmain = rmain->main;
2542 notename = apr_pstrcat(rmain->pool, var, "_rewrite", NULL);
2543 apr_pool_userdata_get(&data, notename, rmain->pool);
2545 char *exp_time = NULL;
2547 expires = apr_strtok(NULL, sep, &tok_cntx);
2548 path = expires ? apr_strtok(NULL, sep, &tok_cntx) : NULL;
2549 secure = path ? apr_strtok(NULL, sep, &tok_cntx) : NULL;
2550 httponly = secure ? apr_strtok(NULL, sep, &tok_cntx) : NULL;
2556 exp_min = atol(expires);
2558 apr_time_exp_gmt(&tms, r->request_time
2559 + apr_time_from_sec((60 * exp_min)));
2560 exp_time = apr_psprintf(r->pool, "%s, %.2d-%s-%.4d "
2561 "%.2d:%.2d:%.2d GMT",
2562 apr_day_snames[tms.tm_wday],
2564 apr_month_snames[tms.tm_mon],
2566 tms.tm_hour, tms.tm_min, tms.tm_sec);
2570 cookie = apr_pstrcat(rmain->pool,
2572 "; path=", path ? path : "/",
2573 "; domain=", domain,
2574 expires ? (exp_time ? "; expires=" : "")
2576 expires ? (exp_time ? exp_time : "")
2578 (secure && (!ap_casecmpstr(secure, "true")
2579 || !strcmp(secure, "1")
2580 || !ap_casecmpstr(secure,
2583 (httponly && (!ap_casecmpstr(httponly, "true")
2584 || !strcmp(httponly, "1")
2585 || !ap_casecmpstr(httponly,
2587 "; HttpOnly" : NULL,
2590 apr_table_addn(rmain->err_headers_out, "Set-Cookie", cookie);
2591 apr_pool_userdata_set("set", notename, NULL, rmain->pool);
2592 rewritelog((rmain, 5, NULL, "setting cookie '%s'", cookie));
2595 rewritelog((rmain, 5, NULL, "skipping already set cookie '%s'",
2603 static void do_expand_cookie(data_item *cookie, rewrite_ctx *ctx)
2606 add_cookie(ctx->r, do_expand(cookie->data, ctx, NULL));
2607 cookie = cookie->next;
2615 * Expand tilde-paths (/~user) through Unix /etc/passwd
2616 * database information (or other OS-specific database)
2618 static char *expand_tildepaths(request_rec *r, char *uri)
2620 if (uri && *uri == '/' && uri[1] == '~') {
2624 while (*p && *p != '/') {
2631 user = apr_pstrmemdup(r->pool, user, p-user);
2632 if (apr_uid_homepath_get(&homedir, user, r->pool) == APR_SUCCESS) {
2634 /* reuse of user variable */
2635 user = homedir + strlen(homedir) - 1;
2636 if (user >= homedir && *user == '/') {
2640 return apr_pstrcat(r->pool, homedir, p, NULL);
2651 #endif /* if APR_HAS_USER */
2655 * +-------------------------------------------------------+
2657 * | rewriting lockfile support
2659 * +-------------------------------------------------------+
2662 static apr_status_t rewritelock_create(server_rec *s, apr_pool_t *p)
2666 /* create the lockfile */
2667 /* XXX See if there are any rewrite map programs before creating
2670 rc = ap_global_mutex_create(&rewrite_mapr_lock_acquire, NULL,
2671 rewritemap_mutex_type, NULL, s, p, 0);
2672 if (rc != APR_SUCCESS) {
2679 static apr_status_t rewritelock_remove(void *data)
2681 /* destroy the rewritelock */
2682 if (rewrite_mapr_lock_acquire) {
2683 apr_global_mutex_destroy(rewrite_mapr_lock_acquire);
2684 rewrite_mapr_lock_acquire = NULL;
2691 * +-------------------------------------------------------+
2693 * | configuration directive handling
2695 * +-------------------------------------------------------+
2699 * own command line parser for RewriteRule and RewriteCond,
2700 * which doesn't have the '\\' problem.
2701 * (returns true on error)
2703 * XXX: what an inclined parser. Seems we have to leave it so
2704 * for backwards compat. *sigh*
2706 static char *parseargline(apr_pool_t *p, char *str, char **a1, char **a2, char **a3)
2710 while (apr_isspace(*str)) {
2715 * determine first argument
2717 quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
2720 for (; *str; ++str) {
2721 if ((apr_isspace(*str) && !quote) || (*str == quote)) {
2724 if (*str == '\\' && apr_isspace(str[1])) {
2731 return "bad argument line: at least two arguments required";
2735 while (apr_isspace(*str)) {
2740 * determine second argument
2742 quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
2745 for (; *str; ++str) {
2746 if ((apr_isspace(*str) && !quote) || (*str == quote)) {
2749 if (*str == '\\' && apr_isspace(str[1])) {
2756 *a3 = NULL; /* 3rd argument is optional */
2761 while (apr_isspace(*str)) {
2766 *a3 = NULL; /* 3rd argument is still optional */
2771 * determine third argument
2773 quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
2775 for (; *str; ++str) {
2776 if ((apr_isspace(*str) && !quote) || (*str == quote)) {
2779 if (*str == '\\' && apr_isspace(str[1])) {
2787 return apr_psprintf(p, "bad flag delimiters: third argument must begin "
2788 "with '[' but found '%c' - too many arguments or rogue "
2789 "whitespace?", **a3);
2791 else if ((*a3)[strlen(*a3)-1] != ']') {
2792 return apr_psprintf(p, "bad flag delimiters: third argument must end "
2793 "with ']' but found '%c' - unintended whitespace within the "
2794 "flags definition?", (*a3)[strlen(*a3)-1]);
2799 static void *config_server_create(apr_pool_t *p, server_rec *s)
2801 rewrite_server_conf *a;
2803 a = (rewrite_server_conf *)apr_pcalloc(p, sizeof(rewrite_server_conf));
2805 a->state = ENGINE_DISABLED;
2806 a->options = OPTION_NONE;
2807 a->rewritemaps = apr_hash_make(p);
2808 a->rewriteconds = apr_array_make(p, 2, sizeof(rewritecond_entry));
2809 a->rewriterules = apr_array_make(p, 2, sizeof(rewriterule_entry));
2815 static void *config_server_merge(apr_pool_t *p, void *basev, void *overridesv)
2817 rewrite_server_conf *a, *base, *overrides;
2819 a = (rewrite_server_conf *)apr_pcalloc(p,
2820 sizeof(rewrite_server_conf));
2821 base = (rewrite_server_conf *)basev;
2822 overrides = (rewrite_server_conf *)overridesv;
2824 a->state = (overrides->state_set == 0) ? base->state : overrides->state;
2825 a->state_set = overrides->state_set || base->state_set;
2826 a->options = (overrides->options_set == 0) ? base->options : overrides->options;
2827 a->options_set = overrides->options_set || base->options_set;
2829 a->server = overrides->server;
2831 if (a->options & OPTION_INHERIT ||
2832 (base->options & OPTION_INHERIT_DOWN &&
2833 !(a->options & OPTION_IGNORE_INHERIT))) {
2835 * local directives override
2836 * and anything else is inherited
2838 a->rewritemaps = apr_hash_overlay(p, overrides->rewritemaps,
2840 a->rewriteconds = apr_array_append(p, overrides->rewriteconds,
2841 base->rewriteconds);
2842 a->rewriterules = apr_array_append(p, overrides->rewriterules,
2843 base->rewriterules);
2845 else if (a->options & OPTION_INHERIT_BEFORE ||
2846 (base->options & OPTION_INHERIT_DOWN_BEFORE &&
2847 !(a->options & OPTION_IGNORE_INHERIT))) {
2849 * local directives override
2850 * and anything else is inherited (preserving order)
2852 a->rewritemaps = apr_hash_overlay(p, base->rewritemaps,
2853 overrides->rewritemaps);
2854 a->rewriteconds = apr_array_append(p, base->rewriteconds,
2855 overrides->rewriteconds);
2856 a->rewriterules = apr_array_append(p, base->rewriterules,
2857 overrides->rewriterules);
2861 * local directives override
2862 * and anything else gets defaults
2864 a->rewritemaps = overrides->rewritemaps;
2865 a->rewriteconds = overrides->rewriteconds;
2866 a->rewriterules = overrides->rewriterules;
2872 static void *config_perdir_create(apr_pool_t *p, char *path)
2874 rewrite_perdir_conf *a;
2876 a = (rewrite_perdir_conf *)apr_pcalloc(p, sizeof(rewrite_perdir_conf));
2878 a->state = ENGINE_DISABLED;
2879 a->options = OPTION_NONE;
2881 a->rewriteconds = apr_array_make(p, 2, sizeof(rewritecond_entry));
2882 a->rewriterules = apr_array_make(p, 2, sizeof(rewriterule_entry));
2885 a->directory = NULL;
2888 /* make sure it has a trailing slash */
2889 if (path[strlen(path)-1] == '/') {
2890 a->directory = apr_pstrdup(p, path);
2893 a->directory = apr_pstrcat(p, path, "/", NULL);
2900 static void *config_perdir_merge(apr_pool_t *p, void *basev, void *overridesv)
2902 rewrite_perdir_conf *a, *base, *overrides;
2904 a = (rewrite_perdir_conf *)apr_pcalloc(p,
2905 sizeof(rewrite_perdir_conf));
2906 base = (rewrite_perdir_conf *)basev;
2907 overrides = (rewrite_perdir_conf *)overridesv;
2909 a->state = (overrides->state_set == 0) ? base->state : overrides->state;
2910 a->state_set = overrides->state_set || base->state_set;
2911 a->options = (overrides->options_set == 0) ? base->options : overrides->options;
2912 a->options_set = overrides->options_set || base->options_set;
2914 if (a->options & OPTION_MERGEBASE) {
2915 a->baseurl = (overrides->baseurl_set == 0) ? base->baseurl : overrides->baseurl;
2916 a->baseurl_set = overrides->baseurl_set || base->baseurl_set;
2919 a->baseurl = overrides->baseurl;
2922 a->directory = overrides->directory;
2924 if (a->options & OPTION_INHERIT ||
2925 (base->options & OPTION_INHERIT_DOWN &&
2926 !(a->options & OPTION_IGNORE_INHERIT))) {
2927 a->rewriteconds = apr_array_append(p, overrides->rewriteconds,
2928 base->rewriteconds);
2929 a->rewriterules = apr_array_append(p, overrides->rewriterules,
2930 base->rewriterules);
2932 else if (a->options & OPTION_INHERIT_BEFORE ||
2933 (base->options & OPTION_INHERIT_DOWN_BEFORE &&
2934 !(a->options & OPTION_IGNORE_INHERIT))) {
2935 a->rewriteconds = apr_array_append(p, base->rewriteconds,
2936 overrides->rewriteconds);
2937 a->rewriterules = apr_array_append(p, base->rewriterules,
2938 overrides->rewriterules);
2941 a->rewriteconds = overrides->rewriteconds;
2942 a->rewriterules = overrides->rewriterules;
2948 static const char *cmd_rewriteengine(cmd_parms *cmd,
2949 void *in_dconf, int flag)
2951 rewrite_perdir_conf *dconf = in_dconf;
2952 rewrite_server_conf *sconf;
2954 sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
2956 /* server command? set both global scope and base directory scope */
2957 if (cmd->path == NULL) {
2958 sconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
2959 sconf->state_set = 1;
2960 dconf->state = sconf->state;
2961 dconf->state_set = 1;
2963 /* directory command? set directory scope only */
2965 dconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
2966 dconf->state_set = 1;
2972 static const char *cmd_rewriteoptions(cmd_parms *cmd,
2973 void *in_dconf, const char *option)
2978 char *w = ap_getword_conf(cmd->temp_pool, &option);
2980 if (!strcasecmp(w, "inherit")) {
2981 options |= OPTION_INHERIT;
2983 else if (!strcasecmp(w, "inheritbefore")) {
2984 options |= OPTION_INHERIT_BEFORE;
2986 else if (!strcasecmp(w, "inheritdown")) {
2987 options |= OPTION_INHERIT_DOWN;
2989 else if(!strcasecmp(w, "inheritdownbefore")) {
2990 options |= OPTION_INHERIT_DOWN_BEFORE;
2992 else if (!strcasecmp(w, "ignoreinherit")) {
2993 options |= OPTION_IGNORE_INHERIT;
2995 else if (!strcasecmp(w, "allownoslash")) {
2996 options |= OPTION_NOSLASH;
2998 else if (!strncasecmp(w, "MaxRedirects=", 13)) {
2999 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00664)
3000 "RewriteOptions: MaxRedirects option has been "
3001 "removed in favor of the global "
3002 "LimitInternalRecursion directive and will be "
3005 else if (!strcasecmp(w, "allowanyuri")) {
3006 options |= OPTION_ANYURI;
3008 else if (!strcasecmp(w, "mergebase")) {
3009 options |= OPTION_MERGEBASE;
3011 else if (!strcasecmp(w, "ignorecontextinfo")) {
3012 options |= OPTION_IGNORE_CONTEXT_INFO;
3015 return apr_pstrcat(cmd->pool, "RewriteOptions: unknown option '",
3020 /* server command? set both global scope and base directory scope */
3021 if (cmd->path == NULL) { /* is server command */
3022 rewrite_perdir_conf *dconf = in_dconf;
3023 rewrite_server_conf *sconf =
3024 ap_get_module_config(cmd->server->module_config,
3027 sconf->options |= options;
3028 sconf->options_set = 1;
3029 dconf->options |= options;
3030 dconf->options_set = 1;
3032 /* directory command? set directory scope only */
3033 else { /* is per-directory command */
3034 rewrite_perdir_conf *dconf = in_dconf;
3036 dconf->options |= options;
3037 dconf->options_set = 1;
3043 static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, const char *a1,
3044 const char *a2, const char *a3)
3046 rewrite_server_conf *sconf;
3047 rewritemap_entry *newmap;
3051 sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
3053 newmap = apr_pcalloc(cmd->pool, sizeof(rewritemap_entry));
3055 if (strncasecmp(a2, "txt:", 4) == 0) {
3056 if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) {
3057 return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ",
3061 newmap->type = MAPTYPE_TXT;
3062 newmap->datafile = fname;
3063 newmap->checkfile = fname;
3064 newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
3065 (void *)cmd->server, a1);
3067 else if (strncasecmp(a2, "rnd:", 4) == 0) {
3068 if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) {
3069 return apr_pstrcat(cmd->pool, "RewriteMap: bad path to rnd map: ",
3073 newmap->type = MAPTYPE_RND;
3074 newmap->datafile = fname;
3075 newmap->checkfile = fname;
3076 newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
3077 (void *)cmd->server, a1);
3079 else if (strncasecmp(a2, "dbm", 3) == 0) {
3082 newmap->type = MAPTYPE_DBM;
3084 newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
3085 (void *)cmd->server, a1);
3088 newmap->dbmtype = "default";
3091 else if (a2[3] == '=') {
3092 const char *colon = ap_strchr_c(a2 + 4, ':');
3095 newmap->dbmtype = apr_pstrndup(cmd->pool, a2 + 4,
3096 colon - (a2 + 3) - 1);
3102 return apr_pstrcat(cmd->pool, "RewriteMap: bad map:",
3106 if ((newmap->datafile = ap_server_root_relative(cmd->pool,
3108 return apr_pstrcat(cmd->pool, "RewriteMap: bad path to dbm map: ",
3112 rv = apr_dbm_get_usednames_ex(cmd->pool, newmap->dbmtype,
3113 newmap->datafile, &newmap->checkfile,
3114 &newmap->checkfile2);
3115 if (rv != APR_SUCCESS) {
3116 return apr_pstrcat(cmd->pool, "RewriteMap: dbm type ",
3117 newmap->dbmtype, " is invalid", NULL);
3120 else if ((strncasecmp(a2, "dbd:", 4) == 0)
3121 || (strncasecmp(a2, "fastdbd:", 8) == 0)) {
3122 if (dbd_prepare == NULL) {
3123 return "RewriteMap types dbd and fastdbd require mod_dbd!";
3125 if ((a2[0] == 'd') || (a2[0] == 'D')) {
3126 newmap->type = MAPTYPE_DBD;
3130 newmap->type = MAPTYPE_DBD_CACHE;
3132 newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
3133 (void *)cmd->server, a1);
3136 dbd_prepare(cmd->server, fname, newmap->dbdq);
3138 else if (strncasecmp(a2, "prg:", 4) == 0) {
3139 apr_tokenize_to_argv(a2 + 4, &newmap->argv, cmd->pool);
3141 fname = newmap->argv[0];
3142 if ((newmap->argv[0] = ap_server_root_relative(cmd->pool,
3144 return apr_pstrcat(cmd->pool, "RewriteMap: bad path to prg map: ",
3148 newmap->type = MAPTYPE_PRG;
3149 newmap->checkfile = newmap->argv[0];
3152 newmap->user = apr_strtok(apr_pstrdup(cmd->pool, a3), ":", &tok_cntx);
3153 newmap->group = apr_strtok(NULL, ":", &tok_cntx);
3156 else if (strncasecmp(a2, "int:", 4) == 0) {
3157 newmap->type = MAPTYPE_INT;
3158 newmap->func = (char *(*)(request_rec *,char *))
3159 apr_hash_get(mapfunc_hash, a2+4, strlen(a2+4));
3160 if (newmap->func == NULL) {
3161 return apr_pstrcat(cmd->pool, "RewriteMap: internal map not found:",
3166 if ((fname = ap_server_root_relative(cmd->pool, a2)) == NULL) {
3167 return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ",
3171 newmap->type = MAPTYPE_TXT;
3172 newmap->datafile = fname;
3173 newmap->checkfile = fname;
3174 newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
3175 (void *)cmd->server, a1);
3178 if (newmap->checkfile
3179 && (apr_stat(&st, newmap->checkfile, APR_FINFO_MIN,
3180 cmd->pool) != APR_SUCCESS)) {
3181 return apr_pstrcat(cmd->pool,
3182 "RewriteMap: file for map ", a1,
3183 " not found:", newmap->checkfile, NULL);
3186 apr_hash_set(sconf->rewritemaps, a1, APR_HASH_KEY_STRING, newmap);
3191 static const char *cmd_rewritebase(cmd_parms *cmd, void *in_dconf,
3194 rewrite_perdir_conf *dconf = in_dconf;
3196 if (cmd->path == NULL || dconf == NULL) {
3197 return "RewriteBase: only valid in per-directory config files";
3199 if (a1[0] == '\0') {
3200 return "RewriteBase: empty URL not allowed";
3203 return "RewriteBase: argument is not a valid URL";
3206 dconf->baseurl = a1;
3207 dconf->baseurl_set = 1;
3213 * generic lexer for RewriteRule and RewriteCond flags.
3214 * The parser will be passed in as a function pointer
3215 * and called if a flag was found
3217 static const char *cmd_parseflagfield(apr_pool_t *p, void *cfg, char *key,
3218 const char *(*parse)(apr_pool_t *,
3222 char *val, *nextp, *endp;
3225 endp = key + strlen(key) - 1;
3226 /* This should have been checked before, but just in case... */
3227 if (*key != '[' || *endp != ']') {
3228 return "bad flag delimiters";
3231 *endp = ','; /* for simpler parsing */
3235 /* skip leading spaces */
3236 while (apr_isspace(*key)) {
3240 if (!*key || (nextp = ap_strchr(key, ',')) == NULL) { /* NULL should not
3246 /* strip trailing spaces */
3248 while (apr_isspace(*endp)) {
3253 /* split key and val */
3254 val = ap_strchr(key, '=');
3262 err = parse(p, cfg, key, val);
3273 static const char *cmd_rewritecond_setflag(apr_pool_t *p, void *_cfg,
3274 char *key, char *val)
3276 rewritecond_entry *cfg = _cfg;
3278 if ( strcasecmp(key, "nocase") == 0
3279 || strcasecmp(key, "NC") == 0 ) {
3280 cfg->flags |= CONDFLAG_NOCASE;
3282 else if ( strcasecmp(key, "ornext") == 0
3283 || strcasecmp(key, "OR") == 0 ) {
3284 cfg->flags |= CONDFLAG_ORNEXT;
3286 else if ( strcasecmp(key, "novary") == 0
3287 || strcasecmp(key, "NV") == 0 ) {
3288 cfg->flags |= CONDFLAG_NOVARY;
3291 return apr_pstrcat(p, "unknown flag '", key, "'", NULL);
3296 static const char *cmd_rewritecond(cmd_parms *cmd, void *in_dconf,
3299 rewrite_perdir_conf *dconf = in_dconf;
3300 char *str = apr_pstrdup(cmd->pool, in_str);
3301 rewrite_server_conf *sconf;
3302 rewritecond_entry *newcond;
3304 char *a1 = NULL, *a2 = NULL, *a3 = NULL;
3307 sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
3309 /* make a new entry in the internal temporary rewrite rule list */
3310 if (cmd->path == NULL) { /* is server command */
3311 newcond = apr_array_push(sconf->rewriteconds);
3313 else { /* is per-directory command */
3314 newcond = apr_array_push(dconf->rewriteconds);
3317 /* parse the argument line ourself
3318 * a1 .. a3 are substrings of str, which is a fresh copy
3319 * of the argument line. So we can use a1 .. a3 without
3320 * copying them again.
3322 if ((err = parseargline(cmd->pool, str, &a1, &a2, &a3))) {
3323 return apr_psprintf(cmd->pool, "RewriteCond: %s "
3324 "(TestString=%s, CondPattern=%s, flags=%s)",
3328 /* arg1: the input string */
3329 newcond->input = a1;
3331 /* arg3: optional flags field
3332 * (this has to be parsed first, because we need to
3333 * know if the regex should be compiled with ICASE!)
3335 newcond->flags = CONDFLAG_NONE;
3337 if ((err = cmd_parseflagfield(cmd->pool, newcond, a3,
3338 cmd_rewritecond_setflag)) != NULL) {
3339 return apr_pstrcat(cmd->pool, "RewriteCond: ", err, NULL);
3343 /* arg2: the pattern */
3344 newcond->pattern = a2;
3346 newcond->flags |= CONDFLAG_NOTMATCH;
3350 /* determine the pattern type */
3351 newcond->ptype = CONDPAT_REGEX;
3352 if (strcasecmp(a1, "expr") == 0) {
3353 newcond->ptype = CONDPAT_AP_EXPR;
3355 else if (*a2 && a2[1]) {
3359 case 'f': newcond->ptype = CONDPAT_FILE_EXISTS; break;
3360 case 's': newcond->ptype = CONDPAT_FILE_SIZE; break;
3361 case 'd': newcond->ptype = CONDPAT_FILE_DIR; break;
3362 case 'x': newcond->ptype = CONDPAT_FILE_XBIT; break;
3363 case 'h': newcond->ptype = CONDPAT_FILE_LINK; break;
3364 case 'L': newcond->ptype = CONDPAT_FILE_LINK; break;
3365 case 'l': newcond->ptype = CONDPAT_FILE_LINK; break;
3366 case 'U': newcond->ptype = CONDPAT_LU_URL; break;
3367 case 'F': newcond->ptype = CONDPAT_LU_FILE; break;
3375 newcond->ptype = CONDPAT_INT_LT;
3377 else if (a2[2] == 'e') {
3379 newcond->ptype = CONDPAT_INT_LE;
3386 newcond->ptype = CONDPAT_INT_GT;
3388 else if (a2[2] == 'e') {
3390 newcond->ptype = CONDPAT_INT_GE;
3397 newcond->ptype = CONDPAT_INT_EQ;
3403 /* Inversion, ensure !-ne == -eq */
3405 newcond->ptype = CONDPAT_INT_EQ;
3406 newcond->flags ^= CONDFLAG_NOTMATCH;
3414 case '>': if (*++a2 == '=')
3415 ++a2, newcond->ptype = CONDPAT_STR_GE;
3417 newcond->ptype = CONDPAT_STR_GT;
3420 case '<': if (*++a2 == '=')
3421 ++a2, newcond->ptype = CONDPAT_STR_LE;
3423 newcond->ptype = CONDPAT_STR_LT;
3426 case '=': newcond->ptype = CONDPAT_STR_EQ;
3427 /* "" represents an empty string */
3428 if (*++a2 == '"' && a2[1] == '"' && !a2[2])
3435 if ((newcond->ptype != CONDPAT_REGEX) &&
3436 (newcond->ptype < CONDPAT_STR_LT || newcond->ptype > CONDPAT_STR_GE) &&
3437 (newcond->flags & CONDFLAG_NOCASE)) {
3438 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00665)
3439 "RewriteCond: NoCase option for non-regex pattern '%s' "
3440 "is not supported and will be ignored. (%s:%d)", a2,
3441 cmd->directive->filename, cmd->directive->line_num);
3442 newcond->flags &= ~CONDFLAG_NOCASE;
3445 newcond->pskip = a2 - newcond->pattern;
3446 newcond->pattern += newcond->pskip;
3448 if (newcond->ptype == CONDPAT_REGEX) {
3449 regexp = ap_pregcomp(cmd->pool, a2,
3450 AP_REG_EXTENDED | ((newcond->flags & CONDFLAG_NOCASE)
3451 ? AP_REG_ICASE : 0));
3453 return apr_pstrcat(cmd->pool, "RewriteCond: cannot compile regular "
3454 "expression '", a2, "'", NULL);
3457 newcond->regexp = regexp;
3459 else if (newcond->ptype == CONDPAT_AP_EXPR) {
3460 unsigned int flags = newcond->flags & CONDFLAG_NOVARY ?
3461 AP_EXPR_FLAG_DONT_VARY : 0;
3462 newcond->expr = ap_expr_parse_cmd(cmd, a2, flags, &err, NULL);
3464 return apr_psprintf(cmd->pool, "RewriteCond: cannot compile "
3465 "expression \"%s\": %s", a2, err);
3471 static const char *cmd_rewriterule_setflag(apr_pool_t *p, void *_cfg,
3472 char *key, char *val)
3474 rewriterule_entry *cfg = _cfg;
3480 if (!*key || !strcasecmp(key, "ackrefescaping")) {
3481 cfg->flags |= RULEFLAG_ESCAPEBACKREF;
3486 else if (!strcasecmp(key, "NP") || !strcasecmp(key, "ackrefernoplus")) {
3487 cfg->flags |= RULEFLAG_ESCAPENOPLUS;
3495 if (!*key || !strcasecmp(key, "hain")) { /* chain */
3496 cfg->flags |= RULEFLAG_CHAIN;
3498 else if (((*key == 'O' || *key == 'o') && !key[1])
3499 || !strcasecmp(key, "ookie")) { /* cookie */
3500 data_item *cp = cfg->cookie;
3503 cp = cfg->cookie = apr_palloc(p, sizeof(*cp));
3509 cp->next = apr_palloc(p, sizeof(*cp));
3522 if (!*key || !strcasecmp(key, "PI") || !strcasecmp(key,"iscardpath")) {
3523 cfg->flags |= (RULEFLAG_DISCARDPATHINFO);
3528 if (!*key || !strcasecmp(key, "nv")) { /* env */
3529 data_item *cp = cfg->env;
3532 cp = cfg->env = apr_palloc(p, sizeof(*cp));
3538 cp->next = apr_palloc(p, sizeof(*cp));
3545 else if (!strcasecmp(key, "nd")) { /* end */
3546 cfg->flags |= RULEFLAG_END;
3555 if (!*key || !strcasecmp(key, "orbidden")) { /* forbidden */
3556 cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB);
3557 cfg->forced_responsecode = HTTP_FORBIDDEN;
3566 if (!*key || !strcasecmp(key, "one")) { /* gone */
3567 cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB);
3568 cfg->forced_responsecode = HTTP_GONE;
3577 if (!*key || !strcasecmp(key, "andler")) { /* handler */
3578 cfg->forced_handler = val;
3586 if (!*key || !strcasecmp(key, "ast")) { /* last */
3587 cfg->flags |= RULEFLAG_LASTRULE;
3596 if (((*key == 'E' || *key == 'e') && !key[1])
3597 || !strcasecmp(key, "oescape")) { /* noescape */
3598 cfg->flags |= RULEFLAG_NOESCAPE;
3600 else if (!*key || !strcasecmp(key, "ext")) { /* next */
3601 cfg->flags |= RULEFLAG_NEWROUND;
3603 cfg->maxrounds = atoi(val);
3607 else if (((*key == 'S' || *key == 's') && !key[1])
3608 || !strcasecmp(key, "osubreq")) { /* nosubreq */
3609 cfg->flags |= RULEFLAG_IGNOREONSUBREQ;
3611 else if (((*key == 'C' || *key == 'c') && !key[1])
3612 || !strcasecmp(key, "ocase")) { /* nocase */
3613 cfg->flags |= RULEFLAG_NOCASE;
3622 if (!*key || !strcasecmp(key, "roxy")) { /* proxy */
3623 cfg->flags |= RULEFLAG_PROXY;
3625 else if (((*key == 'T' || *key == 't') && !key[1])
3626 || !strcasecmp(key, "assthrough")) { /* passthrough */
3627 cfg->flags |= RULEFLAG_PASSTHROUGH;
3636 if ( !strcasecmp(key, "SA")
3637 || !strcasecmp(key, "sappend")) { /* qsappend */
3638 cfg->flags |= RULEFLAG_QSAPPEND;
3639 } else if ( !strcasecmp(key, "SD")
3640 || !strcasecmp(key, "sdiscard") ) { /* qsdiscard */
3641 cfg->flags |= RULEFLAG_QSDISCARD;
3650 if (!*key || !strcasecmp(key, "edirect")) { /* redirect */
3653 cfg->flags |= RULEFLAG_FORCEREDIRECT;
3655 if (strcasecmp(val, "permanent") == 0) {
3656 status = HTTP_MOVED_PERMANENTLY;
3658 else if (strcasecmp(val, "temp") == 0) {
3659 status = HTTP_MOVED_TEMPORARILY;
3661 else if (strcasecmp(val, "seeother") == 0) {
3662 status = HTTP_SEE_OTHER;
3664 else if (apr_isdigit(*val)) {
3666 if (status != HTTP_INTERNAL_SERVER_ERROR) {
3668 ap_index_of_response(HTTP_INTERNAL_SERVER_ERROR);
3670 if (ap_index_of_response(status) == idx) {
3671 return apr_psprintf(p, "invalid HTTP "
3672 "response code '%s' for "
3677 if (!ap_is_HTTP_REDIRECT(status)) {
3678 cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB);
3681 cfg->forced_responsecode = status;
3691 if (!*key || !strcasecmp(key, "kip")) { /* skip */
3692 cfg->skip = atoi(val);
3701 if (!*key || !strcasecmp(key, "ype")) { /* type */
3702 cfg->forced_mimetype = val;
3714 return apr_pstrcat(p, "unknown flag '", --key, "'", NULL);
3720 static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf,
3723 rewrite_perdir_conf *dconf = in_dconf;
3724 char *str = apr_pstrdup(cmd->pool, in_str);
3725 rewrite_server_conf *sconf;
3726 rewriterule_entry *newrule;
3728 char *a1 = NULL, *a2 = NULL, *a3 = NULL;
3731 sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
3733 /* make a new entry in the internal rewrite rule list */
3734 if (cmd->path == NULL) { /* is server command */
3735 newrule = apr_array_push(sconf->rewriterules);
3737 else { /* is per-directory command */
3738 newrule = apr_array_push(dconf->rewriterules);
3741 /* parse the argument line ourself */
3742 if ((err = parseargline(cmd->pool, str, &a1, &a2, &a3))) {
3743 return apr_psprintf(cmd->pool, "RewriteRule: %s "
3744 "(pattern='%s', substitution='%s', flags='%s')",
3748 /* arg3: optional flags field */
3749 newrule->forced_mimetype = NULL;
3750 newrule->forced_handler = NULL;
3751 newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY;
3752 newrule->flags = RULEFLAG_NONE;
3753 newrule->env = NULL;
3754 newrule->cookie = NULL;
3756 newrule->maxrounds = REWRITE_MAX_ROUNDS;
3758 if ((err = cmd_parseflagfield(cmd->pool, newrule, a3,
3759 cmd_rewriterule_setflag)) != NULL) {
3760 return apr_pstrcat(cmd->pool, "RewriteRule: ", err, NULL);
3764 /* arg1: the pattern
3765 * try to compile the regexp to test if is ok
3768 newrule->flags |= RULEFLAG_NOTMATCH;
3772 regexp = ap_pregcomp(cmd->pool, a1, AP_REG_EXTENDED |
3773 ((newrule->flags & RULEFLAG_NOCASE)
3774 ? AP_REG_ICASE : 0));
3776 return apr_pstrcat(cmd->pool,
3777 "RewriteRule: cannot compile regular expression '",
3781 newrule->pattern = a1;
3782 newrule->regexp = regexp;
3784 /* arg2: the output string */
3785 newrule->output = a2;
3786 if (*a2 == '-' && !a2[1]) {
3787 newrule->flags |= RULEFLAG_NOSUB;
3790 /* now, if the server or per-dir config holds an
3791 * array of RewriteCond entries, we take it for us
3792 * and clear the array
3794 if (cmd->path == NULL) { /* is server command */
3795 newrule->rewriteconds = sconf->rewriteconds;
3796 sconf->rewriteconds = apr_array_make(cmd->pool, 2,
3797 sizeof(rewritecond_entry));
3799 else { /* is per-directory command */
3800 newrule->rewriteconds = dconf->rewriteconds;
3801 dconf->rewriteconds = apr_array_make(cmd->pool, 2,
3802 sizeof(rewritecond_entry));
3810 * +-------------------------------------------------------+
3812 * | the rewriting engine
3814 * +-------------------------------------------------------+
3817 /* Lexicographic Compare */
3818 static APR_INLINE int compare_lexicography(char *a, char *b)
3820 apr_size_t i, lena, lenb;
3826 for (i = 0; i < lena; ++i) {
3828 return ((unsigned char)a[i] > (unsigned char)b[i]) ? 1 : -1;
3835 return ((lena > lenb) ? 1 : -1);
3839 * Apply a single rewriteCond
3841 static int apply_rewrite_cond(rewritecond_entry *p, rewrite_ctx *ctx)
3845 request_rec *rsub, *r = ctx->r;
3846 ap_regmatch_t regmatch[AP_MAX_REG_MATCH];
3850 if (p->ptype != CONDPAT_AP_EXPR)
3851 input = do_expand(p->input, ctx, NULL);
3854 case CONDPAT_FILE_EXISTS:
3855 if ( apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS
3856 && sb.filetype == APR_REG) {
3861 case CONDPAT_FILE_SIZE:
3862 if ( apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS
3863 && sb.filetype == APR_REG && sb.size > 0) {
3868 case CONDPAT_FILE_LINK:
3870 if ( apr_stat(&sb, input, APR_FINFO_MIN | APR_FINFO_LINK,
3871 r->pool) == APR_SUCCESS
3872 && sb.filetype == APR_LNK) {
3878 case CONDPAT_FILE_DIR:
3879 if ( apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS
3880 && sb.filetype == APR_DIR) {
3885 case CONDPAT_FILE_XBIT:
3886 if ( apr_stat(&sb, input, APR_FINFO_PROT, r->pool) == APR_SUCCESS
3887 && (sb.protection & (APR_UEXECUTE | APR_GEXECUTE | APR_WEXECUTE))) {
3892 case CONDPAT_LU_URL:
3893 if (*input && subreq_ok(r)) {
3894 rsub = ap_sub_req_lookup_uri(input, r, NULL);
3895 if (rsub->status < 400) {
3898 rewritelog((r, 5, NULL, "RewriteCond URI (-U) check: "
3899 "path=%s -> status=%d", input, rsub->status));
3900 ap_destroy_sub_req(rsub);
3904 case CONDPAT_LU_FILE:
3905 if (*input && subreq_ok(r)) {
3906 rsub = ap_sub_req_lookup_file(input, r, NULL);
3907 if (rsub->status < 300 &&
3908 /* double-check that file exists since default result is 200 */
3909 apr_stat(&sb, rsub->filename, APR_FINFO_MIN,
3910 r->pool) == APR_SUCCESS) {
3913 rewritelog((r, 5, NULL, "RewriteCond file (-F) check: path=%s "
3914 "-> file=%s status=%d", input, rsub->filename,
3916 ap_destroy_sub_req(rsub);
3920 case CONDPAT_STR_GE:
3923 case CONDPAT_STR_GT:
3926 if (p->flags & CONDFLAG_NOCASE) {
3927 rc = (strcasecmp(input, p->pattern) >= basis) ? 1 : 0;
3930 rc = (compare_lexicography(input, p->pattern) >= basis) ? 1 : 0;
3934 case CONDPAT_STR_LE:
3937 case CONDPAT_STR_LT:
3940 if (p->flags & CONDFLAG_NOCASE) {
3941 rc = (strcasecmp(input, p->pattern) <= basis) ? 1 : 0;
3944 rc = (compare_lexicography(input, p->pattern) <= basis) ? 1 : 0;
3948 case CONDPAT_STR_EQ:
3949 /* Note: the only type where the operator is dropped from p->pattern */
3950 if (p->flags & CONDFLAG_NOCASE) {
3951 rc = !strcasecmp(input, p->pattern);
3954 rc = !strcmp(input, p->pattern);
3958 case CONDPAT_INT_GE: rc = (atoi(input) >= atoi(p->pattern)); break;
3959 case CONDPAT_INT_GT: rc = (atoi(input) > atoi(p->pattern)); break;
3961 case CONDPAT_INT_LE: rc = (atoi(input) <= atoi(p->pattern)); break;
3962 case CONDPAT_INT_LT: rc = (atoi(input) < atoi(p->pattern)); break;
3964 case CONDPAT_INT_EQ: rc = (atoi(input) == atoi(p->pattern)); break;
3966 case CONDPAT_AP_EXPR:
3968 const char *err, *source;
3969 rc = ap_expr_exec_re(r, p->expr, AP_MAX_REG_MATCH, regmatch,
3971 if (rc < 0 || err) {
3972 rewritelog((r, 1, ctx->perdir,
3973 "RewriteCond: expr='%s' evaluation failed: %s",
3974 p->pattern - p->pskip, err));
3977 /* update briRC backref info */
3978 if (rc && !(p->flags & CONDFLAG_NOTMATCH)) {
3979 ctx->briRC.source = source;
3980 memcpy(ctx->briRC.regmatch, regmatch, sizeof(regmatch));
3985 /* it is really a regexp pattern, so apply it */
3986 rc = !ap_regexec(p->regexp, input, AP_MAX_REG_MATCH, regmatch, 0);
3988 /* update briRC backref info */
3989 if (rc && !(p->flags & CONDFLAG_NOTMATCH)) {
3990 ctx->briRC.source = input;
3991 memcpy(ctx->briRC.regmatch, regmatch, sizeof(regmatch));
3996 if (p->flags & CONDFLAG_NOTMATCH) {
4000 rewritelog((r, 4, ctx->perdir, "RewriteCond: input='%s' pattern='%s'%s "
4001 "=> %s", input, p->pattern - p->pskip,
4002 (p->flags & CONDFLAG_NOCASE) ? " [NC]" : "",
4003 rc ? "matched" : "not-matched"));
4008 /* check for forced type and handler */
4009 static APR_INLINE void force_type_handler(rewriterule_entry *p,
4014 if (p->forced_mimetype) {
4015 expanded = do_expand(p->forced_mimetype, ctx, p);
4018 ap_str_tolower(expanded);
4020 rewritelog((ctx->r, 2, ctx->perdir, "remember %s to have MIME-type "
4021 "'%s'", ctx->r->filename, expanded));
4023 apr_table_setn(ctx->r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
4028 if (p->forced_handler) {
4029 expanded = do_expand(p->forced_handler, ctx, p);
4032 ap_str_tolower(expanded);
4034 rewritelog((ctx->r, 2, ctx->perdir, "remember %s to have "
4035 "Content-handler '%s'", ctx->r->filename, expanded));
4037 apr_table_setn(ctx->r->notes, REWRITE_FORCED_HANDLER_NOTEVAR,
4044 * Apply a single RewriteRule
4046 static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx)
4048 ap_regmatch_t regmatch[AP_MAX_REG_MATCH];
4049 apr_array_header_t *rewriteconds;
4050 rewritecond_entry *conds;
4052 char *newuri = NULL;
4053 request_rec *r = ctx->r;
4054 int is_proxyreq = 0;
4056 ctx->uri = r->filename;
4059 apr_size_t dirlen = strlen(ctx->perdir);
4064 is_proxyreq = ( r->proxyreq && r->filename
4065 && !strncmp(r->filename, "proxy:", 6));
4067 /* Since we want to match against the (so called) full URL, we have
4068 * to re-add the PATH_INFO postfix
4070 if (r->path_info && *r->path_info) {
4071 rewritelog((r, 3, ctx->perdir, "add path info postfix: %s -> %s%s",
4072 ctx->uri, ctx->uri, r->path_info));
4073 ctx->uri = apr_pstrcat(r->pool, ctx->uri, r->path_info, NULL);
4076 /* Additionally we strip the physical path from the url to match
4077 * it independent from the underlaying filesystem.
4079 if (!is_proxyreq && strlen(ctx->uri) >= dirlen &&
4080 !strncmp(ctx->uri, ctx->perdir, dirlen)) {
4082 rewritelog((r, 3, ctx->perdir, "strip per-dir prefix: %s -> %s",
4083 ctx->uri, ctx->uri + dirlen));
4084 ctx->uri = ctx->uri + dirlen;
4088 /* Try to match the URI against the RewriteRule pattern
4089 * and exit immediately if it didn't apply.
4091 rewritelog((r, 3, ctx->perdir, "applying pattern '%s' to uri '%s'",
4092 p->pattern, ctx->uri));
4094 rc = !ap_regexec(p->regexp, ctx->uri, AP_MAX_REG_MATCH, regmatch, 0);
4095 if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) ||
4096 (!rc && (p->flags & RULEFLAG_NOTMATCH)) ) ) {
4100 /* It matched, wow! Now it's time to prepare the context structure for
4101 * further processing
4103 ctx->vary_this = NULL;
4104 ctx->briRC.source = NULL;
4106 if (p->flags & RULEFLAG_NOTMATCH) {
4107 ctx->briRR.source = NULL;
4110 ctx->briRR.source = apr_pstrdup(r->pool, ctx->uri);
4111 memcpy(ctx->briRR.regmatch, regmatch, sizeof(regmatch));
4114 /* Ok, we already know the pattern has matched, but we now
4115 * additionally have to check for all existing preconditions
4116 * (RewriteCond) which have to be also true. We do this at
4117 * this very late stage to avoid unnessesary checks which
4118 * would slow down the rewriting engine.
4120 rewriteconds = p->rewriteconds;
4121 conds = (rewritecond_entry *)rewriteconds->elts;
4123 for (i = 0; i < rewriteconds->nelts; ++i) {
4124 rewritecond_entry *c = &conds[i];
4126 rc = apply_rewrite_cond(c, ctx);
4128 * Reset vary_this if the novary flag is set for this condition.
4130 if (c->flags & CONDFLAG_NOVARY) {
4131 ctx->vary_this = NULL;
4133 if (c->flags & CONDFLAG_ORNEXT) {
4135 /* One condition is false, but another can be still true. */
4136 ctx->vary_this = NULL;
4140 /* skip the rest of the chained OR conditions */
4141 while ( i < rewriteconds->nelts
4142 && c->flags & CONDFLAG_ORNEXT) {
4151 /* If some HTTP header was involved in the condition, remember it
4154 if (ctx->vary_this) {
4155 ctx->vary = ctx->vary
4156 ? apr_pstrcat(r->pool, ctx->vary, ", ", ctx->vary_this,
4159 ctx->vary_this = NULL;
4163 /* expand the result */
4164 if (!(p->flags & RULEFLAG_NOSUB)) {
4165 newuri = do_expand(p->output, ctx, p);
4166 rewritelog((r, 2, ctx->perdir, "rewrite '%s' -> '%s'", ctx->uri,
4170 /* expand [E=var:val] and [CO=<cookie>] */
4171 do_expand_env(p->env, ctx);
4172 do_expand_cookie(p->cookie, ctx);
4174 /* non-substitution rules ('RewriteRule <pat> -') end here. */
4175 if (p->flags & RULEFLAG_NOSUB) {
4176 force_type_handler(p, ctx);
4178 if (p->flags & RULEFLAG_STATUS) {
4179 rewritelog((r, 2, ctx->perdir, "forcing responsecode %d for %s",
4180 p->forced_responsecode, r->filename));
4182 r->status = p->forced_responsecode;
4188 /* Now adjust API's knowledge about r->filename and r->args */
4189 r->filename = newuri;
4191 if (ctx->perdir && (p->flags & RULEFLAG_DISCARDPATHINFO)) {
4192 r->path_info = NULL;
4195 splitout_queryargs(r, p->flags & RULEFLAG_QSAPPEND, p->flags & RULEFLAG_QSDISCARD);
4197 /* Add the previously stripped per-directory location prefix, unless
4198 * (1) it's an absolute URL path and
4199 * (2) it's a full qualified URL
4201 if ( ctx->perdir && !is_proxyreq && *r->filename != '/'
4202 && !is_absolute_uri(r->filename, NULL)) {
4203 rewritelog((r, 3, ctx->perdir, "add per-dir prefix: %s -> %s%s",
4204 r->filename, ctx->perdir, r->filename));
4206 r->filename = apr_pstrcat(r->pool, ctx->perdir, r->filename, NULL);
4209 /* If this rule is forced for proxy throughput
4210 * (`RewriteRule ... ... [P]') then emulate mod_proxy's
4211 * URL-to-filename handler to be sure mod_proxy is triggered
4212 * for this URL later in the Apache API. But make sure it is
4213 * a fully-qualified URL. (If not it is qualified with
4216 if (p->flags & RULEFLAG_PROXY) {
4217 /* For rules evaluated in server context, the mod_proxy fixup
4218 * hook can be relied upon to escape the URI as and when
4219 * necessary, since it occurs later. If in directory context,
4220 * the ordering of the fixup hooks is forced such that
4221 * mod_proxy comes first, so the URI must be escaped here
4222 * instead. See PR 39746, 46428, and other headaches. */
4223 if (ctx->perdir && (p->flags & RULEFLAG_NOESCAPE) == 0) {
4224 char *old_filename = r->filename;
4226 r->filename = ap_escape_uri(r->pool, r->filename);
4227 rewritelog((r, 2, ctx->perdir, "escaped URI in per-dir context "
4228 "for proxy, %s -> %s", old_filename, r->filename));
4231 fully_qualify_uri(r);
4233 rewritelog((r, 2, ctx->perdir, "forcing proxy-throughput with %s",
4236 r->filename = apr_pstrcat(r->pool, "proxy:", r->filename, NULL);
4240 /* If this rule is explicitly forced for HTTP redirection
4241 * (`RewriteRule .. .. [R]') then force an external HTTP
4242 * redirect. But make sure it is a fully-qualified URL. (If
4243 * not it is qualified with ourself).
4245 if (p->flags & RULEFLAG_FORCEREDIRECT) {
4246 fully_qualify_uri(r);
4248 rewritelog((r, 2, ctx->perdir, "explicitly forcing redirect with %s",
4251 r->status = p->forced_responsecode;
4255 /* Special Rewriting Feature: Self-Reduction
4256 * We reduce the URL by stripping a possible
4257 * http[s]://<ourhost>[:<port>] prefix, i.e. a prefix which
4258 * corresponds to ourself. This is to simplify rewrite maps
4259 * and to avoid recursion, etc. When this prefix is not a
4260 * coincidence then the user has to use [R] explicitly (see
4265 /* If this rule is still implicitly forced for HTTP
4266 * redirection (`RewriteRule .. <scheme>://...') then
4267 * directly force an external HTTP redirect.
4269 if (is_absolute_uri(r->filename, NULL)) {
4270 rewritelog((r, 2, ctx->perdir, "implicitly forcing redirect (rc=%d) "
4271 "with %s", p->forced_responsecode, r->filename));
4273 r->status = p->forced_responsecode;
4277 /* Finally remember the forced mime-type */
4278 force_type_handler(p, ctx);
4280 /* Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_)
4281 * But now we're done for this particular rule.
4287 * Apply a complete rule set,
4288 * i.e. a list of rewrite rules
4290 static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules,
4293 rewriterule_entry *entries;
4294 rewriterule_entry *p;
4302 ctx = apr_palloc(r->pool, sizeof(*ctx));
4303 ctx->perdir = perdir;
4307 * Iterate over all existing rules
4309 entries = (rewriterule_entry *)rewriterules->elts;
4312 for (i = 0; i < rewriterules->nelts; i++) {
4316 * Ignore this rule on subrequests if we are explicitly
4317 * asked to do so or this is a proxy-throughput or a
4318 * forced redirect rule.
4320 if (r->main != NULL &&
4321 (p->flags & RULEFLAG_IGNOREONSUBREQ ||
4322 p->flags & RULEFLAG_FORCEREDIRECT )) {
4327 * Apply the current rule.
4330 rc = apply_rewrite_rule(p, ctx);
4333 /* Regardless of what we do next, we've found a match. Check to see
4334 * if any of the request header fields were involved, and add them
4335 * to the Vary field of the response.
4338 apr_table_merge(r->headers_out, "Vary", ctx->vary);
4342 * The rule sets the response code (implies match-only)
4344 if (p->flags & RULEFLAG_STATUS) {
4345 return ACTION_STATUS;
4349 * Indicate a change if this was not a match-only rule.
4352 changed = ((p->flags & RULEFLAG_NOESCAPE)
4353 ? ACTION_NOESCAPE : ACTION_NORMAL);
4357 * Pass-Through Feature (`RewriteRule .. .. [PT]'):
4358 * Because the Apache 1.x API is very limited we
4359 * need this hack to pass the rewritten URL to other
4360 * modules like mod_alias, mod_userdir, etc.
4362 if (p->flags & RULEFLAG_PASSTHROUGH) {
4363 rewritelog((r, 2, perdir, "forcing '%s' to get passed through "
4364 "to next API URI-to-filename handler", r->filename));
4365 r->filename = apr_pstrcat(r->pool, "passthrough:",
4367 changed = ACTION_NORMAL;
4371 if (p->flags & RULEFLAG_END) {
4372 rewritelog((r, 8, perdir, "Rule has END flag, no further rewriting for this request"));
4373 apr_pool_userdata_set("1", really_last_key, apr_pool_cleanup_null, r->pool);
4377 * Stop processing also on proxy pass-through and
4378 * last-rule and new-round flags.
4380 if (p->flags & (RULEFLAG_PROXY | RULEFLAG_LASTRULE)) {
4385 * On "new-round" flag we just start from the top of
4386 * the rewriting ruleset again.
4388 if (p->flags & RULEFLAG_NEWROUND) {
4389 if (++round >= p->maxrounds) {
4390 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02596)
4391 "RewriteRule '%s' and URI '%s' exceeded "
4392 "maximum number of rounds (%d) via the [N] flag",
4393 p->pattern, r->uri, p->maxrounds);
4395 r->status = HTTP_INTERNAL_SERVER_ERROR;
4396 return ACTION_STATUS;
4402 * If we are forced to skip N next rules, do it now.
4406 while ( i < rewriterules->nelts
4415 * If current rule is chained with next rule(s),
4416 * skip all this next rule(s)
4418 while ( i < rewriterules->nelts
4419 && p->flags & RULEFLAG_CHAIN) {
4430 * +-------------------------------------------------------+
4432 * | Module Initialization Hooks
4434 * +-------------------------------------------------------+
4437 static int pre_config(apr_pool_t *pconf,
4441 APR_OPTIONAL_FN_TYPE(ap_register_rewrite_mapfunc) *map_pfn_register;
4443 ap_mutex_register(pconf, rewritemap_mutex_type, NULL, APR_LOCK_DEFAULT, 0);
4445 /* register int: rewritemap handlers */
4446 map_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_rewrite_mapfunc);
4447 if (map_pfn_register) {
4448 map_pfn_register("tolower", rewrite_mapfunc_tolower);
4449 map_pfn_register("toupper", rewrite_mapfunc_toupper);
4450 map_pfn_register("escape", rewrite_mapfunc_escape);
4451 map_pfn_register("unescape", rewrite_mapfunc_unescape);
4453 dbd_acquire = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_acquire);
4454 dbd_prepare = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_prepare);
4458 static int post_config(apr_pool_t *p,
4465 /* check if proxy module is available */
4466 proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL);
4468 rv = rewritelock_create(s, p);
4469 if (rv != APR_SUCCESS) {
4470 return HTTP_INTERNAL_SERVER_ERROR;
4473 apr_pool_cleanup_register(p, (void *)s, rewritelock_remove,
4474 apr_pool_cleanup_null);
4476 /* if we are not doing the initial config, step through the servers and
4477 * open the RewriteMap prg:xxx programs,
4479 if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_CONFIG) {
4480 for (; s; s = s->next) {
4481 if (run_rewritemap_programs(s, p) != APR_SUCCESS) {
4482 return HTTP_INTERNAL_SERVER_ERROR;
4487 rewrite_ssl_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
4488 rewrite_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
4493 static void init_child(apr_pool_t *p, server_rec *s)
4495 apr_status_t rv = 0; /* get a rid of gcc warning (REWRITELOG_DISABLED) */
4497 if (rewrite_mapr_lock_acquire) {
4498 rv = apr_global_mutex_child_init(&rewrite_mapr_lock_acquire,
4499 apr_global_mutex_lockfile(rewrite_mapr_lock_acquire), p);
4500 if (rv != APR_SUCCESS) {
4501 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00666)
4502 "mod_rewrite: could not init rewrite_mapr_lock_acquire"
4507 /* create the lookup cache */
4508 if (!init_cache(p)) {
4509 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00667)
4510 "mod_rewrite: could not init map cache in child");
4516 * +-------------------------------------------------------+
4520 * +-------------------------------------------------------+
4524 * URI-to-filename hook
4525 * [deals with RewriteRules in server context]
4527 static int hook_uri2file(request_rec *r)
4529 rewrite_perdir_conf *dconf;
4530 rewrite_server_conf *conf;
4531 const char *saved_rulestatus;
4533 const char *thisserver;
4535 const char *thisurl;
4541 * retrieve the config structures
4543 conf = ap_get_module_config(r->server->module_config, &rewrite_module);
4545 dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
4549 * only do something under runtime if the engine is really enabled,
4550 * else return immediately!
4552 if (!dconf || dconf->state == ENGINE_DISABLED) {
4557 * check for the ugly API case of a virtual host section where no
4558 * mod_rewrite directives exists. In this situation we became no chance
4559 * by the API to setup our default per-server config so we have to
4560 * on-the-fly assume we have the default config. But because the default
4561 * config has a disabled rewriting engine we are lucky because can
4562 * just stop operating now.
4564 if (conf->server != r->server) {
4568 /* END flag was used as a RewriteRule flag on this request */
4569 apr_pool_userdata_get(&skipdata, really_last_key, r->pool);
4570 if (skipdata != NULL) {
4571 rewritelog((r, 8, NULL, "Declining, no further rewriting due to END flag"));
4575 /* Unless the anyuri option is set, ensure that the input to the
4576 * first rule really is a URL-path, avoiding security issues with
4577 * poorly configured rules. See CVE-2011-3368, CVE-2011-4317. */
4578 if ((dconf->options & OPTION_ANYURI) == 0
4579 && ((r->unparsed_uri[0] == '*' && r->unparsed_uri[1] == '\0')
4580 || !r->uri || r->uri[0] != '/')) {
4581 rewritelog((r, 8, NULL, "Declining, request-URI '%s' is not a URL-path. "
4582 "Consult the manual entry for the RewriteOptions directive "
4583 "for options and caveats about matching other strings.",
4589 * add the SCRIPT_URL variable to the env. this is a bit complicated
4590 * due to the fact that apache uses subrequests and internal redirects
4593 if (r->main == NULL) {
4594 var = apr_table_get(r->subprocess_env, REDIRECT_ENVVAR_SCRIPT_URL);
4596 apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, r->uri);
4599 apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
4603 var = apr_table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL);
4604 apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
4608 * create the SCRIPT_URI variable for the env
4611 /* add the canonical URI of this URL */
4612 thisserver = ap_get_server_name_for_url(r);
4613 port = ap_get_server_port(r);
4614 if (ap_is_default_port(port, r)) {
4618 thisport = apr_psprintf(r->pool, ":%u", port);
4620 thisurl = apr_table_get(r->subprocess_env, ENVVAR_SCRIPT_URL);
4622 /* set the variable */
4623 var = apr_pstrcat(r->pool, ap_http_scheme(r), "://", thisserver, thisport,
4625 apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URI, var);
4627 if (!(saved_rulestatus = apr_table_get(r->notes,"mod_rewrite_rewritten"))) {
4628 /* if filename was not initially set,
4629 * we start with the requested URI
4631 if (r->filename == NULL) {
4632 r->filename = apr_pstrdup(r->pool, r->uri);
4633 rewritelog((r, 2, NULL, "init rewrite engine with requested uri %s",
4637 rewritelog((r, 2, NULL, "init rewrite engine with passed filename "
4638 "%s. Original uri = %s", r->filename, r->uri));
4642 * now apply the rules ...
4644 rulestatus = apply_rewrite_list(r, conf->rewriterules, NULL);
4645 apr_table_setn(r->notes, "mod_rewrite_rewritten",
4646 apr_psprintf(r->pool,"%d",rulestatus));
4649 rewritelog((r, 2, NULL, "uri already rewritten. Status %s, Uri %s, "
4650 "r->filename %s", saved_rulestatus, r->uri, r->filename));
4652 rulestatus = atoi(saved_rulestatus);
4659 if (ACTION_STATUS == rulestatus) {
4662 r->status = HTTP_OK;
4666 flen = r->filename ? strlen(r->filename) : 0;
4667 if (flen > 6 && strncmp(r->filename, "proxy:", 6) == 0) {
4668 /* it should be go on as an internal proxy request */
4670 /* check if the proxy module is enabled, so
4671 * we can actually use it!
4673 if (!proxy_available) {
4674 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00669)
4675 "attempt to make remote request from mod_rewrite "
4676 "without proxy enabled: %s", r->filename);
4677 return HTTP_FORBIDDEN;
4680 if (rulestatus == ACTION_NOESCAPE) {
4681 apr_table_setn(r->notes, "proxy-nocanon", "1");
4684 /* make sure the QUERY_STRING and
4685 * PATH_INFO parts get incorporated
4687 if (r->path_info != NULL) {
4688 r->filename = apr_pstrcat(r->pool, r->filename,
4689 r->path_info, NULL);
4691 if ((r->args != NULL)
4692 && ((r->proxyreq == PROXYREQ_PROXY)
4693 || (rulestatus == ACTION_NOESCAPE))) {
4694 /* see proxy_http:proxy_http_canon() */
4695 r->filename = apr_pstrcat(r->pool, r->filename,
4696 "?", r->args, NULL);
4699 /* now make sure the request gets handled by the proxy handler */
4700 if (PROXYREQ_NONE == r->proxyreq) {
4701 r->proxyreq = PROXYREQ_REVERSE;
4703 r->handler = "proxy-server";
4705 rewritelog((r, 1, NULL, "go-ahead with proxy request %s [OK]",
4709 else if ((skip = is_absolute_uri(r->filename, NULL)) > 0) {
4712 /* it was finally rewritten to a remote URL */
4714 if (rulestatus != ACTION_NOESCAPE) {
4715 rewritelog((r, 1, NULL, "escaping %s for redirect",
4717 r->filename = escape_absolute_uri(r->pool, r->filename, skip);
4720 /* append the QUERY_STRING part */
4722 r->filename = apr_pstrcat(r->pool, r->filename, "?",
4723 (rulestatus == ACTION_NOESCAPE)
4725 : ap_escape_uri(r->pool, r->args),
4729 /* determine HTTP redirect response code */
4730 if (ap_is_HTTP_REDIRECT(r->status)) {
4732 r->status = HTTP_OK; /* make Apache kernel happy */
4735 n = HTTP_MOVED_TEMPORARILY;
4738 /* now do the redirection */
4739 apr_table_setn(r->headers_out, "Location", r->filename);
4740 rewritelog((r, 1, NULL, "redirect to %s [REDIRECT/%d]", r->filename,
4745 else if (flen > 12 && strncmp(r->filename, "passthrough:", 12) == 0) {
4747 * Hack because of underpowered API: passing the current
4748 * rewritten filename through to other URL-to-filename handlers
4749 * just as it were the requested URL. This is to enable
4750 * post-processing by mod_alias, etc. which always act on
4751 * r->uri! The difference here is: We do not try to
4752 * add the document root
4754 r->uri = apr_pstrdup(r->pool, r->filename+12);
4758 /* it was finally rewritten to a local path */
4760 /* expand "/~user" prefix */
4762 r->filename = expand_tildepaths(r, r->filename);
4764 rewritelog((r, 2, NULL, "local path result: %s", r->filename));
4766 /* the filename must be either an absolute local path or an
4767 * absolute local URL.
4769 if ( *r->filename != '/'
4770 && !ap_os_is_path_absolute(r->pool, r->filename)) {
4771 return HTTP_BAD_REQUEST;
4774 /* if there is no valid prefix, we call
4775 * the translator from the core and
4776 * prefix the filename with document_root
4779 * We cannot leave out the prefix_stat because
4780 * - when we always prefix with document_root
4781 * then no absolute path can be created, e.g. via
4782 * emulating a ScriptAlias directive, etc.
4783 * - when we always NOT prefix with document_root
4784 * then the files under document_root have to
4785 * be references directly and document_root
4786 * gets never used and will be a dummy parameter -
4790 * Under real Unix systems this is no problem,
4791 * because we only do stat() on the first directory
4792 * and this gets cached by the kernel for along time!
4794 if (!prefix_stat(r->filename, r->pool)) {
4798 r->uri = r->filename;
4799 res = ap_core_translate(r);
4803 rewritelog((r, 1, NULL, "prefixing with document_root of %s"
4804 " FAILED", r->filename));
4809 rewritelog((r, 2, NULL, "prefixed with document_root to %s",
4813 rewritelog((r, 1, NULL, "go-ahead with %s [OK]", r->filename));
4818 rewritelog((r, 1, NULL, "pass through %s", r->filename));
4825 * [RewriteRules in directory context]
4827 static int hook_fixup(request_rec *r)
4829 rewrite_perdir_conf *dconf;
4836 char *ofilename, *oargs;
4840 dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
4843 /* if there is no per-dir config we return immediately */
4844 if (dconf == NULL) {
4849 * only do something under runtime if the engine is really enabled,
4850 * for this directory, else return immediately!
4852 if (dconf->state == ENGINE_DISABLED) {
4856 /* if there are no real (i.e. no RewriteRule directives!)
4857 per-dir config of us, we return also immediately */
4858 if (dconf->directory == NULL) {
4865 is_proxyreq = ( r->proxyreq && r->filename
4866 && !strncmp(r->filename, "proxy:", 6));
4869 * .htaccess file is called before really entering the directory, i.e.:
4870 * URL: http://localhost/foo and .htaccess is located in foo directory
4871 * Ignore such attempts, allowing mod_dir to direct the client to the
4872 * canonical URL. This can be controlled with the AllowNoSlash option.
4874 if (!is_proxyreq && !(dconf->options & OPTION_NOSLASH)) {
4875 l = strlen(dconf->directory) - 1;
4876 if (r->filename && strlen(r->filename) == l &&
4877 (dconf->directory)[l] == '/' &&
4878 !strncmp(r->filename, dconf->directory, l)) {
4883 /* END flag was used as a RewriteRule flag on this request */
4884 apr_pool_userdata_get(&skipdata, really_last_key, r->pool);
4885 if (skipdata != NULL) {
4886 rewritelog((r, 8, dconf->directory, "Declining, no further rewriting due to END flag"));
4891 * Do the Options check after engine check, so
4892 * the user is able to explicitely turn RewriteEngine Off.
4894 if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) {
4895 /* FollowSymLinks is mandatory! */
4896 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00670)
4897 "Options FollowSymLinks and SymLinksIfOwnerMatch are both off, "
4898 "so the RewriteRule directive is also forbidden "
4899 "due to its similar ability to circumvent directory restrictions : "
4901 return HTTP_FORBIDDEN;
4905 * remember the current filename before rewriting for later check
4906 * to prevent deadlooping because of internal redirects
4907 * on final URL/filename which can be equal to the inital one.
4908 * also, we'll restore original r->filename if we decline this
4911 ofilename = r->filename;
4914 if (r->filename == NULL) {
4915 r->filename = apr_pstrdup(r->pool, r->uri);
4916 rewritelog((r, 2, dconf->directory, "init rewrite engine with"
4917 " requested uri %s", r->filename));
4921 * now apply the rules ...
4923 rulestatus = apply_rewrite_list(r, dconf->rewriterules, dconf->directory);
4927 if (ACTION_STATUS == rulestatus) {
4930 r->status = HTTP_OK;
4934 l = strlen(r->filename);
4935 if (l > 6 && strncmp(r->filename, "proxy:", 6) == 0) {
4936 /* it should go on as an internal proxy request */
4938 /* make sure the QUERY_STRING and
4939 * PATH_INFO parts get incorporated
4940 * (r->path_info was already appended by the
4941 * rewriting engine because of the per-dir context!)
4943 if (r->args != NULL) {
4944 /* see proxy_http:proxy_http_canon() */
4945 r->filename = apr_pstrcat(r->pool, r->filename,
4946 "?", r->args, NULL);
4949 /* now make sure the request gets handled by the proxy handler */
4950 if (PROXYREQ_NONE == r->proxyreq) {
4951 r->proxyreq = PROXYREQ_REVERSE;
4953 r->handler = "proxy-server";
4955 rewritelog((r, 1, dconf->directory, "go-ahead with proxy request "
4956 "%s [OK]", r->filename));
4959 else if ((skip = is_absolute_uri(r->filename, NULL)) > 0) {
4960 /* it was finally rewritten to a remote URL */
4962 /* because we are in a per-dir context
4963 * first try to replace the directory with its base-URL
4964 * if there is a base-URL available
4966 if (dconf->baseurl != NULL) {
4967 /* skip 'scheme://' */
4968 cp = r->filename + skip;
4970 if ((cp = ap_strchr(cp, '/')) != NULL && *(++cp)) {
4971 rewritelog((r, 2, dconf->directory,
4972 "trying to replace prefix %s with %s",
4973 dconf->directory, dconf->baseurl));
4975 /* I think, that hack needs an explanation:
4977 * mod_rewrite was written for unix systems, were
4978 * absolute file-system paths start with a slash.
4979 * URL-paths _also_ start with slashes, so they
4980 * can be easily compared with system paths.
4982 * the following assumes, that the actual url-path
4983 * may be prefixed by the current directory path and
4984 * tries to replace the system path with the RewriteBase
4986 * That assumption is true if we use a RewriteRule like
4988 * RewriteRule ^foo bar [R]
4990 * (see apply_rewrite_rule function)
4991 * However on systems that don't have a / as system
4992 * root this will never match, so we skip the / after the
4993 * hostname and compare/substitute only the stuff after it.
4995 * (note that cp was already increased to the right value)
4997 cp2 = subst_prefix_path(r, cp, (*dconf->directory == '/')
4998 ? dconf->directory + 1
5000 dconf->baseurl + 1);
5001 if (strcmp(cp2, cp) != 0) {
5003 r->filename = apr_pstrcat(r->pool, r->filename,
5009 /* now prepare the redirect... */
5010 if (rulestatus != ACTION_NOESCAPE) {
5011 rewritelog((r, 1, dconf->directory, "escaping %s for redirect",
5013 r->filename = escape_absolute_uri(r->pool, r->filename, skip);
5016 /* append the QUERY_STRING part */
5018 char *escaped_args = NULL;
5019 int noescape = (rulestatus == ACTION_NOESCAPE ||
5020 (oargs && !strcmp(r->args, oargs)));
5022 r->filename = apr_pstrcat(r->pool, r->filename, "?",
5025 : (escaped_args = ap_escape_uri(r->pool, r->args)),
5028 rewritelog((r, 1, dconf->directory, "%s %s to query string for redirect %s",
5029 noescape ? "copying" : "escaping",
5031 noescape ? "" : escaped_args));
5034 /* determine HTTP redirect response code */
5035 if (ap_is_HTTP_REDIRECT(r->status)) {
5037 r->status = HTTP_OK; /* make Apache kernel happy */
5040 n = HTTP_MOVED_TEMPORARILY;
5043 /* now do the redirection */
5044 apr_table_setn(r->headers_out, "Location", r->filename);
5045 rewritelog((r, 1, dconf->directory, "redirect to %s [REDIRECT/%d]",
5050 const char *tmpfilename = NULL;
5051 /* it was finally rewritten to a local path */
5053 /* if someone used the PASSTHROUGH flag in per-dir
5054 * context we just ignore it. It is only useful
5055 * in per-server context
5057 if (l > 12 && strncmp(r->filename, "passthrough:", 12) == 0) {
5058 r->filename = apr_pstrdup(r->pool, r->filename+12);
5061 rewritelog((r, 2, NULL, "local path result: %s", r->filename));
5063 /* the filename must be either an absolute local path or an
5064 * absolute local URL.
5066 if ( *r->filename != '/'
5067 && !ap_os_is_path_absolute(r->pool, r->filename)) {
5068 return HTTP_BAD_REQUEST;
5071 tmpfilename = r->filename;
5073 /* if there is a valid base-URL then substitute
5074 * the per-dir prefix with this base-URL if the
5075 * current filename still is inside this per-dir
5076 * context. If not then treat the result as a
5079 if (dconf->baseurl != NULL) {
5080 rewritelog((r, 2, dconf->directory, "trying to replace prefix "
5081 "%s with %s", dconf->directory, dconf->baseurl));
5083 r->filename = subst_prefix_path(r, r->filename,
5088 /* if no explicit base-URL exists we assume
5089 * that the directory prefix is also a valid URL
5090 * for this webserver and only try to remove the
5091 * document_root if it is prefix
5093 if ((ccp = ap_document_root(r)) != NULL) {
5094 /* strip trailing slash */
5096 if (ccp[l-1] == '/') {
5099 if (!strncmp(r->filename, ccp, l) &&
5100 r->filename[l] == '/') {
5101 rewritelog((r, 2,dconf->directory, "strip document_root"
5102 " prefix: %s -> %s", r->filename,
5105 r->filename = apr_pstrdup(r->pool, r->filename+l);
5110 /* No base URL, or r->filename wasn't still under dconf->directory
5111 * or, r->filename wasn't still under the document root.
5112 * If there's a context document root AND a context prefix, and
5113 * the context document root is a prefix of r->filename, replace.
5114 * This allows a relative substitution on a path found by mod_userdir
5115 * or mod_alias without baking in a RewriteBase.
5117 if (tmpfilename == r->filename &&
5118 !(dconf->options & OPTION_IGNORE_CONTEXT_INFO)) {
5119 if ((ccp = ap_context_document_root(r)) != NULL) {
5120 const char *prefix = ap_context_prefix(r);
5121 if (prefix != NULL) {
5122 rewritelog((r, 2, dconf->directory, "trying to replace "
5123 "context docroot %s with context prefix %s",
5125 r->filename = subst_prefix_path(r, r->filename,
5131 /* Check for deadlooping:
5132 * At this point we KNOW that at least one rewriting
5133 * rule was applied, but when the resulting URL is
5134 * the same as the initial URL, we are not allowed to
5135 * use the following internal redirection stuff because
5136 * this would lead to a deadloop.
5138 if (ofilename != NULL && strcmp(r->filename, ofilename) == 0) {
5139 rewritelog((r, 1, dconf->directory, "initial URL equal rewritten"
5140 " URL: %s [IGNORING REWRITE]", r->filename));
5145 /* now initiate the internal redirect */
5146 rewritelog((r, 1, dconf->directory, "internal redirect with %s "
5147 "[INTERNAL REDIRECT]", r->filename));
5148 r->filename = apr_pstrcat(r->pool, "redirect:", r->filename, NULL);
5149 r->handler = REWRITE_REDIRECT_HANDLER_NAME;
5154 rewritelog((r, 1, dconf->directory, "pass through %s", r->filename));
5155 r->filename = ofilename;
5162 * [T=...,H=...] execution
5164 static int hook_mimetype(request_rec *r)
5169 t = apr_table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR);
5171 rewritelog((r, 1, NULL, "force filename %s to have MIME-type '%s'",
5174 ap_set_content_type(r, t);
5178 t = apr_table_get(r->notes, REWRITE_FORCED_HANDLER_NOTEVAR);
5180 rewritelog((r, 1, NULL, "force filename %s to have the "
5181 "Content-handler '%s'", r->filename, t));
5191 * "content" handler for internal redirects
5193 static int handler_redirect(request_rec *r)
5195 if (strcmp(r->handler, REWRITE_REDIRECT_HANDLER_NAME)) {
5199 /* just make sure that we are really meant! */
5200 if (strncmp(r->filename, "redirect:", 9) != 0) {
5204 /* now do the internal redirect */
5205 ap_internal_redirect(apr_pstrcat(r->pool, r->filename+9,
5206 r->args ? "?" : NULL, r->args, NULL), r);
5208 /* and return gracefully */
5214 * +-------------------------------------------------------+
5216 * | Module paraphernalia
5218 * +-------------------------------------------------------+
5221 static const command_rec command_table[] = {
5222 AP_INIT_FLAG( "RewriteEngine", cmd_rewriteengine, NULL, OR_FILEINFO,
5223 "On or Off to enable or disable (default) the whole "
5224 "rewriting engine"),
5225 AP_INIT_ITERATE( "RewriteOptions", cmd_rewriteoptions, NULL, OR_FILEINFO,
5226 "List of option strings to set"),
5227 AP_INIT_TAKE1( "RewriteBase", cmd_rewritebase, NULL, OR_FILEINFO,
5228 "the base URL of the per-directory context"),
5229 AP_INIT_RAW_ARGS("RewriteCond", cmd_rewritecond, NULL, OR_FILEINFO,
5230 "an input string and a to be applied regexp-pattern"),
5231 AP_INIT_RAW_ARGS("RewriteRule", cmd_rewriterule, NULL, OR_FILEINFO,
5232 "an URL-applied regexp-pattern and a substitution URL"),
5233 AP_INIT_TAKE23( "RewriteMap", cmd_rewritemap, NULL, RSRC_CONF,
5234 "a mapname and a filename and options"),
5238 static void ap_register_rewrite_mapfunc(char *name, rewrite_mapfunc_t *func)
5240 apr_hash_set(mapfunc_hash, name, strlen(name), (const void *)func);
5243 static void register_hooks(apr_pool_t *p)
5245 /* fixup after mod_proxy, so that the proxied url will not
5246 * escaped accidentally by mod_proxy's fixup.
5248 static const char * const aszPre[]={ "mod_proxy.c", NULL };
5250 /* make the hashtable before registering the function, so that
5251 * other modules are prevented from accessing uninitialized memory.
5253 mapfunc_hash = apr_hash_make(p);
5254 APR_REGISTER_OPTIONAL_FN(ap_register_rewrite_mapfunc);
5256 ap_hook_handler(handler_redirect, NULL, NULL, APR_HOOK_MIDDLE);
5257 ap_hook_pre_config(pre_config, NULL, NULL, APR_HOOK_MIDDLE);
5258 ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_MIDDLE);
5259 ap_hook_child_init(init_child, NULL, NULL, APR_HOOK_MIDDLE);
5261 ap_hook_fixups(hook_fixup, aszPre, NULL, APR_HOOK_FIRST);
5262 ap_hook_fixups(hook_mimetype, NULL, NULL, APR_HOOK_LAST);
5263 ap_hook_translate_name(hook_uri2file, NULL, NULL, APR_HOOK_FIRST);
5266 /* the main config structure */
5267 AP_DECLARE_MODULE(rewrite) = {
5268 STANDARD20_MODULE_STUFF,
5269 config_perdir_create, /* create per-dir config structures */
5270 config_perdir_merge, /* merge per-dir config structures */
5271 config_server_create, /* create per-server config structures */
5272 config_server_merge, /* merge per-server config structures */
5273 command_table, /* table of config file commands */
5274 register_hooks /* register hooks */