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