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