]> granicus.if.org Git - apache/blob - modules/mappers/mod_rewrite.c
MF 1.3 the fis for the mod_rewrite stupidity.
[apache] / modules / mappers / mod_rewrite.c
1 /* ====================================================================
2  * The Apache Software License, Version 1.1
3  *
4  * Copyright (c) 2000 The Apache Software Foundation.  All rights
5  * reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  *
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in
16  *    the documentation and/or other materials provided with the
17  *    distribution.
18  *
19  * 3. The end-user documentation included with the redistribution,
20  *    if any, must include the following acknowledgment:
21  *       "This product includes software developed by the
22  *        Apache Software Foundation (http://www.apache.org/)."
23  *    Alternately, this acknowledgment may appear in the software itself,
24  *    if and wherever such third-party acknowledgments normally appear.
25  *
26  * 4. The names "Apache" and "Apache Software Foundation" must
27  *    not be used to endorse or promote products derived from this
28  *    software without prior written permission. For written
29  *    permission, please contact apache@apache.org.
30  *
31  * 5. Products derived from this software may not be called "Apache",
32  *    nor may "Apache" appear in their name, without prior written
33  *    permission of the Apache Software Foundation.
34  *
35  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
46  * SUCH DAMAGE.
47  * ====================================================================
48  *
49  * This software consists of voluntary contributions made by many
50  * individuals on behalf of the Apache Software Foundation.  For more
51  * information on the Apache Software Foundation, please see
52  * <http://www.apache.org/>.
53  *
54  * Portions of this software are based upon public domain software
55  * originally written at the National Center for Supercomputing Applications,
56  * University of Illinois, Urbana-Champaign.
57  */
58
59 /*                       _                            _ _
60 **   _ __ ___   ___   __| |    _ __ _____      ___ __(_) |_ ___
61 **  | '_ ` _ \ / _ \ / _` |   | '__/ _ \ \ /\ / / '__| | __/ _ \
62 **  | | | | | | (_) | (_| |   | | |  __/\ V  V /| |  | | ||  __/
63 **  |_| |_| |_|\___/ \__,_|___|_|  \___| \_/\_/ |_|  |_|\__\___|
64 **                       |_____|
65 **
66 **  URL Rewriting Module
67 **
68 **  This module uses a rule-based rewriting engine (based on a
69 **  regular-expression parser) to rewrite requested URLs on the fly.
70 **
71 **  It supports an unlimited number of additional rule conditions (which can
72 **  operate on a lot of variables, even on HTTP headers) for granular
73 **  matching and even external database lookups (either via plain text
74 **  tables, DBM hash files or even external processes) for advanced URL
75 **  substitution.
76 **
77 **  It operates on the full URLs (including the PATH_INFO part) both in
78 **  per-server context (httpd.conf) and per-dir context (.htaccess) and even
79 **  can generate QUERY_STRING parts on result.   The rewriting result finally
80 **  can lead to internal subprocessing, external request redirection or even
81 **  to internal proxy throughput.
82 **
83 **  This module was originally written in April 1996 and
84 **  gifted exclusively to the The Apache Software Foundation in July 1997 by
85 **
86 **      Ralf S. Engelschall
87 **      rse@engelschall.com
88 **      www.engelschall.com
89 */
90
91 #include "ap_config.h"
92 #include "httpd.h"
93 #include "http_config.h"
94 #include "http_request.h"
95 #include "http_core.h"
96 #include "http_log.h"
97 #include "http_protocol.h"
98 #include "mod_rewrite.h"
99 #include "apr_strings.h"
100 #include "apr_user.h"
101
102 #if !defined(OS2) && !defined(WIN32)
103 #include "unixd.h"
104 #endif
105
106 #ifndef NO_WRITEV
107 #ifndef NETWARE
108 #ifdef HAVE_SYS_TYPES_H
109 #include <sys/types.h>
110 #endif
111 #endif
112 #if APR_HAVE_SYS_UIO_H
113 #include <sys/uio.h>
114 #endif
115 #endif
116 #ifdef HAVE_UNISTD_H
117 #include <unistd.h>
118 #endif
119 #ifdef HAVE_STRINGS_H
120 #include <strings.h>
121 #endif
122
123 /*
124 ** +-------------------------------------------------------+
125 ** |                                                       |
126 ** |             static module configuration
127 ** |                                                       |
128 ** +-------------------------------------------------------+
129 */
130
131
132 /*
133 **  Our interface to the Apache server kernel:
134 **
135 **  o  Runtime logic of a request is as following:
136 **       while(request or subrequest)
137 **           foreach(stage #0...#9)
138 **               foreach(module) (**)
139 **                   try to run hook
140 **
141 **  o  the order of modules at (**) is the inverted order as
142 **     given in the "Configuration" file, i.e. the last module
143 **     specified is the first one called for each hook!
144 **     The core module is always the last!
145 **
146 **  o  there are two different types of result checking and
147 **     continue processing:
148 **     for hook #0,#1,#4,#5,#6,#8:
149 **         hook run loop stops on first modules which gives
150 **         back a result != DECLINED, i.e. it usually returns OK
151 **         which says "OK, module has handled this _stage_" and for #1
152 **         this have not to mean "Ok, the filename is now valid".
153 **     for hook #2,#3,#7,#9:
154 **         all hooks are run, independend of result
155 **
156 **  o  at the last stage, the core module always
157 **       - says "HTTP_BAD_REQUEST" if r->filename does not begin with "/"
158 **       - prefix URL with document_root or replaced server_root
159 **         with document_root and sets r->filename
160 **       - always return a "OK" independed if the file really exists
161 **         or not!
162 */
163
164     /* The section for the Configure script:
165     * XXX: this needs updating for apache-2.0 configuration method
166      * MODULE-DEFINITION-START
167      * Name: rewrite_module
168      * ConfigStart
169     . ./helpers/find-dbm-lib
170     if [ "x$found_dbm" = "x1" ]; then
171         echo "      enabling DBM support for mod_rewrite"
172     else
173         echo "      disabling DBM support for mod_rewrite"
174         echo "      (perhaps you need to add -ldbm, -lndbm or -lgdbm to EXTRA_LIBS)"
175         CFLAGS="$CFLAGS -DNO_DBM_REWRITEMAP"
176     fi
177      * ConfigEnd
178      * MODULE-DEFINITION-END
179      */
180
181     /* the module (predeclaration) */
182 module AP_MODULE_DECLARE_DATA rewrite_module;
183
184     /* the cache */
185 static cache *cachep;
186
187     /* whether proxy module is available or not */
188 static int proxy_available;
189
190 static const char *lockname;
191 static apr_lock_t *rewrite_mapr_lock = NULL;
192 static apr_lock_t *rewrite_log_lock = NULL;
193
194 /*
195 ** +-------------------------------------------------------+
196 ** |                                                       |
197 ** |           configuration directive handling
198 ** |                                                       |
199 ** +-------------------------------------------------------+
200 */
201
202 /*
203 **
204 **  per-server configuration structure handling
205 **
206 */
207
208 static void *config_server_create(apr_pool_t *p, server_rec *s)
209 {
210     rewrite_server_conf *a;
211
212     a = (rewrite_server_conf *)apr_pcalloc(p, sizeof(rewrite_server_conf));
213
214     a->state           = ENGINE_DISABLED;
215     a->options         = OPTION_NONE;
216     a->rewritelogfile  = NULL;
217     a->rewritelogfp    = NULL;
218     a->rewriteloglevel = 0;
219     a->rewritemaps     = apr_make_array(p, 2, sizeof(rewritemap_entry));
220     a->rewriteconds    = apr_make_array(p, 2, sizeof(rewritecond_entry));
221     a->rewriterules    = apr_make_array(p, 2, sizeof(rewriterule_entry));
222     a->server          = s;
223
224     return (void *)a;
225 }
226
227 static void *config_server_merge(apr_pool_t *p, void *basev, void *overridesv)
228 {
229     rewrite_server_conf *a, *base, *overrides;
230
231     a         = (rewrite_server_conf *)apr_pcalloc(p, sizeof(rewrite_server_conf));
232     base      = (rewrite_server_conf *)basev;
233     overrides = (rewrite_server_conf *)overridesv;
234
235     a->state   = overrides->state;
236     a->options = overrides->options;
237     a->server  = overrides->server;
238
239     if (a->options & OPTION_INHERIT) {
240         /*
241          *  local directives override
242          *  and anything else is inherited
243          */
244         a->rewriteloglevel = overrides->rewriteloglevel != 0 
245                              ? overrides->rewriteloglevel
246                              : base->rewriteloglevel;
247         a->rewritelogfile  = overrides->rewritelogfile != NULL 
248                              ? overrides->rewritelogfile
249                              : base->rewritelogfile;
250         a->rewritelogfp    = overrides->rewritelogfp != NULL 
251                              ? overrides->rewritelogfp 
252                              : base->rewritelogfp;
253         a->rewritemaps     = apr_append_arrays(p, overrides->rewritemaps,
254                                               base->rewritemaps);
255         a->rewriteconds    = apr_append_arrays(p, overrides->rewriteconds,
256                                               base->rewriteconds);
257         a->rewriterules    = apr_append_arrays(p, overrides->rewriterules,
258                                               base->rewriterules);
259     }
260     else {
261         /*
262          *  local directives override
263          *  and anything else gets defaults
264          */
265         a->rewriteloglevel = overrides->rewriteloglevel;
266         a->rewritelogfile  = overrides->rewritelogfile;
267         a->rewritelogfp    = overrides->rewritelogfp;
268         a->rewritemaps     = overrides->rewritemaps;
269         a->rewriteconds    = overrides->rewriteconds;
270         a->rewriterules    = overrides->rewriterules;
271     }
272
273     return (void *)a;
274 }
275
276
277 /*
278 **
279 **  per-directory configuration structure handling
280 **
281 */
282
283 static void *config_perdir_create(apr_pool_t *p, char *path)
284 {
285     rewrite_perdir_conf *a;
286
287     a = (rewrite_perdir_conf *)apr_pcalloc(p, sizeof(rewrite_perdir_conf));
288
289     a->state           = ENGINE_DISABLED;
290     a->options         = OPTION_NONE;
291     a->baseurl         = NULL;
292     a->rewriteconds    = apr_make_array(p, 2, sizeof(rewritecond_entry));
293     a->rewriterules    = apr_make_array(p, 2, sizeof(rewriterule_entry));
294
295     if (path == NULL) {
296         a->directory = NULL;
297     }
298     else {
299         /* make sure it has a trailing slash */
300         if (path[strlen(path)-1] == '/') {
301             a->directory = apr_pstrdup(p, path);
302         }
303         else {
304             a->directory = apr_pstrcat(p, path, "/", NULL);
305         }
306     }
307
308     return (void *)a;
309 }
310
311 static void *config_perdir_merge(apr_pool_t *p, void *basev, void *overridesv)
312 {
313     rewrite_perdir_conf *a, *base, *overrides;
314
315     a         = (rewrite_perdir_conf *)apr_pcalloc(p,
316                                                   sizeof(rewrite_perdir_conf));
317     base      = (rewrite_perdir_conf *)basev;
318     overrides = (rewrite_perdir_conf *)overridesv;
319
320     a->state     = overrides->state;
321     a->options   = overrides->options;
322     a->directory = overrides->directory;
323     a->baseurl   = overrides->baseurl;
324
325     if (a->options & OPTION_INHERIT) {
326         a->rewriteconds = apr_append_arrays(p, overrides->rewriteconds,
327                                            base->rewriteconds);
328         a->rewriterules = apr_append_arrays(p, overrides->rewriterules,
329                                            base->rewriterules);
330     }
331     else {
332         a->rewriteconds = overrides->rewriteconds;
333         a->rewriterules = overrides->rewriterules;
334     }
335
336     return (void *)a;
337 }
338
339
340 /*
341 **
342 **  the configuration commands
343 **
344 */
345
346 static const char *cmd_rewriteengine(cmd_parms *cmd,
347                                      void *in_dconf, int flag)
348 {
349     rewrite_perdir_conf *dconf = in_dconf;
350     rewrite_server_conf *sconf;
351
352     sconf = 
353         (rewrite_server_conf *)ap_get_module_config(cmd->server->module_config,
354                                                     &rewrite_module);
355
356     if (cmd->path == NULL) { /* is server command */
357         sconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
358     }
359     else                   /* is per-directory command */ {
360         dconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
361     }
362
363     return NULL;
364 }
365
366 static const char *cmd_rewriteoptions(cmd_parms *cmd,
367                                       void *in_dconf, const char *option)
368 {
369     rewrite_perdir_conf *dconf = in_dconf;
370     rewrite_server_conf *sconf;
371     const char *err;
372
373     sconf = (rewrite_server_conf *)
374             ap_get_module_config(cmd->server->module_config, &rewrite_module);
375
376     if (cmd->path == NULL) { /* is server command */
377         err = cmd_rewriteoptions_setoption(cmd->pool,
378                                            &(sconf->options), option);
379     }
380     else {                 /* is per-directory command */
381         err = cmd_rewriteoptions_setoption(cmd->pool,
382                                            &(dconf->options), option);
383     }
384
385     return err;
386 }
387
388 static const char *cmd_rewriteoptions_setoption(apr_pool_t *p, int *options,
389                                                 const char *name)
390 {
391     if (strcasecmp(name, "inherit") == 0) {
392         *options |= OPTION_INHERIT;
393     }
394     else {
395         return apr_pstrcat(p, "RewriteOptions: unknown option '",
396                           name, "'", NULL);
397     }
398     return NULL;
399 }
400
401 static const char *cmd_rewritelog(cmd_parms *cmd, void *dconf, const char *a1)
402 {
403     rewrite_server_conf *sconf;
404
405     sconf = (rewrite_server_conf *)
406             ap_get_module_config(cmd->server->module_config, &rewrite_module);
407
408     sconf->rewritelogfile = a1;
409
410     return NULL;
411 }
412
413 static const char *cmd_rewriteloglevel(cmd_parms *cmd, void *dconf, const char *a1)
414 {
415     rewrite_server_conf *sconf;
416
417     sconf = (rewrite_server_conf *)
418             ap_get_module_config(cmd->server->module_config, &rewrite_module);
419
420     sconf->rewriteloglevel = atoi(a1);
421
422     return NULL;
423 }
424
425 static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, const char *a1,
426                                   const char *a2)
427 {
428     rewrite_server_conf *sconf;
429     rewritemap_entry *newmap;
430     apr_finfo_t st;
431
432     sconf = (rewrite_server_conf *)
433             ap_get_module_config(cmd->server->module_config, &rewrite_module);
434
435     newmap = apr_push_array(sconf->rewritemaps);
436
437     newmap->name = a1;
438     newmap->func = NULL;
439     if (strncmp(a2, "txt:", 4) == 0) {
440         newmap->type      = MAPTYPE_TXT;
441         newmap->datafile  = a2+4;
442         newmap->checkfile = a2+4;
443     }
444     else if (strncmp(a2, "rnd:", 4) == 0) {
445         newmap->type      = MAPTYPE_RND;
446         newmap->datafile  = a2+4;
447         newmap->checkfile = a2+4;
448     }
449     else if (strncmp(a2, "dbm:", 4) == 0) {
450 #ifndef NO_DBM_REWRITEMAP
451         newmap->type      = MAPTYPE_DBM;
452         newmap->datafile  = a2+4;
453         newmap->checkfile = apr_pstrcat(cmd->pool, a2+4, NDBM_FILE_SUFFIX, NULL);
454 #else
455         return apr_pstrdup(cmd->pool, "RewriteMap: cannot use NDBM mapfile, "
456                           "because no NDBM support is compiled in");
457 #endif
458     }
459     else if (strncmp(a2, "prg:", 4) == 0) {
460         newmap->type = MAPTYPE_PRG;
461         newmap->datafile = a2+4;
462         newmap->checkfile = a2+4;
463     }
464     else if (strncmp(a2, "int:", 4) == 0) {
465         newmap->type      = MAPTYPE_INT;
466         newmap->datafile  = NULL;
467         newmap->checkfile = NULL;
468         if (strcmp(a2+4, "tolower") == 0) {
469             newmap->func = rewrite_mapfunc_tolower;
470         }
471         else if (strcmp(a2+4, "toupper") == 0) {
472             newmap->func = rewrite_mapfunc_toupper;
473         }
474         else if (strcmp(a2+4, "escape") == 0) {
475             newmap->func = rewrite_mapfunc_escape;
476         }
477         else if (strcmp(a2+4, "unescape") == 0) {
478             newmap->func = rewrite_mapfunc_unescape;
479         }
480         else if (sconf->state == ENGINE_ENABLED) {
481             return apr_pstrcat(cmd->pool, "RewriteMap: internal map not found:",
482                               a2+4, NULL);
483         }
484     }
485     else {
486         newmap->type      = MAPTYPE_TXT;
487         newmap->datafile  = a2;
488         newmap->checkfile = a2;
489     }
490     newmap->fpin  = NULL;
491     newmap->fpout = NULL;
492
493     if (newmap->checkfile && (sconf->state == ENGINE_ENABLED)
494         && (apr_stat(&st, newmap->checkfile, APR_FINFO_MIN, 
495                      cmd->pool) != APR_SUCCESS)) {
496         return apr_pstrcat(cmd->pool,
497                           "RewriteMap: map file or program not found:",
498                           newmap->checkfile, NULL);
499     }
500
501     return NULL;
502 }
503
504 static const char *cmd_rewritelock(cmd_parms *cmd, void *dconf, const char *a1)
505 {
506     const char *error;
507
508     if ((error = ap_check_cmd_context(cmd, GLOBAL_ONLY)) != NULL)
509         return error;
510
511     lockname = a1;
512
513     return NULL;
514 }
515
516 static const char *cmd_rewritebase(cmd_parms *cmd, void *in_dconf,
517                                    const char *a1)
518 {
519     rewrite_perdir_conf *dconf = in_dconf;
520
521     if (cmd->path == NULL || dconf == NULL) {
522         return "RewriteBase: only valid in per-directory config files";
523     }
524     if (a1[0] == '\0') {
525         return "RewriteBase: empty URL not allowed";
526     }
527     if (a1[0] != '/') {
528         return "RewriteBase: argument is not a valid URL";
529     }
530
531     dconf->baseurl = a1;
532
533     return NULL;
534 }
535
536 static const char *cmd_rewritecond(cmd_parms *cmd, void *in_dconf,
537                                    const char *in_str)
538 {
539     rewrite_perdir_conf *dconf = in_dconf;
540     char *str = apr_pstrdup(cmd->pool, in_str);
541     rewrite_server_conf *sconf;
542     rewritecond_entry *newcond;
543     regex_t *regexp;
544     char *a1;
545     char *a2;
546     char *a3;
547     char *cp;
548     const char *err;
549     int rc;
550
551     sconf = (rewrite_server_conf *)
552             ap_get_module_config(cmd->server->module_config, &rewrite_module);
553
554     /*  make a new entry in the internal temporary rewrite rule list */
555     if (cmd->path == NULL) {   /* is server command */
556         newcond = apr_push_array(sconf->rewriteconds);
557     }
558     else {                     /* is per-directory command */
559         newcond = apr_push_array(dconf->rewriteconds);
560     }
561
562     /*  parse the argument line ourself */
563     if (parseargline(str, &a1, &a2, &a3)) {
564         return apr_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str,
565                           "'", NULL);
566     }
567
568     /*  arg1: the input string */
569     newcond->input = apr_pstrdup(cmd->pool, a1);
570
571     /* arg3: optional flags field
572        (this have to be first parsed, because we need to
573         know if the regex should be compiled with ICASE!) */
574     newcond->flags = CONDFLAG_NONE;
575     if (a3 != NULL) {
576         if ((err = cmd_rewritecond_parseflagfield(cmd->pool, newcond,
577                                                   a3)) != NULL) {
578             return err;
579         }
580     }
581
582     /*  arg2: the pattern
583         try to compile the regexp to test if is ok */
584     cp = a2;
585     if (cp[0] == '!') {
586         newcond->flags |= CONDFLAG_NOTMATCH;
587         cp++;
588     }
589
590     /* now be careful: Under the POSIX regex library
591        we can compile the pattern for case insensitive matching,
592        under the old V8 library we have to do it self via a hack */
593     if (newcond->flags & CONDFLAG_NOCASE) {
594         rc = ((regexp = ap_pregcomp(cmd->pool, cp, REG_EXTENDED|REG_ICASE))
595               == NULL);
596     }
597     else {
598         rc = ((regexp = ap_pregcomp(cmd->pool, cp, REG_EXTENDED)) == NULL);
599     }
600     if (rc) {
601         return apr_pstrcat(cmd->pool,
602                           "RewriteCond: cannot compile regular expression '",
603                           a2, "'", NULL);
604     }
605
606     newcond->pattern = apr_pstrdup(cmd->pool, cp);
607     newcond->regexp  = regexp;
608
609     return NULL;
610 }
611
612 static const char *cmd_rewritecond_parseflagfield(apr_pool_t *p,
613                                                   rewritecond_entry *cfg,
614                                                   char *str)
615 {
616     char *cp;
617     char *cp1;
618     char *cp2;
619     char *cp3;
620     char *key;
621     char *val;
622     const char *err;
623
624     if (str[0] != '[' || str[strlen(str)-1] != ']') {
625         return "RewriteCond: bad flag delimiters";
626     }
627
628     cp = str+1;
629     str[strlen(str)-1] = ','; /* for simpler parsing */
630     for ( ; *cp != '\0'; ) {
631         /* skip whitespaces */
632         for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++)
633             ;
634         if (*cp == '\0') {
635             break;
636         }
637         cp1 = cp;
638         if ((cp2 = strchr(cp, ',')) != NULL) {
639             cp = cp2+1;
640             for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--)
641                 ;
642             *cp2 = '\0';
643             if ((cp3 = strchr(cp1, '=')) != NULL) {
644                 *cp3 = '\0';
645                 key = cp1;
646                 val = cp3+1;
647             }
648             else {
649                 key = cp1;
650                 val = "";
651             }
652             if ((err = cmd_rewritecond_setflag(p, cfg, key, val)) != NULL) {
653                 return err;
654             }
655         }
656         else {
657             break;
658         }
659     }
660
661     return NULL;
662 }
663
664 static const char *cmd_rewritecond_setflag(apr_pool_t *p, rewritecond_entry *cfg,
665                                            char *key, char *val)
666 {
667     if (   strcasecmp(key, "nocase") == 0
668         || strcasecmp(key, "NC") == 0    ) {
669         cfg->flags |= CONDFLAG_NOCASE;
670     }
671     else if (   strcasecmp(key, "ornext") == 0
672              || strcasecmp(key, "OR") == 0    ) {
673         cfg->flags |= CONDFLAG_ORNEXT;
674     }
675     else {
676         return apr_pstrcat(p, "RewriteCond: unknown flag '", key, "'", NULL);
677     }
678     return NULL;
679 }
680
681 static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf,
682                                    const char *in_str)
683 {
684     rewrite_perdir_conf *dconf = in_dconf;
685     char *str = apr_pstrdup(cmd->pool, in_str);
686     rewrite_server_conf *sconf;
687     rewriterule_entry *newrule;
688     regex_t *regexp;
689     char *a1;
690     char *a2;
691     char *a3;
692     char *cp;
693     const char *err;
694     int mode;
695
696     sconf = (rewrite_server_conf *)
697             ap_get_module_config(cmd->server->module_config, &rewrite_module);
698
699     /*  make a new entry in the internal rewrite rule list */
700     if (cmd->path == NULL) {   /* is server command */
701         newrule = apr_push_array(sconf->rewriterules);
702     }
703     else {                     /* is per-directory command */
704         newrule = apr_push_array(dconf->rewriterules);
705     }
706
707     /*  parse the argument line ourself */
708     if (parseargline(str, &a1, &a2, &a3)) {
709         return apr_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str,
710                           "'", NULL);
711     }
712
713     /* arg3: optional flags field */
714     newrule->forced_mimetype     = NULL;
715     newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY;
716     newrule->flags  = RULEFLAG_NONE;
717     newrule->env[0] = NULL;
718     newrule->skip   = 0;
719     if (a3 != NULL) {
720         if ((err = cmd_rewriterule_parseflagfield(cmd->pool, newrule,
721                                                   a3)) != NULL) {
722             return err;
723         }
724     }
725
726     /*  arg1: the pattern
727      *  try to compile the regexp to test if is ok
728      */
729     cp = a1;
730     if (cp[0] == '!') {
731         newrule->flags |= RULEFLAG_NOTMATCH;
732         cp++;
733     }
734     mode = REG_EXTENDED;
735     if (newrule->flags & RULEFLAG_NOCASE) {
736         mode |= REG_ICASE;
737     }
738     if ((regexp = ap_pregcomp(cmd->pool, cp, mode)) == NULL) {
739         return apr_pstrcat(cmd->pool,
740                           "RewriteRule: cannot compile regular expression '",
741                           a1, "'", NULL);
742     }
743     newrule->pattern = apr_pstrdup(cmd->pool, cp);
744     newrule->regexp  = regexp;
745
746     /*  arg2: the output string
747      *  replace the $<N> by \<n> which is needed by the currently
748      *  used Regular Expression library
749      *
750      * TODO: Is this still required for PCRE?  If not, does it *work* with PCRE?
751      */
752     newrule->output = apr_pstrdup(cmd->pool, a2);
753
754     /* now, if the server or per-dir config holds an
755      * array of RewriteCond entries, we take it for us
756      * and clear the array
757      */
758     if (cmd->path == NULL) {  /* is server command */
759         newrule->rewriteconds   = sconf->rewriteconds;
760         sconf->rewriteconds = apr_make_array(cmd->pool, 2,
761                                             sizeof(rewritecond_entry));
762     }
763     else {                    /* is per-directory command */
764         newrule->rewriteconds   = dconf->rewriteconds;
765         dconf->rewriteconds = apr_make_array(cmd->pool, 2,
766                                             sizeof(rewritecond_entry));
767     }
768
769     return NULL;
770 }
771
772 static const char *cmd_rewriterule_parseflagfield(apr_pool_t *p,
773                                                   rewriterule_entry *cfg,
774                                                   char *str)
775 {
776     char *cp;
777     char *cp1;
778     char *cp2;
779     char *cp3;
780     char *key;
781     char *val;
782     const char *err;
783
784     if (str[0] != '[' || str[strlen(str)-1] != ']') {
785         return "RewriteRule: bad flag delimiters";
786     }
787
788     cp = str+1;
789     str[strlen(str)-1] = ','; /* for simpler parsing */
790     for ( ; *cp != '\0'; ) {
791         /* skip whitespaces */
792         for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++)
793             ;
794         if (*cp == '\0') {
795             break;
796         }
797         cp1 = cp;
798         if ((cp2 = strchr(cp, ',')) != NULL) {
799             cp = cp2+1;
800             for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--)
801                 ;
802             *cp2 = '\0';
803             if ((cp3 = strchr(cp1, '=')) != NULL) {
804                 *cp3 = '\0';
805                 key = cp1;
806                 val = cp3+1;
807             }
808             else {
809                 key = cp1;
810                 val = "";
811             }
812             if ((err = cmd_rewriterule_setflag(p, cfg, key, val)) != NULL) {
813                 return err;
814             }
815         }
816         else {
817             break;
818         }
819     }
820
821     return NULL;
822 }
823
824 static const char *cmd_rewriterule_setflag(apr_pool_t *p, rewriterule_entry *cfg,
825                                            char *key, char *val)
826 {
827     int status = 0;
828     int i;
829
830     if (   strcasecmp(key, "redirect") == 0
831         || strcasecmp(key, "R") == 0       ) {
832         cfg->flags |= RULEFLAG_FORCEREDIRECT;
833         if (strlen(val) > 0) {
834             if (strcasecmp(val, "permanent") == 0) {
835                 status = HTTP_MOVED_PERMANENTLY;
836             }
837             else if (strcasecmp(val, "temp") == 0) {
838                 status = HTTP_MOVED_TEMPORARILY;
839             }
840             else if (strcasecmp(val, "seeother") == 0) {
841                 status = HTTP_SEE_OTHER;
842             }
843             else if (apr_isdigit(*val)) {
844                 status = atoi(val);
845             }
846             if (!ap_is_HTTP_REDIRECT(status)) {
847                 return "RewriteRule: invalid HTTP response code "
848                        "for flag 'R'";
849             }
850             cfg->forced_responsecode = status;
851         }
852     }
853     else if (   strcasecmp(key, "last") == 0
854              || strcasecmp(key, "L") == 0   ) {
855         cfg->flags |= RULEFLAG_LASTRULE;
856     }
857     else if (   strcasecmp(key, "next") == 0
858              || strcasecmp(key, "N") == 0   ) {
859         cfg->flags |= RULEFLAG_NEWROUND;
860     }
861     else if (   strcasecmp(key, "chain") == 0
862              || strcasecmp(key, "C") == 0    ) {
863         cfg->flags |= RULEFLAG_CHAIN;
864     }
865     else if (   strcasecmp(key, "type") == 0
866              || strcasecmp(key, "T") == 0   ) {
867         cfg->forced_mimetype = apr_pstrdup(p, val);
868         ap_str_tolower(cfg->forced_mimetype);
869     }
870     else if (   strcasecmp(key, "env") == 0
871              || strcasecmp(key, "E") == 0   ) {
872         for (i = 0; (cfg->env[i] != NULL) && (i < MAX_ENV_FLAGS); i++)
873             ;
874         if (i < MAX_ENV_FLAGS) {
875             cfg->env[i] = apr_pstrdup(p, val);
876             cfg->env[i+1] = NULL;
877         }
878         else {
879             return "RewriteRule: too many environment flags 'E'";
880         }
881     }
882     else if (   strcasecmp(key, "nosubreq") == 0
883              || strcasecmp(key, "NS") == 0      ) {
884         cfg->flags |= RULEFLAG_IGNOREONSUBREQ;
885     }
886     else if (   strcasecmp(key, "proxy") == 0
887              || strcasecmp(key, "P") == 0      ) {
888         cfg->flags |= RULEFLAG_PROXY;
889     }
890     else if (   strcasecmp(key, "passthrough") == 0
891              || strcasecmp(key, "PT") == 0      ) {
892         cfg->flags |= RULEFLAG_PASSTHROUGH;
893     }
894     else if (   strcasecmp(key, "skip") == 0
895              || strcasecmp(key, "S") == 0   ) {
896         cfg->skip = atoi(val);
897     }
898     else if (   strcasecmp(key, "forbidden") == 0
899              || strcasecmp(key, "F") == 0   ) {
900         cfg->flags |= RULEFLAG_FORBIDDEN;
901     }
902     else if (   strcasecmp(key, "gone") == 0
903              || strcasecmp(key, "G") == 0   ) {
904         cfg->flags |= RULEFLAG_GONE;
905     }
906     else if (   strcasecmp(key, "qsappend") == 0
907              || strcasecmp(key, "QSA") == 0   ) {
908         cfg->flags |= RULEFLAG_QSAPPEND;
909     }
910     else if (   strcasecmp(key, "nocase") == 0
911              || strcasecmp(key, "NC") == 0    ) {
912         cfg->flags |= RULEFLAG_NOCASE;
913     }
914     else {
915         return apr_pstrcat(p, "RewriteRule: unknown flag '", key, "'", NULL);
916     }
917     return NULL;
918 }
919
920
921 /*
922 **
923 **  Global Module Initialization
924 **  [called from read_config() after all
925 **  config commands were already called]
926 **
927 */
928
929 static void init_module(apr_pool_t *p,
930                         apr_pool_t *plog,
931                         apr_pool_t *ptemp,
932                         server_rec *s)
933 {
934     apr_status_t rv;
935     void *data;
936     int first_time = 0;
937     const char *userdata_key = "rewrite_init_module";
938
939     apr_get_userdata(&data, userdata_key, s->process->pool);
940     if (!data) {
941         first_time = 1;
942         apr_set_userdata((const void *)1, userdata_key,
943                          apr_null_cleanup, s->process->pool);
944     }
945
946     /* check if proxy module is available */
947     proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL);
948
949     /* create the rewriting lockfiles in the parent */
950     if ((rv = apr_create_lock (&rewrite_log_lock, APR_MUTEX, APR_LOCKALL,
951                                NULL, p)) != APR_SUCCESS) {
952         ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
953                      "mod_rewrite: could not create rewrite_log_lock");
954         exit(1);
955     }
956
957     rewritelock_create(s, p);
958     apr_register_cleanup(p, (void *)s, rewritelock_remove, apr_null_cleanup);
959
960     /* step through the servers and
961      * - open each rewriting logfile
962      * - open the RewriteMap prg:xxx programs
963      */
964     for (; s; s = s->next) {
965         open_rewritelog(s, p);
966         if (!first_time)
967            run_rewritemap_programs(s, p);
968     }
969 }
970
971
972 /*
973 **
974 **  Per-Child Module Initialization
975 **  [called after a child process is spawned]
976 **
977 */
978
979 static void init_child(apr_pool_t *p, server_rec *s)
980 {
981     apr_status_t rv;
982
983     if (lockname != NULL && *(lockname) != '\0')
984     {
985         rv = apr_child_init_lock (&rewrite_mapr_lock, lockname, p);
986         if (rv != APR_SUCCESS) {
987             ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
988                          "mod_rewrite: could not init rewrite_mapr_lock "
989                          "in child");
990         }
991     }
992
993     /* create the lookup cache */
994     cachep = init_cache(p);
995 }
996
997
998 /*
999 ** +-------------------------------------------------------+
1000 ** |                                                       |
1001 ** |                     runtime hooks
1002 ** |                                                       |
1003 ** +-------------------------------------------------------+
1004 */
1005
1006 /*
1007 **
1008 **  URI-to-filename hook
1009 **
1010 **  [used for the rewriting engine triggered by
1011 **  the per-server 'RewriteRule' directives]
1012 **
1013 */
1014
1015 static int hook_uri2file(request_rec *r)
1016 {
1017     void *sconf;
1018     rewrite_server_conf *conf;
1019     const char *var;
1020     const char *thisserver;
1021     char *thisport;
1022     const char *thisurl;
1023     char buf[512];
1024     char docroot[512];
1025     char *cp, *cp2;
1026     const char *ccp;
1027     apr_finfo_t finfo;
1028     unsigned int port;
1029     int n;
1030     int l;
1031
1032     /*
1033      *  retrieve the config structures
1034      */
1035     sconf = r->server->module_config;
1036     conf  = (rewrite_server_conf *)ap_get_module_config(sconf,
1037                                                         &rewrite_module);
1038
1039     /*
1040      *  only do something under runtime if the engine is really enabled,
1041      *  else return immediately!
1042      */
1043     if (conf->state == ENGINE_DISABLED) {
1044         return DECLINED;
1045     }
1046
1047     /*
1048      *  check for the ugly API case of a virtual host section where no
1049      *  mod_rewrite directives exists. In this situation we became no chance
1050      *  by the API to setup our default per-server config so we have to
1051      *  on-the-fly assume we have the default config. But because the default
1052      *  config has a disabled rewriting engine we are lucky because can
1053      *  just stop operating now.
1054      */
1055     if (conf->server != r->server) {
1056         return DECLINED;
1057     }
1058
1059     /*
1060      *  add the SCRIPT_URL variable to the env. this is a bit complicated
1061      *  due to the fact that apache uses subrequests and internal redirects
1062      */
1063
1064     if (r->main == NULL) {
1065          var = apr_pstrcat(r->pool, "REDIRECT_", ENVVAR_SCRIPT_URL, NULL);
1066          var = apr_table_get(r->subprocess_env, var);
1067          if (var == NULL) {
1068              apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, r->uri);
1069          }
1070          else {
1071              apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
1072          }
1073     }
1074     else {
1075          var = apr_table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL);
1076          apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
1077     }
1078
1079     /*
1080      *  create the SCRIPT_URI variable for the env
1081      */
1082
1083     /* add the canonical URI of this URL */
1084     thisserver = ap_get_server_name(r);
1085     port = ap_get_server_port(r);
1086     if (ap_is_default_port(port, r)) {
1087         thisport = "";
1088     }
1089     else {
1090         apr_snprintf(buf, sizeof(buf), ":%u", port);
1091         thisport = buf;
1092     }
1093     thisurl = apr_table_get(r->subprocess_env, ENVVAR_SCRIPT_URL);
1094
1095     /* set the variable */
1096     var = apr_pstrcat(r->pool, ap_http_method(r), "://", thisserver, thisport,
1097                      thisurl, NULL);
1098     apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URI, var);
1099
1100     /* if filename was not initially set,
1101      * we start with the requested URI
1102      */
1103     if (r->filename == NULL) {
1104         r->filename = apr_pstrdup(r->pool, r->uri);
1105         rewritelog(r, 2, "init rewrite engine with requested uri %s",
1106                    r->filename);
1107     }
1108
1109     /*
1110      *  now apply the rules ...
1111      */
1112     if (apply_rewrite_list(r, conf->rewriterules, NULL)) {
1113
1114         if (strlen(r->filename) > 6 &&
1115             strncmp(r->filename, "proxy:", 6) == 0) {
1116             /* it should be go on as an internal proxy request */
1117
1118             /* check if the proxy module is enabled, so
1119              * we can actually use it!
1120              */
1121             if (!proxy_available) {
1122                 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1123                              "attempt to make remote request from mod_rewrite "
1124                              "without proxy enabled: %s", r->filename);
1125                 return HTTP_FORBIDDEN;
1126             }
1127
1128             /* make sure the QUERY_STRING and
1129              * PATH_INFO parts get incorporated
1130              */
1131             if (r->path_info != NULL) {
1132                 r->filename = apr_pstrcat(r->pool, r->filename,
1133                                          r->path_info, NULL);
1134             }
1135             if (r->args != NULL &&
1136                 r->uri == r->unparsed_uri) {
1137                 /* see proxy_http:proxy_http_canon() */
1138                 r->filename = apr_pstrcat(r->pool, r->filename,
1139                                          "?", r->args, NULL);
1140             }
1141
1142             /* now make sure the request gets handled by the proxy handler */
1143             r->proxyreq = 1;
1144             r->handler  = "proxy-server";
1145
1146             rewritelog(r, 1, "go-ahead with proxy request %s [OK]",
1147                        r->filename);
1148             return OK;
1149         }
1150         else if (is_absolute_uri(r->filename)) {
1151             /* it was finally rewritten to a remote URL */
1152
1153             /* skip 'scheme:' */
1154             for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1155                 ;
1156             /* skip '://' */
1157             cp += 3;
1158             /* skip host part */
1159             for ( ; *cp != '/' && *cp != '\0'; cp++)
1160                 ;
1161             if (*cp != '\0') {
1162                 rewritelog(r, 1, "escaping %s for redirect", r->filename);
1163                 cp2 = ap_escape_uri(r->pool, cp);
1164                 *cp = '\0';
1165                 r->filename = apr_pstrcat(r->pool, r->filename, cp2, NULL);
1166             }
1167
1168             /* append the QUERY_STRING part */
1169             if (r->args != NULL) {
1170                 r->filename = apr_pstrcat(r->pool, r->filename, "?", 
1171                                          ap_escape_uri(r->pool, r->args), NULL);
1172             }
1173
1174             /* determine HTTP redirect response code */
1175             if (ap_is_HTTP_REDIRECT(r->status)) {
1176                 n = r->status;
1177                 r->status = HTTP_OK; /* make Apache kernel happy */
1178             }
1179             else {
1180                 n = HTTP_MOVED_TEMPORARILY;
1181             }
1182
1183             /* now do the redirection */
1184             apr_table_setn(r->headers_out, "Location", r->filename);
1185             rewritelog(r, 1, "redirect to %s [REDIRECT/%d]", r->filename, n);
1186             return n;
1187         }
1188         else if (strlen(r->filename) > 10 &&
1189                  strncmp(r->filename, "forbidden:", 10) == 0) {
1190             /* This URLs is forced to be forbidden for the requester */
1191             return HTTP_FORBIDDEN;
1192         }
1193         else if (strlen(r->filename) > 5 &&
1194                  strncmp(r->filename, "gone:", 5) == 0) {
1195             /* This URLs is forced to be gone */
1196             return HTTP_GONE;
1197         }
1198         else if (strlen(r->filename) > 12 &&
1199                  strncmp(r->filename, "passthrough:", 12) == 0) {
1200             /*
1201              * Hack because of underpowered API: passing the current
1202              * rewritten filename through to other URL-to-filename handlers
1203              * just as it were the requested URL. This is to enable
1204              * post-processing by mod_alias, etc.  which always act on
1205              * r->uri! The difference here is: We do not try to
1206              * add the document root
1207              */
1208             r->uri = apr_pstrdup(r->pool, r->filename+12);
1209             return DECLINED;
1210         }
1211         else {
1212             /* it was finally rewritten to a local path */
1213
1214             /* expand "/~user" prefix */
1215 #if APR_HAS_USER
1216             r->filename = expand_tildepaths(r, r->filename);
1217 #endif
1218             rewritelog(r, 2, "local path result: %s", r->filename);
1219
1220             /* the filename has to start with a slash! */
1221             if (r->filename[0] != '/') {
1222                 return HTTP_BAD_REQUEST;
1223             }
1224
1225             /* if there is no valid prefix, we have
1226              * to emulate the translator from the core and
1227              * prefix the filename with document_root
1228              *
1229              * NOTICE:
1230              * We cannot leave out the prefix_stat because
1231              * - when we always prefix with document_root
1232              *   then no absolute path can be created, e.g. via
1233              *   emulating a ScriptAlias directive, etc.
1234              * - when we always NOT prefix with document_root
1235              *   then the files under document_root have to
1236              *   be references directly and document_root
1237              *   gets never used and will be a dummy parameter -
1238              *   this is also bad
1239              *
1240              * BUT:
1241              * Under real Unix systems this is no problem,
1242              * because we only do stat() on the first directory
1243              * and this gets cached by the kernel for along time!
1244              */
1245             n = prefix_stat(r->filename, &finfo);
1246             if (n == 0) {
1247                 if ((ccp = ap_document_root(r)) != NULL) {
1248                     l = apr_cpystrn(docroot, ccp, sizeof(docroot)) - docroot;
1249
1250                     /* always NOT have a trailing slash */
1251                     if (docroot[l-1] == '/') {
1252                         docroot[l-1] = '\0';
1253                     }
1254                     if (r->server->path
1255                         && !strncmp(r->filename, r->server->path,
1256                                     r->server->pathlen)) {
1257                         r->filename = apr_pstrcat(r->pool, docroot,
1258                                                  (r->filename +
1259                                                   r->server->pathlen), NULL);
1260                     }
1261                     else {
1262                         r->filename = apr_pstrcat(r->pool, docroot, 
1263                                                  r->filename, NULL);
1264                     }
1265                     rewritelog(r, 2, "prefixed with document_root to %s",
1266                                r->filename);
1267                 }
1268             }
1269
1270             rewritelog(r, 1, "go-ahead with %s [OK]", r->filename);
1271             return OK;
1272         }
1273     }
1274     else {
1275         rewritelog(r, 1, "pass through %s", r->filename);
1276         return DECLINED;
1277     }
1278 }
1279
1280
1281 /*
1282 **
1283 **  MIME-type hook
1284 **
1285 **  [used to support the forced-MIME-type feature]
1286 **
1287 */
1288
1289 static int hook_mimetype(request_rec *r)
1290 {
1291     const char *t;
1292
1293     /* now check if we have to force a MIME-type */
1294     t = apr_table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR);
1295     if (t == NULL) {
1296         return DECLINED;
1297     }
1298     else {
1299         rewritelog(r, 1, "force filename %s to have MIME-type '%s'",
1300                    r->filename, t);
1301         r->content_type = t;
1302         return OK;
1303     }
1304 }
1305
1306
1307 /*
1308 **
1309 **  Fixup hook
1310 **
1311 **  [used for the rewriting engine triggered by
1312 **  the per-directory 'RewriteRule' directives]
1313 **
1314 */
1315
1316 static int hook_fixup(request_rec *r)
1317 {
1318     rewrite_perdir_conf *dconf;
1319     char *cp;
1320     char *cp2;
1321     const char *ccp;
1322     char *prefix;
1323     int l;
1324     int n;
1325     char *ofilename;
1326
1327     dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
1328                                                         &rewrite_module);
1329
1330     /* if there is no per-dir config we return immediately */
1331     if (dconf == NULL) {
1332         return DECLINED;
1333     }
1334
1335     /* we shouldn't do anything in subrequests */
1336     if (r->main != NULL) {
1337         return DECLINED;
1338     }
1339
1340     /* if there are no real (i.e. no RewriteRule directives!)
1341        per-dir config of us, we return also immediately */
1342     if (dconf->directory == NULL) {
1343         return DECLINED;
1344     }
1345
1346     /*
1347      *  only do something under runtime if the engine is really enabled,
1348      *  for this directory, else return immediately!
1349      */
1350     if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) {
1351         /* FollowSymLinks is mandatory! */
1352         ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1353                      "Options FollowSymLinks or SymLinksIfOwnerMatch is off "
1354                      "which implies that RewriteRule directive is forbidden: "
1355                      "%s", r->filename);
1356         return HTTP_FORBIDDEN;
1357     }
1358     else {
1359         /* FollowSymLinks is given, but the user can
1360          * still turn off the rewriting engine
1361          */
1362         if (dconf->state == ENGINE_DISABLED) {
1363             return DECLINED;
1364         }
1365     }
1366
1367     /*
1368      *  remember the current filename before rewriting for later check
1369      *  to prevent deadlooping because of internal redirects
1370      *  on final URL/filename which can be equal to the inital one.
1371      */
1372     ofilename = r->filename;
1373
1374     /*
1375      *  now apply the rules ...
1376      */
1377     if (apply_rewrite_list(r, dconf->rewriterules, dconf->directory)) {
1378
1379         if (strlen(r->filename) > 6 &&
1380             strncmp(r->filename, "proxy:", 6) == 0) {
1381             /* it should go on as an internal proxy request */
1382
1383             /* make sure the QUERY_STRING and
1384              * PATH_INFO parts get incorporated
1385              * (r->path_info was already appended by the
1386              * rewriting engine because of the per-dir context!)
1387              */
1388             if (r->args != NULL) {
1389                 r->filename = apr_pstrcat(r->pool, r->filename,
1390                                          "?", r->args, NULL);
1391             }
1392
1393             /* now make sure the request gets handled by the proxy handler */
1394             r->proxyreq = 1;
1395             r->handler  = "proxy-server";
1396
1397             rewritelog(r, 1, "[per-dir %s] go-ahead with proxy request "
1398                        "%s [OK]", dconf->directory, r->filename);
1399             return OK;
1400         }
1401         else if (is_absolute_uri(r->filename)) {
1402             /* it was finally rewritten to a remote URL */
1403
1404             /* because we are in a per-dir context
1405              * first try to replace the directory with its base-URL
1406              * if there is a base-URL available
1407              */
1408             if (dconf->baseurl != NULL) {
1409                 /* skip 'scheme:' */
1410                 for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1411                     ;
1412                 /* skip '://' */
1413                 cp += 3;
1414                 if ((cp = strchr(cp, '/')) != NULL) {
1415                     rewritelog(r, 2,
1416                                "[per-dir %s] trying to replace "
1417                                "prefix %s with %s",
1418                                dconf->directory, dconf->directory,
1419                                dconf->baseurl);
1420                     cp2 = subst_prefix_path(r, cp, dconf->directory,
1421                                             dconf->baseurl);
1422                     if (strcmp(cp2, cp) != 0) {
1423                         *cp = '\0';
1424                         r->filename = apr_pstrcat(r->pool, r->filename,
1425                                                  cp2, NULL);
1426                     }
1427                 }
1428             }
1429
1430             /* now prepare the redirect... */
1431
1432             /* skip 'scheme:' */
1433             for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1434                 ;
1435             /* skip '://' */
1436             cp += 3;
1437             /* skip host part */
1438             for ( ; *cp != '/' && *cp != '\0'; cp++)
1439                 ;
1440             if (*cp != '\0') {
1441                 rewritelog(r, 1, "[per-dir %s] escaping %s for redirect",
1442                            dconf->directory, r->filename);
1443                 cp2 = ap_escape_uri(r->pool, cp);
1444                 *cp = '\0';
1445                 r->filename = apr_pstrcat(r->pool, r->filename, cp2, NULL);
1446             }
1447
1448             /* append the QUERY_STRING part */
1449             if (r->args != NULL) {
1450                 r->filename = apr_pstrcat(r->pool, r->filename, "?", 
1451                                          ap_escape_uri(r->pool, r->args), NULL);
1452             }
1453
1454             /* determine HTTP redirect response code */
1455             if (ap_is_HTTP_REDIRECT(r->status)) {
1456                 n = r->status;
1457                 r->status = HTTP_OK; /* make Apache kernel happy */
1458             }
1459             else {
1460                 n = HTTP_MOVED_TEMPORARILY;
1461             }
1462
1463             /* now do the redirection */
1464             apr_table_setn(r->headers_out, "Location", r->filename);
1465             rewritelog(r, 1, "[per-dir %s] redirect to %s [REDIRECT/%d]",
1466                        dconf->directory, r->filename, n);
1467             return n;
1468         }
1469         else if (strlen(r->filename) > 10 &&
1470                  strncmp(r->filename, "forbidden:", 10) == 0) {
1471             /* This URL is forced to be forbidden for the requester */
1472             return HTTP_FORBIDDEN;
1473         }
1474         else if (strlen(r->filename) > 5 &&
1475                  strncmp(r->filename, "gone:", 5) == 0) {
1476             /* This URL is forced to be gone */
1477             return HTTP_GONE;
1478         }
1479         else {
1480             /* it was finally rewritten to a local path */
1481
1482             /* if someone used the PASSTHROUGH flag in per-dir
1483              * context we just ignore it. It is only useful
1484              * in per-server context
1485              */
1486             if (strlen(r->filename) > 12 &&
1487                 strncmp(r->filename, "passthrough:", 12) == 0) {
1488                 r->filename = apr_pstrdup(r->pool, r->filename+12);
1489             }
1490
1491             /* the filename has to start with a slash! */
1492             if (r->filename[0] != '/') {
1493                 return HTTP_BAD_REQUEST;
1494             }
1495
1496             /* Check for deadlooping:
1497              * At this point we KNOW that at least one rewriting
1498              * rule was applied, but when the resulting URL is
1499              * the same as the initial URL, we are not allowed to
1500              * use the following internal redirection stuff because
1501              * this would lead to a deadloop.
1502              */
1503             if (strcmp(r->filename, ofilename) == 0) {
1504                 rewritelog(r, 1, "[per-dir %s] initial URL equal rewritten "
1505                            "URL: %s [IGNORING REWRITE]",
1506                            dconf->directory, r->filename);
1507                 return OK;
1508             }
1509
1510             /* if there is a valid base-URL then substitute
1511              * the per-dir prefix with this base-URL if the
1512              * current filename still is inside this per-dir
1513              * context. If not then treat the result as a
1514              * plain URL
1515              */
1516             if (dconf->baseurl != NULL) {
1517                 rewritelog(r, 2,
1518                            "[per-dir %s] trying to replace prefix %s with %s",
1519                            dconf->directory, dconf->directory, dconf->baseurl);
1520                 r->filename = subst_prefix_path(r, r->filename,
1521                                                 dconf->directory,
1522                                                 dconf->baseurl);
1523             }
1524             else {
1525                 /* if no explicit base-URL exists we assume
1526                  * that the directory prefix is also a valid URL
1527                  * for this webserver and only try to remove the
1528                  * document_root if it is prefix
1529                  */
1530                 if ((ccp = ap_document_root(r)) != NULL) {
1531                     prefix = apr_pstrdup(r->pool, ccp);
1532                     /* always NOT have a trailing slash */
1533                     l = strlen(prefix);
1534                     if (prefix[l-1] == '/') {
1535                         prefix[l-1] = '\0';
1536                         l--;
1537                     }
1538                     if (strncmp(r->filename, prefix, l) == 0) {
1539                         rewritelog(r, 2,
1540                                    "[per-dir %s] strip document_root "
1541                                    "prefix: %s -> %s",
1542                                    dconf->directory, r->filename,
1543                                    r->filename+l);
1544                         r->filename = apr_pstrdup(r->pool, r->filename+l);
1545                     }
1546                 }
1547             }
1548
1549             /* now initiate the internal redirect */
1550             rewritelog(r, 1, "[per-dir %s] internal redirect with %s "
1551                        "[INTERNAL REDIRECT]", dconf->directory, r->filename);
1552             r->filename = apr_pstrcat(r->pool, "redirect:", r->filename, NULL);
1553             r->handler = "redirect-handler";
1554             return OK;
1555         }
1556     }
1557     else {
1558         rewritelog(r, 1, "[per-dir %s] pass through %s", 
1559                    dconf->directory, r->filename);
1560         return DECLINED;
1561     }
1562 }
1563
1564
1565 /*
1566 **
1567 **  Content-Handlers
1568 **
1569 **  [used for redirect support]
1570 **
1571 */
1572
1573 static int handler_redirect(request_rec *r)
1574 {
1575     if (strcmp(r->handler, "redirect-handler")) {
1576         return DECLINED;
1577     }
1578
1579     /* just make sure that we are really meant! */
1580     if (strncmp(r->filename, "redirect:", 9) != 0) {
1581         return DECLINED;
1582     }
1583
1584     /* now do the internal redirect */
1585     ap_internal_redirect(apr_pstrcat(r->pool, r->filename+9,
1586                                     r->args ? "?" : NULL, r->args, NULL), r);
1587
1588     /* and return gracefully */
1589     return OK;
1590 }
1591
1592
1593 /*
1594 ** +-------------------------------------------------------+
1595 ** |                                                       |
1596 ** |                  the rewriting engine
1597 ** |                                                       |
1598 ** +-------------------------------------------------------+
1599 */
1600
1601 /*
1602  *  Apply a complete rule set,
1603  *  i.e. a list of rewrite rules
1604  */
1605 static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules,
1606                               char *perdir)
1607 {
1608     rewriterule_entry *entries;
1609     rewriterule_entry *p;
1610     int i;
1611     int changed;
1612     int rc;
1613     int s;
1614
1615     /*
1616      *  Iterate over all existing rules
1617      */
1618     entries = (rewriterule_entry *)rewriterules->elts;
1619     changed = 0;
1620     loop:
1621     for (i = 0; i < rewriterules->nelts; i++) {
1622         p = &entries[i];
1623
1624         /*
1625          *  Ignore this rule on subrequests if we are explicitly
1626          *  asked to do so or this is a proxy-throughput or a
1627          *  forced redirect rule.
1628          */
1629         if (r->main != NULL &&
1630             (p->flags & RULEFLAG_IGNOREONSUBREQ ||
1631              p->flags & RULEFLAG_PROXY          ||
1632              p->flags & RULEFLAG_FORCEREDIRECT    )) {
1633             continue;
1634         }
1635
1636         /*
1637          *  Apply the current rule.
1638          */
1639         rc = apply_rewrite_rule(r, p, perdir);
1640         if (rc) {
1641             /*
1642              *  Indicate a change if this was not a match-only rule.
1643              */
1644             if (rc != 2) {
1645                 changed = 1;
1646             }
1647
1648             /*
1649              *  Pass-Through Feature (`RewriteRule .. .. [PT]'):
1650              *  Because the Apache 1.x API is very limited we
1651              *  need this hack to pass the rewritten URL to other
1652              *  modules like mod_alias, mod_userdir, etc.
1653              */
1654             if (p->flags & RULEFLAG_PASSTHROUGH) {
1655                 rewritelog(r, 2, "forcing '%s' to get passed through "
1656                            "to next API URI-to-filename handler", r->filename);
1657                 r->filename = apr_pstrcat(r->pool, "passthrough:",
1658                                          r->filename, NULL);
1659                 changed = 1;
1660                 break;
1661             }
1662
1663             /*
1664              *  Rule has the "forbidden" flag set which means that
1665              *  we stop processing and indicate this to the caller.
1666              */
1667             if (p->flags & RULEFLAG_FORBIDDEN) {
1668                 rewritelog(r, 2, "forcing '%s' to be forbidden", r->filename);
1669                 r->filename = apr_pstrcat(r->pool, "forbidden:",
1670                                          r->filename, NULL);
1671                 changed = 1;
1672                 break;
1673             }
1674
1675             /*
1676              *  Rule has the "gone" flag set which means that
1677              *  we stop processing and indicate this to the caller.
1678              */
1679             if (p->flags & RULEFLAG_GONE) {
1680                 rewritelog(r, 2, "forcing '%s' to be gone", r->filename);
1681                 r->filename = apr_pstrcat(r->pool, "gone:", r->filename, NULL);
1682                 changed = 1;
1683                 break;
1684             }
1685
1686             /*
1687              *  Stop processing also on proxy pass-through and
1688              *  last-rule and new-round flags.
1689              */
1690             if (p->flags & RULEFLAG_PROXY) {
1691                 break;
1692             }
1693             if (p->flags & RULEFLAG_LASTRULE) {
1694                 break;
1695             }
1696
1697             /*
1698              *  On "new-round" flag we just start from the top of
1699              *  the rewriting ruleset again.
1700              */
1701             if (p->flags & RULEFLAG_NEWROUND) {
1702                 goto loop;
1703             }
1704
1705             /*
1706              *  If we are forced to skip N next rules, do it now.
1707              */
1708             if (p->skip > 0) {
1709                 s = p->skip;
1710                 while (   i < rewriterules->nelts
1711                        && s > 0) {
1712                     i++;
1713                     p = &entries[i];
1714                     s--;
1715                 }
1716             }
1717         }
1718         else {
1719             /*
1720              *  If current rule is chained with next rule(s),
1721              *  skip all this next rule(s)
1722              */
1723             while (   i < rewriterules->nelts
1724                    && p->flags & RULEFLAG_CHAIN) {
1725                 i++;
1726                 p = &entries[i];
1727             }
1728         }
1729     }
1730     return changed;
1731 }
1732
1733 /*
1734  *  Apply a single(!) rewrite rule
1735  */
1736 static int apply_rewrite_rule(request_rec *r, rewriterule_entry *p,
1737                               char *perdir)
1738 {
1739     char *uri;
1740     char *output;
1741     const char *vary;
1742     char newuri[MAX_STRING_LEN];
1743     regex_t *regexp;
1744     regmatch_t regmatch[MAX_NMATCH];
1745     backrefinfo *briRR = NULL;
1746     backrefinfo *briRC = NULL;
1747     int prefixstrip;
1748     int failed;
1749     apr_array_header_t *rewriteconds;
1750     rewritecond_entry *conds;
1751     rewritecond_entry *c;
1752     int i;
1753     int rc;
1754
1755     /*
1756      *  Initialisation
1757      */
1758     uri     = r->filename;
1759     regexp  = p->regexp;
1760     output  = p->output;
1761
1762     /*
1763      *  Add (perhaps splitted away) PATH_INFO postfix to URL to
1764      *  make sure we really match against the complete URL.
1765      */
1766     if (perdir != NULL && r->path_info != NULL && r->path_info[0] != '\0') {
1767         rewritelog(r, 3, "[per-dir %s] add path info postfix: %s -> %s%s",
1768                    perdir, uri, uri, r->path_info);
1769         uri = apr_pstrcat(r->pool, uri, r->path_info, NULL);
1770     }
1771
1772     /*
1773      *  On per-directory context (.htaccess) strip the location
1774      *  prefix from the URL to make sure patterns apply only to
1775      *  the local part.  Additionally indicate this special
1776      *  threatment in the logfile.
1777      */
1778     prefixstrip = 0;
1779     if (perdir != NULL) {
1780         if (   strlen(uri) >= strlen(perdir)
1781             && strncmp(uri, perdir, strlen(perdir)) == 0) {
1782             rewritelog(r, 3, "[per-dir %s] strip per-dir prefix: %s -> %s",
1783                        perdir, uri, uri+strlen(perdir));
1784             uri = uri+strlen(perdir);
1785             prefixstrip = 1;
1786         }
1787     }
1788
1789     /*
1790      *  Try to match the URI against the RewriteRule pattern
1791      *  and exit immeddiately if it didn't apply.
1792      */
1793     if (perdir == NULL) {
1794         rewritelog(r, 3, "applying pattern '%s' to uri '%s'",
1795                    p->pattern, uri);
1796     }
1797     else {
1798         rewritelog(r, 3, "[per-dir %s] applying pattern '%s' to uri '%s'",
1799                    perdir, p->pattern, uri);
1800     }
1801     rc = (ap_regexec(regexp, uri, regexp->re_nsub+1, regmatch, 0) == 0);
1802     if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) ||
1803            (!rc &&  (p->flags & RULEFLAG_NOTMATCH))   ) ) {
1804         return 0;
1805     }
1806
1807     /*
1808      *  Else create the RewriteRule `regsubinfo' structure which
1809      *  holds the substitution information.
1810      */
1811     briRR = (backrefinfo *)apr_palloc(r->pool, sizeof(backrefinfo));
1812     if (!rc && (p->flags & RULEFLAG_NOTMATCH)) {
1813         /*  empty info on negative patterns  */
1814         briRR->source = "";
1815         briRR->nsub   = 0;
1816     }
1817     else {
1818         briRR->source = apr_pstrdup(r->pool, uri);
1819         briRR->nsub   = regexp->re_nsub;
1820         memcpy((void *)(briRR->regmatch), (void *)(regmatch),
1821                sizeof(regmatch));
1822     }
1823
1824     /*
1825      *  Initiallally create the RewriteCond backrefinfo with
1826      *  empty backrefinfo, i.e. not subst parts
1827      *  (this one is adjusted inside apply_rewrite_cond() later!!)
1828      */
1829     briRC = (backrefinfo *)apr_pcalloc(r->pool, sizeof(backrefinfo));
1830     briRC->source = "";
1831     briRC->nsub   = 0;
1832
1833     /*
1834      *  Ok, we already know the pattern has matched, but we now
1835      *  additionally have to check for all existing preconditions
1836      *  (RewriteCond) which have to be also true. We do this at
1837      *  this very late stage to avoid unnessesary checks which
1838      *  would slow down the rewriting engine!!
1839      */
1840     rewriteconds = p->rewriteconds;
1841     conds = (rewritecond_entry *)rewriteconds->elts;
1842     failed = 0;
1843     for (i = 0; i < rewriteconds->nelts; i++) {
1844         c = &conds[i];
1845         rc = apply_rewrite_cond(r, c, perdir, briRR, briRC);
1846         if (c->flags & CONDFLAG_ORNEXT) {
1847             /*
1848              *  The "OR" case
1849              */
1850             if (rc == 0) {
1851                 /*  One condition is false, but another can be
1852                  *  still true, so we have to continue...
1853                  */
1854                 apr_table_unset(r->notes, VARY_KEY_THIS);
1855                 continue;
1856             }
1857             else {
1858                 /*  One true condition is enough in "or" case, so
1859                  *  skip the other conditions which are "ornext"
1860                  *  chained
1861                  */
1862                 while (   i < rewriteconds->nelts
1863                        && c->flags & CONDFLAG_ORNEXT) {
1864                     i++;
1865                     c = &conds[i];
1866                 }
1867                 continue;
1868             }
1869         }
1870         else {
1871             /*
1872              *  The "AND" case, i.e. no "or" flag,
1873              *  so a single failure means total failure.
1874              */
1875             if (rc == 0) {
1876                 failed = 1;
1877                 break;
1878             }
1879         }
1880         vary = apr_table_get(r->notes, VARY_KEY_THIS);
1881         if (vary != NULL) {
1882             apr_table_merge(r->notes, VARY_KEY, vary);
1883             apr_table_unset(r->notes, VARY_KEY_THIS);
1884         }
1885     }
1886     /*  if any condition fails the complete rule fails  */
1887     if (failed) {
1888         apr_table_unset(r->notes, VARY_KEY);
1889         apr_table_unset(r->notes, VARY_KEY_THIS);
1890         return 0;
1891     }
1892
1893     /*
1894      * Regardless of what we do next, we've found a match.  Check to see
1895      * if any of the request header fields were involved, and add them
1896      * to the Vary field of the response.
1897      */
1898     if ((vary = apr_table_get(r->notes, VARY_KEY)) != NULL) {
1899         apr_table_merge(r->headers_out, "Vary", vary);
1900         apr_table_unset(r->notes, VARY_KEY);
1901     }
1902
1903     /*
1904      *  If this is a pure matching rule (`RewriteRule <pat> -')
1905      *  we stop processing and return immediately. The only thing
1906      *  we have not to forget are the environment variables
1907      *  (`RewriteRule <pat> - [E=...]')
1908      */
1909     if (strcmp(output, "-") == 0) {
1910         do_expand_env(r, p->env, briRR, briRC);
1911         if (p->forced_mimetype != NULL) {
1912             if (perdir == NULL) {
1913                 /* In the per-server context we can force the MIME-type
1914                  * the correct way by notifying our MIME-type hook handler
1915                  * to do the job when the MIME-type API stage is reached.
1916                  */
1917                 rewritelog(r, 2, "remember %s to have MIME-type '%s'",
1918                            r->filename, p->forced_mimetype);
1919                 apr_table_setn(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
1920                               p->forced_mimetype);
1921             }
1922             else {
1923                 /* In per-directory context we operate in the Fixup API hook
1924                  * which is after the MIME-type hook, so our MIME-type handler
1925                  * has no chance to set r->content_type. And because we are
1926                  * in the situation where no substitution takes place no
1927                  * sub-request will happen (which could solve the
1928                  * restriction). As a workaround we do it ourself now
1929                  * immediately although this is not strictly API-conforming.
1930                  * But it's the only chance we have...
1931                  */
1932                 rewritelog(r, 1, "[per-dir %s] force %s to have MIME-type "
1933                            "'%s'", perdir, r->filename, p->forced_mimetype);
1934                 r->content_type = p->forced_mimetype;
1935             }
1936         }
1937         return 2;
1938     }
1939
1940     /*
1941      *  Ok, now we finally know all patterns have matched and
1942      *  that there is something to replace, so we create the
1943      *  substitution URL string in `newuri'.
1944      */
1945     do_expand(r, output, newuri, sizeof(newuri), briRR, briRC);
1946     if (perdir == NULL) {
1947         rewritelog(r, 2, "rewrite %s -> %s", uri, newuri);
1948     }
1949     else {
1950         rewritelog(r, 2, "[per-dir %s] rewrite %s -> %s", perdir, uri, newuri);
1951     }
1952
1953     /*
1954      *  Additionally do expansion for the environment variable
1955      *  strings (`RewriteRule .. .. [E=<string>]').
1956      */
1957     do_expand_env(r, p->env, briRR, briRC);
1958
1959     /*
1960      *  Now replace API's knowledge of the current URI:
1961      *  Replace r->filename with the new URI string and split out
1962      *  an on-the-fly generated QUERY_STRING part into r->args
1963      */
1964     r->filename = apr_pstrdup(r->pool, newuri);
1965     splitout_queryargs(r, p->flags & RULEFLAG_QSAPPEND);
1966
1967     /*
1968      *   Again add the previously stripped per-directory location
1969      *   prefix if the new URI is not a new one for this
1970      *   location, i.e. if it's not starting with either a slash
1971      *   or a fully qualified URL scheme.
1972      */
1973     if (prefixstrip && r->filename[0] != '/'
1974         && !is_absolute_uri(r->filename)) {
1975         rewritelog(r, 3, "[per-dir %s] add per-dir prefix: %s -> %s%s",
1976                    perdir, r->filename, perdir, r->filename);
1977         r->filename = apr_pstrcat(r->pool, perdir, r->filename, NULL);
1978     }
1979
1980     /*
1981      *  If this rule is forced for proxy throughput
1982      *  (`RewriteRule ... ... [P]') then emulate mod_proxy's
1983      *  URL-to-filename handler to be sure mod_proxy is triggered
1984      *  for this URL later in the Apache API. But make sure it is
1985      *  a fully-qualified URL. (If not it is qualified with
1986      *  ourself).
1987      */
1988     if (p->flags & RULEFLAG_PROXY) {
1989         fully_qualify_uri(r);
1990         if (perdir == NULL) {
1991             rewritelog(r, 2, "forcing proxy-throughput with %s", r->filename);
1992         }
1993         else {
1994             rewritelog(r, 2, "[per-dir %s] forcing proxy-throughput with %s",
1995                        perdir, r->filename);
1996         }
1997         r->filename = apr_pstrcat(r->pool, "proxy:", r->filename, NULL);
1998         return 1;
1999     }
2000
2001     /*
2002      *  If this rule is explicitly forced for HTTP redirection
2003      *  (`RewriteRule .. .. [R]') then force an external HTTP
2004      *  redirect. But make sure it is a fully-qualified URL. (If
2005      *  not it is qualified with ourself).
2006      */
2007     if (p->flags & RULEFLAG_FORCEREDIRECT) {
2008         fully_qualify_uri(r);
2009         if (perdir == NULL) {
2010             rewritelog(r, 2,
2011                        "explicitly forcing redirect with %s", r->filename);
2012         }
2013         else {
2014             rewritelog(r, 2,
2015                        "[per-dir %s] explicitly forcing redirect with %s",
2016                        perdir, r->filename);
2017         }
2018         r->status = p->forced_responsecode;
2019         return 1;
2020     }
2021
2022     /*
2023      *  Special Rewriting Feature: Self-Reduction
2024      *  We reduce the URL by stripping a possible
2025      *  http[s]://<ourhost>[:<port>] prefix, i.e. a prefix which
2026      *  corresponds to ourself. This is to simplify rewrite maps
2027      *  and to avoid recursion, etc. When this prefix is not a
2028      *  coincidence then the user has to use [R] explicitly (see
2029      *  above).
2030      */
2031     reduce_uri(r);
2032
2033     /*
2034      *  If this rule is still implicitly forced for HTTP
2035      *  redirection (`RewriteRule .. <scheme>://...') then
2036      *  directly force an external HTTP redirect.
2037      */
2038     if (is_absolute_uri(r->filename)) {
2039         if (perdir == NULL) {
2040             rewritelog(r, 2,
2041                        "implicitly forcing redirect (rc=%d) with %s",
2042                        p->forced_responsecode, r->filename);
2043         }
2044         else {
2045             rewritelog(r, 2, "[per-dir %s] implicitly forcing redirect "
2046                        "(rc=%d) with %s", perdir, p->forced_responsecode,
2047                        r->filename);
2048         }
2049         r->status = p->forced_responsecode;
2050         return 1;
2051     }
2052
2053     /*
2054      *  Now we are sure it is not a fully qualified URL.  But
2055      *  there is still one special case left: A local rewrite in
2056      *  per-directory context, i.e. a substitution URL which does
2057      *  not start with a slash. Here we add again the initially
2058      *  stripped per-directory prefix.
2059      */
2060     if (prefixstrip && r->filename[0] != '/') {
2061         rewritelog(r, 3, "[per-dir %s] add per-dir prefix: %s -> %s%s",
2062                    perdir, r->filename, perdir, r->filename);
2063         r->filename = apr_pstrcat(r->pool, perdir, r->filename, NULL);
2064     }
2065
2066     /*
2067      *  Finally we had to remember if a MIME-type should be
2068      *  forced for this URL (`RewriteRule .. .. [T=<type>]')
2069      *  Later in the API processing phase this is forced by our
2070      *  MIME API-hook function. This time its no problem even for
2071      *  the per-directory context (where the MIME-type hook was
2072      *  already processed) because a sub-request happens ;-)
2073      */
2074     if (p->forced_mimetype != NULL) {
2075         apr_table_setn(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
2076                       p->forced_mimetype);
2077         if (perdir == NULL) {
2078             rewritelog(r, 2, "remember %s to have MIME-type '%s'",
2079                        r->filename, p->forced_mimetype);
2080         }
2081         else {
2082             rewritelog(r, 2,
2083                        "[per-dir %s] remember %s to have MIME-type '%s'",
2084                        perdir, r->filename, p->forced_mimetype);
2085         }
2086     }
2087
2088     /*
2089      *  Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_)
2090      *  But now we're done for this particular rule.
2091      */
2092     return 1;
2093 }
2094
2095 static int apply_rewrite_cond(request_rec *r, rewritecond_entry *p,
2096                               char *perdir, backrefinfo *briRR,
2097                               backrefinfo *briRC)
2098 {
2099     char input[MAX_STRING_LEN];
2100     apr_finfo_t sb;
2101     request_rec *rsub;
2102     regmatch_t regmatch[MAX_NMATCH];
2103     int rc;
2104
2105     /*
2106      *   Construct the string we match against
2107      */
2108
2109     do_expand(r, p->input, input, sizeof(input), briRR, briRC);
2110
2111     /*
2112      *   Apply the patterns
2113      */
2114
2115     rc = 0;
2116     if (strcmp(p->pattern, "-f") == 0) {
2117         if (apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS) {
2118             if (sb.filetype == APR_REG) {
2119                 rc = 1;
2120             }
2121         }
2122     }
2123     else if (strcmp(p->pattern, "-s") == 0) {
2124         if (apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS) {
2125             if ((sb.filetype == APR_REG) && sb.size > 0) {
2126                 rc = 1;
2127             }
2128         }
2129     }
2130     else if (strcmp(p->pattern, "-l") == 0) {
2131 #if !defined(OS2)
2132         if (apr_lstat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS) {
2133             if (sb.filetype == APR_LNK) {
2134                 rc = 1;
2135             }
2136         }
2137 #endif
2138     }
2139     else if (strcmp(p->pattern, "-d") == 0) {
2140         if (apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS) {
2141             if (sb.filetype == APR_DIR) {
2142                 rc = 1;
2143             }
2144         }
2145     }
2146     else if (strcmp(p->pattern, "-U") == 0) {
2147         /* avoid infinite subrequest recursion */
2148         if (strlen(input) > 0 && subreq_ok(r)) {
2149
2150             /* run a URI-based subrequest */
2151             rsub = ap_sub_req_lookup_uri(input, r, NULL);
2152
2153             /* URI exists for any result up to 3xx, redirects allowed */
2154             if (rsub->status < 400)
2155                 rc = 1;
2156
2157             /* log it */
2158             rewritelog(r, 5, "RewriteCond URI (-U) check: "
2159                        "path=%s -> status=%d", input, rsub->status);
2160
2161             /* cleanup by destroying the subrequest */
2162             ap_destroy_sub_req(rsub);
2163         }
2164     }
2165     else if (strcmp(p->pattern, "-F") == 0) {
2166         /* avoid infinite subrequest recursion */
2167         if (strlen(input) > 0 && subreq_ok(r)) {
2168
2169             /* process a file-based subrequest:
2170              * this differs from -U in that no path translation is done.
2171              */
2172             rsub = ap_sub_req_lookup_file(input, r, NULL);
2173
2174             /* file exists for any result up to 2xx, no redirects */
2175             if (rsub->status < 300 &&
2176                 /* double-check that file exists since default result is 200 */
2177                 apr_stat(&sb, rsub->filename, APR_FINFO_MIN, 
2178                          r->pool) == APR_SUCCESS) {
2179                 rc = 1;
2180             }
2181
2182             /* log it */
2183             rewritelog(r, 5, "RewriteCond file (-F) check: path=%s "
2184                        "-> file=%s status=%d", input, rsub->filename, 
2185                        rsub->status);
2186
2187             /* cleanup by destroying the subrequest */
2188             ap_destroy_sub_req(rsub);
2189         }
2190     }
2191     else if (strlen(p->pattern) > 1 && *(p->pattern) == '>') {
2192         rc = (compare_lexicography(input, p->pattern+1) == 1 ? 1 : 0);
2193     }
2194     else if (strlen(p->pattern) > 1 && *(p->pattern) == '<') {
2195         rc = (compare_lexicography(input, p->pattern+1) == -1 ? 1 : 0);
2196     }
2197     else if (strlen(p->pattern) > 1 && *(p->pattern) == '=') {
2198         if (strcmp(p->pattern+1, "\"\"") == 0) {
2199             rc = (*input == '\0');
2200         }
2201         else {
2202             rc = (strcmp(input, p->pattern+1) == 0 ? 1 : 0);
2203         }
2204     }
2205     else {
2206         /* it is really a regexp pattern, so apply it */
2207         rc = (ap_regexec(p->regexp, input,
2208                          p->regexp->re_nsub+1, regmatch,0) == 0);
2209
2210         /* if it isn't a negated pattern and really matched
2211            we update the passed-through regex subst info structure */
2212         if (rc && !(p->flags & CONDFLAG_NOTMATCH)) {
2213             briRC->source = apr_pstrdup(r->pool, input);
2214             briRC->nsub   = p->regexp->re_nsub;
2215             memcpy((void *)(briRC->regmatch), (void *)(regmatch),
2216                    sizeof(regmatch));
2217         }
2218     }
2219
2220     /* if this is a non-matching regexp, just negate the result */
2221     if (p->flags & CONDFLAG_NOTMATCH) {
2222         rc = !rc;
2223     }
2224
2225     rewritelog(r, 4, "RewriteCond: input='%s' pattern='%s%s' => %s",
2226                input, (p->flags & CONDFLAG_NOTMATCH ? "!" : ""),
2227                p->pattern, rc ? "matched" : "not-matched");
2228
2229     /* end just return the result */
2230     return rc;
2231 }
2232
2233
2234 /*
2235 ** +-------------------------------------------------------+
2236 ** |                                                       |
2237 ** |              URL transformation functions
2238 ** |                                                       |
2239 ** +-------------------------------------------------------+
2240 */
2241
2242
2243 /*
2244 **
2245 **  perform all the expansions on the input string
2246 **  leaving the result in the supplied buffer
2247 **
2248 */
2249
2250 static void do_expand(request_rec *r, char *input, char *buffer, int nbuf,
2251                        backrefinfo *briRR, backrefinfo *briRC)
2252 {
2253     char *inp, *outp;
2254     size_t span, space;
2255
2256     /*
2257      * for security reasons this expansion must be perfomed in a
2258      * single pass, otherwise an attacker can arrange for the result
2259      * of an earlier expansion to include expansion specifiers that
2260      * are interpreted by a later expansion, producing results that
2261      * were not intended by the administrator.
2262      */
2263
2264     inp = input;
2265     outp = buffer;
2266     space = nbuf - 1; /* room for '\0' */
2267
2268     for (;;) {
2269         span = strcspn(inp, "$%");
2270         if (span > space) {
2271             span = space;
2272         }
2273         memcpy(outp, inp, span);
2274         inp += span;
2275         outp += span;
2276         space -= span;
2277         if (space == 0 || *inp == '\0') {
2278             break;
2279         }
2280         /* now we have a '$' or a '%' */
2281         if (inp[1] == '{') {
2282             char *endp;
2283             endp = find_closing_bracket(inp+2, '{', '}');
2284             if (endp == NULL) {
2285                 goto skip;
2286             }
2287             /*
2288              * These lookups may be recursive in a very convoluted
2289              * fashion -- see the LA-U and LA-F variable expansion
2290              * prefixes -- so we copy lookup keys to a separate buffer
2291              * rather than adding zero bytes in order to use them in
2292              * place.
2293              */
2294             if (inp[0] == '$') {
2295                 /* ${...} map lookup expansion */
2296                 /*
2297                  * To make rewrite maps useful the lookup key and
2298                  * default values must be expanded, so we make
2299                  * recursive calls to do the work. For security
2300                  * reasons we must never expand a string that includes
2301                  * verbatim data from the network. The recursion here
2302                  * isn't a problem because the result of expansion is
2303                  * only passed to lookup_map() so it cannot be
2304                  * re-expanded, only re-looked-up. Another way of
2305                  * looking at it is that the recursion is entirely
2306                  * driven by the syntax of the nested curly brackets.
2307                  */
2308                 char *map, *key, *dflt, *result;
2309                 char xkey[MAX_STRING_LEN];
2310                 char xdflt[MAX_STRING_LEN];
2311                 key = find_char_in_brackets(inp+2, ':', '{', '}');
2312                 if (key == NULL)
2313                     goto skip;
2314                 map  = apr_pstrndup(r->pool, inp+2, key-inp-2);
2315                 dflt = find_char_in_brackets(key+1, '|', '{', '}');
2316                 if (dflt == NULL) {
2317                     key  = apr_pstrndup(r->pool, key+1, endp-key-1);
2318                     dflt = "";
2319                 } else {
2320                     key  = apr_pstrndup(r->pool, key+1, dflt-key-1);
2321                     dflt = apr_pstrndup(r->pool, dflt+1, endp-dflt-1);
2322                 }
2323                 do_expand(r, key,  xkey,  sizeof(xkey),  briRR, briRC);
2324                 result = lookup_map(r, map, xkey);
2325                 if (result) {
2326                     span = apr_cpystrn(outp, result, space) - outp;
2327                 } else {
2328                     do_expand(r, dflt, xdflt, sizeof(xdflt), briRR, briRC);
2329                     span = apr_cpystrn(outp, xdflt, space) - outp;
2330                 }
2331             }
2332             else if (inp[0] == '%') {
2333                 /* %{...} variable lookup expansion */
2334                 char *var;
2335                 var  = apr_pstrndup(r->pool, inp+2, endp-inp-2);
2336                 span = apr_cpystrn(outp, lookup_variable(r, var), space) - outp;
2337             }
2338             else {
2339                 span = 0;
2340             }
2341             inp = endp+1;
2342             outp += span;
2343             space -= span;
2344             continue;
2345         }
2346         else if (apr_isdigit(inp[1])) {
2347             int n = inp[1] - '0';
2348             backrefinfo *bri = NULL;
2349             if (inp[0] == '$') {
2350                 /* $N RewriteRule regexp backref expansion */
2351                 bri = briRR;
2352             }
2353             else if (inp[0] == '%') {
2354                 /* %N RewriteCond regexp backref expansion */
2355                 bri = briRC;
2356             }
2357             /* see ap_pregsub() in src/main/util.c */
2358             if (bri && n <= bri->nsub &&
2359                 bri->regmatch[n].rm_eo > bri->regmatch[n].rm_so) {
2360                 span = bri->regmatch[n].rm_eo - bri->regmatch[n].rm_so;
2361                 if (span > space) {
2362                     span = space;
2363                 }
2364                 memcpy(outp, bri->source + bri->regmatch[n].rm_so, span);
2365                 outp += span;
2366                 space -= span;
2367             }
2368             inp += 2;
2369             continue;
2370         }
2371     skip:
2372         *outp++ = *inp++;
2373         space--;
2374     }
2375     *outp++ = '\0';
2376 }
2377
2378
2379 /*
2380 **
2381 **  perform all the expansions on the environment variables
2382 **
2383 */
2384
2385 static void do_expand_env(request_rec *r, char *env[],
2386                           backrefinfo *briRR, backrefinfo *briRC)
2387 {
2388     int i;
2389     char buf[MAX_STRING_LEN];
2390
2391     for (i = 0; env[i] != NULL; i++) {
2392         do_expand(r, env[i], buf, sizeof(buf), briRR, briRC);
2393         add_env_variable(r, buf);
2394     }
2395 }
2396
2397
2398 /*
2399 **
2400 **  split out a QUERY_STRING part from
2401 **  the current URI string
2402 **
2403 */
2404
2405 static void splitout_queryargs(request_rec *r, int qsappend)
2406 {
2407     char *q;
2408     char *olduri;
2409
2410     q = strchr(r->filename, '?');
2411     if (q != NULL) {
2412         olduri = apr_pstrdup(r->pool, r->filename);
2413         *q++ = '\0';
2414         if (qsappend) {
2415             r->args = apr_pstrcat(r->pool, q, "&", r->args, NULL);
2416         }
2417         else {
2418             r->args = apr_pstrdup(r->pool, q);
2419         }
2420         if (strlen(r->args) == 0) {
2421             r->args = NULL;
2422             rewritelog(r, 3, "split uri=%s -> uri=%s, args=<none>", olduri,
2423                        r->filename);
2424         }
2425         else {
2426             if (r->args[strlen(r->args)-1] == '&') {
2427                 r->args[strlen(r->args)-1] = '\0';
2428             }
2429             rewritelog(r, 3, "split uri=%s -> uri=%s, args=%s", olduri,
2430                        r->filename, r->args);
2431         }
2432     }
2433     return;
2434 }
2435
2436
2437 /*
2438 **
2439 **  strip 'http[s]://ourhost/' from URI
2440 **
2441 */
2442
2443 static void reduce_uri(request_rec *r)
2444 {
2445     char *cp;
2446     unsigned short port;
2447     char *portp;
2448     char *hostp;
2449     char *url;
2450     char c;
2451     char host[LONG_STRING_LEN];
2452     char buf[MAX_STRING_LEN];
2453     char *olduri;
2454     int l;
2455
2456     cp = (char *)ap_http_method(r);
2457     l  = strlen(cp);
2458     if (   strlen(r->filename) > l+3 
2459         && strncasecmp(r->filename, cp, l) == 0
2460         && r->filename[l]   == ':'
2461         && r->filename[l+1] == '/'
2462         && r->filename[l+2] == '/'             ) {
2463         /* there was really a rewrite to a remote path */
2464
2465         olduri = apr_pstrdup(r->pool, r->filename); /* save for logging */
2466
2467         /* cut the hostname and port out of the URI */
2468         apr_cpystrn(buf, r->filename+(l+3), sizeof(buf));
2469         hostp = buf;
2470         for (cp = hostp; *cp != '\0' && *cp != '/' && *cp != ':'; cp++)
2471             ;
2472         if (*cp == ':') {
2473             /* set host */
2474             *cp++ = '\0';
2475             apr_cpystrn(host, hostp, sizeof(host));
2476             /* set port */
2477             portp = cp;
2478             for (; *cp != '\0' && *cp != '/'; cp++)
2479                 ;
2480             c = *cp;
2481             *cp = '\0';
2482             port = atoi(portp);
2483             *cp = c;
2484             /* set remaining url */
2485             url = cp;
2486         }
2487         else if (*cp == '/') {
2488             /* set host */
2489             *cp = '\0';
2490             apr_cpystrn(host, hostp, sizeof(host));
2491             *cp = '/';
2492             /* set port */
2493             port = ap_default_port(r);
2494             /* set remaining url */
2495             url = cp;
2496         }
2497         else {
2498             /* set host */
2499             apr_cpystrn(host, hostp, sizeof(host));
2500             /* set port */
2501             port = ap_default_port(r);
2502             /* set remaining url */
2503             url = "/";
2504         }
2505
2506         /* now check whether we could reduce it to a local path... */
2507         if (ap_matches_request_vhost(r, host, port)) {
2508             /* this is our host, so only the URL remains */
2509             r->filename = apr_pstrdup(r->pool, url);
2510             rewritelog(r, 3, "reduce %s -> %s", olduri, r->filename);
2511         }
2512     }
2513     return;
2514 }
2515
2516
2517 /*
2518 **
2519 **  add 'http[s]://ourhost[:ourport]/' to URI
2520 **  if URI is still not fully qualified
2521 **
2522 */
2523
2524 static void fully_qualify_uri(request_rec *r)
2525 {
2526     char buf[32];
2527     const char *thisserver;
2528     char *thisport;
2529     int port;
2530
2531     if (!is_absolute_uri(r->filename)) {
2532
2533         thisserver = ap_get_server_name(r);
2534         port = ap_get_server_port(r);
2535         if (ap_is_default_port(port,r)) {
2536             thisport = "";
2537         }
2538         else {
2539             apr_snprintf(buf, sizeof(buf), ":%u", port);
2540             thisport = buf;
2541         }
2542
2543         if (r->filename[0] == '/') {
2544             r->filename = apr_psprintf(r->pool, "%s://%s%s%s",
2545                                       ap_http_method(r), thisserver,
2546                                       thisport, r->filename);
2547         }
2548         else {
2549             r->filename = apr_psprintf(r->pool, "%s://%s%s/%s",
2550                                       ap_http_method(r), thisserver,
2551                                       thisport, r->filename);
2552         }
2553     }
2554     return;
2555 }
2556
2557
2558 /*
2559 **
2560 **  return non-zero if the URI is absolute (includes a scheme etc.)
2561 **
2562 */
2563
2564 static int is_absolute_uri(char *uri)
2565 {
2566     int i = strlen(uri);
2567     if (   (i > 7 && strncasecmp(uri, "http://",   7) == 0)
2568         || (i > 8 && strncasecmp(uri, "https://",  8) == 0)
2569         || (i > 9 && strncasecmp(uri, "gopher://", 9) == 0)
2570         || (i > 6 && strncasecmp(uri, "ftp://",    6) == 0)
2571         || (i > 5 && strncasecmp(uri, "ldap:",     5) == 0)
2572         || (i > 5 && strncasecmp(uri, "news:",     5) == 0)
2573         || (i > 7 && strncasecmp(uri, "mailto:",   7) == 0) ) {
2574         return 1;
2575     }
2576     else {
2577         return 0;
2578     }
2579 }
2580
2581
2582 /*
2583 **
2584 **  Expand tilde-paths (/~user) through Unix /etc/passwd 
2585 **  database information (or other OS-specific database)
2586 **
2587 */
2588 #if APR_HAS_USER
2589 static char *expand_tildepaths(request_rec *r, char *uri)
2590 {
2591     char user[LONG_STRING_LEN];
2592     char *newuri;
2593     int i, j;
2594     char *homedir;
2595
2596     newuri = uri;
2597     if (uri != NULL && strlen(uri) > 2 && uri[0] == '/' && uri[1] == '~') {
2598         /* cut out the username */
2599         for (j = 0, i = 2; j < sizeof(user)-1
2600                && uri[i] != '\0'
2601                && uri[i] != '/'  ; ) {
2602             user[j++] = uri[i++];
2603         }
2604         user[j] = '\0';
2605
2606         /* lookup username in systems passwd file */
2607         if (apr_get_home_directory(&homedir, user, r->pool) == APR_SUCCESS) {
2608             /* ok, user was found, so expand the ~user string */
2609             if (uri[i] != '\0') {
2610                 /* ~user/anything...  has to be expanded */
2611                 if (homedir[strlen(homedir)-1] == '/') {
2612                     homedir[strlen(homedir)-1] = '\0';
2613                 }
2614                 newuri = apr_pstrcat(r->pool, homedir, uri+i, NULL);
2615             }
2616             else {
2617                 /* only ~user has to be expanded */
2618                 newuri = homedir;
2619             }
2620         }
2621     }
2622     return newuri;
2623 }
2624 #endif  /* if APR_HAS_USER */
2625
2626
2627
2628 /*
2629 ** +-------------------------------------------------------+
2630 ** |                                                       |
2631 ** |              DBM hashfile support
2632 ** |                                                       |
2633 ** +-------------------------------------------------------+
2634 */
2635
2636
2637 static char *lookup_map(request_rec *r, char *name, char *key)
2638 {
2639     void *sconf;
2640     rewrite_server_conf *conf;
2641     apr_array_header_t *rewritemaps;
2642     rewritemap_entry *entries;
2643     rewritemap_entry *s;
2644     char *value;
2645     apr_finfo_t st;
2646     apr_status_t rv;
2647     int i;
2648
2649     /* get map configuration */
2650     sconf = r->server->module_config;
2651     conf  = (rewrite_server_conf *)ap_get_module_config(sconf, 
2652                                                         &rewrite_module);
2653     rewritemaps = conf->rewritemaps;
2654
2655     entries = (rewritemap_entry *)rewritemaps->elts;
2656     for (i = 0; i < rewritemaps->nelts; i++) {
2657         s = &entries[i];
2658         if (strcmp(s->name, name) == 0) {
2659             if (s->type == MAPTYPE_TXT) {
2660                 if ((rv = apr_stat(&st, s->checkfile, 
2661                                    APR_FINFO_MIN, r->pool)) != APR_SUCCESS) {
2662                     ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
2663                                  "mod_rewrite: can't access text RewriteMap "
2664                                  "file %s", s->checkfile);
2665                     rewritelog(r, 1, "can't open RewriteMap file, "
2666                                "see error log");
2667                     return NULL;
2668                 }
2669                 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2670                                          st.mtime, key);
2671                 if (value == NULL) {
2672                     rewritelog(r, 6, "cache lookup FAILED, forcing new "
2673                                "map lookup");
2674                     if ((value =
2675                          lookup_map_txtfile(r, s->datafile, key)) != NULL) {
2676                         rewritelog(r, 5, "map lookup OK: map=%s key=%s[txt] "
2677                                    "-> val=%s", s->name, key, value);
2678                         set_cache_string(cachep, s->name, CACHEMODE_TS,
2679                                          st.mtime, key, value);
2680                         return value;
2681                     }
2682                     else {
2683                         rewritelog(r, 5, "map lookup FAILED: map=%s[txt] "
2684                                    "key=%s", s->name, key);
2685                         set_cache_string(cachep, s->name, CACHEMODE_TS,
2686                                          st.mtime, key, "");
2687                         return NULL;
2688                     }
2689                 }
2690                 else {
2691                     rewritelog(r, 5, "cache lookup OK: map=%s[txt] key=%s "
2692                                "-> val=%s", s->name, key, value);
2693                     return value[0] != '\0' ? value : NULL;
2694                 }
2695             }
2696             else if (s->type == MAPTYPE_DBM) {
2697 #ifndef NO_DBM_REWRITEMAP
2698                 if ((rv = apr_stat(&st, s->checkfile,
2699                                    APR_FINFO_MIN, r->pool)) != APR_SUCCESS) {
2700                     ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
2701                                  "mod_rewrite: can't access DBM RewriteMap "
2702                                  "file %s", s->checkfile);
2703                     rewritelog(r, 1, "can't open DBM RewriteMap file, "
2704                                "see error log");
2705                     return NULL;
2706                 }
2707                 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2708                                          st.mtime, key);
2709                 if (value == NULL) {
2710                     rewritelog(r, 6,
2711                                "cache lookup FAILED, forcing new map lookup");
2712                     if ((value =
2713                          lookup_map_dbmfile(r, s->datafile, key)) != NULL) {
2714                         rewritelog(r, 5, "map lookup OK: map=%s[dbm] key=%s "
2715                                    "-> val=%s", s->name, key, value);
2716                         set_cache_string(cachep, s->name, CACHEMODE_TS,
2717                                          st.mtime, key, value);
2718                         return value;
2719                     }
2720                     else {
2721                         rewritelog(r, 5, "map lookup FAILED: map=%s[dbm] "
2722                                    "key=%s", s->name, key);
2723                         set_cache_string(cachep, s->name, CACHEMODE_TS,
2724                                          st.mtime, key, "");
2725                         return NULL;
2726                     }
2727                 }
2728                 else {
2729                     rewritelog(r, 5, "cache lookup OK: map=%s[dbm] key=%s "
2730                                "-> val=%s", s->name, key, value);
2731                     return value[0] != '\0' ? value : NULL;
2732                 }
2733 #else
2734                 return NULL;
2735 #endif
2736             }
2737             else if (s->type == MAPTYPE_PRG) {
2738                 if ((value =
2739                      lookup_map_program(r, s->fpin, s->fpout, key)) != NULL) {
2740                     rewritelog(r, 5, "map lookup OK: map=%s key=%s -> val=%s",
2741                                s->name, key, value);
2742                     return value;
2743                 }
2744                 else {
2745                     rewritelog(r, 5, "map lookup FAILED: map=%s key=%s",
2746                                s->name, key);
2747                 }
2748             }
2749             else if (s->type == MAPTYPE_INT) {
2750                 if ((value = lookup_map_internal(r, s->func, key)) != NULL) {
2751                     rewritelog(r, 5, "map lookup OK: map=%s key=%s -> val=%s",
2752                                s->name, key, value);
2753                     return value;
2754                 }
2755                 else {
2756                     rewritelog(r, 5, "map lookup FAILED: map=%s key=%s",
2757                                s->name, key);
2758                 }
2759             }
2760             else if (s->type == MAPTYPE_RND) {
2761                 if ((rv = apr_stat(&st, s->checkfile,
2762                                    APR_FINFO_MIN, r->pool)) != APR_SUCCESS) {
2763                     ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
2764                                  "mod_rewrite: can't access text RewriteMap "
2765                                  "file %s", s->checkfile);
2766                     rewritelog(r, 1, "can't open RewriteMap file, "
2767                                "see error log");
2768                     return NULL;
2769                 }
2770                 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2771                                          st.mtime, key);
2772                 if (value == NULL) {
2773                     rewritelog(r, 6, "cache lookup FAILED, forcing new "
2774                                "map lookup");
2775                     if ((value =
2776                          lookup_map_txtfile(r, s->datafile, key)) != NULL) {
2777                         rewritelog(r, 5, "map lookup OK: map=%s key=%s[txt] "
2778                                    "-> val=%s", s->name, key, value);
2779                         set_cache_string(cachep, s->name, CACHEMODE_TS,
2780                                          st.mtime, key, value);
2781                     }
2782                     else {
2783                         rewritelog(r, 5, "map lookup FAILED: map=%s[txt] "
2784                                    "key=%s", s->name, key);
2785                         set_cache_string(cachep, s->name, CACHEMODE_TS,
2786                                          st.mtime, key, "");
2787                         return NULL;
2788                     }
2789                 }
2790                 else {
2791                     rewritelog(r, 5, "cache lookup OK: map=%s[txt] key=%s "
2792                                "-> val=%s", s->name, key, value);
2793                 }
2794                 if (value[0] != '\0') {
2795                    value = select_random_value_part(r, value);
2796                    rewritelog(r, 5, "randomly choosen the subvalue `%s'", value);
2797                 }
2798                 else {
2799                     value = NULL;
2800                 }
2801                 return value;
2802             }
2803         }
2804     }
2805     return NULL;
2806 }
2807
2808 static char *lookup_map_txtfile(request_rec *r, const char *file, char *key)
2809 {
2810     apr_file_t *fp = NULL;
2811     apr_status_t rc;
2812     char line[1024];
2813     char *value = NULL;
2814     char *cpT;
2815     size_t skip;
2816     char *curkey;
2817     char *curval;
2818
2819     rc = apr_open(&fp, file, APR_READ, APR_OS_DEFAULT, r->pool);
2820     if (rc != APR_SUCCESS) {
2821        return NULL;
2822     }
2823
2824     while (apr_fgets(line, sizeof(line), fp) == APR_SUCCESS) {
2825         if (line[0] == '#')
2826             continue; /* ignore comments */
2827         cpT = line;
2828         curkey = cpT;
2829         skip = strcspn(cpT," \t\r\n");
2830         if (skip == 0)
2831             continue; /* ignore lines that start with a space, tab, CR, or LF */
2832         cpT += skip;
2833         *cpT = '\0';
2834         if (strcmp(curkey, key) != 0)
2835             continue; /* key does not match... */
2836             
2837         /* found a matching key; now extract and return the value */
2838         ++cpT;
2839         skip = strspn(cpT, " \t\r\n");
2840         cpT += skip;
2841         curval = cpT;
2842         skip = strcspn(cpT, " \t\r\n");
2843         if (skip == 0)
2844             continue; /* no value... */
2845         cpT += skip;
2846         *cpT = '\0';
2847         value = apr_pstrdup(r->pool, curval);
2848         break;
2849     }
2850     apr_close(fp);
2851     return value;
2852 }
2853
2854 #ifndef NO_DBM_REWRITEMAP
2855 static char *lookup_map_dbmfile(request_rec *r, const char *file, char *key)
2856 {
2857     DBM *dbmfp = NULL;
2858     datum dbmkey;
2859     datum dbmval;
2860     char *value = NULL;
2861     char buf[MAX_STRING_LEN];
2862
2863     dbmkey.dptr  = key;
2864     dbmkey.dsize = strlen(key);
2865     if ((dbmfp = dbm_open(file, O_RDONLY, 0666)) != NULL) {
2866         dbmval = dbm_fetch(dbmfp, dbmkey);
2867         if (dbmval.dptr != NULL) {
2868             memcpy(buf, dbmval.dptr, 
2869                    dbmval.dsize < sizeof(buf)-1 ? 
2870                    dbmval.dsize : sizeof(buf)-1  );
2871             buf[dbmval.dsize] = '\0';
2872             value = apr_pstrdup(r->pool, buf);
2873         }
2874         dbm_close(dbmfp);
2875     }
2876     return value;
2877 }
2878 #endif
2879
2880 static char *lookup_map_program(request_rec *r, apr_file_t *fpin,
2881                                 apr_file_t *fpout, char *key)
2882 {
2883     char buf[LONG_STRING_LEN];
2884     char c;
2885     int i;
2886     apr_size_t nbytes;
2887
2888 #ifndef NO_WRITEV
2889     struct iovec iova[2];
2890     apr_size_t niov;
2891 #endif
2892
2893     /* when `RewriteEngine off' was used in the per-server
2894      * context then the rewritemap-programs were not spawned.
2895      * In this case using such a map (usually in per-dir context)
2896      * is useless because it is not available.
2897      */
2898     if (fpin == NULL || fpout == NULL) {
2899         return NULL;
2900     }
2901
2902     /* take the lock */
2903
2904     if (rewrite_mapr_lock) {
2905         apr_lock(rewrite_mapr_lock);
2906     }
2907
2908     /* write out the request key */
2909 #ifdef NO_WRITEV
2910     nbytes = strlen(key);
2911     apr_write(fpin, key, &nbytes);
2912     nbytes = 1;
2913     apr_write(fpin, "\n", &nbytes);
2914 #else
2915     iova[0].iov_base = key;
2916     iova[0].iov_len = strlen(key);
2917     iova[1].iov_base = "\n";
2918     iova[1].iov_len = 1;
2919
2920     niov = 2;
2921     apr_writev(fpin, iova, niov, &nbytes);
2922 #endif
2923
2924     /* read in the response value */
2925     i = 0;
2926     nbytes = 1;
2927     apr_read(fpout, &c, &nbytes);
2928     while (nbytes == 1 && (i < LONG_STRING_LEN-1)) {
2929         if (c == '\n') {
2930             break;
2931         }
2932         buf[i++] = c;
2933
2934         apr_read(fpout, &c, &nbytes);
2935     }
2936     buf[i] = '\0';
2937
2938     /* give the lock back */
2939     if (rewrite_mapr_lock) {
2940         apr_unlock(rewrite_mapr_lock);
2941     }
2942
2943     if (strcasecmp(buf, "NULL") == 0) {
2944         return NULL;
2945     }
2946     else {
2947         return apr_pstrdup(r->pool, buf);
2948     }
2949 }
2950
2951 static char *lookup_map_internal(request_rec *r,
2952                                  char *(*func)(request_rec *, char *),
2953                                  char *key)
2954 {
2955     /* currently we just let the function convert
2956        the key to a corresponding value */
2957     return func(r, key);
2958 }
2959
2960 static char *rewrite_mapfunc_toupper(request_rec *r, char *key)
2961 {
2962     char *value, *cp;
2963
2964     for (cp = value = apr_pstrdup(r->pool, key); cp != NULL && *cp != '\0';
2965          cp++) {
2966         *cp = apr_toupper(*cp);
2967     }
2968     return value;
2969 }
2970
2971 static char *rewrite_mapfunc_tolower(request_rec *r, char *key)
2972 {
2973     char *value, *cp;
2974
2975     for (cp = value = apr_pstrdup(r->pool, key); cp != NULL && *cp != '\0';
2976          cp++) {
2977         *cp = apr_tolower(*cp);
2978     }
2979     return value;
2980 }
2981
2982 static char *rewrite_mapfunc_escape(request_rec *r, char *key)
2983 {
2984     char *value;
2985
2986     value = ap_escape_uri(r->pool, key);
2987     return value;
2988 }
2989
2990 static char *rewrite_mapfunc_unescape(request_rec *r, char *key)
2991 {
2992     char *value;
2993
2994     value = apr_pstrdup(r->pool, key);
2995     ap_unescape_url(value);
2996     return value;
2997 }
2998
2999 static int rewrite_rand_init_done = 0;
3000
3001 static void rewrite_rand_init(void)
3002 {
3003     if (!rewrite_rand_init_done) {
3004         srand((unsigned)(getpid()));
3005         rewrite_rand_init_done = 1;
3006     }
3007     return;
3008 }
3009
3010 static int rewrite_rand(int l, int h)
3011 {
3012     rewrite_rand_init();
3013
3014     /* Get [0,1) and then scale to the appropriate range. Note that using
3015      * a floating point value ensures that we use all bits of the rand()
3016      * result. Doing an integer modulus would only use the lower-order bits
3017      * which may not be as uniformly random.
3018      */
3019     return ((double)(rand() % RAND_MAX) / RAND_MAX) * (h - l + 1) + l;
3020 }
3021
3022 static char *select_random_value_part(request_rec *r, char *value)
3023 {
3024     char *buf;
3025     int n, i, k;
3026
3027     /*  count number of distinct values  */
3028     for (n = 1, i = 0; value[i] != '\0'; i++) {
3029         if (value[i] == '|') {
3030             n++;
3031         }
3032     }
3033
3034     /*  when only one value we have no option to choose  */
3035     if (n == 1) {
3036         return value;
3037     }
3038
3039     /*  else randomly select one  */
3040     k = rewrite_rand(1, n);
3041
3042     /*  and grep it out  */
3043     for (n = 1, i = 0; value[i] != '\0'; i++) {
3044         if (n == k) {
3045             break;
3046         }
3047         if (value[i] == '|') {
3048             n++;
3049         }
3050     }
3051     buf = apr_pstrdup(r->pool, &value[i]);
3052     for (i = 0; buf[i] != '\0' && buf[i] != '|'; i++)
3053         ;
3054     buf[i] = '\0';
3055     return buf;
3056 }
3057
3058
3059 /*
3060 ** +-------------------------------------------------------+
3061 ** |                                                       |
3062 ** |              rewriting logfile support
3063 ** |                                                       |
3064 ** +-------------------------------------------------------+
3065 */
3066
3067
3068 static void open_rewritelog(server_rec *s, apr_pool_t *p)
3069 {
3070     rewrite_server_conf *conf;
3071     const char *fname;
3072     apr_status_t rc;
3073     piped_log *pl;
3074     int    rewritelog_flags = ( APR_WRITE | APR_APPEND | APR_CREATE );
3075     mode_t rewritelog_mode  = ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD );
3076
3077     conf = ap_get_module_config(s->module_config, &rewrite_module);
3078
3079     if (conf->rewritelogfile == NULL) {
3080         return;
3081     }
3082     if (*(conf->rewritelogfile) == '\0') {
3083         return;
3084     }
3085     if (conf->rewritelogfp != NULL) {
3086         return; /* virtual log shared w/ main server */
3087     }
3088
3089     fname = ap_server_root_relative(p, conf->rewritelogfile);
3090
3091     if (*conf->rewritelogfile == '|') {
3092         if ((pl = ap_open_piped_log(p, conf->rewritelogfile+1)) == NULL) {
3093             ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, s, 
3094                          "mod_rewrite: could not open reliable pipe "
3095                          "to RewriteLog filter %s", conf->rewritelogfile+1);
3096             exit(1);
3097         }
3098         conf->rewritelogfp = ap_piped_log_write_fd(pl);
3099     }
3100     else if (*conf->rewritelogfile != '\0') {
3101         rc = apr_open(&conf->rewritelogfp, fname, rewritelog_flags, rewritelog_mode, p);
3102         if (rc != APR_SUCCESS)  {
3103             ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, 
3104                          "mod_rewrite: could not open RewriteLog "
3105                          "file %s", fname);
3106             exit(1);
3107         }
3108     }
3109     return;
3110 }
3111
3112 static void rewritelog(request_rec *r, int level, const char *text, ...)
3113 {
3114     rewrite_server_conf *conf;
3115     conn_rec *conn;
3116     char *str1;
3117     char str2[512];
3118     char str3[1024];
3119     char type[20];
3120     char redir[20];
3121     va_list ap;
3122     int i;
3123     apr_size_t nbytes;
3124     request_rec *req;
3125     char *ruser;
3126     const char *rhost;
3127
3128     va_start(ap, text);
3129     conf = ap_get_module_config(r->server->module_config, &rewrite_module);
3130     conn = r->connection;
3131
3132     if (conf->rewritelogfp == NULL) {
3133         return;
3134     }
3135     if (conf->rewritelogfile == NULL) {
3136         return;
3137     }
3138     if (*(conf->rewritelogfile) == '\0') {
3139         return;
3140     }
3141
3142     if (level > conf->rewriteloglevel) {
3143         return;
3144     }
3145
3146     if (r->user == NULL) {
3147         ruser = "-";
3148     }
3149     else if (strlen(r->user) != 0) {
3150         ruser = r->user;
3151     }
3152     else {
3153         ruser = "\"\"";
3154     }
3155
3156     rhost = ap_get_remote_host(conn, r->server->module_config, 
3157                                REMOTE_NOLOOKUP);
3158     if (rhost == NULL) {
3159         rhost = "UNKNOWN-HOST";
3160     }
3161
3162     str1 = apr_pstrcat(r->pool, rhost, " ",
3163                       (conn->remote_logname != NULL ?
3164                       conn->remote_logname : "-"), " ",
3165                       ruser, NULL);
3166     apr_vsnprintf(str2, sizeof(str2), text, ap);
3167
3168     if (r->main == NULL) {
3169         strcpy(type, "initial");
3170     }
3171     else {
3172         strcpy(type, "subreq");
3173     }
3174
3175     for (i = 0, req = r; req->prev != NULL; req = req->prev) {
3176         i++;
3177     }
3178     if (i == 0) {
3179         redir[0] = '\0';
3180     }
3181     else {
3182         apr_snprintf(redir, sizeof(redir), "/redir#%d", i);
3183     }
3184
3185     apr_snprintf(str3, sizeof(str3),
3186                 "%s %s [%s/sid#%lx][rid#%lx/%s%s] (%d) %s\n", str1,
3187                 current_logtime(r), ap_get_server_name(r),
3188                 (unsigned long)(r->server), (unsigned long)r,
3189                 type, redir, level, str2);
3190
3191     apr_lock(rewrite_log_lock);
3192     nbytes = strlen(str3);
3193     apr_write(conf->rewritelogfp, str3, &nbytes);
3194     apr_unlock(rewrite_log_lock);
3195
3196     va_end(ap);
3197     return;
3198 }
3199
3200 static char *current_logtime(request_rec *r)
3201 {
3202     apr_exploded_time_t t;
3203     char tstr[80];
3204     apr_size_t len;
3205
3206     apr_explode_localtime(&t, apr_now());
3207
3208     apr_strftime(tstr, &len, 80, "[%d/%b/%Y:%H:%M:%S ", &t);
3209     apr_snprintf(tstr + strlen(tstr), 80-strlen(tstr), "%c%.2d%.2d]",
3210                 t.tm_gmtoff < 0 ? '-' : '+',
3211                 t.tm_gmtoff / (60*60), t.tm_gmtoff % (60*60));
3212     return apr_pstrdup(r->pool, tstr);
3213 }
3214
3215
3216
3217
3218 /*
3219 ** +-------------------------------------------------------+
3220 ** |                                                       |
3221 ** |              rewriting lockfile support
3222 ** |                                                       |
3223 ** +-------------------------------------------------------+
3224 */
3225
3226 #define REWRITELOCK_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD )
3227
3228 static void rewritelock_create(server_rec *s, apr_pool_t *p)
3229 {
3230     apr_status_t rc;
3231
3232     /* only operate if a lockfile is used */
3233     if (lockname == NULL || *(lockname) == '\0') {
3234         return;
3235     }
3236
3237     /* fixup the path, especially for rewritelock_remove() */
3238     lockname = ap_server_root_relative(p, lockname);
3239
3240     /* create the lockfile */
3241     rc = apr_create_lock (&rewrite_mapr_lock, APR_MUTEX, APR_LOCKALL, lockname, p);
3242     if (rc != APR_SUCCESS) {
3243         ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
3244                      "mod_rewrite: Parent could not create RewriteLock "
3245                      "file %s", lockname);
3246         exit(1);
3247     }
3248
3249     return;
3250 }
3251
3252 static apr_status_t rewritelock_remove(void *data)
3253 {
3254     /* only operate if a lockfile is used */
3255     if (lockname == NULL || *(lockname) == '\0') {
3256         return APR_SUCCESS;
3257     }
3258
3259     /* destroy the rewritelock */
3260     apr_destroy_lock (rewrite_mapr_lock);
3261     rewrite_mapr_lock = NULL;
3262     lockname = NULL;
3263     return(0);
3264 }
3265
3266
3267 /*
3268 ** +-------------------------------------------------------+
3269 ** |                                                       |
3270 ** |                  program map support
3271 ** |                                                       |
3272 ** +-------------------------------------------------------+
3273 */
3274
3275 static void run_rewritemap_programs(server_rec *s, apr_pool_t *p)
3276 {
3277     rewrite_server_conf *conf;
3278     apr_file_t *fpin = NULL;
3279     apr_file_t *fpout = NULL;
3280     apr_file_t *fperr = NULL;
3281     apr_array_header_t *rewritemaps;
3282     rewritemap_entry *entries;
3283     rewritemap_entry *map;
3284     int i;
3285     apr_status_t rc;
3286
3287     conf = ap_get_module_config(s->module_config, &rewrite_module);
3288
3289     /*  If the engine isn't turned on,
3290      *  don't even try to do anything.
3291      */
3292     if (conf->state == ENGINE_DISABLED) {
3293         return;
3294     }
3295
3296     rewritemaps = conf->rewritemaps;
3297     entries = (rewritemap_entry *)rewritemaps->elts;
3298     for (i = 0; i < rewritemaps->nelts; i++) {
3299         map = &entries[i];
3300         if (map->type != MAPTYPE_PRG) {
3301             continue;
3302         }
3303         if (map->datafile == NULL
3304             || *(map->datafile) == '\0'
3305             || map->fpin  != NULL
3306             || map->fpout != NULL        ) {
3307             continue;
3308         }
3309         fpin  = NULL;
3310         fpout = NULL;
3311         rc = rewritemap_program_child(p, map->datafile,
3312                                      &fpout, &fpin, &fperr);
3313         if (rc != APR_SUCCESS || fpin == NULL || fpout == NULL) {
3314             ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
3315                          "mod_rewrite: could not fork child for "
3316                          "RewriteMap process");
3317             exit(1);
3318         }
3319         map->fpin  = fpin;
3320         map->fpout = fpout;
3321         map->fperr = fperr;
3322     }
3323     return;
3324 }
3325
3326 /* child process code */
3327 static apr_status_t rewritemap_program_child(apr_pool_t *p, const char *progname,
3328                                              apr_file_t **fpout, apr_file_t **fpin,
3329                                              apr_file_t **fperr)
3330 {
3331     apr_status_t rc;
3332     apr_procattr_t *procattr;
3333     apr_proc_t *procnew;
3334
3335 #ifdef SIGHUP
3336     apr_signal(SIGHUP, SIG_IGN);
3337 #endif
3338
3339     
3340     if (((rc = apr_createprocattr_init(&procattr, p)) != APR_SUCCESS) ||
3341         ((rc = apr_setprocattr_io(procattr, APR_FULL_BLOCK,
3342                                   APR_FULL_NONBLOCK,
3343                                   APR_FULL_NONBLOCK)) != APR_SUCCESS) ||
3344         ((rc = apr_setprocattr_dir(procattr, 
3345                                    ap_make_dirstr_parent(p, progname)))
3346          != APR_SUCCESS) ||
3347         ((rc = apr_setprocattr_cmdtype(procattr, APR_PROGRAM)) != APR_SUCCESS)) {
3348         /* Something bad happened, give up and go away. */
3349     }
3350     else {
3351         procnew = apr_pcalloc(p, sizeof(*procnew));
3352         rc = apr_create_process(procnew, progname, NULL, NULL, procattr, p);
3353     
3354         if (rc == APR_SUCCESS) {
3355             apr_note_subprocess(p, procnew, kill_after_timeout);
3356
3357             if (fpin) {
3358                 (*fpin) = procnew->in;
3359             }
3360
3361             if (fpout) {
3362                 (*fpout) = procnew->out;
3363             }
3364
3365             if (fperr) {
3366                 (*fperr) = procnew->err;
3367             }
3368         }
3369     }
3370
3371     return (rc);
3372 }
3373
3374
3375
3376
3377 /*
3378 ** +-------------------------------------------------------+
3379 ** |                                                       |
3380 ** |             environment variable support
3381 ** |                                                       |
3382 ** +-------------------------------------------------------+
3383 */
3384
3385
3386 static char *lookup_variable(request_rec *r, char *var)
3387 {
3388     const char *result;
3389     char resultbuf[LONG_STRING_LEN];
3390     apr_exploded_time_t tm;
3391     request_rec *rsub;
3392
3393     result = NULL;
3394
3395     /* HTTP headers */
3396     if (strcasecmp(var, "HTTP_USER_AGENT") == 0) {
3397         result = lookup_header(r, "User-Agent");
3398     }
3399     else if (strcasecmp(var, "HTTP_REFERER") == 0) {
3400         result = lookup_header(r, "Referer");
3401     }
3402     else if (strcasecmp(var, "HTTP_COOKIE") == 0) {
3403         result = lookup_header(r, "Cookie");
3404     }
3405     else if (strcasecmp(var, "HTTP_FORWARDED") == 0) {
3406         result = lookup_header(r, "Forwarded");
3407     }
3408     else if (strcasecmp(var, "HTTP_HOST") == 0) {
3409         result = lookup_header(r, "Host");
3410     }
3411     else if (strcasecmp(var, "HTTP_PROXY_CONNECTION") == 0) {
3412         result = lookup_header(r, "Proxy-Connection");
3413     }
3414     else if (strcasecmp(var, "HTTP_ACCEPT") == 0) {
3415         result = lookup_header(r, "Accept");
3416     }
3417     /* all other headers from which we are still not know about */
3418     else if (strlen(var) > 5 && strncasecmp(var, "HTTP:", 5) == 0) {
3419         result = lookup_header(r, var+5);
3420     }
3421
3422     /* connection stuff */
3423     else if (strcasecmp(var, "REMOTE_ADDR") == 0) {
3424         result = r->connection->remote_ip;
3425     }
3426     else if (strcasecmp(var, "REMOTE_HOST") == 0) {
3427         result = (char *)ap_get_remote_host(r->connection,
3428                                          r->per_dir_config, REMOTE_NAME);
3429     }
3430     else if (strcasecmp(var, "REMOTE_USER") == 0) {
3431         result = r->user;
3432     }
3433     else if (strcasecmp(var, "REMOTE_IDENT") == 0) {
3434         result = (char *)ap_get_remote_logname(r);
3435     }
3436
3437     /* request stuff */
3438     else if (strcasecmp(var, "THE_REQUEST") == 0) { /* non-standard */
3439         result = r->the_request;
3440     }
3441     else if (strcasecmp(var, "REQUEST_METHOD") == 0) {
3442         result = r->method;
3443     }
3444     else if (strcasecmp(var, "REQUEST_URI") == 0) { /* non-standard */
3445         result = r->uri;
3446     }
3447     else if (strcasecmp(var, "SCRIPT_FILENAME") == 0 ||
3448              strcasecmp(var, "REQUEST_FILENAME") == 0  ) {
3449         result = r->filename;
3450     }
3451     else if (strcasecmp(var, "PATH_INFO") == 0) {
3452         result = r->path_info;
3453     }
3454     else if (strcasecmp(var, "QUERY_STRING") == 0) {
3455         result = r->args;
3456     }
3457     else if (strcasecmp(var, "AUTH_TYPE") == 0) {
3458         result = r->ap_auth_type;
3459     }
3460     else if (strcasecmp(var, "IS_SUBREQ") == 0) { /* non-standard */
3461         result = (r->main != NULL ? "true" : "false");
3462     }
3463
3464     /* internal server stuff */
3465     else if (strcasecmp(var, "DOCUMENT_ROOT") == 0) {
3466         result = ap_document_root(r);
3467     }
3468     else if (strcasecmp(var, "SERVER_ADMIN") == 0) {
3469         result = r->server->server_admin;
3470     }
3471     else if (strcasecmp(var, "SERVER_NAME") == 0) {
3472         result = ap_get_server_name(r);
3473     }
3474     else if (strcasecmp(var, "SERVER_ADDR") == 0) { /* non-standard */
3475         result = r->connection->local_ip;
3476     }
3477     else if (strcasecmp(var, "SERVER_PORT") == 0) {
3478         apr_snprintf(resultbuf, sizeof(resultbuf), "%u", ap_get_server_port(r));
3479         result = resultbuf;
3480     }
3481     else if (strcasecmp(var, "SERVER_PROTOCOL") == 0) {
3482         result = r->protocol;
3483     }
3484     else if (strcasecmp(var, "SERVER_SOFTWARE") == 0) {
3485         result = ap_get_server_version();
3486     }
3487     else if (strcasecmp(var, "API_VERSION") == 0) { /* non-standard */
3488         apr_snprintf(resultbuf, sizeof(resultbuf), "%d:%d",
3489                     MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR);
3490         result = resultbuf;
3491     }
3492
3493 /* XXX: wow this has gotta be slow if you actually use it for a lot, recalculates exploded time for each variable */
3494     /* underlaying Unix system stuff */
3495     else if (strcasecmp(var, "TIME_YEAR") == 0) {
3496         apr_explode_localtime(&tm, apr_now());
3497         apr_snprintf(resultbuf, sizeof(resultbuf), "%04d", tm.tm_year + 1900);
3498         result = resultbuf;
3499     }
3500 #define MKTIMESTR(format, tmfield) \
3501     apr_explode_localtime(&tm, apr_now()); \
3502     apr_snprintf(resultbuf, sizeof(resultbuf), format, tm.tmfield); \
3503     result = resultbuf;
3504     else if (strcasecmp(var, "TIME_MON") == 0) {
3505         MKTIMESTR("%02d", tm_mon+1)
3506     }
3507     else if (strcasecmp(var, "TIME_DAY") == 0) {
3508         MKTIMESTR("%02d", tm_mday)
3509     }
3510     else if (strcasecmp(var, "TIME_HOUR") == 0) {
3511         MKTIMESTR("%02d", tm_hour)
3512     }
3513     else if (strcasecmp(var, "TIME_MIN") == 0) {
3514         MKTIMESTR("%02d", tm_min)
3515     }
3516     else if (strcasecmp(var, "TIME_SEC") == 0) {
3517         MKTIMESTR("%02d", tm_sec)
3518     }
3519     else if (strcasecmp(var, "TIME_WDAY") == 0) {
3520         MKTIMESTR("%d", tm_wday)
3521     }
3522     else if (strcasecmp(var, "TIME") == 0) {
3523         apr_explode_localtime(&tm, apr_now());
3524         apr_snprintf(resultbuf, sizeof(resultbuf),
3525                     "%04d%02d%02d%02d%02d%02d", tm.tm_year + 1900,
3526                     tm.tm_mon+1, tm.tm_mday,
3527                     tm.tm_hour, tm.tm_min, tm.tm_sec);
3528         result = resultbuf;
3529         rewritelog(r, 1, "RESULT='%s'", result);
3530     }
3531
3532     /* all other env-variables from the parent Apache process */
3533     else if (strlen(var) > 4 && strncasecmp(var, "ENV:", 4) == 0) {
3534         /* first try the internal Apache notes structure */
3535         result = apr_table_get(r->notes, var+4);
3536         /* second try the internal Apache env structure  */
3537         if (result == NULL) {
3538             result = apr_table_get(r->subprocess_env, var+4);
3539         }
3540         /* third try the external OS env */
3541         if (result == NULL) {
3542             result = getenv(var+4);
3543         }
3544     }
3545
3546 #define LOOKAHEAD(subrecfunc) \
3547         if ( \
3548           /* filename is safe to use */ \
3549           r->filename != NULL \
3550               /* - and we're either not in a subrequest */ \
3551               && ( r->main == NULL \
3552                   /* - or in a subrequest where paths are non-NULL... */ \
3553                     || ( r->main->uri != NULL && r->uri != NULL \
3554                         /*   ...and sub and main paths differ */ \
3555                         && strcmp(r->main->uri, r->uri) != 0))) { \
3556             /* process a file-based subrequest */ \
3557             rsub = subrecfunc(r->filename, r, NULL); \
3558             /* now recursively lookup the variable in the sub_req */ \
3559             result = lookup_variable(rsub, var+5); \
3560             /* copy it up to our scope before we destroy sub_req's apr_pool_t */ \
3561             result = apr_pstrdup(r->pool, result); \
3562             /* cleanup by destroying the subrequest */ \
3563             ap_destroy_sub_req(rsub); \
3564             /* log it */ \
3565             rewritelog(r, 5, "lookahead: path=%s var=%s -> val=%s", \
3566                        r->filename, var+5, result); \
3567             /* return ourself to prevent re-pstrdup */ \
3568             return (char *)result; \
3569         }
3570
3571     /* look-ahead for parameter through URI-based sub-request */
3572     else if (strlen(var) > 5 && strncasecmp(var, "LA-U:", 5) == 0) {
3573         LOOKAHEAD(ap_sub_req_lookup_uri)
3574     }
3575     /* look-ahead for parameter through file-based sub-request */
3576     else if (strlen(var) > 5 && strncasecmp(var, "LA-F:", 5) == 0) {
3577         LOOKAHEAD(ap_sub_req_lookup_file)
3578     }
3579
3580     /* file stuff */
3581     else if (strcasecmp(var, "SCRIPT_USER") == 0) {
3582         result = "<unknown>";
3583         if (r->finfo.valid & APR_FINFO_USER) {
3584             apr_get_username((char **)&result, r->finfo.user, r->pool);
3585         }
3586     }
3587     else if (strcasecmp(var, "SCRIPT_GROUP") == 0) {
3588         result = "<unknown>";
3589         if (r->finfo.valid & APR_FINFO_GROUP) {
3590             apr_get_groupname((char **)&result, r->finfo.group, r->pool);
3591         }
3592     }
3593
3594     if (result == NULL) {
3595         return apr_pstrdup(r->pool, "");
3596     }
3597     else {
3598         return apr_pstrdup(r->pool, result);
3599     }
3600 }
3601
3602 static char *lookup_header(request_rec *r, const char *name)
3603 {
3604     apr_array_header_t *hdrs_arr;
3605     apr_table_entry_t *hdrs;
3606     int i;
3607
3608     hdrs_arr = apr_table_elts(r->headers_in);
3609     hdrs = (apr_table_entry_t *)hdrs_arr->elts;
3610     for (i = 0; i < hdrs_arr->nelts; ++i) {
3611         if (hdrs[i].key == NULL) {
3612             continue;
3613         }
3614         if (strcasecmp(hdrs[i].key, name) == 0) {
3615             apr_table_merge(r->notes, VARY_KEY_THIS, name);
3616             return hdrs[i].val;
3617         }
3618     }
3619     return NULL;
3620 }
3621
3622
3623
3624
3625 /*
3626 ** +-------------------------------------------------------+
3627 ** |                                                       |
3628 ** |                    caching support
3629 ** |                                                       |
3630 ** +-------------------------------------------------------+
3631 */
3632
3633
3634 static cache *init_cache(apr_pool_t *p)
3635 {
3636     cache *c;
3637
3638     c = (cache *)apr_palloc(p, sizeof(cache));
3639     if (apr_create_pool(&c->pool, p) != APR_SUCCESS)
3640                 return NULL;
3641     c->lists = apr_make_array(c->pool, 2, sizeof(cachelist));
3642     return c;
3643 }
3644
3645 static void set_cache_string(cache *c, const char *res, int mode, time_t t,
3646                              char *key, char *value)
3647 {
3648     cacheentry ce;
3649
3650     ce.time  = t;
3651     ce.key   = key;
3652     ce.value = value;
3653     store_cache_string(c, res, &ce);
3654     return;
3655 }
3656
3657 static char *get_cache_string(cache *c, const char *res, int mode,
3658                               time_t t, char *key)
3659 {
3660     cacheentry *ce;
3661
3662     ce = retrieve_cache_string(c, res, key);
3663     if (ce == NULL) {
3664         return NULL;
3665     }
3666     if (mode & CACHEMODE_TS) {
3667         if (t != ce->time) {
3668             return NULL;
3669         }
3670     }
3671     else if (mode & CACHEMODE_TTL) {
3672         if (t > ce->time) {
3673             return NULL;
3674         }
3675     }
3676     return apr_pstrdup(c->pool, ce->value);
3677 }
3678
3679 static int cache_tlb_hash(char *key)
3680 {
3681     unsigned long n;
3682     char *p;
3683
3684     n = 0;
3685     for (p = key; *p != '\0'; p++) {
3686         n = ((n << 5) + n) ^ (unsigned long)(*p++);
3687     }
3688
3689     return n % CACHE_TLB_ROWS;
3690 }
3691
3692 static cacheentry *cache_tlb_lookup(cachetlbentry *tlb, cacheentry *elt,
3693                                     char *key)
3694 {
3695     int ix = cache_tlb_hash(key);
3696     int i;
3697     int j;
3698
3699     for (i=0; i < CACHE_TLB_COLS; ++i) {
3700         j = tlb[ix].t[i];
3701         if (j < 0)
3702             return NULL;
3703         if (strcmp(elt[j].key, key) == 0)
3704             return &elt[j];
3705     }
3706     return NULL;
3707 }
3708
3709 static void cache_tlb_replace(cachetlbentry *tlb, cacheentry *elt,
3710                               cacheentry *e)
3711 {
3712     int ix = cache_tlb_hash(e->key);
3713     int i;
3714
3715     tlb = &tlb[ix];
3716
3717     for (i=1; i < CACHE_TLB_COLS; ++i)
3718         tlb->t[i] = tlb->t[i-1];
3719
3720     tlb->t[0] = e - elt;
3721 }
3722
3723 static void store_cache_string(cache *c, const char *res, cacheentry *ce)
3724 {
3725     int i;
3726     int j;
3727     cachelist *l;
3728     cacheentry *e;
3729     cachetlbentry *t;
3730     int found_list;
3731
3732     found_list = 0;
3733     /* first try to edit an existing entry */
3734     for (i = 0; i < c->lists->nelts; i++) {
3735         l = &(((cachelist *)c->lists->elts)[i]);
3736         if (strcmp(l->resource, res) == 0) {
3737             found_list = 1;
3738
3739             e = cache_tlb_lookup((cachetlbentry *)l->tlb->elts,
3740                                  (cacheentry *)l->entries->elts, ce->key);
3741             if (e != NULL) {
3742                 e->time  = ce->time;
3743                 e->value = apr_pstrdup(c->pool, ce->value);
3744                 return;
3745             }
3746
3747             for (j = 0; j < l->entries->nelts; j++) {
3748                 e = &(((cacheentry *)l->entries->elts)[j]);
3749                 if (strcmp(e->key, ce->key) == 0) {
3750                     e->time  = ce->time;
3751                     e->value = apr_pstrdup(c->pool, ce->value);
3752                   cache_tlb_replace((cachetlbentry *)l->tlb->elts,
3753                                     (cacheentry *)l->entries->elts, e);
3754                     return;
3755                 }
3756             }
3757         }
3758     }
3759
3760     /* create a needed new list */
3761     if (!found_list) {
3762         l = apr_push_array(c->lists);
3763         l->resource = apr_pstrdup(c->pool, res);
3764         l->entries  = apr_make_array(c->pool, 2, sizeof(cacheentry));
3765         l->tlb      = apr_make_array(c->pool, CACHE_TLB_ROWS,
3766                                     sizeof(cachetlbentry));
3767         for (i=0; i<CACHE_TLB_ROWS; ++i) {
3768             t = &((cachetlbentry *)l->tlb->elts)[i];
3769                 for (j=0; j<CACHE_TLB_COLS; ++j)
3770                     t->t[j] = -1;
3771         }
3772     }
3773
3774     /* create the new entry */
3775     for (i = 0; i < c->lists->nelts; i++) {
3776         l = &(((cachelist *)c->lists->elts)[i]);
3777         if (strcmp(l->resource, res) == 0) {
3778             e = apr_push_array(l->entries);
3779             e->time  = ce->time;
3780             e->key   = apr_pstrdup(c->pool, ce->key);
3781             e->value = apr_pstrdup(c->pool, ce->value);
3782             cache_tlb_replace((cachetlbentry *)l->tlb->elts,
3783                               (cacheentry *)l->entries->elts, e);
3784             return;
3785         }
3786     }
3787
3788     /* not reached, but when it is no problem... */
3789     return;
3790 }
3791
3792 static cacheentry *retrieve_cache_string(cache *c, const char *res, char *key)
3793 {
3794     int i;
3795     int j;
3796     cachelist *l;
3797     cacheentry *e;
3798
3799     for (i = 0; i < c->lists->nelts; i++) {
3800         l = &(((cachelist *)c->lists->elts)[i]);
3801         if (strcmp(l->resource, res) == 0) {
3802
3803             e = cache_tlb_lookup((cachetlbentry *)l->tlb->elts,
3804                                  (cacheentry *)l->entries->elts, key);
3805             if (e != NULL)
3806                 return e;
3807
3808             for (j = 0; j < l->entries->nelts; j++) {
3809                 e = &(((cacheentry *)l->entries->elts)[j]);
3810                 if (strcmp(e->key, key) == 0) {
3811                     return e;
3812                 }
3813             }
3814         }
3815     }
3816     return NULL;
3817 }
3818
3819
3820
3821
3822 /*
3823 ** +-------------------------------------------------------+
3824 ** |                                                       |
3825 ** |                    misc functions
3826 ** |                                                       |
3827 ** +-------------------------------------------------------+
3828 */
3829
3830 static char *subst_prefix_path(request_rec *r, char *input, char *match,
3831                                const char *subst)
3832 {
3833     char matchbuf[LONG_STRING_LEN];
3834     char substbuf[LONG_STRING_LEN];
3835     char *output;
3836     int l;
3837
3838     output = input;
3839
3840     /* first create a match string which always has a trailing slash */
3841     l = apr_cpystrn(matchbuf, match, sizeof(matchbuf)) - matchbuf;
3842     if (matchbuf[l-1] != '/') {
3843        matchbuf[l] = '/';
3844        matchbuf[l+1] = '\0';
3845        l++;
3846     }
3847     /* now compare the prefix */
3848     if (strncmp(input, matchbuf, l) == 0) {
3849         rewritelog(r, 5, "strip matching prefix: %s -> %s", output, output+l);
3850         output = apr_pstrdup(r->pool, output+l);
3851
3852         /* and now add the base-URL as replacement prefix */
3853         l = apr_cpystrn(substbuf, subst, sizeof(substbuf)) - substbuf;
3854         if (substbuf[l-1] != '/') {
3855            substbuf[l] = '/';
3856            substbuf[l+1] = '\0';
3857            l++;
3858         }
3859         if (output[0] == '/') {
3860             rewritelog(r, 4, "add subst prefix: %s -> %s%s",
3861                        output, substbuf, output+1);
3862             output = apr_pstrcat(r->pool, substbuf, output+1, NULL);
3863         }
3864         else {
3865             rewritelog(r, 4, "add subst prefix: %s -> %s%s",
3866                        output, substbuf, output);
3867             output = apr_pstrcat(r->pool, substbuf, output, NULL);
3868         }
3869     }
3870     return output;
3871 }
3872
3873
3874 /*
3875 **
3876 **  own command line parser which don't have the '\\' problem
3877 **
3878 */
3879
3880 static int parseargline(char *str, char **a1, char **a2, char **a3)
3881 {
3882     char *cp;
3883     int isquoted;
3884
3885 #define SKIP_WHITESPACE(cp) \
3886     for ( ; *cp == ' ' || *cp == '\t'; ) { \
3887         cp++; \
3888     };
3889
3890 #define CHECK_QUOTATION(cp,isquoted) \
3891     isquoted = 0; \
3892     if (*cp == '"') { \
3893         isquoted = 1; \
3894         cp++; \
3895     }
3896
3897 #define DETERMINE_NEXTSTRING(cp,isquoted) \
3898     for ( ; *cp != '\0'; cp++) { \
3899         if (   (isquoted    && (*cp     == ' ' || *cp     == '\t')) \
3900             || (*cp == '\\' && (*(cp+1) == ' ' || *(cp+1) == '\t'))) { \
3901             cp++; \
3902             continue; \
3903         } \
3904         if (   (!isquoted && (*cp == ' ' || *cp == '\t')) \
3905             || (isquoted  && *cp == '"')                  ) { \
3906             break; \
3907         } \
3908     }
3909
3910     cp = str;
3911     SKIP_WHITESPACE(cp);
3912
3913     /*  determine first argument */
3914     CHECK_QUOTATION(cp, isquoted);
3915     *a1 = cp;
3916     DETERMINE_NEXTSTRING(cp, isquoted);
3917     if (*cp == '\0') {
3918         return 1;
3919     }
3920     *cp++ = '\0';
3921
3922     SKIP_WHITESPACE(cp);
3923
3924     /*  determine second argument */
3925     CHECK_QUOTATION(cp, isquoted);
3926     *a2 = cp;
3927     DETERMINE_NEXTSTRING(cp, isquoted);
3928     if (*cp == '\0') {
3929         *cp++ = '\0';
3930         *a3 = NULL;
3931         return 0;
3932     }
3933     *cp++ = '\0';
3934
3935     SKIP_WHITESPACE(cp);
3936
3937     /* again check if there are only two arguments */
3938     if (*cp == '\0') {
3939         *cp++ = '\0';
3940         *a3 = NULL;
3941         return 0;
3942     }
3943
3944     /*  determine second argument */
3945     CHECK_QUOTATION(cp, isquoted);
3946     *a3 = cp;
3947     DETERMINE_NEXTSTRING(cp, isquoted);
3948     *cp++ = '\0';
3949
3950     return 0;
3951 }
3952
3953
3954 static void add_env_variable(request_rec *r, char *s)
3955 {
3956     char var[MAX_STRING_LEN];
3957     char val[MAX_STRING_LEN];
3958     char *cp;
3959     int n;
3960
3961     if ((cp = strchr(s, ':')) != NULL) {
3962         n = ((cp-s) > MAX_STRING_LEN-1 ? MAX_STRING_LEN-1 : (cp-s));
3963         memcpy(var, s, n);
3964         var[n] = '\0';
3965         apr_cpystrn(val, cp+1, sizeof(val));
3966         apr_table_set(r->subprocess_env, var, val);
3967         rewritelog(r, 5, "setting env variable '%s' to '%s'", var, val);
3968     }
3969 }
3970
3971
3972 /*
3973 **
3974 **  check that a subrequest won't cause infinite recursion
3975 **
3976 */
3977
3978 static int subreq_ok(request_rec *r)
3979 {
3980     /*
3981      * either not in a subrequest, or in a subrequest
3982      * and URIs aren't NULL and sub/main URIs differ
3983      */
3984     return (r->main == NULL ||
3985             (r->main->uri != NULL && r->uri != NULL &&
3986              strcmp(r->main->uri, r->uri) != 0));
3987 }
3988
3989
3990 /*
3991 **
3992 **  stat() for only the prefix of a path
3993 **
3994 */
3995
3996 static int prefix_stat(const char *path, apr_finfo_t *sb)
3997 {
3998     char curpath[LONG_STRING_LEN];
3999     char *cp;
4000
4001     apr_cpystrn(curpath, path, sizeof(curpath));
4002     if (curpath[0] != '/') {
4003         return 0;
4004     }
4005     if ((cp = strchr(curpath+1, '/')) != NULL) {
4006         *cp = '\0';
4007     }
4008     if (apr_stat(sb, curpath, APR_FINFO_MIN, NULL) == APR_SUCCESS) {
4009         return 1;
4010     }
4011     else {
4012         return 0;
4013     }
4014 }
4015
4016
4017 /*
4018 **
4019 **  Lexicographic Compare
4020 **
4021 */
4022
4023 static int compare_lexicography(char *cpNum1, char *cpNum2)
4024 {
4025     int i;
4026     int n1, n2;
4027
4028     n1 = strlen(cpNum1);
4029     n2 = strlen(cpNum2);
4030     if (n1 > n2) {
4031         return 1;
4032     }
4033     if (n1 < n2) {
4034         return -1;
4035     }
4036     for (i = 0; i < n1; i++) {
4037         if (cpNum1[i] > cpNum2[i]) {
4038             return 1;
4039         }
4040         if (cpNum1[i] < cpNum2[i]) {
4041             return -1;
4042         }
4043     }
4044     return 0;
4045 }
4046
4047 /*
4048 **
4049 **  Bracketed expression handling
4050 **  s points after the opening bracket
4051 **
4052 */
4053
4054 static char *find_closing_bracket(char *s, int left, int right)
4055 {
4056     int depth;
4057
4058     for (depth = 1; *s; ++s) {
4059         if (*s == right && --depth == 0) {
4060             return s;
4061         }
4062         else if (*s == left) {
4063             ++depth;
4064         }
4065     }
4066     return NULL;
4067 }
4068
4069 static char *find_char_in_brackets(char *s, int c, int left, int right)
4070 {
4071     int depth;
4072
4073     for (depth = 1; *s; ++s) {
4074         if (*s == c && depth == 1) {
4075             return s;
4076         }
4077         else if (*s == right && --depth == 0) {
4078             return NULL;
4079         }
4080         else if (*s == left) {
4081             ++depth;
4082         }
4083     }
4084     return NULL;
4085 }
4086
4087 /*
4088 **
4089 ** Module paraphernalia
4090 **
4091 */
4092
4093 #ifdef NETWARE
4094 int main(int argc, char *argv[]) 
4095 {
4096     ExitThread(TSR_THREAD, 0);
4097 }
4098 #endif
4099
4100     /* the apr_table_t of commands we provide */
4101 static const command_rec command_table[] = {
4102     AP_INIT_FLAG(    "RewriteEngine",   cmd_rewriteengine,  NULL, OR_FILEINFO,
4103                      "On or Off to enable or disable (default) the whole "
4104                      "rewriting engine"),
4105     AP_INIT_ITERATE( "RewriteOptions",  cmd_rewriteoptions,  NULL, OR_FILEINFO,
4106                      "List of option strings to set"),
4107     AP_INIT_TAKE1(   "RewriteBase",     cmd_rewritebase,     NULL, OR_FILEINFO, 
4108                      "the base URL of the per-directory context"),
4109     AP_INIT_RAW_ARGS("RewriteCond",     cmd_rewritecond,     NULL, OR_FILEINFO,
4110                      "an input string and a to be applied regexp-pattern"),
4111     AP_INIT_RAW_ARGS("RewriteRule",     cmd_rewriterule,     NULL, OR_FILEINFO,
4112                      "an URL-applied regexp-pattern and a substitution URL"),
4113     AP_INIT_TAKE2(   "RewriteMap",      cmd_rewritemap,      NULL, RSRC_CONF,
4114                      "a mapname and a filename"),
4115     AP_INIT_TAKE1(   "RewriteLock",     cmd_rewritelock,     NULL, RSRC_CONF,
4116                      "the filename of a lockfile used for inter-process "
4117                      "synchronization"),
4118     AP_INIT_TAKE1(   "RewriteLog",      cmd_rewritelog,      NULL, RSRC_CONF,
4119                      "the filename of the rewriting logfile"),
4120     AP_INIT_TAKE1(   "RewriteLogLevel", cmd_rewriteloglevel, NULL, RSRC_CONF,
4121                      "the level of the rewriting logfile verbosity "
4122                      "(0=none, 1=std, .., 9=max)"),
4123     { NULL }
4124 };
4125
4126 static void register_hooks(apr_pool_t *p)
4127 {
4128     ap_hook_handler(handler_redirect, NULL, NULL, APR_HOOK_MIDDLE);
4129     ap_hook_post_config(init_module,NULL,NULL,APR_HOOK_MIDDLE);
4130     ap_hook_child_init(init_child,NULL,NULL,APR_HOOK_MIDDLE);
4131
4132     ap_hook_fixups(hook_fixup,NULL,NULL,APR_HOOK_FIRST);
4133     ap_hook_translate_name(hook_uri2file,NULL,NULL,APR_HOOK_FIRST);
4134     ap_hook_type_checker(hook_mimetype,NULL,NULL,APR_HOOK_MIDDLE);
4135 }
4136
4137     /* the main config structure */
4138 module AP_MODULE_DECLARE_DATA rewrite_module = {
4139    STANDARD20_MODULE_STUFF,
4140    config_perdir_create,        /* create per-dir    config structures */
4141    config_perdir_merge,         /* merge  per-dir    config structures */
4142    config_server_create,        /* create per-server config structures */
4143    config_server_merge,         /* merge  per-server config structures */
4144    command_table,               /* apr_table_t of config file commands  */
4145    register_hooks               /* register hooks                      */
4146 };
4147  
4148 /*EOF*/