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