]> granicus.if.org Git - apache/blob - modules/mappers/mod_rewrite.c
fix error handling during flag parsing
[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     const char *eol = APR_EOL_STR;
1352     int eolc = 0;
1353
1354 #ifndef NO_WRITEV
1355     struct iovec iova[2];
1356     apr_size_t niov;
1357 #endif
1358
1359     /* when `RewriteEngine off' was used in the per-server
1360      * context then the rewritemap-programs were not spawned.
1361      * In this case using such a map (usually in per-dir context)
1362      * is useless because it is not available.
1363      *
1364      * newlines in the key leave bytes in the pipe and cause
1365      * bad things to happen (next map lookup will use the chars
1366      * after the \n instead of the new key etc etc - in other words,
1367      * the Rewritemap falls out of sync with the requests).
1368      */
1369     if (fpin == NULL || fpout == NULL || ap_strchr(key, '\n')) {
1370         return NULL;
1371     }
1372
1373     /* take the lock */
1374     if (rewrite_mapr_lock_acquire) {
1375         rv = apr_global_mutex_lock(rewrite_mapr_lock_acquire);
1376         if (rv != APR_SUCCESS) {
1377             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1378                           "apr_global_mutex_lock(rewrite_mapr_lock_acquire) "
1379                           "failed");
1380             return NULL; /* Maybe this should be fatal? */
1381         }
1382     }
1383
1384     /* write out the request key */
1385 #ifdef NO_WRITEV
1386     nbytes = strlen(key);
1387     apr_file_write(fpin, key, &nbytes);
1388     nbytes = 1;
1389     apr_file_write(fpin, "\n", &nbytes);
1390 #else
1391     iova[0].iov_base = key;
1392     iova[0].iov_len = strlen(key);
1393     iova[1].iov_base = "\n";
1394     iova[1].iov_len = 1;
1395
1396     niov = 2;
1397     apr_file_writev(fpin, iova, niov, &nbytes);
1398 #endif
1399
1400     /* read in the response value */
1401     i = 0;
1402     nbytes = 1;
1403     apr_file_read(fpout, &c, &nbytes);
1404     while (nbytes == 1 && (i < REWRITE_MAX_PRG_MAP_LINE)) {
1405         if (c == eol[eolc]) {
1406             if (!eol[++eolc]) {
1407                 /* remove eol from the buffer */
1408                 i -= --eolc;
1409                 break;
1410             }
1411         }
1412
1413         /* only partial (invalid) eol sequence -> reset the counter */
1414         else if (eolc) {
1415             eolc = 0;
1416         }
1417
1418         /* catch binary mode, e.g. on Win32 */
1419         else if (c == '\n') { 
1420             break;
1421         }
1422
1423         buf[i++] = c;
1424         apr_file_read(fpout, &c, &nbytes);
1425     }
1426
1427     /* give the lock back */
1428     if (rewrite_mapr_lock_acquire) {
1429         rv = apr_global_mutex_unlock(rewrite_mapr_lock_acquire);
1430         if (rv != APR_SUCCESS) {
1431             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1432                           "apr_global_mutex_unlock(rewrite_mapr_lock_acquire) "
1433                           "failed");
1434             return NULL; /* Maybe this should be fatal? */
1435         }
1436     }
1437
1438     /* buf is not zero terminated, so be careful! */
1439     if (i == 4 && strncasecmp(buf, "NULL", 4) == 0) {
1440         return NULL;
1441     }
1442
1443     return apr_pstrmemdup(r->pool, buf, i);
1444 }
1445
1446 /*
1447  * generic map lookup
1448  */
1449 static char *lookup_map(request_rec *r, char *name, char *key)
1450 {
1451     rewrite_server_conf *conf;
1452     rewritemap_entry *s;
1453     char *value;
1454     apr_finfo_t st;
1455     apr_status_t rv;
1456
1457     /* get map configuration */
1458     conf = ap_get_module_config(r->server->module_config, &rewrite_module);
1459     s = apr_hash_get(conf->rewritemaps, name, APR_HASH_KEY_STRING);
1460
1461     /* map doesn't exist */
1462     if (!s) {
1463         return NULL;
1464     }
1465
1466     switch (s->type) {
1467     /*
1468      * Text file map (perhaps random)
1469      */
1470     case MAPTYPE_RND:
1471     case MAPTYPE_TXT:
1472         rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool);
1473         if (rv != APR_SUCCESS) {
1474             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1475                           "mod_rewrite: can't access text RewriteMap file %s",
1476                           s->checkfile);
1477             rewritelog((r, 1, NULL,
1478                         "can't open RewriteMap file, see error log"));
1479             return NULL;
1480         }
1481
1482         value = get_cache_value(name, st.mtime, key, r->pool);
1483         if (!value) {
1484             rewritelog((r, 6, NULL,
1485                         "cache lookup FAILED, forcing new map lookup"));
1486
1487             value = lookup_map_txtfile(r, s->datafile, key);
1488             if (!value) {
1489                 rewritelog((r, 5, NULL, "map lookup FAILED: map=%s[txt] key=%s",
1490                             name, key));
1491                 set_cache_value(name, st.mtime, key, "");
1492                 return NULL;
1493             }
1494
1495             rewritelog((r, 5, NULL,"map lookup OK: map=%s[txt] key=%s -> val=%s",
1496                         name, key, value));
1497             set_cache_value(name, st.mtime, key, value);
1498         }
1499         else {
1500             rewritelog((r,5,NULL,"cache lookup OK: map=%s[txt] key=%s -> val=%s",
1501                         name, key, value));
1502         }
1503
1504         if (s->type == MAPTYPE_RND && *value) {
1505             value = select_random_value_part(r, value);
1506             rewritelog((r, 5, NULL, "randomly chosen the subvalue `%s'",value));
1507         }
1508
1509         return *value ? value : NULL;
1510
1511     /*
1512      * DBM file map
1513      */
1514     case MAPTYPE_DBM:
1515         rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool);
1516         if (rv != APR_SUCCESS) {
1517             ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
1518                           "mod_rewrite: can't access DBM RewriteMap file %s",
1519                           s->checkfile);
1520             rewritelog((r, 1, NULL,
1521                         "can't open DBM RewriteMap file, see error log"));
1522             return NULL;
1523         }
1524
1525         value = get_cache_value(name, st.mtime, key, r->pool);
1526         if (!value) {
1527             rewritelog((r, 6, NULL,
1528                         "cache lookup FAILED, forcing new map lookup"));
1529
1530             value = lookup_map_dbmfile(r, s->datafile, s->dbmtype, key);
1531             if (!value) {
1532                 rewritelog((r, 5, NULL, "map lookup FAILED: map=%s[dbm] key=%s",
1533                             name, key));
1534                 set_cache_value(name, st.mtime, key, "");
1535                 return NULL;
1536             }
1537
1538             rewritelog((r, 5, NULL, "map lookup OK: map=%s[dbm] key=%s -> "
1539                         "val=%s", name, key, value));
1540
1541             set_cache_value(name, st.mtime, key, value);
1542             return value;
1543         }
1544
1545         rewritelog((r, 5, NULL, "cache lookup OK: map=%s[dbm] key=%s -> val=%s",
1546                     name, key, value));
1547         return *value ? value : NULL;
1548
1549     /*
1550      * Program file map
1551      */
1552     case MAPTYPE_PRG:
1553         value = lookup_map_program(r, s->fpin, s->fpout, key);
1554         if (!value) {
1555             rewritelog((r, 5,NULL,"map lookup FAILED: map=%s key=%s", name,
1556                         key));
1557             return NULL;
1558         }
1559
1560         rewritelog((r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s",
1561                     name, key, value));
1562         return value;
1563
1564     /*
1565      * Internal Map
1566      */
1567     case MAPTYPE_INT:
1568         value = s->func(r, key);
1569         if (!value) {
1570             rewritelog((r, 5,NULL,"map lookup FAILED: map=%s key=%s", name,
1571                         key));
1572             return NULL;
1573         }
1574
1575         rewritelog((r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s",
1576                     name, key, value));
1577         return value;
1578     }
1579
1580     return NULL;
1581 }
1582
1583 /*
1584  * lookup a HTTP header and set VARY note
1585  */
1586 static const char *lookup_header(const char *name, rewrite_ctx *ctx)
1587 {
1588     const char *val = apr_table_get(ctx->r->headers_in, name);
1589
1590     if (val) {
1591         ctx->vary_this = ctx->vary_this
1592                          ? apr_pstrcat(ctx->r->pool, ctx->vary_this, ", ",
1593                                        name, NULL)
1594                          : apr_pstrdup(ctx->r->pool, name);
1595     }
1596
1597     return val;
1598 }
1599
1600 /*
1601  * lookahead helper function
1602  * Determine the correct URI path in perdir context
1603  */
1604 static APR_INLINE const char *la_u(rewrite_ctx *ctx)
1605 {
1606     rewrite_perdir_conf *conf;
1607
1608     if (*ctx->uri == '/') {
1609         return ctx->uri;
1610     }
1611
1612     conf = ap_get_module_config(ctx->r->per_dir_config, &rewrite_module);
1613
1614     return apr_pstrcat(ctx->r->pool, conf->baseurl
1615                                      ? conf->baseurl : conf->directory,
1616                        ctx->uri, NULL);
1617 }
1618
1619 /*
1620  * generic variable lookup
1621  */
1622 static char *lookup_variable(char *var, rewrite_ctx *ctx)
1623 {
1624     const char *result;
1625     request_rec *r = ctx->r;
1626     apr_size_t varlen = strlen(var);
1627
1628     /* fast exit */
1629     if (varlen < 4) {
1630         return apr_pstrdup(r->pool, "");
1631     }
1632
1633     result = NULL;
1634
1635     /* fast tests for variable length variables (sic) first */
1636     if (var[3] == ':') {
1637         if (var[4] && !strncasecmp(var, "ENV", 3)) {
1638             var += 4;
1639             result = apr_table_get(r->notes, var);
1640
1641             if (!result) {
1642                 result = apr_table_get(r->subprocess_env, var);
1643             }
1644             if (!result) {
1645                 result = getenv(var);
1646             }
1647         }
1648     }
1649     else if (var[4] == ':') {
1650         if (var[5]) {
1651             request_rec *rr;
1652             const char *path;
1653
1654             if (!strncasecmp(var, "HTTP", 4)) {
1655                 result = lookup_header(var+5, ctx);
1656             }
1657             else if (!strncasecmp(var, "LA-U", 4)) {
1658                 if (ctx->uri && subreq_ok(r)) {
1659                     path = ctx->perdir ? la_u(ctx) : ctx->uri;
1660                     rr = ap_sub_req_lookup_uri(path, r, NULL);
1661                     ctx->r = rr;
1662                     result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx));
1663                     ctx->r = r;
1664                     ap_destroy_sub_req(rr);
1665
1666                     rewritelog((r, 5, ctx->perdir, "lookahead: path=%s var=%s "
1667                                 "-> val=%s", path, var+5, result));
1668
1669                     return (char *)result;
1670                 }
1671             }
1672             else if (!strncasecmp(var, "LA-F", 4)) {
1673                 if (ctx->uri && subreq_ok(r)) {
1674                     path = ctx->uri;
1675                     if (ctx->perdir && *path == '/') {
1676                         /* sigh, the user wants a file based subrequest, but
1677                          * we can't do one, since we don't know what the file
1678                          * path is! In this case behave like LA-U.
1679                          */
1680                         rr = ap_sub_req_lookup_uri(path, r, NULL);
1681                     }
1682                     else {
1683                         if (ctx->perdir) {
1684                             rewrite_perdir_conf *conf;
1685
1686                             conf = ap_get_module_config(r->per_dir_config,
1687                                                         &rewrite_module);
1688
1689                             path = apr_pstrcat(r->pool, conf->directory, path,
1690                                                NULL);
1691                         }
1692
1693                         rr = ap_sub_req_lookup_file(path, r, NULL);
1694                     }
1695
1696                     ctx->r = rr;
1697                     result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx));
1698                     ctx->r = r;
1699                     ap_destroy_sub_req(rr);
1700
1701                     rewritelog((r, 5, ctx->perdir, "lookahead: path=%s var=%s "
1702                                 "-> val=%s", path, var+5, result));
1703
1704                     return (char *)result;
1705                 }
1706             }
1707         }
1708     }
1709
1710     /* well, do it the hard way */
1711     else {
1712         char *p;
1713         apr_time_exp_t tm;
1714
1715         /* can't do this above, because of the getenv call */
1716         for (p = var; *p; ++p) {
1717             *p = apr_toupper(*p);
1718         }
1719
1720         switch (varlen) {
1721         case  4:
1722             if (!strcmp(var, "TIME")) {
1723                 apr_time_exp_lt(&tm, apr_time_now());
1724                 result = apr_psprintf(r->pool, "%04d%02d%02d%02d%02d%02d",
1725                                       tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday,
1726                                       tm.tm_hour, tm.tm_min, tm.tm_sec);
1727                 rewritelog((r, 1, ctx->perdir, "RESULT='%s'", result));
1728                 return (char *)result;
1729             }
1730             break;
1731
1732         case  8:
1733             switch (var[6]) {
1734             case 'A':
1735                 if (!strcmp(var, "TIME_DAY")) {
1736                     apr_time_exp_lt(&tm, apr_time_now());
1737                     return apr_psprintf(r->pool, "%02d", tm.tm_mday);
1738                 }
1739                 break;
1740
1741             case 'E':
1742                 if (!strcmp(var, "TIME_SEC")) {
1743                     apr_time_exp_lt(&tm, apr_time_now());
1744                     return apr_psprintf(r->pool, "%02d", tm.tm_sec);
1745                 }
1746                 break;
1747
1748             case 'I':
1749                 if (!strcmp(var, "TIME_MIN")) {
1750                     apr_time_exp_lt(&tm, apr_time_now());
1751                     return apr_psprintf(r->pool, "%02d", tm.tm_min);
1752                 }
1753                 break;
1754
1755             case 'O':
1756                 if (!strcmp(var, "TIME_MON")) {
1757                     apr_time_exp_lt(&tm, apr_time_now());
1758                     return apr_psprintf(r->pool, "%02d", tm.tm_mon+1);
1759                 }
1760                 break;
1761             }
1762             break;
1763
1764         case  9:
1765             switch (var[7]) {
1766             case 'A':
1767                 if (var[8] == 'Y' && !strcmp(var, "TIME_WDAY")) {
1768                     apr_time_exp_lt(&tm, apr_time_now());
1769                     return apr_psprintf(r->pool, "%d", tm.tm_wday);
1770                 }
1771                 else if (!strcmp(var, "TIME_YEAR")) {
1772                     apr_time_exp_lt(&tm, apr_time_now());
1773                     return apr_psprintf(r->pool, "%04d", tm.tm_year+1900);
1774                 }
1775                 break;
1776
1777             case 'E':
1778                 if (!strcmp(var, "IS_SUBREQ")) {
1779                     result = (r->main ? "true" : "false");
1780                 }
1781                 break;
1782
1783             case 'F':
1784                 if (!strcmp(var, "PATH_INFO")) {
1785                     result = r->path_info;
1786                 }
1787                 break;
1788
1789             case 'P':
1790                 if (!strcmp(var, "AUTH_TYPE")) {
1791                     result = r->ap_auth_type;
1792                 }
1793                 break;
1794
1795             case 'S':
1796                 if (!strcmp(var, "HTTP_HOST")) {
1797                     result = lookup_header("Host", ctx);
1798                 }
1799                 break;
1800
1801             case 'U':
1802                 if (!strcmp(var, "TIME_HOUR")) {
1803                     apr_time_exp_lt(&tm, apr_time_now());
1804                     return apr_psprintf(r->pool, "%02d", tm.tm_hour);
1805                 }
1806                 break;
1807             }
1808             break;
1809
1810         case 11:
1811             switch (var[8]) {
1812             case 'A':
1813                 if (!strcmp(var, "SERVER_NAME")) {
1814                     result = ap_get_server_name(r);
1815                 }
1816                 break;
1817
1818             case 'D':
1819                 if (*var == 'R' && !strcmp(var, "REMOTE_ADDR")) {
1820                     result = r->connection->remote_ip;
1821                 }
1822                 else if (!strcmp(var, "SERVER_ADDR")) {
1823                     result = r->connection->local_ip;
1824                 }
1825                 break;
1826
1827             case 'E':
1828                 if (*var == 'H' && !strcmp(var, "HTTP_ACCEPT")) {
1829                     result = lookup_header("Accept", ctx);
1830                 }
1831                 else if (!strcmp(var, "THE_REQUEST")) {
1832                     result = r->the_request;
1833                 }
1834                 break;
1835
1836             case 'I':
1837                 if (!strcmp(var, "API_VERSION")) {
1838                     return apr_psprintf(r->pool, "%d:%d",
1839                                         MODULE_MAGIC_NUMBER_MAJOR,
1840                                         MODULE_MAGIC_NUMBER_MINOR);
1841                 }
1842                 break;
1843
1844             case 'K':
1845                 if (!strcmp(var, "HTTP_COOKIE")) {
1846                     result = lookup_header("Cookie", ctx);
1847                 }
1848                 break;
1849
1850             case 'O':
1851                 if (*var == 'S' && !strcmp(var, "SERVER_PORT")) {
1852                     return apr_psprintf(r->pool, "%u", ap_get_server_port(r));
1853                 }
1854                 else if (var[7] == 'H' && !strcmp(var, "REMOTE_HOST")) {
1855                     result = ap_get_remote_host(r->connection,r->per_dir_config,
1856                                                 REMOTE_NAME, NULL);
1857                 }
1858                 else if (!strcmp(var, "REMOTE_PORT")) {
1859                     return apr_itoa(r->pool, r->connection->remote_addr->port);
1860                 }
1861                 break;
1862
1863             case 'S':
1864                 if (*var == 'R' && !strcmp(var, "REMOTE_USER")) {
1865                     result = r->user;
1866                 }
1867                 else if (!strcmp(var, "SCRIPT_USER")) {
1868                     result = "<unknown>";
1869                     if (r->finfo.valid & APR_FINFO_USER) {
1870                         apr_uid_name_get((char **)&result, r->finfo.user,
1871                                          r->pool);
1872                     }
1873                 }
1874                 break;
1875
1876             case 'U':
1877                 if (!strcmp(var, "REQUEST_URI")) {
1878                     result = r->uri;
1879                 }
1880                 break;
1881             }
1882             break;
1883
1884         case 12:
1885             switch (var[3]) {
1886             case 'I':
1887                 if (!strcmp(var, "SCRIPT_GROUP")) {
1888                     result = "<unknown>";
1889                     if (r->finfo.valid & APR_FINFO_GROUP) {
1890                         apr_gid_name_get((char **)&result, r->finfo.group,
1891                                          r->pool);
1892                     }
1893                 }
1894                 break;
1895
1896             case 'O':
1897                 if (!strcmp(var, "REMOTE_IDENT")) {
1898                     result = ap_get_remote_logname(r);
1899                 }
1900                 break;
1901
1902             case 'P':
1903                 if (!strcmp(var, "HTTP_REFERER")) {
1904                     result = lookup_header("Referer", ctx);
1905                 }
1906                 break;
1907
1908             case 'R':
1909                 if (!strcmp(var, "QUERY_STRING")) {
1910                     result = r->args;
1911                 }
1912                 break;
1913
1914             case 'V':
1915                 if (!strcmp(var, "SERVER_ADMIN")) {
1916                     result = r->server->server_admin;
1917                 }
1918                 break;
1919             }
1920             break;
1921
1922         case 13:
1923             if (!strcmp(var, "DOCUMENT_ROOT")) {
1924                 result = ap_document_root(r);
1925             }
1926             break;
1927
1928         case 14:
1929             if (*var == 'H' && !strcmp(var, "HTTP_FORWARDED")) {
1930                 result = lookup_header("Forwarded", ctx);
1931             }
1932             else if (!strcmp(var, "REQUEST_METHOD")) {
1933                 result = r->method;
1934             }
1935             break;
1936
1937         case 15:
1938             switch (var[7]) {
1939             case 'E':
1940                 if (!strcmp(var, "HTTP_USER_AGENT")) {
1941                     result = lookup_header("User-Agent", ctx);
1942                 }
1943                 break;
1944
1945             case 'F':
1946                 if (!strcmp(var, "SCRIPT_FILENAME")) {
1947                     result = r->filename; /* same as request_filename (16) */
1948                 }
1949                 break;
1950
1951             case 'P':
1952                 if (!strcmp(var, "SERVER_PROTOCOL")) {
1953                     result = r->protocol;
1954                 }
1955                 break;
1956
1957             case 'S':
1958                 if (!strcmp(var, "SERVER_SOFTWARE")) {
1959                     result = ap_get_server_version();
1960                 }
1961                 break;
1962             }
1963             break;
1964
1965         case 16:
1966             if (!strcmp(var, "REQUEST_FILENAME")) {
1967                 result = r->filename; /* same as script_filename (15) */
1968             }
1969             break;
1970
1971         case 21:
1972             if (!strcmp(var, "HTTP_PROXY_CONNECTION")) {
1973                 result = lookup_header("Proxy-Connection", ctx);
1974             }
1975             break;
1976         }
1977     }
1978
1979     return apr_pstrdup(r->pool, result ? result : "");
1980 }
1981
1982
1983 /*
1984  * +-------------------------------------------------------+
1985  * |                                                       |
1986  * |                 Expansion functions
1987  * |                                                       |
1988  * +-------------------------------------------------------+
1989  */
1990
1991 /*
1992  * Bracketed expression handling
1993  * s points after the opening bracket
1994  */
1995 static APR_INLINE char *find_closing_curly(char *s)
1996 {
1997     unsigned depth;
1998
1999     for (depth = 1; *s; ++s) {
2000         if (*s == RIGHT_CURLY && --depth == 0) {
2001             return s;
2002         }
2003         else if (*s == LEFT_CURLY) {
2004             ++depth;
2005         }
2006     }
2007
2008     return NULL;
2009 }
2010
2011 static APR_INLINE char *find_char_in_curlies(char *s, int c)
2012 {
2013     unsigned depth;
2014
2015     for (depth = 1; *s; ++s) {
2016         if (*s == c && depth == 1) {
2017             return s;
2018         }
2019         else if (*s == RIGHT_CURLY && --depth == 0) {
2020             return NULL;
2021         }
2022         else if (*s == LEFT_CURLY) {
2023             ++depth;
2024         }
2025     }
2026
2027     return NULL;
2028 }
2029
2030 /* perform all the expansions on the input string
2031  * putting the result into a new string
2032  *
2033  * for security reasons this expansion must be performed in a
2034  * single pass, otherwise an attacker can arrange for the result
2035  * of an earlier expansion to include expansion specifiers that
2036  * are interpreted by a later expansion, producing results that
2037  * were not intended by the administrator.
2038  */
2039 static char *do_expand(char *input, rewrite_ctx *ctx)
2040 {
2041     result_list *result, *current;
2042     result_list sresult[SMALL_EXPANSION];
2043     unsigned spc = 0;
2044     apr_size_t span, inputlen, outlen;
2045     char *p, *c;
2046     apr_pool_t *pool = ctx->r->pool;
2047
2048     span = strcspn(input, "\\$%");
2049     inputlen = strlen(input);
2050
2051     /* fast exit */
2052     if (inputlen == span) {
2053         return apr_pstrdup(pool, input);
2054     }
2055
2056     /* well, actually something to do */
2057     result = current = &(sresult[spc++]);
2058
2059     p = input + span;
2060     current->next = NULL;
2061     current->string = input;
2062     current->len = span;
2063     outlen = span;
2064
2065     /* loop for specials */
2066     do {
2067         /* prepare next entry */
2068         if (current->len) {
2069             current->next = (spc < SMALL_EXPANSION)
2070                             ? &(sresult[spc++])
2071                             : (result_list *)apr_palloc(pool,
2072                                                         sizeof(result_list));
2073             current = current->next;
2074             current->next = NULL;
2075             current->len = 0;
2076         }
2077
2078         /* escaped character */
2079         if (*p == '\\') {
2080             current->len = 1;
2081             ++outlen;
2082             if (!p[1]) {
2083                 current->string = p;
2084                 break;
2085             }
2086             else {
2087                 current->string = ++p;
2088                 ++p;
2089             }
2090         }
2091
2092         /* variable or map lookup */
2093         else if (p[1] == '{') {
2094             char *endp;
2095
2096             endp = find_closing_curly(p+2);
2097             if (!endp) {
2098                 current->len = 2;
2099                 current->string = p;
2100                 outlen += 2;
2101                 p += 2;
2102             }
2103
2104             /* variable lookup */
2105             else if (*p == '%') {
2106                 p = lookup_variable(apr_pstrmemdup(pool, p+2, endp-p-2), ctx);
2107
2108                 span = strlen(p);
2109                 current->len = span;
2110                 current->string = p;
2111                 outlen += span;
2112                 p = endp + 1;
2113             }
2114
2115             /* map lookup */
2116             else {     /* *p == '$' */
2117                 char *key;
2118
2119                 /*
2120                  * To make rewrite maps useful, the lookup key and
2121                  * default values must be expanded, so we make
2122                  * recursive calls to do the work. For security
2123                  * reasons we must never expand a string that includes
2124                  * verbatim data from the network. The recursion here
2125                  * isn't a problem because the result of expansion is
2126                  * only passed to lookup_map() so it cannot be
2127                  * re-expanded, only re-looked-up. Another way of
2128                  * looking at it is that the recursion is entirely
2129                  * driven by the syntax of the nested curly brackets.
2130                  */
2131
2132                 key = find_char_in_curlies(p+2, ':');
2133                 if (!key) {
2134                     current->len = 2;
2135                     current->string = p;
2136                     outlen += 2;
2137                     p += 2;
2138                 }
2139                 else {
2140                     char *map, *dflt;
2141
2142                     map = apr_pstrmemdup(pool, p+2, endp-p-2);
2143                     key = map + (key-p-2);
2144                     *key++ = '\0';
2145                     dflt = find_char_in_curlies(key, '|');
2146                     if (dflt) {
2147                         *dflt++ = '\0';
2148                     }
2149
2150                     /* reuse of key variable as result */
2151                     key = lookup_map(ctx->r, map, do_expand(key, ctx));
2152
2153                     if (!key && dflt && *dflt) {
2154                         key = do_expand(dflt, ctx);
2155                     }
2156
2157                     if (key) {
2158                         span = strlen(key);
2159                         current->len = span;
2160                         current->string = key;
2161                         outlen += span;
2162                     }
2163
2164                     p = endp + 1;
2165                 }
2166             }
2167         }
2168
2169         /* backreference */
2170         else if (apr_isdigit(p[1])) {
2171             int n = p[1] - '0';
2172             backrefinfo *bri = (*p == '$') ? &ctx->briRR : &ctx->briRC;
2173
2174             /* see ap_pregsub() in server/util.c */
2175             if (bri->source && n < AP_MAX_REG_MATCH
2176                 && bri->regmatch[n].rm_eo > bri->regmatch[n].rm_so) {
2177                 span = bri->regmatch[n].rm_eo - bri->regmatch[n].rm_so;
2178
2179                 current->len = span;
2180                 current->string = bri->source + bri->regmatch[n].rm_so;
2181                 outlen += span;
2182             }
2183
2184             p += 2;
2185         }
2186
2187         /* not for us, just copy it */
2188         else {
2189             current->len = 1;
2190             current->string = p++;
2191             ++outlen;
2192         }
2193
2194         /* check the remainder */
2195         if (*p && (span = strcspn(p, "\\$%")) > 0) {
2196             if (current->len) {
2197                 current->next = (spc < SMALL_EXPANSION)
2198                                 ? &(sresult[spc++])
2199                                 : (result_list *)apr_palloc(pool,
2200                                                            sizeof(result_list));
2201                 current = current->next;
2202                 current->next = NULL;
2203             }
2204
2205             current->len = span;
2206             current->string = p;
2207             p += span;
2208             outlen += span;
2209         }
2210
2211     } while (p < input+inputlen);
2212
2213     /* assemble result */
2214     c = p = apr_palloc(pool, outlen + 1); /* don't forget the \0 */
2215     do {
2216         if (result->len) {
2217             ap_assert(c+result->len <= p+outlen); /* XXX: can be removed after
2218                                                    * extensive testing and
2219                                                    * review
2220                                                    */
2221             memcpy(c, result->string, result->len);
2222             c += result->len;
2223         }
2224         result = result->next;
2225     } while (result);
2226
2227     p[outlen] = '\0';
2228
2229     return p;
2230 }
2231
2232 /*
2233  * perform all the expansions on the environment variables
2234  */
2235 static void do_expand_env(data_item *env, rewrite_ctx *ctx)
2236 {
2237     char *name, *val;
2238
2239     while (env) {
2240         name = do_expand(env->data, ctx);
2241         if ((val = ap_strchr(name, ':')) != NULL) {
2242             *val++ = '\0';
2243
2244             apr_table_set(ctx->r->subprocess_env, name, val);
2245             rewritelog((ctx->r, 5, NULL, "setting env variable '%s' to '%s'",
2246                         name, val));
2247         }
2248
2249         env = env->next;
2250     }
2251
2252     return;
2253 }
2254
2255 /*
2256  * perform all the expansions on the cookies
2257  *
2258  * TODO: use cached time similar to how logging does it
2259  */
2260 static void add_cookie(request_rec *r, char *s)
2261 {
2262     char *var;
2263     char *val;
2264     char *domain;
2265     char *expires;
2266     char *path;
2267
2268     char *tok_cntx;
2269     char *cookie;
2270
2271     var = apr_strtok(s, ":", &tok_cntx);
2272     val = apr_strtok(NULL, ":", &tok_cntx);
2273     domain = apr_strtok(NULL, ":", &tok_cntx);
2274
2275     if (var && val && domain) {
2276         request_rec *rmain = r;
2277         char *notename;
2278         void *data;
2279
2280         while (rmain->main) {
2281             rmain = rmain->main;
2282         }
2283
2284         notename = apr_pstrcat(rmain->pool, var, "_rewrite", NULL);
2285         apr_pool_userdata_get(&data, notename, rmain->pool);
2286         if (!data) {
2287             char *exp_time = NULL;
2288
2289             expires = apr_strtok(NULL, ":", &tok_cntx);
2290             path = expires ? apr_strtok(NULL, ":", &tok_cntx) : NULL;
2291
2292             if (expires) {
2293                 apr_time_exp_t tms;
2294                 apr_time_exp_gmt(&tms, r->request_time
2295                                      + apr_time_from_sec((60 * atol(expires))));
2296                 exp_time = apr_psprintf(r->pool, "%s, %.2d-%s-%.4d "
2297                                                  "%.2d:%.2d:%.2d GMT",
2298                                         apr_day_snames[tms.tm_wday],
2299                                         tms.tm_mday,
2300                                         apr_month_snames[tms.tm_mon],
2301                                         tms.tm_year+1900,
2302                                         tms.tm_hour, tms.tm_min, tms.tm_sec);
2303             }
2304
2305             cookie = apr_pstrcat(rmain->pool,
2306                                  var, "=", val,
2307                                  "; path=", path ? path : "/",
2308                                  "; domain=", domain,
2309                                  expires ? "; expires=" : NULL,
2310                                  expires ? exp_time : NULL,
2311                                  NULL);
2312
2313             apr_table_addn(rmain->err_headers_out, "Set-Cookie", cookie);
2314             apr_pool_userdata_set("set", notename, NULL, rmain->pool);
2315             rewritelog((rmain, 5, NULL, "setting cookie '%s'", cookie));
2316         }
2317         else {
2318             rewritelog((rmain, 5, NULL, "skipping already set cookie '%s'",
2319                         var));
2320         }
2321     }
2322
2323     return;
2324 }
2325
2326 static void do_expand_cookie(data_item *cookie, rewrite_ctx *ctx)
2327 {
2328     while (cookie) {
2329         add_cookie(ctx->r, do_expand(cookie->data, ctx));
2330         cookie = cookie->next;
2331     }
2332
2333     return;
2334 }
2335
2336 #if APR_HAS_USER
2337 /*
2338  * Expand tilde-paths (/~user) through Unix /etc/passwd
2339  * database information (or other OS-specific database)
2340  */
2341 static char *expand_tildepaths(request_rec *r, char *uri)
2342 {
2343     if (uri && *uri == '/' && uri[1] == '~') {
2344         char *p, *user;
2345
2346         p = user = uri + 2;
2347         while (*p && *p != '/') {
2348             ++p;
2349         }
2350
2351         if (p > user) {
2352             char *homedir;
2353
2354             user = apr_pstrmemdup(r->pool, user, p-user);
2355             if (apr_uid_homepath_get(&homedir, user, r->pool) == APR_SUCCESS) {
2356                 if (*p) {
2357                     /* reuse of user variable */
2358                     user = homedir + strlen(homedir) - 1;
2359                     if (user >= homedir && *user == '/') {
2360                         *user = '\0';
2361                     }
2362
2363                     return apr_pstrcat(r->pool, homedir, p, NULL);
2364                 }
2365                 else {
2366                     return homedir;
2367                 }
2368             }
2369         }
2370     }
2371
2372     return uri;
2373 }
2374 #endif  /* if APR_HAS_USER */
2375
2376
2377 /*
2378  * +-------------------------------------------------------+
2379  * |                                                       |
2380  * |              rewriting lockfile support
2381  * |                                                       |
2382  * +-------------------------------------------------------+
2383  */
2384
2385 static apr_status_t rewritelock_create(server_rec *s, apr_pool_t *p)
2386 {
2387     apr_status_t rc;
2388
2389     /* only operate if a lockfile is used */
2390     if (lockname == NULL || *(lockname) == '\0') {
2391         return APR_SUCCESS;
2392     }
2393
2394     /* create the lockfile */
2395     rc = apr_global_mutex_create(&rewrite_mapr_lock_acquire, lockname,
2396                                  APR_LOCK_DEFAULT, p);
2397     if (rc != APR_SUCCESS) {
2398         ap_log_error(APLOG_MARK, APLOG_CRIT, rc, s,
2399                      "mod_rewrite: Parent could not create RewriteLock "
2400                      "file %s", lockname);
2401         return rc;
2402     }
2403
2404 #ifdef MOD_REWRITE_SET_MUTEX_PERMS
2405     rc = unixd_set_global_mutex_perms(rewrite_mapr_lock_acquire);
2406     if (rc != APR_SUCCESS) {
2407         ap_log_error(APLOG_MARK, APLOG_CRIT, rc, s,
2408                      "mod_rewrite: Parent could not set permissions "
2409                      "on RewriteLock; check User and Group directives");
2410         return rc;
2411     }
2412 #endif
2413
2414     return APR_SUCCESS;
2415 }
2416
2417 static apr_status_t rewritelock_remove(void *data)
2418 {
2419     /* only operate if a lockfile is used */
2420     if (lockname == NULL || *(lockname) == '\0') {
2421         return APR_SUCCESS;
2422     }
2423
2424     /* destroy the rewritelock */
2425     apr_global_mutex_destroy (rewrite_mapr_lock_acquire);
2426     rewrite_mapr_lock_acquire = NULL;
2427     lockname = NULL;
2428     return(0);
2429 }
2430
2431
2432 /*
2433  * +-------------------------------------------------------+
2434  * |                                                       |
2435  * |           configuration directive handling
2436  * |                                                       |
2437  * +-------------------------------------------------------+
2438  */
2439
2440 /*
2441  * own command line parser for RewriteRule and RewriteCond,
2442  * which doesn't have the '\\' problem.
2443  * (returns true on error)
2444  *
2445  * XXX: what an inclined parser. Seems we have to leave it so
2446  *      for backwards compat. *sigh*
2447  */
2448 static int parseargline(char *str, char **a1, char **a2, char **a3)
2449 {
2450     char quote;
2451
2452     while (apr_isspace(*str)) {
2453         ++str;
2454     }
2455
2456     /*
2457      * determine first argument
2458      */
2459     quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
2460     *a1 = str;
2461
2462     for (; *str; ++str) {
2463         if ((apr_isspace(*str) && !quote) || (*str == quote)) {
2464             break;
2465         }
2466         if (*str == '\\' && apr_isspace(str[1])) {
2467             ++str;
2468             continue;
2469         }
2470     }
2471
2472     if (!*str) {
2473         return 1;
2474     }
2475     *str++ = '\0';
2476
2477     while (apr_isspace(*str)) {
2478         ++str;
2479     }
2480
2481     /*
2482      * determine second argument
2483      */
2484     quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
2485     *a2 = str;
2486
2487     for (; *str; ++str) {
2488         if ((apr_isspace(*str) && !quote) || (*str == quote)) {
2489             break;
2490         }
2491         if (*str == '\\' && apr_isspace(str[1])) {
2492             ++str;
2493             continue;
2494         }
2495     }
2496
2497     if (!*str) {
2498         *a3 = NULL; /* 3rd argument is optional */
2499         return 0;
2500     }
2501     *str++ = '\0';
2502
2503     while (apr_isspace(*str)) {
2504         ++str;
2505     }
2506
2507     if (!*str) {
2508         *a3 = NULL; /* 3rd argument is still optional */
2509         return 0;
2510     }
2511
2512     /*
2513      * determine third argument
2514      */
2515     quote = (*str == '"' || *str == '\'') ? *str++ : '\0';
2516     *a3 = str;
2517     for (; *str; ++str) {
2518         if ((apr_isspace(*str) && !quote) || (*str == quote)) {
2519             break;
2520         }
2521         if (*str == '\\' && apr_isspace(str[1])) {
2522             ++str;
2523             continue;
2524         }
2525     }
2526     *str = '\0';
2527
2528     return 0;
2529 }
2530
2531 static void *config_server_create(apr_pool_t *p, server_rec *s)
2532 {
2533     rewrite_server_conf *a;
2534
2535     a = (rewrite_server_conf *)apr_pcalloc(p, sizeof(rewrite_server_conf));
2536
2537     a->state           = ENGINE_DISABLED;
2538     a->options         = OPTION_NONE;
2539 #ifndef REWRITELOG_DISABLED
2540     a->rewritelogfile  = NULL;
2541     a->rewritelogfp    = NULL;
2542     a->rewriteloglevel = 0;
2543 #endif
2544     a->rewritemaps     = apr_hash_make(p);
2545     a->rewriteconds    = apr_array_make(p, 2, sizeof(rewritecond_entry));
2546     a->rewriterules    = apr_array_make(p, 2, sizeof(rewriterule_entry));
2547     a->server          = s;
2548     a->redirect_limit  = 0; /* unset (use default) */
2549
2550     return (void *)a;
2551 }
2552
2553 static void *config_server_merge(apr_pool_t *p, void *basev, void *overridesv)
2554 {
2555     rewrite_server_conf *a, *base, *overrides;
2556
2557     a         = (rewrite_server_conf *)apr_pcalloc(p,
2558                                                    sizeof(rewrite_server_conf));
2559     base      = (rewrite_server_conf *)basev;
2560     overrides = (rewrite_server_conf *)overridesv;
2561
2562     a->state   = overrides->state;
2563     a->options = overrides->options;
2564     a->server  = overrides->server;
2565     a->redirect_limit = overrides->redirect_limit
2566                           ? overrides->redirect_limit
2567                           : base->redirect_limit;
2568
2569     if (a->options & OPTION_INHERIT) {
2570         /*
2571          *  local directives override
2572          *  and anything else is inherited
2573          */
2574 #ifndef REWRITELOG_DISABLED
2575         a->rewriteloglevel = overrides->rewriteloglevel != 0
2576                              ? overrides->rewriteloglevel
2577                              : base->rewriteloglevel;
2578         a->rewritelogfile  = overrides->rewritelogfile != NULL
2579                              ? overrides->rewritelogfile
2580                              : base->rewritelogfile;
2581         a->rewritelogfp    = overrides->rewritelogfp != NULL
2582                              ? overrides->rewritelogfp
2583                              : base->rewritelogfp;
2584 #endif
2585         a->rewritemaps     = apr_hash_overlay(p, overrides->rewritemaps,
2586                                               base->rewritemaps);
2587         a->rewriteconds    = apr_array_append(p, overrides->rewriteconds,
2588                                               base->rewriteconds);
2589         a->rewriterules    = apr_array_append(p, overrides->rewriterules,
2590                                               base->rewriterules);
2591     }
2592     else {
2593         /*
2594          *  local directives override
2595          *  and anything else gets defaults
2596          */
2597 #ifndef REWRITELOG_DISABLED
2598         a->rewriteloglevel = overrides->rewriteloglevel;
2599         a->rewritelogfile  = overrides->rewritelogfile;
2600         a->rewritelogfp    = overrides->rewritelogfp;
2601 #endif
2602         a->rewritemaps     = overrides->rewritemaps;
2603         a->rewriteconds    = overrides->rewriteconds;
2604         a->rewriterules    = overrides->rewriterules;
2605     }
2606
2607     return (void *)a;
2608 }
2609
2610 static void *config_perdir_create(apr_pool_t *p, char *path)
2611 {
2612     rewrite_perdir_conf *a;
2613
2614     a = (rewrite_perdir_conf *)apr_pcalloc(p, sizeof(rewrite_perdir_conf));
2615
2616     a->state           = ENGINE_DISABLED;
2617     a->options         = OPTION_NONE;
2618     a->baseurl         = NULL;
2619     a->rewriteconds    = apr_array_make(p, 2, sizeof(rewritecond_entry));
2620     a->rewriterules    = apr_array_make(p, 2, sizeof(rewriterule_entry));
2621     a->redirect_limit  = 0; /* unset (use server config) */
2622
2623     if (path == NULL) {
2624         a->directory = NULL;
2625     }
2626     else {
2627         /* make sure it has a trailing slash */
2628         if (path[strlen(path)-1] == '/') {
2629             a->directory = apr_pstrdup(p, path);
2630         }
2631         else {
2632             a->directory = apr_pstrcat(p, path, "/", NULL);
2633         }
2634     }
2635
2636     return (void *)a;
2637 }
2638
2639 static void *config_perdir_merge(apr_pool_t *p, void *basev, void *overridesv)
2640 {
2641     rewrite_perdir_conf *a, *base, *overrides;
2642
2643     a         = (rewrite_perdir_conf *)apr_pcalloc(p,
2644                                                   sizeof(rewrite_perdir_conf));
2645     base      = (rewrite_perdir_conf *)basev;
2646     overrides = (rewrite_perdir_conf *)overridesv;
2647
2648     a->state     = overrides->state;
2649     a->options   = overrides->options;
2650     a->directory = overrides->directory;
2651     a->baseurl   = overrides->baseurl;
2652     a->redirect_limit = overrides->redirect_limit
2653                           ? overrides->redirect_limit
2654                           : base->redirect_limit;
2655
2656     if (a->options & OPTION_INHERIT) {
2657         a->rewriteconds = apr_array_append(p, overrides->rewriteconds,
2658                                            base->rewriteconds);
2659         a->rewriterules = apr_array_append(p, overrides->rewriterules,
2660                                            base->rewriterules);
2661     }
2662     else {
2663         a->rewriteconds = overrides->rewriteconds;
2664         a->rewriterules = overrides->rewriterules;
2665     }
2666
2667     return (void *)a;
2668 }
2669
2670 static const char *cmd_rewriteengine(cmd_parms *cmd,
2671                                      void *in_dconf, int flag)
2672 {
2673     rewrite_perdir_conf *dconf = in_dconf;
2674     rewrite_server_conf *sconf;
2675
2676     sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
2677
2678     if (cmd->path == NULL) { /* is server command */
2679         sconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
2680     }
2681     else                   /* is per-directory command */ {
2682         dconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
2683     }
2684
2685     return NULL;
2686 }
2687
2688 static const char *cmd_rewriteoptions(cmd_parms *cmd,
2689                                       void *in_dconf, const char *option)
2690 {
2691     int options = 0, limit = 0;
2692     char *w;
2693
2694     while (*option) {
2695         w = ap_getword_conf(cmd->pool, &option);
2696
2697         if (!strcasecmp(w, "inherit")) {
2698             options |= OPTION_INHERIT;
2699         }
2700         else if (!strncasecmp(w, "MaxRedirects=", 13)) {
2701             limit = atoi(&w[13]);
2702             if (limit <= 0) {
2703                 return "RewriteOptions: MaxRedirects takes a number greater "
2704                        "than zero.";
2705             }
2706         }
2707         else if (!strcasecmp(w, "MaxRedirects")) { /* be nice */
2708             return "RewriteOptions: MaxRedirects has the format MaxRedirects"
2709                    "=n.";
2710         }
2711         else {
2712             return apr_pstrcat(cmd->pool, "RewriteOptions: unknown option '",
2713                                w, "'", NULL);
2714         }
2715     }
2716
2717     /* put it into the appropriate config */
2718     if (cmd->path == NULL) { /* is server command */
2719         rewrite_server_conf *conf =
2720             ap_get_module_config(cmd->server->module_config,
2721                                  &rewrite_module);
2722
2723         conf->options |= options;
2724         conf->redirect_limit = limit;
2725     }
2726     else {                  /* is per-directory command */
2727         rewrite_perdir_conf *conf = in_dconf;
2728
2729         conf->options |= options;
2730         conf->redirect_limit = limit;
2731     }
2732
2733     return NULL;
2734 }
2735
2736 #ifndef REWRITELOG_DISABLED
2737 static const char *cmd_rewritelog(cmd_parms *cmd, void *dconf, const char *a1)
2738 {
2739     rewrite_server_conf *sconf;
2740
2741     sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
2742     sconf->rewritelogfile = a1;
2743
2744     return NULL;
2745 }
2746
2747 static const char *cmd_rewriteloglevel(cmd_parms *cmd, void *dconf,
2748                                        const char *a1)
2749 {
2750     rewrite_server_conf *sconf;
2751
2752     sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
2753     sconf->rewriteloglevel = atoi(a1);
2754
2755     return NULL;
2756 }
2757 #endif /* rewritelog */
2758
2759 static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, const char *a1,
2760                                   const char *a2)
2761 {
2762     rewrite_server_conf *sconf;
2763     rewritemap_entry *newmap;
2764     apr_finfo_t st;
2765     const char *fname;
2766
2767     sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
2768
2769     newmap = apr_palloc(cmd->pool, sizeof(rewritemap_entry));
2770     newmap->func = NULL;
2771
2772     if (strncasecmp(a2, "txt:", 4) == 0) {
2773         if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) {
2774             return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ",
2775                                a2+4, NULL);
2776         }
2777
2778         newmap->type      = MAPTYPE_TXT;
2779         newmap->datafile  = fname;
2780         newmap->checkfile = fname;
2781     }
2782     else if (strncasecmp(a2, "rnd:", 4) == 0) {
2783         if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) {
2784             return apr_pstrcat(cmd->pool, "RewriteMap: bad path to rnd map: ",
2785                                a2+4, NULL);
2786         }
2787
2788         newmap->type      = MAPTYPE_RND;
2789         newmap->datafile  = fname;
2790         newmap->checkfile = fname;
2791     }
2792     else if (strncasecmp(a2, "dbm", 3) == 0) {
2793         const char *ignored_fname;
2794         apr_status_t rv;
2795
2796         newmap->type = MAPTYPE_DBM;
2797         fname = NULL;
2798
2799         if (a2[3] == ':') {
2800             newmap->dbmtype = "default";
2801             fname = a2+4;
2802         }
2803         else if (a2[3] == '=') {
2804             const char *colon = ap_strchr_c(a2 + 4, ':');
2805
2806             if (colon) {
2807                 newmap->dbmtype = apr_pstrndup(cmd->pool, a2 + 4,
2808                                                colon - (a2 + 3) - 1);
2809                 fname = colon + 1;
2810             }
2811         }
2812
2813         if (!fname) {
2814             return apr_pstrcat(cmd->pool, "RewriteMap: bad map:",
2815                                a2, NULL);
2816         }
2817
2818         if ((newmap->datafile = ap_server_root_relative(cmd->pool,
2819                                                         fname)) == NULL) {
2820             return apr_pstrcat(cmd->pool, "RewriteMap: bad path to dbm map: ",
2821                                fname, NULL);
2822         }
2823
2824         rv = apr_dbm_get_usednames_ex(cmd->pool, newmap->dbmtype,
2825                                       newmap->datafile, &newmap->checkfile,
2826                                       &ignored_fname);
2827         if (rv != APR_SUCCESS) {
2828             return apr_pstrcat(cmd->pool, "RewriteMap: dbm type ",
2829                                newmap->dbmtype, " is invalid", NULL);
2830         }
2831     }
2832     else if (strncasecmp(a2, "prg:", 4) == 0) {
2833         apr_tokenize_to_argv(a2 + 4, &newmap->argv, cmd->pool);
2834
2835         fname = newmap->argv[0];
2836         if ((newmap->argv[0] = ap_server_root_relative(cmd->pool,
2837                                                        fname)) == NULL) {
2838             return apr_pstrcat(cmd->pool, "RewriteMap: bad path to prg map: ",
2839                                fname, NULL);
2840         }
2841
2842         newmap->type      = MAPTYPE_PRG;
2843         newmap->datafile  = NULL;
2844         newmap->checkfile = newmap->argv[0];
2845     }
2846     else if (strncasecmp(a2, "int:", 4) == 0) {
2847         newmap->type      = MAPTYPE_INT;
2848         newmap->datafile  = NULL;
2849         newmap->checkfile = NULL;
2850         newmap->func      = (char *(*)(request_rec *,char *))
2851                             apr_hash_get(mapfunc_hash, a2+4, strlen(a2+4));
2852         if ((sconf->state == ENGINE_ENABLED) && (newmap->func == NULL)) {
2853             return apr_pstrcat(cmd->pool, "RewriteMap: internal map not found:",
2854                                a2+4, NULL);
2855         }
2856     }
2857     else {
2858         if ((fname = ap_server_root_relative(cmd->pool, a2)) == NULL) {
2859             return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ",
2860                                a2, NULL);
2861         }
2862
2863         newmap->type      = MAPTYPE_TXT;
2864         newmap->datafile  = fname;
2865         newmap->checkfile = fname;
2866     }
2867     newmap->fpin  = NULL;
2868     newmap->fpout = NULL;
2869
2870     if (newmap->checkfile && (sconf->state == ENGINE_ENABLED)
2871         && (apr_stat(&st, newmap->checkfile, APR_FINFO_MIN,
2872                      cmd->pool) != APR_SUCCESS)) {
2873         return apr_pstrcat(cmd->pool,
2874                            "RewriteMap: file for map ", a1,
2875                            " not found:", newmap->checkfile, NULL);
2876     }
2877
2878     apr_hash_set(sconf->rewritemaps, a1, APR_HASH_KEY_STRING, newmap);
2879
2880     return NULL;
2881 }
2882
2883 static const char *cmd_rewritelock(cmd_parms *cmd, void *dconf, const char *a1)
2884 {
2885     const char *error;
2886
2887     if ((error = ap_check_cmd_context(cmd, GLOBAL_ONLY)) != NULL)
2888         return error;
2889
2890     /* fixup the path, especially for rewritelock_remove() */
2891     lockname = ap_server_root_relative(cmd->pool, a1);
2892
2893     if (!lockname) {
2894         return apr_pstrcat(cmd->pool, "Invalid RewriteLock path ", a1);
2895     }
2896
2897     return NULL;
2898 }
2899
2900 static const char *cmd_rewritebase(cmd_parms *cmd, void *in_dconf,
2901                                    const char *a1)
2902 {
2903     rewrite_perdir_conf *dconf = in_dconf;
2904
2905     if (cmd->path == NULL || dconf == NULL) {
2906         return "RewriteBase: only valid in per-directory config files";
2907     }
2908     if (a1[0] == '\0') {
2909         return "RewriteBase: empty URL not allowed";
2910     }
2911     if (a1[0] != '/') {
2912         return "RewriteBase: argument is not a valid URL";
2913     }
2914
2915     dconf->baseurl = a1;
2916
2917     return NULL;
2918 }
2919
2920 /*
2921  * generic lexer for RewriteRule and RewriteCond flags.
2922  * The parser will be passed in as a function pointer
2923  * and called if a flag was found
2924  */
2925 static const char *cmd_parseflagfield(apr_pool_t *p, void *cfg, char *key,
2926                                       const char *(*parse)(apr_pool_t *,
2927                                                            void *,
2928                                                            char *, char *))
2929 {
2930     char *val, *nextp, *endp;
2931     const char *err;
2932
2933     endp = key + strlen(key) - 1;
2934     if (*key != '[' || *endp != ']') {
2935         return "RewriteCond: bad flag delimiters";
2936     }
2937
2938     *endp = ','; /* for simpler parsing */
2939     ++key;
2940
2941     while (*key) {
2942         /* skip leading spaces */
2943         while (apr_isspace(*key)) {
2944             ++key;
2945         }
2946
2947         if (!*key || (nextp = ap_strchr(key, ',')) == NULL) { /* NULL should not
2948                                                                * happen, but ...
2949                                                                */
2950             break;
2951         }
2952
2953         /* strip trailing spaces */
2954         endp = nextp - 1;
2955         while (apr_isspace(*endp)) {
2956             --endp;
2957         }
2958         *++endp = '\0';
2959
2960         /* split key and val */
2961         val = ap_strchr(key, '=');
2962         if (val) {
2963             *val++ = '\0';
2964         }
2965         else {
2966             val = endp;
2967         }
2968
2969         err = parse(p, cfg, key, val);
2970         if (err) {
2971             return err;
2972         }
2973
2974         key = nextp + 1;
2975     }
2976
2977     return NULL;
2978 }
2979
2980 static const char *cmd_rewritecond_setflag(apr_pool_t *p, void *_cfg,
2981                                            char *key, char *val)
2982 {
2983     rewritecond_entry *cfg = _cfg;
2984
2985     if (   strcasecmp(key, "nocase") == 0
2986         || strcasecmp(key, "NC") == 0    ) {
2987         cfg->flags |= CONDFLAG_NOCASE;
2988     }
2989     else if (   strcasecmp(key, "ornext") == 0
2990              || strcasecmp(key, "OR") == 0    ) {
2991         cfg->flags |= CONDFLAG_ORNEXT;
2992     }
2993     else {
2994         return apr_pstrcat(p, "RewriteCond: unknown flag '", key, "'", NULL);
2995     }
2996     return NULL;
2997 }
2998
2999 static const char *cmd_rewritecond(cmd_parms *cmd, void *in_dconf,
3000                                    const char *in_str)
3001 {
3002     rewrite_perdir_conf *dconf = in_dconf;
3003     char *str = apr_pstrdup(cmd->pool, in_str);
3004     rewrite_server_conf *sconf;
3005     rewritecond_entry *newcond;
3006     regex_t *regexp;
3007     char *a1;
3008     char *a2;
3009     char *a3;
3010     const char *err;
3011
3012     sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
3013
3014     /*  make a new entry in the internal temporary rewrite rule list */
3015     if (cmd->path == NULL) {   /* is server command */
3016         newcond = apr_array_push(sconf->rewriteconds);
3017     }
3018     else {                     /* is per-directory command */
3019         newcond = apr_array_push(dconf->rewriteconds);
3020     }
3021
3022     /* parse the argument line ourself
3023      * a1 .. a3 are substrings of str, which is a fresh copy
3024      * of the argument line. So we can use a1 .. a3 without
3025      * copying them again.
3026      */
3027     if (parseargline(str, &a1, &a2, &a3)) {
3028         return apr_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str,
3029                            "'", NULL);
3030     }
3031
3032     /* arg1: the input string */
3033     newcond->input = a1;
3034
3035     /* arg3: optional flags field
3036      * (this has to be parsed first, because we need to
3037      *  know if the regex should be compiled with ICASE!)
3038      */
3039     newcond->flags = CONDFLAG_NONE;
3040     if (a3 != NULL) {
3041         if ((err = cmd_parseflagfield(cmd->pool, newcond, a3,
3042                                       cmd_rewritecond_setflag)) != NULL) {
3043             return err;
3044         }
3045     }
3046
3047     /* arg2: the pattern */
3048     if (*a2 == '!') {
3049         newcond->flags |= CONDFLAG_NOTMATCH;
3050         ++a2;
3051     }
3052
3053     /* determine the pattern type */
3054     newcond->ptype = 0;
3055     if (*a2 && a2[1]) {
3056         if (!a2[2] && *a2 == '-') {
3057             switch (a2[1]) {
3058             case 'f': newcond->ptype = CONDPAT_FILE_EXISTS; break;
3059             case 's': newcond->ptype = CONDPAT_FILE_SIZE;   break;
3060             case 'l': newcond->ptype = CONDPAT_FILE_LINK;   break;
3061             case 'd': newcond->ptype = CONDPAT_FILE_DIR;    break;
3062             case 'x': newcond->ptype = CONDPAT_FILE_XBIT;   break;
3063             case 'U': newcond->ptype = CONDPAT_LU_URL;      break;
3064             case 'F': newcond->ptype = CONDPAT_LU_FILE;     break;
3065             }
3066         }
3067         else {
3068             switch (*a2) {
3069             case '>': newcond->ptype = CONDPAT_STR_GT; break;
3070             case '<': newcond->ptype = CONDPAT_STR_LT; break;
3071             case '=': newcond->ptype = CONDPAT_STR_EQ;
3072                 /* "" represents an empty string */
3073                 if (*++a2 == '"' && a2[1] == '"' && !a2[2]) {
3074                     a2 += 2;
3075                 }
3076                 break;
3077             }
3078         }
3079     }
3080
3081     if (newcond->ptype && newcond->ptype != CONDPAT_STR_EQ &&
3082         (newcond->flags & CONDFLAG_NOCASE)) {
3083         ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server,
3084                      "RewriteCond: NoCase option for non-regex pattern '%s' "
3085                      "is not supported and will be ignored.", a2);
3086         newcond->flags &= ~CONDFLAG_NOCASE;
3087     }
3088
3089     newcond->pattern = a2;
3090
3091     if (!newcond->ptype) {
3092         regexp = ap_pregcomp(cmd->pool, a2,
3093                              REG_EXTENDED | ((newcond->flags & CONDFLAG_NOCASE)
3094                                              ? REG_ICASE : 0));
3095         if (!regexp) {
3096             return apr_pstrcat(cmd->pool, "RewriteCond: cannot compile regular "
3097                                "expression '", a2, "'", NULL);
3098         }
3099
3100         newcond->regexp  = regexp;
3101     }
3102
3103     return NULL;
3104 }
3105
3106 static const char *cmd_rewriterule_setflag(apr_pool_t *p, void *_cfg,
3107                                            char *key, char *val)
3108 {
3109     rewriterule_entry *cfg = _cfg;
3110     int error = 0;
3111
3112     switch (*key++) {
3113     case 'c':
3114     case 'C':
3115         if (!*key || !strcasecmp(key, "hain")) {           /* chain */
3116             cfg->flags |= RULEFLAG_CHAIN;
3117         }
3118         else if (((*key == 'O' || *key == 'o') && !key[1])
3119                  || !strcasecmp(key, "ookie")) {           /* cookie */
3120             data_item *cp = cfg->cookie;
3121
3122             if (!cp) {
3123                 cp = cfg->cookie = apr_palloc(p, sizeof(*cp));
3124             }
3125             else {
3126                 while (cp->next) {
3127                     cp = cp->next;
3128                 }
3129                 cp->next = apr_palloc(p, sizeof(*cp));
3130                 cp = cp->next;
3131             }
3132
3133             cp->next = NULL;
3134             cp->data = val;
3135         }
3136         else {
3137             ++error;
3138         }
3139         break;
3140
3141     case 'e':
3142     case 'E':
3143         if (!*key || !strcasecmp(key, "nv")) {             /* env */
3144             data_item *cp = cfg->env;
3145
3146             if (!cp) {
3147                 cp = cfg->env = apr_palloc(p, sizeof(*cp));
3148             }
3149             else {
3150                 while (cp->next) {
3151                     cp = cp->next;
3152                 }
3153                 cp->next = apr_palloc(p, sizeof(*cp));
3154                 cp = cp->next;
3155             }
3156
3157             cp->next = NULL;
3158             cp->data = val;
3159         }
3160         else {
3161             ++error;
3162         }
3163         break;
3164
3165     case 'f':
3166     case 'F':
3167         if (!*key || !strcasecmp(key, "orbidden")) {       /* forbidden */
3168             cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB);
3169             cfg->forced_responsecode = HTTP_FORBIDDEN;
3170         }
3171         else {
3172             ++error;
3173         }
3174         break;
3175
3176     case 'g':
3177     case 'G':
3178         if (!*key || !strcasecmp(key, "one")) {            /* gone */
3179             cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB);
3180             cfg->forced_responsecode = HTTP_GONE;
3181         }
3182         else {
3183             ++error;
3184         }
3185         break;
3186
3187     case 'h':
3188     case 'H':
3189         if (!*key || !strcasecmp(key, "andler")) {         /* handler */
3190             cfg->forced_handler = val;
3191         }
3192         else {
3193             ++error;
3194         }
3195         break;
3196
3197     case 'l':
3198     case 'L':
3199         if (!*key || !strcasecmp(key, "ast")) {            /* last */
3200             cfg->flags |= RULEFLAG_LASTRULE;
3201         }
3202         else {
3203             ++error;
3204         }
3205         break;
3206
3207     case 'n':
3208     case 'N':
3209         if (((*key == 'E' || *key == 'e') && !key[1])
3210             || !strcasecmp(key, "oescape")) {              /* noescape */
3211             cfg->flags |= RULEFLAG_NOESCAPE;
3212         }
3213         else if (!*key || !strcasecmp(key, "ext")) {       /* next */
3214             cfg->flags |= RULEFLAG_NEWROUND;
3215         }
3216         else if (((*key == 'S' || *key == 's') && !key[1])
3217             || !strcasecmp(key, "osubreq")) {              /* nosubreq */
3218             cfg->flags |= RULEFLAG_IGNOREONSUBREQ;
3219         }
3220         else if (((*key == 'C' || *key == 'c') && !key[1])
3221             || !strcasecmp(key, "ocase")) {                /* nocase */
3222             cfg->flags |= RULEFLAG_NOCASE;
3223         }
3224         else {
3225             ++error;
3226         }
3227         break;
3228
3229     case 'p':
3230     case 'P':
3231         if (!*key || !strcasecmp(key, "roxy")) {           /* proxy */
3232             cfg->flags |= RULEFLAG_PROXY;
3233         }
3234         else if (((*key == 'T' || *key == 't') && !key[1])
3235             || !strcasecmp(key, "assthrough")) {           /* passthrough */
3236             cfg->flags |= RULEFLAG_PASSTHROUGH;
3237         }
3238         else {
3239             ++error;
3240         }
3241         break;
3242
3243     case 'q':
3244     case 'Q':
3245         if (   !strcasecmp(key, "QSA")
3246             || !strcasecmp(key, "qsappend")) {             /* qsappend */
3247             cfg->flags |= RULEFLAG_QSAPPEND;
3248         }
3249         else {
3250             ++error;
3251         }
3252         break;
3253
3254     case 'r':
3255     case 'R':
3256         if (!*key || !strcasecmp(key, "edirect")) {        /* redirect */
3257             int status = 0;
3258
3259             cfg->flags |= RULEFLAG_FORCEREDIRECT;
3260             if (strlen(val) > 0) {
3261                 if (strcasecmp(val, "permanent") == 0) {
3262                     status = HTTP_MOVED_PERMANENTLY;
3263                 }
3264                 else if (strcasecmp(val, "temp") == 0) {
3265                     status = HTTP_MOVED_TEMPORARILY;
3266                 }
3267                 else if (strcasecmp(val, "seeother") == 0) {
3268                     status = HTTP_SEE_OTHER;
3269                 }
3270                 else if (apr_isdigit(*val)) {
3271                     status = atoi(val);
3272                     if (status != HTTP_INTERNAL_SERVER_ERROR) {
3273                         int idx =
3274                             ap_index_of_response(HTTP_INTERNAL_SERVER_ERROR);
3275
3276                         if (ap_index_of_response(status) == idx) {
3277                             return apr_psprintf(p, "RewriteRule: invalid HTTP "
3278                                                    "response code '%s' for "
3279                                                    "flag 'R'",
3280                                                 val);
3281                         }
3282                     }
3283                     if (!ap_is_HTTP_REDIRECT(status)) {
3284                         cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB);
3285                     }
3286                 }
3287                 cfg->forced_responsecode = status;
3288             }
3289         }
3290         else {
3291             ++error;
3292         }
3293         break;
3294
3295     case 's':
3296     case 'S':
3297         if (!*key || !strcasecmp(key, "kip")) {            /* skip */
3298             cfg->skip = atoi(val);
3299         }
3300         else {
3301             ++error;
3302         }
3303         break;
3304
3305     case 't':
3306     case 'T':
3307         if (!*key || !strcasecmp(key, "ype")) {            /* type */
3308             cfg->forced_mimetype = val;
3309         }
3310         else {
3311             ++error;
3312         }
3313         break;
3314
3315     default:
3316         ++error;
3317         break;
3318     }
3319
3320     if (error) {
3321         return apr_pstrcat(p, "RewriteRule: unknown flag '", --key, "'", NULL);
3322     }
3323
3324     return NULL;
3325 }
3326
3327 static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf,
3328                                    const char *in_str)
3329 {
3330     rewrite_perdir_conf *dconf = in_dconf;
3331     char *str = apr_pstrdup(cmd->pool, in_str);
3332     rewrite_server_conf *sconf;
3333     rewriterule_entry *newrule;
3334     regex_t *regexp;
3335     char *a1;
3336     char *a2;
3337     char *a3;
3338     const char *err;
3339
3340     sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module);
3341
3342     /*  make a new entry in the internal rewrite rule list */
3343     if (cmd->path == NULL) {   /* is server command */
3344         newrule = apr_array_push(sconf->rewriterules);
3345     }
3346     else {                     /* is per-directory command */
3347         newrule = apr_array_push(dconf->rewriterules);
3348     }
3349
3350     /*  parse the argument line ourself */
3351     if (parseargline(str, &a1, &a2, &a3)) {
3352         return apr_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str,
3353                            "'", NULL);
3354     }
3355
3356     /* arg3: optional flags field */
3357     newrule->forced_mimetype     = NULL;
3358     newrule->forced_handler      = NULL;
3359     newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY;
3360     newrule->flags  = RULEFLAG_NONE;
3361     newrule->env = NULL;
3362     newrule->cookie = NULL;
3363     newrule->skip   = 0;
3364     if (a3 != NULL) {
3365         if ((err = cmd_parseflagfield(cmd->pool, newrule, a3,
3366                                       cmd_rewriterule_setflag)) != NULL) {
3367             return err;
3368         }
3369     }
3370
3371     /* arg1: the pattern
3372      * try to compile the regexp to test if is ok
3373      */
3374     if (*a1 == '!') {
3375         newrule->flags |= RULEFLAG_NOTMATCH;
3376         ++a1;
3377     }
3378
3379     regexp = ap_pregcomp(cmd->pool, a1, REG_EXTENDED |
3380                                         ((newrule->flags & RULEFLAG_NOCASE)
3381                                          ? REG_ICASE : 0));
3382     if (!regexp) {
3383         return apr_pstrcat(cmd->pool,
3384                            "RewriteRule: cannot compile regular expression '",
3385                            a1, "'", NULL);
3386     }
3387
3388     newrule->pattern = a1;
3389     newrule->regexp  = regexp;
3390
3391     /* arg2: the output string */
3392     newrule->output = a2;
3393     if (*a2 == '-' && !a2[1]) {
3394         newrule->flags |= RULEFLAG_NOSUB;
3395     }
3396
3397     /* now, if the server or per-dir config holds an
3398      * array of RewriteCond entries, we take it for us
3399      * and clear the array
3400      */
3401     if (cmd->path == NULL) {  /* is server command */
3402         newrule->rewriteconds   = sconf->rewriteconds;
3403         sconf->rewriteconds = apr_array_make(cmd->pool, 2,
3404                                              sizeof(rewritecond_entry));
3405     }
3406     else {                    /* is per-directory command */
3407         newrule->rewriteconds   = dconf->rewriteconds;
3408         dconf->rewriteconds = apr_array_make(cmd->pool, 2,
3409                                              sizeof(rewritecond_entry));
3410     }
3411
3412     return NULL;
3413 }
3414
3415
3416 /*
3417  * +-------------------------------------------------------+
3418  * |                                                       |
3419  * |                  the rewriting engine
3420  * |                                                       |
3421  * +-------------------------------------------------------+
3422  */
3423
3424 /* Lexicographic Compare */
3425 static APR_INLINE int compare_lexicography(char *a, char *b)
3426 {
3427     apr_size_t i, lena, lenb;
3428
3429     lena = strlen(a);
3430     lenb = strlen(b);
3431
3432     if (lena == lenb) {
3433         for (i = 0; i < lena; ++i) {
3434             if (a[i] != b[i]) {
3435                 return ((unsigned char)a[i] > (unsigned char)b[i]) ? 1 : -1;
3436             }
3437         }
3438
3439         return 0;
3440     }
3441
3442     return ((lena > lenb) ? 1 : -1);
3443 }
3444
3445 /*
3446  * Apply a single rewriteCond
3447  */
3448 static int apply_rewrite_cond(rewritecond_entry *p, rewrite_ctx *ctx)
3449 {
3450     char *input = do_expand(p->input, ctx);
3451     apr_finfo_t sb;
3452     request_rec *rsub, *r = ctx->r;
3453     regmatch_t regmatch[AP_MAX_REG_MATCH];
3454     int rc = 0;
3455
3456     switch (p->ptype) {
3457     case CONDPAT_FILE_EXISTS:
3458         if (   apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS
3459             && sb.filetype == APR_REG) {
3460             rc = 1;
3461         }
3462         break;
3463
3464     case CONDPAT_FILE_SIZE:
3465         if (   apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS
3466             && sb.filetype == APR_REG && sb.size > 0) {
3467             rc = 1;
3468         }
3469         break;
3470
3471     case CONDPAT_FILE_LINK:
3472 #if !defined(OS2)
3473         if (   apr_stat(&sb, input, APR_FINFO_MIN | APR_FINFO_LINK,
3474                         r->pool) == APR_SUCCESS
3475             && sb.filetype == APR_LNK) {
3476             rc = 1;
3477         }
3478 #endif
3479         break;
3480
3481     case CONDPAT_FILE_DIR:
3482         if (   apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS
3483             && sb.filetype == APR_DIR) {
3484             rc = 1;
3485         }
3486         break;
3487
3488     case CONDPAT_FILE_XBIT:
3489         if (   apr_stat(&sb, input, APR_FINFO_PROT, r->pool) == APR_SUCCESS
3490             && (sb.protection & (APR_UEXECUTE | APR_GEXECUTE | APR_WEXECUTE))) {
3491             rc = 1;
3492         }
3493         break;
3494
3495     case CONDPAT_LU_URL:
3496         if (*input && subreq_ok(r)) {
3497             rsub = ap_sub_req_lookup_uri(input, r, NULL);
3498             if (rsub->status < 400) {
3499                 rc = 1;
3500             }
3501             rewritelog((r, 5, NULL, "RewriteCond URI (-U) check: "
3502                         "path=%s -> status=%d", input, rsub->status));
3503             ap_destroy_sub_req(rsub);
3504         }
3505         break;
3506
3507     case CONDPAT_LU_FILE:
3508         if (*input && subreq_ok(r)) {
3509             rsub = ap_sub_req_lookup_file(input, r, NULL);
3510             if (rsub->status < 300 &&
3511                 /* double-check that file exists since default result is 200 */
3512                 apr_stat(&sb, rsub->filename, APR_FINFO_MIN,
3513                          r->pool) == APR_SUCCESS) {
3514                 rc = 1;
3515             }
3516             rewritelog((r, 5, NULL, "RewriteCond file (-F) check: path=%s "
3517                         "-> file=%s status=%d", input, rsub->filename,
3518                         rsub->status));
3519             ap_destroy_sub_req(rsub);
3520         }
3521         break;
3522
3523     case CONDPAT_STR_GT:
3524         rc = (compare_lexicography(input, p->pattern+1) == 1) ? 1 : 0;
3525         break;
3526
3527     case CONDPAT_STR_LT:
3528         rc = (compare_lexicography(input, p->pattern+1) == -1) ? 1 : 0;
3529         break;
3530
3531     case CONDPAT_STR_EQ:
3532         if (p->flags & CONDFLAG_NOCASE) {
3533             rc = !strcasecmp(input, p->pattern);
3534         }
3535         else {
3536             rc = !strcmp(input, p->pattern);
3537         }
3538         break;
3539
3540     default:
3541         /* it is really a regexp pattern, so apply it */
3542         rc = !ap_regexec(p->regexp, input, AP_MAX_REG_MATCH, regmatch, 0);
3543
3544         /* update briRC backref info */
3545         if (rc && !(p->flags & CONDFLAG_NOTMATCH)) {
3546             ctx->briRC.source = input;
3547             ctx->briRC.nsub   = p->regexp->re_nsub;
3548             memcpy(ctx->briRC.regmatch, regmatch, sizeof(regmatch));
3549         }
3550         break;
3551     }
3552
3553     if (p->flags & CONDFLAG_NOTMATCH) {
3554         rc = !rc;
3555     }
3556
3557     rewritelog((r, 4, ctx->perdir, "RewriteCond: input='%s' pattern='%s%s%s'%s "
3558                 "=> %s", input, (p->flags & CONDFLAG_NOTMATCH) ? "!" : "",
3559                 (p->ptype == CONDPAT_STR_EQ) ? "=" : "", p->pattern,
3560                 (p->flags & CONDFLAG_NOCASE) ? " [NC]" : "",
3561                 rc ? "matched" : "not-matched"));
3562
3563     return rc;
3564 }
3565
3566 /* check for forced type and handler */
3567 static APR_INLINE void force_type_handler(rewriterule_entry *p,
3568                                           rewrite_ctx *ctx)
3569 {
3570     char *expanded;
3571
3572     if (p->forced_mimetype) {
3573         expanded = do_expand(p->forced_mimetype, ctx);
3574
3575         if (*expanded) {
3576             ap_str_tolower(expanded);
3577
3578             rewritelog((ctx->r, 2, ctx->perdir, "remember %s to have MIME-type "
3579                         "'%s'", ctx->r->filename, expanded));
3580
3581             apr_table_setn(ctx->r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
3582                            expanded);
3583         }
3584     }
3585
3586     if (p->forced_handler) {
3587         expanded = do_expand(p->forced_handler, ctx);
3588
3589         if (*expanded) {
3590             ap_str_tolower(expanded);
3591
3592             rewritelog((ctx->r, 2, ctx->perdir, "remember %s to have "
3593                         "Content-handler '%s'", ctx->r->filename, expanded));
3594
3595             apr_table_setn(ctx->r->notes, REWRITE_FORCED_HANDLER_NOTEVAR,
3596                            expanded);
3597         }
3598     }
3599 }
3600
3601 /*
3602  * Apply a single RewriteRule
3603  */
3604 static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx)
3605 {
3606     regmatch_t regmatch[AP_MAX_REG_MATCH];
3607     apr_array_header_t *rewriteconds;
3608     rewritecond_entry *conds;
3609     int i, rc;
3610     char *newuri = NULL;
3611     request_rec *r = ctx->r;
3612
3613     ctx->uri = r->filename;
3614
3615     if (ctx->perdir) {
3616         apr_size_t dirlen = strlen(ctx->perdir);
3617
3618         /* Since we want to match against the (so called) full URL, we have
3619          * to re-add the PATH_INFO postfix
3620          */
3621         if (r->path_info && *r->path_info) {
3622             rewritelog((r, 3, ctx->perdir, "add path info postfix: %s -> %s%s",
3623                         ctx->uri, ctx->uri, r->path_info));
3624             ctx->uri = apr_pstrcat(r->pool, ctx->uri, r->path_info, NULL);
3625         }
3626
3627         /* Additionally we strip the physical path from the url to match
3628          * it independent from the underlaying filesystem.
3629          */
3630         if (strlen(ctx->uri) >= dirlen &&
3631             !strncmp(ctx->uri, ctx->perdir, dirlen)) {
3632
3633             rewritelog((r, 3, ctx->perdir, "strip per-dir prefix: %s -> %s",
3634                         ctx->uri, ctx->uri + dirlen));
3635             ctx->uri = ctx->uri + dirlen;
3636         }
3637     }
3638
3639     /* Try to match the URI against the RewriteRule pattern
3640      * and exit immediately if it didn't apply.
3641      */
3642     rewritelog((r, 3, ctx->perdir, "applying pattern '%s' to uri '%s'",
3643                 p->pattern, ctx->uri));
3644
3645     rc = !ap_regexec(p->regexp, ctx->uri, AP_MAX_REG_MATCH, regmatch, 0);
3646     if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) ||
3647            (!rc &&  (p->flags & RULEFLAG_NOTMATCH))   ) ) {
3648         return 0;
3649     }
3650
3651     /* It matched, wow! Now it's time to prepare the context structure for
3652      * further processing
3653      */
3654     ctx->vary_this = NULL;
3655     ctx->briRC.source = NULL;
3656
3657     if (p->flags & RULEFLAG_NOTMATCH) {
3658         ctx->briRR.source = NULL;
3659     }
3660     else {
3661         ctx->briRR.source = apr_pstrdup(r->pool, ctx->uri);
3662         ctx->briRR.nsub   = p->regexp->re_nsub;
3663         memcpy(ctx->briRR.regmatch, regmatch, sizeof(regmatch));
3664     }
3665
3666     /* Ok, we already know the pattern has matched, but we now
3667      * additionally have to check for all existing preconditions
3668      * (RewriteCond) which have to be also true. We do this at
3669      * this very late stage to avoid unnessesary checks which
3670      * would slow down the rewriting engine.
3671      */
3672     rewriteconds = p->rewriteconds;
3673     conds = (rewritecond_entry *)rewriteconds->elts;
3674
3675     for (i = 0; i < rewriteconds->nelts; ++i) {
3676         rewritecond_entry *c = &conds[i];
3677
3678         rc = apply_rewrite_cond(c, ctx);
3679         if (c->flags & CONDFLAG_ORNEXT) {
3680             if (!rc) {
3681                 /* One condition is false, but another can be still true. */
3682                 ctx->vary_this = NULL;
3683                 continue;
3684             }
3685             else {
3686                 /* skip the rest of the chained OR conditions */
3687                 while (   i < rewriteconds->nelts
3688                        && c->flags & CONDFLAG_ORNEXT) {
3689                     c = &conds[++i];
3690                 }
3691                 continue;
3692             }
3693         }
3694         else if (!rc) {
3695             return 0;
3696         }
3697
3698         /* If some HTTP header was involved in the condition, remember it
3699          * for later use
3700          */
3701         if (ctx->vary_this) {
3702             ctx->vary = ctx->vary
3703                         ? apr_pstrcat(r->pool, ctx->vary, ", ", ctx->vary_this,
3704                                       NULL)
3705                         : ctx->vary_this;
3706             ctx->vary_this = NULL;
3707         }
3708     }
3709
3710     /* expand the result */
3711     if (!(p->flags & RULEFLAG_NOSUB)) {
3712         newuri = do_expand(p->output, ctx);
3713         rewritelog((r, 2, ctx->perdir, "rewrite '%s' -> '%s'", ctx->uri,
3714                     newuri));
3715     }
3716
3717     /* expand [E=var:val] and [CO=<cookie>] */
3718     do_expand_env(p->env, ctx);
3719     do_expand_cookie(p->cookie, ctx);
3720
3721     /* non-substitution rules ('RewriteRule <pat> -') end here. */
3722     if (p->flags & RULEFLAG_NOSUB) {
3723         force_type_handler(p, ctx);
3724
3725         if (p->flags & RULEFLAG_STATUS) {
3726             rewritelog((r, 2, ctx->perdir, "forcing responsecode %d for %s",
3727                         p->forced_responsecode, r->filename));
3728
3729             r->status = p->forced_responsecode;
3730         }
3731
3732         return 2;
3733     }
3734
3735     /* Now adjust API's knowledge about r->filename and r->args */
3736     r->filename = newuri;
3737     splitout_queryargs(r, p->flags & RULEFLAG_QSAPPEND);
3738
3739     /* Add the previously stripped per-directory location prefix, unless
3740      * (1) it's an absolute URL path and
3741      * (2) it's a full qualified URL
3742      */
3743     if (ctx->perdir && *r->filename != '/' && !is_absolute_uri(r->filename)) {
3744         rewritelog((r, 3, ctx->perdir, "add per-dir prefix: %s -> %s%s",
3745                     r->filename, ctx->perdir, r->filename));
3746
3747         r->filename = apr_pstrcat(r->pool, ctx->perdir, r->filename, NULL);
3748     }
3749
3750     /* If this rule is forced for proxy throughput
3751      * (`RewriteRule ... ... [P]') then emulate mod_proxy's
3752      * URL-to-filename handler to be sure mod_proxy is triggered
3753      * for this URL later in the Apache API. But make sure it is
3754      * a fully-qualified URL. (If not it is qualified with
3755      * ourself).
3756      */
3757     if (p->flags & RULEFLAG_PROXY) {
3758         fully_qualify_uri(r);
3759
3760         rewritelog((r, 2, ctx->perdir, "forcing proxy-throughput with %s",
3761                     r->filename));
3762
3763         r->filename = apr_pstrcat(r->pool, "proxy:", r->filename, NULL);
3764         return 1;
3765     }
3766
3767     /* If this rule is explicitly forced for HTTP redirection
3768      * (`RewriteRule .. .. [R]') then force an external HTTP
3769      * redirect. But make sure it is a fully-qualified URL. (If
3770      * not it is qualified with ourself).
3771      */
3772     if (p->flags & RULEFLAG_FORCEREDIRECT) {
3773         fully_qualify_uri(r);
3774
3775         rewritelog((r, 2, ctx->perdir, "explicitly forcing redirect with %s",
3776                     r->filename));
3777
3778         r->status = p->forced_responsecode;
3779         return 1;
3780     }
3781
3782     /* Special Rewriting Feature: Self-Reduction
3783      * We reduce the URL by stripping a possible
3784      * http[s]://<ourhost>[:<port>] prefix, i.e. a prefix which
3785      * corresponds to ourself. This is to simplify rewrite maps
3786      * and to avoid recursion, etc. When this prefix is not a
3787      * coincidence then the user has to use [R] explicitly (see
3788      * above).
3789      */
3790     reduce_uri(r);
3791
3792     /* If this rule is still implicitly forced for HTTP
3793      * redirection (`RewriteRule .. <scheme>://...') then
3794      * directly force an external HTTP redirect.
3795      */
3796     if (is_absolute_uri(r->filename)) {
3797         rewritelog((r, 2, ctx->perdir, "implicitly forcing redirect (rc=%d) "
3798                     "with %s", p->forced_responsecode, r->filename));
3799
3800         r->status = p->forced_responsecode;
3801         return 1;
3802     }
3803
3804     /* Finally remember the forced mime-type */
3805     force_type_handler(p, ctx);
3806
3807     /* Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_)
3808      * But now we're done for this particular rule.
3809      */
3810     return 1;
3811 }
3812
3813 /*
3814  * Apply a complete rule set,
3815  * i.e. a list of rewrite rules
3816  */
3817 static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules,
3818                               char *perdir)
3819 {
3820     rewriterule_entry *entries;
3821     rewriterule_entry *p;
3822     int i;
3823     int changed;
3824     int rc;
3825     int s;
3826     rewrite_ctx *ctx;
3827
3828     ctx = apr_palloc(r->pool, sizeof(*ctx));
3829     ctx->perdir = perdir;
3830     ctx->r = r;
3831
3832     /*
3833      *  Iterate over all existing rules
3834      */
3835     entries = (rewriterule_entry *)rewriterules->elts;
3836     changed = 0;
3837     loop:
3838     for (i = 0; i < rewriterules->nelts; i++) {
3839         p = &entries[i];
3840
3841         /*
3842          *  Ignore this rule on subrequests if we are explicitly
3843          *  asked to do so or this is a proxy-throughput or a
3844          *  forced redirect rule.
3845          */
3846         if (r->main != NULL &&
3847             (p->flags & RULEFLAG_IGNOREONSUBREQ ||
3848              p->flags & RULEFLAG_FORCEREDIRECT    )) {
3849             continue;
3850         }
3851
3852         /*
3853          *  Apply the current rule.
3854          */
3855         ctx->vary = NULL;
3856         rc = apply_rewrite_rule(p, ctx);
3857
3858         if (rc) {
3859             /* Regardless of what we do next, we've found a match. Check to see
3860              * if any of the request header fields were involved, and add them
3861              * to the Vary field of the response.
3862              */
3863             if (ctx->vary) {
3864                 apr_table_merge(r->headers_out, "Vary", ctx->vary);
3865             }
3866
3867             /*
3868              * The rule sets the response code (implies match-only)
3869              */
3870             if (p->flags & RULEFLAG_STATUS) {
3871                 return ACTION_STATUS;
3872             }
3873
3874             /*
3875              * Indicate a change if this was not a match-only rule.
3876              */
3877             if (rc != 2) {
3878                 changed = ((p->flags & RULEFLAG_NOESCAPE)
3879                            ? ACTION_NOESCAPE : ACTION_NORMAL);
3880             }
3881
3882             /*
3883              *  Pass-Through Feature (`RewriteRule .. .. [PT]'):
3884              *  Because the Apache 1.x API is very limited we
3885              *  need this hack to pass the rewritten URL to other
3886              *  modules like mod_alias, mod_userdir, etc.
3887              */
3888             if (p->flags & RULEFLAG_PASSTHROUGH) {
3889                 rewritelog((r, 2, perdir, "forcing '%s' to get passed through "
3890                            "to next API URI-to-filename handler", r->filename));
3891                 r->filename = apr_pstrcat(r->pool, "passthrough:",
3892                                          r->filename, NULL);
3893                 changed = ACTION_NORMAL;
3894                 break;
3895             }
3896
3897             /*
3898              *  Stop processing also on proxy pass-through and
3899              *  last-rule and new-round flags.
3900              */
3901             if (p->flags & (RULEFLAG_PROXY | RULEFLAG_LASTRULE)) {
3902                 break;
3903             }
3904
3905             /*
3906              *  On "new-round" flag we just start from the top of
3907              *  the rewriting ruleset again.
3908              */
3909             if (p->flags & RULEFLAG_NEWROUND) {
3910                 goto loop;
3911             }
3912
3913             /*
3914              *  If we are forced to skip N next rules, do it now.
3915              */
3916             if (p->skip > 0) {
3917                 s = p->skip;
3918                 while (   i < rewriterules->nelts
3919                        && s > 0) {
3920                     i++;
3921                     p = &entries[i];
3922                     s--;
3923                 }
3924             }
3925         }
3926         else {
3927             /*
3928              *  If current rule is chained with next rule(s),
3929              *  skip all this next rule(s)
3930              */
3931             while (   i < rewriterules->nelts
3932                    && p->flags & RULEFLAG_CHAIN) {
3933                 i++;
3934                 p = &entries[i];
3935             }
3936         }
3937     }
3938     return changed;
3939 }
3940
3941
3942 /*
3943  * +-------------------------------------------------------+
3944  * |                                                       |
3945  * |             Module Initialization Hooks
3946  * |                                                       |
3947  * +-------------------------------------------------------+
3948  */
3949
3950 static int pre_config(apr_pool_t *pconf,
3951                       apr_pool_t *plog,
3952                       apr_pool_t *ptemp)
3953 {
3954     APR_OPTIONAL_FN_TYPE(ap_register_rewrite_mapfunc) *map_pfn_register;
3955
3956     /* register int: rewritemap handlers */
3957     mapfunc_hash = apr_hash_make(pconf);
3958     map_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_rewrite_mapfunc);
3959     if (map_pfn_register) {
3960         map_pfn_register("tolower", rewrite_mapfunc_tolower);
3961         map_pfn_register("toupper", rewrite_mapfunc_toupper);
3962         map_pfn_register("escape", rewrite_mapfunc_escape);
3963         map_pfn_register("unescape", rewrite_mapfunc_unescape);
3964     }
3965     return OK;
3966 }
3967
3968 static int post_config(apr_pool_t *p,
3969                        apr_pool_t *plog,
3970                        apr_pool_t *ptemp,
3971                        server_rec *s)
3972 {
3973     apr_status_t rv;
3974     void *data;
3975     int first_time = 0;
3976     const char *userdata_key = "rewrite_init_module";
3977
3978     apr_pool_userdata_get(&data, userdata_key, s->process->pool);
3979     if (!data) {
3980         first_time = 1;
3981         apr_pool_userdata_set((const void *)1, userdata_key,
3982                               apr_pool_cleanup_null, s->process->pool);
3983     }
3984
3985     /* check if proxy module is available */
3986     proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL);
3987
3988 #ifndef REWRITELOG_DISABLED
3989     /* create the rewriting lockfiles in the parent */
3990     if ((rv = apr_global_mutex_create(&rewrite_log_lock, NULL,
3991                                       APR_LOCK_DEFAULT, p)) != APR_SUCCESS) {
3992         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
3993                      "mod_rewrite: could not create rewrite_log_lock");
3994         return HTTP_INTERNAL_SERVER_ERROR;
3995     }
3996
3997 #ifdef MOD_REWRITE_SET_MUTEX_PERMS
3998     rv = unixd_set_global_mutex_perms(rewrite_log_lock);
3999     if (rv != APR_SUCCESS) {
4000         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
4001                      "mod_rewrite: Could not set permissions on "
4002                      "rewrite_log_lock; check User and Group directives");
4003         return HTTP_INTERNAL_SERVER_ERROR;
4004     }
4005 #endif /* perms */
4006 #endif /* rewritelog */
4007
4008     rv = rewritelock_create(s, p);
4009     if (rv != APR_SUCCESS) {
4010         return HTTP_INTERNAL_SERVER_ERROR;
4011     }
4012
4013     apr_pool_cleanup_register(p, (void *)s, rewritelock_remove,
4014                               apr_pool_cleanup_null);
4015
4016     /* step through the servers and
4017      * - open each rewriting logfile
4018      * - open the RewriteMap prg:xxx programs
4019      */
4020     for (; s; s = s->next) {
4021 #ifndef REWRITELOG_DISABLED
4022         if (!open_rewritelog(s, p)) {
4023             return HTTP_INTERNAL_SERVER_ERROR;
4024         }
4025 #endif
4026
4027         if (!first_time) {
4028             if (run_rewritemap_programs(s, p) != APR_SUCCESS) {
4029                 return HTTP_INTERNAL_SERVER_ERROR;
4030             }
4031         }
4032     }
4033
4034     return OK;
4035 }
4036
4037 static void init_child(apr_pool_t *p, server_rec *s)
4038 {
4039     apr_status_t rv = 0; /* get a rid of gcc warning (REWRITELOG_DISABLED) */
4040
4041     if (lockname != NULL && *(lockname) != '\0') {
4042         rv = apr_global_mutex_child_init(&rewrite_mapr_lock_acquire,
4043                                          lockname, p);
4044         if (rv != APR_SUCCESS) {
4045             ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
4046                          "mod_rewrite: could not init rewrite_mapr_lock_acquire"
4047                          " in child");
4048         }
4049     }
4050
4051 #ifndef REWRITELOG_DISABLED
4052     rv = apr_global_mutex_child_init(&rewrite_log_lock, NULL, p);
4053     if (rv != APR_SUCCESS) {
4054         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
4055                      "mod_rewrite: could not init rewrite log lock in child");
4056     }
4057 #endif
4058
4059     /* create the lookup cache */
4060     if (!init_cache(p)) {
4061         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
4062                      "mod_rewrite: could not init map cache in child");
4063     }
4064 }
4065
4066
4067 /*
4068  * +-------------------------------------------------------+
4069  * |                                                       |
4070  * |                     runtime hooks
4071  * |                                                       |
4072  * +-------------------------------------------------------+
4073  */
4074
4075 /*
4076  * URI-to-filename hook
4077  * [deals with RewriteRules in server context]
4078  */
4079 static int hook_uri2file(request_rec *r)
4080 {
4081     rewrite_server_conf *conf;
4082     const char *saved_rulestatus;
4083     const char *var;
4084     const char *thisserver;
4085     char *thisport;
4086     const char *thisurl;
4087     unsigned int port;
4088     int rulestatus;
4089
4090     /*
4091      *  retrieve the config structures
4092      */
4093     conf = ap_get_module_config(r->server->module_config, &rewrite_module);
4094
4095     /*
4096      *  only do something under runtime if the engine is really enabled,
4097      *  else return immediately!
4098      */
4099     if (conf->state == ENGINE_DISABLED) {
4100         return DECLINED;
4101     }
4102
4103     /*
4104      *  check for the ugly API case of a virtual host section where no
4105      *  mod_rewrite directives exists. In this situation we became no chance
4106      *  by the API to setup our default per-server config so we have to
4107      *  on-the-fly assume we have the default config. But because the default
4108      *  config has a disabled rewriting engine we are lucky because can
4109      *  just stop operating now.
4110      */
4111     if (conf->server != r->server) {
4112         return DECLINED;
4113     }
4114
4115     /*
4116      *  add the SCRIPT_URL variable to the env. this is a bit complicated
4117      *  due to the fact that apache uses subrequests and internal redirects
4118      */
4119
4120     if (r->main == NULL) {
4121          var = apr_table_get(r->subprocess_env, REDIRECT_ENVVAR_SCRIPT_URL);
4122          if (var == NULL) {
4123              apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, r->uri);
4124          }
4125          else {
4126              apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
4127          }
4128     }
4129     else {
4130          var = apr_table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL);
4131          apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
4132     }
4133
4134     /*
4135      *  create the SCRIPT_URI variable for the env
4136      */
4137
4138     /* add the canonical URI of this URL */
4139     thisserver = ap_get_server_name(r);
4140     port = ap_get_server_port(r);
4141     if (ap_is_default_port(port, r)) {
4142         thisport = "";
4143     }
4144     else {
4145         thisport = apr_psprintf(r->pool, ":%u", port);
4146     }
4147     thisurl = apr_table_get(r->subprocess_env, ENVVAR_SCRIPT_URL);
4148
4149     /* set the variable */
4150     var = apr_pstrcat(r->pool, ap_http_method(r), "://", thisserver, thisport,
4151                       thisurl, NULL);
4152     apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URI, var);
4153
4154     if (!(saved_rulestatus = apr_table_get(r->notes,"mod_rewrite_rewritten"))) {
4155         /* if filename was not initially set,
4156          * we start with the requested URI
4157          */
4158         if (r->filename == NULL) {
4159             r->filename = apr_pstrdup(r->pool, r->uri);
4160             rewritelog((r, 2, NULL, "init rewrite engine with requested uri %s",
4161                         r->filename));
4162         }
4163         else {
4164             rewritelog((r, 2, NULL, "init rewrite engine with passed filename "
4165                         "%s. Original uri = %s", r->filename, r->uri));
4166         }
4167
4168         /*
4169          *  now apply the rules ...
4170          */
4171         rulestatus = apply_rewrite_list(r, conf->rewriterules, NULL);
4172         apr_table_set(r->notes,"mod_rewrite_rewritten",
4173                       apr_psprintf(r->pool,"%d",rulestatus));
4174     }
4175     else {
4176         rewritelog((r, 2, NULL, "uri already rewritten. Status %s, Uri %s, "
4177                     "r->filename %s", saved_rulestatus, r->uri, r->filename));
4178
4179         rulestatus = atoi(saved_rulestatus);
4180     }
4181
4182     if (rulestatus) {
4183         unsigned skip;
4184         apr_size_t flen;
4185
4186         if (ACTION_STATUS == rulestatus) {
4187             int n = r->status;
4188
4189             r->status = HTTP_OK;
4190             return n;
4191         }
4192
4193         flen = strlen(r->filename);
4194         if (flen > 6 && strncmp(r->filename, "proxy:", 6) == 0) {
4195             /* it should be go on as an internal proxy request */
4196
4197             /* check if the proxy module is enabled, so
4198              * we can actually use it!
4199              */
4200             if (!proxy_available) {
4201                 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4202                               "attempt to make remote request from mod_rewrite "
4203                               "without proxy enabled: %s", r->filename);
4204                 return HTTP_FORBIDDEN;
4205             }
4206
4207             /* make sure the QUERY_STRING and
4208              * PATH_INFO parts get incorporated
4209              */
4210             if (r->path_info != NULL) {
4211                 r->filename = apr_pstrcat(r->pool, r->filename,
4212                                           r->path_info, NULL);
4213             }
4214             if (r->args != NULL &&
4215                 r->uri == r->unparsed_uri) {
4216                 /* see proxy_http:proxy_http_canon() */
4217                 r->filename = apr_pstrcat(r->pool, r->filename,
4218                                           "?", r->args, NULL);
4219             }
4220
4221             /* now make sure the request gets handled by the proxy handler */
4222             r->proxyreq = PROXYREQ_REVERSE;
4223             r->handler  = "proxy-server";
4224
4225             rewritelog((r, 1, NULL, "go-ahead with proxy request %s [OK]",
4226                         r->filename));
4227             return OK;
4228         }
4229         else if ((skip = is_absolute_uri(r->filename)) > 0) {
4230             int n;
4231
4232             /* it was finally rewritten to a remote URL */
4233
4234             if (rulestatus != ACTION_NOESCAPE) {
4235                 rewritelog((r, 1, NULL, "escaping %s for redirect",
4236                             r->filename));
4237                 r->filename = escape_absolute_uri(r->pool, r->filename, skip);
4238             }
4239
4240             /* append the QUERY_STRING part */
4241             if (r->args) {
4242                 r->filename = apr_pstrcat(r->pool, r->filename, "?",
4243                                           (rulestatus == ACTION_NOESCAPE)
4244                                             ? r->args
4245                                             : ap_escape_uri(r->pool, r->args),
4246                                           NULL);
4247             }
4248
4249             /* determine HTTP redirect response code */
4250             if (ap_is_HTTP_REDIRECT(r->status)) {
4251                 n = r->status;
4252                 r->status = HTTP_OK; /* make Apache kernel happy */
4253             }
4254             else {
4255                 n = HTTP_MOVED_TEMPORARILY;
4256             }
4257
4258             /* now do the redirection */
4259             apr_table_setn(r->headers_out, "Location", r->filename);
4260             rewritelog((r, 1, NULL, "redirect to %s [REDIRECT/%d]", r->filename,
4261                         n));
4262
4263             return n;
4264         }
4265         else if (flen > 12 && strncmp(r->filename, "passthrough:", 12) == 0) {
4266             /*
4267              * Hack because of underpowered API: passing the current
4268              * rewritten filename through to other URL-to-filename handlers
4269              * just as it were the requested URL. This is to enable
4270              * post-processing by mod_alias, etc.  which always act on
4271              * r->uri! The difference here is: We do not try to
4272              * add the document root
4273              */
4274             r->uri = apr_pstrdup(r->pool, r->filename+12);
4275             return DECLINED;
4276         }
4277         else {
4278             /* it was finally rewritten to a local path */
4279
4280             /* expand "/~user" prefix */
4281 #if APR_HAS_USER
4282             r->filename = expand_tildepaths(r, r->filename);
4283 #endif
4284             rewritelog((r, 2, NULL, "local path result: %s", r->filename));
4285
4286             /* the filename must be either an absolute local path or an
4287              * absolute local URL.
4288              */
4289             if (   *r->filename != '/'
4290                 && !ap_os_is_path_absolute(r->pool, r->filename)) {
4291                 return HTTP_BAD_REQUEST;
4292             }
4293
4294             /* if there is no valid prefix, we call
4295              * the translator from the core and
4296              * prefix the filename with document_root
4297              *
4298              * NOTICE:
4299              * We cannot leave out the prefix_stat because
4300              * - when we always prefix with document_root
4301              *   then no absolute path can be created, e.g. via
4302              *   emulating a ScriptAlias directive, etc.
4303              * - when we always NOT prefix with document_root
4304              *   then the files under document_root have to
4305              *   be references directly and document_root
4306              *   gets never used and will be a dummy parameter -
4307              *   this is also bad
4308              *
4309              * BUT:
4310              * Under real Unix systems this is no problem,
4311              * because we only do stat() on the first directory
4312              * and this gets cached by the kernel for along time!
4313              */
4314             if (!prefix_stat(r->filename, r->pool)) {
4315                 int res;
4316                 char *tmp = r->uri;
4317
4318                 r->uri = r->filename;
4319                 res = ap_core_translate(r);
4320                 r->uri = tmp;
4321
4322                 if (res != OK) {
4323                     rewritelog((r, 1, NULL, "prefixing with document_root of %s"
4324                                 " FAILED", r->filename));
4325
4326                     return res;
4327                 }
4328
4329                 rewritelog((r, 2, NULL, "prefixed with document_root to %s",
4330                             r->filename));
4331             }
4332
4333             rewritelog((r, 1, NULL, "go-ahead with %s [OK]", r->filename));
4334             return OK;
4335         }
4336     }
4337     else {
4338         rewritelog((r, 1, NULL, "pass through %s", r->filename));
4339         return DECLINED;
4340     }
4341 }
4342
4343 /*
4344  * Fixup hook
4345  * [RewriteRules in directory context]
4346  */
4347 static int hook_fixup(request_rec *r)
4348 {
4349     rewrite_perdir_conf *dconf;
4350     char *cp;
4351     char *cp2;
4352     const char *ccp;
4353     apr_size_t l;
4354     int rulestatus;
4355     int n;
4356     char *ofilename;
4357
4358     dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
4359                                                         &rewrite_module);
4360
4361     /* if there is no per-dir config we return immediately */
4362     if (dconf == NULL) {
4363         return DECLINED;
4364     }
4365
4366     /* if there are no real (i.e. no RewriteRule directives!)
4367        per-dir config of us, we return also immediately */
4368     if (dconf->directory == NULL) {
4369         return DECLINED;
4370     }
4371
4372     /*
4373      *  .htaccess file is called before really entering the directory, i.e.:
4374      *  URL: http://localhost/foo  and .htaccess is located in foo directory
4375      *  Ignore such attempts, since they may lead to undefined behaviour.
4376      */
4377     l = strlen(dconf->directory) - 1;
4378     if (r->filename && strlen(r->filename) == l &&
4379         (dconf->directory)[l] == '/' &&
4380         !strncmp(r->filename, dconf->directory, l)) {
4381         return DECLINED;
4382     }
4383
4384     /*
4385      *  only do something under runtime if the engine is really enabled,
4386      *  for this directory, else return immediately!
4387      */
4388     if (dconf->state == ENGINE_DISABLED) {
4389         return DECLINED;
4390     }
4391
4392     /*
4393      *  Do the Options check after engine check, so
4394      *  the user is able to explicitely turn RewriteEngine Off.
4395      */
4396     if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) {
4397         /* FollowSymLinks is mandatory! */
4398         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4399                      "Options FollowSymLinks or SymLinksIfOwnerMatch is off "
4400                      "which implies that RewriteRule directive is forbidden: "
4401                      "%s", r->filename);
4402         return HTTP_FORBIDDEN;
4403     }
4404
4405     /*
4406      *  remember the current filename before rewriting for later check
4407      *  to prevent deadlooping because of internal redirects
4408      *  on final URL/filename which can be equal to the inital one.
4409      */
4410     ofilename = r->filename;
4411
4412     /*
4413      *  now apply the rules ...
4414      */
4415     rulestatus = apply_rewrite_list(r, dconf->rewriterules, dconf->directory);
4416     if (rulestatus) {
4417         unsigned skip;
4418
4419         if (ACTION_STATUS == rulestatus) {
4420             int n = r->status;
4421
4422             r->status = HTTP_OK;
4423             return n;
4424         }
4425
4426         l = strlen(r->filename);
4427         if (l > 6 && strncmp(r->filename, "proxy:", 6) == 0) {
4428             /* it should go on as an internal proxy request */
4429
4430             /* make sure the QUERY_STRING and
4431              * PATH_INFO parts get incorporated
4432              * (r->path_info was already appended by the
4433              * rewriting engine because of the per-dir context!)
4434              */
4435             if (r->args != NULL) {
4436                 r->filename = apr_pstrcat(r->pool, r->filename,
4437                                           "?", r->args, NULL);
4438             }
4439
4440             /* now make sure the request gets handled by the proxy handler */
4441             r->proxyreq = PROXYREQ_REVERSE;
4442             r->handler  = "proxy-server";
4443
4444             rewritelog((r, 1, dconf->directory, "go-ahead with proxy request "
4445                         "%s [OK]", r->filename));
4446             return OK;
4447         }
4448         else if ((skip = is_absolute_uri(r->filename)) > 0) {
4449             /* it was finally rewritten to a remote URL */
4450
4451             /* because we are in a per-dir context
4452              * first try to replace the directory with its base-URL
4453              * if there is a base-URL available
4454              */
4455             if (dconf->baseurl != NULL) {
4456                 /* skip 'scheme://' */
4457                 cp = r->filename + skip;
4458
4459                 if ((cp = ap_strchr(cp, '/')) != NULL && *(++cp)) {
4460                     rewritelog((r, 2, dconf->directory, 
4461                                 "trying to replace prefix %s with %s",
4462                                 dconf->directory, dconf->baseurl));
4463
4464                     /* I think, that hack needs an explanation:
4465                      * well, here is it:
4466                      * mod_rewrite was written for unix systems, were
4467                      * absolute file-system paths start with a slash.
4468                      * URL-paths _also_ start with slashes, so they
4469                      * can be easily compared with system paths.
4470                      *
4471                      * the following assumes, that the actual url-path
4472                      * may be prefixed by the current directory path and
4473                      * tries to replace the system path with the RewriteBase
4474                      * URL.
4475                      * That assumption is true if we use a RewriteRule like
4476                      *
4477                      * RewriteRule ^foo bar [R]
4478                      *
4479                      * (see apply_rewrite_rule function)
4480                      * However on systems that don't have a / as system
4481                      * root this will never match, so we skip the / after the
4482                      * hostname and compare/substitute only the stuff after it.
4483                      *
4484                      * (note that cp was already increased to the right value)
4485                      */
4486                     cp2 = subst_prefix_path(r, cp, (*dconf->directory == '/')
4487                                                    ? dconf->directory + 1
4488                                                    : dconf->directory,
4489                                             dconf->baseurl + 1);
4490                     if (strcmp(cp2, cp) != 0) {
4491                         *cp = '\0';
4492                         r->filename = apr_pstrcat(r->pool, r->filename,
4493                                                   cp2, NULL);
4494                     }
4495                 }
4496             }
4497
4498             /* now prepare the redirect... */
4499             if (rulestatus != ACTION_NOESCAPE) {
4500                 rewritelog((r, 1, dconf->directory, "escaping %s for redirect",
4501                             r->filename));
4502                 r->filename = escape_absolute_uri(r->pool, r->filename, skip);
4503             }
4504
4505             /* append the QUERY_STRING part */
4506             if (r->args) {
4507                 r->filename = apr_pstrcat(r->pool, r->filename, "?",
4508                                           (rulestatus == ACTION_NOESCAPE)
4509                                             ? r->args
4510                                             : ap_escape_uri(r->pool, r->args),
4511                                           NULL);
4512             }
4513
4514             /* determine HTTP redirect response code */
4515             if (ap_is_HTTP_REDIRECT(r->status)) {
4516                 n = r->status;
4517                 r->status = HTTP_OK; /* make Apache kernel happy */
4518             }
4519             else {
4520                 n = HTTP_MOVED_TEMPORARILY;
4521             }
4522
4523             /* now do the redirection */
4524             apr_table_setn(r->headers_out, "Location", r->filename);
4525             rewritelog((r, 1, dconf->directory, "redirect to %s [REDIRECT/%d]",
4526                         r->filename, n));
4527             return n;
4528         }
4529         else {
4530             /* it was finally rewritten to a local path */
4531
4532             /* if someone used the PASSTHROUGH flag in per-dir
4533              * context we just ignore it. It is only useful
4534              * in per-server context
4535              */
4536             if (l > 12 && strncmp(r->filename, "passthrough:", 12) == 0) {
4537                 r->filename = apr_pstrdup(r->pool, r->filename+12);
4538             }
4539
4540             /* the filename must be either an absolute local path or an
4541              * absolute local URL.
4542              */
4543             if (   *r->filename != '/'
4544                 && !ap_os_is_path_absolute(r->pool, r->filename)) {
4545                 return HTTP_BAD_REQUEST;
4546             }
4547
4548             /* Check for deadlooping:
4549              * At this point we KNOW that at least one rewriting
4550              * rule was applied, but when the resulting URL is
4551              * the same as the initial URL, we are not allowed to
4552              * use the following internal redirection stuff because
4553              * this would lead to a deadloop.
4554              */
4555             if (strcmp(r->filename, ofilename) == 0) {
4556                 rewritelog((r, 1, dconf->directory, "initial URL equal rewritten"
4557                             " URL: %s [IGNORING REWRITE]", r->filename));
4558                 return OK;
4559             }
4560
4561             /* if there is a valid base-URL then substitute
4562              * the per-dir prefix with this base-URL if the
4563              * current filename still is inside this per-dir
4564              * context. If not then treat the result as a
4565              * plain URL
4566              */
4567             if (dconf->baseurl != NULL) {
4568                 rewritelog((r, 2, dconf->directory, "trying to replace prefix "
4569                             "%s with %s", dconf->directory, dconf->baseurl));
4570
4571                 r->filename = subst_prefix_path(r, r->filename,
4572                                                 dconf->directory,
4573                                                 dconf->baseurl);
4574             }
4575             else {
4576                 /* if no explicit base-URL exists we assume
4577                  * that the directory prefix is also a valid URL
4578                  * for this webserver and only try to remove the
4579                  * document_root if it is prefix
4580                  */
4581                 if ((ccp = ap_document_root(r)) != NULL) {
4582                     /* strip trailing slash */
4583                     l = strlen(ccp);
4584                     if (ccp[l-1] == '/') {
4585                         --l;
4586                     }
4587                     if (!strncmp(r->filename, ccp, l) &&
4588                         r->filename[l] == '/') {
4589                         rewritelog((r, 2,dconf->directory, "strip document_root"
4590                                     " prefix: %s -> %s", r->filename,
4591                                     r->filename+l));
4592
4593                         r->filename = apr_pstrdup(r->pool, r->filename+l);
4594                     }
4595                 }
4596             }
4597
4598             /* now initiate the internal redirect */
4599             rewritelog((r, 1, dconf->directory, "internal redirect with %s "
4600                         "[INTERNAL REDIRECT]", r->filename));
4601             r->filename = apr_pstrcat(r->pool, "redirect:", r->filename, NULL);
4602             r->handler = "redirect-handler";
4603             return OK;
4604         }
4605     }
4606     else {
4607         rewritelog((r, 1, dconf->directory, "pass through %s", r->filename));
4608         return DECLINED;
4609     }
4610 }
4611
4612 /*
4613  * MIME-type hook
4614  * [T=...,H=...] execution
4615  */
4616 static int hook_mimetype(request_rec *r)
4617 {
4618     const char *t;
4619
4620     /* type */
4621     t = apr_table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR);
4622     if (t && *t) {
4623         rewritelog((r, 1, NULL, "force filename %s to have MIME-type '%s'",
4624                     r->filename, t));
4625
4626         ap_set_content_type(r, t);
4627     }
4628
4629     /* handler */
4630     t = apr_table_get(r->notes, REWRITE_FORCED_HANDLER_NOTEVAR);
4631     if (t && *t) {
4632         rewritelog((r, 1, NULL, "force filename %s to have the "
4633                     "Content-handler '%s'", r->filename, t));
4634
4635         r->handler = t;
4636     }
4637
4638     return OK;
4639 }
4640
4641 /* check whether redirect limit is reached */
4642 static int is_redirect_limit_exceeded(request_rec *r)
4643 {
4644     request_rec *top = r;
4645     rewrite_request_conf *reqc;
4646     rewrite_perdir_conf *dconf;
4647
4648     /* we store it in the top request */
4649     while (top->main) {
4650         top = top->main;
4651     }
4652     while (top->prev) {
4653         top = top->prev;
4654     }
4655
4656     /* fetch our config */
4657     reqc = (rewrite_request_conf *) ap_get_module_config(top->request_config,
4658                                                          &rewrite_module);
4659
4660     /* no config there? create one. */
4661     if (!reqc) {
4662         rewrite_server_conf *sconf;
4663
4664         reqc = apr_palloc(top->pool, sizeof(rewrite_request_conf));
4665         sconf = ap_get_module_config(r->server->module_config, &rewrite_module);
4666
4667         reqc->redirects = 0;
4668         reqc->redirect_limit = sconf->redirect_limit
4669                                  ? sconf->redirect_limit
4670                                  : REWRITE_REDIRECT_LIMIT;
4671
4672         /* associate it with this request */
4673         ap_set_module_config(top->request_config, &rewrite_module, reqc);
4674     }
4675
4676     /* allow to change the limit during redirects. */
4677     dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
4678                                                         &rewrite_module);
4679
4680     /* 0 == unset; take server conf ... */
4681     if (dconf->redirect_limit) {
4682         reqc->redirect_limit = dconf->redirect_limit;
4683     }
4684
4685     ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r,
4686                   "mod_rewrite's internal redirect status: %d/%d.",
4687                   reqc->redirects, reqc->redirect_limit);
4688
4689     /* and now give the caller a hint */
4690     return (reqc->redirects++ >= reqc->redirect_limit);
4691 }
4692
4693 /*
4694  * "content" handler for internal redirects
4695  */
4696 static int handler_redirect(request_rec *r)
4697 {
4698     if (strcmp(r->handler, "redirect-handler")) {
4699         return DECLINED;
4700     }
4701
4702     /* just make sure that we are really meant! */
4703     if (strncmp(r->filename, "redirect:", 9) != 0) {
4704         return DECLINED;
4705     }
4706
4707     if (is_redirect_limit_exceeded(r)) {
4708         ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
4709                       "mod_rewrite: maximum number of internal redirects "
4710                       "reached. Assuming configuration error. Use "
4711                       "'RewriteOptions MaxRedirects' to increase the limit "
4712                       "if neccessary.");
4713         return HTTP_INTERNAL_SERVER_ERROR;
4714     }
4715
4716     /* now do the internal redirect */
4717     ap_internal_redirect(apr_pstrcat(r->pool, r->filename+9,
4718                                      r->args ? "?" : NULL, r->args, NULL), r);
4719
4720     /* and return gracefully */
4721     return OK;
4722 }
4723
4724
4725 /*
4726  * +-------------------------------------------------------+
4727  * |                                                       |
4728  * |                Module paraphernalia
4729  * |                                                       |
4730  * +-------------------------------------------------------+
4731  */
4732
4733 #ifdef REWRITELOG_DISABLED
4734 static const char *fake_rewritelog(cmd_parms *cmd, void *dummy, const char *a1)
4735 {
4736     return "RewriteLog and RewriteLogLevel are not supported by this build "
4737            "of mod_rewrite because it was compiled using the "
4738            "-DREWRITELOG_DISABLED compiler option. You have to recompile "
4739            "mod_rewrite WITHOUT this option in order to use the rewrite log.";
4740 }
4741 #endif
4742
4743 static const command_rec command_table[] = {
4744     AP_INIT_FLAG(    "RewriteEngine",   cmd_rewriteengine,  NULL, OR_FILEINFO,
4745                      "On or Off to enable or disable (default) the whole "
4746                      "rewriting engine"),
4747     AP_INIT_ITERATE( "RewriteOptions",  cmd_rewriteoptions,  NULL, OR_FILEINFO,
4748                      "List of option strings to set"),
4749     AP_INIT_TAKE1(   "RewriteBase",     cmd_rewritebase,     NULL, OR_FILEINFO,
4750                      "the base URL of the per-directory context"),
4751     AP_INIT_RAW_ARGS("RewriteCond",     cmd_rewritecond,     NULL, OR_FILEINFO,
4752                      "an input string and a to be applied regexp-pattern"),
4753     AP_INIT_RAW_ARGS("RewriteRule",     cmd_rewriterule,     NULL, OR_FILEINFO,
4754                      "an URL-applied regexp-pattern and a substitution URL"),
4755     AP_INIT_TAKE2(   "RewriteMap",      cmd_rewritemap,      NULL, RSRC_CONF,
4756                      "a mapname and a filename"),
4757     AP_INIT_TAKE1(   "RewriteLock",     cmd_rewritelock,     NULL, RSRC_CONF,
4758                      "the filename of a lockfile used for inter-process "
4759                      "synchronization"),
4760 #ifndef REWRITELOG_DISABLED
4761     AP_INIT_TAKE1(   "RewriteLog",      cmd_rewritelog,      NULL, RSRC_CONF,
4762                      "the filename of the rewriting logfile"),
4763     AP_INIT_TAKE1(   "RewriteLogLevel", cmd_rewriteloglevel, NULL, RSRC_CONF,
4764                      "the level of the rewriting logfile verbosity "
4765                      "(0=none, 1=std, .., 9=max)"),
4766 #else
4767     AP_INIT_TAKE1(   "RewriteLog", fake_rewritelog, NULL, RSRC_CONF,
4768                      "[DISABLED] the filename of the rewriting logfile"),
4769     AP_INIT_TAKE1(   "RewriteLogLevel", fake_rewritelog, NULL, RSRC_CONF,
4770                      "[DISABLED] the level of the rewriting logfile verbosity"),
4771 #endif
4772     { NULL }
4773 };
4774
4775 static void ap_register_rewrite_mapfunc(char *name, rewrite_mapfunc_t *func)
4776 {
4777     apr_hash_set(mapfunc_hash, name, strlen(name), (const void *)func);
4778 }
4779
4780 static void register_hooks(apr_pool_t *p)
4781 {
4782     /* fixup after mod_proxy, so that the proxied url will not
4783      * escaped accidentally by mod_proxy's fixup.
4784      */
4785     static const char * const aszPre[]={ "mod_proxy.c", NULL };
4786
4787     APR_REGISTER_OPTIONAL_FN(ap_register_rewrite_mapfunc);
4788
4789     ap_hook_handler(handler_redirect, NULL, NULL, APR_HOOK_MIDDLE);
4790     ap_hook_pre_config(pre_config, NULL, NULL, APR_HOOK_MIDDLE);
4791     ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_MIDDLE);
4792     ap_hook_child_init(init_child, NULL, NULL, APR_HOOK_MIDDLE);
4793
4794     ap_hook_fixups(hook_fixup, aszPre, NULL, APR_HOOK_FIRST);
4795     ap_hook_fixups(hook_mimetype, NULL, NULL, APR_HOOK_LAST);
4796     ap_hook_translate_name(hook_uri2file, NULL, NULL, APR_HOOK_FIRST);
4797 }
4798
4799     /* the main config structure */
4800 module AP_MODULE_DECLARE_DATA rewrite_module = {
4801    STANDARD20_MODULE_STUFF,
4802    config_perdir_create,        /* create per-dir    config structures */
4803    config_perdir_merge,         /* merge  per-dir    config structures */
4804    config_server_create,        /* create per-server config structures */
4805    config_server_merge,         /* merge  per-server config structures */
4806    command_table,               /* table of config file commands       */
4807    register_hooks               /* register hooks                      */
4808 };
4809
4810 /*EOF*/