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