1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
19 * 3. The end-user documentation included with the redistribution,
20 * if any, must include the following acknowledgment:
21 * "This product includes software developed by the
22 * Apache Software Foundation (http://www.apache.org/)."
23 * Alternately, this acknowledgment may appear in the software itself,
24 * if and wherever such third-party acknowledgments normally appear.
26 * 4. The names "Apache" and "Apache Software Foundation" must
27 * not be used to endorse or promote products derived from this
28 * software without prior written permission. For written
29 * permission, please contact apache@apache.org.
31 * 5. Products derived from this software may not be called "Apache",
32 * nor may "Apache" appear in their name, without prior written
33 * permission of the Apache Software Foundation.
35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Software Foundation. For more
51 * information on the Apache Software Foundation, please see
52 * <http://www.apache.org/>.
54 * Portions of this software are based upon public domain software
55 * originally written at the National Center for Supercomputing Applications,
56 * University of Illinois, Urbana-Champaign.
60 ** _ __ ___ ___ __| | _ __ _____ ___ __(_) |_ ___
61 ** | '_ ` _ \ / _ \ / _` | | '__/ _ \ \ /\ / / '__| | __/ _ \
62 ** | | | | | | (_) | (_| | | | | __/\ V V /| | | | || __/
63 ** |_| |_| |_|\___/ \__,_|___|_| \___| \_/\_/ |_| |_|\__\___|
66 ** URL Rewriting Module
68 ** This module uses a rule-based rewriting engine (based on a
69 ** regular-expression parser) to rewrite requested URLs on the fly.
71 ** It supports an unlimited number of additional rule conditions (which can
72 ** operate on a lot of variables, even on HTTP headers) for granular
73 ** matching and even external database lookups (either via plain text
74 ** tables, DBM hash files or even external processes) for advanced URL
77 ** It operates on the full URLs (including the PATH_INFO part) both in
78 ** per-server context (httpd.conf) and per-dir context (.htaccess) and even
79 ** can generate QUERY_STRING parts on result. The rewriting result finally
80 ** can lead to internal subprocessing, external request redirection or even
81 ** to internal proxy throughput.
83 ** This module was originally written in April 1996 and
84 ** gifted exclusively to the The Apache Software Foundation in July 1997 by
86 ** Ralf S. Engelschall
87 ** rse@engelschall.com
88 ** www.engelschall.com
91 #include "ap_config.h"
93 #include "http_config.h"
94 #include "http_request.h"
95 #include "http_core.h"
97 #include "http_protocol.h"
98 #include "mod_rewrite.h"
100 #if !defined(OS2) && !defined(WIN32)
106 #ifdef HAVE_SYS_TYPES_H
107 #include <sys/types.h>
110 #ifdef HAVE_SYS_UIO_H
125 ** +-------------------------------------------------------+
127 ** | static module configuration
129 ** +-------------------------------------------------------+
134 ** Our interface to the Apache server kernel:
136 ** o Runtime logic of a request is as following:
137 ** while(request or subrequest)
138 ** foreach(stage #0...#9)
139 ** foreach(module) (**)
142 ** o the order of modules at (**) is the inverted order as
143 ** given in the "Configuration" file, i.e. the last module
144 ** specified is the first one called for each hook!
145 ** The core module is always the last!
147 ** o there are two different types of result checking and
148 ** continue processing:
149 ** for hook #0,#1,#4,#5,#6,#8:
150 ** hook run loop stops on first modules which gives
151 ** back a result != DECLINED, i.e. it usually returns OK
152 ** which says "OK, module has handled this _stage_" and for #1
153 ** this have not to mean "Ok, the filename is now valid".
154 ** for hook #2,#3,#7,#9:
155 ** all hooks are run, independend of result
157 ** o at the last stage, the core module always
158 ** - says "HTTP_BAD_REQUEST" if r->filename does not begin with "/"
159 ** - prefix URL with document_root or replaced server_root
160 ** with document_root and sets r->filename
161 ** - always return a "OK" independed if the file really exists
165 /* The section for the Configure script:
166 * XXX: this needs updating for apache-2.0 configuration method
167 * MODULE-DEFINITION-START
168 * Name: rewrite_module
170 . ./helpers/find-dbm-lib
171 if [ "x$found_dbm" = "x1" ]; then
172 echo " enabling DBM support for mod_rewrite"
174 echo " disabling DBM support for mod_rewrite"
175 echo " (perhaps you need to add -ldbm, -lndbm or -lgdbm to EXTRA_LIBS)"
176 CFLAGS="$CFLAGS -DNO_DBM_REWRITEMAP"
179 * MODULE-DEFINITION-END
182 /* the ap_table_t of commands we provide */
183 static const command_rec command_table[] = {
184 { "RewriteEngine", cmd_rewriteengine, NULL, OR_FILEINFO, FLAG,
185 "On or Off to enable or disable (default) the whole rewriting engine" },
186 { "RewriteOptions", cmd_rewriteoptions, NULL, OR_FILEINFO, ITERATE,
187 "List of option strings to set" },
188 { "RewriteBase", cmd_rewritebase, NULL, OR_FILEINFO, TAKE1,
189 "the base URL of the per-directory context" },
190 { "RewriteCond", cmd_rewritecond, NULL, OR_FILEINFO, RAW_ARGS,
191 "an input string and a to be applied regexp-pattern" },
192 { "RewriteRule", cmd_rewriterule, NULL, OR_FILEINFO, RAW_ARGS,
193 "an URL-applied regexp-pattern and a substitution URL" },
194 { "RewriteMap", cmd_rewritemap, NULL, RSRC_CONF, TAKE2,
195 "a mapname and a filename" },
196 { "RewriteLock", cmd_rewritelock, NULL, RSRC_CONF, TAKE1,
197 "the filename of a lockfile used for inter-process synchronization"},
198 { "RewriteLog", cmd_rewritelog, NULL, RSRC_CONF, TAKE1,
199 "the filename of the rewriting logfile" },
200 { "RewriteLogLevel", cmd_rewriteloglevel, NULL, RSRC_CONF, TAKE1,
201 "the level of the rewriting logfile verbosity "
202 "(0=none, 1=std, .., 9=max)" },
206 /* the ap_table_t of content handlers we provide */
207 static const handler_rec handler_table[] = {
208 { "redirect-handler", handler_redirect },
212 static void register_hooks(void)
214 ap_hook_post_config(init_module,NULL,NULL,AP_HOOK_MIDDLE);
215 ap_hook_child_init(init_child,NULL,NULL,AP_HOOK_MIDDLE);
217 ap_hook_fixups(hook_fixup,NULL,NULL,AP_HOOK_FIRST);
218 ap_hook_translate_name(hook_uri2file,NULL,NULL,AP_HOOK_FIRST);
219 ap_hook_type_checker(hook_mimetype,NULL,NULL,AP_HOOK_MIDDLE);
222 /* the main config structure */
223 module MODULE_VAR_EXPORT rewrite_module = {
224 STANDARD20_MODULE_STUFF,
225 config_perdir_create, /* create per-dir config structures */
226 config_perdir_merge, /* merge per-dir config structures */
227 config_server_create, /* create per-server config structures */
228 config_server_merge, /* merge per-server config structures */
229 command_table, /* ap_table_t of config file commands */
230 handler_table, /* [#8] MIME-typed-dispatched handlers */
231 register_hooks /* register hooks */
235 static cache *cachep;
237 /* whether proxy module is available or not */
238 static int proxy_available;
239 static int once_through = 0;
241 static const char *lockname;
242 static ap_lock_t *rewrite_map_lock = NULL;
243 static ap_lock_t *rewrite_log_lock = NULL;
246 ** +-------------------------------------------------------+
248 ** | configuration directive handling
250 ** +-------------------------------------------------------+
255 ** per-server configuration structure handling
259 static void *config_server_create(ap_pool_t *p, server_rec *s)
261 rewrite_server_conf *a;
263 a = (rewrite_server_conf *)ap_pcalloc(p, sizeof(rewrite_server_conf));
265 a->state = ENGINE_DISABLED;
266 a->options = OPTION_NONE;
267 a->rewritelogfile = NULL;
268 a->rewritelogfp = NULL;
269 a->rewriteloglevel = 0;
270 a->rewritemaps = ap_make_array(p, 2, sizeof(rewritemap_entry));
271 a->rewriteconds = ap_make_array(p, 2, sizeof(rewritecond_entry));
272 a->rewriterules = ap_make_array(p, 2, sizeof(rewriterule_entry));
278 static void *config_server_merge(ap_pool_t *p, void *basev, void *overridesv)
280 rewrite_server_conf *a, *base, *overrides;
282 a = (rewrite_server_conf *)ap_pcalloc(p, sizeof(rewrite_server_conf));
283 base = (rewrite_server_conf *)basev;
284 overrides = (rewrite_server_conf *)overridesv;
286 a->state = overrides->state;
287 a->options = overrides->options;
288 a->server = overrides->server;
290 if (a->options & OPTION_INHERIT) {
292 * local directives override
293 * and anything else is inherited
295 a->rewriteloglevel = overrides->rewriteloglevel != 0
296 ? overrides->rewriteloglevel
297 : base->rewriteloglevel;
298 a->rewritelogfile = overrides->rewritelogfile != NULL
299 ? overrides->rewritelogfile
300 : base->rewritelogfile;
301 a->rewritelogfp = overrides->rewritelogfp != NULL
302 ? overrides->rewritelogfp
303 : base->rewritelogfp;
304 a->rewritemaps = ap_append_arrays(p, overrides->rewritemaps,
306 a->rewriteconds = ap_append_arrays(p, overrides->rewriteconds,
308 a->rewriterules = ap_append_arrays(p, overrides->rewriterules,
313 * local directives override
314 * and anything else gets defaults
316 a->rewriteloglevel = overrides->rewriteloglevel;
317 a->rewritelogfile = overrides->rewritelogfile;
318 a->rewritelogfp = overrides->rewritelogfp;
319 a->rewritemaps = overrides->rewritemaps;
320 a->rewriteconds = overrides->rewriteconds;
321 a->rewriterules = overrides->rewriterules;
330 ** per-directory configuration structure handling
334 static void *config_perdir_create(ap_pool_t *p, char *path)
336 rewrite_perdir_conf *a;
338 a = (rewrite_perdir_conf *)ap_pcalloc(p, sizeof(rewrite_perdir_conf));
340 a->state = ENGINE_DISABLED;
341 a->options = OPTION_NONE;
343 a->rewriteconds = ap_make_array(p, 2, sizeof(rewritecond_entry));
344 a->rewriterules = ap_make_array(p, 2, sizeof(rewriterule_entry));
350 /* make sure it has a trailing slash */
351 if (path[strlen(path)-1] == '/') {
352 a->directory = ap_pstrdup(p, path);
355 a->directory = ap_pstrcat(p, path, "/", NULL);
362 static void *config_perdir_merge(ap_pool_t *p, void *basev, void *overridesv)
364 rewrite_perdir_conf *a, *base, *overrides;
366 a = (rewrite_perdir_conf *)ap_pcalloc(p,
367 sizeof(rewrite_perdir_conf));
368 base = (rewrite_perdir_conf *)basev;
369 overrides = (rewrite_perdir_conf *)overridesv;
371 a->state = overrides->state;
372 a->options = overrides->options;
373 a->directory = overrides->directory;
374 a->baseurl = overrides->baseurl;
376 if (a->options & OPTION_INHERIT) {
377 a->rewriteconds = ap_append_arrays(p, overrides->rewriteconds,
379 a->rewriterules = ap_append_arrays(p, overrides->rewriterules,
383 a->rewriteconds = overrides->rewriteconds;
384 a->rewriterules = overrides->rewriterules;
393 ** the configuration commands
397 static const char *cmd_rewriteengine(cmd_parms *cmd,
398 rewrite_perdir_conf *dconf, int flag)
400 rewrite_server_conf *sconf;
403 (rewrite_server_conf *)ap_get_module_config(cmd->server->module_config,
406 if (cmd->path == NULL) { /* is server command */
407 sconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
409 else /* is per-directory command */ {
410 dconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
416 static const char *cmd_rewriteoptions(cmd_parms *cmd,
417 rewrite_perdir_conf *dconf, char *option)
419 rewrite_server_conf *sconf;
422 sconf = (rewrite_server_conf *)
423 ap_get_module_config(cmd->server->module_config, &rewrite_module);
425 if (cmd->path == NULL) { /* is server command */
426 err = cmd_rewriteoptions_setoption(cmd->pool,
427 &(sconf->options), option);
429 else { /* is per-directory command */
430 err = cmd_rewriteoptions_setoption(cmd->pool,
431 &(dconf->options), option);
437 static const char *cmd_rewriteoptions_setoption(ap_pool_t *p, int *options,
440 if (strcasecmp(name, "inherit") == 0) {
441 *options |= OPTION_INHERIT;
444 return ap_pstrcat(p, "RewriteOptions: unknown option '",
450 static const char *cmd_rewritelog(cmd_parms *cmd, void *dconf, char *a1)
452 rewrite_server_conf *sconf;
454 sconf = (rewrite_server_conf *)
455 ap_get_module_config(cmd->server->module_config, &rewrite_module);
457 sconf->rewritelogfile = a1;
462 static const char *cmd_rewriteloglevel(cmd_parms *cmd, void *dconf, char *a1)
464 rewrite_server_conf *sconf;
466 sconf = (rewrite_server_conf *)
467 ap_get_module_config(cmd->server->module_config, &rewrite_module);
469 sconf->rewriteloglevel = atoi(a1);
474 static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, char *a1,
477 rewrite_server_conf *sconf;
478 rewritemap_entry *newmap;
481 sconf = (rewrite_server_conf *)
482 ap_get_module_config(cmd->server->module_config, &rewrite_module);
484 newmap = ap_push_array(sconf->rewritemaps);
488 if (strncmp(a2, "txt:", 4) == 0) {
489 newmap->type = MAPTYPE_TXT;
490 newmap->datafile = a2+4;
491 newmap->checkfile = a2+4;
493 else if (strncmp(a2, "rnd:", 4) == 0) {
494 newmap->type = MAPTYPE_RND;
495 newmap->datafile = a2+4;
496 newmap->checkfile = a2+4;
498 else if (strncmp(a2, "dbm:", 4) == 0) {
499 #ifndef NO_DBM_REWRITEMAP
500 newmap->type = MAPTYPE_DBM;
501 newmap->datafile = a2+4;
502 newmap->checkfile = ap_pstrcat(cmd->pool, a2+4, NDBM_FILE_SUFFIX, NULL);
504 return ap_pstrdup(cmd->pool, "RewriteMap: cannot use NDBM mapfile, "
505 "because no NDBM support is compiled in");
508 else if (strncmp(a2, "prg:", 4) == 0) {
509 newmap->type = MAPTYPE_PRG;
510 newmap->datafile = a2+4;
511 newmap->checkfile = a2+4;
513 else if (strncmp(a2, "int:", 4) == 0) {
514 newmap->type = MAPTYPE_INT;
515 newmap->datafile = NULL;
516 newmap->checkfile = NULL;
517 if (strcmp(a2+4, "tolower") == 0) {
518 newmap->func = rewrite_mapfunc_tolower;
520 else if (strcmp(a2+4, "toupper") == 0) {
521 newmap->func = rewrite_mapfunc_toupper;
523 else if (strcmp(a2+4, "escape") == 0) {
524 newmap->func = rewrite_mapfunc_escape;
526 else if (strcmp(a2+4, "unescape") == 0) {
527 newmap->func = rewrite_mapfunc_unescape;
529 else if (sconf->state == ENGINE_ENABLED) {
530 return ap_pstrcat(cmd->pool, "RewriteMap: internal map not found:",
535 newmap->type = MAPTYPE_TXT;
536 newmap->datafile = a2;
537 newmap->checkfile = a2;
540 newmap->fpout = NULL;
542 if (newmap->checkfile && (sconf->state == ENGINE_ENABLED)
543 && (ap_stat(&st, newmap->checkfile, cmd->pool) != APR_SUCCESS)) {
544 return ap_pstrcat(cmd->pool,
545 "RewriteMap: map file or program not found:",
546 newmap->checkfile, NULL);
552 static const char *cmd_rewritelock(cmd_parms *cmd, void *dconf, char *a1)
556 if ((error = ap_check_cmd_context(cmd, GLOBAL_ONLY)) != NULL)
564 static const char *cmd_rewritebase(cmd_parms *cmd, rewrite_perdir_conf *dconf,
567 if (cmd->path == NULL || dconf == NULL) {
568 return "RewriteBase: only valid in per-directory config files";
571 return "RewriteBase: empty URL not allowed";
574 return "RewriteBase: argument is not a valid URL";
582 static const char *cmd_rewritecond(cmd_parms *cmd, rewrite_perdir_conf *dconf,
585 rewrite_server_conf *sconf;
586 rewritecond_entry *newcond;
595 sconf = (rewrite_server_conf *)
596 ap_get_module_config(cmd->server->module_config, &rewrite_module);
598 /* make a new entry in the internal temporary rewrite rule list */
599 if (cmd->path == NULL) { /* is server command */
600 newcond = ap_push_array(sconf->rewriteconds);
602 else { /* is per-directory command */
603 newcond = ap_push_array(dconf->rewriteconds);
606 /* parse the argument line ourself */
607 if (parseargline(str, &a1, &a2, &a3)) {
608 return ap_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str,
612 /* arg1: the input string */
613 newcond->input = ap_pstrdup(cmd->pool, a1);
615 /* arg3: optional flags field
616 (this have to be first parsed, because we need to
617 know if the regex should be compiled with ICASE!) */
618 newcond->flags = CONDFLAG_NONE;
620 if ((err = cmd_rewritecond_parseflagfield(cmd->pool, newcond,
627 try to compile the regexp to test if is ok */
630 newcond->flags |= CONDFLAG_NOTMATCH;
634 /* now be careful: Under the POSIX regex library
635 we can compile the pattern for case insensitive matching,
636 under the old V8 library we have to do it self via a hack */
637 if (newcond->flags & CONDFLAG_NOCASE) {
638 rc = ((regexp = ap_pregcomp(cmd->pool, cp, REG_EXTENDED|REG_ICASE))
642 rc = ((regexp = ap_pregcomp(cmd->pool, cp, REG_EXTENDED)) == NULL);
645 return ap_pstrcat(cmd->pool,
646 "RewriteCond: cannot compile regular expression '",
650 newcond->pattern = ap_pstrdup(cmd->pool, cp);
651 newcond->regexp = regexp;
656 static const char *cmd_rewritecond_parseflagfield(ap_pool_t *p,
657 rewritecond_entry *cfg,
668 if (str[0] != '[' || str[strlen(str)-1] != ']') {
669 return "RewriteCond: bad flag delimiters";
673 str[strlen(str)-1] = ','; /* for simpler parsing */
674 for ( ; *cp != '\0'; ) {
675 /* skip whitespaces */
676 for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++)
682 if ((cp2 = strchr(cp, ',')) != NULL) {
684 for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--)
687 if ((cp3 = strchr(cp1, '=')) != NULL) {
696 if ((err = cmd_rewritecond_setflag(p, cfg, key, val)) != NULL) {
708 static const char *cmd_rewritecond_setflag(ap_pool_t *p, rewritecond_entry *cfg,
709 char *key, char *val)
711 if ( strcasecmp(key, "nocase") == 0
712 || strcasecmp(key, "NC") == 0 ) {
713 cfg->flags |= CONDFLAG_NOCASE;
715 else if ( strcasecmp(key, "ornext") == 0
716 || strcasecmp(key, "OR") == 0 ) {
717 cfg->flags |= CONDFLAG_ORNEXT;
720 return ap_pstrcat(p, "RewriteCond: unknown flag '", key, "'\n", NULL);
725 static const char *cmd_rewriterule(cmd_parms *cmd, rewrite_perdir_conf *dconf,
728 rewrite_server_conf *sconf;
729 rewriterule_entry *newrule;
738 sconf = (rewrite_server_conf *)
739 ap_get_module_config(cmd->server->module_config, &rewrite_module);
741 /* make a new entry in the internal rewrite rule list */
742 if (cmd->path == NULL) { /* is server command */
743 newrule = ap_push_array(sconf->rewriterules);
745 else { /* is per-directory command */
746 newrule = ap_push_array(dconf->rewriterules);
749 /* parse the argument line ourself */
750 if (parseargline(str, &a1, &a2, &a3)) {
751 return ap_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str,
755 /* arg3: optional flags field */
756 newrule->forced_mimetype = NULL;
757 newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY;
758 newrule->flags = RULEFLAG_NONE;
759 newrule->env[0] = NULL;
762 if ((err = cmd_rewriterule_parseflagfield(cmd->pool, newrule,
769 * try to compile the regexp to test if is ok
773 newrule->flags |= RULEFLAG_NOTMATCH;
777 if (newrule->flags & RULEFLAG_NOCASE) {
780 if ((regexp = ap_pregcomp(cmd->pool, cp, mode)) == NULL) {
781 return ap_pstrcat(cmd->pool,
782 "RewriteRule: cannot compile regular expression '",
785 newrule->pattern = ap_pstrdup(cmd->pool, cp);
786 newrule->regexp = regexp;
788 /* arg2: the output string
789 * replace the $<N> by \<n> which is needed by the currently
790 * used Regular Expression library
792 newrule->output = ap_pstrdup(cmd->pool, a2);
794 /* now, if the server or per-dir config holds an
795 * array of RewriteCond entries, we take it for us
796 * and clear the array
798 if (cmd->path == NULL) { /* is server command */
799 newrule->rewriteconds = sconf->rewriteconds;
800 sconf->rewriteconds = ap_make_array(cmd->pool, 2,
801 sizeof(rewritecond_entry));
803 else { /* is per-directory command */
804 newrule->rewriteconds = dconf->rewriteconds;
805 dconf->rewriteconds = ap_make_array(cmd->pool, 2,
806 sizeof(rewritecond_entry));
812 static const char *cmd_rewriterule_parseflagfield(ap_pool_t *p,
813 rewriterule_entry *cfg,
824 if (str[0] != '[' || str[strlen(str)-1] != ']') {
825 return "RewriteRule: bad flag delimiters";
829 str[strlen(str)-1] = ','; /* for simpler parsing */
830 for ( ; *cp != '\0'; ) {
831 /* skip whitespaces */
832 for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++)
838 if ((cp2 = strchr(cp, ',')) != NULL) {
840 for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--)
843 if ((cp3 = strchr(cp1, '=')) != NULL) {
852 if ((err = cmd_rewriterule_setflag(p, cfg, key, val)) != NULL) {
864 static const char *cmd_rewriterule_setflag(ap_pool_t *p, rewriterule_entry *cfg,
865 char *key, char *val)
870 if ( strcasecmp(key, "redirect") == 0
871 || strcasecmp(key, "R") == 0 ) {
872 cfg->flags |= RULEFLAG_FORCEREDIRECT;
873 if (strlen(val) > 0) {
874 if (strcasecmp(val, "permanent") == 0) {
875 status = HTTP_MOVED_PERMANENTLY;
877 else if (strcasecmp(val, "temp") == 0) {
878 status = HTTP_MOVED_TEMPORARILY;
880 else if (strcasecmp(val, "seeother") == 0) {
881 status = HTTP_SEE_OTHER;
883 else if (ap_isdigit(*val)) {
886 if (!ap_is_HTTP_REDIRECT(status)) {
887 return "RewriteRule: invalid HTTP response code "
890 cfg->forced_responsecode = status;
893 else if ( strcasecmp(key, "last") == 0
894 || strcasecmp(key, "L") == 0 ) {
895 cfg->flags |= RULEFLAG_LASTRULE;
897 else if ( strcasecmp(key, "next") == 0
898 || strcasecmp(key, "N") == 0 ) {
899 cfg->flags |= RULEFLAG_NEWROUND;
901 else if ( strcasecmp(key, "chain") == 0
902 || strcasecmp(key, "C") == 0 ) {
903 cfg->flags |= RULEFLAG_CHAIN;
905 else if ( strcasecmp(key, "type") == 0
906 || strcasecmp(key, "T") == 0 ) {
907 cfg->forced_mimetype = ap_pstrdup(p, val);
908 ap_str_tolower(cfg->forced_mimetype);
910 else if ( strcasecmp(key, "env") == 0
911 || strcasecmp(key, "E") == 0 ) {
912 for (i = 0; (cfg->env[i] != NULL) && (i < MAX_ENV_FLAGS); i++)
914 if (i < MAX_ENV_FLAGS) {
915 cfg->env[i] = ap_pstrdup(p, val);
916 cfg->env[i+1] = NULL;
919 return "RewriteRule: too many environment flags 'E'";
922 else if ( strcasecmp(key, "nosubreq") == 0
923 || strcasecmp(key, "NS") == 0 ) {
924 cfg->flags |= RULEFLAG_IGNOREONSUBREQ;
926 else if ( strcasecmp(key, "proxy") == 0
927 || strcasecmp(key, "P") == 0 ) {
928 cfg->flags |= RULEFLAG_PROXY;
930 else if ( strcasecmp(key, "passthrough") == 0
931 || strcasecmp(key, "PT") == 0 ) {
932 cfg->flags |= RULEFLAG_PASSTHROUGH;
934 else if ( strcasecmp(key, "skip") == 0
935 || strcasecmp(key, "S") == 0 ) {
936 cfg->skip = atoi(val);
938 else if ( strcasecmp(key, "forbidden") == 0
939 || strcasecmp(key, "F") == 0 ) {
940 cfg->flags |= RULEFLAG_FORBIDDEN;
942 else if ( strcasecmp(key, "gone") == 0
943 || strcasecmp(key, "G") == 0 ) {
944 cfg->flags |= RULEFLAG_GONE;
946 else if ( strcasecmp(key, "qsappend") == 0
947 || strcasecmp(key, "QSA") == 0 ) {
948 cfg->flags |= RULEFLAG_QSAPPEND;
950 else if ( strcasecmp(key, "nocase") == 0
951 || strcasecmp(key, "NC") == 0 ) {
952 cfg->flags |= RULEFLAG_NOCASE;
955 return ap_pstrcat(p, "RewriteRule: unknown flag '", key, "'\n", NULL);
963 ** Global Module Initialization
964 ** [called from read_config() after all
965 ** config commands were already called]
969 static void init_module(ap_pool_t *p,
974 /* check if proxy module is available */
975 proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL);
977 /* create the rewriting lockfiles in the parent */
978 if (ap_create_lock (&rewrite_log_lock, APR_MUTEX, APR_INTRAPROCESS,
979 NULL, NULL) != APR_SUCCESS)
980 exit(1); /* ugly but I can't log anything yet. This is what */
981 /* the pre-existing rewritelock_create code did. */
983 rewritelock_create(s, p);
984 ap_register_cleanup(p, (void *)s, rewritelock_remove, ap_null_cleanup);
986 /* step through the servers and
987 * - open each rewriting logfile
988 * - open the RewriteMap prg:xxx programs
990 for (; s; s = s->next) {
991 open_rewritelog(s, p);
992 if (once_through > 0)
993 run_rewritemap_programs(s, p);
1002 ** Per-Child Module Initialization
1003 ** [called after a child process is spawned]
1007 static void init_child(ap_pool_t *p, server_rec *s)
1010 if (lockname != NULL && *(lockname) != '\0')
1011 ap_child_init_lock (&rewrite_map_lock, lockname, p);
1013 /* create the lookup cache */
1014 cachep = init_cache(p);
1019 ** +-------------------------------------------------------+
1023 ** +-------------------------------------------------------+
1028 ** URI-to-filename hook
1030 ** [used for the rewriting engine triggered by
1031 ** the per-server 'RewriteRule' directives]
1035 static int hook_uri2file(request_rec *r)
1038 rewrite_server_conf *conf;
1040 const char *thisserver;
1042 const char *thisurl;
1053 * retrieve the config structures
1055 sconf = r->server->module_config;
1056 conf = (rewrite_server_conf *)ap_get_module_config(sconf,
1060 * only do something under runtime if the engine is really enabled,
1061 * else return immediately!
1063 if (conf->state == ENGINE_DISABLED) {
1068 * check for the ugly API case of a virtual host section where no
1069 * mod_rewrite directives exists. In this situation we became no chance
1070 * by the API to setup our default per-server config so we have to
1071 * on-the-fly assume we have the default config. But because the default
1072 * config has a disabled rewriting engine we are lucky because can
1073 * just stop operating now.
1075 if (conf->server != r->server) {
1080 * add the SCRIPT_URL variable to the env. this is a bit complicated
1081 * due to the fact that apache uses subrequests and internal redirects
1084 if (r->main == NULL) {
1085 var = ap_pstrcat(r->pool, "REDIRECT_", ENVVAR_SCRIPT_URL, NULL);
1086 var = ap_table_get(r->subprocess_env, var);
1088 ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, r->uri);
1091 ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
1095 var = ap_table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL);
1096 ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
1100 * create the SCRIPT_URI variable for the env
1103 /* add the canonical URI of this URL */
1104 thisserver = ap_get_server_name(r);
1105 port = ap_get_server_port(r);
1106 if (ap_is_default_port(port, r)) {
1110 ap_snprintf(buf, sizeof(buf), ":%u", port);
1113 thisurl = ap_table_get(r->subprocess_env, ENVVAR_SCRIPT_URL);
1115 /* set the variable */
1116 var = ap_pstrcat(r->pool, ap_http_method(r), "://", thisserver, thisport,
1118 ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URI, var);
1120 /* if filename was not initially set,
1121 * we start with the requested URI
1123 if (r->filename == NULL) {
1124 r->filename = ap_pstrdup(r->pool, r->uri);
1125 rewritelog(r, 2, "init rewrite engine with requested uri %s",
1130 * now apply the rules ...
1132 if (apply_rewrite_list(r, conf->rewriterules, NULL)) {
1134 if (strlen(r->filename) > 6 &&
1135 strncmp(r->filename, "proxy:", 6) == 0) {
1136 /* it should be go on as an internal proxy request */
1138 /* check if the proxy module is enabled, so
1139 * we can actually use it!
1141 if (!proxy_available) {
1142 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1143 "attempt to make remote request from mod_rewrite "
1144 "without proxy enabled: %s", r->filename);
1145 return HTTP_FORBIDDEN;
1148 /* make sure the QUERY_STRING and
1149 * PATH_INFO parts get incorporated
1151 if (r->path_info != NULL) {
1152 r->filename = ap_pstrcat(r->pool, r->filename,
1153 r->path_info, NULL);
1155 if (r->args != NULL &&
1156 r->uri == r->unparsed_uri) {
1157 /* see proxy_http:proxy_http_canon() */
1158 r->filename = ap_pstrcat(r->pool, r->filename,
1159 "?", r->args, NULL);
1162 /* now make sure the request gets handled by the proxy handler */
1164 r->handler = "proxy-server";
1166 rewritelog(r, 1, "go-ahead with proxy request %s [OK]",
1170 else if ( (strlen(r->filename) > 7 &&
1171 strncasecmp(r->filename, "http://", 7) == 0)
1172 || (strlen(r->filename) > 8 &&
1173 strncasecmp(r->filename, "https://", 8) == 0)
1174 || (strlen(r->filename) > 9 &&
1175 strncasecmp(r->filename, "gopher://", 9) == 0)
1176 || (strlen(r->filename) > 6 &&
1177 strncasecmp(r->filename, "ftp://", 6) == 0)
1178 || (strlen(r->filename) > 5 &&
1179 strncasecmp(r->filename, "ldap:", 5) == 0)
1180 || (strlen(r->filename) > 5 &&
1181 strncasecmp(r->filename, "news:", 5) == 0)
1182 || (strlen(r->filename) > 7 &&
1183 strncasecmp(r->filename, "mailto:", 7) == 0)) {
1184 /* it was finally rewritten to a remote URL */
1186 /* skip 'scheme:' */
1187 for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1191 /* skip host part */
1192 for ( ; *cp != '/' && *cp != '\0'; cp++)
1195 rewritelog(r, 1, "escaping %s for redirect", r->filename);
1196 cp2 = ap_escape_uri(r->pool, cp);
1198 r->filename = ap_pstrcat(r->pool, r->filename, cp2, NULL);
1201 /* append the QUERY_STRING part */
1202 if (r->args != NULL) {
1203 r->filename = ap_pstrcat(r->pool, r->filename, "?",
1204 ap_escape_uri(r->pool, r->args), NULL);
1207 /* determine HTTP redirect response code */
1208 if (ap_is_HTTP_REDIRECT(r->status)) {
1210 r->status = HTTP_OK; /* make Apache kernel happy */
1213 n = HTTP_MOVED_TEMPORARILY;
1216 /* now do the redirection */
1217 ap_table_setn(r->headers_out, "Location", r->filename);
1218 rewritelog(r, 1, "redirect to %s [REDIRECT/%d]", r->filename, n);
1221 else if (strlen(r->filename) > 10 &&
1222 strncmp(r->filename, "forbidden:", 10) == 0) {
1223 /* This URLs is forced to be forbidden for the requester */
1224 return HTTP_FORBIDDEN;
1226 else if (strlen(r->filename) > 5 &&
1227 strncmp(r->filename, "gone:", 5) == 0) {
1228 /* This URLs is forced to be gone */
1231 else if (strlen(r->filename) > 12 &&
1232 strncmp(r->filename, "passthrough:", 12) == 0) {
1234 * Hack because of underpowered API: passing the current
1235 * rewritten filename through to other URL-to-filename handlers
1236 * just as it were the requested URL. This is to enable
1237 * post-processing by mod_alias, etc. which always act on
1238 * r->uri! The difference here is: We do not try to
1239 * add the document root
1241 r->uri = ap_pstrdup(r->pool, r->filename+12);
1245 /* it was finally rewritten to a local path */
1247 /* expand "/~user" prefix */
1248 #if !defined(WIN32) && !defined(NETWARE)
1249 r->filename = expand_tildepaths(r, r->filename);
1251 rewritelog(r, 2, "local path result: %s", r->filename);
1253 /* the filename has to start with a slash! */
1254 if (r->filename[0] != '/') {
1255 return HTTP_BAD_REQUEST;
1258 /* if there is no valid prefix, we have
1259 * to emulate the translator from the core and
1260 * prefix the filename with document_root
1263 * We cannot leave out the prefix_stat because
1264 * - when we always prefix with document_root
1265 * then no absolute path can be created, e.g. via
1266 * emulating a ScriptAlias directive, etc.
1267 * - when we always NOT prefix with document_root
1268 * then the files under document_root have to
1269 * be references directly and document_root
1270 * gets never used and will be a dummy parameter -
1274 * Under real Unix systems this is no problem,
1275 * because we only do stat() on the first directory
1276 * and this gets cached by the kernel for along time!
1278 n = prefix_stat(r->filename, &finfo);
1280 if ((ccp = ap_document_root(r)) != NULL) {
1281 l = ap_cpystrn(docroot, ccp, sizeof(docroot)) - docroot;
1283 /* always NOT have a trailing slash */
1284 if (docroot[l-1] == '/') {
1285 docroot[l-1] = '\0';
1288 && !strncmp(r->filename, r->server->path,
1289 r->server->pathlen)) {
1290 r->filename = ap_pstrcat(r->pool, docroot,
1292 r->server->pathlen), NULL);
1295 r->filename = ap_pstrcat(r->pool, docroot,
1298 rewritelog(r, 2, "prefixed with document_root to %s",
1303 rewritelog(r, 1, "go-ahead with %s [OK]", r->filename);
1308 rewritelog(r, 1, "pass through %s", r->filename);
1318 ** [used to support the forced-MIME-type feature]
1322 static int hook_mimetype(request_rec *r)
1326 /* now check if we have to force a MIME-type */
1327 t = ap_table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR);
1332 rewritelog(r, 1, "force filename %s to have MIME-type '%s'",
1334 r->content_type = t;
1344 ** [used for the rewriting engine triggered by
1345 ** the per-directory 'RewriteRule' directives]
1349 static int hook_fixup(request_rec *r)
1351 rewrite_perdir_conf *dconf;
1360 dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
1363 /* if there is no per-dir config we return immediately */
1364 if (dconf == NULL) {
1368 /* we shouldn't do anything in subrequests */
1369 if (r->main != NULL) {
1373 /* if there are no real (i.e. no RewriteRule directives!)
1374 per-dir config of us, we return also immediately */
1375 if (dconf->directory == NULL) {
1380 * only do something under runtime if the engine is really enabled,
1381 * for this directory, else return immediately!
1383 if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) {
1384 /* FollowSymLinks is mandatory! */
1385 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1386 "Options FollowSymLinks or SymLinksIfOwnerMatch is off "
1387 "which implies that RewriteRule directive is forbidden: "
1389 return HTTP_FORBIDDEN;
1392 /* FollowSymLinks is given, but the user can
1393 * still turn off the rewriting engine
1395 if (dconf->state == ENGINE_DISABLED) {
1401 * remember the current filename before rewriting for later check
1402 * to prevent deadlooping because of internal redirects
1403 * on final URL/filename which can be equal to the inital one.
1405 ofilename = r->filename;
1408 * now apply the rules ...
1410 if (apply_rewrite_list(r, dconf->rewriterules, dconf->directory)) {
1412 if (strlen(r->filename) > 6 &&
1413 strncmp(r->filename, "proxy:", 6) == 0) {
1414 /* it should go on as an internal proxy request */
1416 /* make sure the QUERY_STRING and
1417 * PATH_INFO parts get incorporated
1418 * (r->path_info was already appended by the
1419 * rewriting engine because of the per-dir context!)
1421 if (r->args != NULL) {
1422 r->filename = ap_pstrcat(r->pool, r->filename,
1423 "?", r->args, NULL);
1426 /* now make sure the request gets handled by the proxy handler */
1428 r->handler = "proxy-server";
1430 rewritelog(r, 1, "[per-dir %s] go-ahead with proxy request "
1431 "%s [OK]", dconf->directory, r->filename);
1434 else if ( (strlen(r->filename) > 7 &&
1435 strncasecmp(r->filename, "http://", 7) == 0)
1436 || (strlen(r->filename) > 8 &&
1437 strncasecmp(r->filename, "https://", 8) == 0)
1438 || (strlen(r->filename) > 9 &&
1439 strncasecmp(r->filename, "gopher://", 9) == 0)
1440 || (strlen(r->filename) > 6 &&
1441 strncasecmp(r->filename, "ftp://", 6) == 0)
1442 || (strlen(r->filename) > 5 &&
1443 strncasecmp(r->filename, "ldap:", 5) == 0)
1444 || (strlen(r->filename) > 5 &&
1445 strncasecmp(r->filename, "news:", 5) == 0)
1446 || (strlen(r->filename) > 7 &&
1447 strncasecmp(r->filename, "mailto:", 7) == 0)) {
1448 /* it was finally rewritten to a remote URL */
1450 /* because we are in a per-dir context
1451 * first try to replace the directory with its base-URL
1452 * if there is a base-URL available
1454 if (dconf->baseurl != NULL) {
1455 /* skip 'scheme:' */
1456 for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1460 if ((cp = strchr(cp, '/')) != NULL) {
1462 "[per-dir %s] trying to replace "
1463 "prefix %s with %s",
1464 dconf->directory, dconf->directory,
1466 cp2 = subst_prefix_path(r, cp, dconf->directory,
1468 if (strcmp(cp2, cp) != 0) {
1470 r->filename = ap_pstrcat(r->pool, r->filename,
1476 /* now prepare the redirect... */
1478 /* skip 'scheme:' */
1479 for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1483 /* skip host part */
1484 for ( ; *cp != '/' && *cp != '\0'; cp++)
1487 rewritelog(r, 1, "[per-dir %s] escaping %s for redirect",
1488 dconf->directory, r->filename);
1489 cp2 = ap_escape_uri(r->pool, cp);
1491 r->filename = ap_pstrcat(r->pool, r->filename, cp2, NULL);
1494 /* append the QUERY_STRING part */
1495 if (r->args != NULL) {
1496 r->filename = ap_pstrcat(r->pool, r->filename, "?",
1497 ap_escape_uri(r->pool, r->args), NULL);
1500 /* determine HTTP redirect response code */
1501 if (ap_is_HTTP_REDIRECT(r->status)) {
1503 r->status = HTTP_OK; /* make Apache kernel happy */
1506 n = HTTP_MOVED_TEMPORARILY;
1509 /* now do the redirection */
1510 ap_table_setn(r->headers_out, "Location", r->filename);
1511 rewritelog(r, 1, "[per-dir %s] redirect to %s [REDIRECT/%d]",
1512 dconf->directory, r->filename, n);
1515 else if (strlen(r->filename) > 10 &&
1516 strncmp(r->filename, "forbidden:", 10) == 0) {
1517 /* This URL is forced to be forbidden for the requester */
1518 return HTTP_FORBIDDEN;
1520 else if (strlen(r->filename) > 5 &&
1521 strncmp(r->filename, "gone:", 5) == 0) {
1522 /* This URL is forced to be gone */
1526 /* it was finally rewritten to a local path */
1528 /* if someone used the PASSTHROUGH flag in per-dir
1529 * context we just ignore it. It is only useful
1530 * in per-server context
1532 if (strlen(r->filename) > 12 &&
1533 strncmp(r->filename, "passthrough:", 12) == 0) {
1534 r->filename = ap_pstrdup(r->pool, r->filename+12);
1537 /* the filename has to start with a slash! */
1538 if (r->filename[0] != '/') {
1539 return HTTP_BAD_REQUEST;
1542 /* Check for deadlooping:
1543 * At this point we KNOW that at least one rewriting
1544 * rule was applied, but when the resulting URL is
1545 * the same as the initial URL, we are not allowed to
1546 * use the following internal redirection stuff because
1547 * this would lead to a deadloop.
1549 if (strcmp(r->filename, ofilename) == 0) {
1550 rewritelog(r, 1, "[per-dir %s] initial URL equal rewritten "
1551 "URL: %s [IGNORING REWRITE]",
1552 dconf->directory, r->filename);
1556 /* if there is a valid base-URL then substitute
1557 * the per-dir prefix with this base-URL if the
1558 * current filename still is inside this per-dir
1559 * context. If not then treat the result as a
1562 if (dconf->baseurl != NULL) {
1564 "[per-dir %s] trying to replace prefix %s with %s",
1565 dconf->directory, dconf->directory, dconf->baseurl);
1566 r->filename = subst_prefix_path(r, r->filename,
1571 /* if no explicit base-URL exists we assume
1572 * that the directory prefix is also a valid URL
1573 * for this webserver and only try to remove the
1574 * document_root if it is prefix
1576 if ((ccp = ap_document_root(r)) != NULL) {
1577 prefix = ap_pstrdup(r->pool, ccp);
1578 /* always NOT have a trailing slash */
1580 if (prefix[l-1] == '/') {
1584 if (strncmp(r->filename, prefix, l) == 0) {
1586 "[per-dir %s] strip document_root "
1588 dconf->directory, r->filename,
1590 r->filename = ap_pstrdup(r->pool, r->filename+l);
1595 /* now initiate the internal redirect */
1596 rewritelog(r, 1, "[per-dir %s] internal redirect with %s "
1597 "[INTERNAL REDIRECT]", dconf->directory, r->filename);
1598 r->filename = ap_pstrcat(r->pool, "redirect:", r->filename, NULL);
1599 r->handler = "redirect-handler";
1604 rewritelog(r, 1, "[per-dir %s] pass through %s",
1605 dconf->directory, r->filename);
1615 ** [used for redirect support]
1619 static int handler_redirect(request_rec *r)
1621 /* just make sure that we are really meant! */
1622 if (strncmp(r->filename, "redirect:", 9) != 0) {
1626 /* now do the internal redirect */
1627 ap_internal_redirect(ap_pstrcat(r->pool, r->filename+9,
1628 r->args ? "?" : NULL, r->args, NULL), r);
1630 /* and return gracefully */
1636 ** +-------------------------------------------------------+
1638 ** | the rewriting engine
1640 ** +-------------------------------------------------------+
1644 * Apply a complete rule set,
1645 * i.e. a list of rewrite rules
1647 static int apply_rewrite_list(request_rec *r, ap_array_header_t *rewriterules,
1650 rewriterule_entry *entries;
1651 rewriterule_entry *p;
1658 * Iterate over all existing rules
1660 entries = (rewriterule_entry *)rewriterules->elts;
1663 for (i = 0; i < rewriterules->nelts; i++) {
1667 * Ignore this rule on subrequests if we are explicitly
1668 * asked to do so or this is a proxy-throughput or a
1669 * forced redirect rule.
1671 if (r->main != NULL &&
1672 (p->flags & RULEFLAG_IGNOREONSUBREQ ||
1673 p->flags & RULEFLAG_PROXY ||
1674 p->flags & RULEFLAG_FORCEREDIRECT )) {
1679 * Apply the current rule.
1681 rc = apply_rewrite_rule(r, p, perdir);
1684 * Indicate a change if this was not a match-only rule.
1691 * Pass-Through Feature (`RewriteRule .. .. [PT]'):
1692 * Because the Apache 1.x API is very limited we
1693 * need this hack to pass the rewritten URL to other
1694 * modules like mod_alias, mod_userdir, etc.
1696 if (p->flags & RULEFLAG_PASSTHROUGH) {
1697 rewritelog(r, 2, "forcing '%s' to get passed through "
1698 "to next API URI-to-filename handler", r->filename);
1699 r->filename = ap_pstrcat(r->pool, "passthrough:",
1706 * Rule has the "forbidden" flag set which means that
1707 * we stop processing and indicate this to the caller.
1709 if (p->flags & RULEFLAG_FORBIDDEN) {
1710 rewritelog(r, 2, "forcing '%s' to be forbidden", r->filename);
1711 r->filename = ap_pstrcat(r->pool, "forbidden:",
1718 * Rule has the "gone" flag set which means that
1719 * we stop processing and indicate this to the caller.
1721 if (p->flags & RULEFLAG_GONE) {
1722 rewritelog(r, 2, "forcing '%s' to be gone", r->filename);
1723 r->filename = ap_pstrcat(r->pool, "gone:", r->filename, NULL);
1729 * Stop processing also on proxy pass-through and
1730 * last-rule and new-round flags.
1732 if (p->flags & RULEFLAG_PROXY) {
1735 if (p->flags & RULEFLAG_LASTRULE) {
1740 * On "new-round" flag we just start from the top of
1741 * the rewriting ruleset again.
1743 if (p->flags & RULEFLAG_NEWROUND) {
1748 * If we are forced to skip N next rules, do it now.
1752 while ( i < rewriterules->nelts
1762 * If current rule is chained with next rule(s),
1763 * skip all this next rule(s)
1765 while ( i < rewriterules->nelts
1766 && p->flags & RULEFLAG_CHAIN) {
1776 * Apply a single(!) rewrite rule
1778 static int apply_rewrite_rule(request_rec *r, rewriterule_entry *p,
1784 char newuri[MAX_STRING_LEN];
1785 char env[MAX_STRING_LEN];
1787 regmatch_t regmatch[MAX_NMATCH];
1788 backrefinfo *briRR = NULL;
1789 backrefinfo *briRC = NULL;
1792 ap_array_header_t *rewriteconds;
1793 rewritecond_entry *conds;
1794 rewritecond_entry *c;
1806 * Add (perhaps splitted away) PATH_INFO postfix to URL to
1807 * make sure we really match against the complete URL.
1809 if (perdir != NULL && r->path_info != NULL && r->path_info[0] != '\0') {
1810 rewritelog(r, 3, "[per-dir %s] add path info postfix: %s -> %s%s",
1811 perdir, uri, uri, r->path_info);
1812 uri = ap_pstrcat(r->pool, uri, r->path_info, NULL);
1816 * On per-directory context (.htaccess) strip the location
1817 * prefix from the URL to make sure patterns apply only to
1818 * the local part. Additionally indicate this special
1819 * threatment in the logfile.
1822 if (perdir != NULL) {
1823 if ( strlen(uri) >= strlen(perdir)
1824 && strncmp(uri, perdir, strlen(perdir)) == 0) {
1825 rewritelog(r, 3, "[per-dir %s] strip per-dir prefix: %s -> %s",
1826 perdir, uri, uri+strlen(perdir));
1827 uri = uri+strlen(perdir);
1833 * Try to match the URI against the RewriteRule pattern
1834 * and exit immeddiately if it didn't apply.
1836 if (perdir == NULL) {
1837 rewritelog(r, 3, "applying pattern '%s' to uri '%s'",
1841 rewritelog(r, 3, "[per-dir %s] applying pattern '%s' to uri '%s'",
1842 perdir, p->pattern, uri);
1844 rc = (ap_regexec(regexp, uri, regexp->re_nsub+1, regmatch, 0) == 0);
1845 if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) ||
1846 (!rc && (p->flags & RULEFLAG_NOTMATCH)) ) ) {
1851 * Else create the RewriteRule `regsubinfo' structure which
1852 * holds the substitution information.
1854 briRR = (backrefinfo *)ap_palloc(r->pool, sizeof(backrefinfo));
1855 if (!rc && (p->flags & RULEFLAG_NOTMATCH)) {
1856 /* empty info on negative patterns */
1861 briRR->source = ap_pstrdup(r->pool, uri);
1862 briRR->nsub = regexp->re_nsub;
1863 memcpy((void *)(briRR->regmatch), (void *)(regmatch),
1868 * Initiallally create the RewriteCond backrefinfo with
1869 * empty backrefinfo, i.e. not subst parts
1870 * (this one is adjusted inside apply_rewrite_cond() later!!)
1872 briRC = (backrefinfo *)ap_pcalloc(r->pool, sizeof(backrefinfo));
1877 * Ok, we already know the pattern has matched, but we now
1878 * additionally have to check for all existing preconditions
1879 * (RewriteCond) which have to be also true. We do this at
1880 * this very late stage to avoid unnessesary checks which
1881 * would slow down the rewriting engine!!
1883 rewriteconds = p->rewriteconds;
1884 conds = (rewritecond_entry *)rewriteconds->elts;
1886 for (i = 0; i < rewriteconds->nelts; i++) {
1888 rc = apply_rewrite_cond(r, c, perdir, briRR, briRC);
1889 if (c->flags & CONDFLAG_ORNEXT) {
1894 /* One condition is false, but another can be
1895 * still true, so we have to continue...
1897 ap_table_unset(r->notes, VARY_KEY_THIS);
1901 /* One true condition is enough in "or" case, so
1902 * skip the other conditions which are "ornext"
1905 while ( i < rewriteconds->nelts
1906 && c->flags & CONDFLAG_ORNEXT) {
1915 * The "AND" case, i.e. no "or" flag,
1916 * so a single failure means total failure.
1923 vary = ap_table_get(r->notes, VARY_KEY_THIS);
1925 ap_table_merge(r->notes, VARY_KEY, vary);
1926 ap_table_unset(r->notes, VARY_KEY_THIS);
1929 /* if any condition fails the complete rule fails */
1931 ap_table_unset(r->notes, VARY_KEY);
1932 ap_table_unset(r->notes, VARY_KEY_THIS);
1937 * Regardless of what we do next, we've found a match. Check to see
1938 * if any of the request header fields were involved, and add them
1939 * to the Vary field of the response.
1941 if ((vary = ap_table_get(r->notes, VARY_KEY)) != NULL) {
1942 ap_table_merge(r->headers_out, "Vary", vary);
1943 ap_table_unset(r->notes, VARY_KEY);
1947 * If this is a pure matching rule (`RewriteRule <pat> -')
1948 * we stop processing and return immediately. The only thing
1949 * we have not to forget are the environment variables
1950 * (`RewriteRule <pat> - [E=...]')
1952 if (strcmp(output, "-") == 0) {
1953 for (i = 0; p->env[i] != NULL; i++) {
1954 /* 1. take the string */
1955 ap_cpystrn(env, p->env[i], sizeof(env));
1956 /* 2. expand $N (i.e. backrefs to RewriteRule pattern) */
1957 expand_backref_inbuffer(r->pool, env, sizeof(env), briRR, '$');
1958 /* 3. expand %N (i.e. backrefs to latest RewriteCond pattern) */
1959 expand_backref_inbuffer(r->pool, env, sizeof(env), briRC, '%');
1960 /* 4. expand %{...} (i.e. variables) */
1961 expand_variables_inbuffer(r, env, sizeof(env));
1962 /* 5. expand ${...} (RewriteMap lookups) */
1963 expand_map_lookups(r, env, sizeof(env));
1964 /* and add the variable to Apache's structures */
1965 add_env_variable(r, env);
1967 if (p->forced_mimetype != NULL) {
1968 if (perdir == NULL) {
1969 /* In the per-server context we can force the MIME-type
1970 * the correct way by notifying our MIME-type hook handler
1971 * to do the job when the MIME-type API stage is reached.
1973 rewritelog(r, 2, "remember %s to have MIME-type '%s'",
1974 r->filename, p->forced_mimetype);
1975 ap_table_setn(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
1976 p->forced_mimetype);
1979 /* In per-directory context we operate in the Fixup API hook
1980 * which is after the MIME-type hook, so our MIME-type handler
1981 * has no chance to set r->content_type. And because we are
1982 * in the situation where no substitution takes place no
1983 * sub-request will happen (which could solve the
1984 * restriction). As a workaround we do it ourself now
1985 * immediately although this is not strictly API-conforming.
1986 * But it's the only chance we have...
1988 rewritelog(r, 1, "[per-dir %s] force %s to have MIME-type "
1989 "'%s'", perdir, r->filename, p->forced_mimetype);
1990 r->content_type = p->forced_mimetype;
1997 * Ok, now we finally know all patterns have matched and
1998 * that there is something to replace, so we create the
1999 * substitution URL string in `newuri'.
2001 /* 1. take the output string */
2002 ap_cpystrn(newuri, output, sizeof(newuri));
2003 /* 2. expand $N (i.e. backrefs to RewriteRule pattern) */
2004 expand_backref_inbuffer(r->pool, newuri, sizeof(newuri), briRR, '$');
2005 /* 3. expand %N (i.e. backrefs to latest RewriteCond pattern) */
2006 expand_backref_inbuffer(r->pool, newuri, sizeof(newuri), briRC, '%');
2007 /* 4. expand %{...} (i.e. variables) */
2008 expand_variables_inbuffer(r, newuri, sizeof(newuri));
2009 /* 5. expand ${...} (RewriteMap lookups) */
2010 expand_map_lookups(r, newuri, sizeof(newuri));
2011 /* and log the result... */
2012 if (perdir == NULL) {
2013 rewritelog(r, 2, "rewrite %s -> %s", uri, newuri);
2016 rewritelog(r, 2, "[per-dir %s] rewrite %s -> %s", perdir, uri, newuri);
2020 * Additionally do expansion for the environment variable
2021 * strings (`RewriteRule .. .. [E=<string>]').
2023 for (i = 0; p->env[i] != NULL; i++) {
2024 /* 1. take the string */
2025 ap_cpystrn(env, p->env[i], sizeof(env));
2026 /* 2. expand $N (i.e. backrefs to RewriteRule pattern) */
2027 expand_backref_inbuffer(r->pool, env, sizeof(env), briRR, '$');
2028 /* 3. expand %N (i.e. backrefs to latest RewriteCond pattern) */
2029 expand_backref_inbuffer(r->pool, env, sizeof(env), briRC, '%');
2030 /* 4. expand %{...} (i.e. variables) */
2031 expand_variables_inbuffer(r, env, sizeof(env));
2032 /* 5. expand ${...} (RewriteMap lookups) */
2033 expand_map_lookups(r, env, sizeof(env));
2034 /* and add the variable to Apache's structures */
2035 add_env_variable(r, env);
2039 * Now replace API's knowledge of the current URI:
2040 * Replace r->filename with the new URI string and split out
2041 * an on-the-fly generated QUERY_STRING part into r->args
2043 r->filename = ap_pstrdup(r->pool, newuri);
2044 splitout_queryargs(r, p->flags & RULEFLAG_QSAPPEND);
2047 * Again add the previously stripped per-directory location
2048 * prefix if the new URI is not a new one for this
2049 * location, i.e. if it's not starting with either a slash
2050 * or a fully qualified URL scheme.
2052 i = strlen(r->filename);
2054 && !( r->filename[0] == '/'
2055 || ( (i > 7 && strncasecmp(r->filename, "http://", 7) == 0)
2056 || (i > 8 && strncasecmp(r->filename, "https://", 8) == 0)
2057 || (i > 9 && strncasecmp(r->filename, "gopher://", 9) == 0)
2058 || (i > 6 && strncasecmp(r->filename, "ftp://", 6) == 0)
2059 || (i > 5 && strncasecmp(r->filename, "ldap:", 5) == 0)
2060 || (i > 5 && strncasecmp(r->filename, "news:", 5) == 0)
2061 || (i > 7 && strncasecmp(r->filename, "mailto:", 7) == 0)))) {
2062 rewritelog(r, 3, "[per-dir %s] add per-dir prefix: %s -> %s%s",
2063 perdir, r->filename, perdir, r->filename);
2064 r->filename = ap_pstrcat(r->pool, perdir, r->filename, NULL);
2068 * If this rule is forced for proxy throughput
2069 * (`RewriteRule ... ... [P]') then emulate mod_proxy's
2070 * URL-to-filename handler to be sure mod_proxy is triggered
2071 * for this URL later in the Apache API. But make sure it is
2072 * a fully-qualified URL. (If not it is qualified with
2075 if (p->flags & RULEFLAG_PROXY) {
2076 fully_qualify_uri(r);
2077 if (perdir == NULL) {
2078 rewritelog(r, 2, "forcing proxy-throughput with %s", r->filename);
2081 rewritelog(r, 2, "[per-dir %s] forcing proxy-throughput with %s",
2082 perdir, r->filename);
2084 r->filename = ap_pstrcat(r->pool, "proxy:", r->filename, NULL);
2089 * If this rule is explicitly forced for HTTP redirection
2090 * (`RewriteRule .. .. [R]') then force an external HTTP
2091 * redirect. But make sure it is a fully-qualified URL. (If
2092 * not it is qualified with ourself).
2094 if (p->flags & RULEFLAG_FORCEREDIRECT) {
2095 fully_qualify_uri(r);
2096 if (perdir == NULL) {
2098 "explicitly forcing redirect with %s", r->filename);
2102 "[per-dir %s] explicitly forcing redirect with %s",
2103 perdir, r->filename);
2105 r->status = p->forced_responsecode;
2110 * Special Rewriting Feature: Self-Reduction
2111 * We reduce the URL by stripping a possible
2112 * http[s]://<ourhost>[:<port>] prefix, i.e. a prefix which
2113 * corresponds to ourself. This is to simplify rewrite maps
2114 * and to avoid recursion, etc. When this prefix is not a
2115 * coincidence then the user has to use [R] explicitly (see
2121 * If this rule is still implicitly forced for HTTP
2122 * redirection (`RewriteRule .. <scheme>://...') then
2123 * directly force an external HTTP redirect.
2125 i = strlen(r->filename);
2126 if ( (i > 7 && strncasecmp(r->filename, "http://", 7) == 0)
2127 || (i > 8 && strncasecmp(r->filename, "https://", 8) == 0)
2128 || (i > 9 && strncasecmp(r->filename, "gopher://", 9) == 0)
2129 || (i > 6 && strncasecmp(r->filename, "ftp://", 6) == 0)
2130 || (i > 5 && strncasecmp(r->filename, "ldap:", 5) == 0)
2131 || (i > 5 && strncasecmp(r->filename, "news:", 5) == 0)
2132 || (i > 7 && strncasecmp(r->filename, "mailto:", 7) == 0) ) {
2133 if (perdir == NULL) {
2135 "implicitly forcing redirect (rc=%d) with %s",
2136 p->forced_responsecode, r->filename);
2139 rewritelog(r, 2, "[per-dir %s] implicitly forcing redirect "
2140 "(rc=%d) with %s", perdir, p->forced_responsecode,
2143 r->status = p->forced_responsecode;
2148 * Now we are sure it is not a fully qualified URL. But
2149 * there is still one special case left: A local rewrite in
2150 * per-directory context, i.e. a substitution URL which does
2151 * not start with a slash. Here we add again the initially
2152 * stripped per-directory prefix.
2154 if (prefixstrip && r->filename[0] != '/') {
2155 rewritelog(r, 3, "[per-dir %s] add per-dir prefix: %s -> %s%s",
2156 perdir, r->filename, perdir, r->filename);
2157 r->filename = ap_pstrcat(r->pool, perdir, r->filename, NULL);
2161 * Finally we had to remember if a MIME-type should be
2162 * forced for this URL (`RewriteRule .. .. [T=<type>]')
2163 * Later in the API processing phase this is forced by our
2164 * MIME API-hook function. This time its no problem even for
2165 * the per-directory context (where the MIME-type hook was
2166 * already processed) because a sub-request happens ;-)
2168 if (p->forced_mimetype != NULL) {
2169 ap_table_setn(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
2170 p->forced_mimetype);
2171 if (perdir == NULL) {
2172 rewritelog(r, 2, "remember %s to have MIME-type '%s'",
2173 r->filename, p->forced_mimetype);
2177 "[per-dir %s] remember %s to have MIME-type '%s'",
2178 perdir, r->filename, p->forced_mimetype);
2183 * Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_)
2184 * But now we're done for this particular rule.
2189 static int apply_rewrite_cond(request_rec *r, rewritecond_entry *p,
2190 char *perdir, backrefinfo *briRR,
2193 char input[MAX_STRING_LEN];
2196 regmatch_t regmatch[MAX_NMATCH];
2200 * Construct the string we match against
2203 /* 1. take the string */
2204 ap_cpystrn(input, p->input, sizeof(input));
2205 /* 2. expand $N (i.e. backrefs to RewriteRule pattern) */
2206 expand_backref_inbuffer(r->pool, input, sizeof(input), briRR, '$');
2207 /* 3. expand %N (i.e. backrefs to latest RewriteCond pattern) */
2208 expand_backref_inbuffer(r->pool, input, sizeof(input), briRC, '%');
2209 /* 4. expand %{...} (i.e. variables) */
2210 expand_variables_inbuffer(r, input, sizeof(input));
2211 /* 5. expand ${...} (RewriteMap lookups) */
2212 expand_map_lookups(r, input, sizeof(input));
2215 * Apply the patterns
2219 if (strcmp(p->pattern, "-f") == 0) {
2220 if (ap_stat(&sb, input, r->pool) == APR_SUCCESS) {
2221 if (sb.filetype == APR_REG) {
2226 else if (strcmp(p->pattern, "-s") == 0) {
2227 if (ap_stat(&sb, input, r->pool) == APR_SUCCESS) {
2228 if ((sb.filetype == APR_REG) && sb.size > 0) {
2233 else if (strcmp(p->pattern, "-l") == 0) {
2234 #if !defined(OS2) && !defined(WIN32)
2235 if (ap_lstat(&sb, input, r->pool) == APR_SUCCESS) {
2236 if (sb.filetype == APR_LNK) {
2242 else if (strcmp(p->pattern, "-d") == 0) {
2243 if (ap_stat(&sb, input, r->pool) == APR_SUCCESS) {
2244 if (sb.filetype == APR_DIR) {
2249 else if (strcmp(p->pattern, "-U") == 0) {
2250 /* avoid infinite subrequest recursion */
2251 if (strlen(input) > 0 /* nonempty path, and */
2252 && ( r->main == NULL /* - either not in a subrequest */
2253 || ( r->main->uri != NULL /* - or in a subrequest... */
2254 && r->uri != NULL /* ...and URIs aren't NULL... */
2255 /* ...and sub/main URIs differ */
2256 && strcmp(r->main->uri, r->uri) != 0) ) ) {
2258 /* run a URI-based subrequest */
2259 rsub = ap_sub_req_lookup_uri(input, r);
2261 /* URI exists for any result up to 3xx, redirects allowed */
2262 if (rsub->status < 400)
2266 rewritelog(r, 5, "RewriteCond URI (-U) check: "
2267 "path=%s -> status=%d", input, rsub->status);
2269 /* cleanup by destroying the subrequest */
2270 ap_destroy_sub_req(rsub);
2273 else if (strcmp(p->pattern, "-F") == 0) {
2274 /* avoid infinite subrequest recursion */
2275 if (strlen(input) > 0 /* nonempty path, and */
2276 && ( r->main == NULL /* - either not in a subrequest */
2277 || ( r->main->uri != NULL /* - or in a subrequest... */
2278 && r->uri != NULL /* ...and URIs aren't NULL... */
2279 /* ...and sub/main URIs differ */
2280 && strcmp(r->main->uri, r->uri) != 0) ) ) {
2282 /* process a file-based subrequest:
2283 * this differs from -U in that no path translation is done.
2285 rsub = ap_sub_req_lookup_file(input, r);
2287 /* file exists for any result up to 2xx, no redirects */
2288 if (rsub->status < 300 &&
2289 /* double-check that file exists since default result is 200 */
2290 ap_stat(&sb, rsub->filename, r->pool) == APR_SUCCESS) {
2295 rewritelog(r, 5, "RewriteCond file (-F) check: path=%s "
2296 "-> file=%s status=%d", input, rsub->filename,
2299 /* cleanup by destroying the subrequest */
2300 ap_destroy_sub_req(rsub);
2303 else if (strlen(p->pattern) > 1 && *(p->pattern) == '>') {
2304 rc = (compare_lexicography(input, p->pattern+1) == 1 ? 1 : 0);
2306 else if (strlen(p->pattern) > 1 && *(p->pattern) == '<') {
2307 rc = (compare_lexicography(input, p->pattern+1) == -1 ? 1 : 0);
2309 else if (strlen(p->pattern) > 1 && *(p->pattern) == '=') {
2310 if (strcmp(p->pattern+1, "\"\"") == 0) {
2311 rc = (*input == '\0');
2314 rc = (strcmp(input, p->pattern+1) == 0 ? 1 : 0);
2318 /* it is really a regexp pattern, so apply it */
2319 rc = (ap_regexec(p->regexp, input,
2320 p->regexp->re_nsub+1, regmatch,0) == 0);
2322 /* if it isn't a negated pattern and really matched
2323 we update the passed-through regex subst info structure */
2324 if (rc && !(p->flags & CONDFLAG_NOTMATCH)) {
2325 briRC->source = ap_pstrdup(r->pool, input);
2326 briRC->nsub = p->regexp->re_nsub;
2327 memcpy((void *)(briRC->regmatch), (void *)(regmatch),
2332 /* if this is a non-matching regexp, just negate the result */
2333 if (p->flags & CONDFLAG_NOTMATCH) {
2337 rewritelog(r, 4, "RewriteCond: input='%s' pattern='%s%s' => %s",
2338 input, (p->flags & CONDFLAG_NOTMATCH ? "!" : ""),
2339 p->pattern, rc ? "matched" : "not-matched");
2341 /* end just return the result */
2347 ** +-------------------------------------------------------+
2349 ** | URL transformation functions
2351 ** +-------------------------------------------------------+
2356 ** split out a QUERY_STRING part from
2357 ** the current URI string
2361 static void splitout_queryargs(request_rec *r, int qsappend)
2366 q = strchr(r->filename, '?');
2368 olduri = ap_pstrdup(r->pool, r->filename);
2371 r->args = ap_pstrcat(r->pool, q, "&", r->args, NULL);
2374 r->args = ap_pstrdup(r->pool, q);
2376 if (strlen(r->args) == 0) {
2378 rewritelog(r, 3, "split uri=%s -> uri=%s, args=<none>", olduri,
2382 if (r->args[strlen(r->args)-1] == '&') {
2383 r->args[strlen(r->args)-1] = '\0';
2385 rewritelog(r, 3, "split uri=%s -> uri=%s, args=%s", olduri,
2386 r->filename, r->args);
2395 ** strip 'http[s]://ourhost/' from URI
2399 static void reduce_uri(request_rec *r)
2402 unsigned short port;
2407 char host[LONG_STRING_LEN];
2408 char buf[MAX_STRING_LEN];
2412 cp = (char *)ap_http_method(r);
2414 if ( strlen(r->filename) > l+3
2415 && strncasecmp(r->filename, cp, l) == 0
2416 && r->filename[l] == ':'
2417 && r->filename[l+1] == '/'
2418 && r->filename[l+2] == '/' ) {
2419 /* there was really a rewrite to a remote path */
2421 olduri = ap_pstrdup(r->pool, r->filename); /* save for logging */
2423 /* cut the hostname and port out of the URI */
2424 ap_cpystrn(buf, r->filename+(l+3), sizeof(buf));
2426 for (cp = hostp; *cp != '\0' && *cp != '/' && *cp != ':'; cp++)
2431 ap_cpystrn(host, hostp, sizeof(host));
2434 for (; *cp != '\0' && *cp != '/'; cp++)
2440 /* set remaining url */
2443 else if (*cp == '/') {
2446 ap_cpystrn(host, hostp, sizeof(host));
2449 port = ap_default_port(r);
2450 /* set remaining url */
2455 ap_cpystrn(host, hostp, sizeof(host));
2457 port = ap_default_port(r);
2458 /* set remaining url */
2462 /* now check whether we could reduce it to a local path... */
2463 if (ap_matches_request_vhost(r, host, port)) {
2464 /* this is our host, so only the URL remains */
2465 r->filename = ap_pstrdup(r->pool, url);
2466 rewritelog(r, 3, "reduce %s -> %s", olduri, r->filename);
2475 ** add 'http[s]://ourhost[:ourport]/' to URI
2476 ** if URI is still not fully qualified
2480 static void fully_qualify_uri(request_rec *r)
2484 const char *thisserver;
2488 i = strlen(r->filename);
2489 if (!( (i > 7 && strncasecmp(r->filename, "http://", 7) == 0)
2490 || (i > 8 && strncasecmp(r->filename, "https://", 8) == 0)
2491 || (i > 9 && strncasecmp(r->filename, "gopher://", 9) == 0)
2492 || (i > 6 && strncasecmp(r->filename, "ftp://", 6) == 0)
2493 || (i > 5 && strncasecmp(r->filename, "ldap:", 5) == 0)
2494 || (i > 5 && strncasecmp(r->filename, "news:", 5) == 0)
2495 || (i > 7 && strncasecmp(r->filename, "mailto:", 7) == 0))) {
2497 thisserver = ap_get_server_name(r);
2498 port = ap_get_server_port(r);
2499 if (ap_is_default_port(port,r)) {
2503 ap_snprintf(buf, sizeof(buf), ":%u", port);
2507 if (r->filename[0] == '/') {
2508 r->filename = ap_psprintf(r->pool, "%s://%s%s%s",
2509 ap_http_method(r), thisserver,
2510 thisport, r->filename);
2513 r->filename = ap_psprintf(r->pool, "%s://%s%s/%s",
2514 ap_http_method(r), thisserver,
2515 thisport, r->filename);
2524 ** Expand the %0-%9 or $0-$9 regex backreferences
2528 static void expand_backref_inbuffer(ap_pool_t *p, char *buf, int nbuf,
2529 backrefinfo *bri, char c)
2533 /* protect existing $N and & backrefs and replace <c>N with $N backrefs */
2534 for (i = 0; buf[i] != '\0' && i < nbuf; i++) {
2535 if (buf[i] == '\\' && (buf[i+1] != '\0' && i < (nbuf-1))) {
2536 i++; /* protect next */
2538 else if (buf[i] == '&') {
2541 else if (c != '$' && buf[i] == '$' && (buf[i+1] >= '0' && buf[i+1] <= '9')) {
2545 else if (buf[i] == c && (buf[i+1] >= '0' && buf[i+1] <= '9')) {
2551 /* now apply the standard regex substitution function */
2552 ap_cpystrn(buf, ap_pregsub(p, buf, bri->source,
2553 bri->nsub+1, bri->regmatch), nbuf);
2555 /* restore the original $N and & backrefs */
2556 for (i = 0; buf[i] != '\0' && i < nbuf; i++) {
2557 if (buf[i] == '\001') {
2560 else if (buf[i] == '\002') {
2569 ** Expand tilde-paths (/~user) through
2570 ** Unix /etc/passwd database information
2573 #if !defined(WIN32) && !defined(NETWARE)
2574 static char *expand_tildepaths(request_rec *r, char *uri)
2576 char user[LONG_STRING_LEN];
2582 if (uri != NULL && strlen(uri) > 2 && uri[0] == '/' && uri[1] == '~') {
2583 /* cut out the username */
2584 for (j = 0, i = 2; j < sizeof(user)-1
2586 && uri[i] != '/' ; ) {
2587 user[j++] = uri[i++];
2591 /* lookup username in systems passwd file */
2592 if ((pw = getpwnam(user)) != NULL) {
2593 /* ok, user was found, so expand the ~user string */
2594 if (uri[i] != '\0') {
2595 /* ~user/anything... has to be expanded */
2596 if (pw->pw_dir[strlen(pw->pw_dir)-1] == '/') {
2597 pw->pw_dir[strlen(pw->pw_dir)-1] = '\0';
2599 newuri = ap_pstrcat(r->pool, pw->pw_dir, uri+i, NULL);
2602 /* only ~user has to be expanded */
2603 newuri = ap_pstrdup(r->pool, pw->pw_dir);
2613 ** mapfile expansion support
2614 ** i.e. expansion of MAP lookup directives
2615 ** ${<mapname>:<key>} in RewriteRule rhs
2619 #define limit_length(n) (n > LONG_STRING_LEN-1 ? LONG_STRING_LEN-1 : n)
2621 static void expand_map_lookups(request_rec *r, char *uri, int uri_len)
2623 char newuri[MAX_STRING_LEN];
2629 char mapname[LONG_STRING_LEN];
2630 char mapkey[LONG_STRING_LEN];
2631 char defaultvalue[LONG_STRING_LEN];
2635 cpIE = cpI+strlen(cpI);
2637 while (cpI < cpIE) {
2638 if (cpI+6 < cpIE && strncmp(cpI, "${", 2) == 0) {
2639 /* missing delimiter -> take it as plain text */
2640 if ( strchr(cpI+2, ':') == NULL
2641 || strchr(cpI+2, '}') == NULL) {
2642 memcpy(cpO, cpI, 2);
2649 cpT = strchr(cpI, ':');
2651 memcpy(mapname, cpI, limit_length(n));
2652 mapname[limit_length(n)] = '\0';
2655 cpT2 = strchr(cpI, '|');
2656 cpT = strchr(cpI, '}');
2657 if (cpT2 != NULL && cpT2 < cpT) {
2659 memcpy(mapkey, cpI, limit_length(n));
2660 mapkey[limit_length(n)] = '\0';
2664 memcpy(defaultvalue, cpI, limit_length(n));
2665 defaultvalue[limit_length(n)] = '\0';
2670 memcpy(mapkey, cpI, limit_length(n));
2671 mapkey[limit_length(n)] = '\0';
2674 defaultvalue[0] = '\0';
2677 cpT = lookup_map(r, mapname, mapkey);
2680 if (cpO + n >= newuri + sizeof(newuri)) {
2681 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
2682 0, r, "insufficient space in "
2683 "expand_map_lookups, aborting");
2686 memcpy(cpO, cpT, n);
2690 n = strlen(defaultvalue);
2691 if (cpO + n >= newuri + sizeof(newuri)) {
2692 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
2693 0, r, "insufficient space in "
2694 "expand_map_lookups, aborting");
2697 memcpy(cpO, defaultvalue, n);
2702 cpT = strstr(cpI, "${");
2704 cpT = cpI+strlen(cpI);
2706 if (cpO + n >= newuri + sizeof(newuri)) {
2707 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
2708 0, r, "insufficient space in "
2709 "expand_map_lookups, aborting");
2712 memcpy(cpO, cpI, n);
2718 ap_cpystrn(uri, newuri, uri_len);
2727 ** +-------------------------------------------------------+
2729 ** | DBM hashfile support
2731 ** +-------------------------------------------------------+
2735 static char *lookup_map(request_rec *r, char *name, char *key)
2738 rewrite_server_conf *conf;
2739 ap_array_header_t *rewritemaps;
2740 rewritemap_entry *entries;
2741 rewritemap_entry *s;
2746 /* get map configuration */
2747 sconf = r->server->module_config;
2748 conf = (rewrite_server_conf *)ap_get_module_config(sconf,
2750 rewritemaps = conf->rewritemaps;
2752 entries = (rewritemap_entry *)rewritemaps->elts;
2753 for (i = 0; i < rewritemaps->nelts; i++) {
2755 if (strcmp(s->name, name) == 0) {
2756 if (s->type == MAPTYPE_TXT) {
2757 if (ap_stat(&st, s->checkfile, r->pool) != APR_SUCCESS) {
2758 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2759 "mod_rewrite: can't access text RewriteMap "
2760 "file %s", s->checkfile);
2761 rewritelog(r, 1, "can't open RewriteMap file, "
2765 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2767 if (value == NULL) {
2768 rewritelog(r, 6, "cache lookup FAILED, forcing new "
2771 lookup_map_txtfile(r, s->datafile, key)) != NULL) {
2772 rewritelog(r, 5, "map lookup OK: map=%s key=%s[txt] "
2773 "-> val=%s", s->name, key, value);
2774 set_cache_string(cachep, s->name, CACHEMODE_TS,
2775 st.mtime, key, value);
2779 rewritelog(r, 5, "map lookup FAILED: map=%s[txt] "
2780 "key=%s", s->name, key);
2781 set_cache_string(cachep, s->name, CACHEMODE_TS,
2787 rewritelog(r, 5, "cache lookup OK: map=%s[txt] key=%s "
2788 "-> val=%s", s->name, key, value);
2789 return value[0] != '\0' ? value : NULL;
2792 else if (s->type == MAPTYPE_DBM) {
2793 #ifndef NO_DBM_REWRITEMAP
2794 if (ap_stat(&st, s->checkfile, r->pool) != APR_SUCCESS) {
2795 ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
2796 "mod_rewrite: can't access DBM RewriteMap "
2797 "file %s", s->checkfile);
2798 rewritelog(r, 1, "can't open DBM RewriteMap file, "
2802 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2804 if (value == NULL) {
2806 "cache lookup FAILED, forcing new map lookup");
2808 lookup_map_dbmfile(r, s->datafile, key)) != NULL) {
2809 rewritelog(r, 5, "map lookup OK: map=%s[dbm] key=%s "
2810 "-> val=%s", s->name, key, value);
2811 set_cache_string(cachep, s->name, CACHEMODE_TS,
2812 st.mtime, key, value);
2816 rewritelog(r, 5, "map lookup FAILED: map=%s[dbm] "
2817 "key=%s", s->name, key);
2818 set_cache_string(cachep, s->name, CACHEMODE_TS,
2824 rewritelog(r, 5, "cache lookup OK: map=%s[dbm] key=%s "
2825 "-> val=%s", s->name, key, value);
2826 return value[0] != '\0' ? value : NULL;
2832 else if (s->type == MAPTYPE_PRG) {
2834 lookup_map_program(r, s->fpin, s->fpout, key)) != NULL) {
2835 rewritelog(r, 5, "map lookup OK: map=%s key=%s -> val=%s",
2836 s->name, key, value);
2840 rewritelog(r, 5, "map lookup FAILED: map=%s key=%s",
2844 else if (s->type == MAPTYPE_INT) {
2845 if ((value = lookup_map_internal(r, s->func, key)) != NULL) {
2846 rewritelog(r, 5, "map lookup OK: map=%s key=%s -> val=%s",
2847 s->name, key, value);
2851 rewritelog(r, 5, "map lookup FAILED: map=%s key=%s",
2855 else if (s->type == MAPTYPE_RND) {
2856 if (ap_stat(&st, s->checkfile, r->pool) == -1) {
2857 ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
2858 "mod_rewrite: can't access text RewriteMap "
2859 "file %s", s->checkfile);
2860 rewritelog(r, 1, "can't open RewriteMap file, "
2864 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2866 if (value == NULL) {
2867 rewritelog(r, 6, "cache lookup FAILED, forcing new "
2870 lookup_map_txtfile(r, s->datafile, key)) != NULL) {
2871 rewritelog(r, 5, "map lookup OK: map=%s key=%s[txt] "
2872 "-> val=%s", s->name, key, value);
2873 set_cache_string(cachep, s->name, CACHEMODE_TS,
2874 st.mtime, key, value);
2877 rewritelog(r, 5, "map lookup FAILED: map=%s[txt] "
2878 "key=%s", s->name, key);
2879 set_cache_string(cachep, s->name, CACHEMODE_TS,
2885 rewritelog(r, 5, "cache lookup OK: map=%s[txt] key=%s "
2886 "-> val=%s", s->name, key, value);
2888 if (value[0] != '\0') {
2889 value = select_random_value_part(r, value);
2890 rewritelog(r, 5, "randomly choosen the subvalue `%s'", value);
2902 static char *lookup_map_txtfile(request_rec *r, char *file, char *key)
2904 ap_file_t *fp = NULL;
2913 rc = ap_open(&fp, file, APR_READ, APR_OS_DEFAULT, r->pool);
2914 if (rc != APR_SUCCESS) {
2918 while (ap_fgets(line, sizeof(line), fp) == APR_SUCCESS) {
2920 continue; /* ignore comments */
2923 skip = strcspn(cpT," \t\r\n");
2925 continue; /* ignore lines that start with a space, tab, CR, or LF */
2928 if (strcmp(curkey, key) != 0)
2929 continue; /* key does not match... */
2931 /* found a matching key; now extract and return the value */
2933 skip = strspn(cpT, " \t\r\n");
2936 skip = strcspn(cpT, " \t\r\n");
2938 continue; /* no value... */
2941 value = ap_pstrdup(r->pool, curval);
2948 #ifndef NO_DBM_REWRITEMAP
2949 static char *lookup_map_dbmfile(request_rec *r, char *file, char *key)
2955 char buf[MAX_STRING_LEN];
2958 dbmkey.dsize = strlen(key);
2959 if ((dbmfp = dbm_open(file, O_RDONLY, 0666)) != NULL) {
2960 dbmval = dbm_fetch(dbmfp, dbmkey);
2961 if (dbmval.dptr != NULL) {
2962 memcpy(buf, dbmval.dptr,
2963 dbmval.dsize < sizeof(buf)-1 ?
2964 dbmval.dsize : sizeof(buf)-1 );
2965 buf[dbmval.dsize] = '\0';
2966 value = ap_pstrdup(r->pool, buf);
2974 static char *lookup_map_program(request_rec *r, ap_file_t *fpin,
2975 ap_file_t *fpout, char *key)
2977 char buf[LONG_STRING_LEN];
2983 struct iovec iova[2];
2987 /* when `RewriteEngine off' was used in the per-server
2988 * context then the rewritemap-programs were not spawned.
2989 * In this case using such a map (usually in per-dir context)
2990 * is useless because it is not available.
2992 if (fpin == NULL || fpout == NULL) {
2998 ap_lock(rewrite_map_lock);
3000 /* write out the request key */
3002 nbytes = strlen(key);
3003 ap_write(fpin, key, &nbytes);
3005 ap_write(fpin, "\n", &nbytes);
3007 iova[0].iov_base = key;
3008 iova[0].iov_len = strlen(key);
3009 iova[1].iov_base = "\n";
3010 iova[1].iov_len = 1;
3013 ap_writev(fpin, iova, niov, &nbytes);
3016 /* read in the response value */
3019 ap_read(fpout, &c, &nbytes);
3020 while (nbytes == 1 && (i < LONG_STRING_LEN-1)) {
3026 ap_read(fpout, &c, &nbytes);
3030 /* give the lock back */
3031 ap_unlock(rewrite_map_lock);
3033 if (strcasecmp(buf, "NULL") == 0) {
3037 return ap_pstrdup(r->pool, buf);
3041 static char *lookup_map_internal(request_rec *r,
3042 char *(*func)(request_rec *, char *),
3045 /* currently we just let the function convert
3046 the key to a corresponding value */
3047 return func(r, key);
3050 static char *rewrite_mapfunc_toupper(request_rec *r, char *key)
3054 for (cp = value = ap_pstrdup(r->pool, key); cp != NULL && *cp != '\0';
3056 *cp = ap_toupper(*cp);
3061 static char *rewrite_mapfunc_tolower(request_rec *r, char *key)
3065 for (cp = value = ap_pstrdup(r->pool, key); cp != NULL && *cp != '\0';
3067 *cp = ap_tolower(*cp);
3072 static char *rewrite_mapfunc_escape(request_rec *r, char *key)
3076 value = ap_escape_uri(r->pool, key);
3080 static char *rewrite_mapfunc_unescape(request_rec *r, char *key)
3084 value = ap_pstrdup(r->pool, key);
3085 ap_unescape_url(value);
3089 static int rewrite_rand_init_done = 0;
3091 static void rewrite_rand_init(void)
3093 if (!rewrite_rand_init_done) {
3094 srand((unsigned)(getpid()));
3095 rewrite_rand_init_done = 1;
3100 static int rewrite_rand(int l, int h)
3102 rewrite_rand_init();
3104 /* Get [0,1) and then scale to the appropriate range. Note that using
3105 * a floating point value ensures that we use all bits of the rand()
3106 * result. Doing an integer modulus would only use the lower-order bits
3107 * which may not be as uniformly random.
3109 return ((double)(rand() % RAND_MAX) / RAND_MAX) * (h - l + 1) + l;
3112 static char *select_random_value_part(request_rec *r, char *value)
3117 /* count number of distinct values */
3118 for (n = 1, i = 0; value[i] != '\0'; i++) {
3119 if (value[i] == '|') {
3124 /* when only one value we have no option to choose */
3129 /* else randomly select one */
3130 k = rewrite_rand(1, n);
3132 /* and grep it out */
3133 for (n = 1, i = 0; value[i] != '\0'; i++) {
3137 if (value[i] == '|') {
3141 buf = ap_pstrdup(r->pool, &value[i]);
3142 for (i = 0; buf[i] != '\0' && buf[i] != '|'; i++)
3150 ** +-------------------------------------------------------+
3152 ** | rewriting logfile support
3154 ** +-------------------------------------------------------+
3158 static void open_rewritelog(server_rec *s, ap_pool_t *p)
3160 rewrite_server_conf *conf;
3164 int rewritelog_flags = ( APR_WRITE | APR_APPEND | APR_CREATE );
3165 mode_t rewritelog_mode = ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD );
3167 conf = ap_get_module_config(s->module_config, &rewrite_module);
3169 if (conf->rewritelogfile == NULL) {
3172 if (*(conf->rewritelogfile) == '\0') {
3175 if (conf->rewritelogfp != NULL) {
3176 return; /* virtual log shared w/ main server */
3179 fname = ap_server_root_relative(p, conf->rewritelogfile);
3181 if (*conf->rewritelogfile == '|') {
3182 if ((pl = ap_open_piped_log(p, conf->rewritelogfile+1)) == NULL) {
3183 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
3184 "mod_rewrite: could not open reliable pipe "
3185 "to RewriteLog filter %s", conf->rewritelogfile+1);
3188 conf->rewritelogfp = ap_piped_log_write_fd(pl);
3190 else if (*conf->rewritelogfile != '\0') {
3191 rc = ap_open(&conf->rewritelogfp, fname, rewritelog_flags, rewritelog_mode, p);
3192 if (rc != APR_SUCCESS) {
3193 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
3194 "mod_rewrite: could not open RewriteLog "
3202 static void rewritelog(request_rec *r, int level, const char *text, ...)
3204 rewrite_server_conf *conf;
3219 conf = ap_get_module_config(r->server->module_config, &rewrite_module);
3220 conn = r->connection;
3222 if (conf->rewritelogfp == NULL) {
3225 if (conf->rewritelogfile == NULL) {
3228 if (*(conf->rewritelogfile) == '\0') {
3232 if (level > conf->rewriteloglevel) {
3236 if (r->user == NULL) {
3239 else if (strlen(r->user) != 0) {
3246 rhost = ap_get_remote_host(conn, r->server->module_config,
3248 if (rhost == NULL) {
3249 rhost = "UNKNOWN-HOST";
3252 str1 = ap_pstrcat(r->pool, rhost, " ",
3253 (conn->remote_logname != NULL ?
3254 conn->remote_logname : "-"), " ",
3256 ap_vsnprintf(str2, sizeof(str2), text, ap);
3258 if (r->main == NULL) {
3259 strcpy(type, "initial");
3262 strcpy(type, "subreq");
3265 for (i = 0, req = r; req->prev != NULL; req = req->prev) {
3272 ap_snprintf(redir, sizeof(redir), "/redir#%d", i);
3275 ap_snprintf(str3, sizeof(str3),
3276 "%s %s [%s/sid#%lx][rid#%lx/%s%s] (%d) %s\n", str1,
3277 current_logtime(r), ap_get_server_name(r),
3278 (unsigned long)(r->server), (unsigned long)r,
3279 type, redir, level, str2);
3281 ap_lock(rewrite_log_lock);
3282 nbytes = strlen(str3);
3283 ap_write(conf->rewritelogfp, str3, &nbytes);
3284 ap_unlock(rewrite_log_lock);
3290 static char *current_logtime(request_rec *r)
3292 ap_exploded_time_t t;
3296 ap_explode_localtime(&t, ap_now());
3298 ap_strftime(tstr, &len, 80, "[%d/%b/%Y:%H:%M:%S ", &t);
3299 ap_snprintf(tstr + strlen(tstr), 80-strlen(tstr), "%c%.2d%.2d]",
3300 t.tm_gmtoff < 0 ? '-' : '+',
3301 t.tm_gmtoff / (60*60), t.tm_gmtoff % (60*60));
3302 return ap_pstrdup(r->pool, tstr);
3309 ** +-------------------------------------------------------+
3311 ** | rewriting lockfile support
3313 ** +-------------------------------------------------------+
3316 #define REWRITELOCK_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD )
3318 static void rewritelock_create(server_rec *s, ap_pool_t *p)
3322 /* only operate if a lockfile is used */
3323 if (lockname == NULL || *(lockname) == '\0') {
3327 /* fixup the path, especially for rewritelock_remove() */
3328 lockname = ap_server_root_relative(p, lockname);
3330 /* create the lockfile */
3331 rc = ap_create_lock (&rewrite_map_lock, APR_MUTEX, APR_LOCKALL, lockname, p);
3332 if (rc != APR_SUCCESS) {
3333 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
3334 "mod_rewrite: Parent could not create RewriteLock "
3335 "file %s", lockname);
3342 static ap_status_t rewritelock_remove(void *data)
3344 /* only operate if a lockfile is used */
3345 if (lockname == NULL || *(lockname) == '\0') {
3349 /* destroy the rewritelock */
3350 ap_destroy_lock (rewrite_map_lock);
3351 rewrite_map_lock = NULL;
3358 ** +-------------------------------------------------------+
3360 ** | program map support
3362 ** +-------------------------------------------------------+
3365 static void run_rewritemap_programs(server_rec *s, ap_pool_t *p)
3367 rewrite_server_conf *conf;
3368 ap_file_t *fpin = NULL;
3369 ap_file_t *fpout = NULL;
3370 ap_file_t *fperr = NULL;
3371 ap_array_header_t *rewritemaps;
3372 rewritemap_entry *entries;
3373 rewritemap_entry *map;
3377 conf = ap_get_module_config(s->module_config, &rewrite_module);
3379 /* If the engine isn't turned on,
3380 * don't even try to do anything.
3382 if (conf->state == ENGINE_DISABLED) {
3386 rewritemaps = conf->rewritemaps;
3387 entries = (rewritemap_entry *)rewritemaps->elts;
3388 for (i = 0; i < rewritemaps->nelts; i++) {
3390 if (map->type != MAPTYPE_PRG) {
3393 if (map->datafile == NULL
3394 || *(map->datafile) == '\0'
3395 || map->fpin != NULL
3396 || map->fpout != NULL ) {
3401 rc = rewritemap_program_child(p, map->datafile,
3402 &fpout, &fpin, &fperr);
3403 if (rc != APR_SUCCESS || fpin == NULL || fpout == NULL) {
3404 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
3405 "mod_rewrite: could not fork child for "
3406 "RewriteMap process. %d", rc);
3416 /* child process code */
3417 static int rewritemap_program_child(ap_pool_t *p, char *progname,
3418 ap_file_t **fpout, ap_file_t **fpin,
3422 ap_procattr_t *procattr;
3426 ap_signal(SIGHUP, SIG_IGN);
3430 if ((ap_createprocattr_init(&procattr, p) != APR_SUCCESS) ||
3431 (ap_setprocattr_io(procattr, APR_FULL_BLOCK,
3433 APR_FULL_NONBLOCK) != APR_SUCCESS) ||
3434 (ap_setprocattr_dir(procattr, ap_make_dirstr_parent(p, progname))
3436 (ap_setprocattr_cmdtype(procattr, APR_PROGRAM) != APR_SUCCESS)) {
3437 /* Something bad happened, give up and go away. */
3441 rc = ap_create_process(&procnew, progname, NULL, NULL, procattr, p);
3443 if (rc == APR_SUCCESS) {
3444 ap_note_subprocess(p, &procnew, kill_after_timeout);
3447 (*fpin) = procnew.in;
3451 (*fpout) = procnew.out;
3455 (*fperr) = procnew.err;
3467 ** +-------------------------------------------------------+
3469 ** | environment variable support
3471 ** +-------------------------------------------------------+
3475 static void expand_variables_inbuffer(request_rec *r, char *buf, int buf_len)
3478 newbuf = expand_variables(r, buf);
3479 if (strcmp(newbuf, buf) != 0) {
3480 ap_cpystrn(buf, newbuf, buf_len);
3485 static char *expand_variables(request_rec *r, char *str)
3487 char output[MAX_STRING_LEN];
3488 char input[MAX_STRING_LEN];
3496 ap_cpystrn(input, str, sizeof(input));
3499 endp = output + sizeof(output);
3501 for (cp = input; cp < input+MAX_STRING_LEN; ) {
3502 if ((cp2 = strstr(cp, "%{")) != NULL) {
3503 if ((cp3 = strstr(cp2, "}")) != NULL) {
3505 outp = ap_cpystrn(outp, cp, endp - outp);
3509 outp = ap_cpystrn(outp, lookup_variable(r, cp2), endp - outp);
3516 outp = ap_cpystrn(outp, cp, endp - outp);
3519 return expanded ? ap_pstrdup(r->pool, output) : str;
3522 static char *lookup_variable(request_rec *r, char *var)
3525 char resultbuf[LONG_STRING_LEN];
3526 ap_exploded_time_t tm;
3537 if (strcasecmp(var, "HTTP_USER_AGENT") == 0) {
3538 result = lookup_header(r, "User-Agent");
3540 else if (strcasecmp(var, "HTTP_REFERER") == 0) {
3541 result = lookup_header(r, "Referer");
3543 else if (strcasecmp(var, "HTTP_COOKIE") == 0) {
3544 result = lookup_header(r, "Cookie");
3546 else if (strcasecmp(var, "HTTP_FORWARDED") == 0) {
3547 result = lookup_header(r, "Forwarded");
3549 else if (strcasecmp(var, "HTTP_HOST") == 0) {
3550 result = lookup_header(r, "Host");
3552 else if (strcasecmp(var, "HTTP_PROXY_CONNECTION") == 0) {
3553 result = lookup_header(r, "Proxy-Connection");
3555 else if (strcasecmp(var, "HTTP_ACCEPT") == 0) {
3556 result = lookup_header(r, "Accept");
3558 /* all other headers from which we are still not know about */
3559 else if (strlen(var) > 5 && strncasecmp(var, "HTTP:", 5) == 0) {
3560 result = lookup_header(r, var+5);
3563 /* connection stuff */
3564 else if (strcasecmp(var, "REMOTE_ADDR") == 0) {
3565 result = r->connection->remote_ip;
3567 else if (strcasecmp(var, "REMOTE_HOST") == 0) {
3568 result = (char *)ap_get_remote_host(r->connection,
3569 r->per_dir_config, REMOTE_NAME);
3571 else if (strcasecmp(var, "REMOTE_USER") == 0) {
3574 else if (strcasecmp(var, "REMOTE_IDENT") == 0) {
3575 result = (char *)ap_get_remote_logname(r);
3579 else if (strcasecmp(var, "THE_REQUEST") == 0) { /* non-standard */
3580 result = r->the_request;
3582 else if (strcasecmp(var, "REQUEST_METHOD") == 0) {
3585 else if (strcasecmp(var, "REQUEST_URI") == 0) { /* non-standard */
3588 else if (strcasecmp(var, "SCRIPT_FILENAME") == 0 ||
3589 strcasecmp(var, "REQUEST_FILENAME") == 0 ) {
3590 result = r->filename;
3592 else if (strcasecmp(var, "PATH_INFO") == 0) {
3593 result = r->path_info;
3595 else if (strcasecmp(var, "QUERY_STRING") == 0) {
3598 else if (strcasecmp(var, "AUTH_TYPE") == 0) {
3599 result = r->ap_auth_type;
3601 else if (strcasecmp(var, "IS_SUBREQ") == 0) { /* non-standard */
3602 result = (r->main != NULL ? "true" : "false");
3605 /* internal server stuff */
3606 else if (strcasecmp(var, "DOCUMENT_ROOT") == 0) {
3607 result = ap_document_root(r);
3609 else if (strcasecmp(var, "SERVER_ADMIN") == 0) {
3610 result = r->server->server_admin;
3612 else if (strcasecmp(var, "SERVER_NAME") == 0) {
3613 result = ap_get_server_name(r);
3615 else if (strcasecmp(var, "SERVER_ADDR") == 0) { /* non-standard */
3616 result = r->connection->local_ip;
3618 else if (strcasecmp(var, "SERVER_PORT") == 0) {
3619 ap_snprintf(resultbuf, sizeof(resultbuf), "%u", ap_get_server_port(r));
3622 else if (strcasecmp(var, "SERVER_PROTOCOL") == 0) {
3623 result = r->protocol;
3625 else if (strcasecmp(var, "SERVER_SOFTWARE") == 0) {
3626 result = ap_get_server_version();
3628 else if (strcasecmp(var, "API_VERSION") == 0) { /* non-standard */
3629 ap_snprintf(resultbuf, sizeof(resultbuf), "%d:%d",
3630 MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR);
3634 /* XXX: wow this has gotta be slow if you actually use it for a lot, recalculates exploded time for each variable */
3635 /* underlaying Unix system stuff */
3636 else if (strcasecmp(var, "TIME_YEAR") == 0) {
3637 ap_explode_localtime(&tm, ap_now());
3638 ap_snprintf(resultbuf, sizeof(resultbuf), "%04d", tm.tm_year + 1900);
3641 #define MKTIMESTR(format, tmfield) \
3642 ap_explode_localtime(&tm, ap_now()); \
3643 ap_snprintf(resultbuf, sizeof(resultbuf), format, tm.tmfield); \
3645 else if (strcasecmp(var, "TIME_MON") == 0) {
3646 MKTIMESTR("%02d", tm_mon+1)
3648 else if (strcasecmp(var, "TIME_DAY") == 0) {
3649 MKTIMESTR("%02d", tm_mday)
3651 else if (strcasecmp(var, "TIME_HOUR") == 0) {
3652 MKTIMESTR("%02d", tm_hour)
3654 else if (strcasecmp(var, "TIME_MIN") == 0) {
3655 MKTIMESTR("%02d", tm_min)
3657 else if (strcasecmp(var, "TIME_SEC") == 0) {
3658 MKTIMESTR("%02d", tm_sec)
3660 else if (strcasecmp(var, "TIME_WDAY") == 0) {
3661 MKTIMESTR("%d", tm_wday)
3663 else if (strcasecmp(var, "TIME") == 0) {
3664 ap_explode_localtime(&tm, ap_now());
3665 ap_snprintf(resultbuf, sizeof(resultbuf),
3666 "%04d%02d%02d%02d%02d%02d", tm.tm_year + 1900,
3667 tm.tm_mon+1, tm.tm_mday,
3668 tm.tm_hour, tm.tm_min, tm.tm_sec);
3670 rewritelog(r, 1, "RESULT='%s'", result);
3673 /* all other env-variables from the parent Apache process */
3674 else if (strlen(var) > 4 && strncasecmp(var, "ENV:", 4) == 0) {
3675 /* first try the internal Apache notes structure */
3676 result = ap_table_get(r->notes, var+4);
3677 /* second try the internal Apache env structure */
3678 if (result == NULL) {
3679 result = ap_table_get(r->subprocess_env, var+4);
3681 /* third try the external OS env */
3682 if (result == NULL) {
3683 result = getenv(var+4);
3687 #define LOOKAHEAD(subrecfunc) \
3689 /* filename is safe to use */ \
3690 r->filename != NULL \
3691 /* - and we're either not in a subrequest */ \
3692 && ( r->main == NULL \
3693 /* - or in a subrequest where paths are non-NULL... */ \
3694 || ( r->main->uri != NULL && r->uri != NULL \
3695 /* ...and sub and main paths differ */ \
3696 && strcmp(r->main->uri, r->uri) != 0))) { \
3697 /* process a file-based subrequest */ \
3698 rsub = subrecfunc(r->filename, r); \
3699 /* now recursively lookup the variable in the sub_req */ \
3700 result = lookup_variable(rsub, var+5); \
3701 /* copy it up to our scope before we destroy sub_req's ap_pool_t */ \
3702 result = ap_pstrdup(r->pool, result); \
3703 /* cleanup by destroying the subrequest */ \
3704 ap_destroy_sub_req(rsub); \
3706 rewritelog(r, 5, "lookahead: path=%s var=%s -> val=%s", \
3707 r->filename, var+5, result); \
3708 /* return ourself to prevent re-pstrdup */ \
3709 return (char *)result; \
3712 /* look-ahead for parameter through URI-based sub-request */
3713 else if (strlen(var) > 5 && strncasecmp(var, "LA-U:", 5) == 0) {
3714 LOOKAHEAD(ap_sub_req_lookup_uri)
3716 /* look-ahead for parameter through file-based sub-request */
3717 else if (strlen(var) > 5 && strncasecmp(var, "LA-F:", 5) == 0) {
3718 LOOKAHEAD(ap_sub_req_lookup_file)
3721 #if !defined(WIN32) && !defined(NETWARE)
3722 /* Win32 has a rather different view of file ownerships.
3723 For now, just forget it */
3726 else if (strcasecmp(var, "SCRIPT_USER") == 0) {
3727 result = "<unknown>";
3728 if (r->finfo.protection != 0) {
3729 if ((pw = getpwuid(r->finfo.user)) != NULL) {
3730 result = pw->pw_name;
3734 if (ap_stat(&finfo, r->filename, r->pool) == APR_SUCCESS) {
3735 if ((pw = getpwuid(finfo.user)) != NULL) {
3736 result = pw->pw_name;
3741 else if (strcasecmp(var, "SCRIPT_GROUP") == 0) {
3742 result = "<unknown>";
3743 if (r->finfo.protection != 0) {
3744 if ((gr = getgrgid(r->finfo.group)) != NULL) {
3745 result = gr->gr_name;
3749 if (ap_stat(&finfo, r->filename, r->pool) == 0) {
3750 if ((gr = getgrgid(finfo.group)) != NULL) {
3751 result = gr->gr_name;
3756 #endif /* ndef WIN32 && NETWARE*/
3758 if (result == NULL) {
3759 return ap_pstrdup(r->pool, "");
3762 return ap_pstrdup(r->pool, result);
3766 static char *lookup_header(request_rec *r, const char *name)
3768 ap_array_header_t *hdrs_arr;
3769 ap_table_entry_t *hdrs;
3772 hdrs_arr = ap_table_elts(r->headers_in);
3773 hdrs = (ap_table_entry_t *)hdrs_arr->elts;
3774 for (i = 0; i < hdrs_arr->nelts; ++i) {
3775 if (hdrs[i].key == NULL) {
3778 if (strcasecmp(hdrs[i].key, name) == 0) {
3779 ap_table_merge(r->notes, VARY_KEY_THIS, name);
3790 ** +-------------------------------------------------------+
3792 ** | caching support
3794 ** +-------------------------------------------------------+
3798 static cache *init_cache(ap_pool_t *p)
3802 c = (cache *)ap_palloc(p, sizeof(cache));
3803 if (ap_create_pool(&c->pool, p) != APR_SUCCESS)
3805 c->lists = ap_make_array(c->pool, 2, sizeof(cachelist));
3809 static void set_cache_string(cache *c, char *res, int mode, time_t t,
3810 char *key, char *value)
3817 store_cache_string(c, res, &ce);
3821 static char *get_cache_string(cache *c, char *res, int mode,
3822 time_t t, char *key)
3826 ce = retrieve_cache_string(c, res, key);
3830 if (mode & CACHEMODE_TS) {
3831 if (t != ce->time) {
3835 else if (mode & CACHEMODE_TTL) {
3840 return ap_pstrdup(c->pool, ce->value);
3843 static int cache_tlb_hash(char *key)
3849 for (p = key; *p != '\0'; p++) {
3850 n = ((n << 5) + n) ^ (unsigned long)(*p++);
3853 return n % CACHE_TLB_ROWS;
3856 static cacheentry *cache_tlb_lookup(cachetlbentry *tlb, cacheentry *elt,
3859 int ix = cache_tlb_hash(key);
3863 for (i=0; i < CACHE_TLB_COLS; ++i) {
3867 if (strcmp(elt[j].key, key) == 0)
3873 static void cache_tlb_replace(cachetlbentry *tlb, cacheentry *elt,
3876 int ix = cache_tlb_hash(e->key);
3881 for (i=1; i < CACHE_TLB_COLS; ++i)
3882 tlb->t[i] = tlb->t[i-1];
3884 tlb->t[0] = e - elt;
3887 static void store_cache_string(cache *c, char *res, cacheentry *ce)
3897 /* first try to edit an existing entry */
3898 for (i = 0; i < c->lists->nelts; i++) {
3899 l = &(((cachelist *)c->lists->elts)[i]);
3900 if (strcmp(l->resource, res) == 0) {
3903 e = cache_tlb_lookup((cachetlbentry *)l->tlb->elts,
3904 (cacheentry *)l->entries->elts, ce->key);
3907 e->value = ap_pstrdup(c->pool, ce->value);
3911 for (j = 0; j < l->entries->nelts; j++) {
3912 e = &(((cacheentry *)l->entries->elts)[j]);
3913 if (strcmp(e->key, ce->key) == 0) {
3915 e->value = ap_pstrdup(c->pool, ce->value);
3916 cache_tlb_replace((cachetlbentry *)l->tlb->elts,
3917 (cacheentry *)l->entries->elts, e);
3924 /* create a needed new list */
3926 l = ap_push_array(c->lists);
3927 l->resource = ap_pstrdup(c->pool, res);
3928 l->entries = ap_make_array(c->pool, 2, sizeof(cacheentry));
3929 l->tlb = ap_make_array(c->pool, CACHE_TLB_ROWS,
3930 sizeof(cachetlbentry));
3931 for (i=0; i<CACHE_TLB_ROWS; ++i) {
3932 t = &((cachetlbentry *)l->tlb->elts)[i];
3933 for (j=0; j<CACHE_TLB_COLS; ++j)
3938 /* create the new entry */
3939 for (i = 0; i < c->lists->nelts; i++) {
3940 l = &(((cachelist *)c->lists->elts)[i]);
3941 if (strcmp(l->resource, res) == 0) {
3942 e = ap_push_array(l->entries);
3944 e->key = ap_pstrdup(c->pool, ce->key);
3945 e->value = ap_pstrdup(c->pool, ce->value);
3946 cache_tlb_replace((cachetlbentry *)l->tlb->elts,
3947 (cacheentry *)l->entries->elts, e);
3952 /* not reached, but when it is no problem... */
3956 static cacheentry *retrieve_cache_string(cache *c, char *res, char *key)
3963 for (i = 0; i < c->lists->nelts; i++) {
3964 l = &(((cachelist *)c->lists->elts)[i]);
3965 if (strcmp(l->resource, res) == 0) {
3967 e = cache_tlb_lookup((cachetlbentry *)l->tlb->elts,
3968 (cacheentry *)l->entries->elts, key);
3972 for (j = 0; j < l->entries->nelts; j++) {
3973 e = &(((cacheentry *)l->entries->elts)[j]);
3974 if (strcmp(e->key, key) == 0) {
3987 ** +-------------------------------------------------------+
3991 ** +-------------------------------------------------------+
3994 static char *subst_prefix_path(request_rec *r, char *input, char *match,
3997 char matchbuf[LONG_STRING_LEN];
3998 char substbuf[LONG_STRING_LEN];
4004 /* first create a match string which always has a trailing slash */
4005 l = ap_cpystrn(matchbuf, match, sizeof(matchbuf)) - matchbuf;
4006 if (matchbuf[l-1] != '/') {
4008 matchbuf[l+1] = '\0';
4011 /* now compare the prefix */
4012 if (strncmp(input, matchbuf, l) == 0) {
4013 rewritelog(r, 5, "strip matching prefix: %s -> %s", output, output+l);
4014 output = ap_pstrdup(r->pool, output+l);
4016 /* and now add the base-URL as replacement prefix */
4017 l = ap_cpystrn(substbuf, subst, sizeof(substbuf)) - substbuf;
4018 if (substbuf[l-1] != '/') {
4020 substbuf[l+1] = '\0';
4023 if (output[0] == '/') {
4024 rewritelog(r, 4, "add subst prefix: %s -> %s%s",
4025 output, substbuf, output+1);
4026 output = ap_pstrcat(r->pool, substbuf, output+1, NULL);
4029 rewritelog(r, 4, "add subst prefix: %s -> %s%s",
4030 output, substbuf, output);
4031 output = ap_pstrcat(r->pool, substbuf, output, NULL);
4040 ** own command line parser which don't have the '\\' problem
4044 static int parseargline(char *str, char **a1, char **a2, char **a3)
4049 #define SKIP_WHITESPACE(cp) \
4050 for ( ; *cp == ' ' || *cp == '\t'; ) { \
4054 #define CHECK_QUOTATION(cp,isquoted) \
4061 #define DETERMINE_NEXTSTRING(cp,isquoted) \
4062 for ( ; *cp != '\0'; cp++) { \
4063 if ( (isquoted && (*cp == ' ' || *cp == '\t')) \
4064 || (*cp == '\\' && (*(cp+1) == ' ' || *(cp+1) == '\t'))) { \
4068 if ( (!isquoted && (*cp == ' ' || *cp == '\t')) \
4069 || (isquoted && *cp == '"') ) { \
4075 SKIP_WHITESPACE(cp);
4077 /* determine first argument */
4078 CHECK_QUOTATION(cp, isquoted);
4080 DETERMINE_NEXTSTRING(cp, isquoted);
4086 SKIP_WHITESPACE(cp);
4088 /* determine second argument */
4089 CHECK_QUOTATION(cp, isquoted);
4091 DETERMINE_NEXTSTRING(cp, isquoted);
4099 SKIP_WHITESPACE(cp);
4101 /* again check if there are only two arguments */
4108 /* determine second argument */
4109 CHECK_QUOTATION(cp, isquoted);
4111 DETERMINE_NEXTSTRING(cp, isquoted);
4118 static void add_env_variable(request_rec *r, char *s)
4120 char var[MAX_STRING_LEN];
4121 char val[MAX_STRING_LEN];
4125 if ((cp = strchr(s, ':')) != NULL) {
4126 n = ((cp-s) > MAX_STRING_LEN-1 ? MAX_STRING_LEN-1 : (cp-s));
4129 ap_cpystrn(val, cp+1, sizeof(val));
4130 ap_table_set(r->subprocess_env, var, val);
4131 rewritelog(r, 5, "setting env variable '%s' to '%s'", var, val);
4139 ** stat() for only the prefix of a path
4143 static int prefix_stat(const char *path, ap_finfo_t *sb)
4145 char curpath[LONG_STRING_LEN];
4148 ap_cpystrn(curpath, path, sizeof(curpath));
4149 if (curpath[0] != '/') {
4152 if ((cp = strchr(curpath+1, '/')) != NULL) {
4155 if (ap_stat(sb, curpath, NULL) == 0) {
4166 ** Lexicographic Compare
4170 static int compare_lexicography(char *cpNum1, char *cpNum2)
4175 n1 = strlen(cpNum1);
4176 n2 = strlen(cpNum2);
4183 for (i = 0; i < n1; i++) {
4184 if (cpNum1[i] > cpNum2[i]) {
4187 if (cpNum1[i] < cpNum2[i]) {
4195 int main(int argc, char *argv[])
4197 ExitThread(TSR_THREAD, 0);