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