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