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