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