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