]> granicus.if.org Git - apache/blob - modules/mappers/mod_rewrite.c
e2e9207943e11c0b6790e6395a206ff6ce27feae
[apache] / modules / mappers / mod_rewrite.c
1 /* Copyright 1999-2004 The Apache Software Foundation
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 /*                       _                            _ _
17  *   _ __ ___   ___   __| |    _ __ _____      ___ __(_) |_ ___
18  *  | '_ ` _ \ / _ \ / _` |   | '__/ _ \ \ /\ / / '__| | __/ _ \
19  *  | | | | | | (_) | (_| |   | | |  __/\ V  V /| |  | | ||  __/
20  *  |_| |_| |_|\___/ \__,_|___|_|  \___| \_/\_/ |_|  |_|\__\___|
21  *                       |_____|
22  *
23  *  URL Rewriting Module
24  *
25  *  This module uses a rule-based rewriting engine (based on a
26  *  regular-expression parser) to rewrite requested URLs on the fly.
27  *
28  *  It supports an unlimited number of additional rule conditions (which can
29  *  operate on a lot of variables, even on HTTP headers) for granular
30  *  matching and even external database lookups (either via plain text
31  *  tables, DBM hash files or even external processes) for advanced URL
32  *  substitution.
33  *
34  *  It operates on the full URLs (including the PATH_INFO part) both in
35  *  per-server context (httpd.conf) and per-dir context (.htaccess) and even
36  *  can generate QUERY_STRING parts on result.   The rewriting result finally
37  *  can lead to internal subprocessing, external request redirection or even
38  *  to internal proxy throughput.
39  *
40  *  This module was originally written in April 1996 and
41  *  gifted exclusively to the The Apache Software Foundation in July 1997 by
42  *
43  *      Ralf S. Engelschall
44  *      rse engelschall.com
45  *      www.engelschall.com
46  */
47
48 #include "apr.h"
49 #include "apr_strings.h"
50 #include "apr_hash.h"
51 #include "apr_user.h"
52 #include "apr_lib.h"
53 #include "apr_signal.h"
54 #include "apr_global_mutex.h"
55 #include "apr_dbm.h"
56
57 #if APR_HAS_THREADS
58 #include "apr_thread_mutex.h"
59 #endif
60
61 #define APR_WANT_MEMFUNC
62 #define APR_WANT_STRFUNC
63 #define APR_WANT_IOVEC
64 #include "apr_want.h"
65
66 /* XXX: Do we really need these headers? */
67 #if APR_HAVE_UNISTD_H
68 #include <unistd.h>
69 #endif
70 #if APR_HAVE_SYS_TYPES_H
71 #include <sys/types.h>
72 #endif
73 #if APR_HAVE_STDARG_H
74 #include <stdarg.h>
75 #endif
76 #if APR_HAVE_STDLIB_H
77 #include <stdlib.h>
78 #endif
79 #if APR_HAVE_CTYPE_H
80 #include <ctype.h>
81 #endif
82
83 #include "ap_config.h"
84 #include "httpd.h"
85 #include "http_config.h"
86 #include "http_request.h"
87 #include "http_core.h"
88 #include "http_log.h"
89 #include "http_protocol.h"
90 #include "http_vhost.h"
91
92 #include "mod_ssl.h"
93
94 #include "mod_rewrite.h"
95
96 #if !defined(OS2) && !defined(WIN32) && !defined(BEOS)  && !defined(NETWARE)
97 #include "unixd.h"
98 #define MOD_REWRITE_SET_MUTEX_PERMS /* XXX Apache should define something */
99 #endif
100
101 /*
102  * in order to improve performance on running production systems, you
103  * may strip all rewritelog code entirely from mod_rewrite by using the
104  * -DREWRITELOG_DISABLED compiler option.
105  *
106  * DO NOT USE THIS OPTION FOR PUBLIC BINARY RELEASES. Otherwise YOU are
107  * responsible for answering all the mod_rewrite questions out there.
108  */
109 #ifndef REWRITELOG_DISABLED
110
111 #define rewritelog(x) do_rewritelog x
112 #define REWRITELOG_MODE  ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD )
113 #define REWRITELOG_FLAGS ( APR_WRITE | APR_APPEND | APR_CREATE )
114
115 #else /* !REWRITELOG_DISABLED */
116
117 #define rewritelog(x)
118
119 #endif /* REWRITELOG_DISABLED */
120
121 /* remembered mime-type for [T=...] */
122 #define REWRITE_FORCED_MIMETYPE_NOTEVAR "rewrite-forced-mimetype"
123 #define REWRITE_FORCED_HANDLER_NOTEVAR  "rewrite-forced-handler"
124
125 #define ENVVAR_SCRIPT_URL "SCRIPT_URL"
126 #define REDIRECT_ENVVAR_SCRIPT_URL "REDIRECT_" ENVVAR_SCRIPT_URL
127 #define ENVVAR_SCRIPT_URI "SCRIPT_URI"
128
129 #define CONDFLAG_NONE               1<<0
130 #define CONDFLAG_NOCASE             1<<1
131 #define CONDFLAG_NOTMATCH           1<<2
132 #define CONDFLAG_ORNEXT             1<<3
133
134 #define RULEFLAG_NONE               1<<0
135 #define RULEFLAG_FORCEREDIRECT      1<<1
136 #define RULEFLAG_LASTRULE           1<<2
137 #define RULEFLAG_NEWROUND           1<<3
138 #define RULEFLAG_CHAIN              1<<4
139 #define RULEFLAG_IGNOREONSUBREQ     1<<5
140 #define RULEFLAG_NOTMATCH           1<<6
141 #define RULEFLAG_PROXY              1<<7
142 #define RULEFLAG_PASSTHROUGH        1<<8
143 #define RULEFLAG_QSAPPEND           1<<9
144 #define RULEFLAG_NOCASE             1<<10
145 #define RULEFLAG_NOESCAPE           1<<11
146 #define RULEFLAG_NOSUB              1<<12
147 #define RULEFLAG_STATUS             1<<13
148
149 /* return code of the rewrite rule
150  * the result may be escaped - or not
151  */
152 #define ACTION_NORMAL               1<<0
153 #define ACTION_NOESCAPE             1<<1
154 #define ACTION_STATUS               1<<2
155
156
157 #define MAPTYPE_TXT                 1<<0
158 #define MAPTYPE_DBM                 1<<1
159 #define MAPTYPE_PRG                 1<<2
160 #define MAPTYPE_INT                 1<<3
161 #define MAPTYPE_RND                 1<<4
162
163 #define ENGINE_DISABLED             1<<0
164 #define ENGINE_ENABLED              1<<1
165
166 #define OPTION_NONE                 1<<0
167 #define OPTION_INHERIT              1<<1
168
169 #ifndef RAND_MAX
170 #define RAND_MAX 32767
171 #endif
172
173 /* max cookie size in rfc 2109 */
174 /* XXX: not used at all. We should do a check somewhere and/or cut the cookie */
175 #define MAX_COOKIE_LEN 4096
176
177 /* default maximum number of internal redirects */
178 #define REWRITE_REDIRECT_LIMIT 10
179
180 /* max line length (incl.\n) in text rewrite maps */
181 #ifndef REWRITE_MAX_TXT_MAP_LINE
182 #define REWRITE_MAX_TXT_MAP_LINE 1024
183 #endif
184
185 /* buffer length for prg rewrite maps */
186 #ifndef REWRITE_PRG_MAP_BUF
187 #define REWRITE_PRG_MAP_BUF 1024
188 #endif
189
190 /* for better readbility */
191 #define LEFT_CURLY  '{'
192 #define RIGHT_CURLY '}'
193
194 /*
195  * expansion result items on the stack to save some cycles
196  *
197  * (5 == about 2 variables like "foo%{var}bar%{var}baz")
198  */
199 #define SMALL_EXPANSION 5
200
201 /*
202  * check that a subrequest won't cause infinite recursion
203  *
204  * either not in a subrequest, or in a subrequest
205  * and URIs aren't NULL and sub/main URIs differ
206  */
207 #define subreq_ok(r) (!r->main || \
208     (r->main->uri && r->uri && strcmp(r->main->uri, r->uri)))
209
210
211 /*
212  * +-------------------------------------------------------+
213  * |                                                       |
214  * |                 Types and Structures
215  * |                                                       |
216  * +-------------------------------------------------------+
217  */
218
219 typedef struct {
220     const char *datafile;          /* filename for map data files         */
221     const char *dbmtype;           /* dbm type for dbm map data files     */
222     const char *checkfile;         /* filename to check for map existence */
223     const char *cachename;         /* for cached maps (txt/rnd/dbm)       */
224     int   type;                    /* the type of the map                 */
225     apr_file_t *fpin;              /* in  file pointer for program maps   */
226     apr_file_t *fpout;             /* out file pointer for program maps   */
227     apr_file_t *fperr;             /* err file pointer for program maps   */
228     char *(*func)(request_rec *,   /* function pointer for internal maps  */
229                   char *);
230     char **argv;                   /* argv of the external rewrite map    */
231 } rewritemap_entry;
232
233 /* special pattern types for RewriteCond */
234 typedef enum {
235     CONDPAT_REGEX = 0,
236     CONDPAT_FILE_EXISTS,
237     CONDPAT_FILE_SIZE,
238     CONDPAT_FILE_LINK,
239     CONDPAT_FILE_DIR,
240     CONDPAT_FILE_XBIT,
241     CONDPAT_LU_URL,
242     CONDPAT_LU_FILE,
243     CONDPAT_STR_GT,
244     CONDPAT_STR_LT,
245     CONDPAT_STR_EQ
246 } pattern_type;
247
248 typedef struct {
249     char        *input;   /* Input string of RewriteCond   */
250     char        *pattern; /* the RegExp pattern string     */
251     regex_t     *regexp;  /* the precompiled regexp        */
252     int          flags;   /* Flags which control the match */
253     pattern_type ptype;   /* pattern type                  */
254 } rewritecond_entry;
255
256 /* single linked list for env vars and cookies */
257 typedef struct data_item {
258     struct data_item *next;
259     char *data;
260 } data_item;
261
262 typedef struct {
263     apr_array_header_t *rewriteconds;/* the corresponding RewriteCond entries */
264     char      *pattern;              /* the RegExp pattern string             */
265     regex_t   *regexp;               /* the RegExp pattern compilation        */
266     char      *output;               /* the Substitution string               */
267     int        flags;                /* Flags which control the substitution  */
268     char      *forced_mimetype;      /* forced MIME type of substitution      */
269     char      *forced_handler;       /* forced content handler of subst.      */
270     int        forced_responsecode;  /* forced HTTP response status           */
271     data_item *env;                  /* added environment variables           */
272     data_item *cookie;               /* added cookies                         */
273     int        skip;                 /* number of next rules to skip          */
274 } rewriterule_entry;
275
276 typedef struct {
277     int           state;              /* the RewriteEngine state            */
278     int           options;            /* the RewriteOption state            */
279 #ifndef REWRITELOG_DISABLED
280     const char   *rewritelogfile;     /* the RewriteLog filename            */
281     apr_file_t   *rewritelogfp;       /* the RewriteLog open filepointer    */
282     int           rewriteloglevel;    /* the RewriteLog level of verbosity  */
283 #endif
284     apr_hash_t         *rewritemaps;  /* the RewriteMap entries             */
285     apr_array_header_t *rewriteconds; /* the RewriteCond entries (temp.)    */
286     apr_array_header_t *rewriterules; /* the RewriteRule entries            */
287     server_rec   *server;             /* the corresponding server indicator */
288     int          redirect_limit;      /* max number of internal redirects   */
289 } rewrite_server_conf;
290
291 typedef struct {
292     int           state;              /* the RewriteEngine state           */
293     int           options;            /* the RewriteOption state           */
294     apr_array_header_t *rewriteconds; /* the RewriteCond entries (temp.)   */
295     apr_array_header_t *rewriterules; /* the RewriteRule entries           */
296     char         *directory;          /* the directory where it applies    */
297     const char   *baseurl;            /* the base-URL  where it applies    */
298     int          redirect_limit;      /* max. number of internal redirects */
299 } rewrite_perdir_conf;
300
301 typedef struct {
302     int           redirects;      /* current number of redirects */
303     int           redirect_limit; /* maximum number of redirects */
304 } rewrite_request_conf;
305
306 /* the (per-child) cache structures.
307  */
308 typedef struct cache {
309     apr_pool_t         *pool;
310     apr_hash_t         *maps;
311 #if APR_HAS_THREADS
312     apr_thread_mutex_t *lock;
313 #endif
314 } cache;
315
316 /* cached maps contain an mtime for the whole map and live in a subpool
317  * of the cachep->pool. That makes it easy to forget them if necessary.
318  */
319 typedef struct {
320     apr_time_t mtime;
321     apr_pool_t *pool;
322     apr_hash_t *entries;
323 } cachedmap;
324
325 /* the regex structure for the
326  * substitution of backreferences
327  */
328 typedef struct backrefinfo {
329     char *source;
330     int nsub;
331     regmatch_t regmatch[AP_MAX_REG_MATCH];
332 } backrefinfo;
333
334 /* single linked list used for
335  * variable expansion
336  */
337 typedef struct result_list {
338     struct result_list *next;
339     apr_size_t len;
340     const char *string;
341 } result_list;
342
343 /* context structure for variable lookup and expansion
344  */
345 typedef struct {
346     request_rec *r;
347     const char  *uri;
348     const char  *vary_this;
349     const char  *vary;
350     char        *perdir;
351     backrefinfo briRR;
352     backrefinfo briRC;
353 } rewrite_ctx;
354
355 /*
356  * +-------------------------------------------------------+
357  * |                                                       |
358  * |                 static module data
359  * |                                                       |
360  * +-------------------------------------------------------+
361  */
362
363 /* the global module structure */
364 module AP_MODULE_DECLARE_DATA rewrite_module;
365
366 /* rewritemap int: handler function registry */
367 static apr_hash_t *mapfunc_hash;
368
369 /* the cache */
370 static cache *cachep;
371
372 /* whether proxy module is available or not */
373 static int proxy_available;
374
375 /* whether random seed can be reaped */
376 static int rewrite_rand_init_done = 0;
377
378 /* Locks/Mutexes */
379 static const char *lockname;
380 static apr_global_mutex_t *rewrite_mapr_lock_acquire = NULL;
381
382 #ifndef REWRITELOG_DISABLED
383 static apr_global_mutex_t *rewrite_log_lock = NULL;
384 #endif
385
386 /* Optional functions imported from mod_ssl when loaded: */
387 static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *rewrite_ssl_lookup = NULL;
388 static APR_OPTIONAL_FN_TYPE(ssl_is_https) *rewrite_is_https = NULL;
389
390 /*
391  * +-------------------------------------------------------+
392  * |                                                       |
393  * |              rewriting logfile support
394  * |                                                       |
395  * +-------------------------------------------------------+
396  */
397
398 #ifndef REWRITELOG_DISABLED
399 static char *current_logtime(request_rec *r)
400 {
401     apr_time_exp_t t;
402     char tstr[80];
403     apr_size_t len;
404
405     apr_time_exp_lt(&t, apr_time_now());
406
407     apr_strftime(tstr, &len, sizeof(tstr), "[%d/%b/%Y:%H:%M:%S ", &t);
408     apr_snprintf(tstr+len, sizeof(tstr)-len, "%c%.2d%.2d]",
409                  t.tm_gmtoff < 0 ? '-' : '+',
410                  t.tm_gmtoff / (60*60), t.tm_gmtoff % (60*60));
411
412     return apr_pstrdup(r->pool, tstr);
413 }
414
415 static int open_rewritelog(server_rec *s, apr_pool_t *p)
416 {
417     rewrite_server_conf *conf;
418     const char *fname;
419
420     conf = ap_get_module_config(s->module_config, &rewrite_module);
421
422     /* - no logfile configured
423      * - logfilename empty
424      * - virtual log shared w/ main server
425      */
426     if (!conf->rewritelogfile || !*conf->rewritelogfile || conf->rewritelogfp) {
427         return 1;
428     }
429
430     if (*conf->rewritelogfile == '|') {
431         piped_log *pl;
432
433         fname = ap_server_root_relative(p, conf->rewritelogfile+1);
434         if (!fname) {
435             ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s,
436                          "mod_rewrite: Invalid RewriteLog "
437                          "path %s", conf->rewritelogfile+1);
438             return 0;
439         }
440
441         if ((pl = ap_open_piped_log(p, fname)) == NULL) {
442             ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
443                          "mod_rewrite: could not open reliable pipe "
444                          "to RewriteLog filter %s", fname);
445             return 0;
446         }
447         conf->rewritelogfp = ap_piped_log_write_fd(pl);
448     }
449     else {
450         apr_status_t rc;
451
452         fname = ap_server_root_relative(p, conf->rewritelogfile);
453         if (!fname) {
454             ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s,
455                          "mod_rewrite: Invalid RewriteLog "
456                          "path %s", conf->rewritelogfile);
457             return 0;
458         }
459
460         if ((rc = apr_file_open(&conf->rewritelogfp, fname,
461                                 REWRITELOG_FLAGS, REWRITELOG_MODE, p))
462                 != APR_SUCCESS) {
463             ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
464                          "mod_rewrite: could not open RewriteLog "
465                          "file %s", fname);
466             return 0;
467         }
468     }
469
470     return 1;
471 }
472
473 static void do_rewritelog(request_rec *r, int level, char *perdir,
474                           const char *fmt, ...)
475 {
476     rewrite_server_conf *conf;
477     char *logline, *text;
478     const char *rhost, *rname;
479     apr_size_t nbytes;
480     int redir;
481     apr_status_t rv;
482     request_rec *req;
483     va_list ap;
484
485     conf = ap_get_module_config(r->server->module_config, &rewrite_module);
486
487     if (!conf->rewritelogfp || level > conf->rewriteloglevel) {
488         return;
489     }
490
491     rhost = ap_get_remote_host(r->connection, r->per_dir_config,
492                                REMOTE_NOLOOKUP, NULL);
493     rname = ap_get_remote_logname(r);
494
495     for (redir=0, req=r; req->prev; req = req->prev) {
496         ++redir;
497     }
498
499     va_start(ap, fmt);
500     text = apr_pvsprintf(r->pool, fmt, ap);
501     va_end(ap);
502
503     logline = apr_psprintf(r->pool, "%s %s %s %s [%s/sid#%pp][rid#%pp/%s%s%s] "
504                                     "(%d) %s%s%s%s" APR_EOL_STR,
505                            rhost ? rhost : "UNKNOWN-HOST",
506                            rname ? rname : "-",
507                            r->user ? (*r->user ? r->user : "\"\"") : "-",
508                            current_logtime(r),
509                            ap_get_server_name(r),
510                            (void *)(r->server),
511                            (void *)r,
512                            r->main ? "subreq" : "initial",
513                            redir ? "/redir#" : "",
514                            redir ? apr_itoa(r->pool, redir) : "",
515                            level,
516                            perdir ? "[perdir " : "",
517                            perdir ? perdir : "",
518                            perdir ? "] ": "",
519                            text);
520
521     rv = apr_global_mutex_lock(rewrite_log_lock);
522     if (rv != APR_SUCCESS) {
523         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
524                       "apr_global_mutex_lock(rewrite_log_lock) failed");
525         /* XXX: Maybe this should be fatal? */
526     }
527
528     nbytes = strlen(logline);
529     apr_file_write(conf->rewritelogfp, logline, &nbytes);
530
531     rv = apr_global_mutex_unlock(rewrite_log_lock);
532     if (rv != APR_SUCCESS) {
533         ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
534                       "apr_global_mutex_unlock(rewrite_log_lock) failed");
535         /* XXX: Maybe this should be fatal? */
536     }
537
538     return;
539 }
540 #endif /* !REWRITELOG_DISABLED */
541
542
543 /*
544  * +-------------------------------------------------------+
545  * |                                                       |
546  * |                URI and path functions
547  * |                                                       |
548  * +-------------------------------------------------------+
549  */
550
551 /* return number of chars of the scheme (incl. '://')
552  * if the URI is absolute (includes a scheme etc.)
553  * otherwise 0.
554  *
555  * NOTE: If you add new schemes here, please have a
556  *       look at escape_absolute_uri and splitout_queryargs.
557  *       Not every scheme takes query strings and some schemes
558  *       may be handled in a special way.
559  *
560  * XXX: we may consider a scheme registry, perhaps with
561  *      appropriate escape callbacks to allow other modules
562  *      to extend mod_rewrite at runtime.
563  */
564 static unsigned is_absolute_uri(char *uri)
565 {
566     /* fast exit */
567     if (*uri == '/' || strlen(uri) <= 5) {
568         return 0;
569     }
570
571     switch (*uri++) {
572     case 'f':
573     case 'F':
574         if (!strncasecmp(uri, "tp://", 5)) {        /* ftp://    */
575             return 6;
576         }
577         break;
578
579     case 'g':
580     case 'G':
581         if (!strncasecmp(uri, "opher://", 8)) {     /* gopher:// */
582             return 9;
583         }
584         break;
585
586     case 'h':
587     case 'H':
588         if (!strncasecmp(uri, "ttp://", 6)) {       /* http://   */
589             return 7;
590         }
591         else if (!strncasecmp(uri, "ttps://", 7)) { /* https://  */
592             return 8;
593         }
594         break;
595
596     case 'l':
597     case 'L':
598         if (!strncasecmp(uri, "dap://", 6)) {       /* ldap://   */
599             return 7;
600         }
601         break;
602
603     case 'm':
604     case 'M':
605         if (!strncasecmp(uri, "ailto:", 6)) {       /* mailto:   */
606             return 7;
607         }
608         break;
609
610     case 'n':
611     case 'N':
612         if (!strncasecmp(uri, "ews:", 4)) {         /* news:     */
613             return 5;
614         }
615         else if (!strncasecmp(uri, "ntp://", 6)) {  /* nntp://   */
616             return 7;
617         }
618         break;
619     }
620
621     return 0;
622 }
623
624 /*
625  * escape absolute uri, which may or may not be path oriented.
626  * So let's handle them differently.
627  */
628 static char *escape_absolute_uri(apr_pool_t *p, char *uri, unsigned scheme)
629 {
630     char *cp;
631
632     /* be safe.
633      * NULL should indicate elsewhere, that something's wrong
634      */
635     if (!scheme || strlen(uri) < scheme) {
636         return NULL;
637     }
638
639     cp = uri + scheme;
640
641     /* scheme with authority part? */
642     if (cp[-1] == '/') {
643         /* skip host part */
644         while (*cp && *cp != '/') {
645             ++cp;
646         }
647
648         /* nothing after the hostpart. ready! */
649         if (!*cp || !*++cp) {
650             return apr_pstrdup(p, uri);
651         }
652
653         /* remember the hostname stuff */
654         scheme = cp - uri;
655
656         /* special thing for ldap.
657          * The parts are separated by question marks. From RFC 2255:
658          *     ldapurl = scheme "://" [hostport] ["/"
659          *               [dn ["?" [attributes] ["?" [scope]
660          *               ["?" [filter] ["?" extensions]]]]]]
661          */
662         if (!strncasecmp(uri, "ldap", 4)) {
663             char *token[5];
664             int c = 0;
665
666             token[0] = cp = apr_pstrdup(p, cp);
667             while (*cp && c < 5) {
668                 if (*cp == '?') {
669                     token[++c] = cp + 1;
670                     *cp = '\0';
671                 }
672                 ++cp;
673             }
674
675             return apr_pstrcat(p, apr_pstrndup(p, uri, scheme),
676                                           ap_escape_uri(p, token[0]),
677                                (c >= 1) ? "?" : NULL,
678                                (c >= 1) ? ap_escape_uri(p, token[1]) : NULL,
679                                (c >= 2) ? "?" : NULL,
680                                (c >= 2) ? ap_escape_uri(p, token[2]) : NULL,
681                                (c >= 3) ? "?" : NULL,
682                                (c >= 3) ? ap_escape_uri(p, token[3]) : NULL,
683                                (c >= 4) ? "?" : NULL,
684                                (c >= 4) ? ap_escape_uri(p, token[4]) : NULL,
685                                NULL);
686         }
687     }
688
689     /* Nothing special here. Apply normal escaping. */
690     return apr_pstrcat(p, apr_pstrndup(p, uri, scheme),
691                        ap_escape_uri(p, cp), NULL);
692 }
693
694 /*
695  * split out a QUERY_STRING part from
696  * the current URI string
697  */
698 static void splitout_queryargs(request_rec *r, int qsappend)
699 {
700     char *q;
701
702     /* don't touch, unless it's an http or mailto URL.
703      * See RFC 1738 and RFC 2368.
704      */
705     if (   is_absolute_uri(r->filename)
706         && strncasecmp(r->filename, "http", 4)
707         && strncasecmp(r->filename, "mailto", 6)) {
708         r->args = NULL; /* forget the query that's still flying around */
709         return;
710     }
711
712     q = ap_strchr(r->filename, '?');
713     if (q != NULL) {
714         char *olduri;
715         apr_size_t len;
716
717         olduri = apr_pstrdup(r->pool, r->filename);
718         *q++ = '\0';
719         if (qsappend) {
720             r->args = apr_pstrcat(r->pool, q, "&", r->args, NULL);
721         }
722         else {
723             r->args = apr_pstrdup(r->pool, q);
724         }
725
726         len = strlen(r->args);
727         if (!len) {
728             r->args = NULL;
729         }
730         else if (r->args[len-1] == '&') {
731             r->args[len-1] = '\0';
732         }
733
734         rewritelog((r, 3, NULL, "split uri=%s -> uri=%s, args=%s", olduri,
735                     r->filename, r->args ? r->args : "<none>"));
736     }
737
738     return;
739 }
740
741 /*
742  * strip 'http[s]://ourhost/' from URI
743  */
744 static void reduce_uri(request_rec *r)
745 {
746     char *cp;
747     apr_size_t l;
748
749     cp = (char *)ap_http_method(r);
750     l  = strlen(cp);
751     if (   strlen(r->filename) > l+3
752         && strncasecmp(r->filename, cp, l) == 0
753         && r->filename[l]   == ':'
754         && r->filename[l+1] == '/'
755         && r->filename[l+2] == '/' ) {
756
757         unsigned short port;
758         char *portp, *host, *url, *scratch;
759
760         scratch = apr_pstrdup(r->pool, r->filename); /* our scratchpad */
761
762         /* cut the hostname and port out of the URI */
763         cp = host = scratch + l + 3;    /* 3 == strlen("://") */
764         while (*cp && *cp != '/' && *cp != ':') {
765             ++cp;
766         }
767
768         if (*cp == ':') {      /* additional port given */
769             *cp++ = '\0';
770             portp = cp;
771             while (*cp && *cp != '/') {
772                 ++cp;
773             }
774             *cp = '\0';
775
776             port = atoi(portp);
777             url = r->filename + (cp - scratch);
778             if (!*url) {
779                 url = "/";
780             }
781         }
782         else if (*cp == '/') { /* default port */
783             *cp = '\0';
784
785             port = ap_default_port(r);
786             url = r->filename + (cp - scratch);
787         }
788         else {
789             port = ap_default_port(r);
790             url = "/";
791         }
792
793         /* now check whether we could reduce it to a local path... */
794         if (ap_matches_request_vhost(r, host, port)) {
795             rewritelog((r, 3, NULL, "reduce %s -> %s", r->filename, url));
796             r->filename = apr_pstrdup(r->pool, url);
797         }
798     }
799
800     return;
801 }
802
803 /*
804  * add 'http[s]://ourhost[:ourport]/' to URI
805  * if URI is still not fully qualified
806  */
807 static void fully_qualify_uri(request_rec *r)
808 {
809     if (!is_absolute_uri(r->filename)) {
810         const char *thisserver;
811         char *thisport;
812         int port;
813
814         thisserver = ap_get_server_name(r);
815         port = ap_get_server_port(r);
816         thisport = ap_is_default_port(port, r)
817                    ? ""
818                    : apr_psprintf(r->pool, ":%u", port);
819
820         r->filename = apr_psprintf(r->pool, "%s://%s%s%s%s",
821                                    ap_http_method(r), thisserver, thisport,
822                                    (*r->filename == '/') ? "" : "/",
823                                    r->filename);
824     }
825
826     return;
827 }
828
829 /*
830  * stat() only the first segment of a path
831  */
832 static int prefix_stat(const char *path, apr_pool_t *pool)
833 {
834     const char *curpath = path;
835     const char *root;
836     const char *slash;
837     char *statpath;
838     apr_status_t rv;
839
840     rv = apr_filepath_root(&root, &curpath, APR_FILEPATH_TRUENAME, pool);
841
842     if (rv != APR_SUCCESS) {
843         return 0;
844     }
845
846     /* let's recognize slashes only, the mod_rewrite semantics are opaque
847      * enough.
848      */
849     if ((slash = ap_strchr_c(curpath, '/')) != NULL) {
850         rv = apr_filepath_merge(&statpath, root,
851                                 apr_pstrndup(pool, curpath,
852                                              (apr_size_t)(slash - curpath)),
853                                 APR_FILEPATH_NOTABOVEROOT |
854                                 APR_FILEPATH_NOTRELATIVE, pool);
855     }
856     else {
857         rv = apr_filepath_merge(&statpath, root, curpath,
858                                 APR_FILEPATH_NOTABOVEROOT |
859                                 APR_FILEPATH_NOTRELATIVE, pool);
860     }
861
862     if (rv == APR_SUCCESS) {
863         apr_finfo_t sb;
864         
865         if (apr_stat(&sb, statpath, APR_FINFO_MIN, pool) == APR_SUCCESS) {
866             return 1;
867         }
868     }
869
870     return 0;
871 }
872
873 /*
874  * substitute the prefix path 'match' in 'input' with 'subst' (RewriteBase)
875  */
876 static char *subst_prefix_path(request_rec *r, char *input, char *match,
877                                const char *subst)
878 {
879     apr_size_t len = strlen(match);
880
881     if (len && match[len - 1] == '/') {
882         --len;
883     }
884
885     if (!strncmp(input, match, len) && input[len++] == '/') {
886         apr_size_t slen, outlen;
887         char *output;
888
889         rewritelog((r, 5, NULL, "strip matching prefix: %s -> %s", input,
890                     input+len));
891
892         slen = strlen(subst);
893         if (slen && subst[slen - 1] != '/') {
894             ++slen;
895         }
896
897         outlen = strlen(input) + slen - len;
898         output = apr_palloc(r->pool, outlen + 1); /* don't forget the \0 */
899
900         memcpy(output, subst, slen);
901         if (slen && !output[slen-1]) {
902             output[slen-1] = '/';
903         }
904         memcpy(output+slen, input+len, outlen - slen);
905         output[outlen] = '\0';
906
907         rewritelog((r, 4, NULL, "add subst prefix: %s -> %s", input+len,
908                     output));
909
910         return output;
911     }
912
913     /* prefix didn't match */
914     return input;
915 }
916
917
918 /*
919  * +-------------------------------------------------------+
920  * |                                                       |
921  * |                    caching support
922  * |                                                       |
923  * +-------------------------------------------------------+
924  */
925
926 static void set_cache_value(const char *name, apr_time_t t, char *key,
927                             char *val)
928 {
929     cachedmap *map;
930
931     if (cachep) {
932 #if APR_HAS_THREADS
933         apr_thread_mutex_lock(cachep->lock);
934 #endif
935         map = apr_hash_get(cachep->maps, name, APR_HASH_KEY_STRING);
936
937         if (!map) {
938             apr_pool_t *p;
939
940             if (apr_pool_create(&p, cachep->pool) != APR_SUCCESS) {
941 #if APR_HAS_THREADS
942                 apr_thread_mutex_unlock(cachep->lock);
943 #endif
944                 return;
945             }
946
947             map = apr_palloc(cachep->pool, sizeof(cachedmap));
948             map->pool = p;
949             map->entries = apr_hash_make(map->pool);
950             map->mtime = t;
951
952             apr_hash_set(cachep->maps, name, APR_HASH_KEY_STRING, map);
953         }
954         else if (map->mtime != t) {
955             apr_pool_clear(map->pool);
956             map->entries = apr_hash_make(map->pool);
957             map->mtime = t;
958         }
959
960         /* Now we should have a valid map->entries hash, where we
961          * can store our value.
962          *
963          * We need to copy the key and the value into OUR pool,
964          * so that we don't leave it during the r->pool cleanup.
965          */
966         apr_hash_set(map->entries,
967                      apr_pstrdup(map->pool, key), APR_HASH_KEY_STRING,
968                      apr_pstrdup(map->pool, val));
969
970 #if APR_HAS_THREADS
971         apr_thread_mutex_unlock(cachep->lock);
972 #endif
973     }
974
975     return;
976 }
977
978 static char *get_cache_value(const char *name, apr_time_t t, char *key,
979                              apr_pool_t *p)
980 {
981     cachedmap *map;
982     char *val = NULL;
983
984     if (cachep) {
985 #if APR_HAS_THREADS
986         apr_thread_mutex_lock(cachep->lock);
987 #endif
988         map = apr_hash_get(cachep->maps, name, APR_HASH_KEY_STRING);
989
990         if (map) {
991             /* if this map is outdated, forget it. */
992             if (map->mtime != t) {
993                 apr_pool_clear(map->pool);
994                 map->entries = apr_hash_make(map->pool);
995                 map->mtime = t;
996             }
997             else {
998                 val = apr_hash_get(map->entries, key, APR_HASH_KEY_STRING);
999                 if (val) {
1000                     /* copy the cached value into the supplied pool,
1001                      * where it belongs (r->pool usually)
1002                      */
1003                     val = apr_pstrdup(p, val);
1004                 }
1005             }
1006         }
1007
1008 #if APR_HAS_THREADS
1009         apr_thread_mutex_unlock(cachep->lock);
1010 #endif
1011     }
1012
1013     return val;
1014 }
1015
1016 static int init_cache(apr_pool_t *p)
1017 {
1018     cachep = apr_palloc(p, sizeof(cache));
1019     if (apr_pool_create(&cachep->pool, p) != APR_SUCCESS) {
1020         cachep = NULL; /* turns off cache */
1021         return 0;
1022     }
1023
1024     cachep->maps = apr_hash_make(cachep->pool);
1025 #if APR_HAS_THREADS
1026     (void)apr_thread_mutex_create(&(cachep->lock), APR_THREAD_MUTEX_DEFAULT, p);
1027 #endif
1028
1029     return 1;
1030 }
1031
1032
1033 /*
1034  * +-------------------------------------------------------+
1035  * |                                                       |
1036  * |                    Map Functions
1037  * |                                                       |
1038  * +-------------------------------------------------------+
1039  */
1040
1041 /*
1042  * General Note: key is already a fresh string, created (expanded) just
1043  * for the purpose to be passed in here. So one can modify key itself.
1044  */
1045
1046 static char *rewrite_mapfunc_toupper(request_rec *r, char *key)
1047 {
1048     char *p;
1049
1050     for (p = key; *p; ++p) {
1051         *p = apr_toupper(*p);
1052     }
1053
1054     return key;
1055 }
1056
1057 static char *rewrite_mapfunc_tolower(request_rec *r, char *key)
1058 {
1059     char *p;
1060
1061     for (p = key; *p; ++p) {
1062         *p = apr_tolower(*p);
1063     }
1064
1065     return key;
1066 }
1067
1068 static char *rewrite_mapfunc_escape(request_rec *r, char *key)
1069 {
1070     return ap_escape_uri(r->pool, key);
1071 }
1072
1073 static char *rewrite_mapfunc_unescape(request_rec *r, char *key)
1074 {
1075     ap_unescape_url(key);
1076
1077     return key;
1078 }
1079
1080 static char *select_random_value_part(request_rec *r, char *value)
1081 {
1082     char *p = value;
1083     unsigned n = 1;
1084
1085     /* count number of distinct values */
1086     while ((p = ap_strchr(p, '|')) != NULL) {
1087         ++n;
1088         ++p;
1089     }
1090
1091     if (n > 1) {
1092         /* initialize random generator
1093          *
1094          * XXX: Probably this should be wrapped into a thread mutex,
1095          * shouldn't it? Is it worth the effort?
1096          */
1097         if (!rewrite_rand_init_done) {
1098             srand((unsigned)(getpid()));
1099             rewrite_rand_init_done = 1;
1100         }
1101
1102         /* select a random subvalue */
1103         n = (int)(((double)(rand() % RAND_MAX) / RAND_MAX) * n + 1);
1104
1105         /* extract it from the whole string */
1106         while (--n && (value = ap_strchr(value, '|')) != NULL) {
1107             ++value;
1108         }
1109
1110         if (value) { /* should not be NULL, but ... */
1111             p = ap_strchr(value, '|');
1112             if (p) {
1113                 *p = '\0';
1114             }
1115         }
1116     }
1117
1118     return value;
1119 }
1120
1121 /* child process code */
1122 static void rewrite_child_errfn(apr_pool_t *p, apr_status_t err,
1123                                 const char *desc)
1124 {
1125     ap_log_error(APLOG_MARK, APLOG_ERR, err, NULL, "%s", desc);
1126 }
1127
1128 static apr_status_t rewritemap_program_child(apr_pool_t *p,
1129                                              const char *progname, char **argv,
1130                                              apr_file_t **fpout,
1131                                              apr_file_t **fpin)
1132 {
1133     apr_status_t rc;
1134     apr_procattr_t *procattr;
1135     apr_proc_t *procnew;
1136
1137     if (   APR_SUCCESS == (rc=apr_procattr_create(&procattr, p))
1138         && APR_SUCCESS == (rc=apr_procattr_io_set(procattr, APR_FULL_BLOCK,
1139                                                   APR_FULL_BLOCK, APR_NO_PIPE))
1140         && APR_SUCCESS == (rc=apr_procattr_dir_set(procattr,
1141                                              ap_make_dirstr_parent(p, argv[0])))
1142         && APR_SUCCESS == (rc=apr_procattr_cmdtype_set(procattr, APR_PROGRAM))
1143         && APR_SUCCESS == (rc=apr_procattr_child_errfn_set(procattr,
1144                                                            rewrite_child_errfn))
1145         && APR_SUCCESS == (rc=apr_procattr_error_check_set(procattr, 1))) {
1146
1147         procnew = apr_pcalloc(p, sizeof(*procnew));
1148         rc = apr_proc_create(procnew, argv[0], (const char **)argv, NULL,
1149                              procattr, p);
1150
1151         if (rc == APR_SUCCESS) {
1152             apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT);
1153
1154             if (fpin) {
1155                 (*fpin) = procnew->in;
1156             }
1157
1158             if (fpout) {
1159                 (*fpout) = procnew->out;
1160             }
1161         }
1162     }
1163
1164     return (rc);
1165 }
1166
1167 static apr_status_t run_rewritemap_programs(server_rec *s, apr_pool_t *p)
1168 {
1169     rewrite_server_conf *conf;
1170     apr_hash_index_t *hi;
1171     apr_status_t rc;
1172     int lock_warning_issued = 0;
1173
1174     conf = ap_get_module_config(s->module_config, &rewrite_module);
1175
1176     /*  If the engine isn't turned on,
1177      *  don't even try to do anything.
1178      */
1179     if (conf->state == ENGINE_DISABLED) {
1180         return APR_SUCCESS;
1181     }
1182
1183     for (hi = apr_hash_first(p, conf->rewritemaps); hi; hi = apr_hash_next(hi)){
1184         apr_file_t *fpin = NULL;
1185         apr_file_t *fpout = NULL;
1186         rewritemap_entry *map;
1187         void *val;
1188
1189         apr_hash_this(hi, NULL, NULL, &val);
1190         map = val;
1191
1192         if (map->type != MAPTYPE_PRG) {
1193             continue;
1194         }
1195         if (!(map->argv[0]) || !*(map->argv[0]) || map->fpin || map->fpout) {
1196             continue;
1197         }
1198
1199         if (!lock_warning_issued && (!lockname || !*lockname)) {
1200             ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s,
1201                          "mod_rewrite: Running external rewrite maps "
1202                          "without defining a RewriteLock is DANGEROUS!");
1203             ++lock_warning_issued;
1204         }
1205
1206         rc = rewritemap_program_child(p, map->argv[0], map->argv,
1207                                       &fpout, &fpin);
1208         if (rc != APR_SUCCESS || fpin == NULL || fpout == NULL) {
1209             ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
1210                          "mod_rewrite: could not start RewriteMap "
1211                          "program %s", map->checkfile);
1212             return rc;
1213         }
1214         map->fpin  = fpin;
1215         map->fpout = fpout;
1216     }
1217
1218     return APR_SUCCESS;
1219 }
1220
1221
1222 /*
1223  * +-------------------------------------------------------+
1224  * |                                                       |
1225  * |                  Lookup functions
1226  * |                                                       |
1227  * +-------------------------------------------------------+
1228  */
1229
1230 static char *lookup_map_txtfile(request_rec *r, const char *file, char *key)
1231 {
1232     apr_file_t *fp = NULL;
1233     char line[REWRITE_MAX_TXT_MAP_LINE + 1]; /* +1 for \0 */
1234     char *value, *keylast;
1235
1236     if (apr_file_open(&fp, file, APR_READ, APR_OS_DEFAULT,
1237                       r->pool) != APR_SUCCESS) {
1238         return NULL;
1239     }
1240
1241     keylast = key + strlen(key);
1242     value = NULL;
1243     while (apr_file_gets(line, sizeof(line), fp) == APR_SUCCESS) {
1244         char *p, *c;
1245
1246         /* ignore comments and lines starting with whitespaces */
1247         if (*line == '#' || apr_isspace(*line)) {
1248             continue;
1249         }
1250
1251         p = line;
1252         c = key;
1253         while (c < keylast && *p == *c && !apr_isspace(*p)) {
1254             ++p;
1255             ++c;
1256         }
1257
1258         /* key doesn't match - ignore. */
1259         if (c != keylast || !apr_isspace(*p)) {
1260             continue;
1261         }
1262
1263         /* jump to the value */
1264         while (*p && apr_isspace(*p)) {
1265             ++p;
1266         }
1267
1268         /* no value? ignore */
1269         if (!*p) {
1270             continue;
1271         }
1272
1273         /* extract the value and return. */
1274         c = p;
1275         while (*p && !apr_isspace(*p)) {
1276             ++p;
1277         }
1278         value = apr_pstrmemdup(r->pool, c, p - c);
1279         break;
1280     }
1281     apr_file_close(fp);
1282
1283     return value;
1284 }
1285
1286 static char *lookup_map_dbmfile(request_rec *r, const char *file,
1287                                 const char *dbmtype, char *key)
1288 {
1289     apr_dbm_t *dbmfp = NULL;
1290     apr_datum_t dbmkey;
1291     apr_datum_t dbmval;
1292     char *value;
1293
1294     if (apr_dbm_open_ex(&dbmfp, dbmtype, file, APR_DBM_READONLY, APR_OS_DEFAULT, 
1295                         r->pool) != APR_SUCCESS) {
1296         return NULL;
1297     }
1298
1299     dbmkey.dptr  = key;
1300     dbmkey.dsize = strlen(key);
1301
1302     if (apr_dbm_fetch(dbmfp, dbmkey, &dbmval) == APR_SUCCESS && dbmval.dptr) {
1303         value = apr_pstrmemdup(r->pool, dbmval.dptr, dbmval.dsize);
1304     }
1305     else {
1306         value = NULL;
1307     }
1308
1309     apr_dbm_close(dbmfp);
1310
1311     return value;
1312 }
1313
1314 static char *lookup_map_program(request_rec *r, apr_file_t *fpin,
1315                                 apr_file_t *fpout, char *key)
1316 {
1317     char *buf;
1318     char c;
1319     apr_size_t i, nbytes, combined_len = 0;
1320     apr_status_t rv;
1321     const char *eol = APR_EOL_STR;
1322     int eolc = 0, found_nl = 0;
1323     result_list *buflist = NULL, *curbuf = NULL;
1324
1325 #ifndef NO_WRITEV
1326     struct iovec iova[2];
1327     apr_size_t niov;
1328 #endif
1329
1330     /* when `RewriteEngine off' was used in the per-server
1331      * context then the rewritemap-programs were not spawned.
1332      * In this case using such a map (usually in per-dir context)
1333      * is useless because it is not available.
1334      *
1335      * newlines in the key leave bytes in the pipe and cause
1336      * bad things to happen (next map lookup will use the chars
1337      * after the \n instead of the new key etc etc - in other words,
1338      * the Rewritemap falls out of sync with the requests).
1339      */
1340     if (fpin == NULL || fpout == NULL || ap_strchr(key, '\n')) {
1341         return NULL;
1342     }
1343
1344     /* take the lock */
1345     if (rewrite_mapr_lock_acquire) {
1346         rv = apr_global_mutex_lock(rewrite_mapr_lock_acquire);
1347         if (rv != APR_SUCCESS) {
1348             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1349                           "apr_global_mutex_lock(rewrite_mapr_lock_acquire) "
1350                           "failed");
1351             return NULL; /* Maybe this should be fatal? */
1352         }
1353     }
1354
1355     /* write out the request key */
1356 #ifdef NO_WRITEV
1357     nbytes = strlen(key);
1358     apr_file_write(fpin, key, &nbytes);
1359     nbytes = 1;
1360     apr_file_write(fpin, "\n", &nbytes);
1361 #else
1362     iova[0].iov_base = key;
1363     iova[0].iov_len = strlen(key);
1364     iova[1].iov_base = "\n";
1365     iova[1].iov_len = 1;
1366
1367     niov = 2;
1368     apr_file_writev(fpin, iova, niov, &nbytes);
1369 #endif
1370
1371     buf = apr_palloc(r->pool, REWRITE_PRG_MAP_BUF + 1);
1372
1373     /* read in the response value */
1374     nbytes = 1;
1375     apr_file_read(fpout, &c, &nbytes);
1376     do {
1377         i = 0;
1378         while (nbytes == 1 && (i < REWRITE_PRG_MAP_BUF)) {
1379             if (c == eol[eolc]) {
1380                 if (!eol[++eolc]) {
1381                     /* remove eol from the buffer */
1382                     --eolc;
1383                     if (i < eolc) {
1384                         curbuf->len -= eolc-i;
1385                         i = 0;
1386                     }
1387                     else {
1388                         i -= eolc;
1389                     }
1390                     ++found_nl;
1391                     break;
1392                 }
1393             }
1394
1395             /* only partial (invalid) eol sequence -> reset the counter */
1396             else if (eolc) {
1397                 eolc = 0;
1398             }
1399
1400             /* catch binary mode, e.g. on Win32 */
1401             else if (c == '\n') {
1402                 ++found_nl;
1403                 break;
1404             }
1405
1406             buf[i++] = c;
1407             apr_file_read(fpout, &c, &nbytes);
1408         }
1409
1410         /* well, if there wasn't a newline yet, we need to read further */
1411         if (buflist || (nbytes == 1 && !found_nl)) {
1412             if (!buflist) {
1413                 curbuf = buflist = apr_palloc(r->pool, sizeof(*buflist));
1414             }
1415             else if (i) {
1416                 curbuf->next = apr_palloc(r->pool, sizeof(*buflist));
1417                 curbuf = curbuf->next;
1418                 
1419             }
1420             curbuf->next = NULL;
1421
1422             if (i) {
1423                 curbuf->string = buf;
1424                 curbuf->len = i;
1425                 combined_len += i;
1426                 buf = apr_palloc(r->pool, REWRITE_PRG_MAP_BUF);
1427             }
1428
1429             if (nbytes == 1 && !found_nl) {
1430                 i = 0;
1431                 continue;
1432             }
1433         }
1434
1435         break;
1436     } while (1);
1437
1438     /* concat the stuff */
1439     if (buflist) {
1440         char *p;
1441
1442         p = buf = apr_palloc(r->pool, combined_len + 1); /* \0 */
1443         while (buflist) {
1444             if (buflist->len) {
1445                 memcpy(p, buflist->string, buflist->len);
1446                 p += buflist->len;
1447             }
1448             buflist = buflist->next;
1449         }
1450         *p = '\0';
1451         i = combined_len;
1452     }
1453     else {
1454         buf[i] = '\0';
1455     }
1456
1457     /* give the lock back */
1458     if (rewrite_mapr_lock_acquire) {
1459         rv = apr_global_mutex_unlock(rewrite_mapr_lock_acquire);
1460         if (rv != APR_SUCCESS) {
1461             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1462                           "apr_global_mutex_unlock(rewrite_mapr_lock_acquire) "
1463                           "failed");
1464             return NULL; /* Maybe this should be fatal? */
1465         }
1466     }
1467
1468     /* catch the "failed" case */
1469     if (i == 4 && !strcasecmp(buf, "NULL")) {
1470         return NULL;
1471     }
1472
1473     return buf;
1474 }
1475
1476 /*
1477  * generic map lookup
1478  */
1479 static char *lookup_map(request_rec *r, char *name, char *key)
1480 {
1481     rewrite_server_conf *conf;
1482     rewritemap_entry *s;
1483     char *value;
1484     apr_finfo_t st;
1485     apr_status_t rv;
1486
1487     /* get map configuration */
1488     conf = ap_get_module_config(r->server->module_config, &rewrite_module);
1489     s = apr_hash_get(conf->rewritemaps, name, APR_HASH_KEY_STRING);
1490
1491     /* map doesn't exist */
1492     if (!s) {
1493         return NULL;
1494     }
1495
1496     switch (s->type) {
1497     /*
1498      * Text file map (perhaps random)
1499      */
1500     case MAPTYPE_RND:
1501     case MAPTYPE_TXT:
1502         rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool);
1503         if (rv != APR_SUCCESS) {
1504             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1505                           "mod_rewrite: can't access text RewriteMap file %s",
1506                           s->checkfile);
1507             rewritelog((r, 1, NULL,
1508                         "can't open RewriteMap file, see error log"));
1509             return NULL;
1510         }
1511
1512         value = get_cache_value(s->cachename, st.mtime, key, r->pool);
1513         if (!value) {
1514             rewritelog((r, 6, NULL,
1515                         "cache lookup FAILED, forcing new map lookup"));
1516
1517             value = lookup_map_txtfile(r, s->datafile, key);
1518             if (!value) {
1519                 rewritelog((r, 5, NULL, "map lookup FAILED: map=%s[txt] key=%s",
1520                             name, key));
1521                 set_cache_value(s->cachename, st.mtime, key, "");
1522                 return NULL;
1523             }
1524
1525             rewritelog((r, 5, NULL,"map lookup OK: map=%s[txt] key=%s -> val=%s",
1526                         name, key, value));
1527             set_cache_value(s->cachename, st.mtime, key, value);
1528         }
1529         else {
1530             rewritelog((r,5,NULL,"cache lookup OK: map=%s[txt] key=%s -> val=%s",
1531                         name, key, value));
1532         }
1533
1534         if (s->type == MAPTYPE_RND && *value) {
1535             value = select_random_value_part(r, value);
1536             rewritelog((r, 5, NULL, "randomly chosen the subvalue `%s'",value));
1537         }
1538
1539         return *value ? value : NULL;
1540
1541     /*
1542      * DBM file map
1543      */
1544     case MAPTYPE_DBM:
1545         rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool);
1546         if (rv != APR_SUCCESS) {
1547             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1548                           "mod_rewrite: can't access DBM RewriteMap file %s",
1549                           s->checkfile);
1550             rewritelog((r, 1, NULL,
1551                         "can't open DBM RewriteMap file, see error log"));
1552             return NULL;
1553         }
1554
1555         value = get_cache_value(s->cachename, st.mtime, key, r->pool);
1556         if (!value) {
1557             rewritelog((r, 6, NULL,
1558                         "cache lookup FAILED, forcing new map lookup"));
1559
1560             value = lookup_map_dbmfile(r, s->datafile, s->dbmtype, key);
1561             if (!value) {
1562                 rewritelog((r, 5, NULL, "map lookup FAILED: map=%s[dbm] key=%s",
1563                             name, key));
1564                 set_cache_value(s->cachename, st.mtime, key, "");
1565                 return NULL;
1566             }
1567
1568             rewritelog((r, 5, NULL, "map lookup OK: map=%s[dbm] key=%s -> "
1569                         "val=%s", name, key, value));
1570
1571             set_cache_value(s->cachename, st.mtime, key, value);
1572             return value;
1573         }
1574
1575         rewritelog((r, 5, NULL, "cache lookup OK: map=%s[dbm] key=%s -> val=%s",
1576                     name, key, value));
1577         return *value ? value : NULL;
1578
1579     /*
1580      * Program file map
1581      */
1582     case MAPTYPE_PRG:
1583         value = lookup_map_program(r, s->fpin, s->fpout, key);
1584         if (!value) {
1585             rewritelog((r, 5,NULL,"map lookup FAILED: map=%s key=%s", name,
1586                         key));
1587             return NULL;
1588         }
1589
1590         rewritelog((r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s",
1591                     name, key, value));
1592         return value;
1593
1594     /*
1595      * Internal Map
1596      */
1597     case MAPTYPE_INT:
1598         value = s->func(r, key);
1599         if (!value) {
1600             rewritelog((r, 5,NULL,"map lookup FAILED: map=%s key=%s", name,
1601                         key));
1602             return NULL;
1603         }
1604
1605         rewritelog((r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s",
1606                     name, key, value));
1607         return value;
1608     }
1609
1610     return NULL;
1611 }
1612
1613 /*
1614  * lookup a HTTP header and set VARY note
1615  */
1616 static const char *lookup_header(const char *name, rewrite_ctx *ctx)
1617 {
1618     const char *val = apr_table_get(ctx->r->headers_in, name);
1619
1620     if (val) {
1621         ctx->vary_this = ctx->vary_this
1622                          ? apr_pstrcat(ctx->r->pool, ctx->vary_this, ", ",
1623                                        name, NULL)
1624                          : apr_pstrdup(ctx->r->pool, name);
1625     }
1626
1627     return val;
1628 }
1629
1630 /*
1631  * lookahead helper function
1632  * Determine the correct URI path in perdir context
1633  */
1634 static APR_INLINE const char *la_u(rewrite_ctx *ctx)
1635 {
1636     rewrite_perdir_conf *conf;
1637
1638     if (*ctx->uri == '/') {
1639         return ctx->uri;
1640     }
1641
1642     conf = ap_get_module_config(ctx->r->per_dir_config, &rewrite_module);
1643
1644     return apr_pstrcat(ctx->r->pool, conf->baseurl
1645                                      ? conf->baseurl : conf->directory,
1646                        ctx->uri, NULL);
1647 }
1648
1649 /*
1650  * generic variable lookup
1651  */
1652 static char *lookup_variable(char *var, rewrite_ctx *ctx)
1653 {
1654     const char *result;
1655     request_rec *r = ctx->r;
1656     apr_size_t varlen = strlen(var);
1657
1658     /* fast exit */
1659     if (varlen < 4) {
1660         return apr_pstrdup(r->pool, "");
1661     }
1662
1663     result = NULL;
1664
1665     /* fast tests for variable length variables (sic) first */
1666     if (var[3] == ':') {
1667         if (var[4] && !strncasecmp(var, "ENV", 3)) {
1668             var += 4;
1669             result = apr_table_get(r->notes, var);
1670
1671             if (!result) {
1672                 result = apr_table_get(r->subprocess_env, var);
1673             }
1674             if (!result) {
1675                 result = getenv(var);
1676             }
1677         }
1678         else if (var[4] && !strncasecmp(var, "SSL", 3) && rewrite_ssl_lookup) {
1679             result = rewrite_ssl_lookup(r->pool, r->server, r->connection, r,
1680                                         var + 4);
1681         }
1682     }
1683     else if (var[4] == ':') {
1684         if (var[5]) {
1685             request_rec *rr;
1686             const char *path;
1687
1688             if (!strncasecmp(var, "HTTP", 4)) {
1689                 result = lookup_header(var+5, ctx);
1690             }
1691             else if (!strncasecmp(var, "LA-U", 4)) {
1692                 if (ctx->uri && subreq_ok(r)) {
1693                     path = ctx->perdir ? la_u(ctx) : ctx->uri;
1694                     rr = ap_sub_req_lookup_uri(path, r, NULL);
1695                     ctx->r = rr;
1696                     result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx));
1697                     ctx->r = r;
1698                     ap_destroy_sub_req(rr);
1699
1700                     rewritelog((r, 5, ctx->perdir, "lookahead: path=%s var=%s "
1701                                 "-> val=%s", path, var+5, result));
1702
1703                     return (char *)result;
1704                 }
1705             }
1706             else if (!strncasecmp(var, "LA-F", 4)) {
1707                 if (ctx->uri && subreq_ok(r)) {
1708                     path = ctx->uri;
1709                     if (ctx->perdir && *path == '/') {
1710                         /* sigh, the user wants a file based subrequest, but
1711                          * we can't do one, since we don't know what the file
1712                          * path is! In this case behave like LA-U.
1713                          */
1714                         rr = ap_sub_req_lookup_uri(path, r, NULL);
1715                     }
1716                     else {
1717                         if (ctx->perdir) {
1718                             rewrite_perdir_conf *conf;
1719
1720                             conf = ap_get_module_config(r->per_dir_config,
1721                                                         &rewrite_module);
1722
1723                             path = apr_pstrcat(r->pool, conf->directory, path,
1724                                                NULL);
1725                         }
1726
1727                         rr = ap_sub_req_lookup_file(path, r, NULL);
1728                     }
1729
1730                     ctx->r = rr;
1731                     result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx));
1732                     ctx->r = r;
1733                     ap_destroy_sub_req(rr);
1734
1735                     rewritelog((r, 5, ctx->perdir, "lookahead: path=%s var=%s "
1736                                 "-> val=%s", path, var+5, result));
1737
1738                     return (char *)result;
1739                 }
1740             }
1741         }
1742     }
1743
1744     /* well, do it the hard way */
1745     else {
1746         char *p;
1747         apr_time_exp_t tm;
1748
1749         /* can't do this above, because of the getenv call */
1750         for (p = var; *p; ++p) {
1751             *p = apr_toupper(*p);
1752         }
1753
1754         switch (varlen) {
1755         case  4:
1756             if (!strcmp(var, "TIME")) {
1757                 apr_time_exp_lt(&tm, apr_time_now());
1758                 result = apr_psprintf(r->pool, "%04d%02d%02d%02d%02d%02d",
1759                                       tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
1760                                       tm.tm_hour, tm.tm_min, tm.tm_sec);
1761                 rewritelog((r, 1, ctx->perdir, "RESULT='%s'", result));
1762                 return (char *)result;
1763             }
1764             break;
1765             
1766         case  5:
1767             if (!strcmp(var, "HTTPS")) {
1768                 int flag = rewrite_is_https && rewrite_is_https(r->connection);
1769                 return apr_pstrdup(r->pool, flag ? "on" : "off");
1770             }
1771             break;
1772
1773         case  8:
1774             switch (var[6]) {
1775             case 'A':
1776                 if (!strcmp(var, "TIME_DAY")) {
1777                     apr_time_exp_lt(&tm, apr_time_now());
1778                     return apr_psprintf(r->pool, "%02d", tm.tm_mday);
1779                 }
1780                 break;
1781
1782             case 'E':
1783                 if (!strcmp(var, "TIME_SEC")) {
1784                     apr_time_exp_lt(&tm, apr_time_now());
1785                     return apr_psprintf(r->pool, "%02d", tm.tm_sec);
1786                 }
1787                 break;
1788
1789             case 'I':
1790                 if (!strcmp(var, "TIME_MIN")) {
1791                     apr_time_exp_lt(&tm, apr_time_now());
1792                     return apr_psprintf(r->pool, "%02d", tm.tm_min);
1793                 }
1794                 break;
1795
1796             case 'O':
1797                 if (!strcmp(var, "TIME_MON")) {
1798                     apr_time_exp_lt(&tm, apr_time_now());
1799                     return apr_psprintf(r->pool, "%02d", tm.tm_mon+1);
1800                 }
1801                 break;
1802             }
1803             break;
1804
1805         case  9:
1806             switch (var[7]) {
1807             case 'A':
1808                 if (var[8] == 'Y' && !strcmp(var, "TIME_WDAY")) {
1809                     apr_time_exp_lt(&tm, apr_time_now());
1810                     return apr_psprintf(r->pool, "%d", tm.tm_wday);
1811                 }
1812                 else if (!strcmp(var, "TIME_YEAR")) {
1813                     apr_time_exp_lt(&tm, apr_time_now());
1814                     return apr_psprintf(r->pool, "%04d", tm.tm_year+1900);
1815                 }
1816                 break;
1817
1818             case 'E':
1819                 if (!strcmp(var, "IS_SUBREQ")) {
1820                     result = (r->main ? "true" : "false");
1821                 }
1822                 break;
1823
1824             case 'F':
1825                 if (!strcmp(var, "PATH_INFO")) {
1826                     result = r->path_info;
1827                 }
1828                 break;
1829
1830             case 'P':
1831                 if (!strcmp(var, "AUTH_TYPE")) {
1832                     result = r->ap_auth_type;
1833                 }
1834                 break;
1835
1836             case 'S':
1837                 if (!strcmp(var, "HTTP_HOST")) {
1838                     result = lookup_header("Host", ctx);
1839                 }
1840                 break;
1841
1842             case 'U':
1843                 if (!strcmp(var, "TIME_HOUR")) {
1844                     apr_time_exp_lt(&tm, apr_time_now());
1845                     return apr_psprintf(r->pool, "%02d", tm.tm_hour);
1846                 }
1847                 break;
1848             }
1849             break;
1850
1851         case 11:
1852             switch (var[8]) {
1853             case 'A':
1854                 if (!strcmp(var, "SERVER_NAME")) {
1855                     result = ap_get_server_name(r);
1856                 }
1857                 break;
1858
1859             case 'D':
1860                 if (*var == 'R' && !strcmp(var, "REMOTE_ADDR")) {
1861                     result = r->connection->remote_ip;
1862                 }
1863                 else if (!strcmp(var, "SERVER_ADDR")) {
1864                     result = r->connection->local_ip;
1865                 }
1866                 break;
1867
1868             case 'E':
1869                 if (*var == 'H' && !strcmp(var, "HTTP_ACCEPT")) {
1870                     result = lookup_header("Accept", ctx);
1871                 }
1872                 else if (!strcmp(var, "THE_REQUEST")) {
1873                     result = r->the_request;
1874                 }
1875                 break;
1876
1877             case 'I':
1878                 if (!strcmp(var, "API_VERSION")) {
1879                     return apr_psprintf(r->pool, "%d:%d",
1880                                         MODULE_MAGIC_NUMBER_MAJOR,
1881                                         MODULE_MAGIC_NUMBER_MINOR);
1882                 }
1883                 break;
1884
1885             case 'K':
1886                 if (!strcmp(var, "HTTP_COOKIE")) {
1887                     result = lookup_header("Cookie", ctx);
1888                 }
1889                 break;
1890
1891             case 'O':
1892                 if (*var == 'S' && !strcmp(var, "SERVER_PORT")) {
1893                     return apr_psprintf(r->pool, "%u", ap_get_server_port(r));
1894                 }
1895                 else if (var[7] == 'H' && !strcmp(var, "REMOTE_HOST")) {
1896                     result = ap_get_remote_host(r->connection,r->per_dir_config,
1897                                                 REMOTE_NAME, NULL);
1898                 }
1899                 else if (!strcmp(var, "REMOTE_PORT")) {
1900                     return apr_itoa(r->pool, r->connection->remote_addr->port);
1901                 }
1902                 break;
1903
1904             case 'S':
1905                 if (*var == 'R' && !strcmp(var, "REMOTE_USER")) {
1906                     result = r->user;
1907                 }
1908                 else if (!strcmp(var, "SCRIPT_USER")) {
1909                     result = "<unknown>";
1910                     if (r->finfo.valid & APR_FINFO_USER) {
1911                         apr_uid_name_get((char **)&result, r->finfo.user,
1912                                          r->pool);
1913                     }
1914                 }
1915                 break;
1916
1917             case 'U':
1918                 if (!strcmp(var, "REQUEST_URI")) {
1919                     result = r->uri;
1920                 }
1921                 break;
1922             }
1923             break;
1924
1925         case 12:
1926             switch (var[3]) {
1927             case 'I':
1928                 if (!strcmp(var, "SCRIPT_GROUP")) {
1929                     result = "<unknown>";
1930                     if (r->finfo.valid & APR_FINFO_GROUP) {
1931                         apr_gid_name_get((char **)&result, r->finfo.group,
1932                                          r->pool);
1933                     }
1934                 }
1935                 break;
1936
1937             case 'O':
1938                 if (!strcmp(var, "REMOTE_IDENT")) {
1939                     result = ap_get_remote_logname(r);
1940                 }
1941                 break;
1942
1943             case 'P':
1944                 if (!strcmp(var, "HTTP_REFERER")) {
1945                     result = lookup_header("Referer", ctx);
1946                 }
1947                 break;
1948
1949             case 'R':
1950                 if (!strcmp(var, "QUERY_STRING")) {
1951                     result = r->args;
1952                 }
1953                 break;
1954
1955             case 'V':
1956                 if (!strcmp(var, "SERVER_ADMIN")) {
1957                     result = r->server->server_admin;
1958                 }
1959                 break;
1960             }
1961             break;
1962
1963         case 13:
1964             if (!strcmp(var, "DOCUMENT_ROOT")) {
1965                 result = ap_document_root(r);
1966             }
1967             break;
1968
1969         case 14:
1970             if (*var == 'H' && !strcmp(var, "HTTP_FORWARDED")) {
1971                 result = lookup_header("Forwarded", ctx);
1972             }
1973             else if (!strcmp(var, "REQUEST_METHOD")) {
1974                 result = r->method;
1975             }
1976             break;
1977
1978         case 15:
1979             switch (var[7]) {
1980             case 'E':
1981                 if (!strcmp(var, "HTTP_USER_AGENT")) {
1982                     result = lookup_header("User-Agent", ctx);
1983                 }
1984                 break;
1985
1986             case 'F':
1987                 if (!strcmp(var, "SCRIPT_FILENAME")) {
1988                     result = r->filename; /* same as request_filename (16) */
1989                 }
1990                 break;
1991
1992             case 'P':
1993                 if (!strcmp(var, "SERVER_PROTOCOL")) {
1994                     result = r->protocol;
1995                 }
1996                 break;
1997
1998             case 'S':
1999                 if (!strcmp(var, "SERVER_SOFTWARE")) {
2000                     result = ap_get_server_version();
2001                 }
2002                 break;
2003             }
2004             break;
2005
2006         case 16:
2007             if (!strcmp(var, "REQUEST_FILENAME")) {
2008                 result = r->filename; /* same as script_filename (15) */
2009             }
2010             break;
2011
2012         case 21:
2013             if (!strcmp(var, "HTTP_PROXY_CONNECTION")) {
2014                 result = lookup_header("Proxy-Connection", ctx);
2015             }
2016             break;
2017         }
2018     }
2019
2020     return apr_pstrdup(r->pool, result ? result : "");
2021 }
2022
2023
2024 /*
2025  * +-------------------------------------------------------+
2026  * |                                                       |
2027  * |                 Expansion functions
2028  * |                                                       |
2029  * +-------------------------------------------------------+
2030  */
2031
2032 /*
2033  * Bracketed expression handling
2034  * s points after the opening bracket
2035  */
2036 static APR_INLINE char *find_closing_curly(char *s)
2037 {
2038     unsigned depth;
2039
2040     for (depth = 1; *s; ++s) {
2041         if (*s == RIGHT_CURLY && --depth == 0) {
2042             return s;
2043         }
2044         else if (*s == LEFT_CURLY) {
2045             ++depth;
2046         }
2047     }
2048
2049     return NULL;
2050 }
2051
2052 static APR_INLINE char *find_char_in_curlies(char *s, int c)
2053 {
2054     unsigned depth;
2055
2056     for (depth = 1; *s; ++s) {
2057         if (*s == c && depth == 1) {
2058             return s;
2059         }
2060         else if (*s == RIGHT_CURLY && --depth == 0) {
2061             return NULL;
2062         }
2063         else if (*s == LEFT_CURLY) {
2064             ++depth;
2065         }
2066     }
2067
2068     return NULL;
2069 }
2070
2071 /* perform all the expansions on the input string
2072  * putting the result into a new string
2073  *
2074  * for security reasons this expansion must be performed in a
2075  * single pass, otherwise an attacker can arrange for the result
2076  * of an earlier expansion to include expansion specifiers that
2077  * are interpreted by a later expansion, producing results that
2078  * were not intended by the administrator.
2079  */
2080 static char *do_expand(char *input, rewrite_ctx *ctx)
2081 {
2082     result_list *result, *current;
2083     result_list sresult[SMALL_EXPANSION];
2084     unsigned spc = 0;
2085     apr_size_t span, inputlen, outlen;
2086     char *p, *c;
2087     apr_pool_t *pool = ctx->r->pool;
2088
2089     span = strcspn(input, "\\$%");
2090     inputlen = strlen(input);
2091
2092     /* fast exit */
2093     if (inputlen == span) {
2094         return apr_pstrdup(pool, input);
2095     }
2096
2097     /* well, actually something to do */
2098     result = current = &(sresult[spc++]);
2099
2100     p = input + span;
2101     current->next = NULL;
2102     current->string = input;
2103     current->len = span;
2104     outlen = span;
2105
2106     /* loop for specials */
2107     do {
2108         /* prepare next entry */
2109         if (current->len) {
2110             current->next = (spc < SMALL_EXPANSION)
2111                             ? &(sresult[spc++])
2112                             : (result_list *)apr_palloc(pool,
2113                                                         sizeof(result_list));
2114             current = current->next;
2115             current->next = NULL;
2116             current->len = 0;
2117         }
2118
2119         /* escaped character */
2120         if (*p == '\\') {
2121             current->len = 1;
2122             ++outlen;
2123             if (!p[1]) {
2124                 current->string = p;
2125                 break;
2126             }
2127             else {
2128                 current->string = ++p;
2129                 ++p;
2130             }
2131         }
2132
2133         /* variable or map lookup */
2134         else if (p[1] == '{') {
2135             char *endp;
2136
2137             endp = find_closing_curly(p+2);
2138             if (!endp) {
2139                 current->len = 2;
2140                 current->string = p;
2141                 outlen += 2;
2142                 p += 2;
2143             }
2144
2145             /* variable lookup */
2146             else if (*p == '%') {
2147                 p = lookup_variable(apr_pstrmemdup(pool, p+2, endp-p-2), ctx);
2148
2149                 span = strlen(p);
2150                 current->len = span;
2151                 current->string = p;
2152                 outlen += span;
2153                 p = endp + 1;
2154             }
2155
2156             /* map lookup */
2157             else {     /* *p == '$' */
2158                 char *key;
2159
2160                 /*
2161                  * To make rewrite maps useful, the lookup key and
2162                  * default values must be expanded, so we make
2163                  * recursive calls to do the work. For security
2164                  * reasons we must never expand a string that includes
2165                  * verbatim data from the network. The recursion here
2166                  * isn't a problem because the result of expansion is
2167                  * only passed to lookup_map() so it cannot be
2168                  * re-expanded, only re-looked-up. Another way of
2169                  * looking at it is that the recursion is entirely
2170                  * driven by the syntax of the nested curly brackets.
2171                  */
2172
2173                 key = find_char_in_curlies(p+2, ':');
2174                 if (!key) {
2175                     current->len = 2;
2176                     current->string = p;
2177                     outlen += 2;
2178                     p += 2;
2179                 }
2180                 else {
2181                     char *map, *dflt;
2182
2183                     map = apr_pstrmemdup(pool, p+2, endp-p-2);
2184                     key = map + (key-p-2);
2185                     *key++ = '\0';
2186                     dflt = find_char_in_curlies(key, '|');
2187                     if (dflt) {
2188                         *dflt++ = '\0';
2189                     }
2190
2191                     /* reuse of key variable as result */
2192                     key = lookup_map(ctx->r, map, do_expand(key, ctx));
2193
2194                     if (!key && dflt && *dflt) {
2195                         key = do_expand(dflt, ctx);
2196                     }
2197
2198                     if (key) {
2199                         span = strlen(key);
2200                         current->len = span;
2201                         current->string = key;
2202                         outlen += span;
2203                     }
2204
2205                     p = endp + 1;
2206                 }
2207             }
2208         }
2209
2210         /* backreference */
2211         else if (apr_isdigit(p[1])) {
2212             int n = p[1] - '0';
2213             backrefinfo *bri = (*p == '$') ? &ctx->briRR : &ctx->briRC;
2214
2215             /* see ap_pregsub() in server/util.c */
2216             if (bri->source && n < AP_MAX_REG_MATCH
2217                 && bri->regmatch[n].rm_eo > bri->regmatch[n].rm_so) {
2218                 span = bri->regmatch[n].rm_eo - bri->regmatch[n].rm_so;
2219
2220                 current->len = span;
2221                 current->string = bri->source + bri->regmatch[n].rm_so;
2222                 outlen += span;
2223             }
2224
2225             p += 2;
2226         }
2227
2228         /* not for us, just copy it */
2229         else {
2230             current->len = 1;
2231             current->string = p++;
2232             ++outlen;
2233         }
2234
2235         /* check the remainder */
2236         if (*p && (span = strcspn(p, "\\$%")) > 0) {
2237             if (current->len) {
2238                 current->next = (spc < SMALL_EXPANSION)
2239                                 ? &(sresult[spc++])
2240                                 : (result_list *)apr_palloc(pool,
2241                                                            sizeof(result_list));
2242                 current = current->next;
2243                 current->next = NULL;
2244             }
2245
2246             current->len = span;
2247             current->string = p;
2248             p += span;
2249             outlen += span;
2250         }
2251
2252     } while (p < input+inputlen);
2253
2254     /* assemble result */
2255     c = p = apr_palloc(pool, outlen + 1); /* don't forget the \0 */
2256     do {
2257         if (result->len) {
2258             ap_assert(c+result->len <= p+outlen); /* XXX: can be removed after
2259                                                    * extensive testing and
2260                                                    * review
2261                                                    */
2262             memcpy(c, result->string, result->len);
2263             c += result->len;
2264         }
2265         result = result->next;
2266     } while (result);
2267
2268     p[outlen] = '\0';
2269
2270     return p;
2271 }
2272
2273 /*
2274  * perform all the expansions on the environment variables
2275  */
2276 static void do_expand_env(data_item *env, rewrite_ctx *ctx)
2277 {
2278     char *name, *val;
2279
2280     while (env) {
2281         name = do_expand(env->data, ctx);
2282         if ((val = ap_strchr(name, ':')) != NULL) {
2283             *val++ = '\0';
2284
2285             apr_table_set(ctx->r->subprocess_env, name, val);
2286             rewritelog((ctx->r, 5, NULL, "setting env variable '%s' to '%s'",
2287                         name, val));
2288         }
2289
2290         env = env->next;
2291     }
2292
2293     return;
2294 }
2295
2296 /*
2297  * perform all the expansions on the cookies
2298  *
2299  * TODO: use cached time similar to how logging does it
2300  */
2301 static void add_cookie(request_rec *r, char *s)
2302 {
2303     char *var;
2304     char *val;
2305     char *domain;
2306     char *expires;
2307     char *path;
2308
2309     char *tok_cntx;
2310     char *cookie;
2311
2312     var = apr_strtok(s, ":", &tok_cntx);
2313     val = apr_strtok(NULL, ":", &tok_cntx);
2314     domain = apr_strtok(NULL, ":", &tok_cntx);
2315
2316     if (var && val && domain) {
2317         request_rec *rmain = r;
2318         char *notename;
2319         void *data;
2320
2321         while (rmain->main) {
2322             rmain = rmain->main;
2323         }
2324
2325         notename = apr_pstrcat(rmain->pool, var, "_rewrite", NULL);
2326         apr_pool_userdata_get(&data, notename, rmain->pool);
2327         if (!data) {
2328             char *exp_time = NULL;
2329
2330             expires = apr_strtok(NULL, ":", &tok_cntx);
2331             path = expires ? apr_strtok(NULL, ":", &tok_cntx) : NULL;
2332
2333             if (expires) {
2334                 apr_time_exp_t tms;
2335                 apr_time_exp_gmt(&tms, r->request_time
2336                                      + apr_time_from_sec((60 * atol(expires))));
2337                 exp_time = apr_psprintf(r->pool, "%s, %.2d-%s-%.4d "
2338                                                  "%.2d:%.2d:%.2d GMT",
2339                                         apr_day_snames[tms.tm_wday],
2340                                         tms.tm_mday,
2341                                         apr_month_snames[tms.tm_mon],
2342                                         tms.tm_year+1900,
2343                                         tms.tm_hour, tms.tm_min, tms.tm_sec);
2344             }
2345
2346             cookie = apr_pstrcat(rmain->pool,
2347                                  var, "=", val,
2348                                  "; path=", path ? path : "/",
2349                                  "; domain=", domain,
2350                                  expires ? "; expires=" : NULL,
2351                                  expires ? exp_time : NULL,
2352                                  NULL);
2353
2354             apr_table_addn(rmain->err_headers_out, "Set-Cookie", cookie);
2355             apr_pool_userdata_set("set", notename, NULL, rmain->pool);
2356             rewritelog((rmain, 5, NULL, "setting cookie '%s'", cookie));
2357         }
2358         else {
2359             rewritelog((rmain, 5, NULL, "skipping already set cookie '%s'",
2360                         var));
2361         }
2362     }
2363
2364     return;
2365 }
2366
2367 static void do_expand_cookie(data_item *cookie, rewrite_ctx *ctx)
2368 {
2369     while (cookie) {
2370         add_cookie(ctx->r, do_expand(cookie->data, ctx));
2371         cookie = cookie->next;
2372     }
2373
2374     return;
2375 }
2376
2377 #if APR_HAS_USER
2378 /*
2379  * Expand tilde-paths (/~user) through Unix /etc/passwd
2380  * database information (or other OS-specific database)
2381  */
2382 static char *expand_tildepaths(request_rec *r, char *uri)
2383 {
2384     if (uri && *uri == '/' && uri[1] == '~') {
2385         char *p, *user;
2386
2387         p = user = uri + 2;
2388         while (*p && *p != '/') {
2389             ++p;
2390         }
2391
2392         if (p > user) {
2393             char *homedir;
2394
2395             user = apr_pstrmemdup(r->pool, user, p-user);
2396             if (apr_uid_homepath_get(&homedir, user, r->pool) == APR_SUCCESS) {
2397                 if (*p) {
2398                     /* reuse of user variable */
2399                     user = homedir + strlen(homedir) - 1;
2400                     if (user >= homedir && *user == '/') {
2401                         *user = '\0';
2402                     }
2403
2404                     return apr_pstrcat(r->pool, homedir, p, NULL);
2405                 }
2406                 else {
2407                     return homedir;
2408                 }
2409             }
2410         }
2411     }
2412
2413     return uri;
2414 }
2415 #endif  /* if APR_HAS_USER */
2416
2417
2418 /*
2419  * +-------------------------------------------------------+
2420  * |                                                       |
2421  * |              rewriting lockfile support
2422  * |                                                       |
2423  * +-------------------------------------------------------+
2424  */
2425
2426 static apr_status_t rewritelock_create(server_rec *s, apr_pool_t *p)
2427 {
2428     apr_status_t rc;
2429
2430     /* only operate if a lockfile is used */
2431     if (lockname == NULL || *(lockname) == '\0') {
2432         return APR_SUCCESS;
2433     }
2434
2435     /* create the lockfile */
2436     rc = apr_global_mutex_create(&rewrite_mapr_lock_acquire, lockname,
2437                                  APR_LOCK_DEFAULT, p);
2438     if (rc != APR_SUCCESS) {
2439         ap_log_error(APLOG_MARK, APLOG_CRIT, rc, s,
2440                      "mod_rewrite: Parent could not create RewriteLock "
2441                      "file %s", lockname);
2442         return rc;
2443     }
2444
2445 #ifdef MOD_REWRITE_SET_MUTEX_PERMS
2446     rc = unixd_set_global_mutex_perms(rewrite_mapr_lock_acquire);
2447     if (rc != APR_SUCCESS) {
2448         ap_log_error(APLOG_MARK, APLOG_CRIT, rc, s,
2449                      "mod_rewrite: Parent could not set permissions "
2450                      "on RewriteLock; check User and Group directives");
2451         return rc;
2452     }
2453 #endif
2454
2455     return APR_SUCCESS;
2456 }
2457
2458 static apr_status_t rewritelock_remove(void *data)
2459 {
2460     /* only operate if a lockfile is used */
2461     if (lockname == NULL || *(lockname) == '\0') {
2462         return APR_SUCCESS;
2463     }
2464
2465     /* destroy the rewritelock */
2466     apr_global_mutex_destroy (rewrite_mapr_lock_acquire);
2467     rewrite_mapr_lock_acquire = NULL;
2468     lockname = NULL;
2469     return(0);
2470 }
2471
2472
2473 /*
2474  * +-------------------------------------------------------+
2475  * |                                                       |
2476  * |           configuration directive handling
2477  * |                                                       |
2478  * +-------------------------------------------------------+
2479  */
2480
2481 /*
2482  * own command line parser for RewriteRule and RewriteCond,
2483  * which doesn't have the '\\' problem.
2484  * (returns true on error)
2485  *
2486  * XXX: what an inclined parser. Seems we have to leave it so
2487  *      for backwards compat. *sigh*
2488  */
2489 static int parseargline(char *str, char **a1, char **a2, char **a3)
2490 {
2491     char quote;
2492
2493     while (apr_isspace(*str)) {
2494         ++str;
2495     }
2496
2497     /*
2498      * determine first argument
2499      */
2500     quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
2501     *a1 = str;
2502
2503     for (; *str; ++str) {
2504         if ((apr_isspace(*str) && !quote) || (*str == quote)) {
2505             break;
2506         }
2507         if (*str == '\\' && apr_isspace(str[1])) {
2508             ++str;
2509             continue;
2510         }
2511     }
2512
2513     if (!*str) {
2514         return 1;
2515     }
2516     *str++ = '\0';
2517
2518     while (apr_isspace(*str)) {
2519         ++str;
2520     }
2521
2522     /*
2523      * determine second argument
2524      */
2525     quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
2526     *a2 = str;
2527
2528     for (; *str; ++str) {
2529         if ((apr_isspace(*str) && !quote) || (*str == quote)) {
2530             break;
2531         }
2532         if (*str == '\\' && apr_isspace(str[1])) {
2533             ++str;
2534             continue;
2535         }
2536     }
2537
2538     if (!*str) {
2539         *a3 = NULL; /* 3rd argument is optional */
2540         return 0;
2541     }
2542     *str++ = '\0';
2543
2544     while (apr_isspace(*str)) {
2545         ++str;
2546     }
2547
2548     if (!*str) {
2549         *a3 = NULL; /* 3rd argument is still optional */
2550         return 0;
2551     }
2552
2553     /*
2554      * determine third argument
2555      */
2556     quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
2557     *a3 = str;
2558     for (; *str; ++str) {
2559         if ((apr_isspace(*str) && !quote) || (*str == quote)) {
2560             break;
2561         }
2562         if (*str == '\\' && apr_isspace(str[1])) {
2563             ++str;
2564             continue;
2565         }
2566     }
2567     *str = '\0';
2568
2569     return 0;
2570 }
2571
2572 static void *config_server_create(apr_pool_t *p, server_rec *s)
2573 {
2574     rewrite_server_conf *a;
2575
2576     a = (rewrite_server_conf *)apr_pcalloc(p, sizeof(rewrite_server_conf));
2577
2578     a->state           = ENGINE_DISABLED;
2579     a->options         = OPTION_NONE;
2580 #ifndef REWRITELOG_DISABLED
2581     a->rewritelogfile  = NULL;
2582     a->rewritelogfp    = NULL;
2583     a->rewriteloglevel = 0;
2584 #endif
2585     a->rewritemaps     = apr_hash_make(p);
2586     a->rewriteconds    = apr_array_make(p, 2, sizeof(rewritecond_entry));
2587     a->rewriterules    = apr_array_make(p, 2, sizeof(rewriterule_entry));
2588     a->server          = s;
2589     a->redirect_limit  = 0; /* unset (use default) */
2590
2591     return (void *)a;
2592 }
2593
2594 static void *config_server_merge(apr_pool_t *p, void *basev, void *overridesv)
2595 {
2596     rewrite_server_conf *a, *base, *overrides;
2597
2598     a         = (rewrite_server_conf *)apr_pcalloc(p,
2599                                                    sizeof(rewrite_server_conf));
2600     base      = (rewrite_server_conf *)basev;
2601     overrides = (rewrite_server_conf *)overridesv;
2602
2603     a->state   = overrides->state;
2604     a->options = overrides->options;
2605     a->server  = overrides->server;
2606     a->redirect_limit = overrides->redirect_limit
2607                           ? overrides->redirect_limit
2608                           : base->redirect_limit;
2609
2610     if (a->options & OPTION_INHERIT) {
2611         /*
2612          *  local directives override
2613          *  and anything else is inherited
2614          */
2615 #ifndef REWRITELOG_DISABLED
2616         a->rewriteloglevel = overrides->rewriteloglevel != 0
2617                              ? overrides->rewriteloglevel
2618                              : base->rewriteloglevel;
2619         a->rewritelogfile  = overrides->rewritelogfile != NULL
2620                              ? overrides->rewritelogfile
2621                              : base->rewritelogfile;
2622         a->rewritelogfp    = overrides->rewritelogfp != NULL
2623                              ? overrides->rewritelogfp
2624                              : base->rewritelogfp;
2625 #endif
2626         a->rewritemaps     = apr_hash_overlay(p, overrides->rewritemaps,
2627                                               base->rewritemaps);
2628         a->rewriteconds    = apr_array_append(p, overrides->rewriteconds,
2629                                               base->rewriteconds);
2630         a->rewriterules    = apr_array_append(p, overrides->rewriterules,
2631                                               base->rewriterules);
2632     }
2633     else {
2634         /*
2635          *  local directives override
2636          *  and anything else gets defaults
2637          */
2638 #ifndef REWRITELOG_DISABLED
2639         a->rewriteloglevel = overrides->rewriteloglevel;
2640         a->rewritelogfile  = overrides->rewritelogfile;
2641         a->rewritelogfp    = overrides->rewritelogfp;
2642 #endif
2643         a->rewritemaps     = overrides->rewritemaps;
2644         a->rewriteconds    = overrides->rewriteconds;
2645         a->rewriterules    = overrides->rewriterules;
2646     }
2647
2648     return (void *)a;
2649 }
2650
2651 static void *config_perdir_create(apr_pool_t *p, char *path)
2652 {
2653     rewrite_perdir_conf *a;
2654
2655     a = (rewrite_perdir_conf *)apr_pcalloc(p, sizeof(rewrite_perdir_conf));
2656
2657     a->state           = ENGINE_DISABLED;
2658     a->options         = OPTION_NONE;
2659     a->baseurl         = NULL;
2660     a->rewriteconds    = apr_array_make(p, 2, sizeof(rewritecond_entry));
2661     a->rewriterules    = apr_array_make(p, 2, sizeof(rewriterule_entry));
2662     a->redirect_limit  = 0; /* unset (use server config) */
2663
2664     if (path == NULL) {
2665         a->directory = NULL;
2666     }
2667     else {
2668         /* make sure it has a trailing slash */
2669         if (path[strlen(path)-1] == '/') {
2670             a->directory = apr_pstrdup(p, path);
2671         }
2672         else {
2673             a->directory = apr_pstrcat(p, path, "/", NULL);
2674         }
2675     }
2676
2677     return (void *)a;
2678 }
2679
2680 static void *config_perdir_merge(apr_pool_t *p, void *basev, void *overridesv)
2681 {
2682     rewrite_perdir_conf *a, *base, *overrides;
2683
2684     a         = (rewrite_perdir_conf *)apr_pcalloc(p,
2685                                                   sizeof(rewrite_perdir_conf));
2686     base      = (rewrite_perdir_conf *)basev;
2687     overrides = (rewrite_perdir_conf *)overridesv;
2688
2689     a->state     = overrides->state;
2690     a->options   = overrides->options;
2691     a->directory = overrides->directory;
2692     a->baseurl   = overrides->baseurl;
2693     a->redirect_limit = overrides->redirect_limit
2694                           ? overrides->redirect_limit
2695                           : base->redirect_limit;
2696
2697     if (a->options & OPTION_INHERIT) {
2698         a->rewriteconds = apr_array_append(p, overrides->rewriteconds,
2699                                            base->rewriteconds);
2700         a->rewriterules = apr_array_append(p, overrides->rewriterules,
2701                                            base->rewriterules);
2702     }
2703     else {
2704         a->rewriteconds = overrides->rewriteconds;
2705         a->rewriterules = overrides->rewriterules;
2706     }
2707
2708     return (void *)a;
2709 }
2710
2711 static const char *cmd_rewriteengine(cmd_parms *cmd,
2712                                      void *in_dconf, int flag)
2713 {
2714     rewrite_perdir_conf *dconf = in_dconf;
2715     rewrite_server_conf *sconf;
2716
2717     sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
2718
2719     if (cmd->path == NULL) { /* is server command */
2720         sconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
2721     }
2722     else                   /* is per-directory command */ {
2723         dconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
2724     }
2725
2726     return NULL;
2727 }
2728
2729 static const char *cmd_rewriteoptions(cmd_parms *cmd,
2730                                       void *in_dconf, const char *option)
2731 {
2732     int options = 0, limit = 0;
2733     char *w;
2734
2735     while (*option) {
2736         w = ap_getword_conf(cmd->pool, &option);
2737
2738         if (!strcasecmp(w, "inherit")) {
2739             options |= OPTION_INHERIT;
2740         }
2741         else if (!strncasecmp(w, "MaxRedirects=", 13)) {
2742             limit = atoi(&w[13]);
2743             if (limit <= 0) {
2744                 return "RewriteOptions: MaxRedirects takes a number greater "
2745                        "than zero.";
2746             }
2747         }
2748         else if (!strcasecmp(w, "MaxRedirects")) { /* be nice */
2749             return "RewriteOptions: MaxRedirects has the format MaxRedirects"
2750                    "=n.";
2751         }
2752         else {
2753             return apr_pstrcat(cmd->pool, "RewriteOptions: unknown option '",
2754                                w, "'", NULL);
2755         }
2756     }
2757
2758     /* put it into the appropriate config */
2759     if (cmd->path == NULL) { /* is server command */
2760         rewrite_server_conf *conf =
2761             ap_get_module_config(cmd->server->module_config,
2762                                  &rewrite_module);
2763
2764         conf->options |= options;
2765         conf->redirect_limit = limit;
2766     }
2767     else {                  /* is per-directory command */
2768         rewrite_perdir_conf *conf = in_dconf;
2769
2770         conf->options |= options;
2771         conf->redirect_limit = limit;
2772     }
2773
2774     return NULL;
2775 }
2776
2777 #ifndef REWRITELOG_DISABLED
2778 static const char *cmd_rewritelog(cmd_parms *cmd, void *dconf, const char *a1)
2779 {
2780     rewrite_server_conf *sconf;
2781
2782     sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
2783     sconf->rewritelogfile = a1;
2784
2785     return NULL;
2786 }
2787
2788 static const char *cmd_rewriteloglevel(cmd_parms *cmd, void *dconf,
2789                                        const char *a1)
2790 {
2791     rewrite_server_conf *sconf;
2792
2793     sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
2794     sconf->rewriteloglevel = atoi(a1);
2795
2796     return NULL;
2797 }
2798 #endif /* rewritelog */
2799
2800 static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, const char *a1,
2801                                   const char *a2)
2802 {
2803     rewrite_server_conf *sconf;
2804     rewritemap_entry *newmap;
2805     apr_finfo_t st;
2806     const char *fname;
2807
2808     sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
2809
2810     newmap = apr_palloc(cmd->pool, sizeof(rewritemap_entry));
2811     newmap->func = NULL;
2812
2813     if (strncasecmp(a2, "txt:", 4) == 0) {
2814         if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) {
2815             return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ",
2816                                a2+4, NULL);
2817         }
2818
2819         newmap->type      = MAPTYPE_TXT;
2820         newmap->datafile  = fname;
2821         newmap->checkfile = fname;
2822         newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
2823                                          (void *)cmd->server, a1);
2824     }
2825     else if (strncasecmp(a2, "rnd:", 4) == 0) {
2826         if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) {
2827             return apr_pstrcat(cmd->pool, "RewriteMap: bad path to rnd map: ",
2828                                a2+4, NULL);
2829         }
2830
2831         newmap->type      = MAPTYPE_RND;
2832         newmap->datafile  = fname;
2833         newmap->checkfile = fname;
2834         newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
2835                                          (void *)cmd->server, a1);
2836     }
2837     else if (strncasecmp(a2, "dbm", 3) == 0) {
2838         const char *ignored_fname;
2839         apr_status_t rv;
2840
2841         newmap->type = MAPTYPE_DBM;
2842         fname = NULL;
2843         newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
2844                                          (void *)cmd->server, a1);
2845
2846         if (a2[3] == ':') {
2847             newmap->dbmtype = "default";
2848             fname = a2+4;
2849         }
2850         else if (a2[3] == '=') {
2851             const char *colon = ap_strchr_c(a2 + 4, ':');
2852
2853             if (colon) {
2854                 newmap->dbmtype = apr_pstrndup(cmd->pool, a2 + 4,
2855                                                colon - (a2 + 3) - 1);
2856                 fname = colon + 1;
2857             }
2858         }
2859
2860         if (!fname) {
2861             return apr_pstrcat(cmd->pool, "RewriteMap: bad map:",
2862                                a2, NULL);
2863         }
2864
2865         if ((newmap->datafile = ap_server_root_relative(cmd->pool,
2866                                                         fname)) == NULL) {
2867             return apr_pstrcat(cmd->pool, "RewriteMap: bad path to dbm map: ",
2868                                fname, NULL);
2869         }
2870
2871         rv = apr_dbm_get_usednames_ex(cmd->pool, newmap->dbmtype,
2872                                       newmap->datafile, &newmap->checkfile,
2873                                       &ignored_fname);
2874         if (rv != APR_SUCCESS) {
2875             return apr_pstrcat(cmd->pool, "RewriteMap: dbm type ",
2876                                newmap->dbmtype, " is invalid", NULL);
2877         }
2878     }
2879     else if (strncasecmp(a2, "prg:", 4) == 0) {
2880         apr_tokenize_to_argv(a2 + 4, &newmap->argv, cmd->pool);
2881
2882         fname = newmap->argv[0];
2883         if ((newmap->argv[0] = ap_server_root_relative(cmd->pool,
2884                                                        fname)) == NULL) {
2885             return apr_pstrcat(cmd->pool, "RewriteMap: bad path to prg map: ",
2886                                fname, NULL);
2887         }
2888
2889         newmap->type      = MAPTYPE_PRG;
2890         newmap->datafile  = NULL;
2891         newmap->checkfile = newmap->argv[0];
2892         newmap->cachename = NULL;
2893     }
2894     else if (strncasecmp(a2, "int:", 4) == 0) {
2895         newmap->type      = MAPTYPE_INT;
2896         newmap->datafile  = NULL;
2897         newmap->checkfile = NULL;
2898         newmap->cachename = NULL;
2899         newmap->func      = (char *(*)(request_rec *,char *))
2900                             apr_hash_get(mapfunc_hash, a2+4, strlen(a2+4));
2901         if ((sconf->state == ENGINE_ENABLED) && (newmap->func == NULL)) {
2902             return apr_pstrcat(cmd->pool, "RewriteMap: internal map not found:",
2903                                a2+4, NULL);
2904         }
2905     }
2906     else {
2907         if ((fname = ap_server_root_relative(cmd->pool, a2)) == NULL) {
2908             return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ",
2909                                a2, NULL);
2910         }
2911
2912         newmap->type      = MAPTYPE_TXT;
2913         newmap->datafile  = fname;
2914         newmap->checkfile = fname;
2915         newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s",
2916                                          (void *)cmd->server, a1);
2917     }
2918     newmap->fpin  = NULL;
2919     newmap->fpout = NULL;
2920
2921     if (newmap->checkfile && (sconf->state == ENGINE_ENABLED)
2922         && (apr_stat(&st, newmap->checkfile, APR_FINFO_MIN,
2923                      cmd->pool) != APR_SUCCESS)) {
2924         return apr_pstrcat(cmd->pool,
2925                            "RewriteMap: file for map ", a1,
2926                            " not found:", newmap->checkfile, NULL);
2927     }
2928
2929     apr_hash_set(sconf->rewritemaps, a1, APR_HASH_KEY_STRING, newmap);
2930
2931     return NULL;
2932 }
2933
2934 static const char *cmd_rewritelock(cmd_parms *cmd, void *dconf, const char *a1)
2935 {
2936     const char *error;
2937
2938     if ((error = ap_check_cmd_context(cmd, GLOBAL_ONLY)) != NULL)
2939         return error;
2940
2941     /* fixup the path, especially for rewritelock_remove() */
2942     lockname = ap_server_root_relative(cmd->pool, a1);
2943
2944     if (!lockname) {
2945         return apr_pstrcat(cmd->pool, "Invalid RewriteLock path ", a1);
2946     }
2947
2948     return NULL;
2949 }
2950
2951 static const char *cmd_rewritebase(cmd_parms *cmd, void *in_dconf,
2952                                    const char *a1)
2953 {
2954     rewrite_perdir_conf *dconf = in_dconf;
2955
2956     if (cmd->path == NULL || dconf == NULL) {
2957         return "RewriteBase: only valid in per-directory config files";
2958     }
2959     if (a1[0] == '\0') {
2960         return "RewriteBase: empty URL not allowed";
2961     }
2962     if (a1[0] != '/') {
2963         return "RewriteBase: argument is not a valid URL";
2964     }
2965
2966     dconf->baseurl = a1;
2967
2968     return NULL;
2969 }
2970
2971 /*
2972  * generic lexer for RewriteRule and RewriteCond flags.
2973  * The parser will be passed in as a function pointer
2974  * and called if a flag was found
2975  */
2976 static const char *cmd_parseflagfield(apr_pool_t *p, void *cfg, char *key,
2977                                       const char *(*parse)(apr_pool_t *,
2978                                                            void *,
2979                                                            char *, char *))
2980 {
2981     char *val, *nextp, *endp;
2982     const char *err;
2983
2984     endp = key + strlen(key) - 1;
2985     if (*key != '[' || *endp != ']') {
2986         return "RewriteCond: bad flag delimiters";
2987     }
2988
2989     *endp = ','; /* for simpler parsing */
2990     ++key;
2991
2992     while (*key) {
2993         /* skip leading spaces */
2994         while (apr_isspace(*key)) {
2995             ++key;
2996         }
2997
2998         if (!*key || (nextp = ap_strchr(key, ',')) == NULL) { /* NULL should not
2999                                                                * happen, but ...
3000                                                                */
3001             break;
3002         }
3003
3004         /* strip trailing spaces */
3005         endp = nextp - 1;
3006         while (apr_isspace(*endp)) {
3007             --endp;
3008         }
3009         *++endp = '\0';
3010
3011         /* split key and val */
3012         val = ap_strchr(key, '=');
3013         if (val) {
3014             *val++ = '\0';
3015         }
3016         else {
3017             val = endp;
3018         }
3019
3020         err = parse(p, cfg, key, val);
3021         if (err) {
3022             return err;
3023         }
3024
3025         key = nextp + 1;
3026     }
3027
3028     return NULL;
3029 }
3030
3031 static const char *cmd_rewritecond_setflag(apr_pool_t *p, void *_cfg,
3032                                            char *key, char *val)
3033 {
3034     rewritecond_entry *cfg = _cfg;
3035
3036     if (   strcasecmp(key, "nocase") == 0
3037         || strcasecmp(key, "NC") == 0    ) {
3038         cfg->flags |= CONDFLAG_NOCASE;
3039     }
3040     else if (   strcasecmp(key, "ornext") == 0
3041              || strcasecmp(key, "OR") == 0    ) {
3042         cfg->flags |= CONDFLAG_ORNEXT;
3043     }
3044     else {
3045         return apr_pstrcat(p, "RewriteCond: unknown flag '", key, "'", NULL);
3046     }
3047     return NULL;
3048 }
3049
3050 static const char *cmd_rewritecond(cmd_parms *cmd, void *in_dconf,
3051                                    const char *in_str)
3052 {
3053     rewrite_perdir_conf *dconf = in_dconf;
3054     char *str = apr_pstrdup(cmd->pool, in_str);
3055     rewrite_server_conf *sconf;
3056     rewritecond_entry *newcond;
3057     regex_t *regexp;
3058     char *a1;
3059     char *a2;
3060     char *a3;
3061     const char *err;
3062
3063     sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
3064
3065     /*  make a new entry in the internal temporary rewrite rule list */
3066     if (cmd->path == NULL) {   /* is server command */
3067         newcond = apr_array_push(sconf->rewriteconds);
3068     }
3069     else {                     /* is per-directory command */
3070         newcond = apr_array_push(dconf->rewriteconds);
3071     }
3072
3073     /* parse the argument line ourself
3074      * a1 .. a3 are substrings of str, which is a fresh copy
3075      * of the argument line. So we can use a1 .. a3 without
3076      * copying them again.
3077      */
3078     if (parseargline(str, &a1, &a2, &a3)) {
3079         return apr_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str,
3080                            "'", NULL);
3081     }
3082
3083     /* arg1: the input string */
3084     newcond->input = a1;
3085
3086     /* arg3: optional flags field
3087      * (this has to be parsed first, because we need to
3088      *  know if the regex should be compiled with ICASE!)
3089      */
3090     newcond->flags = CONDFLAG_NONE;
3091     if (a3 != NULL) {
3092         if ((err = cmd_parseflagfield(cmd->pool, newcond, a3,
3093                                       cmd_rewritecond_setflag)) != NULL) {
3094             return err;
3095         }
3096     }
3097
3098     /* arg2: the pattern */
3099     if (*a2 == '!') {
3100         newcond->flags |= CONDFLAG_NOTMATCH;
3101         ++a2;
3102     }
3103
3104     /* determine the pattern type */
3105     newcond->ptype = 0;
3106     if (*a2 && a2[1]) {
3107         if (!a2[2] && *a2 == '-') {
3108             switch (a2[1]) {
3109             case 'f': newcond->ptype = CONDPAT_FILE_EXISTS; break;
3110             case 's': newcond->ptype = CONDPAT_FILE_SIZE;   break;
3111             case 'l': newcond->ptype = CONDPAT_FILE_LINK;   break;
3112             case 'd': newcond->ptype = CONDPAT_FILE_DIR;    break;
3113             case 'x': newcond->ptype = CONDPAT_FILE_XBIT;   break;
3114             case 'U': newcond->ptype = CONDPAT_LU_URL;      break;
3115             case 'F': newcond->ptype = CONDPAT_LU_FILE;     break;
3116             }
3117         }
3118         else {
3119             switch (*a2) {
3120             case '>': newcond->ptype = CONDPAT_STR_GT; break;
3121             case '<': newcond->ptype = CONDPAT_STR_LT; break;
3122             case '=': newcond->ptype = CONDPAT_STR_EQ;
3123                 /* "" represents an empty string */
3124                 if (*++a2 == '"' && a2[1] == '"' && !a2[2]) {
3125                     a2 += 2;
3126                 }
3127                 break;
3128             }
3129         }
3130     }
3131
3132     if (newcond->ptype && newcond->ptype != CONDPAT_STR_EQ &&
3133         (newcond->flags & CONDFLAG_NOCASE)) {
3134         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
3135                      "RewriteCond: NoCase option for non-regex pattern '%s' "
3136                      "is not supported and will be ignored.", a2);
3137         newcond->flags &= ~CONDFLAG_NOCASE;
3138     }
3139
3140     newcond->pattern = a2;
3141
3142     if (!newcond->ptype) {
3143         regexp = ap_pregcomp(cmd->pool, a2,
3144                              REG_EXTENDED | ((newcond->flags & CONDFLAG_NOCASE)
3145                                              ? REG_ICASE : 0));
3146         if (!regexp) {
3147             return apr_pstrcat(cmd->pool, "RewriteCond: cannot compile regular "
3148                                "expression '", a2, "'", NULL);
3149         }
3150
3151         newcond->regexp  = regexp;
3152     }
3153
3154     return NULL;
3155 }
3156
3157 static const char *cmd_rewriterule_setflag(apr_pool_t *p, void *_cfg,
3158                                            char *key, char *val)
3159 {
3160     rewriterule_entry *cfg = _cfg;
3161     int error = 0;
3162
3163     switch (*key++) {
3164     case 'c':
3165     case 'C':
3166         if (!*key || !strcasecmp(key, "hain")) {           /* chain */
3167             cfg->flags |= RULEFLAG_CHAIN;
3168         }
3169         else if (((*key == 'O' || *key == 'o') && !key[1])
3170                  || !strcasecmp(key, "ookie")) {           /* cookie */
3171             data_item *cp = cfg->cookie;
3172
3173             if (!cp) {
3174                 cp = cfg->cookie = apr_palloc(p, sizeof(*cp));
3175             }
3176             else {
3177                 while (cp->next) {
3178                     cp = cp->next;
3179                 }
3180                 cp->next = apr_palloc(p, sizeof(*cp));
3181                 cp = cp->next;
3182             }
3183
3184             cp->next = NULL;
3185             cp->data = val;
3186         }
3187         else {
3188             ++error;
3189         }
3190         break;
3191
3192     case 'e':
3193     case 'E':
3194         if (!*key || !strcasecmp(key, "nv")) {             /* env */
3195             data_item *cp = cfg->env;
3196
3197             if (!cp) {
3198                 cp = cfg->env = apr_palloc(p, sizeof(*cp));
3199             }
3200             else {
3201                 while (cp->next) {
3202                     cp = cp->next;
3203                 }
3204                 cp->next = apr_palloc(p, sizeof(*cp));
3205                 cp = cp->next;
3206             }
3207
3208             cp->next = NULL;
3209             cp->data = val;
3210         }
3211         else {
3212             ++error;
3213         }
3214         break;
3215
3216     case 'f':
3217     case 'F':
3218         if (!*key || !strcasecmp(key, "orbidden")) {       /* forbidden */
3219             cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB);
3220             cfg->forced_responsecode = HTTP_FORBIDDEN;
3221         }
3222         else {
3223             ++error;
3224         }
3225         break;
3226
3227     case 'g':
3228     case 'G':
3229         if (!*key || !strcasecmp(key, "one")) {            /* gone */
3230             cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB);
3231             cfg->forced_responsecode = HTTP_GONE;
3232         }
3233         else {
3234             ++error;
3235         }
3236         break;
3237
3238     case 'h':
3239     case 'H':
3240         if (!*key || !strcasecmp(key, "andler")) {         /* handler */
3241             cfg->forced_handler = val;
3242         }
3243         else {
3244             ++error;
3245         }
3246         break;
3247
3248     case 'l':
3249     case 'L':
3250         if (!*key || !strcasecmp(key, "ast")) {            /* last */
3251             cfg->flags |= RULEFLAG_LASTRULE;
3252         }
3253         else {
3254             ++error;
3255         }
3256         break;
3257
3258     case 'n':
3259     case 'N':
3260         if (((*key == 'E' || *key == 'e') && !key[1])
3261             || !strcasecmp(key, "oescape")) {              /* noescape */
3262             cfg->flags |= RULEFLAG_NOESCAPE;
3263         }
3264         else if (!*key || !strcasecmp(key, "ext")) {       /* next */
3265             cfg->flags |= RULEFLAG_NEWROUND;
3266         }
3267         else if (((*key == 'S' || *key == 's') && !key[1])
3268             || !strcasecmp(key, "osubreq")) {              /* nosubreq */
3269             cfg->flags |= RULEFLAG_IGNOREONSUBREQ;
3270         }
3271         else if (((*key == 'C' || *key == 'c') && !key[1])
3272             || !strcasecmp(key, "ocase")) {                /* nocase */
3273             cfg->flags |= RULEFLAG_NOCASE;
3274         }
3275         else {
3276             ++error;
3277         }
3278         break;
3279
3280     case 'p':
3281     case 'P':
3282         if (!*key || !strcasecmp(key, "roxy")) {           /* proxy */
3283             cfg->flags |= RULEFLAG_PROXY;
3284         }
3285         else if (((*key == 'T' || *key == 't') && !key[1])
3286             || !strcasecmp(key, "assthrough")) {           /* passthrough */
3287             cfg->flags |= RULEFLAG_PASSTHROUGH;
3288         }
3289         else {
3290             ++error;
3291         }
3292         break;
3293
3294     case 'q':
3295     case 'Q':
3296         if (   !strcasecmp(key, "SA")
3297             || !strcasecmp(key, "sappend")) {              /* qsappend */
3298             cfg->flags |= RULEFLAG_QSAPPEND;
3299         }
3300         else {
3301             ++error;
3302         }
3303         break;
3304
3305     case 'r':
3306     case 'R':
3307         if (!*key || !strcasecmp(key, "edirect")) {        /* redirect */
3308             int status = 0;
3309
3310             cfg->flags |= RULEFLAG_FORCEREDIRECT;
3311             if (strlen(val) > 0) {
3312                 if (strcasecmp(val, "permanent") == 0) {
3313                     status = HTTP_MOVED_PERMANENTLY;
3314                 }
3315                 else if (strcasecmp(val, "temp") == 0) {
3316                     status = HTTP_MOVED_TEMPORARILY;
3317                 }
3318                 else if (strcasecmp(val, "seeother") == 0) {
3319                     status = HTTP_SEE_OTHER;
3320                 }
3321                 else if (apr_isdigit(*val)) {
3322                     status = atoi(val);
3323                     if (status != HTTP_INTERNAL_SERVER_ERROR) {
3324                         int idx =
3325                             ap_index_of_response(HTTP_INTERNAL_SERVER_ERROR);
3326
3327                         if (ap_index_of_response(status) == idx) {
3328                             return apr_psprintf(p, "RewriteRule: invalid HTTP "
3329                                                    "response code '%s' for "
3330                                                    "flag 'R'",
3331                                                 val);
3332                         }
3333                     }
3334                     if (!ap_is_HTTP_REDIRECT(status)) {
3335                         cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB);
3336                     }
3337                 }
3338                 cfg->forced_responsecode = status;
3339             }
3340         }
3341         else {
3342             ++error;
3343         }
3344         break;
3345
3346     case 's':
3347     case 'S':
3348         if (!*key || !strcasecmp(key, "kip")) {            /* skip */
3349             cfg->skip = atoi(val);
3350         }
3351         else {
3352             ++error;
3353         }
3354         break;
3355
3356     case 't':
3357     case 'T':
3358         if (!*key || !strcasecmp(key, "ype")) {            /* type */
3359             cfg->forced_mimetype = val;
3360         }
3361         else {
3362             ++error;
3363         }
3364         break;
3365
3366     default:
3367         ++error;
3368         break;
3369     }
3370
3371     if (error) {
3372         return apr_pstrcat(p, "RewriteRule: unknown flag '", --key, "'", NULL);
3373     }
3374
3375     return NULL;
3376 }
3377
3378 static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf,
3379                                    const char *in_str)
3380 {
3381     rewrite_perdir_conf *dconf = in_dconf;
3382     char *str = apr_pstrdup(cmd->pool, in_str);
3383     rewrite_server_conf *sconf;
3384     rewriterule_entry *newrule;
3385     regex_t *regexp;
3386     char *a1;
3387     char *a2;
3388     char *a3;
3389     const char *err;
3390
3391     sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
3392
3393     /*  make a new entry in the internal rewrite rule list */
3394     if (cmd->path == NULL) {   /* is server command */
3395         newrule = apr_array_push(sconf->rewriterules);
3396     }
3397     else {                     /* is per-directory command */
3398         newrule = apr_array_push(dconf->rewriterules);
3399     }
3400
3401     /*  parse the argument line ourself */
3402     if (parseargline(str, &a1, &a2, &a3)) {
3403         return apr_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str,
3404                            "'", NULL);
3405     }
3406
3407     /* arg3: optional flags field */
3408     newrule->forced_mimetype     = NULL;
3409     newrule->forced_handler      = NULL;
3410     newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY;
3411     newrule->flags  = RULEFLAG_NONE;
3412     newrule->env = NULL;
3413     newrule->cookie = NULL;
3414     newrule->skip   = 0;
3415     if (a3 != NULL) {
3416         if ((err = cmd_parseflagfield(cmd->pool, newrule, a3,
3417                                       cmd_rewriterule_setflag)) != NULL) {
3418             return err;
3419         }
3420     }
3421
3422     /* arg1: the pattern
3423      * try to compile the regexp to test if is ok
3424      */
3425     if (*a1 == '!') {
3426         newrule->flags |= RULEFLAG_NOTMATCH;
3427         ++a1;
3428     }
3429
3430     regexp = ap_pregcomp(cmd->pool, a1, REG_EXTENDED |
3431                                         ((newrule->flags & RULEFLAG_NOCASE)
3432                                          ? REG_ICASE : 0));
3433     if (!regexp) {
3434         return apr_pstrcat(cmd->pool,
3435                            "RewriteRule: cannot compile regular expression '",
3436                            a1, "'", NULL);
3437     }
3438
3439     newrule->pattern = a1;
3440     newrule->regexp  = regexp;
3441
3442     /* arg2: the output string */
3443     newrule->output = a2;
3444     if (*a2 == '-' && !a2[1]) {
3445         newrule->flags |= RULEFLAG_NOSUB;
3446     }
3447
3448     /* now, if the server or per-dir config holds an
3449      * array of RewriteCond entries, we take it for us
3450      * and clear the array
3451      */
3452     if (cmd->path == NULL) {  /* is server command */
3453         newrule->rewriteconds   = sconf->rewriteconds;
3454         sconf->rewriteconds = apr_array_make(cmd->pool, 2,
3455                                              sizeof(rewritecond_entry));
3456     }
3457     else {                    /* is per-directory command */
3458         newrule->rewriteconds   = dconf->rewriteconds;
3459         dconf->rewriteconds = apr_array_make(cmd->pool, 2,
3460                                              sizeof(rewritecond_entry));
3461     }
3462
3463     return NULL;
3464 }
3465
3466
3467 /*
3468  * +-------------------------------------------------------+
3469  * |                                                       |
3470  * |                  the rewriting engine
3471  * |                                                       |
3472  * +-------------------------------------------------------+
3473  */
3474
3475 /* Lexicographic Compare */
3476 static APR_INLINE int compare_lexicography(char *a, char *b)
3477 {
3478     apr_size_t i, lena, lenb;
3479
3480     lena = strlen(a);
3481     lenb = strlen(b);
3482
3483     if (lena == lenb) {
3484         for (i = 0; i < lena; ++i) {
3485             if (a[i] != b[i]) {
3486                 return ((unsigned char)a[i] > (unsigned char)b[i]) ? 1 : -1;
3487             }
3488         }
3489
3490         return 0;
3491     }
3492
3493     return ((lena > lenb) ? 1 : -1);
3494 }
3495
3496 /*
3497  * Apply a single rewriteCond
3498  */
3499 static int apply_rewrite_cond(rewritecond_entry *p, rewrite_ctx *ctx)
3500 {
3501     char *input = do_expand(p->input, ctx);
3502     apr_finfo_t sb;
3503     request_rec *rsub, *r = ctx->r;
3504     regmatch_t regmatch[AP_MAX_REG_MATCH];
3505     int rc = 0;
3506
3507     switch (p->ptype) {
3508     case CONDPAT_FILE_EXISTS:
3509         if (   apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS
3510             && sb.filetype == APR_REG) {
3511             rc = 1;
3512         }
3513         break;
3514
3515     case CONDPAT_FILE_SIZE:
3516         if (   apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS
3517             && sb.filetype == APR_REG && sb.size > 0) {
3518             rc = 1;
3519         }
3520         break;
3521
3522     case CONDPAT_FILE_LINK:
3523 #if !defined(OS2)
3524         if (   apr_stat(&sb, input, APR_FINFO_MIN | APR_FINFO_LINK,
3525                         r->pool) == APR_SUCCESS
3526             && sb.filetype == APR_LNK) {
3527             rc = 1;
3528         }
3529 #endif
3530         break;
3531
3532     case CONDPAT_FILE_DIR:
3533         if (   apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS
3534             && sb.filetype == APR_DIR) {
3535             rc = 1;
3536         }
3537         break;
3538
3539     case CONDPAT_FILE_XBIT:
3540         if (   apr_stat(&sb, input, APR_FINFO_PROT, r->pool) == APR_SUCCESS
3541             && (sb.protection & (APR_UEXECUTE | APR_GEXECUTE | APR_WEXECUTE))) {
3542             rc = 1;
3543         }
3544         break;
3545
3546     case CONDPAT_LU_URL:
3547         if (*input && subreq_ok(r)) {
3548             rsub = ap_sub_req_lookup_uri(input, r, NULL);
3549             if (rsub->status < 400) {
3550                 rc = 1;
3551             }
3552             rewritelog((r, 5, NULL, "RewriteCond URI (-U) check: "
3553                         "path=%s -> status=%d", input, rsub->status));
3554             ap_destroy_sub_req(rsub);
3555         }
3556         break;
3557
3558     case CONDPAT_LU_FILE:
3559         if (*input && subreq_ok(r)) {
3560             rsub = ap_sub_req_lookup_file(input, r, NULL);
3561             if (rsub->status < 300 &&
3562                 /* double-check that file exists since default result is 200 */
3563                 apr_stat(&sb, rsub->filename, APR_FINFO_MIN,
3564                          r->pool) == APR_SUCCESS) {
3565                 rc = 1;
3566             }
3567             rewritelog((r, 5, NULL, "RewriteCond file (-F) check: path=%s "
3568                         "-> file=%s status=%d", input, rsub->filename,
3569                         rsub->status));
3570             ap_destroy_sub_req(rsub);
3571         }
3572         break;
3573
3574     case CONDPAT_STR_GT:
3575         rc = (compare_lexicography(input, p->pattern+1) == 1) ? 1 : 0;
3576         break;
3577
3578     case CONDPAT_STR_LT:
3579         rc = (compare_lexicography(input, p->pattern+1) == -1) ? 1 : 0;
3580         break;
3581
3582     case CONDPAT_STR_EQ:
3583         if (p->flags & CONDFLAG_NOCASE) {
3584             rc = !strcasecmp(input, p->pattern);
3585         }
3586         else {
3587             rc = !strcmp(input, p->pattern);
3588         }
3589         break;
3590
3591     default:
3592         /* it is really a regexp pattern, so apply it */
3593         rc = !ap_regexec(p->regexp, input, AP_MAX_REG_MATCH, regmatch, 0);
3594
3595         /* update briRC backref info */
3596         if (rc && !(p->flags & CONDFLAG_NOTMATCH)) {
3597             ctx->briRC.source = input;
3598             ctx->briRC.nsub   = p->regexp->re_nsub;
3599             memcpy(ctx->briRC.regmatch, regmatch, sizeof(regmatch));
3600         }
3601         break;
3602     }
3603
3604     if (p->flags & CONDFLAG_NOTMATCH) {
3605         rc = !rc;
3606     }
3607
3608     rewritelog((r, 4, ctx->perdir, "RewriteCond: input='%s' pattern='%s%s%s'%s "
3609                 "=> %s", input, (p->flags & CONDFLAG_NOTMATCH) ? "!" : "",
3610                 (p->ptype == CONDPAT_STR_EQ) ? "=" : "", p->pattern,
3611                 (p->flags & CONDFLAG_NOCASE) ? " [NC]" : "",
3612                 rc ? "matched" : "not-matched"));
3613
3614     return rc;
3615 }
3616
3617 /* check for forced type and handler */
3618 static APR_INLINE void force_type_handler(rewriterule_entry *p,
3619                                           rewrite_ctx *ctx)
3620 {
3621     char *expanded;
3622
3623     if (p->forced_mimetype) {
3624         expanded = do_expand(p->forced_mimetype, ctx);
3625
3626         if (*expanded) {
3627             ap_str_tolower(expanded);
3628
3629             rewritelog((ctx->r, 2, ctx->perdir, "remember %s to have MIME-type "
3630                         "'%s'", ctx->r->filename, expanded));
3631
3632             apr_table_setn(ctx->r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
3633                            expanded);
3634         }
3635     }
3636
3637     if (p->forced_handler) {
3638         expanded = do_expand(p->forced_handler, ctx);
3639
3640         if (*expanded) {
3641             ap_str_tolower(expanded);
3642
3643             rewritelog((ctx->r, 2, ctx->perdir, "remember %s to have "
3644                         "Content-handler '%s'", ctx->r->filename, expanded));
3645
3646             apr_table_setn(ctx->r->notes, REWRITE_FORCED_HANDLER_NOTEVAR,
3647                            expanded);
3648         }
3649     }
3650 }
3651
3652 /*
3653  * Apply a single RewriteRule
3654  */
3655 static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx)
3656 {
3657     regmatch_t regmatch[AP_MAX_REG_MATCH];
3658     apr_array_header_t *rewriteconds;
3659     rewritecond_entry *conds;
3660     int i, rc;
3661     char *newuri = NULL;
3662     request_rec *r = ctx->r;
3663     int is_proxyreq = 0;
3664
3665     ctx->uri = r->filename;
3666
3667     if (ctx->perdir) {
3668         apr_size_t dirlen = strlen(ctx->perdir);
3669
3670         /*
3671          * Proxy request?
3672          */
3673         is_proxyreq = (   r->proxyreq && r->filename
3674                        && !strncmp(r->filename, "proxy:", 6));
3675
3676         /* Since we want to match against the (so called) full URL, we have
3677          * to re-add the PATH_INFO postfix
3678          */
3679         if (r->path_info && *r->path_info) {
3680             rewritelog((r, 3, ctx->perdir, "add path info postfix: %s -> %s%s",
3681                         ctx->uri, ctx->uri, r->path_info));
3682             ctx->uri = apr_pstrcat(r->pool, ctx->uri, r->path_info, NULL);
3683         }
3684
3685         /* Additionally we strip the physical path from the url to match
3686          * it independent from the underlaying filesystem.
3687          */
3688         if (!is_proxyreq && strlen(ctx->uri) >= dirlen &&
3689             !strncmp(ctx->uri, ctx->perdir, dirlen)) {
3690
3691             rewritelog((r, 3, ctx->perdir, "strip per-dir prefix: %s -> %s",
3692                         ctx->uri, ctx->uri + dirlen));
3693             ctx->uri = ctx->uri + dirlen;
3694         }
3695     }
3696
3697     /* Try to match the URI against the RewriteRule pattern
3698      * and exit immediately if it didn't apply.
3699      */
3700     rewritelog((r, 3, ctx->perdir, "applying pattern '%s' to uri '%s'",
3701                 p->pattern, ctx->uri));
3702
3703     rc = !ap_regexec(p->regexp, ctx->uri, AP_MAX_REG_MATCH, regmatch, 0);
3704     if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) ||
3705            (!rc &&  (p->flags & RULEFLAG_NOTMATCH))   ) ) {
3706         return 0;
3707     }
3708
3709     /* It matched, wow! Now it's time to prepare the context structure for
3710      * further processing
3711      */
3712     ctx->vary_this = NULL;
3713     ctx->briRC.source = NULL;
3714
3715     if (p->flags & RULEFLAG_NOTMATCH) {
3716         ctx->briRR.source = NULL;
3717     }
3718     else {
3719         ctx->briRR.source = apr_pstrdup(r->pool, ctx->uri);
3720         ctx->briRR.nsub   = p->regexp->re_nsub;
3721         memcpy(ctx->briRR.regmatch, regmatch, sizeof(regmatch));
3722     }
3723
3724     /* Ok, we already know the pattern has matched, but we now
3725      * additionally have to check for all existing preconditions
3726      * (RewriteCond) which have to be also true. We do this at
3727      * this very late stage to avoid unnessesary checks which
3728      * would slow down the rewriting engine.
3729      */
3730     rewriteconds = p->rewriteconds;
3731     conds = (rewritecond_entry *)rewriteconds->elts;
3732
3733     for (i = 0; i < rewriteconds->nelts; ++i) {
3734         rewritecond_entry *c = &conds[i];
3735
3736         rc = apply_rewrite_cond(c, ctx);
3737         if (c->flags & CONDFLAG_ORNEXT) {
3738             if (!rc) {
3739                 /* One condition is false, but another can be still true. */
3740                 ctx->vary_this = NULL;
3741                 continue;
3742             }
3743             else {
3744                 /* skip the rest of the chained OR conditions */
3745                 while (   i < rewriteconds->nelts
3746                        && c->flags & CONDFLAG_ORNEXT) {
3747                     c = &conds[++i];
3748                 }
3749                 continue;
3750             }
3751         }
3752         else if (!rc) {
3753             return 0;
3754         }
3755
3756         /* If some HTTP header was involved in the condition, remember it
3757          * for later use
3758          */
3759         if (ctx->vary_this) {
3760             ctx->vary = ctx->vary
3761                         ? apr_pstrcat(r->pool, ctx->vary, ", ", ctx->vary_this,
3762                                       NULL)
3763                         : ctx->vary_this;
3764             ctx->vary_this = NULL;
3765         }
3766     }
3767
3768     /* expand the result */
3769     if (!(p->flags & RULEFLAG_NOSUB)) {
3770         newuri = do_expand(p->output, ctx);
3771         rewritelog((r, 2, ctx->perdir, "rewrite '%s' -> '%s'", ctx->uri,
3772                     newuri));
3773     }
3774
3775     /* expand [E=var:val] and [CO=<cookie>] */
3776     do_expand_env(p->env, ctx);
3777     do_expand_cookie(p->cookie, ctx);
3778
3779     /* non-substitution rules ('RewriteRule <pat> -') end here. */
3780     if (p->flags & RULEFLAG_NOSUB) {
3781         force_type_handler(p, ctx);
3782
3783         if (p->flags & RULEFLAG_STATUS) {
3784             rewritelog((r, 2, ctx->perdir, "forcing responsecode %d for %s",
3785                         p->forced_responsecode, r->filename));
3786
3787             r->status = p->forced_responsecode;
3788         }
3789
3790         return 2;
3791     }
3792
3793     /* Now adjust API's knowledge about r->filename and r->args */
3794     r->filename = newuri;
3795     splitout_queryargs(r, p->flags & RULEFLAG_QSAPPEND);
3796
3797     /* Add the previously stripped per-directory location prefix, unless
3798      * (1) it's an absolute URL path and
3799      * (2) it's a full qualified URL
3800      */
3801     if (   ctx->perdir && !is_proxyreq && *r->filename != '/'
3802         && !is_absolute_uri(r->filename)) {
3803         rewritelog((r, 3, ctx->perdir, "add per-dir prefix: %s -> %s%s",
3804                     r->filename, ctx->perdir, r->filename));
3805
3806         r->filename = apr_pstrcat(r->pool, ctx->perdir, r->filename, NULL);
3807     }
3808
3809     /* If this rule is forced for proxy throughput
3810      * (`RewriteRule ... ... [P]') then emulate mod_proxy's
3811      * URL-to-filename handler to be sure mod_proxy is triggered
3812      * for this URL later in the Apache API. But make sure it is
3813      * a fully-qualified URL. (If not it is qualified with
3814      * ourself).
3815      */
3816     if (p->flags & RULEFLAG_PROXY) {
3817         fully_qualify_uri(r);
3818
3819         rewritelog((r, 2, ctx->perdir, "forcing proxy-throughput with %s",
3820                     r->filename));
3821
3822         r->filename = apr_pstrcat(r->pool, "proxy:", r->filename, NULL);
3823         return 1;
3824     }
3825
3826     /* If this rule is explicitly forced for HTTP redirection
3827      * (`RewriteRule .. .. [R]') then force an external HTTP
3828      * redirect. But make sure it is a fully-qualified URL. (If
3829      * not it is qualified with ourself).
3830      */
3831     if (p->flags & RULEFLAG_FORCEREDIRECT) {
3832         fully_qualify_uri(r);
3833
3834         rewritelog((r, 2, ctx->perdir, "explicitly forcing redirect with %s",
3835                     r->filename));
3836
3837         r->status = p->forced_responsecode;
3838         return 1;
3839     }
3840
3841     /* Special Rewriting Feature: Self-Reduction
3842      * We reduce the URL by stripping a possible
3843      * http[s]://<ourhost>[:<port>] prefix, i.e. a prefix which
3844      * corresponds to ourself. This is to simplify rewrite maps
3845      * and to avoid recursion, etc. When this prefix is not a
3846      * coincidence then the user has to use [R] explicitly (see
3847      * above).
3848      */
3849     reduce_uri(r);
3850
3851     /* If this rule is still implicitly forced for HTTP
3852      * redirection (`RewriteRule .. <scheme>://...') then
3853      * directly force an external HTTP redirect.
3854      */
3855     if (is_absolute_uri(r->filename)) {
3856         rewritelog((r, 2, ctx->perdir, "implicitly forcing redirect (rc=%d) "
3857                     "with %s", p->forced_responsecode, r->filename));
3858
3859         r->status = p->forced_responsecode;
3860         return 1;
3861     }
3862
3863     /* Finally remember the forced mime-type */
3864     force_type_handler(p, ctx);
3865
3866     /* Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_)
3867      * But now we're done for this particular rule.
3868      */
3869     return 1;
3870 }
3871
3872 /*
3873  * Apply a complete rule set,
3874  * i.e. a list of rewrite rules
3875  */
3876 static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules,
3877                               char *perdir)
3878 {
3879     rewriterule_entry *entries;
3880     rewriterule_entry *p;
3881     int i;
3882     int changed;
3883     int rc;
3884     int s;
3885     rewrite_ctx *ctx;
3886
3887     ctx = apr_palloc(r->pool, sizeof(*ctx));
3888     ctx->perdir = perdir;
3889     ctx->r = r;
3890
3891     /*
3892      *  Iterate over all existing rules
3893      */
3894     entries = (rewriterule_entry *)rewriterules->elts;
3895     changed = 0;
3896     loop:
3897     for (i = 0; i < rewriterules->nelts; i++) {
3898         p = &entries[i];
3899
3900         /*
3901          *  Ignore this rule on subrequests if we are explicitly
3902          *  asked to do so or this is a proxy-throughput or a
3903          *  forced redirect rule.
3904          */
3905         if (r->main != NULL &&
3906             (p->flags & RULEFLAG_IGNOREONSUBREQ ||
3907              p->flags & RULEFLAG_FORCEREDIRECT    )) {
3908             continue;
3909         }
3910
3911         /*
3912          *  Apply the current rule.
3913          */
3914         ctx->vary = NULL;
3915         rc = apply_rewrite_rule(p, ctx);
3916
3917         if (rc) {
3918             /* Regardless of what we do next, we've found a match. Check to see
3919              * if any of the request header fields were involved, and add them
3920              * to the Vary field of the response.
3921              */
3922             if (ctx->vary) {
3923                 apr_table_merge(r->headers_out, "Vary", ctx->vary);
3924             }
3925
3926             /*
3927              * The rule sets the response code (implies match-only)
3928              */
3929             if (p->flags & RULEFLAG_STATUS) {
3930                 return ACTION_STATUS;
3931             }
3932
3933             /*
3934              * Indicate a change if this was not a match-only rule.
3935              */
3936             if (rc != 2) {
3937                 changed = ((p->flags & RULEFLAG_NOESCAPE)
3938                            ? ACTION_NOESCAPE : ACTION_NORMAL);
3939             }
3940
3941             /*
3942              *  Pass-Through Feature (`RewriteRule .. .. [PT]'):
3943              *  Because the Apache 1.x API is very limited we
3944              *  need this hack to pass the rewritten URL to other
3945              *  modules like mod_alias, mod_userdir, etc.
3946              */
3947             if (p->flags & RULEFLAG_PASSTHROUGH) {
3948                 rewritelog((r, 2, perdir, "forcing '%s' to get passed through "
3949                            "to next API URI-to-filename handler", r->filename));
3950                 r->filename = apr_pstrcat(r->pool, "passthrough:",
3951                                          r->filename, NULL);
3952                 changed = ACTION_NORMAL;
3953                 break;
3954             }
3955
3956             /*
3957              *  Stop processing also on proxy pass-through and
3958              *  last-rule and new-round flags.
3959              */
3960             if (p->flags & (RULEFLAG_PROXY | RULEFLAG_LASTRULE)) {
3961                 break;
3962             }
3963
3964             /*
3965              *  On "new-round" flag we just start from the top of
3966              *  the rewriting ruleset again.
3967              */
3968             if (p->flags & RULEFLAG_NEWROUND) {
3969                 goto loop;
3970             }
3971
3972             /*
3973              *  If we are forced to skip N next rules, do it now.
3974              */
3975             if (p->skip > 0) {
3976                 s = p->skip;
3977                 while (   i < rewriterules->nelts
3978                        && s > 0) {
3979                     i++;
3980                     p = &entries[i];
3981                     s--;
3982                 }
3983             }
3984         }
3985         else {
3986             /*
3987              *  If current rule is chained with next rule(s),
3988              *  skip all this next rule(s)
3989              */
3990             while (   i < rewriterules->nelts
3991                    && p->flags & RULEFLAG_CHAIN) {
3992                 i++;
3993                 p = &entries[i];
3994             }
3995         }
3996     }
3997     return changed;
3998 }
3999
4000
4001 /*
4002  * +-------------------------------------------------------+
4003  * |                                                       |
4004  * |             Module Initialization Hooks
4005  * |                                                       |
4006  * +-------------------------------------------------------+
4007  */
4008
4009 static int pre_config(apr_pool_t *pconf,
4010                       apr_pool_t *plog,
4011                       apr_pool_t *ptemp)
4012 {
4013     APR_OPTIONAL_FN_TYPE(ap_register_rewrite_mapfunc) *map_pfn_register;
4014
4015     /* register int: rewritemap handlers */
4016     mapfunc_hash = apr_hash_make(pconf);
4017     map_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_rewrite_mapfunc);
4018     if (map_pfn_register) {
4019         map_pfn_register("tolower", rewrite_mapfunc_tolower);
4020         map_pfn_register("toupper", rewrite_mapfunc_toupper);
4021         map_pfn_register("escape", rewrite_mapfunc_escape);
4022         map_pfn_register("unescape", rewrite_mapfunc_unescape);
4023     }
4024     return OK;
4025 }
4026
4027 static int post_config(apr_pool_t *p,
4028                        apr_pool_t *plog,
4029                        apr_pool_t *ptemp,
4030                        server_rec *s)
4031 {
4032     apr_status_t rv;
4033     void *data;
4034     int first_time = 0;
4035     const char *userdata_key = "rewrite_init_module";
4036
4037     apr_pool_userdata_get(&data, userdata_key, s->process->pool);
4038     if (!data) {
4039         first_time = 1;
4040         apr_pool_userdata_set((const void *)1, userdata_key,
4041                               apr_pool_cleanup_null, s->process->pool);
4042     }
4043
4044     /* check if proxy module is available */
4045     proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL);
4046
4047 #ifndef REWRITELOG_DISABLED
4048     /* create the rewriting lockfiles in the parent */
4049     if ((rv = apr_global_mutex_create(&rewrite_log_lock, NULL,
4050                                       APR_LOCK_DEFAULT, p)) != APR_SUCCESS) {
4051         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
4052                      "mod_rewrite: could not create rewrite_log_lock");
4053         return HTTP_INTERNAL_SERVER_ERROR;
4054     }
4055
4056 #ifdef MOD_REWRITE_SET_MUTEX_PERMS
4057     rv = unixd_set_global_mutex_perms(rewrite_log_lock);
4058     if (rv != APR_SUCCESS) {
4059         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
4060                      "mod_rewrite: Could not set permissions on "
4061                      "rewrite_log_lock; check User and Group directives");
4062         return HTTP_INTERNAL_SERVER_ERROR;
4063     }
4064 #endif /* perms */
4065 #endif /* rewritelog */
4066
4067     rv = rewritelock_create(s, p);
4068     if (rv != APR_SUCCESS) {
4069         return HTTP_INTERNAL_SERVER_ERROR;
4070     }
4071
4072     apr_pool_cleanup_register(p, (void *)s, rewritelock_remove,
4073                               apr_pool_cleanup_null);
4074
4075     /* step through the servers and
4076      * - open each rewriting logfile
4077      * - open the RewriteMap prg:xxx programs
4078      */
4079     for (; s; s = s->next) {
4080 #ifndef REWRITELOG_DISABLED
4081         if (!open_rewritelog(s, p)) {
4082             return HTTP_INTERNAL_SERVER_ERROR;
4083         }
4084 #endif
4085
4086         if (!first_time) {
4087             if (run_rewritemap_programs(s, p) != APR_SUCCESS) {
4088                 return HTTP_INTERNAL_SERVER_ERROR;
4089             }
4090         }
4091     }
4092
4093     rewrite_ssl_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup);
4094     rewrite_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https);
4095
4096     return OK;
4097 }
4098
4099 static void init_child(apr_pool_t *p, server_rec *s)
4100 {
4101     apr_status_t rv = 0; /* get a rid of gcc warning (REWRITELOG_DISABLED) */
4102
4103     if (lockname != NULL && *(lockname) != '\0') {
4104         rv = apr_global_mutex_child_init(&rewrite_mapr_lock_acquire,
4105                                          lockname, p);
4106         if (rv != APR_SUCCESS) {
4107             ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
4108                          "mod_rewrite: could not init rewrite_mapr_lock_acquire"
4109                          " in child");
4110         }
4111     }
4112
4113 #ifndef REWRITELOG_DISABLED
4114     rv = apr_global_mutex_child_init(&rewrite_log_lock, NULL, p);
4115     if (rv != APR_SUCCESS) {
4116         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
4117                      "mod_rewrite: could not init rewrite log lock in child");
4118     }
4119 #endif
4120
4121     /* create the lookup cache */
4122     if (!init_cache(p)) {
4123         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
4124                      "mod_rewrite: could not init map cache in child");
4125     }
4126 }
4127
4128
4129 /*
4130  * +-------------------------------------------------------+
4131  * |                                                       |
4132  * |                     runtime hooks
4133  * |                                                       |
4134  * +-------------------------------------------------------+
4135  */
4136
4137 /*
4138  * URI-to-filename hook
4139  * [deals with RewriteRules in server context]
4140  */
4141 static int hook_uri2file(request_rec *r)
4142 {
4143     rewrite_server_conf *conf;
4144     const char *saved_rulestatus;
4145     const char *var;
4146     const char *thisserver;
4147     char *thisport;
4148     const char *thisurl;
4149     unsigned int port;
4150     int rulestatus;
4151
4152     /*
4153      *  retrieve the config structures
4154      */
4155     conf = ap_get_module_config(r->server->module_config, &rewrite_module);
4156
4157     /*
4158      *  only do something under runtime if the engine is really enabled,
4159      *  else return immediately!
4160      */
4161     if (conf->state == ENGINE_DISABLED) {
4162         return DECLINED;
4163     }
4164
4165     /*
4166      *  check for the ugly API case of a virtual host section where no
4167      *  mod_rewrite directives exists. In this situation we became no chance
4168      *  by the API to setup our default per-server config so we have to
4169      *  on-the-fly assume we have the default config. But because the default
4170      *  config has a disabled rewriting engine we are lucky because can
4171      *  just stop operating now.
4172      */
4173     if (conf->server != r->server) {
4174         return DECLINED;
4175     }
4176
4177     /*
4178      *  add the SCRIPT_URL variable to the env. this is a bit complicated
4179      *  due to the fact that apache uses subrequests and internal redirects
4180      */
4181
4182     if (r->main == NULL) {
4183          var = apr_table_get(r->subprocess_env, REDIRECT_ENVVAR_SCRIPT_URL);
4184          if (var == NULL) {
4185              apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, r->uri);
4186          }
4187          else {
4188              apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
4189          }
4190     }
4191     else {
4192          var = apr_table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL);
4193          apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
4194     }
4195
4196     /*
4197      *  create the SCRIPT_URI variable for the env
4198      */
4199
4200     /* add the canonical URI of this URL */
4201     thisserver = ap_get_server_name(r);
4202     port = ap_get_server_port(r);
4203     if (ap_is_default_port(port, r)) {
4204         thisport = "";
4205     }
4206     else {
4207         thisport = apr_psprintf(r->pool, ":%u", port);
4208     }
4209     thisurl = apr_table_get(r->subprocess_env, ENVVAR_SCRIPT_URL);
4210
4211     /* set the variable */
4212     var = apr_pstrcat(r->pool, ap_http_method(r), "://", thisserver, thisport,
4213                       thisurl, NULL);
4214     apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URI, var);
4215
4216     if (!(saved_rulestatus = apr_table_get(r->notes,"mod_rewrite_rewritten"))) {
4217         /* if filename was not initially set,
4218          * we start with the requested URI
4219          */
4220         if (r->filename == NULL) {
4221             r->filename = apr_pstrdup(r->pool, r->uri);
4222             rewritelog((r, 2, NULL, "init rewrite engine with requested uri %s",
4223                         r->filename));
4224         }
4225         else {
4226             rewritelog((r, 2, NULL, "init rewrite engine with passed filename "
4227                         "%s. Original uri = %s", r->filename, r->uri));
4228         }
4229
4230         /*
4231          *  now apply the rules ...
4232          */
4233         rulestatus = apply_rewrite_list(r, conf->rewriterules, NULL);
4234         apr_table_set(r->notes,"mod_rewrite_rewritten",
4235                       apr_psprintf(r->pool,"%d",rulestatus));
4236     }
4237     else {
4238         rewritelog((r, 2, NULL, "uri already rewritten. Status %s, Uri %s, "
4239                     "r->filename %s", saved_rulestatus, r->uri, r->filename));
4240
4241         rulestatus = atoi(saved_rulestatus);
4242     }
4243
4244     if (rulestatus) {
4245         unsigned skip;
4246         apr_size_t flen;
4247
4248         if (ACTION_STATUS == rulestatus) {
4249             int n = r->status;
4250
4251             r->status = HTTP_OK;
4252             return n;
4253         }
4254
4255         flen = strlen(r->filename);
4256         if (flen > 6 && strncmp(r->filename, "proxy:", 6) == 0) {
4257             /* it should be go on as an internal proxy request */
4258
4259             /* check if the proxy module is enabled, so
4260              * we can actually use it!
4261              */
4262             if (!proxy_available) {
4263                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4264                               "attempt to make remote request from mod_rewrite "
4265                               "without proxy enabled: %s", r->filename);
4266                 return HTTP_FORBIDDEN;
4267             }
4268
4269             /* make sure the QUERY_STRING and
4270              * PATH_INFO parts get incorporated
4271              */
4272             if (r->path_info != NULL) {
4273                 r->filename = apr_pstrcat(r->pool, r->filename,
4274                                           r->path_info, NULL);
4275             }
4276             if (r->args != NULL &&
4277                 r->uri == r->unparsed_uri) {
4278                 /* see proxy_http:proxy_http_canon() */
4279                 r->filename = apr_pstrcat(r->pool, r->filename,
4280                                           "?", r->args, NULL);
4281             }
4282
4283             /* now make sure the request gets handled by the proxy handler */
4284             if (PROXYREQ_NONE == r->proxyreq) {
4285                 r->proxyreq = PROXYREQ_REVERSE;
4286             }
4287             r->handler  = "proxy-server";
4288
4289             rewritelog((r, 1, NULL, "go-ahead with proxy request %s [OK]",
4290                         r->filename));
4291             return OK;
4292         }
4293         else if ((skip = is_absolute_uri(r->filename)) > 0) {
4294             int n;
4295
4296             /* it was finally rewritten to a remote URL */
4297
4298             if (rulestatus != ACTION_NOESCAPE) {
4299                 rewritelog((r, 1, NULL, "escaping %s for redirect",
4300                             r->filename));
4301                 r->filename = escape_absolute_uri(r->pool, r->filename, skip);
4302             }
4303
4304             /* append the QUERY_STRING part */
4305             if (r->args) {
4306                 r->filename = apr_pstrcat(r->pool, r->filename, "?",
4307                                           (rulestatus == ACTION_NOESCAPE)
4308                                             ? r->args
4309                                             : ap_escape_uri(r->pool, r->args),
4310                                           NULL);
4311             }
4312
4313             /* determine HTTP redirect response code */
4314             if (ap_is_HTTP_REDIRECT(r->status)) {
4315                 n = r->status;
4316                 r->status = HTTP_OK; /* make Apache kernel happy */
4317             }
4318             else {
4319                 n = HTTP_MOVED_TEMPORARILY;
4320             }
4321
4322             /* now do the redirection */
4323             apr_table_setn(r->headers_out, "Location", r->filename);
4324             rewritelog((r, 1, NULL, "redirect to %s [REDIRECT/%d]", r->filename,
4325                         n));
4326
4327             return n;
4328         }
4329         else if (flen > 12 && strncmp(r->filename, "passthrough:", 12) == 0) {
4330             /*
4331              * Hack because of underpowered API: passing the current
4332              * rewritten filename through to other URL-to-filename handlers
4333              * just as it were the requested URL. This is to enable
4334              * post-processing by mod_alias, etc.  which always act on
4335              * r->uri! The difference here is: We do not try to
4336              * add the document root
4337              */
4338             r->uri = apr_pstrdup(r->pool, r->filename+12);
4339             return DECLINED;
4340         }
4341         else {
4342             /* it was finally rewritten to a local path */
4343
4344             /* expand "/~user" prefix */
4345 #if APR_HAS_USER
4346             r->filename = expand_tildepaths(r, r->filename);
4347 #endif
4348             rewritelog((r, 2, NULL, "local path result: %s", r->filename));
4349
4350             /* the filename must be either an absolute local path or an
4351              * absolute local URL.
4352              */
4353             if (   *r->filename != '/'
4354                 && !ap_os_is_path_absolute(r->pool, r->filename)) {
4355                 return HTTP_BAD_REQUEST;
4356             }
4357
4358             /* if there is no valid prefix, we call
4359              * the translator from the core and
4360              * prefix the filename with document_root
4361              *
4362              * NOTICE:
4363              * We cannot leave out the prefix_stat because
4364              * - when we always prefix with document_root
4365              *   then no absolute path can be created, e.g. via
4366              *   emulating a ScriptAlias directive, etc.
4367              * - when we always NOT prefix with document_root
4368              *   then the files under document_root have to
4369              *   be references directly and document_root
4370              *   gets never used and will be a dummy parameter -
4371              *   this is also bad
4372              *
4373              * BUT:
4374              * Under real Unix systems this is no problem,
4375              * because we only do stat() on the first directory
4376              * and this gets cached by the kernel for along time!
4377              */
4378             if (!prefix_stat(r->filename, r->pool)) {
4379                 int res;
4380                 char *tmp = r->uri;
4381
4382                 r->uri = r->filename;
4383                 res = ap_core_translate(r);
4384                 r->uri = tmp;
4385
4386                 if (res != OK) {
4387                     rewritelog((r, 1, NULL, "prefixing with document_root of %s"
4388                                 " FAILED", r->filename));
4389
4390                     return res;
4391                 }
4392
4393                 rewritelog((r, 2, NULL, "prefixed with document_root to %s",
4394                             r->filename));
4395             }
4396
4397             rewritelog((r, 1, NULL, "go-ahead with %s [OK]", r->filename));
4398             return OK;
4399         }
4400     }
4401     else {
4402         rewritelog((r, 1, NULL, "pass through %s", r->filename));
4403         return DECLINED;
4404     }
4405 }
4406
4407 /*
4408  * Fixup hook
4409  * [RewriteRules in directory context]
4410  */
4411 static int hook_fixup(request_rec *r)
4412 {
4413     rewrite_perdir_conf *dconf;
4414     char *cp;
4415     char *cp2;
4416     const char *ccp;
4417     apr_size_t l;
4418     int rulestatus;
4419     int n;
4420     char *ofilename;
4421     int is_proxyreq;
4422
4423     dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
4424                                                         &rewrite_module);
4425
4426     /* if there is no per-dir config we return immediately */
4427     if (dconf == NULL) {
4428         return DECLINED;
4429     }
4430
4431     /* if there are no real (i.e. no RewriteRule directives!)
4432        per-dir config of us, we return also immediately */
4433     if (dconf->directory == NULL) {
4434         return DECLINED;
4435     }
4436
4437     /*
4438      * Proxy request?
4439      */
4440     is_proxyreq = (   r->proxyreq && r->filename
4441                    && !strncmp(r->filename, "proxy:", 6));
4442
4443     /*
4444      *  .htaccess file is called before really entering the directory, i.e.:
4445      *  URL: http://localhost/foo  and .htaccess is located in foo directory
4446      *  Ignore such attempts, since they may lead to undefined behaviour.
4447      */
4448     if (!is_proxyreq) {
4449         l = strlen(dconf->directory) - 1;
4450         if (r->filename && strlen(r->filename) == l &&
4451             (dconf->directory)[l] == '/' &&
4452             !strncmp(r->filename, dconf->directory, l)) {
4453             return DECLINED;
4454         }
4455     }
4456
4457     /*
4458      *  only do something under runtime if the engine is really enabled,
4459      *  for this directory, else return immediately!
4460      */
4461     if (dconf->state == ENGINE_DISABLED) {
4462         return DECLINED;
4463     }
4464
4465     /*
4466      *  Do the Options check after engine check, so
4467      *  the user is able to explicitely turn RewriteEngine Off.
4468      */
4469     if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) {
4470         /* FollowSymLinks is mandatory! */
4471         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4472                      "Options FollowSymLinks or SymLinksIfOwnerMatch is off "
4473                      "which implies that RewriteRule directive is forbidden: "
4474                      "%s", r->filename);
4475         return HTTP_FORBIDDEN;
4476     }
4477
4478     /*
4479      *  remember the current filename before rewriting for later check
4480      *  to prevent deadlooping because of internal redirects
4481      *  on final URL/filename which can be equal to the inital one.
4482      */
4483     ofilename = r->filename;
4484
4485     /*
4486      *  now apply the rules ...
4487      */
4488     rulestatus = apply_rewrite_list(r, dconf->rewriterules, dconf->directory);
4489     if (rulestatus) {
4490         unsigned skip;
4491
4492         if (ACTION_STATUS == rulestatus) {
4493             int n = r->status;
4494
4495             r->status = HTTP_OK;
4496             return n;
4497         }
4498
4499         l = strlen(r->filename);
4500         if (l > 6 && strncmp(r->filename, "proxy:", 6) == 0) {
4501             /* it should go on as an internal proxy request */
4502
4503             /* make sure the QUERY_STRING and
4504              * PATH_INFO parts get incorporated
4505              * (r->path_info was already appended by the
4506              * rewriting engine because of the per-dir context!)
4507              */
4508             if (r->args != NULL) {
4509                 r->filename = apr_pstrcat(r->pool, r->filename,
4510                                           "?", r->args, NULL);
4511             }
4512
4513             /* now make sure the request gets handled by the proxy handler */
4514             if (PROXYREQ_NONE == r->proxyreq) {
4515                 r->proxyreq = PROXYREQ_REVERSE;
4516             }
4517             r->handler  = "proxy-server";
4518
4519             rewritelog((r, 1, dconf->directory, "go-ahead with proxy request "
4520                         "%s [OK]", r->filename));
4521             return OK;
4522         }
4523         else if ((skip = is_absolute_uri(r->filename)) > 0) {
4524             /* it was finally rewritten to a remote URL */
4525
4526             /* because we are in a per-dir context
4527              * first try to replace the directory with its base-URL
4528              * if there is a base-URL available
4529              */
4530             if (dconf->baseurl != NULL) {
4531                 /* skip 'scheme://' */
4532                 cp = r->filename + skip;
4533
4534                 if ((cp = ap_strchr(cp, '/')) != NULL && *(++cp)) {
4535                     rewritelog((r, 2, dconf->directory, 
4536                                 "trying to replace prefix %s with %s",
4537                                 dconf->directory, dconf->baseurl));
4538
4539                     /* I think, that hack needs an explanation:
4540                      * well, here is it:
4541                      * mod_rewrite was written for unix systems, were
4542                      * absolute file-system paths start with a slash.
4543                      * URL-paths _also_ start with slashes, so they
4544                      * can be easily compared with system paths.
4545                      *
4546                      * the following assumes, that the actual url-path
4547                      * may be prefixed by the current directory path and
4548                      * tries to replace the system path with the RewriteBase
4549                      * URL.
4550                      * That assumption is true if we use a RewriteRule like
4551                      *
4552                      * RewriteRule ^foo bar [R]
4553                      *
4554                      * (see apply_rewrite_rule function)
4555                      * However on systems that don't have a / as system
4556                      * root this will never match, so we skip the / after the
4557                      * hostname and compare/substitute only the stuff after it.
4558                      *
4559                      * (note that cp was already increased to the right value)
4560                      */
4561                     cp2 = subst_prefix_path(r, cp, (*dconf->directory == '/')
4562                                                    ? dconf->directory + 1
4563                                                    : dconf->directory,
4564                                             dconf->baseurl + 1);
4565                     if (strcmp(cp2, cp) != 0) {
4566                         *cp = '\0';
4567                         r->filename = apr_pstrcat(r->pool, r->filename,
4568                                                   cp2, NULL);
4569                     }
4570                 }
4571             }
4572
4573             /* now prepare the redirect... */
4574             if (rulestatus != ACTION_NOESCAPE) {
4575                 rewritelog((r, 1, dconf->directory, "escaping %s for redirect",
4576                             r->filename));
4577                 r->filename = escape_absolute_uri(r->pool, r->filename, skip);
4578             }
4579
4580             /* append the QUERY_STRING part */
4581             if (r->args) {
4582                 r->filename = apr_pstrcat(r->pool, r->filename, "?",
4583                                           (rulestatus == ACTION_NOESCAPE)
4584                                             ? r->args
4585                                             : ap_escape_uri(r->pool, r->args),
4586                                           NULL);
4587             }
4588
4589             /* determine HTTP redirect response code */
4590             if (ap_is_HTTP_REDIRECT(r->status)) {
4591                 n = r->status;
4592                 r->status = HTTP_OK; /* make Apache kernel happy */
4593             }
4594             else {
4595                 n = HTTP_MOVED_TEMPORARILY;
4596             }
4597
4598             /* now do the redirection */
4599             apr_table_setn(r->headers_out, "Location", r->filename);
4600             rewritelog((r, 1, dconf->directory, "redirect to %s [REDIRECT/%d]",
4601                         r->filename, n));
4602             return n;
4603         }
4604         else {
4605             /* it was finally rewritten to a local path */
4606
4607             /* if someone used the PASSTHROUGH flag in per-dir
4608              * context we just ignore it. It is only useful
4609              * in per-server context
4610              */
4611             if (l > 12 && strncmp(r->filename, "passthrough:", 12) == 0) {
4612                 r->filename = apr_pstrdup(r->pool, r->filename+12);
4613             }
4614
4615             /* the filename must be either an absolute local path or an
4616              * absolute local URL.
4617              */
4618             if (   *r->filename != '/'
4619                 && !ap_os_is_path_absolute(r->pool, r->filename)) {
4620                 return HTTP_BAD_REQUEST;
4621             }
4622
4623             /* Check for deadlooping:
4624              * At this point we KNOW that at least one rewriting
4625              * rule was applied, but when the resulting URL is
4626              * the same as the initial URL, we are not allowed to
4627              * use the following internal redirection stuff because
4628              * this would lead to a deadloop.
4629              */
4630             if (strcmp(r->filename, ofilename) == 0) {
4631                 rewritelog((r, 1, dconf->directory, "initial URL equal rewritten"
4632                             " URL: %s [IGNORING REWRITE]", r->filename));
4633                 return OK;
4634             }
4635
4636             /* if there is a valid base-URL then substitute
4637              * the per-dir prefix with this base-URL if the
4638              * current filename still is inside this per-dir
4639              * context. If not then treat the result as a
4640              * plain URL
4641              */
4642             if (dconf->baseurl != NULL) {
4643                 rewritelog((r, 2, dconf->directory, "trying to replace prefix "
4644                             "%s with %s", dconf->directory, dconf->baseurl));
4645
4646                 r->filename = subst_prefix_path(r, r->filename,
4647                                                 dconf->directory,
4648                                                 dconf->baseurl);
4649             }
4650             else {
4651                 /* if no explicit base-URL exists we assume
4652                  * that the directory prefix is also a valid URL
4653                  * for this webserver and only try to remove the
4654                  * document_root if it is prefix
4655                  */
4656                 if ((ccp = ap_document_root(r)) != NULL) {
4657                     /* strip trailing slash */
4658                     l = strlen(ccp);
4659                     if (ccp[l-1] == '/') {
4660                         --l;
4661                     }
4662                     if (!strncmp(r->filename, ccp, l) &&
4663                         r->filename[l] == '/') {
4664                         rewritelog((r, 2,dconf->directory, "strip document_root"
4665                                     " prefix: %s -> %s", r->filename,
4666                                     r->filename+l));
4667
4668                         r->filename = apr_pstrdup(r->pool, r->filename+l);
4669                     }
4670                 }
4671             }
4672
4673             /* now initiate the internal redirect */
4674             rewritelog((r, 1, dconf->directory, "internal redirect with %s "
4675                         "[INTERNAL REDIRECT]", r->filename));
4676             r->filename = apr_pstrcat(r->pool, "redirect:", r->filename, NULL);
4677             r->handler = "redirect-handler";
4678             return OK;
4679         }
4680     }
4681     else {
4682         rewritelog((r, 1, dconf->directory, "pass through %s", r->filename));
4683         return DECLINED;
4684     }
4685 }
4686
4687 /*
4688  * MIME-type hook
4689  * [T=...,H=...] execution
4690  */
4691 static int hook_mimetype(request_rec *r)
4692 {
4693     const char *t;
4694
4695     /* type */
4696     t = apr_table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR);
4697     if (t && *t) {
4698         rewritelog((r, 1, NULL, "force filename %s to have MIME-type '%s'",
4699                     r->filename, t));
4700
4701         ap_set_content_type(r, t);
4702     }
4703
4704     /* handler */
4705     t = apr_table_get(r->notes, REWRITE_FORCED_HANDLER_NOTEVAR);
4706     if (t && *t) {
4707         rewritelog((r, 1, NULL, "force filename %s to have the "
4708                     "Content-handler '%s'", r->filename, t));
4709
4710         r->handler = t;
4711     }
4712
4713     return OK;
4714 }
4715
4716 /* check whether redirect limit is reached */
4717 static int is_redirect_limit_exceeded(request_rec *r)
4718 {
4719     request_rec *top = r;
4720     rewrite_request_conf *reqc;
4721     rewrite_perdir_conf *dconf;
4722
4723     /* we store it in the top request */
4724     while (top->main) {
4725         top = top->main;
4726     }
4727     while (top->prev) {
4728         top = top->prev;
4729     }
4730
4731     /* fetch our config */
4732     reqc = (rewrite_request_conf *) ap_get_module_config(top->request_config,
4733                                                          &rewrite_module);
4734
4735     /* no config there? create one. */
4736     if (!reqc) {
4737         rewrite_server_conf *sconf;
4738
4739         reqc = apr_palloc(top->pool, sizeof(rewrite_request_conf));
4740         sconf = ap_get_module_config(r->server->module_config, &rewrite_module);
4741
4742         reqc->redirects = 0;
4743         reqc->redirect_limit = sconf->redirect_limit
4744                                  ? sconf->redirect_limit
4745                                  : REWRITE_REDIRECT_LIMIT;
4746
4747         /* associate it with this request */
4748         ap_set_module_config(top->request_config, &rewrite_module, reqc);
4749     }
4750
4751     /* allow to change the limit during redirects. */
4752     dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
4753                                                         &rewrite_module);
4754
4755     /* 0 == unset; take server conf ... */
4756     if (dconf->redirect_limit) {
4757         reqc->redirect_limit = dconf->redirect_limit;
4758     }
4759
4760     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
4761                   "mod_rewrite's internal redirect status: %d/%d.",
4762                   reqc->redirects, reqc->redirect_limit);
4763
4764     /* and now give the caller a hint */
4765     return (reqc->redirects++ >= reqc->redirect_limit);
4766 }
4767
4768 /*
4769  * "content" handler for internal redirects
4770  */
4771 static int handler_redirect(request_rec *r)
4772 {
4773     if (strcmp(r->handler, "redirect-handler")) {
4774         return DECLINED;
4775     }
4776
4777     /* just make sure that we are really meant! */
4778     if (strncmp(r->filename, "redirect:", 9) != 0) {
4779         return DECLINED;
4780     }
4781
4782     if (is_redirect_limit_exceeded(r)) {
4783         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4784                       "mod_rewrite: maximum number of internal redirects "
4785                       "reached. Assuming configuration error. Use "
4786                       "'RewriteOptions MaxRedirects' to increase the limit "
4787                       "if neccessary.");
4788         return HTTP_INTERNAL_SERVER_ERROR;
4789     }
4790
4791     /* now do the internal redirect */
4792     ap_internal_redirect(apr_pstrcat(r->pool, r->filename+9,
4793                                      r->args ? "?" : NULL, r->args, NULL), r);
4794
4795     /* and return gracefully */
4796     return OK;
4797 }
4798
4799
4800 /*
4801  * +-------------------------------------------------------+
4802  * |                                                       |
4803  * |                Module paraphernalia
4804  * |                                                       |
4805  * +-------------------------------------------------------+
4806  */
4807
4808 #ifdef REWRITELOG_DISABLED
4809 static const char *fake_rewritelog(cmd_parms *cmd, void *dummy, const char *a1)
4810 {
4811     return "RewriteLog and RewriteLogLevel are not supported by this build "
4812            "of mod_rewrite because it was compiled using the "
4813            "-DREWRITELOG_DISABLED compiler option. You have to recompile "
4814            "mod_rewrite WITHOUT this option in order to use the rewrite log.";
4815 }
4816 #endif
4817
4818 static const command_rec command_table[] = {
4819     AP_INIT_FLAG(    "RewriteEngine",   cmd_rewriteengine,  NULL, OR_FILEINFO,
4820                      "On or Off to enable or disable (default) the whole "
4821                      "rewriting engine"),
4822     AP_INIT_ITERATE( "RewriteOptions",  cmd_rewriteoptions,  NULL, OR_FILEINFO,
4823                      "List of option strings to set"),
4824     AP_INIT_TAKE1(   "RewriteBase",     cmd_rewritebase,     NULL, OR_FILEINFO,
4825                      "the base URL of the per-directory context"),
4826     AP_INIT_RAW_ARGS("RewriteCond",     cmd_rewritecond,     NULL, OR_FILEINFO,
4827                      "an input string and a to be applied regexp-pattern"),
4828     AP_INIT_RAW_ARGS("RewriteRule",     cmd_rewriterule,     NULL, OR_FILEINFO,
4829                      "an URL-applied regexp-pattern and a substitution URL"),
4830     AP_INIT_TAKE2(   "RewriteMap",      cmd_rewritemap,      NULL, RSRC_CONF,
4831                      "a mapname and a filename"),
4832     AP_INIT_TAKE1(   "RewriteLock",     cmd_rewritelock,     NULL, RSRC_CONF,
4833                      "the filename of a lockfile used for inter-process "
4834                      "synchronization"),
4835 #ifndef REWRITELOG_DISABLED
4836     AP_INIT_TAKE1(   "RewriteLog",      cmd_rewritelog,      NULL, RSRC_CONF,
4837                      "the filename of the rewriting logfile"),
4838     AP_INIT_TAKE1(   "RewriteLogLevel", cmd_rewriteloglevel, NULL, RSRC_CONF,
4839                      "the level of the rewriting logfile verbosity "
4840                      "(0=none, 1=std, .., 9=max)"),
4841 #else
4842     AP_INIT_TAKE1(   "RewriteLog", fake_rewritelog, NULL, RSRC_CONF,
4843                      "[DISABLED] the filename of the rewriting logfile"),
4844     AP_INIT_TAKE1(   "RewriteLogLevel", fake_rewritelog, NULL, RSRC_CONF,
4845                      "[DISABLED] the level of the rewriting logfile verbosity"),
4846 #endif
4847     { NULL }
4848 };
4849
4850 static void ap_register_rewrite_mapfunc(char *name, rewrite_mapfunc_t *func)
4851 {
4852     apr_hash_set(mapfunc_hash, name, strlen(name), (const void *)func);
4853 }
4854
4855 static void register_hooks(apr_pool_t *p)
4856 {
4857     /* fixup after mod_proxy, so that the proxied url will not
4858      * escaped accidentally by mod_proxy's fixup.
4859      */
4860     static const char * const aszPre[]={ "mod_proxy.c", NULL };
4861
4862     APR_REGISTER_OPTIONAL_FN(ap_register_rewrite_mapfunc);
4863
4864     ap_hook_handler(handler_redirect, NULL, NULL, APR_HOOK_MIDDLE);
4865     ap_hook_pre_config(pre_config, NULL, NULL, APR_HOOK_MIDDLE);
4866     ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_MIDDLE);
4867     ap_hook_child_init(init_child, NULL, NULL, APR_HOOK_MIDDLE);
4868
4869     ap_hook_fixups(hook_fixup, aszPre, NULL, APR_HOOK_FIRST);
4870     ap_hook_fixups(hook_mimetype, NULL, NULL, APR_HOOK_LAST);
4871     ap_hook_translate_name(hook_uri2file, NULL, NULL, APR_HOOK_FIRST);
4872 }
4873
4874     /* the main config structure */
4875 module AP_MODULE_DECLARE_DATA rewrite_module = {
4876    STANDARD20_MODULE_STUFF,
4877    config_perdir_create,        /* create per-dir    config structures */
4878    config_perdir_merge,         /* merge  per-dir    config structures */
4879    config_server_create,        /* create per-server config structures */
4880    config_server_merge,         /* merge  per-server config structures */
4881    command_table,               /* table of config file commands       */
4882    register_hooks               /* register hooks                      */
4883 };
4884
4885 /*EOF*/