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