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