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