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