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