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