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