1 /* ====================================================================
2 * Copyright (c) 1996-1999 The Apache Group. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in
13 * the documentation and/or other materials provided with the
16 * 3. All advertising materials mentioning features or use of this
17 * software must display the following acknowledgment:
18 * "This product includes software developed by the Apache Group
19 * for use in the Apache HTTP server project (http://www.apache.org/)."
21 * 4. The names "Apache Server" and "Apache Group" must not be used to
22 * endorse or promote products derived from this software without
23 * prior written permission. For written permission, please contact
26 * 5. Products derived from this software may not be called "Apache"
27 * nor may "Apache" appear in their names without prior written
28 * permission of the Apache Group.
30 * 6. Redistributions of any form whatsoever must retain the following
32 * "This product includes software developed by the Apache Group
33 * for use in the Apache HTTP server project (http://www.apache.org/)."
35 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
36 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
37 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
38 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
41 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
42 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
43 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
44 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
45 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
46 * OF THE POSSIBILITY OF SUCH DAMAGE.
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Group and was originally based
51 * on public domain software written at the National Center for
52 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
53 * For more information on the Apache Group and the Apache HTTP server
54 * project, please see <http://www.apache.org/>.
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 Group 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
116 ** +-------------------------------------------------------+
118 ** | static module configuration
120 ** +-------------------------------------------------------+
125 ** Our interface to the Apache server kernel:
127 ** o Runtime logic of a request is as following:
128 ** while(request or subrequest)
129 ** foreach(stage #0...#9)
130 ** foreach(module) (**)
133 ** o the order of modules at (**) is the inverted order as
134 ** given in the "Configuration" file, i.e. the last module
135 ** specified is the first one called for each hook!
136 ** The core module is always the last!
138 ** o there are two different types of result checking and
139 ** continue processing:
140 ** for hook #0,#1,#4,#5,#6,#8:
141 ** hook run loop stops on first modules which gives
142 ** back a result != DECLINED, i.e. it usually returns OK
143 ** which says "OK, module has handled this _stage_" and for #1
144 ** this have not to mean "Ok, the filename is now valid".
145 ** for hook #2,#3,#7,#9:
146 ** all hooks are run, independend of result
148 ** o at the last stage, the core module always
149 ** - says "BAD_REQUEST" if r->filename does not begin with "/"
150 ** - prefix URL with document_root or replaced server_root
151 ** with document_root and sets r->filename
152 ** - always return a "OK" independed if the file really exists
156 /* The section for the Configure script:
157 * MODULE-DEFINITION-START
158 * Name: rewrite_module
160 . ./helpers/find-dbm-lib
161 if [ "x$found_dbm" = "x1" ]; then
162 echo " enabling DBM support for mod_rewrite"
164 echo " disabling DBM support for mod_rewrite"
165 echo " (perhaps you need to add -ldbm, -lndbm or -lgdbm to EXTRA_LIBS)"
166 CFLAGS="$CFLAGS -DNO_DBM_REWRITEMAP"
169 * MODULE-DEFINITION-END
172 /* the ap_table_t of commands we provide */
173 static const command_rec command_table[] = {
174 { "RewriteEngine", cmd_rewriteengine, NULL, OR_FILEINFO, FLAG,
175 "On or Off to enable or disable (default) the whole rewriting engine" },
176 { "RewriteOptions", cmd_rewriteoptions, NULL, OR_FILEINFO, ITERATE,
177 "List of option strings to set" },
178 { "RewriteBase", cmd_rewritebase, NULL, OR_FILEINFO, TAKE1,
179 "the base URL of the per-directory context" },
180 { "RewriteCond", cmd_rewritecond, NULL, OR_FILEINFO, RAW_ARGS,
181 "an input string and a to be applied regexp-pattern" },
182 { "RewriteRule", cmd_rewriterule, NULL, OR_FILEINFO, RAW_ARGS,
183 "an URL-applied regexp-pattern and a substitution URL" },
184 { "RewriteMap", cmd_rewritemap, NULL, RSRC_CONF, TAKE2,
185 "a mapname and a filename" },
186 { "RewriteLock", cmd_rewritelock, NULL, RSRC_CONF, TAKE1,
187 "the filename of a lockfile used for inter-process synchronization"},
188 { "RewriteLog", cmd_rewritelog, NULL, RSRC_CONF, TAKE1,
189 "the filename of the rewriting logfile" },
190 { "RewriteLogLevel", cmd_rewriteloglevel, NULL, RSRC_CONF, TAKE1,
191 "the level of the rewriting logfile verbosity "
192 "(0=none, 1=std, .., 9=max)" },
196 /* the ap_table_t of content handlers we provide */
197 static const handler_rec handler_table[] = {
198 { "redirect-handler", handler_redirect },
202 static void register_hooks(void)
204 ap_hook_post_config(init_module,NULL,NULL,HOOK_MIDDLE);
205 ap_hook_child_init(init_child,NULL,NULL,HOOK_MIDDLE);
207 ap_hook_fixups(hook_fixup,NULL,NULL,HOOK_FIRST);
208 ap_hook_translate_name(hook_uri2file,NULL,NULL,HOOK_FIRST);
209 ap_hook_type_checker(hook_mimetype,NULL,NULL,HOOK_MIDDLE);
212 /* the main config structure */
213 module MODULE_VAR_EXPORT rewrite_module = {
214 STANDARD20_MODULE_STUFF,
215 config_perdir_create, /* create per-dir config structures */
216 config_perdir_merge, /* merge per-dir config structures */
217 config_server_create, /* create per-server config structures */
218 config_server_merge, /* merge per-server config structures */
219 command_table, /* ap_table_t of config file commands */
220 handler_table, /* [#8] MIME-typed-dispatched handlers */
221 register_hooks /* register hooks */
225 static cache *cachep;
227 /* whether proxy module is available or not */
228 static int proxy_available;
229 static int once_through = 0;
231 static const char *lockname;
232 static ap_file_t *lockfd = NULL;
235 ** +-------------------------------------------------------+
237 ** | configuration directive handling
239 ** +-------------------------------------------------------+
244 ** per-server configuration structure handling
248 static void *config_server_create(ap_context_t *p, server_rec *s)
250 rewrite_server_conf *a;
252 a = (rewrite_server_conf *)ap_pcalloc(p, sizeof(rewrite_server_conf));
254 a->state = ENGINE_DISABLED;
255 a->options = OPTION_NONE;
256 a->rewritelogfile = NULL;
257 a->rewritelogfp = NULL;
258 a->rewriteloglevel = 0;
259 a->rewritemaps = ap_make_array(p, 2, sizeof(rewritemap_entry));
260 a->rewriteconds = ap_make_array(p, 2, sizeof(rewritecond_entry));
261 a->rewriterules = ap_make_array(p, 2, sizeof(rewriterule_entry));
267 static void *config_server_merge(ap_context_t *p, void *basev, void *overridesv)
269 rewrite_server_conf *a, *base, *overrides;
271 a = (rewrite_server_conf *)ap_pcalloc(p, sizeof(rewrite_server_conf));
272 base = (rewrite_server_conf *)basev;
273 overrides = (rewrite_server_conf *)overridesv;
275 a->state = overrides->state;
276 a->options = overrides->options;
277 a->server = overrides->server;
279 if (a->options & OPTION_INHERIT) {
281 * local directives override
282 * and anything else is inherited
284 a->rewriteloglevel = overrides->rewriteloglevel != 0
285 ? overrides->rewriteloglevel
286 : base->rewriteloglevel;
287 a->rewritelogfile = overrides->rewritelogfile != NULL
288 ? overrides->rewritelogfile
289 : base->rewritelogfile;
290 a->rewritelogfp = overrides->rewritelogfp != NULL
291 ? overrides->rewritelogfp
292 : base->rewritelogfp;
293 a->rewritemaps = ap_append_arrays(p, overrides->rewritemaps,
295 a->rewriteconds = ap_append_arrays(p, overrides->rewriteconds,
297 a->rewriterules = ap_append_arrays(p, overrides->rewriterules,
302 * local directives override
303 * and anything else gets defaults
305 a->rewriteloglevel = overrides->rewriteloglevel;
306 a->rewritelogfile = overrides->rewritelogfile;
307 a->rewritelogfp = overrides->rewritelogfp;
308 a->rewritemaps = overrides->rewritemaps;
309 a->rewriteconds = overrides->rewriteconds;
310 a->rewriterules = overrides->rewriterules;
319 ** per-directory configuration structure handling
323 static void *config_perdir_create(ap_context_t *p, char *path)
325 rewrite_perdir_conf *a;
327 a = (rewrite_perdir_conf *)ap_pcalloc(p, sizeof(rewrite_perdir_conf));
329 a->state = ENGINE_DISABLED;
330 a->options = OPTION_NONE;
332 a->rewriteconds = ap_make_array(p, 2, sizeof(rewritecond_entry));
333 a->rewriterules = ap_make_array(p, 2, sizeof(rewriterule_entry));
339 /* make sure it has a trailing slash */
340 if (path[strlen(path)-1] == '/') {
341 a->directory = ap_pstrdup(p, path);
344 a->directory = ap_pstrcat(p, path, "/", NULL);
351 static void *config_perdir_merge(ap_context_t *p, void *basev, void *overridesv)
353 rewrite_perdir_conf *a, *base, *overrides;
355 a = (rewrite_perdir_conf *)ap_pcalloc(p,
356 sizeof(rewrite_perdir_conf));
357 base = (rewrite_perdir_conf *)basev;
358 overrides = (rewrite_perdir_conf *)overridesv;
360 a->state = overrides->state;
361 a->options = overrides->options;
362 a->directory = overrides->directory;
363 a->baseurl = overrides->baseurl;
365 if (a->options & OPTION_INHERIT) {
366 a->rewriteconds = ap_append_arrays(p, overrides->rewriteconds,
368 a->rewriterules = ap_append_arrays(p, overrides->rewriterules,
372 a->rewriteconds = overrides->rewriteconds;
373 a->rewriterules = overrides->rewriterules;
382 ** the configuration commands
386 static const char *cmd_rewriteengine(cmd_parms *cmd,
387 rewrite_perdir_conf *dconf, int flag)
389 rewrite_server_conf *sconf;
392 (rewrite_server_conf *)ap_get_module_config(cmd->server->module_config,
395 if (cmd->path == NULL) { /* is server command */
396 sconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
398 else /* is per-directory command */ {
399 dconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
405 static const char *cmd_rewriteoptions(cmd_parms *cmd,
406 rewrite_perdir_conf *dconf, char *option)
408 rewrite_server_conf *sconf;
411 sconf = (rewrite_server_conf *)
412 ap_get_module_config(cmd->server->module_config, &rewrite_module);
414 if (cmd->path == NULL) { /* is server command */
415 err = cmd_rewriteoptions_setoption(cmd->pool,
416 &(sconf->options), option);
418 else { /* is per-directory command */
419 err = cmd_rewriteoptions_setoption(cmd->pool,
420 &(dconf->options), option);
426 static const char *cmd_rewriteoptions_setoption(ap_context_t *p, int *options,
429 if (strcasecmp(name, "inherit") == 0) {
430 *options |= OPTION_INHERIT;
433 return ap_pstrcat(p, "RewriteOptions: unknown option '",
439 static const char *cmd_rewritelog(cmd_parms *cmd, void *dconf, char *a1)
441 rewrite_server_conf *sconf;
443 sconf = (rewrite_server_conf *)
444 ap_get_module_config(cmd->server->module_config, &rewrite_module);
446 sconf->rewritelogfile = a1;
451 static const char *cmd_rewriteloglevel(cmd_parms *cmd, void *dconf, char *a1)
453 rewrite_server_conf *sconf;
455 sconf = (rewrite_server_conf *)
456 ap_get_module_config(cmd->server->module_config, &rewrite_module);
458 sconf->rewriteloglevel = atoi(a1);
463 static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, char *a1,
466 rewrite_server_conf *sconf;
467 rewritemap_entry *newmap;
470 sconf = (rewrite_server_conf *)
471 ap_get_module_config(cmd->server->module_config, &rewrite_module);
473 newmap = ap_push_array(sconf->rewritemaps);
477 if (strncmp(a2, "txt:", 4) == 0) {
478 newmap->type = MAPTYPE_TXT;
479 newmap->datafile = a2+4;
480 newmap->checkfile = a2+4;
482 else if (strncmp(a2, "rnd:", 4) == 0) {
483 newmap->type = MAPTYPE_RND;
484 newmap->datafile = a2+4;
485 newmap->checkfile = a2+4;
487 else if (strncmp(a2, "dbm:", 4) == 0) {
488 #ifndef NO_DBM_REWRITEMAP
489 newmap->type = MAPTYPE_DBM;
490 newmap->datafile = a2+4;
491 newmap->checkfile = ap_pstrcat(cmd->pool, a2+4, NDBM_FILE_SUFFIX, NULL);
493 return ap_pstrdup(cmd->pool, "RewriteMap: cannot use NDBM mapfile, "
494 "because no NDBM support is compiled in");
497 else if (strncmp(a2, "prg:", 4) == 0) {
498 newmap->type = MAPTYPE_PRG;
499 newmap->datafile = a2+4;
500 newmap->checkfile = a2+4;
502 else if (strncmp(a2, "int:", 4) == 0) {
503 newmap->type = MAPTYPE_INT;
504 newmap->datafile = NULL;
505 newmap->checkfile = NULL;
506 if (strcmp(a2+4, "tolower") == 0) {
507 newmap->func = rewrite_mapfunc_tolower;
509 else if (strcmp(a2+4, "toupper") == 0) {
510 newmap->func = rewrite_mapfunc_toupper;
512 else if (strcmp(a2+4, "escape") == 0) {
513 newmap->func = rewrite_mapfunc_escape;
515 else if (strcmp(a2+4, "unescape") == 0) {
516 newmap->func = rewrite_mapfunc_unescape;
518 else if (sconf->state == ENGINE_ENABLED) {
519 return ap_pstrcat(cmd->pool, "RewriteMap: internal map not found:",
524 newmap->type = MAPTYPE_TXT;
525 newmap->datafile = a2;
526 newmap->checkfile = a2;
529 newmap->fpout = NULL;
531 if (newmap->checkfile && (sconf->state == ENGINE_ENABLED)
532 && (stat(newmap->checkfile, &st) == -1)) {
533 return ap_pstrcat(cmd->pool,
534 "RewriteMap: map file or program not found:",
535 newmap->checkfile, NULL);
541 static const char *cmd_rewritelock(cmd_parms *cmd, void *dconf, char *a1)
545 if ((error = ap_check_cmd_context(cmd, GLOBAL_ONLY)) != NULL)
553 static const char *cmd_rewritebase(cmd_parms *cmd, rewrite_perdir_conf *dconf,
556 if (cmd->path == NULL || dconf == NULL) {
557 return "RewriteBase: only valid in per-directory config files";
560 return "RewriteBase: empty URL not allowed";
563 return "RewriteBase: argument is not a valid URL";
571 static const char *cmd_rewritecond(cmd_parms *cmd, rewrite_perdir_conf *dconf,
574 rewrite_server_conf *sconf;
575 rewritecond_entry *newcond;
584 sconf = (rewrite_server_conf *)
585 ap_get_module_config(cmd->server->module_config, &rewrite_module);
587 /* make a new entry in the internal temporary rewrite rule list */
588 if (cmd->path == NULL) { /* is server command */
589 newcond = ap_push_array(sconf->rewriteconds);
591 else { /* is per-directory command */
592 newcond = ap_push_array(dconf->rewriteconds);
595 /* parse the argument line ourself */
596 if (parseargline(str, &a1, &a2, &a3)) {
597 return ap_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str,
601 /* arg1: the input string */
602 newcond->input = ap_pstrdup(cmd->pool, a1);
604 /* arg3: optional flags field
605 (this have to be first parsed, because we need to
606 know if the regex should be compiled with ICASE!) */
607 newcond->flags = CONDFLAG_NONE;
609 if ((err = cmd_rewritecond_parseflagfield(cmd->pool, newcond,
616 try to compile the regexp to test if is ok */
619 newcond->flags |= CONDFLAG_NOTMATCH;
623 /* now be careful: Under the POSIX regex library
624 we can compile the pattern for case insensitive matching,
625 under the old V8 library we have to do it self via a hack */
626 if (newcond->flags & CONDFLAG_NOCASE) {
627 rc = ((regexp = ap_pregcomp(cmd->pool, cp, REG_EXTENDED|REG_ICASE))
631 rc = ((regexp = ap_pregcomp(cmd->pool, cp, REG_EXTENDED)) == NULL);
634 return ap_pstrcat(cmd->pool,
635 "RewriteCond: cannot compile regular expression '",
639 newcond->pattern = ap_pstrdup(cmd->pool, cp);
640 newcond->regexp = regexp;
645 static const char *cmd_rewritecond_parseflagfield(ap_context_t *p,
646 rewritecond_entry *cfg,
657 if (str[0] != '[' || str[strlen(str)-1] != ']') {
658 return "RewriteCond: bad flag delimiters";
662 str[strlen(str)-1] = ','; /* for simpler parsing */
663 for ( ; *cp != '\0'; ) {
664 /* skip whitespaces */
665 for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++)
671 if ((cp2 = strchr(cp, ',')) != NULL) {
673 for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--)
676 if ((cp3 = strchr(cp1, '=')) != NULL) {
685 if ((err = cmd_rewritecond_setflag(p, cfg, key, val)) != NULL) {
697 static const char *cmd_rewritecond_setflag(ap_context_t *p, rewritecond_entry *cfg,
698 char *key, char *val)
700 if ( strcasecmp(key, "nocase") == 0
701 || strcasecmp(key, "NC") == 0 ) {
702 cfg->flags |= CONDFLAG_NOCASE;
704 else if ( strcasecmp(key, "ornext") == 0
705 || strcasecmp(key, "OR") == 0 ) {
706 cfg->flags |= CONDFLAG_ORNEXT;
709 return ap_pstrcat(p, "RewriteCond: unknown flag '", key, "'\n", NULL);
714 static const char *cmd_rewriterule(cmd_parms *cmd, rewrite_perdir_conf *dconf,
717 rewrite_server_conf *sconf;
718 rewriterule_entry *newrule;
727 sconf = (rewrite_server_conf *)
728 ap_get_module_config(cmd->server->module_config, &rewrite_module);
730 /* make a new entry in the internal rewrite rule list */
731 if (cmd->path == NULL) { /* is server command */
732 newrule = ap_push_array(sconf->rewriterules);
734 else { /* is per-directory command */
735 newrule = ap_push_array(dconf->rewriterules);
738 /* parse the argument line ourself */
739 if (parseargline(str, &a1, &a2, &a3)) {
740 return ap_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str,
744 /* arg3: optional flags field */
745 newrule->forced_mimetype = NULL;
746 newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY;
747 newrule->flags = RULEFLAG_NONE;
748 newrule->env[0] = NULL;
751 if ((err = cmd_rewriterule_parseflagfield(cmd->pool, newrule,
758 * try to compile the regexp to test if is ok
762 newrule->flags |= RULEFLAG_NOTMATCH;
766 if (newrule->flags & RULEFLAG_NOCASE) {
769 if ((regexp = ap_pregcomp(cmd->pool, cp, mode)) == NULL) {
770 return ap_pstrcat(cmd->pool,
771 "RewriteRule: cannot compile regular expression '",
774 newrule->pattern = ap_pstrdup(cmd->pool, cp);
775 newrule->regexp = regexp;
777 /* arg2: the output string
778 * replace the $<N> by \<n> which is needed by the currently
779 * used Regular Expression library
781 newrule->output = ap_pstrdup(cmd->pool, a2);
783 /* now, if the server or per-dir config holds an
784 * array of RewriteCond entries, we take it for us
785 * and clear the array
787 if (cmd->path == NULL) { /* is server command */
788 newrule->rewriteconds = sconf->rewriteconds;
789 sconf->rewriteconds = ap_make_array(cmd->pool, 2,
790 sizeof(rewritecond_entry));
792 else { /* is per-directory command */
793 newrule->rewriteconds = dconf->rewriteconds;
794 dconf->rewriteconds = ap_make_array(cmd->pool, 2,
795 sizeof(rewritecond_entry));
801 static const char *cmd_rewriterule_parseflagfield(ap_context_t *p,
802 rewriterule_entry *cfg,
813 if (str[0] != '[' || str[strlen(str)-1] != ']') {
814 return "RewriteRule: bad flag delimiters";
818 str[strlen(str)-1] = ','; /* for simpler parsing */
819 for ( ; *cp != '\0'; ) {
820 /* skip whitespaces */
821 for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++)
827 if ((cp2 = strchr(cp, ',')) != NULL) {
829 for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--)
832 if ((cp3 = strchr(cp1, '=')) != NULL) {
841 if ((err = cmd_rewriterule_setflag(p, cfg, key, val)) != NULL) {
853 static const char *cmd_rewriterule_setflag(ap_context_t *p, rewriterule_entry *cfg,
854 char *key, char *val)
859 if ( strcasecmp(key, "redirect") == 0
860 || strcasecmp(key, "R") == 0 ) {
861 cfg->flags |= RULEFLAG_FORCEREDIRECT;
862 if (strlen(val) > 0) {
863 if (strcasecmp(val, "permanent") == 0) {
864 status = HTTP_MOVED_PERMANENTLY;
866 else if (strcasecmp(val, "temp") == 0) {
867 status = HTTP_MOVED_TEMPORARILY;
869 else if (strcasecmp(val, "seeother") == 0) {
870 status = HTTP_SEE_OTHER;
872 else if (ap_isdigit(*val)) {
875 if (!ap_is_HTTP_REDIRECT(status)) {
876 return "RewriteRule: invalid HTTP response code "
879 cfg->forced_responsecode = status;
882 else if ( strcasecmp(key, "last") == 0
883 || strcasecmp(key, "L") == 0 ) {
884 cfg->flags |= RULEFLAG_LASTRULE;
886 else if ( strcasecmp(key, "next") == 0
887 || strcasecmp(key, "N") == 0 ) {
888 cfg->flags |= RULEFLAG_NEWROUND;
890 else if ( strcasecmp(key, "chain") == 0
891 || strcasecmp(key, "C") == 0 ) {
892 cfg->flags |= RULEFLAG_CHAIN;
894 else if ( strcasecmp(key, "type") == 0
895 || strcasecmp(key, "T") == 0 ) {
896 cfg->forced_mimetype = ap_pstrdup(p, val);
897 ap_str_tolower(cfg->forced_mimetype);
899 else if ( strcasecmp(key, "env") == 0
900 || strcasecmp(key, "E") == 0 ) {
901 for (i = 0; (cfg->env[i] != NULL) && (i < MAX_ENV_FLAGS); i++)
903 if (i < MAX_ENV_FLAGS) {
904 cfg->env[i] = ap_pstrdup(p, val);
905 cfg->env[i+1] = NULL;
908 return "RewriteRule: too many environment flags 'E'";
911 else if ( strcasecmp(key, "nosubreq") == 0
912 || strcasecmp(key, "NS") == 0 ) {
913 cfg->flags |= RULEFLAG_IGNOREONSUBREQ;
915 else if ( strcasecmp(key, "proxy") == 0
916 || strcasecmp(key, "P") == 0 ) {
917 cfg->flags |= RULEFLAG_PROXY;
919 else if ( strcasecmp(key, "passthrough") == 0
920 || strcasecmp(key, "PT") == 0 ) {
921 cfg->flags |= RULEFLAG_PASSTHROUGH;
923 else if ( strcasecmp(key, "skip") == 0
924 || strcasecmp(key, "S") == 0 ) {
925 cfg->skip = atoi(val);
927 else if ( strcasecmp(key, "forbidden") == 0
928 || strcasecmp(key, "F") == 0 ) {
929 cfg->flags |= RULEFLAG_FORBIDDEN;
931 else if ( strcasecmp(key, "gone") == 0
932 || strcasecmp(key, "G") == 0 ) {
933 cfg->flags |= RULEFLAG_GONE;
935 else if ( strcasecmp(key, "qsappend") == 0
936 || strcasecmp(key, "QSA") == 0 ) {
937 cfg->flags |= RULEFLAG_QSAPPEND;
939 else if ( strcasecmp(key, "nocase") == 0
940 || strcasecmp(key, "NC") == 0 ) {
941 cfg->flags |= RULEFLAG_NOCASE;
944 return ap_pstrcat(p, "RewriteRule: unknown flag '", key, "'\n", NULL);
952 ** Global Module Initialization
953 ** [called from read_config() after all
954 ** config commands were already called]
958 static void init_module(ap_context_t *p,
963 /* check if proxy module is available */
964 proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL);
966 /* create the rewriting lockfile in the parent */
967 rewritelock_create(s, p);
968 ap_register_cleanup(p, (void *)s, rewritelock_remove, ap_null_cleanup);
970 /* step through the servers and
971 * - open each rewriting logfile
972 * - open the RewriteMap prg:xxx programs
974 for (; s; s = s->next) {
975 open_rewritelog(s, p);
976 if (once_through > 0)
977 run_rewritemap_programs(s, p);
986 ** Per-Child Module Initialization
987 ** [called after a child process is spawned]
991 static void init_child(ap_context_t *p, server_rec *s)
993 /* open the rewriting lockfile */
994 rewritelock_open(s, p);
996 /* create the lookup cache */
997 cachep = init_cache(p);
1002 ** +-------------------------------------------------------+
1006 ** +-------------------------------------------------------+
1011 ** URI-to-filename hook
1013 ** [used for the rewriting engine triggered by
1014 ** the per-server 'RewriteRule' directives]
1018 static int hook_uri2file(request_rec *r)
1021 rewrite_server_conf *conf;
1023 const char *thisserver;
1025 const char *thisurl;
1036 * retrieve the config structures
1038 sconf = r->server->module_config;
1039 conf = (rewrite_server_conf *)ap_get_module_config(sconf,
1043 * only do something under runtime if the engine is really enabled,
1044 * else return immediately!
1046 if (conf->state == ENGINE_DISABLED) {
1051 * check for the ugly API case of a virtual host section where no
1052 * mod_rewrite directives exists. In this situation we became no chance
1053 * by the API to setup our default per-server config so we have to
1054 * on-the-fly assume we have the default config. But because the default
1055 * config has a disabled rewriting engine we are lucky because can
1056 * just stop operating now.
1058 if (conf->server != r->server) {
1063 * add the SCRIPT_URL variable to the env. this is a bit complicated
1064 * due to the fact that apache uses subrequests and internal redirects
1067 if (r->main == NULL) {
1068 var = ap_pstrcat(r->pool, "REDIRECT_", ENVVAR_SCRIPT_URL, NULL);
1069 var = ap_table_get(r->subprocess_env, var);
1071 ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, r->uri);
1074 ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
1078 var = ap_table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL);
1079 ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
1083 * create the SCRIPT_URI variable for the env
1086 /* add the canonical URI of this URL */
1087 thisserver = ap_get_server_name(r);
1088 port = ap_get_server_port(r);
1089 if (ap_is_default_port(port, r)) {
1093 ap_snprintf(buf, sizeof(buf), ":%u", port);
1096 thisurl = ap_table_get(r->subprocess_env, ENVVAR_SCRIPT_URL);
1098 /* set the variable */
1099 var = ap_pstrcat(r->pool, ap_http_method(r), "://", thisserver, thisport,
1101 ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URI, var);
1103 /* if filename was not initially set,
1104 * we start with the requested URI
1106 if (r->filename == NULL) {
1107 r->filename = ap_pstrdup(r->pool, r->uri);
1108 rewritelog(r, 2, "init rewrite engine with requested uri %s",
1113 * now apply the rules ...
1115 if (apply_rewrite_list(r, conf->rewriterules, NULL)) {
1117 if (strlen(r->filename) > 6 &&
1118 strncmp(r->filename, "proxy:", 6) == 0) {
1119 /* it should be go on as an internal proxy request */
1121 /* check if the proxy module is enabled, so
1122 * we can actually use it!
1124 if (!proxy_available) {
1125 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1126 "attempt to make remote request from mod_rewrite "
1127 "without proxy enabled: %s", r->filename);
1131 /* make sure the QUERY_STRING and
1132 * PATH_INFO parts get incorporated
1134 if (r->path_info != NULL) {
1135 r->filename = ap_pstrcat(r->pool, r->filename,
1136 r->path_info, NULL);
1138 if (r->args != NULL &&
1139 r->uri == r->unparsed_uri) {
1140 /* see proxy_http:proxy_http_canon() */
1141 r->filename = ap_pstrcat(r->pool, r->filename,
1142 "?", r->args, NULL);
1145 /* now make sure the request gets handled by the proxy handler */
1147 r->handler = "proxy-server";
1149 rewritelog(r, 1, "go-ahead with proxy request %s [OK]",
1153 else if ( (strlen(r->filename) > 7 &&
1154 strncasecmp(r->filename, "http://", 7) == 0)
1155 || (strlen(r->filename) > 8 &&
1156 strncasecmp(r->filename, "https://", 8) == 0)
1157 || (strlen(r->filename) > 9 &&
1158 strncasecmp(r->filename, "gopher://", 9) == 0)
1159 || (strlen(r->filename) > 6 &&
1160 strncasecmp(r->filename, "ftp://", 6) == 0)
1161 || (strlen(r->filename) > 5 &&
1162 strncasecmp(r->filename, "ldap:", 5) == 0)
1163 || (strlen(r->filename) > 5 &&
1164 strncasecmp(r->filename, "news:", 5) == 0)
1165 || (strlen(r->filename) > 7 &&
1166 strncasecmp(r->filename, "mailto:", 7) == 0)) {
1167 /* it was finally rewritten to a remote URL */
1169 /* skip 'scheme:' */
1170 for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1174 /* skip host part */
1175 for ( ; *cp != '/' && *cp != '\0'; cp++)
1178 rewritelog(r, 1, "escaping %s for redirect", r->filename);
1179 cp2 = ap_escape_uri(r->pool, cp);
1181 r->filename = ap_pstrcat(r->pool, r->filename, cp2, NULL);
1184 /* append the QUERY_STRING part */
1185 if (r->args != NULL) {
1186 r->filename = ap_pstrcat(r->pool, r->filename, "?",
1187 ap_escape_uri(r->pool, r->args), NULL);
1190 /* determine HTTP redirect response code */
1191 if (ap_is_HTTP_REDIRECT(r->status)) {
1193 r->status = HTTP_OK; /* make Apache kernel happy */
1199 /* now do the redirection */
1200 ap_table_setn(r->headers_out, "Location", r->filename);
1201 rewritelog(r, 1, "redirect to %s [REDIRECT/%d]", r->filename, n);
1204 else if (strlen(r->filename) > 10 &&
1205 strncmp(r->filename, "forbidden:", 10) == 0) {
1206 /* This URLs is forced to be forbidden for the requester */
1209 else if (strlen(r->filename) > 5 &&
1210 strncmp(r->filename, "gone:", 5) == 0) {
1211 /* This URLs is forced to be gone */
1214 else if (strlen(r->filename) > 12 &&
1215 strncmp(r->filename, "passthrough:", 12) == 0) {
1217 * Hack because of underpowered API: passing the current
1218 * rewritten filename through to other URL-to-filename handlers
1219 * just as it were the requested URL. This is to enable
1220 * post-processing by mod_alias, etc. which always act on
1221 * r->uri! The difference here is: We do not try to
1222 * add the document root
1224 r->uri = ap_pstrdup(r->pool, r->filename+12);
1228 /* it was finally rewritten to a local path */
1230 /* expand "/~user" prefix */
1231 #if !defined(WIN32) && !defined(NETWARE)
1232 r->filename = expand_tildepaths(r, r->filename);
1234 rewritelog(r, 2, "local path result: %s", r->filename);
1236 /* the filename has to start with a slash! */
1237 if (r->filename[0] != '/') {
1241 /* if there is no valid prefix, we have
1242 * to emulate the translator from the core and
1243 * prefix the filename with document_root
1246 * We cannot leave out the prefix_stat because
1247 * - when we always prefix with document_root
1248 * then no absolute path can be created, e.g. via
1249 * emulating a ScriptAlias directive, etc.
1250 * - when we always NOT prefix with document_root
1251 * then the files under document_root have to
1252 * be references directly and document_root
1253 * gets never used and will be a dummy parameter -
1257 * Under real Unix systems this is no problem,
1258 * because we only do stat() on the first directory
1259 * and this gets cached by the kernel for along time!
1261 n = prefix_stat(r->filename, &finfo);
1263 if ((ccp = ap_document_root(r)) != NULL) {
1264 l = ap_cpystrn(docroot, ccp, sizeof(docroot)) - docroot;
1266 /* always NOT have a trailing slash */
1267 if (docroot[l-1] == '/') {
1268 docroot[l-1] = '\0';
1271 && !strncmp(r->filename, r->server->path,
1272 r->server->pathlen)) {
1273 r->filename = ap_pstrcat(r->pool, docroot,
1275 r->server->pathlen), NULL);
1278 r->filename = ap_pstrcat(r->pool, docroot,
1281 rewritelog(r, 2, "prefixed with document_root to %s",
1286 rewritelog(r, 1, "go-ahead with %s [OK]", r->filename);
1291 rewritelog(r, 1, "pass through %s", r->filename);
1301 ** [used to support the forced-MIME-type feature]
1305 static int hook_mimetype(request_rec *r)
1309 /* now check if we have to force a MIME-type */
1310 t = ap_table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR);
1315 rewritelog(r, 1, "force filename %s to have MIME-type '%s'",
1317 r->content_type = t;
1327 ** [used for the rewriting engine triggered by
1328 ** the per-directory 'RewriteRule' directives]
1332 static int hook_fixup(request_rec *r)
1334 rewrite_perdir_conf *dconf;
1343 dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
1346 /* if there is no per-dir config we return immediately */
1347 if (dconf == NULL) {
1351 /* we shouldn't do anything in subrequests */
1352 if (r->main != NULL) {
1356 /* if there are no real (i.e. no RewriteRule directives!)
1357 per-dir config of us, we return also immediately */
1358 if (dconf->directory == NULL) {
1363 * only do something under runtime if the engine is really enabled,
1364 * for this directory, else return immediately!
1366 if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) {
1367 /* FollowSymLinks is mandatory! */
1368 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1369 "Options FollowSymLinks or SymLinksIfOwnerMatch is off "
1370 "which implies that RewriteRule directive is forbidden: "
1375 /* FollowSymLinks is given, but the user can
1376 * still turn off the rewriting engine
1378 if (dconf->state == ENGINE_DISABLED) {
1384 * remember the current filename before rewriting for later check
1385 * to prevent deadlooping because of internal redirects
1386 * on final URL/filename which can be equal to the inital one.
1388 ofilename = r->filename;
1391 * now apply the rules ...
1393 if (apply_rewrite_list(r, dconf->rewriterules, dconf->directory)) {
1395 if (strlen(r->filename) > 6 &&
1396 strncmp(r->filename, "proxy:", 6) == 0) {
1397 /* it should go on as an internal proxy request */
1399 /* make sure the QUERY_STRING and
1400 * PATH_INFO parts get incorporated
1401 * (r->path_info was already appended by the
1402 * rewriting engine because of the per-dir context!)
1404 if (r->args != NULL) {
1405 r->filename = ap_pstrcat(r->pool, r->filename,
1406 "?", r->args, NULL);
1409 /* now make sure the request gets handled by the proxy handler */
1411 r->handler = "proxy-server";
1413 rewritelog(r, 1, "[per-dir %s] go-ahead with proxy request "
1414 "%s [OK]", dconf->directory, r->filename);
1417 else if ( (strlen(r->filename) > 7 &&
1418 strncasecmp(r->filename, "http://", 7) == 0)
1419 || (strlen(r->filename) > 8 &&
1420 strncasecmp(r->filename, "https://", 8) == 0)
1421 || (strlen(r->filename) > 9 &&
1422 strncasecmp(r->filename, "gopher://", 9) == 0)
1423 || (strlen(r->filename) > 6 &&
1424 strncasecmp(r->filename, "ftp://", 6) == 0)
1425 || (strlen(r->filename) > 5 &&
1426 strncasecmp(r->filename, "ldap:", 5) == 0)
1427 || (strlen(r->filename) > 5 &&
1428 strncasecmp(r->filename, "news:", 5) == 0)
1429 || (strlen(r->filename) > 7 &&
1430 strncasecmp(r->filename, "mailto:", 7) == 0)) {
1431 /* it was finally rewritten to a remote URL */
1433 /* because we are in a per-dir context
1434 * first try to replace the directory with its base-URL
1435 * if there is a base-URL available
1437 if (dconf->baseurl != NULL) {
1438 /* skip 'scheme:' */
1439 for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1443 if ((cp = strchr(cp, '/')) != NULL) {
1445 "[per-dir %s] trying to replace "
1446 "prefix %s with %s",
1447 dconf->directory, dconf->directory,
1449 cp2 = subst_prefix_path(r, cp, dconf->directory,
1451 if (strcmp(cp2, cp) != 0) {
1453 r->filename = ap_pstrcat(r->pool, r->filename,
1459 /* now prepare the redirect... */
1461 /* skip 'scheme:' */
1462 for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1466 /* skip host part */
1467 for ( ; *cp != '/' && *cp != '\0'; cp++)
1470 rewritelog(r, 1, "[per-dir %s] escaping %s for redirect",
1471 dconf->directory, r->filename);
1472 cp2 = ap_escape_uri(r->pool, cp);
1474 r->filename = ap_pstrcat(r->pool, r->filename, cp2, NULL);
1477 /* append the QUERY_STRING part */
1478 if (r->args != NULL) {
1479 r->filename = ap_pstrcat(r->pool, r->filename, "?",
1480 ap_escape_uri(r->pool, r->args), NULL);
1483 /* determine HTTP redirect response code */
1484 if (ap_is_HTTP_REDIRECT(r->status)) {
1486 r->status = HTTP_OK; /* make Apache kernel happy */
1492 /* now do the redirection */
1493 ap_table_setn(r->headers_out, "Location", r->filename);
1494 rewritelog(r, 1, "[per-dir %s] redirect to %s [REDIRECT/%d]",
1495 dconf->directory, r->filename, n);
1498 else if (strlen(r->filename) > 10 &&
1499 strncmp(r->filename, "forbidden:", 10) == 0) {
1500 /* This URL is forced to be forbidden for the requester */
1503 else if (strlen(r->filename) > 5 &&
1504 strncmp(r->filename, "gone:", 5) == 0) {
1505 /* This URL is forced to be gone */
1509 /* it was finally rewritten to a local path */
1511 /* if someone used the PASSTHROUGH flag in per-dir
1512 * context we just ignore it. It is only useful
1513 * in per-server context
1515 if (strlen(r->filename) > 12 &&
1516 strncmp(r->filename, "passthrough:", 12) == 0) {
1517 r->filename = ap_pstrdup(r->pool, r->filename+12);
1520 /* the filename has to start with a slash! */
1521 if (r->filename[0] != '/') {
1525 /* Check for deadlooping:
1526 * At this point we KNOW that at least one rewriting
1527 * rule was applied, but when the resulting URL is
1528 * the same as the initial URL, we are not allowed to
1529 * use the following internal redirection stuff because
1530 * this would lead to a deadloop.
1532 if (strcmp(r->filename, ofilename) == 0) {
1533 rewritelog(r, 1, "[per-dir %s] initial URL equal rewritten "
1534 "URL: %s [IGNORING REWRITE]",
1535 dconf->directory, r->filename);
1539 /* if there is a valid base-URL then substitute
1540 * the per-dir prefix with this base-URL if the
1541 * current filename still is inside this per-dir
1542 * context. If not then treat the result as a
1545 if (dconf->baseurl != NULL) {
1547 "[per-dir %s] trying to replace prefix %s with %s",
1548 dconf->directory, dconf->directory, dconf->baseurl);
1549 r->filename = subst_prefix_path(r, r->filename,
1554 /* if no explicit base-URL exists we assume
1555 * that the directory prefix is also a valid URL
1556 * for this webserver and only try to remove the
1557 * document_root if it is prefix
1559 if ((ccp = ap_document_root(r)) != NULL) {
1560 prefix = ap_pstrdup(r->pool, ccp);
1561 /* always NOT have a trailing slash */
1563 if (prefix[l-1] == '/') {
1567 if (strncmp(r->filename, prefix, l) == 0) {
1569 "[per-dir %s] strip document_root "
1571 dconf->directory, r->filename,
1573 r->filename = ap_pstrdup(r->pool, r->filename+l);
1578 /* now initiate the internal redirect */
1579 rewritelog(r, 1, "[per-dir %s] internal redirect with %s "
1580 "[INTERNAL REDIRECT]", dconf->directory, r->filename);
1581 r->filename = ap_pstrcat(r->pool, "redirect:", r->filename, NULL);
1582 r->handler = "redirect-handler";
1587 rewritelog(r, 1, "[per-dir %s] pass through %s",
1588 dconf->directory, r->filename);
1598 ** [used for redirect support]
1602 static int handler_redirect(request_rec *r)
1604 /* just make sure that we are really meant! */
1605 if (strncmp(r->filename, "redirect:", 9) != 0) {
1609 /* now do the internal redirect */
1610 ap_internal_redirect(ap_pstrcat(r->pool, r->filename+9,
1611 r->args ? "?" : NULL, r->args, NULL), r);
1613 /* and return gracefully */
1619 ** +-------------------------------------------------------+
1621 ** | the rewriting engine
1623 ** +-------------------------------------------------------+
1627 * Apply a complete rule set,
1628 * i.e. a list of rewrite rules
1630 static int apply_rewrite_list(request_rec *r, ap_array_header_t *rewriterules,
1633 rewriterule_entry *entries;
1634 rewriterule_entry *p;
1641 * Iterate over all existing rules
1643 entries = (rewriterule_entry *)rewriterules->elts;
1646 for (i = 0; i < rewriterules->nelts; i++) {
1650 * Ignore this rule on subrequests if we are explicitly
1651 * asked to do so or this is a proxy-throughput or a
1652 * forced redirect rule.
1654 if (r->main != NULL &&
1655 (p->flags & RULEFLAG_IGNOREONSUBREQ ||
1656 p->flags & RULEFLAG_PROXY ||
1657 p->flags & RULEFLAG_FORCEREDIRECT )) {
1662 * Apply the current rule.
1664 rc = apply_rewrite_rule(r, p, perdir);
1667 * Indicate a change if this was not a match-only rule.
1674 * Pass-Through Feature (`RewriteRule .. .. [PT]'):
1675 * Because the Apache 1.x API is very limited we
1676 * need this hack to pass the rewritten URL to other
1677 * modules like mod_alias, mod_userdir, etc.
1679 if (p->flags & RULEFLAG_PASSTHROUGH) {
1680 rewritelog(r, 2, "forcing '%s' to get passed through "
1681 "to next API URI-to-filename handler", r->filename);
1682 r->filename = ap_pstrcat(r->pool, "passthrough:",
1689 * Rule has the "forbidden" flag set which means that
1690 * we stop processing and indicate this to the caller.
1692 if (p->flags & RULEFLAG_FORBIDDEN) {
1693 rewritelog(r, 2, "forcing '%s' to be forbidden", r->filename);
1694 r->filename = ap_pstrcat(r->pool, "forbidden:",
1701 * Rule has the "gone" flag set which means that
1702 * we stop processing and indicate this to the caller.
1704 if (p->flags & RULEFLAG_GONE) {
1705 rewritelog(r, 2, "forcing '%s' to be gone", r->filename);
1706 r->filename = ap_pstrcat(r->pool, "gone:", r->filename, NULL);
1712 * Stop processing also on proxy pass-through and
1713 * last-rule and new-round flags.
1715 if (p->flags & RULEFLAG_PROXY) {
1718 if (p->flags & RULEFLAG_LASTRULE) {
1723 * On "new-round" flag we just start from the top of
1724 * the rewriting ruleset again.
1726 if (p->flags & RULEFLAG_NEWROUND) {
1731 * If we are forced to skip N next rules, do it now.
1735 while ( i < rewriterules->nelts
1745 * If current rule is chained with next rule(s),
1746 * skip all this next rule(s)
1748 while ( i < rewriterules->nelts
1749 && p->flags & RULEFLAG_CHAIN) {
1759 * Apply a single(!) rewrite rule
1761 static int apply_rewrite_rule(request_rec *r, rewriterule_entry *p,
1767 char newuri[MAX_STRING_LEN];
1768 char env[MAX_STRING_LEN];
1770 regmatch_t regmatch[MAX_NMATCH];
1771 backrefinfo *briRR = NULL;
1772 backrefinfo *briRC = NULL;
1775 ap_array_header_t *rewriteconds;
1776 rewritecond_entry *conds;
1777 rewritecond_entry *c;
1789 * Add (perhaps splitted away) PATH_INFO postfix to URL to
1790 * make sure we really match against the complete URL.
1792 if (perdir != NULL && r->path_info != NULL && r->path_info[0] != '\0') {
1793 rewritelog(r, 3, "[per-dir %s] add path info postfix: %s -> %s%s",
1794 perdir, uri, uri, r->path_info);
1795 uri = ap_pstrcat(r->pool, uri, r->path_info, NULL);
1799 * On per-directory context (.htaccess) strip the location
1800 * prefix from the URL to make sure patterns apply only to
1801 * the local part. Additionally indicate this special
1802 * threatment in the logfile.
1805 if (perdir != NULL) {
1806 if ( strlen(uri) >= strlen(perdir)
1807 && strncmp(uri, perdir, strlen(perdir)) == 0) {
1808 rewritelog(r, 3, "[per-dir %s] strip per-dir prefix: %s -> %s",
1809 perdir, uri, uri+strlen(perdir));
1810 uri = uri+strlen(perdir);
1816 * Try to match the URI against the RewriteRule pattern
1817 * and exit immeddiately if it didn't apply.
1819 if (perdir == NULL) {
1820 rewritelog(r, 3, "applying pattern '%s' to uri '%s'",
1824 rewritelog(r, 3, "[per-dir %s] applying pattern '%s' to uri '%s'",
1825 perdir, p->pattern, uri);
1827 rc = (ap_regexec(regexp, uri, regexp->re_nsub+1, regmatch, 0) == 0);
1828 if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) ||
1829 (!rc && (p->flags & RULEFLAG_NOTMATCH)) ) ) {
1834 * Else create the RewriteRule `regsubinfo' structure which
1835 * holds the substitution information.
1837 briRR = (backrefinfo *)ap_palloc(r->pool, sizeof(backrefinfo));
1838 if (!rc && (p->flags & RULEFLAG_NOTMATCH)) {
1839 /* empty info on negative patterns */
1844 briRR->source = ap_pstrdup(r->pool, uri);
1845 briRR->nsub = regexp->re_nsub;
1846 memcpy((void *)(briRR->regmatch), (void *)(regmatch),
1851 * Initiallally create the RewriteCond backrefinfo with
1852 * empty backrefinfo, i.e. not subst parts
1853 * (this one is adjusted inside apply_rewrite_cond() later!!)
1855 briRC = (backrefinfo *)ap_pcalloc(r->pool, sizeof(backrefinfo));
1860 * Ok, we already know the pattern has matched, but we now
1861 * additionally have to check for all existing preconditions
1862 * (RewriteCond) which have to be also true. We do this at
1863 * this very late stage to avoid unnessesary checks which
1864 * would slow down the rewriting engine!!
1866 rewriteconds = p->rewriteconds;
1867 conds = (rewritecond_entry *)rewriteconds->elts;
1869 for (i = 0; i < rewriteconds->nelts; i++) {
1871 rc = apply_rewrite_cond(r, c, perdir, briRR, briRC);
1872 if (c->flags & CONDFLAG_ORNEXT) {
1877 /* One condition is false, but another can be
1878 * still true, so we have to continue...
1880 ap_table_unset(r->notes, VARY_KEY_THIS);
1884 /* One true condition is enough in "or" case, so
1885 * skip the other conditions which are "ornext"
1888 while ( i < rewriteconds->nelts
1889 && c->flags & CONDFLAG_ORNEXT) {
1898 * The "AND" case, i.e. no "or" flag,
1899 * so a single failure means total failure.
1906 vary = ap_table_get(r->notes, VARY_KEY_THIS);
1908 ap_table_merge(r->notes, VARY_KEY, vary);
1909 ap_table_unset(r->notes, VARY_KEY_THIS);
1912 /* if any condition fails the complete rule fails */
1914 ap_table_unset(r->notes, VARY_KEY);
1915 ap_table_unset(r->notes, VARY_KEY_THIS);
1920 * Regardless of what we do next, we've found a match. Check to see
1921 * if any of the request header fields were involved, and add them
1922 * to the Vary field of the response.
1924 if ((vary = ap_table_get(r->notes, VARY_KEY)) != NULL) {
1925 ap_table_merge(r->headers_out, "Vary", vary);
1926 ap_table_unset(r->notes, VARY_KEY);
1930 * If this is a pure matching rule (`RewriteRule <pat> -')
1931 * we stop processing and return immediately. The only thing
1932 * we have not to forget are the environment variables
1933 * (`RewriteRule <pat> - [E=...]')
1935 if (strcmp(output, "-") == 0) {
1936 for (i = 0; p->env[i] != NULL; i++) {
1937 /* 1. take the string */
1938 ap_cpystrn(env, p->env[i], sizeof(env));
1939 /* 2. expand $N (i.e. backrefs to RewriteRule pattern) */
1940 expand_backref_inbuffer(r->pool, env, sizeof(env), briRR, '$');
1941 /* 3. expand %N (i.e. backrefs to latest RewriteCond pattern) */
1942 expand_backref_inbuffer(r->pool, env, sizeof(env), briRC, '%');
1943 /* 4. expand %{...} (i.e. variables) */
1944 expand_variables_inbuffer(r, env, sizeof(env));
1945 /* 5. expand ${...} (RewriteMap lookups) */
1946 expand_map_lookups(r, env, sizeof(env));
1947 /* and add the variable to Apache's structures */
1948 add_env_variable(r, env);
1950 if (p->forced_mimetype != NULL) {
1951 if (perdir == NULL) {
1952 /* In the per-server context we can force the MIME-type
1953 * the correct way by notifying our MIME-type hook handler
1954 * to do the job when the MIME-type API stage is reached.
1956 rewritelog(r, 2, "remember %s to have MIME-type '%s'",
1957 r->filename, p->forced_mimetype);
1958 ap_table_setn(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
1959 p->forced_mimetype);
1962 /* In per-directory context we operate in the Fixup API hook
1963 * which is after the MIME-type hook, so our MIME-type handler
1964 * has no chance to set r->content_type. And because we are
1965 * in the situation where no substitution takes place no
1966 * sub-request will happen (which could solve the
1967 * restriction). As a workaround we do it ourself now
1968 * immediately although this is not strictly API-conforming.
1969 * But it's the only chance we have...
1971 rewritelog(r, 1, "[per-dir %s] force %s to have MIME-type "
1972 "'%s'", perdir, r->filename, p->forced_mimetype);
1973 r->content_type = p->forced_mimetype;
1980 * Ok, now we finally know all patterns have matched and
1981 * that there is something to replace, so we create the
1982 * substitution URL string in `newuri'.
1984 /* 1. take the output string */
1985 ap_cpystrn(newuri, output, sizeof(newuri));
1986 /* 2. expand $N (i.e. backrefs to RewriteRule pattern) */
1987 expand_backref_inbuffer(r->pool, newuri, sizeof(newuri), briRR, '$');
1988 /* 3. expand %N (i.e. backrefs to latest RewriteCond pattern) */
1989 expand_backref_inbuffer(r->pool, newuri, sizeof(newuri), briRC, '%');
1990 /* 4. expand %{...} (i.e. variables) */
1991 expand_variables_inbuffer(r, newuri, sizeof(newuri));
1992 /* 5. expand ${...} (RewriteMap lookups) */
1993 expand_map_lookups(r, newuri, sizeof(newuri));
1994 /* and log the result... */
1995 if (perdir == NULL) {
1996 rewritelog(r, 2, "rewrite %s -> %s", uri, newuri);
1999 rewritelog(r, 2, "[per-dir %s] rewrite %s -> %s", perdir, uri, newuri);
2003 * Additionally do expansion for the environment variable
2004 * strings (`RewriteRule .. .. [E=<string>]').
2006 for (i = 0; p->env[i] != NULL; i++) {
2007 /* 1. take the string */
2008 ap_cpystrn(env, p->env[i], sizeof(env));
2009 /* 2. expand $N (i.e. backrefs to RewriteRule pattern) */
2010 expand_backref_inbuffer(r->pool, env, sizeof(env), briRR, '$');
2011 /* 3. expand %N (i.e. backrefs to latest RewriteCond pattern) */
2012 expand_backref_inbuffer(r->pool, env, sizeof(env), briRC, '%');
2013 /* 4. expand %{...} (i.e. variables) */
2014 expand_variables_inbuffer(r, env, sizeof(env));
2015 /* 5. expand ${...} (RewriteMap lookups) */
2016 expand_map_lookups(r, env, sizeof(env));
2017 /* and add the variable to Apache's structures */
2018 add_env_variable(r, env);
2022 * Now replace API's knowledge of the current URI:
2023 * Replace r->filename with the new URI string and split out
2024 * an on-the-fly generated QUERY_STRING part into r->args
2026 r->filename = ap_pstrdup(r->pool, newuri);
2027 splitout_queryargs(r, p->flags & RULEFLAG_QSAPPEND);
2030 * Again add the previously stripped per-directory location
2031 * prefix if the new URI is not a new one for this
2032 * location, i.e. if it's not starting with either a slash
2033 * or a fully qualified URL scheme.
2035 i = strlen(r->filename);
2037 && !( r->filename[0] == '/'
2038 || ( (i > 7 && strncasecmp(r->filename, "http://", 7) == 0)
2039 || (i > 8 && strncasecmp(r->filename, "https://", 8) == 0)
2040 || (i > 9 && strncasecmp(r->filename, "gopher://", 9) == 0)
2041 || (i > 6 && strncasecmp(r->filename, "ftp://", 6) == 0)
2042 || (i > 5 && strncasecmp(r->filename, "ldap:", 5) == 0)
2043 || (i > 5 && strncasecmp(r->filename, "news:", 5) == 0)
2044 || (i > 7 && strncasecmp(r->filename, "mailto:", 7) == 0)))) {
2045 rewritelog(r, 3, "[per-dir %s] add per-dir prefix: %s -> %s%s",
2046 perdir, r->filename, perdir, r->filename);
2047 r->filename = ap_pstrcat(r->pool, perdir, r->filename, NULL);
2051 * If this rule is forced for proxy throughput
2052 * (`RewriteRule ... ... [P]') then emulate mod_proxy's
2053 * URL-to-filename handler to be sure mod_proxy is triggered
2054 * for this URL later in the Apache API. But make sure it is
2055 * a fully-qualified URL. (If not it is qualified with
2058 if (p->flags & RULEFLAG_PROXY) {
2059 fully_qualify_uri(r);
2060 if (perdir == NULL) {
2061 rewritelog(r, 2, "forcing proxy-throughput with %s", r->filename);
2064 rewritelog(r, 2, "[per-dir %s] forcing proxy-throughput with %s",
2065 perdir, r->filename);
2067 r->filename = ap_pstrcat(r->pool, "proxy:", r->filename, NULL);
2072 * If this rule is explicitly forced for HTTP redirection
2073 * (`RewriteRule .. .. [R]') then force an external HTTP
2074 * redirect. But make sure it is a fully-qualified URL. (If
2075 * not it is qualified with ourself).
2077 if (p->flags & RULEFLAG_FORCEREDIRECT) {
2078 fully_qualify_uri(r);
2079 if (perdir == NULL) {
2081 "explicitly forcing redirect with %s", r->filename);
2085 "[per-dir %s] explicitly forcing redirect with %s",
2086 perdir, r->filename);
2088 r->status = p->forced_responsecode;
2093 * Special Rewriting Feature: Self-Reduction
2094 * We reduce the URL by stripping a possible
2095 * http[s]://<ourhost>[:<port>] prefix, i.e. a prefix which
2096 * corresponds to ourself. This is to simplify rewrite maps
2097 * and to avoid recursion, etc. When this prefix is not a
2098 * coincidence then the user has to use [R] explicitly (see
2104 * If this rule is still implicitly forced for HTTP
2105 * redirection (`RewriteRule .. <scheme>://...') then
2106 * directly force an external HTTP redirect.
2108 i = strlen(r->filename);
2109 if ( (i > 7 && strncasecmp(r->filename, "http://", 7) == 0)
2110 || (i > 8 && strncasecmp(r->filename, "https://", 8) == 0)
2111 || (i > 9 && strncasecmp(r->filename, "gopher://", 9) == 0)
2112 || (i > 6 && strncasecmp(r->filename, "ftp://", 6) == 0)
2113 || (i > 5 && strncasecmp(r->filename, "ldap:", 5) == 0)
2114 || (i > 5 && strncasecmp(r->filename, "news:", 5) == 0)
2115 || (i > 7 && strncasecmp(r->filename, "mailto:", 7) == 0) ) {
2116 if (perdir == NULL) {
2118 "implicitly forcing redirect (rc=%d) with %s",
2119 p->forced_responsecode, r->filename);
2122 rewritelog(r, 2, "[per-dir %s] implicitly forcing redirect "
2123 "(rc=%d) with %s", perdir, p->forced_responsecode,
2126 r->status = p->forced_responsecode;
2131 * Now we are sure it is not a fully qualified URL. But
2132 * there is still one special case left: A local rewrite in
2133 * per-directory context, i.e. a substitution URL which does
2134 * not start with a slash. Here we add again the initially
2135 * stripped per-directory prefix.
2137 if (prefixstrip && r->filename[0] != '/') {
2138 rewritelog(r, 3, "[per-dir %s] add per-dir prefix: %s -> %s%s",
2139 perdir, r->filename, perdir, r->filename);
2140 r->filename = ap_pstrcat(r->pool, perdir, r->filename, NULL);
2144 * Finally we had to remember if a MIME-type should be
2145 * forced for this URL (`RewriteRule .. .. [T=<type>]')
2146 * Later in the API processing phase this is forced by our
2147 * MIME API-hook function. This time its no problem even for
2148 * the per-directory context (where the MIME-type hook was
2149 * already processed) because a sub-request happens ;-)
2151 if (p->forced_mimetype != NULL) {
2152 ap_table_setn(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
2153 p->forced_mimetype);
2154 if (perdir == NULL) {
2155 rewritelog(r, 2, "remember %s to have MIME-type '%s'",
2156 r->filename, p->forced_mimetype);
2160 "[per-dir %s] remember %s to have MIME-type '%s'",
2161 perdir, r->filename, p->forced_mimetype);
2166 * Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_)
2167 * But now we're done for this particular rule.
2172 static int apply_rewrite_cond(request_rec *r, rewritecond_entry *p,
2173 char *perdir, backrefinfo *briRR,
2176 char input[MAX_STRING_LEN];
2179 regmatch_t regmatch[MAX_NMATCH];
2183 * Construct the string we match against
2186 /* 1. take the string */
2187 ap_cpystrn(input, p->input, sizeof(input));
2188 /* 2. expand $N (i.e. backrefs to RewriteRule pattern) */
2189 expand_backref_inbuffer(r->pool, input, sizeof(input), briRR, '$');
2190 /* 3. expand %N (i.e. backrefs to latest RewriteCond pattern) */
2191 expand_backref_inbuffer(r->pool, input, sizeof(input), briRC, '%');
2192 /* 4. expand %{...} (i.e. variables) */
2193 expand_variables_inbuffer(r, input, sizeof(input));
2194 /* 5. expand ${...} (RewriteMap lookups) */
2195 expand_map_lookups(r, input, sizeof(input));
2198 * Apply the patterns
2202 if (strcmp(p->pattern, "-f") == 0) {
2203 if (stat(input, &sb) == 0) {
2204 if (S_ISREG(sb.st_mode)) {
2209 else if (strcmp(p->pattern, "-s") == 0) {
2210 if (stat(input, &sb) == 0) {
2211 if (S_ISREG(sb.st_mode) && sb.st_size > 0) {
2216 else if (strcmp(p->pattern, "-l") == 0) {
2217 #if !defined(OS2) && !defined(WIN32)
2218 if (lstat(input, &sb) == 0) {
2219 if (S_ISLNK(sb.st_mode)) {
2225 else if (strcmp(p->pattern, "-d") == 0) {
2226 if (stat(input, &sb) == 0) {
2227 if (S_ISDIR(sb.st_mode)) {
2232 else if (strcmp(p->pattern, "-U") == 0) {
2233 /* avoid infinite subrequest recursion */
2234 if (strlen(input) > 0 /* nonempty path, and */
2235 && ( r->main == NULL /* - either not in a subrequest */
2236 || ( r->main->uri != NULL /* - or in a subrequest... */
2237 && r->uri != NULL /* ...and URIs aren't NULL... */
2238 /* ...and sub/main URIs differ */
2239 && strcmp(r->main->uri, r->uri) != 0) ) ) {
2241 /* run a URI-based subrequest */
2242 rsub = ap_sub_req_lookup_uri(input, r);
2244 /* URI exists for any result up to 3xx, redirects allowed */
2245 if (rsub->status < 400)
2249 rewritelog(r, 5, "RewriteCond URI (-U) check: "
2250 "path=%s -> status=%d", input, rsub->status);
2252 /* cleanup by destroying the subrequest */
2253 ap_destroy_sub_req(rsub);
2256 else if (strcmp(p->pattern, "-F") == 0) {
2257 /* avoid infinite subrequest recursion */
2258 if (strlen(input) > 0 /* nonempty path, and */
2259 && ( r->main == NULL /* - either not in a subrequest */
2260 || ( r->main->uri != NULL /* - or in a subrequest... */
2261 && r->uri != NULL /* ...and URIs aren't NULL... */
2262 /* ...and sub/main URIs differ */
2263 && strcmp(r->main->uri, r->uri) != 0) ) ) {
2265 /* process a file-based subrequest:
2266 * this differs from -U in that no path translation is done.
2268 rsub = ap_sub_req_lookup_file(input, r);
2270 /* file exists for any result up to 2xx, no redirects */
2271 if (rsub->status < 300 &&
2272 /* double-check that file exists since default result is 200 */
2273 stat(rsub->filename, &sb) == 0) {
2278 rewritelog(r, 5, "RewriteCond file (-F) check: path=%s "
2279 "-> file=%s status=%d", input, rsub->filename,
2282 /* cleanup by destroying the subrequest */
2283 ap_destroy_sub_req(rsub);
2286 else if (strlen(p->pattern) > 1 && *(p->pattern) == '>') {
2287 rc = (compare_lexicography(input, p->pattern+1) == 1 ? 1 : 0);
2289 else if (strlen(p->pattern) > 1 && *(p->pattern) == '<') {
2290 rc = (compare_lexicography(input, p->pattern+1) == -1 ? 1 : 0);
2292 else if (strlen(p->pattern) > 1 && *(p->pattern) == '=') {
2293 if (strcmp(p->pattern+1, "\"\"") == 0) {
2294 rc = (*input == '\0');
2297 rc = (strcmp(input, p->pattern+1) == 0 ? 1 : 0);
2301 /* it is really a regexp pattern, so apply it */
2302 rc = (ap_regexec(p->regexp, input,
2303 p->regexp->re_nsub+1, regmatch,0) == 0);
2305 /* if it isn't a negated pattern and really matched
2306 we update the passed-through regex subst info structure */
2307 if (rc && !(p->flags & CONDFLAG_NOTMATCH)) {
2308 briRC->source = ap_pstrdup(r->pool, input);
2309 briRC->nsub = p->regexp->re_nsub;
2310 memcpy((void *)(briRC->regmatch), (void *)(regmatch),
2315 /* if this is a non-matching regexp, just negate the result */
2316 if (p->flags & CONDFLAG_NOTMATCH) {
2320 rewritelog(r, 4, "RewriteCond: input='%s' pattern='%s%s' => %s",
2321 input, (p->flags & CONDFLAG_NOTMATCH ? "!" : ""),
2322 p->pattern, rc ? "matched" : "not-matched");
2324 /* end just return the result */
2330 ** +-------------------------------------------------------+
2332 ** | URL transformation functions
2334 ** +-------------------------------------------------------+
2339 ** split out a QUERY_STRING part from
2340 ** the current URI string
2344 static void splitout_queryargs(request_rec *r, int qsappend)
2349 q = strchr(r->filename, '?');
2351 olduri = ap_pstrdup(r->pool, r->filename);
2354 r->args = ap_pstrcat(r->pool, q, "&", r->args, NULL);
2357 r->args = ap_pstrdup(r->pool, q);
2359 if (strlen(r->args) == 0) {
2361 rewritelog(r, 3, "split uri=%s -> uri=%s, args=<none>", olduri,
2365 if (r->args[strlen(r->args)-1] == '&') {
2366 r->args[strlen(r->args)-1] = '\0';
2368 rewritelog(r, 3, "split uri=%s -> uri=%s, args=%s", olduri,
2369 r->filename, r->args);
2378 ** strip 'http[s]://ourhost/' from URI
2382 static void reduce_uri(request_rec *r)
2385 unsigned short port;
2390 char host[LONG_STRING_LEN];
2391 char buf[MAX_STRING_LEN];
2395 cp = (char *)ap_http_method(r);
2397 if ( strlen(r->filename) > l+3
2398 && strncasecmp(r->filename, cp, l) == 0
2399 && r->filename[l] == ':'
2400 && r->filename[l+1] == '/'
2401 && r->filename[l+2] == '/' ) {
2402 /* there was really a rewrite to a remote path */
2404 olduri = ap_pstrdup(r->pool, r->filename); /* save for logging */
2406 /* cut the hostname and port out of the URI */
2407 ap_cpystrn(buf, r->filename+(l+3), sizeof(buf));
2409 for (cp = hostp; *cp != '\0' && *cp != '/' && *cp != ':'; cp++)
2414 ap_cpystrn(host, hostp, sizeof(host));
2417 for (; *cp != '\0' && *cp != '/'; cp++)
2423 /* set remaining url */
2426 else if (*cp == '/') {
2429 ap_cpystrn(host, hostp, sizeof(host));
2432 port = ap_default_port(r);
2433 /* set remaining url */
2438 ap_cpystrn(host, hostp, sizeof(host));
2440 port = ap_default_port(r);
2441 /* set remaining url */
2445 /* now check whether we could reduce it to a local path... */
2446 if (ap_matches_request_vhost(r, host, port)) {
2447 /* this is our host, so only the URL remains */
2448 r->filename = ap_pstrdup(r->pool, url);
2449 rewritelog(r, 3, "reduce %s -> %s", olduri, r->filename);
2458 ** add 'http[s]://ourhost[:ourport]/' to URI
2459 ** if URI is still not fully qualified
2463 static void fully_qualify_uri(request_rec *r)
2467 const char *thisserver;
2471 i = strlen(r->filename);
2472 if (!( (i > 7 && strncasecmp(r->filename, "http://", 7) == 0)
2473 || (i > 8 && strncasecmp(r->filename, "https://", 8) == 0)
2474 || (i > 9 && strncasecmp(r->filename, "gopher://", 9) == 0)
2475 || (i > 6 && strncasecmp(r->filename, "ftp://", 6) == 0)
2476 || (i > 5 && strncasecmp(r->filename, "ldap:", 5) == 0)
2477 || (i > 5 && strncasecmp(r->filename, "news:", 5) == 0)
2478 || (i > 7 && strncasecmp(r->filename, "mailto:", 7) == 0))) {
2480 thisserver = ap_get_server_name(r);
2481 port = ap_get_server_port(r);
2482 if (ap_is_default_port(port,r)) {
2486 ap_snprintf(buf, sizeof(buf), ":%u", port);
2490 if (r->filename[0] == '/') {
2491 r->filename = ap_psprintf(r->pool, "%s://%s%s%s",
2492 ap_http_method(r), thisserver,
2493 thisport, r->filename);
2496 r->filename = ap_psprintf(r->pool, "%s://%s%s/%s",
2497 ap_http_method(r), thisserver,
2498 thisport, r->filename);
2507 ** Expand the %0-%9 or $0-$9 regex backreferences
2511 static void expand_backref_inbuffer(ap_context_t *p, char *buf, int nbuf,
2512 backrefinfo *bri, char c)
2516 /* protect existing $N and & backrefs and replace <c>N with $N backrefs */
2517 for (i = 0; buf[i] != '\0' && i < nbuf; i++) {
2518 if (buf[i] == '\\' && (buf[i+1] != '\0' && i < (nbuf-1))) {
2519 i++; /* protect next */
2521 else if (buf[i] == '&') {
2524 else if (c != '$' && buf[i] == '$' && (buf[i+1] >= '0' && buf[i+1] <= '9')) {
2528 else if (buf[i] == c && (buf[i+1] >= '0' && buf[i+1] <= '9')) {
2534 /* now apply the standard regex substitution function */
2535 ap_cpystrn(buf, ap_pregsub(p, buf, bri->source,
2536 bri->nsub+1, bri->regmatch), nbuf);
2538 /* restore the original $N and & backrefs */
2539 for (i = 0; buf[i] != '\0' && i < nbuf; i++) {
2540 if (buf[i] == '\001') {
2543 else if (buf[i] == '\002') {
2552 ** Expand tilde-paths (/~user) through
2553 ** Unix /etc/passwd database information
2556 #if !defined(WIN32) && !defined(NETWARE)
2557 static char *expand_tildepaths(request_rec *r, char *uri)
2559 char user[LONG_STRING_LEN];
2565 if (uri != NULL && strlen(uri) > 2 && uri[0] == '/' && uri[1] == '~') {
2566 /* cut out the username */
2567 for (j = 0, i = 2; j < sizeof(user)-1
2569 && uri[i] != '/' ; ) {
2570 user[j++] = uri[i++];
2574 /* lookup username in systems passwd file */
2575 if ((pw = getpwnam(user)) != NULL) {
2576 /* ok, user was found, so expand the ~user string */
2577 if (uri[i] != '\0') {
2578 /* ~user/anything... has to be expanded */
2579 if (pw->pw_dir[strlen(pw->pw_dir)-1] == '/') {
2580 pw->pw_dir[strlen(pw->pw_dir)-1] = '\0';
2582 newuri = ap_pstrcat(r->pool, pw->pw_dir, uri+i, NULL);
2585 /* only ~user has to be expanded */
2586 newuri = ap_pstrdup(r->pool, pw->pw_dir);
2596 ** mapfile expansion support
2597 ** i.e. expansion of MAP lookup directives
2598 ** ${<mapname>:<key>} in RewriteRule rhs
2602 #define limit_length(n) (n > LONG_STRING_LEN-1 ? LONG_STRING_LEN-1 : n)
2604 static void expand_map_lookups(request_rec *r, char *uri, int uri_len)
2606 char newuri[MAX_STRING_LEN];
2612 char mapname[LONG_STRING_LEN];
2613 char mapkey[LONG_STRING_LEN];
2614 char defaultvalue[LONG_STRING_LEN];
2618 cpIE = cpI+strlen(cpI);
2620 while (cpI < cpIE) {
2621 if (cpI+6 < cpIE && strncmp(cpI, "${", 2) == 0) {
2622 /* missing delimiter -> take it as plain text */
2623 if ( strchr(cpI+2, ':') == NULL
2624 || strchr(cpI+2, '}') == NULL) {
2625 memcpy(cpO, cpI, 2);
2632 cpT = strchr(cpI, ':');
2634 memcpy(mapname, cpI, limit_length(n));
2635 mapname[limit_length(n)] = '\0';
2638 cpT2 = strchr(cpI, '|');
2639 cpT = strchr(cpI, '}');
2640 if (cpT2 != NULL && cpT2 < cpT) {
2642 memcpy(mapkey, cpI, limit_length(n));
2643 mapkey[limit_length(n)] = '\0';
2647 memcpy(defaultvalue, cpI, limit_length(n));
2648 defaultvalue[limit_length(n)] = '\0';
2653 memcpy(mapkey, cpI, limit_length(n));
2654 mapkey[limit_length(n)] = '\0';
2657 defaultvalue[0] = '\0';
2660 cpT = lookup_map(r, mapname, mapkey);
2663 if (cpO + n >= newuri + sizeof(newuri)) {
2664 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
2665 0, r, "insufficient space in "
2666 "expand_map_lookups, aborting");
2669 memcpy(cpO, cpT, n);
2673 n = strlen(defaultvalue);
2674 if (cpO + n >= newuri + sizeof(newuri)) {
2675 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
2676 0, r, "insufficient space in "
2677 "expand_map_lookups, aborting");
2680 memcpy(cpO, defaultvalue, n);
2685 cpT = strstr(cpI, "${");
2687 cpT = cpI+strlen(cpI);
2689 if (cpO + n >= newuri + sizeof(newuri)) {
2690 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
2691 0, r, "insufficient space in "
2692 "expand_map_lookups, aborting");
2695 memcpy(cpO, cpI, n);
2701 ap_cpystrn(uri, newuri, uri_len);
2710 ** +-------------------------------------------------------+
2712 ** | DBM hashfile support
2714 ** +-------------------------------------------------------+
2718 static char *lookup_map(request_rec *r, char *name, char *key)
2721 rewrite_server_conf *conf;
2722 ap_array_header_t *rewritemaps;
2723 rewritemap_entry *entries;
2724 rewritemap_entry *s;
2729 /* get map configuration */
2730 sconf = r->server->module_config;
2731 conf = (rewrite_server_conf *)ap_get_module_config(sconf,
2733 rewritemaps = conf->rewritemaps;
2735 entries = (rewritemap_entry *)rewritemaps->elts;
2736 for (i = 0; i < rewritemaps->nelts; i++) {
2738 if (strcmp(s->name, name) == 0) {
2739 if (s->type == MAPTYPE_TXT) {
2740 if (stat(s->checkfile, &st) == -1) {
2741 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2742 "mod_rewrite: can't access text RewriteMap "
2743 "file %s", s->checkfile);
2744 rewritelog(r, 1, "can't open RewriteMap file, "
2748 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2750 if (value == NULL) {
2751 rewritelog(r, 6, "cache lookup FAILED, forcing new "
2754 lookup_map_txtfile(r, s->datafile, key)) != NULL) {
2755 rewritelog(r, 5, "map lookup OK: map=%s key=%s[txt] "
2756 "-> val=%s", s->name, key, value);
2757 set_cache_string(cachep, s->name, CACHEMODE_TS,
2758 st.st_mtime, key, value);
2762 rewritelog(r, 5, "map lookup FAILED: map=%s[txt] "
2763 "key=%s", s->name, key);
2764 set_cache_string(cachep, s->name, CACHEMODE_TS,
2765 st.st_mtime, key, "");
2770 rewritelog(r, 5, "cache lookup OK: map=%s[txt] key=%s "
2771 "-> val=%s", s->name, key, value);
2772 return value[0] != '\0' ? value : NULL;
2775 else if (s->type == MAPTYPE_DBM) {
2776 #ifndef NO_DBM_REWRITEMAP
2777 if (stat(s->checkfile, &st) == -1) {
2778 ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
2779 "mod_rewrite: can't access DBM RewriteMap "
2780 "file %s", s->checkfile);
2781 rewritelog(r, 1, "can't open DBM RewriteMap file, "
2785 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2787 if (value == NULL) {
2789 "cache lookup FAILED, forcing new map lookup");
2791 lookup_map_dbmfile(r, s->datafile, key)) != NULL) {
2792 rewritelog(r, 5, "map lookup OK: map=%s[dbm] key=%s "
2793 "-> val=%s", s->name, key, value);
2794 set_cache_string(cachep, s->name, CACHEMODE_TS,
2795 st.st_mtime, key, value);
2799 rewritelog(r, 5, "map lookup FAILED: map=%s[dbm] "
2800 "key=%s", s->name, key);
2801 set_cache_string(cachep, s->name, CACHEMODE_TS,
2802 st.st_mtime, key, "");
2807 rewritelog(r, 5, "cache lookup OK: map=%s[dbm] key=%s "
2808 "-> val=%s", s->name, key, value);
2809 return value[0] != '\0' ? value : NULL;
2815 else if (s->type == MAPTYPE_PRG) {
2817 lookup_map_program(r, s->fpin, s->fpout, key)) != NULL) {
2818 rewritelog(r, 5, "map lookup OK: map=%s key=%s -> val=%s",
2819 s->name, key, value);
2823 rewritelog(r, 5, "map lookup FAILED: map=%s key=%s",
2827 else if (s->type == MAPTYPE_INT) {
2828 if ((value = lookup_map_internal(r, s->func, key)) != NULL) {
2829 rewritelog(r, 5, "map lookup OK: map=%s key=%s -> val=%s",
2830 s->name, key, value);
2834 rewritelog(r, 5, "map lookup FAILED: map=%s key=%s",
2838 else if (s->type == MAPTYPE_RND) {
2839 if (stat(s->checkfile, &st) == -1) {
2840 ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
2841 "mod_rewrite: can't access text RewriteMap "
2842 "file %s", s->checkfile);
2843 rewritelog(r, 1, "can't open RewriteMap file, "
2847 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2849 if (value == NULL) {
2850 rewritelog(r, 6, "cache lookup FAILED, forcing new "
2853 lookup_map_txtfile(r, s->datafile, key)) != NULL) {
2854 rewritelog(r, 5, "map lookup OK: map=%s key=%s[txt] "
2855 "-> val=%s", s->name, key, value);
2856 set_cache_string(cachep, s->name, CACHEMODE_TS,
2857 st.st_mtime, key, value);
2860 rewritelog(r, 5, "map lookup FAILED: map=%s[txt] "
2861 "key=%s", s->name, key);
2862 set_cache_string(cachep, s->name, CACHEMODE_TS,
2863 st.st_mtime, key, "");
2868 rewritelog(r, 5, "cache lookup OK: map=%s[txt] key=%s "
2869 "-> val=%s", s->name, key, value);
2871 if (value[0] != '\0') {
2872 value = select_random_value_part(r, value);
2873 rewritelog(r, 5, "randomly choosen the subvalue `%s'", value);
2885 static char *lookup_map_txtfile(request_rec *r, char *file, char *key)
2887 ap_file_t *fp = NULL;
2896 rc = ap_open(&fp, file, APR_READ | APR_BUFFERED, APR_OS_DEFAULT, r->pool);
2897 if (rc != APR_SUCCESS) {
2901 while (ap_fgets(line, sizeof(line), fp) == APR_SUCCESS) {
2903 continue; /* ignore comments */
2906 skip = strcspn(cpT," \t\r\n");
2908 continue; /* ignore lines that start with a space, tab, CR, or LF */
2911 if (strcmp(curkey, key) != 0)
2912 continue; /* key does not match... */
2914 /* found a matching key; now extract and return the value */
2916 skip = strspn(cpT, " \t\r\n");
2919 skip = strcspn(cpT, " \t\r\n");
2921 continue; /* no value... */
2924 value = ap_pstrdup(r->pool, curval);
2931 #ifndef NO_DBM_REWRITEMAP
2932 static char *lookup_map_dbmfile(request_rec *r, char *file, char *key)
2938 char buf[MAX_STRING_LEN];
2941 dbmkey.dsize = strlen(key);
2942 if ((dbmfp = dbm_open(file, O_RDONLY, 0666)) != NULL) {
2943 dbmval = dbm_fetch(dbmfp, dbmkey);
2944 if (dbmval.dptr != NULL) {
2945 memcpy(buf, dbmval.dptr,
2946 dbmval.dsize < sizeof(buf)-1 ?
2947 dbmval.dsize : sizeof(buf)-1 );
2948 buf[dbmval.dsize] = '\0';
2949 value = ap_pstrdup(r->pool, buf);
2957 static char *lookup_map_program(request_rec *r, ap_file_t *fpin,
2958 ap_file_t *fpout, char *key)
2960 char buf[LONG_STRING_LEN];
2966 struct iovec iova[2];
2970 /* when `RewriteEngine off' was used in the per-server
2971 * context then the rewritemap-programs were not spawned.
2972 * In this case using such a map (usually in per-dir context)
2973 * is useless because it is not available.
2975 if (fpin == NULL || fpout == NULL) {
2980 rewritelock_alloc(r);
2982 /* write out the request key */
2984 ap_write(fpin, key, strlen(key));
2985 ap_write(fpin, "\n", 1);
2987 iova[0].iov_base = key;
2988 iova[0].iov_len = strlen(key);
2989 iova[1].iov_base = "\n";
2990 iova[1].iov_len = 1;
2993 ap_writev(fpin, iova, niov, &nbytes);
2996 /* read in the response value */
2999 ap_read(fpout, &c, &nbytes);
3000 while (nbytes == 1 && (i < LONG_STRING_LEN-1)) {
3006 ap_read(fpout, &c, &nbytes);
3010 /* give the lock back */
3011 rewritelock_free(r);
3013 if (strcasecmp(buf, "NULL") == 0) {
3017 return ap_pstrdup(r->pool, buf);
3021 static char *lookup_map_internal(request_rec *r,
3022 char *(*func)(request_rec *, char *),
3025 /* currently we just let the function convert
3026 the key to a corresponding value */
3027 return func(r, key);
3030 static char *rewrite_mapfunc_toupper(request_rec *r, char *key)
3034 for (cp = value = ap_pstrdup(r->pool, key); cp != NULL && *cp != '\0';
3036 *cp = ap_toupper(*cp);
3041 static char *rewrite_mapfunc_tolower(request_rec *r, char *key)
3045 for (cp = value = ap_pstrdup(r->pool, key); cp != NULL && *cp != '\0';
3047 *cp = ap_tolower(*cp);
3052 static char *rewrite_mapfunc_escape(request_rec *r, char *key)
3056 value = ap_escape_uri(r->pool, key);
3060 static char *rewrite_mapfunc_unescape(request_rec *r, char *key)
3064 value = ap_pstrdup(r->pool, key);
3065 ap_unescape_url(value);
3069 static int rewrite_rand_init_done = 0;
3071 static void rewrite_rand_init(void)
3073 if (!rewrite_rand_init_done) {
3074 srand((unsigned)(getpid()));
3075 rewrite_rand_init_done = 1;
3080 static int rewrite_rand(int l, int h)
3082 rewrite_rand_init();
3084 /* Get [0,1) and then scale to the appropriate range. Note that using
3085 * a floating point value ensures that we use all bits of the rand()
3086 * result. Doing an integer modulus would only use the lower-order bits
3087 * which may not be as uniformly random.
3089 return ((double)(rand() % RAND_MAX) / RAND_MAX) * (h - l + 1) + l;
3092 static char *select_random_value_part(request_rec *r, char *value)
3097 /* count number of distinct values */
3098 for (n = 1, i = 0; value[i] != '\0'; i++) {
3099 if (value[i] == '|') {
3104 /* when only one value we have no option to choose */
3109 /* else randomly select one */
3110 k = rewrite_rand(1, n);
3112 /* and grep it out */
3113 for (n = 1, i = 0; value[i] != '\0'; i++) {
3117 if (value[i] == '|') {
3121 buf = ap_pstrdup(r->pool, &value[i]);
3122 for (i = 0; buf[i] != '\0' && buf[i] != '|'; i++)
3130 ** +-------------------------------------------------------+
3132 ** | rewriting logfile support
3134 ** +-------------------------------------------------------+
3138 static void open_rewritelog(server_rec *s, ap_context_t *p)
3140 rewrite_server_conf *conf;
3144 int rewritelog_flags = ( APR_WRITE | APR_APPEND | APR_CREATE );
3145 mode_t rewritelog_mode = ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD );
3147 conf = ap_get_module_config(s->module_config, &rewrite_module);
3149 if (conf->rewritelogfile == NULL) {
3152 if (*(conf->rewritelogfile) == '\0') {
3155 if (conf->rewritelogfp != NULL) {
3156 return; /* virtual log shared w/ main server */
3159 fname = ap_server_root_relative(p, conf->rewritelogfile);
3161 if (*conf->rewritelogfile == '|') {
3162 if ((pl = ap_open_piped_log(p, conf->rewritelogfile+1)) == NULL) {
3163 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
3164 "mod_rewrite: could not open reliable pipe "
3165 "to RewriteLog filter %s", conf->rewritelogfile+1);
3168 conf->rewritelogfp = ap_piped_log_write_fd(pl);
3170 else if (*conf->rewritelogfile != '\0') {
3171 rc = ap_open(&conf->rewritelogfp, fname, rewritelog_flags, rewritelog_mode, p);
3172 if (rc != APR_SUCCESS) {
3173 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
3174 "mod_rewrite: could not open RewriteLog "
3182 static void rewritelog(request_rec *r, int level, const char *text, ...)
3184 rewrite_server_conf *conf;
3199 conf = ap_get_module_config(r->server->module_config, &rewrite_module);
3200 conn = r->connection;
3202 if (conf->rewritelogfp == NULL) {
3205 if (conf->rewritelogfile == NULL) {
3208 if (*(conf->rewritelogfile) == '\0') {
3212 if (level > conf->rewriteloglevel) {
3216 if (r->user == NULL) {
3219 else if (strlen(r->user) != 0) {
3226 rhost = ap_get_remote_host(conn, r->server->module_config,
3228 if (rhost == NULL) {
3229 rhost = "UNKNOWN-HOST";
3232 str1 = ap_pstrcat(r->pool, rhost, " ",
3233 (conn->remote_logname != NULL ?
3234 conn->remote_logname : "-"), " ",
3236 ap_vsnprintf(str2, sizeof(str2), text, ap);
3238 if (r->main == NULL) {
3239 strcpy(type, "initial");
3242 strcpy(type, "subreq");
3245 for (i = 0, req = r; req->prev != NULL; req = req->prev) {
3252 ap_snprintf(redir, sizeof(redir), "/redir#%d", i);
3255 ap_snprintf(str3, sizeof(str3),
3256 "%s %s [%s/sid#%lx][rid#%lx/%s%s] (%d) %s\n", str1,
3257 current_logtime(r), ap_get_server_name(r),
3258 (unsigned long)(r->server), (unsigned long)r,
3259 type, redir, level, str2);
3261 fd_lock(r, conf->rewritelogfp);
3262 nbytes = strlen(str3);
3263 ap_write(conf->rewritelogfp, str3, &nbytes);
3264 fd_unlock(r, conf->rewritelogfp);
3270 static char *current_logtime(request_rec *r)
3272 ap_exploded_time_t t;
3276 ap_explode_localtime(&t, ap_now());
3278 ap_strftime(tstr, &len, 80, "[%d/%b/%Y:%H:%M:%S ", &t);
3279 ap_snprintf(tstr + strlen(tstr), 80-strlen(tstr), "%c%.2d%.2d]",
3280 t.tm_gmtoff < 0 ? '-' : '+',
3281 t.tm_gmtoff / (60*60), t.tm_gmtoff % (60*60));
3282 return ap_pstrdup(r->pool, tstr);
3289 ** +-------------------------------------------------------+
3291 ** | rewriting lockfile support
3293 ** +-------------------------------------------------------+
3296 #define REWRITELOCK_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD )
3298 static void rewritelock_create(server_rec *s, ap_context_t *p)
3300 rewrite_server_conf *conf;
3303 conf = ap_get_module_config(s->module_config, &rewrite_module);
3305 /* only operate if a lockfile is used */
3306 if (lockname == NULL || *(lockname) == '\0') {
3310 /* fixup the path, especially for rewritelock_remove() */
3311 lockname = ap_server_root_relative(p, lockname);
3313 /* create the lockfile */
3315 rc = ap_open(&lockfd, lockname, APR_WRITE | APR_CREATE, REWRITELOCK_MODE, p);
3316 if (rc != APR_SUCCESS) {
3317 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
3318 "mod_rewrite: Parent could not create RewriteLock "
3319 "file %s", lockname);
3322 #if !defined(OS2) && !defined(WIN32) && !defined(NETWARE)
3323 /* make sure the childs have access to this file */
3324 if (geteuid() == 0 /* is superuser */)
3325 chown(lockname, unixd_config.user_id, -1 /* no gid change */);
3331 static void rewritelock_open(server_rec *s, ap_context_t *p)
3333 rewrite_server_conf *conf;
3336 conf = ap_get_module_config(s->module_config, &rewrite_module);
3338 /* only operate if a lockfile is used */
3339 if (lockname == NULL || *(lockname) == '\0') {
3343 /* open the lockfile (once per child) to get a unique fd */
3344 rc = ap_open(&lockfd, lockname, APR_WRITE | APR_CREATE, REWRITELOCK_MODE, p);
3345 if (rc != APR_SUCCESS) {
3346 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
3347 "mod_rewrite: Child could not open RewriteLock "
3348 "file %s", lockname);
3354 static ap_status_t rewritelock_remove(void *data)
3356 /* only operate if a lockfile is used */
3357 if (lockname == NULL || *(lockname) == '\0') {
3361 /* remove the lockfile */
3368 static void rewritelock_alloc(request_rec *r)
3370 if (lockfd != NULL) {
3376 static void rewritelock_free(request_rec *r)
3378 if (lockfd != NULL) {
3379 fd_unlock(r, lockfd);
3386 ** +-------------------------------------------------------+
3388 ** | program map support
3390 ** +-------------------------------------------------------+
3393 static void run_rewritemap_programs(server_rec *s, ap_context_t *p)
3395 rewrite_server_conf *conf;
3396 ap_file_t *fpin = NULL;
3397 ap_file_t *fpout = NULL;
3398 ap_file_t *fperr = NULL;
3399 ap_array_header_t *rewritemaps;
3400 rewritemap_entry *entries;
3401 rewritemap_entry *map;
3405 conf = ap_get_module_config(s->module_config, &rewrite_module);
3407 /* If the engine isn't turned on,
3408 * don't even try to do anything.
3410 if (conf->state == ENGINE_DISABLED) {
3414 rewritemaps = conf->rewritemaps;
3415 entries = (rewritemap_entry *)rewritemaps->elts;
3416 for (i = 0; i < rewritemaps->nelts; i++) {
3418 if (map->type != MAPTYPE_PRG) {
3421 if (map->datafile == NULL
3422 || *(map->datafile) == '\0'
3423 || map->fpin != NULL
3424 || map->fpout != NULL ) {
3429 rc = rewritemap_program_child(p, map->datafile,
3430 &fpout, &fpin, &fperr);
3431 if (rc != APR_SUCCESS || fpin == NULL || fpout == NULL) {
3432 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
3433 "mod_rewrite: could not fork child for "
3434 "RewriteMap process. %d", rc);
3444 /* child process code */
3445 static int rewritemap_program_child(ap_context_t *p, char *progname,
3446 ap_file_t **fpout, ap_file_t **fpin,
3450 ap_procattr_t *procattr;
3456 signal(SIGHUP, SIG_IGN);
3460 if ((ap_createprocattr_init(&procattr, p) != APR_SUCCESS) ||
3461 (ap_setprocattr_io(procattr, APR_FULL_BLOCK,
3463 APR_FULL_NONBLOCK) != APR_SUCCESS) ||
3464 (ap_setprocattr_dir(procattr, ap_make_dirstr_parent(p, progname))
3466 (ap_setprocattr_cmdtype(procattr, APR_PROGRAM) != APR_SUCCESS)) {
3467 /* Something bad happened, give up and go away. */
3471 rc = ap_create_process(&procnew, progname, NULL, NULL, procattr, p);
3473 if (rc == APR_SUCCESS) {
3474 ap_note_subprocess(p, procnew, kill_after_timeout);
3477 ap_get_childin(fpin, procnew);
3481 ap_get_childout(fpout, procnew);
3485 ap_get_childerr(fperr, procnew);
3490 ap_unblock_alarms();
3499 ** +-------------------------------------------------------+
3501 ** | environment variable support
3503 ** +-------------------------------------------------------+
3507 static void expand_variables_inbuffer(request_rec *r, char *buf, int buf_len)
3510 newbuf = expand_variables(r, buf);
3511 if (strcmp(newbuf, buf) != 0) {
3512 ap_cpystrn(buf, newbuf, buf_len);
3517 static char *expand_variables(request_rec *r, char *str)
3519 char output[MAX_STRING_LEN];
3520 char input[MAX_STRING_LEN];
3528 ap_cpystrn(input, str, sizeof(input));
3531 endp = output + sizeof(output);
3533 for (cp = input; cp < input+MAX_STRING_LEN; ) {
3534 if ((cp2 = strstr(cp, "%{")) != NULL) {
3535 if ((cp3 = strstr(cp2, "}")) != NULL) {
3537 outp = ap_cpystrn(outp, cp, endp - outp);
3541 outp = ap_cpystrn(outp, lookup_variable(r, cp2), endp - outp);
3548 outp = ap_cpystrn(outp, cp, endp - outp);
3551 return expanded ? ap_pstrdup(r->pool, output) : str;
3554 static char *lookup_variable(request_rec *r, char *var)
3557 char resultbuf[LONG_STRING_LEN];
3558 ap_exploded_time_t tm;
3569 if (strcasecmp(var, "HTTP_USER_AGENT") == 0) {
3570 result = lookup_header(r, "User-Agent");
3572 else if (strcasecmp(var, "HTTP_REFERER") == 0) {
3573 result = lookup_header(r, "Referer");
3575 else if (strcasecmp(var, "HTTP_COOKIE") == 0) {
3576 result = lookup_header(r, "Cookie");
3578 else if (strcasecmp(var, "HTTP_FORWARDED") == 0) {
3579 result = lookup_header(r, "Forwarded");
3581 else if (strcasecmp(var, "HTTP_HOST") == 0) {
3582 result = lookup_header(r, "Host");
3584 else if (strcasecmp(var, "HTTP_PROXY_CONNECTION") == 0) {
3585 result = lookup_header(r, "Proxy-Connection");
3587 else if (strcasecmp(var, "HTTP_ACCEPT") == 0) {
3588 result = lookup_header(r, "Accept");
3590 /* all other headers from which we are still not know about */
3591 else if (strlen(var) > 5 && strncasecmp(var, "HTTP:", 5) == 0) {
3592 result = lookup_header(r, var+5);
3595 /* connection stuff */
3596 else if (strcasecmp(var, "REMOTE_ADDR") == 0) {
3597 result = r->connection->remote_ip;
3599 else if (strcasecmp(var, "REMOTE_HOST") == 0) {
3600 result = (char *)ap_get_remote_host(r->connection,
3601 r->per_dir_config, REMOTE_NAME);
3603 else if (strcasecmp(var, "REMOTE_USER") == 0) {
3606 else if (strcasecmp(var, "REMOTE_IDENT") == 0) {
3607 result = (char *)ap_get_remote_logname(r);
3611 else if (strcasecmp(var, "THE_REQUEST") == 0) { /* non-standard */
3612 result = r->the_request;
3614 else if (strcasecmp(var, "REQUEST_METHOD") == 0) {
3617 else if (strcasecmp(var, "REQUEST_URI") == 0) { /* non-standard */
3620 else if (strcasecmp(var, "SCRIPT_FILENAME") == 0 ||
3621 strcasecmp(var, "REQUEST_FILENAME") == 0 ) {
3622 result = r->filename;
3624 else if (strcasecmp(var, "PATH_INFO") == 0) {
3625 result = r->path_info;
3627 else if (strcasecmp(var, "QUERY_STRING") == 0) {
3630 else if (strcasecmp(var, "AUTH_TYPE") == 0) {
3631 result = r->ap_auth_type;
3633 else if (strcasecmp(var, "IS_SUBREQ") == 0) { /* non-standard */
3634 result = (r->main != NULL ? "true" : "false");
3637 /* internal server stuff */
3638 else if (strcasecmp(var, "DOCUMENT_ROOT") == 0) {
3639 result = ap_document_root(r);
3641 else if (strcasecmp(var, "SERVER_ADMIN") == 0) {
3642 result = r->server->server_admin;
3644 else if (strcasecmp(var, "SERVER_NAME") == 0) {
3645 result = ap_get_server_name(r);
3647 else if (strcasecmp(var, "SERVER_ADDR") == 0) { /* non-standard */
3648 result = r->connection->local_ip;
3650 else if (strcasecmp(var, "SERVER_PORT") == 0) {
3651 ap_snprintf(resultbuf, sizeof(resultbuf), "%u", ap_get_server_port(r));
3654 else if (strcasecmp(var, "SERVER_PROTOCOL") == 0) {
3655 result = r->protocol;
3657 else if (strcasecmp(var, "SERVER_SOFTWARE") == 0) {
3658 result = ap_get_server_version();
3660 else if (strcasecmp(var, "API_VERSION") == 0) { /* non-standard */
3661 ap_snprintf(resultbuf, sizeof(resultbuf), "%d:%d",
3662 MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR);
3666 /* XXX: wow this has gotta be slow if you actually use it for a lot, recalculates exploded time for each variable */
3667 /* underlaying Unix system stuff */
3668 else if (strcasecmp(var, "TIME_YEAR") == 0) {
3669 ap_explode_localtime(&tm, ap_now());
3670 ap_snprintf(resultbuf, sizeof(resultbuf), "%04d", tm.tm_year + 1900);
3673 #define MKTIMESTR(format, tmfield) \
3674 ap_explode_localtime(&tm, ap_now()); \
3675 ap_snprintf(resultbuf, sizeof(resultbuf), format, tm.tmfield); \
3677 else if (strcasecmp(var, "TIME_MON") == 0) {
3678 MKTIMESTR("%02d", tm_mon+1)
3680 else if (strcasecmp(var, "TIME_DAY") == 0) {
3681 MKTIMESTR("%02d", tm_mday)
3683 else if (strcasecmp(var, "TIME_HOUR") == 0) {
3684 MKTIMESTR("%02d", tm_hour)
3686 else if (strcasecmp(var, "TIME_MIN") == 0) {
3687 MKTIMESTR("%02d", tm_min)
3689 else if (strcasecmp(var, "TIME_SEC") == 0) {
3690 MKTIMESTR("%02d", tm_sec)
3692 else if (strcasecmp(var, "TIME_WDAY") == 0) {
3693 MKTIMESTR("%d", tm_wday)
3695 else if (strcasecmp(var, "TIME") == 0) {
3696 ap_explode_localtime(&tm, ap_now());
3697 ap_snprintf(resultbuf, sizeof(resultbuf),
3698 "%04d%02d%02d%02d%02d%02d", tm.tm_year + 1900,
3699 tm.tm_mon+1, tm.tm_mday,
3700 tm.tm_hour, tm.tm_min, tm.tm_sec);
3702 rewritelog(r, 1, "RESULT='%s'", result);
3705 /* all other env-variables from the parent Apache process */
3706 else if (strlen(var) > 4 && strncasecmp(var, "ENV:", 4) == 0) {
3707 /* first try the internal Apache notes structure */
3708 result = ap_table_get(r->notes, var+4);
3709 /* second try the internal Apache env structure */
3710 if (result == NULL) {
3711 result = ap_table_get(r->subprocess_env, var+4);
3713 /* third try the external OS env */
3714 if (result == NULL) {
3715 result = getenv(var+4);
3719 #define LOOKAHEAD(subrecfunc) \
3721 /* filename is safe to use */ \
3722 r->filename != NULL \
3723 /* - and we're either not in a subrequest */ \
3724 && ( r->main == NULL \
3725 /* - or in a subrequest where paths are non-NULL... */ \
3726 || ( r->main->uri != NULL && r->uri != NULL \
3727 /* ...and sub and main paths differ */ \
3728 && strcmp(r->main->uri, r->uri) != 0))) { \
3729 /* process a file-based subrequest */ \
3730 rsub = subrecfunc(r->filename, r); \
3731 /* now recursively lookup the variable in the sub_req */ \
3732 result = lookup_variable(rsub, var+5); \
3733 /* copy it up to our scope before we destroy sub_req's ap_context_t */ \
3734 result = ap_pstrdup(r->pool, result); \
3735 /* cleanup by destroying the subrequest */ \
3736 ap_destroy_sub_req(rsub); \
3738 rewritelog(r, 5, "lookahead: path=%s var=%s -> val=%s", \
3739 r->filename, var+5, result); \
3740 /* return ourself to prevent re-pstrdup */ \
3741 return (char *)result; \
3744 /* look-ahead for parameter through URI-based sub-request */
3745 else if (strlen(var) > 5 && strncasecmp(var, "LA-U:", 5) == 0) {
3746 LOOKAHEAD(ap_sub_req_lookup_uri)
3748 /* look-ahead for parameter through file-based sub-request */
3749 else if (strlen(var) > 5 && strncasecmp(var, "LA-F:", 5) == 0) {
3750 LOOKAHEAD(ap_sub_req_lookup_file)
3753 #if !defined(WIN32) && !defined(NETWARE)
3754 /* Win32 has a rather different view of file ownerships.
3755 For now, just forget it */
3758 else if (strcasecmp(var, "SCRIPT_USER") == 0) {
3759 result = "<unknown>";
3760 if (r->finfo.protection != 0) {
3761 if ((pw = getpwuid(r->finfo.user)) != NULL) {
3762 result = pw->pw_name;
3766 if (stat(r->filename, &finfo) == 0) {
3767 if ((pw = getpwuid(finfo.st_uid)) != NULL) {
3768 result = pw->pw_name;
3773 else if (strcasecmp(var, "SCRIPT_GROUP") == 0) {
3774 result = "<unknown>";
3775 if (r->finfo.protection != 0) {
3776 if ((gr = getgrgid(r->finfo.group)) != NULL) {
3777 result = gr->gr_name;
3781 if (stat(r->filename, &finfo) == 0) {
3782 if ((gr = getgrgid(finfo.st_gid)) != NULL) {
3783 result = gr->gr_name;
3788 #endif /* ndef WIN32 && NETWARE*/
3790 if (result == NULL) {
3791 return ap_pstrdup(r->pool, "");
3794 return ap_pstrdup(r->pool, result);
3798 static char *lookup_header(request_rec *r, const char *name)
3800 ap_array_header_t *hdrs_arr;
3801 ap_table_entry_t *hdrs;
3804 hdrs_arr = ap_table_elts(r->headers_in);
3805 hdrs = (ap_table_entry_t *)hdrs_arr->elts;
3806 for (i = 0; i < hdrs_arr->nelts; ++i) {
3807 if (hdrs[i].key == NULL) {
3810 if (strcasecmp(hdrs[i].key, name) == 0) {
3811 ap_table_merge(r->notes, VARY_KEY_THIS, name);
3822 ** +-------------------------------------------------------+
3824 ** | caching support
3826 ** +-------------------------------------------------------+
3830 static cache *init_cache(ap_context_t *p)
3834 c = (cache *)ap_palloc(p, sizeof(cache));
3835 if (ap_create_context(&c->pool, p) != APR_SUCCESS)
3837 c->lists = ap_make_array(c->pool, 2, sizeof(cachelist));
3841 static void set_cache_string(cache *c, char *res, int mode, time_t t,
3842 char *key, char *value)
3849 store_cache_string(c, res, &ce);
3853 static char *get_cache_string(cache *c, char *res, int mode,
3854 time_t t, char *key)
3858 ce = retrieve_cache_string(c, res, key);
3862 if (mode & CACHEMODE_TS) {
3863 if (t != ce->time) {
3867 else if (mode & CACHEMODE_TTL) {
3872 return ap_pstrdup(c->pool, ce->value);
3875 static int cache_tlb_hash(char *key)
3881 for (p=key; *p != '\0'; ++p) {
3882 n = n * 53711 + 134561 + (unsigned)(*p & 0xff);
3885 return n % CACHE_TLB_ROWS;
3888 static cacheentry *cache_tlb_lookup(cachetlbentry *tlb, cacheentry *elt,
3891 int ix = cache_tlb_hash(key);
3895 for (i=0; i < CACHE_TLB_COLS; ++i) {
3899 if (strcmp(elt[j].key, key) == 0)
3905 static void cache_tlb_replace(cachetlbentry *tlb, cacheentry *elt,
3908 int ix = cache_tlb_hash(e->key);
3913 for (i=1; i < CACHE_TLB_COLS; ++i)
3914 tlb->t[i] = tlb->t[i-1];
3916 tlb->t[0] = e - elt;
3919 static void store_cache_string(cache *c, char *res, cacheentry *ce)
3929 /* first try to edit an existing entry */
3930 for (i = 0; i < c->lists->nelts; i++) {
3931 l = &(((cachelist *)c->lists->elts)[i]);
3932 if (strcmp(l->resource, res) == 0) {
3935 e = cache_tlb_lookup((cachetlbentry *)l->tlb->elts,
3936 (cacheentry *)l->entries->elts, ce->key);
3939 e->value = ap_pstrdup(c->pool, ce->value);
3943 for (j = 0; j < l->entries->nelts; j++) {
3944 e = &(((cacheentry *)l->entries->elts)[j]);
3945 if (strcmp(e->key, ce->key) == 0) {
3947 e->value = ap_pstrdup(c->pool, ce->value);
3948 cache_tlb_replace((cachetlbentry *)l->tlb->elts,
3949 (cacheentry *)l->entries->elts, e);
3956 /* create a needed new list */
3958 l = ap_push_array(c->lists);
3959 l->resource = ap_pstrdup(c->pool, res);
3960 l->entries = ap_make_array(c->pool, 2, sizeof(cacheentry));
3961 l->tlb = ap_make_array(c->pool, CACHE_TLB_ROWS,
3962 sizeof(cachetlbentry));
3963 for (i=0; i<CACHE_TLB_ROWS; ++i) {
3964 t = &((cachetlbentry *)l->tlb->elts)[i];
3965 for (j=0; j<CACHE_TLB_COLS; ++j)
3970 /* create the new entry */
3971 for (i = 0; i < c->lists->nelts; i++) {
3972 l = &(((cachelist *)c->lists->elts)[i]);
3973 if (strcmp(l->resource, res) == 0) {
3974 e = ap_push_array(l->entries);
3976 e->key = ap_pstrdup(c->pool, ce->key);
3977 e->value = ap_pstrdup(c->pool, ce->value);
3978 cache_tlb_replace((cachetlbentry *)l->tlb->elts,
3979 (cacheentry *)l->entries->elts, e);
3984 /* not reached, but when it is no problem... */
3988 static cacheentry *retrieve_cache_string(cache *c, char *res, char *key)
3995 for (i = 0; i < c->lists->nelts; i++) {
3996 l = &(((cachelist *)c->lists->elts)[i]);
3997 if (strcmp(l->resource, res) == 0) {
3999 e = cache_tlb_lookup((cachetlbentry *)l->tlb->elts,
4000 (cacheentry *)l->entries->elts, key);
4004 for (j = 0; j < l->entries->nelts; j++) {
4005 e = &(((cacheentry *)l->entries->elts)[j]);
4006 if (strcmp(e->key, key) == 0) {
4019 ** +-------------------------------------------------------+
4023 ** +-------------------------------------------------------+
4026 static char *subst_prefix_path(request_rec *r, char *input, char *match,
4029 char matchbuf[LONG_STRING_LEN];
4030 char substbuf[LONG_STRING_LEN];
4036 /* first create a match string which always has a trailing slash */
4037 l = ap_cpystrn(matchbuf, match, sizeof(matchbuf)) - matchbuf;
4038 if (matchbuf[l-1] != '/') {
4040 matchbuf[l+1] = '\0';
4043 /* now compare the prefix */
4044 if (strncmp(input, matchbuf, l) == 0) {
4045 rewritelog(r, 5, "strip matching prefix: %s -> %s", output, output+l);
4046 output = ap_pstrdup(r->pool, output+l);
4048 /* and now add the base-URL as replacement prefix */
4049 l = ap_cpystrn(substbuf, subst, sizeof(substbuf)) - substbuf;
4050 if (substbuf[l-1] != '/') {
4052 substbuf[l+1] = '\0';
4055 if (output[0] == '/') {
4056 rewritelog(r, 4, "add subst prefix: %s -> %s%s",
4057 output, substbuf, output+1);
4058 output = ap_pstrcat(r->pool, substbuf, output+1, NULL);
4061 rewritelog(r, 4, "add subst prefix: %s -> %s%s",
4062 output, substbuf, output);
4063 output = ap_pstrcat(r->pool, substbuf, output, NULL);
4072 ** own command line parser which don't have the '\\' problem
4076 static int parseargline(char *str, char **a1, char **a2, char **a3)
4081 #define SKIP_WHITESPACE(cp) \
4082 for ( ; *cp == ' ' || *cp == '\t'; ) { \
4086 #define CHECK_QUOTATION(cp,isquoted) \
4093 #define DETERMINE_NEXTSTRING(cp,isquoted) \
4094 for ( ; *cp != '\0'; cp++) { \
4095 if ( (isquoted && (*cp == ' ' || *cp == '\t')) \
4096 || (*cp == '\\' && (*(cp+1) == ' ' || *(cp+1) == '\t'))) { \
4100 if ( (!isquoted && (*cp == ' ' || *cp == '\t')) \
4101 || (isquoted && *cp == '"') ) { \
4107 SKIP_WHITESPACE(cp);
4109 /* determine first argument */
4110 CHECK_QUOTATION(cp, isquoted);
4112 DETERMINE_NEXTSTRING(cp, isquoted);
4118 SKIP_WHITESPACE(cp);
4120 /* determine second argument */
4121 CHECK_QUOTATION(cp, isquoted);
4123 DETERMINE_NEXTSTRING(cp, isquoted);
4131 SKIP_WHITESPACE(cp);
4133 /* again check if there are only two arguments */
4140 /* determine second argument */
4141 CHECK_QUOTATION(cp, isquoted);
4143 DETERMINE_NEXTSTRING(cp, isquoted);
4150 static void add_env_variable(request_rec *r, char *s)
4152 char var[MAX_STRING_LEN];
4153 char val[MAX_STRING_LEN];
4157 if ((cp = strchr(s, ':')) != NULL) {
4158 n = ((cp-s) > MAX_STRING_LEN-1 ? MAX_STRING_LEN-1 : (cp-s));
4161 ap_cpystrn(val, cp+1, sizeof(val));
4162 ap_table_set(r->subprocess_env, var, val);
4163 rewritelog(r, 5, "setting env variable '%s' to '%s'", var, val);
4171 ** stat() for only the prefix of a path
4175 static int prefix_stat(const char *path, struct stat *sb)
4177 char curpath[LONG_STRING_LEN];
4180 ap_cpystrn(curpath, path, sizeof(curpath));
4181 if (curpath[0] != '/') {
4184 if ((cp = strchr(curpath+1, '/')) != NULL) {
4187 if (stat(curpath, sb) == 0) {
4203 static struct flock lock_it;
4204 static struct flock unlock_it;
4207 static void fd_lock(request_rec *r, ap_file_t *fd)
4213 lock_it.l_whence = SEEK_SET; /* from current point */
4214 lock_it.l_start = 0; /* -"- */
4215 lock_it.l_len = 0; /* until end of file */
4216 lock_it.l_type = F_WRLCK; /* set exclusive/write lock */
4217 lock_it.l_pid = 0; /* pid not actually interesting */
4219 ap_get_os_file(&sys_file, fd);
4220 while ( ((rc = fcntl(sys_file, F_SETLKW, &lock_it)) < 0)
4221 && (errno == EINTR) ) {
4226 ap_get_os_file(fd, &sys_file);
4227 while ( ((rc = flock(sys_file, LOCK_EX)) < 0)
4228 && (errno == EINTR) ) {
4233 /* Lock the first byte, always, assume we want to append
4234 and seek to the end afterwards */
4235 ap_seek(fd, APR_SET, 0);
4236 ap_get_os_file(&sys_file, fd);
4237 rc = _locking(sys_file, _LK_LOCK, 1);
4238 ap_seek(fd, APR_END, 0);
4242 ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
4243 "mod_rewrite: failed to lock file descriptor");
4249 static void fd_unlock(request_rec *r, ap_file_t *fd)
4255 unlock_it.l_whence = SEEK_SET; /* from current point */
4256 unlock_it.l_start = 0; /* -"- */
4257 unlock_it.l_len = 0; /* until end of file */
4258 unlock_it.l_type = F_UNLCK; /* unlock */
4259 unlock_it.l_pid = 0; /* pid not actually interesting */
4261 ap_get_os_file(&sys_file, fd);
4262 rc = fcntl(sys_file, F_SETLKW, &unlock_it);
4265 ap_get_os_file(fd, &sys_file);
4266 rc = flock(sys_file, LOCK_UN);
4269 ap_seek(fd, APR_SET, 0);
4270 ap_get_os_file(&sys_file, fd);
4271 rc = _locking(sys_file, _LK_UNLCK, 1);
4272 ap_seek(fd, APR_END, 0);
4276 ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
4277 "mod_rewrite: failed to unlock file descriptor");
4284 ** Lexicographic Compare
4288 static int compare_lexicography(char *cpNum1, char *cpNum2)
4293 n1 = strlen(cpNum1);
4294 n2 = strlen(cpNum2);
4301 for (i = 0; i < n1; i++) {
4302 if (cpNum1[i] > cpNum2[i]) {
4305 if (cpNum1[i] < cpNum2[i]) {
4313 int main(int argc, char *argv[])
4315 ExitThread(TSR_THREAD, 0);