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