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