1 /* ====================================================================
2 * The Apache Software License, Version 1.1
4 * Copyright (c) 2000 The Apache Software Foundation. All rights
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
19 * 3. The end-user documentation included with the redistribution,
20 * if any, must include the following acknowledgment:
21 * "This product includes software developed by the
22 * Apache Software Foundation (http://www.apache.org/)."
23 * Alternately, this acknowledgment may appear in the software itself,
24 * if and wherever such third-party acknowledgments normally appear.
26 * 4. The names "Apache" and "Apache Software Foundation" must
27 * not be used to endorse or promote products derived from this
28 * software without prior written permission. For written
29 * permission, please contact apache@apache.org.
31 * 5. Products derived from this software may not be called "Apache",
32 * nor may "Apache" appear in their name, without prior written
33 * permission of the Apache Software Foundation.
35 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
36 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
37 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
39 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
42 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
43 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
44 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
45 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47 * ====================================================================
49 * This software consists of voluntary contributions made by many
50 * individuals on behalf of the Apache Software Foundation. For more
51 * information on the Apache Software Foundation, please see
52 * <http://www.apache.org/>.
54 * Portions of this software are based upon public domain software
55 * originally written at the National Center for Supercomputing Applications,
56 * University of Illinois, Urbana-Champaign.
60 ** _ __ ___ ___ __| | _ __ _____ ___ __(_) |_ ___
61 ** | '_ ` _ \ / _ \ / _` | | '__/ _ \ \ /\ / / '__| | __/ _ \
62 ** | | | | | | (_) | (_| | | | | __/\ V V /| | | | || __/
63 ** |_| |_| |_|\___/ \__,_|___|_| \___| \_/\_/ |_| |_|\__\___|
66 ** URL Rewriting Module
68 ** This module uses a rule-based rewriting engine (based on a
69 ** regular-expression parser) to rewrite requested URLs on the fly.
71 ** It supports an unlimited number of additional rule conditions (which can
72 ** operate on a lot of variables, even on HTTP headers) for granular
73 ** matching and even external database lookups (either via plain text
74 ** tables, DBM hash files or even external processes) for advanced URL
77 ** It operates on the full URLs (including the PATH_INFO part) both in
78 ** per-server context (httpd.conf) and per-dir context (.htaccess) and even
79 ** can generate QUERY_STRING parts on result. The rewriting result finally
80 ** can lead to internal subprocessing, external request redirection or even
81 ** to internal proxy throughput.
83 ** This module was originally written in April 1996 and
84 ** gifted exclusively to the The Apache Software Foundation in July 1997 by
86 ** Ralf S. Engelschall
87 ** rse@engelschall.com
88 ** www.engelschall.com
91 #include "ap_config.h"
93 #include "http_config.h"
94 #include "http_request.h"
95 #include "http_core.h"
97 #include "http_protocol.h"
98 #include "mod_rewrite.h"
100 #if !defined(OS2) && !defined(WIN32)
106 #ifdef HAVE_SYS_TYPES_H
107 #include <sys/types.h>
110 #ifdef HAVE_SYS_UIO_H
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 * XXX: this needs updating for apache-2.0 configuration method
158 * MODULE-DEFINITION-START
159 * Name: rewrite_module
161 . ./helpers/find-dbm-lib
162 if [ "x$found_dbm" = "x1" ]; then
163 echo " enabling DBM support for mod_rewrite"
165 echo " disabling DBM support for mod_rewrite"
166 echo " (perhaps you need to add -ldbm, -lndbm or -lgdbm to EXTRA_LIBS)"
167 CFLAGS="$CFLAGS -DNO_DBM_REWRITEMAP"
170 * MODULE-DEFINITION-END
173 /* the ap_table_t of commands we provide */
174 static const command_rec command_table[] = {
175 { "RewriteEngine", cmd_rewriteengine, NULL, OR_FILEINFO, FLAG,
176 "On or Off to enable or disable (default) the whole rewriting engine" },
177 { "RewriteOptions", cmd_rewriteoptions, NULL, OR_FILEINFO, ITERATE,
178 "List of option strings to set" },
179 { "RewriteBase", cmd_rewritebase, NULL, OR_FILEINFO, TAKE1,
180 "the base URL of the per-directory context" },
181 { "RewriteCond", cmd_rewritecond, NULL, OR_FILEINFO, RAW_ARGS,
182 "an input string and a to be applied regexp-pattern" },
183 { "RewriteRule", cmd_rewriterule, NULL, OR_FILEINFO, RAW_ARGS,
184 "an URL-applied regexp-pattern and a substitution URL" },
185 { "RewriteMap", cmd_rewritemap, NULL, RSRC_CONF, TAKE2,
186 "a mapname and a filename" },
187 { "RewriteLock", cmd_rewritelock, NULL, RSRC_CONF, TAKE1,
188 "the filename of a lockfile used for inter-process synchronization"},
189 { "RewriteLog", cmd_rewritelog, NULL, RSRC_CONF, TAKE1,
190 "the filename of the rewriting logfile" },
191 { "RewriteLogLevel", cmd_rewriteloglevel, NULL, RSRC_CONF, TAKE1,
192 "the level of the rewriting logfile verbosity "
193 "(0=none, 1=std, .., 9=max)" },
197 /* the ap_table_t of content handlers we provide */
198 static const handler_rec handler_table[] = {
199 { "redirect-handler", handler_redirect },
203 static void register_hooks(void)
205 ap_hook_post_config(init_module,NULL,NULL,AP_HOOK_MIDDLE);
206 ap_hook_child_init(init_child,NULL,NULL,AP_HOOK_MIDDLE);
208 ap_hook_fixups(hook_fixup,NULL,NULL,AP_HOOK_FIRST);
209 ap_hook_translate_name(hook_uri2file,NULL,NULL,AP_HOOK_FIRST);
210 ap_hook_type_checker(hook_mimetype,NULL,NULL,AP_HOOK_MIDDLE);
213 /* the main config structure */
214 module MODULE_VAR_EXPORT rewrite_module = {
215 STANDARD20_MODULE_STUFF,
216 config_perdir_create, /* create per-dir config structures */
217 config_perdir_merge, /* merge per-dir config structures */
218 config_server_create, /* create per-server config structures */
219 config_server_merge, /* merge per-server config structures */
220 command_table, /* ap_table_t of config file commands */
221 handler_table, /* [#8] MIME-typed-dispatched handlers */
222 register_hooks /* register hooks */
226 static cache *cachep;
228 /* whether proxy module is available or not */
229 static int proxy_available;
230 static int once_through = 0;
232 static const char *lockname;
233 static ap_lock_t *rewrite_map_lock = NULL;
234 static ap_lock_t *rewrite_log_lock = NULL;
237 ** +-------------------------------------------------------+
239 ** | configuration directive handling
241 ** +-------------------------------------------------------+
246 ** per-server configuration structure handling
250 static void *config_server_create(ap_pool_t *p, server_rec *s)
252 rewrite_server_conf *a;
254 a = (rewrite_server_conf *)ap_pcalloc(p, sizeof(rewrite_server_conf));
256 a->state = ENGINE_DISABLED;
257 a->options = OPTION_NONE;
258 a->rewritelogfile = NULL;
259 a->rewritelogfp = NULL;
260 a->rewriteloglevel = 0;
261 a->rewritemaps = ap_make_array(p, 2, sizeof(rewritemap_entry));
262 a->rewriteconds = ap_make_array(p, 2, sizeof(rewritecond_entry));
263 a->rewriterules = ap_make_array(p, 2, sizeof(rewriterule_entry));
269 static void *config_server_merge(ap_pool_t *p, void *basev, void *overridesv)
271 rewrite_server_conf *a, *base, *overrides;
273 a = (rewrite_server_conf *)ap_pcalloc(p, sizeof(rewrite_server_conf));
274 base = (rewrite_server_conf *)basev;
275 overrides = (rewrite_server_conf *)overridesv;
277 a->state = overrides->state;
278 a->options = overrides->options;
279 a->server = overrides->server;
281 if (a->options & OPTION_INHERIT) {
283 * local directives override
284 * and anything else is inherited
286 a->rewriteloglevel = overrides->rewriteloglevel != 0
287 ? overrides->rewriteloglevel
288 : base->rewriteloglevel;
289 a->rewritelogfile = overrides->rewritelogfile != NULL
290 ? overrides->rewritelogfile
291 : base->rewritelogfile;
292 a->rewritelogfp = overrides->rewritelogfp != NULL
293 ? overrides->rewritelogfp
294 : base->rewritelogfp;
295 a->rewritemaps = ap_append_arrays(p, overrides->rewritemaps,
297 a->rewriteconds = ap_append_arrays(p, overrides->rewriteconds,
299 a->rewriterules = ap_append_arrays(p, overrides->rewriterules,
304 * local directives override
305 * and anything else gets defaults
307 a->rewriteloglevel = overrides->rewriteloglevel;
308 a->rewritelogfile = overrides->rewritelogfile;
309 a->rewritelogfp = overrides->rewritelogfp;
310 a->rewritemaps = overrides->rewritemaps;
311 a->rewriteconds = overrides->rewriteconds;
312 a->rewriterules = overrides->rewriterules;
321 ** per-directory configuration structure handling
325 static void *config_perdir_create(ap_pool_t *p, char *path)
327 rewrite_perdir_conf *a;
329 a = (rewrite_perdir_conf *)ap_pcalloc(p, sizeof(rewrite_perdir_conf));
331 a->state = ENGINE_DISABLED;
332 a->options = OPTION_NONE;
334 a->rewriteconds = ap_make_array(p, 2, sizeof(rewritecond_entry));
335 a->rewriterules = ap_make_array(p, 2, sizeof(rewriterule_entry));
341 /* make sure it has a trailing slash */
342 if (path[strlen(path)-1] == '/') {
343 a->directory = ap_pstrdup(p, path);
346 a->directory = ap_pstrcat(p, path, "/", NULL);
353 static void *config_perdir_merge(ap_pool_t *p, void *basev, void *overridesv)
355 rewrite_perdir_conf *a, *base, *overrides;
357 a = (rewrite_perdir_conf *)ap_pcalloc(p,
358 sizeof(rewrite_perdir_conf));
359 base = (rewrite_perdir_conf *)basev;
360 overrides = (rewrite_perdir_conf *)overridesv;
362 a->state = overrides->state;
363 a->options = overrides->options;
364 a->directory = overrides->directory;
365 a->baseurl = overrides->baseurl;
367 if (a->options & OPTION_INHERIT) {
368 a->rewriteconds = ap_append_arrays(p, overrides->rewriteconds,
370 a->rewriterules = ap_append_arrays(p, overrides->rewriterules,
374 a->rewriteconds = overrides->rewriteconds;
375 a->rewriterules = overrides->rewriterules;
384 ** the configuration commands
388 static const char *cmd_rewriteengine(cmd_parms *cmd,
389 rewrite_perdir_conf *dconf, int flag)
391 rewrite_server_conf *sconf;
394 (rewrite_server_conf *)ap_get_module_config(cmd->server->module_config,
397 if (cmd->path == NULL) { /* is server command */
398 sconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
400 else /* is per-directory command */ {
401 dconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
407 static const char *cmd_rewriteoptions(cmd_parms *cmd,
408 rewrite_perdir_conf *dconf, char *option)
410 rewrite_server_conf *sconf;
413 sconf = (rewrite_server_conf *)
414 ap_get_module_config(cmd->server->module_config, &rewrite_module);
416 if (cmd->path == NULL) { /* is server command */
417 err = cmd_rewriteoptions_setoption(cmd->pool,
418 &(sconf->options), option);
420 else { /* is per-directory command */
421 err = cmd_rewriteoptions_setoption(cmd->pool,
422 &(dconf->options), option);
428 static const char *cmd_rewriteoptions_setoption(ap_pool_t *p, int *options,
431 if (strcasecmp(name, "inherit") == 0) {
432 *options |= OPTION_INHERIT;
435 return ap_pstrcat(p, "RewriteOptions: unknown option '",
441 static const char *cmd_rewritelog(cmd_parms *cmd, void *dconf, char *a1)
443 rewrite_server_conf *sconf;
445 sconf = (rewrite_server_conf *)
446 ap_get_module_config(cmd->server->module_config, &rewrite_module);
448 sconf->rewritelogfile = a1;
453 static const char *cmd_rewriteloglevel(cmd_parms *cmd, void *dconf, char *a1)
455 rewrite_server_conf *sconf;
457 sconf = (rewrite_server_conf *)
458 ap_get_module_config(cmd->server->module_config, &rewrite_module);
460 sconf->rewriteloglevel = atoi(a1);
465 static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, char *a1,
468 rewrite_server_conf *sconf;
469 rewritemap_entry *newmap;
472 sconf = (rewrite_server_conf *)
473 ap_get_module_config(cmd->server->module_config, &rewrite_module);
475 newmap = ap_push_array(sconf->rewritemaps);
479 if (strncmp(a2, "txt:", 4) == 0) {
480 newmap->type = MAPTYPE_TXT;
481 newmap->datafile = a2+4;
482 newmap->checkfile = a2+4;
484 else if (strncmp(a2, "rnd:", 4) == 0) {
485 newmap->type = MAPTYPE_RND;
486 newmap->datafile = a2+4;
487 newmap->checkfile = a2+4;
489 else if (strncmp(a2, "dbm:", 4) == 0) {
490 #ifndef NO_DBM_REWRITEMAP
491 newmap->type = MAPTYPE_DBM;
492 newmap->datafile = a2+4;
493 newmap->checkfile = ap_pstrcat(cmd->pool, a2+4, NDBM_FILE_SUFFIX, NULL);
495 return ap_pstrdup(cmd->pool, "RewriteMap: cannot use NDBM mapfile, "
496 "because no NDBM support is compiled in");
499 else if (strncmp(a2, "prg:", 4) == 0) {
500 newmap->type = MAPTYPE_PRG;
501 newmap->datafile = a2+4;
502 newmap->checkfile = a2+4;
504 else if (strncmp(a2, "int:", 4) == 0) {
505 newmap->type = MAPTYPE_INT;
506 newmap->datafile = NULL;
507 newmap->checkfile = NULL;
508 if (strcmp(a2+4, "tolower") == 0) {
509 newmap->func = rewrite_mapfunc_tolower;
511 else if (strcmp(a2+4, "toupper") == 0) {
512 newmap->func = rewrite_mapfunc_toupper;
514 else if (strcmp(a2+4, "escape") == 0) {
515 newmap->func = rewrite_mapfunc_escape;
517 else if (strcmp(a2+4, "unescape") == 0) {
518 newmap->func = rewrite_mapfunc_unescape;
520 else if (sconf->state == ENGINE_ENABLED) {
521 return ap_pstrcat(cmd->pool, "RewriteMap: internal map not found:",
526 newmap->type = MAPTYPE_TXT;
527 newmap->datafile = a2;
528 newmap->checkfile = a2;
531 newmap->fpout = NULL;
533 if (newmap->checkfile && (sconf->state == ENGINE_ENABLED)
534 && (stat(newmap->checkfile, &st) == -1)) {
535 return ap_pstrcat(cmd->pool,
536 "RewriteMap: map file or program not found:",
537 newmap->checkfile, NULL);
543 static const char *cmd_rewritelock(cmd_parms *cmd, void *dconf, char *a1)
547 if ((error = ap_check_cmd_context(cmd, GLOBAL_ONLY)) != NULL)
555 static const char *cmd_rewritebase(cmd_parms *cmd, rewrite_perdir_conf *dconf,
558 if (cmd->path == NULL || dconf == NULL) {
559 return "RewriteBase: only valid in per-directory config files";
562 return "RewriteBase: empty URL not allowed";
565 return "RewriteBase: argument is not a valid URL";
573 static const char *cmd_rewritecond(cmd_parms *cmd, rewrite_perdir_conf *dconf,
576 rewrite_server_conf *sconf;
577 rewritecond_entry *newcond;
586 sconf = (rewrite_server_conf *)
587 ap_get_module_config(cmd->server->module_config, &rewrite_module);
589 /* make a new entry in the internal temporary rewrite rule list */
590 if (cmd->path == NULL) { /* is server command */
591 newcond = ap_push_array(sconf->rewriteconds);
593 else { /* is per-directory command */
594 newcond = ap_push_array(dconf->rewriteconds);
597 /* parse the argument line ourself */
598 if (parseargline(str, &a1, &a2, &a3)) {
599 return ap_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str,
603 /* arg1: the input string */
604 newcond->input = ap_pstrdup(cmd->pool, a1);
606 /* arg3: optional flags field
607 (this have to be first parsed, because we need to
608 know if the regex should be compiled with ICASE!) */
609 newcond->flags = CONDFLAG_NONE;
611 if ((err = cmd_rewritecond_parseflagfield(cmd->pool, newcond,
618 try to compile the regexp to test if is ok */
621 newcond->flags |= CONDFLAG_NOTMATCH;
625 /* now be careful: Under the POSIX regex library
626 we can compile the pattern for case insensitive matching,
627 under the old V8 library we have to do it self via a hack */
628 if (newcond->flags & CONDFLAG_NOCASE) {
629 rc = ((regexp = ap_pregcomp(cmd->pool, cp, REG_EXTENDED|REG_ICASE))
633 rc = ((regexp = ap_pregcomp(cmd->pool, cp, REG_EXTENDED)) == NULL);
636 return ap_pstrcat(cmd->pool,
637 "RewriteCond: cannot compile regular expression '",
641 newcond->pattern = ap_pstrdup(cmd->pool, cp);
642 newcond->regexp = regexp;
647 static const char *cmd_rewritecond_parseflagfield(ap_pool_t *p,
648 rewritecond_entry *cfg,
659 if (str[0] != '[' || str[strlen(str)-1] != ']') {
660 return "RewriteCond: bad flag delimiters";
664 str[strlen(str)-1] = ','; /* for simpler parsing */
665 for ( ; *cp != '\0'; ) {
666 /* skip whitespaces */
667 for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++)
673 if ((cp2 = strchr(cp, ',')) != NULL) {
675 for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--)
678 if ((cp3 = strchr(cp1, '=')) != NULL) {
687 if ((err = cmd_rewritecond_setflag(p, cfg, key, val)) != NULL) {
699 static const char *cmd_rewritecond_setflag(ap_pool_t *p, rewritecond_entry *cfg,
700 char *key, char *val)
702 if ( strcasecmp(key, "nocase") == 0
703 || strcasecmp(key, "NC") == 0 ) {
704 cfg->flags |= CONDFLAG_NOCASE;
706 else if ( strcasecmp(key, "ornext") == 0
707 || strcasecmp(key, "OR") == 0 ) {
708 cfg->flags |= CONDFLAG_ORNEXT;
711 return ap_pstrcat(p, "RewriteCond: unknown flag '", key, "'\n", NULL);
716 static const char *cmd_rewriterule(cmd_parms *cmd, rewrite_perdir_conf *dconf,
719 rewrite_server_conf *sconf;
720 rewriterule_entry *newrule;
729 sconf = (rewrite_server_conf *)
730 ap_get_module_config(cmd->server->module_config, &rewrite_module);
732 /* make a new entry in the internal rewrite rule list */
733 if (cmd->path == NULL) { /* is server command */
734 newrule = ap_push_array(sconf->rewriterules);
736 else { /* is per-directory command */
737 newrule = ap_push_array(dconf->rewriterules);
740 /* parse the argument line ourself */
741 if (parseargline(str, &a1, &a2, &a3)) {
742 return ap_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str,
746 /* arg3: optional flags field */
747 newrule->forced_mimetype = NULL;
748 newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY;
749 newrule->flags = RULEFLAG_NONE;
750 newrule->env[0] = NULL;
753 if ((err = cmd_rewriterule_parseflagfield(cmd->pool, newrule,
760 * try to compile the regexp to test if is ok
764 newrule->flags |= RULEFLAG_NOTMATCH;
768 if (newrule->flags & RULEFLAG_NOCASE) {
771 if ((regexp = ap_pregcomp(cmd->pool, cp, mode)) == NULL) {
772 return ap_pstrcat(cmd->pool,
773 "RewriteRule: cannot compile regular expression '",
776 newrule->pattern = ap_pstrdup(cmd->pool, cp);
777 newrule->regexp = regexp;
779 /* arg2: the output string
780 * replace the $<N> by \<n> which is needed by the currently
781 * used Regular Expression library
783 newrule->output = ap_pstrdup(cmd->pool, a2);
785 /* now, if the server or per-dir config holds an
786 * array of RewriteCond entries, we take it for us
787 * and clear the array
789 if (cmd->path == NULL) { /* is server command */
790 newrule->rewriteconds = sconf->rewriteconds;
791 sconf->rewriteconds = ap_make_array(cmd->pool, 2,
792 sizeof(rewritecond_entry));
794 else { /* is per-directory command */
795 newrule->rewriteconds = dconf->rewriteconds;
796 dconf->rewriteconds = ap_make_array(cmd->pool, 2,
797 sizeof(rewritecond_entry));
803 static const char *cmd_rewriterule_parseflagfield(ap_pool_t *p,
804 rewriterule_entry *cfg,
815 if (str[0] != '[' || str[strlen(str)-1] != ']') {
816 return "RewriteRule: bad flag delimiters";
820 str[strlen(str)-1] = ','; /* for simpler parsing */
821 for ( ; *cp != '\0'; ) {
822 /* skip whitespaces */
823 for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++)
829 if ((cp2 = strchr(cp, ',')) != NULL) {
831 for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--)
834 if ((cp3 = strchr(cp1, '=')) != NULL) {
843 if ((err = cmd_rewriterule_setflag(p, cfg, key, val)) != NULL) {
855 static const char *cmd_rewriterule_setflag(ap_pool_t *p, rewriterule_entry *cfg,
856 char *key, char *val)
861 if ( strcasecmp(key, "redirect") == 0
862 || strcasecmp(key, "R") == 0 ) {
863 cfg->flags |= RULEFLAG_FORCEREDIRECT;
864 if (strlen(val) > 0) {
865 if (strcasecmp(val, "permanent") == 0) {
866 status = HTTP_MOVED_PERMANENTLY;
868 else if (strcasecmp(val, "temp") == 0) {
869 status = HTTP_MOVED_TEMPORARILY;
871 else if (strcasecmp(val, "seeother") == 0) {
872 status = HTTP_SEE_OTHER;
874 else if (ap_isdigit(*val)) {
877 if (!ap_is_HTTP_REDIRECT(status)) {
878 return "RewriteRule: invalid HTTP response code "
881 cfg->forced_responsecode = status;
884 else if ( strcasecmp(key, "last") == 0
885 || strcasecmp(key, "L") == 0 ) {
886 cfg->flags |= RULEFLAG_LASTRULE;
888 else if ( strcasecmp(key, "next") == 0
889 || strcasecmp(key, "N") == 0 ) {
890 cfg->flags |= RULEFLAG_NEWROUND;
892 else if ( strcasecmp(key, "chain") == 0
893 || strcasecmp(key, "C") == 0 ) {
894 cfg->flags |= RULEFLAG_CHAIN;
896 else if ( strcasecmp(key, "type") == 0
897 || strcasecmp(key, "T") == 0 ) {
898 cfg->forced_mimetype = ap_pstrdup(p, val);
899 ap_str_tolower(cfg->forced_mimetype);
901 else if ( strcasecmp(key, "env") == 0
902 || strcasecmp(key, "E") == 0 ) {
903 for (i = 0; (cfg->env[i] != NULL) && (i < MAX_ENV_FLAGS); i++)
905 if (i < MAX_ENV_FLAGS) {
906 cfg->env[i] = ap_pstrdup(p, val);
907 cfg->env[i+1] = NULL;
910 return "RewriteRule: too many environment flags 'E'";
913 else if ( strcasecmp(key, "nosubreq") == 0
914 || strcasecmp(key, "NS") == 0 ) {
915 cfg->flags |= RULEFLAG_IGNOREONSUBREQ;
917 else if ( strcasecmp(key, "proxy") == 0
918 || strcasecmp(key, "P") == 0 ) {
919 cfg->flags |= RULEFLAG_PROXY;
921 else if ( strcasecmp(key, "passthrough") == 0
922 || strcasecmp(key, "PT") == 0 ) {
923 cfg->flags |= RULEFLAG_PASSTHROUGH;
925 else if ( strcasecmp(key, "skip") == 0
926 || strcasecmp(key, "S") == 0 ) {
927 cfg->skip = atoi(val);
929 else if ( strcasecmp(key, "forbidden") == 0
930 || strcasecmp(key, "F") == 0 ) {
931 cfg->flags |= RULEFLAG_FORBIDDEN;
933 else if ( strcasecmp(key, "gone") == 0
934 || strcasecmp(key, "G") == 0 ) {
935 cfg->flags |= RULEFLAG_GONE;
937 else if ( strcasecmp(key, "qsappend") == 0
938 || strcasecmp(key, "QSA") == 0 ) {
939 cfg->flags |= RULEFLAG_QSAPPEND;
941 else if ( strcasecmp(key, "nocase") == 0
942 || strcasecmp(key, "NC") == 0 ) {
943 cfg->flags |= RULEFLAG_NOCASE;
946 return ap_pstrcat(p, "RewriteRule: unknown flag '", key, "'\n", NULL);
954 ** Global Module Initialization
955 ** [called from read_config() after all
956 ** config commands were already called]
960 static void init_module(ap_pool_t *p,
965 /* check if proxy module is available */
966 proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL);
968 /* create the rewriting lockfiles in the parent */
969 if (ap_create_lock (&rewrite_log_lock, APR_MUTEX, APR_INTRAPROCESS,
970 NULL, NULL) != APR_SUCCESS)
971 exit(1); /* ugly but I can't log anything yet. This is what */
972 /* the pre-existing rewritelock_create code did. */
974 rewritelock_create(s, p);
975 ap_register_cleanup(p, (void *)s, rewritelock_remove, ap_null_cleanup);
977 /* step through the servers and
978 * - open each rewriting logfile
979 * - open the RewriteMap prg:xxx programs
981 for (; s; s = s->next) {
982 open_rewritelog(s, p);
983 if (once_through > 0)
984 run_rewritemap_programs(s, p);
993 ** Per-Child Module Initialization
994 ** [called after a child process is spawned]
998 static void init_child(ap_pool_t *p, server_rec *s)
1001 if (lockname != NULL && *(lockname) != '\0')
1002 ap_child_init_lock (&rewrite_map_lock, lockname, p);
1004 /* create the lookup cache */
1005 cachep = init_cache(p);
1010 ** +-------------------------------------------------------+
1014 ** +-------------------------------------------------------+
1019 ** URI-to-filename hook
1021 ** [used for the rewriting engine triggered by
1022 ** the per-server 'RewriteRule' directives]
1026 static int hook_uri2file(request_rec *r)
1029 rewrite_server_conf *conf;
1031 const char *thisserver;
1033 const char *thisurl;
1044 * retrieve the config structures
1046 sconf = r->server->module_config;
1047 conf = (rewrite_server_conf *)ap_get_module_config(sconf,
1051 * only do something under runtime if the engine is really enabled,
1052 * else return immediately!
1054 if (conf->state == ENGINE_DISABLED) {
1059 * check for the ugly API case of a virtual host section where no
1060 * mod_rewrite directives exists. In this situation we became no chance
1061 * by the API to setup our default per-server config so we have to
1062 * on-the-fly assume we have the default config. But because the default
1063 * config has a disabled rewriting engine we are lucky because can
1064 * just stop operating now.
1066 if (conf->server != r->server) {
1071 * add the SCRIPT_URL variable to the env. this is a bit complicated
1072 * due to the fact that apache uses subrequests and internal redirects
1075 if (r->main == NULL) {
1076 var = ap_pstrcat(r->pool, "REDIRECT_", ENVVAR_SCRIPT_URL, NULL);
1077 var = ap_table_get(r->subprocess_env, var);
1079 ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, r->uri);
1082 ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
1086 var = ap_table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL);
1087 ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
1091 * create the SCRIPT_URI variable for the env
1094 /* add the canonical URI of this URL */
1095 thisserver = ap_get_server_name(r);
1096 port = ap_get_server_port(r);
1097 if (ap_is_default_port(port, r)) {
1101 ap_snprintf(buf, sizeof(buf), ":%u", port);
1104 thisurl = ap_table_get(r->subprocess_env, ENVVAR_SCRIPT_URL);
1106 /* set the variable */
1107 var = ap_pstrcat(r->pool, ap_http_method(r), "://", thisserver, thisport,
1109 ap_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URI, var);
1111 /* if filename was not initially set,
1112 * we start with the requested URI
1114 if (r->filename == NULL) {
1115 r->filename = ap_pstrdup(r->pool, r->uri);
1116 rewritelog(r, 2, "init rewrite engine with requested uri %s",
1121 * now apply the rules ...
1123 if (apply_rewrite_list(r, conf->rewriterules, NULL)) {
1125 if (strlen(r->filename) > 6 &&
1126 strncmp(r->filename, "proxy:", 6) == 0) {
1127 /* it should be go on as an internal proxy request */
1129 /* check if the proxy module is enabled, so
1130 * we can actually use it!
1132 if (!proxy_available) {
1133 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1134 "attempt to make remote request from mod_rewrite "
1135 "without proxy enabled: %s", r->filename);
1139 /* make sure the QUERY_STRING and
1140 * PATH_INFO parts get incorporated
1142 if (r->path_info != NULL) {
1143 r->filename = ap_pstrcat(r->pool, r->filename,
1144 r->path_info, NULL);
1146 if (r->args != NULL &&
1147 r->uri == r->unparsed_uri) {
1148 /* see proxy_http:proxy_http_canon() */
1149 r->filename = ap_pstrcat(r->pool, r->filename,
1150 "?", r->args, NULL);
1153 /* now make sure the request gets handled by the proxy handler */
1155 r->handler = "proxy-server";
1157 rewritelog(r, 1, "go-ahead with proxy request %s [OK]",
1161 else if ( (strlen(r->filename) > 7 &&
1162 strncasecmp(r->filename, "http://", 7) == 0)
1163 || (strlen(r->filename) > 8 &&
1164 strncasecmp(r->filename, "https://", 8) == 0)
1165 || (strlen(r->filename) > 9 &&
1166 strncasecmp(r->filename, "gopher://", 9) == 0)
1167 || (strlen(r->filename) > 6 &&
1168 strncasecmp(r->filename, "ftp://", 6) == 0)
1169 || (strlen(r->filename) > 5 &&
1170 strncasecmp(r->filename, "ldap:", 5) == 0)
1171 || (strlen(r->filename) > 5 &&
1172 strncasecmp(r->filename, "news:", 5) == 0)
1173 || (strlen(r->filename) > 7 &&
1174 strncasecmp(r->filename, "mailto:", 7) == 0)) {
1175 /* it was finally rewritten to a remote URL */
1177 /* skip 'scheme:' */
1178 for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1182 /* skip host part */
1183 for ( ; *cp != '/' && *cp != '\0'; cp++)
1186 rewritelog(r, 1, "escaping %s for redirect", r->filename);
1187 cp2 = ap_escape_uri(r->pool, cp);
1189 r->filename = ap_pstrcat(r->pool, r->filename, cp2, NULL);
1192 /* append the QUERY_STRING part */
1193 if (r->args != NULL) {
1194 r->filename = ap_pstrcat(r->pool, r->filename, "?",
1195 ap_escape_uri(r->pool, r->args), NULL);
1198 /* determine HTTP redirect response code */
1199 if (ap_is_HTTP_REDIRECT(r->status)) {
1201 r->status = HTTP_OK; /* make Apache kernel happy */
1207 /* now do the redirection */
1208 ap_table_setn(r->headers_out, "Location", r->filename);
1209 rewritelog(r, 1, "redirect to %s [REDIRECT/%d]", r->filename, n);
1212 else if (strlen(r->filename) > 10 &&
1213 strncmp(r->filename, "forbidden:", 10) == 0) {
1214 /* This URLs is forced to be forbidden for the requester */
1217 else if (strlen(r->filename) > 5 &&
1218 strncmp(r->filename, "gone:", 5) == 0) {
1219 /* This URLs is forced to be gone */
1222 else if (strlen(r->filename) > 12 &&
1223 strncmp(r->filename, "passthrough:", 12) == 0) {
1225 * Hack because of underpowered API: passing the current
1226 * rewritten filename through to other URL-to-filename handlers
1227 * just as it were the requested URL. This is to enable
1228 * post-processing by mod_alias, etc. which always act on
1229 * r->uri! The difference here is: We do not try to
1230 * add the document root
1232 r->uri = ap_pstrdup(r->pool, r->filename+12);
1236 /* it was finally rewritten to a local path */
1238 /* expand "/~user" prefix */
1239 #if !defined(WIN32) && !defined(NETWARE)
1240 r->filename = expand_tildepaths(r, r->filename);
1242 rewritelog(r, 2, "local path result: %s", r->filename);
1244 /* the filename has to start with a slash! */
1245 if (r->filename[0] != '/') {
1249 /* if there is no valid prefix, we have
1250 * to emulate the translator from the core and
1251 * prefix the filename with document_root
1254 * We cannot leave out the prefix_stat because
1255 * - when we always prefix with document_root
1256 * then no absolute path can be created, e.g. via
1257 * emulating a ScriptAlias directive, etc.
1258 * - when we always NOT prefix with document_root
1259 * then the files under document_root have to
1260 * be references directly and document_root
1261 * gets never used and will be a dummy parameter -
1265 * Under real Unix systems this is no problem,
1266 * because we only do stat() on the first directory
1267 * and this gets cached by the kernel for along time!
1269 n = prefix_stat(r->filename, &finfo);
1271 if ((ccp = ap_document_root(r)) != NULL) {
1272 l = ap_cpystrn(docroot, ccp, sizeof(docroot)) - docroot;
1274 /* always NOT have a trailing slash */
1275 if (docroot[l-1] == '/') {
1276 docroot[l-1] = '\0';
1279 && !strncmp(r->filename, r->server->path,
1280 r->server->pathlen)) {
1281 r->filename = ap_pstrcat(r->pool, docroot,
1283 r->server->pathlen), NULL);
1286 r->filename = ap_pstrcat(r->pool, docroot,
1289 rewritelog(r, 2, "prefixed with document_root to %s",
1294 rewritelog(r, 1, "go-ahead with %s [OK]", r->filename);
1299 rewritelog(r, 1, "pass through %s", r->filename);
1309 ** [used to support the forced-MIME-type feature]
1313 static int hook_mimetype(request_rec *r)
1317 /* now check if we have to force a MIME-type */
1318 t = ap_table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR);
1323 rewritelog(r, 1, "force filename %s to have MIME-type '%s'",
1325 r->content_type = t;
1335 ** [used for the rewriting engine triggered by
1336 ** the per-directory 'RewriteRule' directives]
1340 static int hook_fixup(request_rec *r)
1342 rewrite_perdir_conf *dconf;
1351 dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
1354 /* if there is no per-dir config we return immediately */
1355 if (dconf == NULL) {
1359 /* we shouldn't do anything in subrequests */
1360 if (r->main != NULL) {
1364 /* if there are no real (i.e. no RewriteRule directives!)
1365 per-dir config of us, we return also immediately */
1366 if (dconf->directory == NULL) {
1371 * only do something under runtime if the engine is really enabled,
1372 * for this directory, else return immediately!
1374 if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) {
1375 /* FollowSymLinks is mandatory! */
1376 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1377 "Options FollowSymLinks or SymLinksIfOwnerMatch is off "
1378 "which implies that RewriteRule directive is forbidden: "
1383 /* FollowSymLinks is given, but the user can
1384 * still turn off the rewriting engine
1386 if (dconf->state == ENGINE_DISABLED) {
1392 * remember the current filename before rewriting for later check
1393 * to prevent deadlooping because of internal redirects
1394 * on final URL/filename which can be equal to the inital one.
1396 ofilename = r->filename;
1399 * now apply the rules ...
1401 if (apply_rewrite_list(r, dconf->rewriterules, dconf->directory)) {
1403 if (strlen(r->filename) > 6 &&
1404 strncmp(r->filename, "proxy:", 6) == 0) {
1405 /* it should go on as an internal proxy request */
1407 /* make sure the QUERY_STRING and
1408 * PATH_INFO parts get incorporated
1409 * (r->path_info was already appended by the
1410 * rewriting engine because of the per-dir context!)
1412 if (r->args != NULL) {
1413 r->filename = ap_pstrcat(r->pool, r->filename,
1414 "?", r->args, NULL);
1417 /* now make sure the request gets handled by the proxy handler */
1419 r->handler = "proxy-server";
1421 rewritelog(r, 1, "[per-dir %s] go-ahead with proxy request "
1422 "%s [OK]", dconf->directory, r->filename);
1425 else if ( (strlen(r->filename) > 7 &&
1426 strncasecmp(r->filename, "http://", 7) == 0)
1427 || (strlen(r->filename) > 8 &&
1428 strncasecmp(r->filename, "https://", 8) == 0)
1429 || (strlen(r->filename) > 9 &&
1430 strncasecmp(r->filename, "gopher://", 9) == 0)
1431 || (strlen(r->filename) > 6 &&
1432 strncasecmp(r->filename, "ftp://", 6) == 0)
1433 || (strlen(r->filename) > 5 &&
1434 strncasecmp(r->filename, "ldap:", 5) == 0)
1435 || (strlen(r->filename) > 5 &&
1436 strncasecmp(r->filename, "news:", 5) == 0)
1437 || (strlen(r->filename) > 7 &&
1438 strncasecmp(r->filename, "mailto:", 7) == 0)) {
1439 /* it was finally rewritten to a remote URL */
1441 /* because we are in a per-dir context
1442 * first try to replace the directory with its base-URL
1443 * if there is a base-URL available
1445 if (dconf->baseurl != NULL) {
1446 /* skip 'scheme:' */
1447 for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1451 if ((cp = strchr(cp, '/')) != NULL) {
1453 "[per-dir %s] trying to replace "
1454 "prefix %s with %s",
1455 dconf->directory, dconf->directory,
1457 cp2 = subst_prefix_path(r, cp, dconf->directory,
1459 if (strcmp(cp2, cp) != 0) {
1461 r->filename = ap_pstrcat(r->pool, r->filename,
1467 /* now prepare the redirect... */
1469 /* skip 'scheme:' */
1470 for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1474 /* skip host part */
1475 for ( ; *cp != '/' && *cp != '\0'; cp++)
1478 rewritelog(r, 1, "[per-dir %s] escaping %s for redirect",
1479 dconf->directory, r->filename);
1480 cp2 = ap_escape_uri(r->pool, cp);
1482 r->filename = ap_pstrcat(r->pool, r->filename, cp2, NULL);
1485 /* append the QUERY_STRING part */
1486 if (r->args != NULL) {
1487 r->filename = ap_pstrcat(r->pool, r->filename, "?",
1488 ap_escape_uri(r->pool, r->args), NULL);
1491 /* determine HTTP redirect response code */
1492 if (ap_is_HTTP_REDIRECT(r->status)) {
1494 r->status = HTTP_OK; /* make Apache kernel happy */
1500 /* now do the redirection */
1501 ap_table_setn(r->headers_out, "Location", r->filename);
1502 rewritelog(r, 1, "[per-dir %s] redirect to %s [REDIRECT/%d]",
1503 dconf->directory, r->filename, n);
1506 else if (strlen(r->filename) > 10 &&
1507 strncmp(r->filename, "forbidden:", 10) == 0) {
1508 /* This URL is forced to be forbidden for the requester */
1511 else if (strlen(r->filename) > 5 &&
1512 strncmp(r->filename, "gone:", 5) == 0) {
1513 /* This URL is forced to be gone */
1517 /* it was finally rewritten to a local path */
1519 /* if someone used the PASSTHROUGH flag in per-dir
1520 * context we just ignore it. It is only useful
1521 * in per-server context
1523 if (strlen(r->filename) > 12 &&
1524 strncmp(r->filename, "passthrough:", 12) == 0) {
1525 r->filename = ap_pstrdup(r->pool, r->filename+12);
1528 /* the filename has to start with a slash! */
1529 if (r->filename[0] != '/') {
1533 /* Check for deadlooping:
1534 * At this point we KNOW that at least one rewriting
1535 * rule was applied, but when the resulting URL is
1536 * the same as the initial URL, we are not allowed to
1537 * use the following internal redirection stuff because
1538 * this would lead to a deadloop.
1540 if (strcmp(r->filename, ofilename) == 0) {
1541 rewritelog(r, 1, "[per-dir %s] initial URL equal rewritten "
1542 "URL: %s [IGNORING REWRITE]",
1543 dconf->directory, r->filename);
1547 /* if there is a valid base-URL then substitute
1548 * the per-dir prefix with this base-URL if the
1549 * current filename still is inside this per-dir
1550 * context. If not then treat the result as a
1553 if (dconf->baseurl != NULL) {
1555 "[per-dir %s] trying to replace prefix %s with %s",
1556 dconf->directory, dconf->directory, dconf->baseurl);
1557 r->filename = subst_prefix_path(r, r->filename,
1562 /* if no explicit base-URL exists we assume
1563 * that the directory prefix is also a valid URL
1564 * for this webserver and only try to remove the
1565 * document_root if it is prefix
1567 if ((ccp = ap_document_root(r)) != NULL) {
1568 prefix = ap_pstrdup(r->pool, ccp);
1569 /* always NOT have a trailing slash */
1571 if (prefix[l-1] == '/') {
1575 if (strncmp(r->filename, prefix, l) == 0) {
1577 "[per-dir %s] strip document_root "
1579 dconf->directory, r->filename,
1581 r->filename = ap_pstrdup(r->pool, r->filename+l);
1586 /* now initiate the internal redirect */
1587 rewritelog(r, 1, "[per-dir %s] internal redirect with %s "
1588 "[INTERNAL REDIRECT]", dconf->directory, r->filename);
1589 r->filename = ap_pstrcat(r->pool, "redirect:", r->filename, NULL);
1590 r->handler = "redirect-handler";
1595 rewritelog(r, 1, "[per-dir %s] pass through %s",
1596 dconf->directory, r->filename);
1606 ** [used for redirect support]
1610 static int handler_redirect(request_rec *r)
1612 /* just make sure that we are really meant! */
1613 if (strncmp(r->filename, "redirect:", 9) != 0) {
1617 /* now do the internal redirect */
1618 ap_internal_redirect(ap_pstrcat(r->pool, r->filename+9,
1619 r->args ? "?" : NULL, r->args, NULL), r);
1621 /* and return gracefully */
1627 ** +-------------------------------------------------------+
1629 ** | the rewriting engine
1631 ** +-------------------------------------------------------+
1635 * Apply a complete rule set,
1636 * i.e. a list of rewrite rules
1638 static int apply_rewrite_list(request_rec *r, ap_array_header_t *rewriterules,
1641 rewriterule_entry *entries;
1642 rewriterule_entry *p;
1649 * Iterate over all existing rules
1651 entries = (rewriterule_entry *)rewriterules->elts;
1654 for (i = 0; i < rewriterules->nelts; i++) {
1658 * Ignore this rule on subrequests if we are explicitly
1659 * asked to do so or this is a proxy-throughput or a
1660 * forced redirect rule.
1662 if (r->main != NULL &&
1663 (p->flags & RULEFLAG_IGNOREONSUBREQ ||
1664 p->flags & RULEFLAG_PROXY ||
1665 p->flags & RULEFLAG_FORCEREDIRECT )) {
1670 * Apply the current rule.
1672 rc = apply_rewrite_rule(r, p, perdir);
1675 * Indicate a change if this was not a match-only rule.
1682 * Pass-Through Feature (`RewriteRule .. .. [PT]'):
1683 * Because the Apache 1.x API is very limited we
1684 * need this hack to pass the rewritten URL to other
1685 * modules like mod_alias, mod_userdir, etc.
1687 if (p->flags & RULEFLAG_PASSTHROUGH) {
1688 rewritelog(r, 2, "forcing '%s' to get passed through "
1689 "to next API URI-to-filename handler", r->filename);
1690 r->filename = ap_pstrcat(r->pool, "passthrough:",
1697 * Rule has the "forbidden" flag set which means that
1698 * we stop processing and indicate this to the caller.
1700 if (p->flags & RULEFLAG_FORBIDDEN) {
1701 rewritelog(r, 2, "forcing '%s' to be forbidden", r->filename);
1702 r->filename = ap_pstrcat(r->pool, "forbidden:",
1709 * Rule has the "gone" flag set which means that
1710 * we stop processing and indicate this to the caller.
1712 if (p->flags & RULEFLAG_GONE) {
1713 rewritelog(r, 2, "forcing '%s' to be gone", r->filename);
1714 r->filename = ap_pstrcat(r->pool, "gone:", r->filename, NULL);
1720 * Stop processing also on proxy pass-through and
1721 * last-rule and new-round flags.
1723 if (p->flags & RULEFLAG_PROXY) {
1726 if (p->flags & RULEFLAG_LASTRULE) {
1731 * On "new-round" flag we just start from the top of
1732 * the rewriting ruleset again.
1734 if (p->flags & RULEFLAG_NEWROUND) {
1739 * If we are forced to skip N next rules, do it now.
1743 while ( i < rewriterules->nelts
1753 * If current rule is chained with next rule(s),
1754 * skip all this next rule(s)
1756 while ( i < rewriterules->nelts
1757 && p->flags & RULEFLAG_CHAIN) {
1767 * Apply a single(!) rewrite rule
1769 static int apply_rewrite_rule(request_rec *r, rewriterule_entry *p,
1775 char newuri[MAX_STRING_LEN];
1776 char env[MAX_STRING_LEN];
1778 regmatch_t regmatch[MAX_NMATCH];
1779 backrefinfo *briRR = NULL;
1780 backrefinfo *briRC = NULL;
1783 ap_array_header_t *rewriteconds;
1784 rewritecond_entry *conds;
1785 rewritecond_entry *c;
1797 * Add (perhaps splitted away) PATH_INFO postfix to URL to
1798 * make sure we really match against the complete URL.
1800 if (perdir != NULL && r->path_info != NULL && r->path_info[0] != '\0') {
1801 rewritelog(r, 3, "[per-dir %s] add path info postfix: %s -> %s%s",
1802 perdir, uri, uri, r->path_info);
1803 uri = ap_pstrcat(r->pool, uri, r->path_info, NULL);
1807 * On per-directory context (.htaccess) strip the location
1808 * prefix from the URL to make sure patterns apply only to
1809 * the local part. Additionally indicate this special
1810 * threatment in the logfile.
1813 if (perdir != NULL) {
1814 if ( strlen(uri) >= strlen(perdir)
1815 && strncmp(uri, perdir, strlen(perdir)) == 0) {
1816 rewritelog(r, 3, "[per-dir %s] strip per-dir prefix: %s -> %s",
1817 perdir, uri, uri+strlen(perdir));
1818 uri = uri+strlen(perdir);
1824 * Try to match the URI against the RewriteRule pattern
1825 * and exit immeddiately if it didn't apply.
1827 if (perdir == NULL) {
1828 rewritelog(r, 3, "applying pattern '%s' to uri '%s'",
1832 rewritelog(r, 3, "[per-dir %s] applying pattern '%s' to uri '%s'",
1833 perdir, p->pattern, uri);
1835 rc = (ap_regexec(regexp, uri, regexp->re_nsub+1, regmatch, 0) == 0);
1836 if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) ||
1837 (!rc && (p->flags & RULEFLAG_NOTMATCH)) ) ) {
1842 * Else create the RewriteRule `regsubinfo' structure which
1843 * holds the substitution information.
1845 briRR = (backrefinfo *)ap_palloc(r->pool, sizeof(backrefinfo));
1846 if (!rc && (p->flags & RULEFLAG_NOTMATCH)) {
1847 /* empty info on negative patterns */
1852 briRR->source = ap_pstrdup(r->pool, uri);
1853 briRR->nsub = regexp->re_nsub;
1854 memcpy((void *)(briRR->regmatch), (void *)(regmatch),
1859 * Initiallally create the RewriteCond backrefinfo with
1860 * empty backrefinfo, i.e. not subst parts
1861 * (this one is adjusted inside apply_rewrite_cond() later!!)
1863 briRC = (backrefinfo *)ap_pcalloc(r->pool, sizeof(backrefinfo));
1868 * Ok, we already know the pattern has matched, but we now
1869 * additionally have to check for all existing preconditions
1870 * (RewriteCond) which have to be also true. We do this at
1871 * this very late stage to avoid unnessesary checks which
1872 * would slow down the rewriting engine!!
1874 rewriteconds = p->rewriteconds;
1875 conds = (rewritecond_entry *)rewriteconds->elts;
1877 for (i = 0; i < rewriteconds->nelts; i++) {
1879 rc = apply_rewrite_cond(r, c, perdir, briRR, briRC);
1880 if (c->flags & CONDFLAG_ORNEXT) {
1885 /* One condition is false, but another can be
1886 * still true, so we have to continue...
1888 ap_table_unset(r->notes, VARY_KEY_THIS);
1892 /* One true condition is enough in "or" case, so
1893 * skip the other conditions which are "ornext"
1896 while ( i < rewriteconds->nelts
1897 && c->flags & CONDFLAG_ORNEXT) {
1906 * The "AND" case, i.e. no "or" flag,
1907 * so a single failure means total failure.
1914 vary = ap_table_get(r->notes, VARY_KEY_THIS);
1916 ap_table_merge(r->notes, VARY_KEY, vary);
1917 ap_table_unset(r->notes, VARY_KEY_THIS);
1920 /* if any condition fails the complete rule fails */
1922 ap_table_unset(r->notes, VARY_KEY);
1923 ap_table_unset(r->notes, VARY_KEY_THIS);
1928 * Regardless of what we do next, we've found a match. Check to see
1929 * if any of the request header fields were involved, and add them
1930 * to the Vary field of the response.
1932 if ((vary = ap_table_get(r->notes, VARY_KEY)) != NULL) {
1933 ap_table_merge(r->headers_out, "Vary", vary);
1934 ap_table_unset(r->notes, VARY_KEY);
1938 * If this is a pure matching rule (`RewriteRule <pat> -')
1939 * we stop processing and return immediately. The only thing
1940 * we have not to forget are the environment variables
1941 * (`RewriteRule <pat> - [E=...]')
1943 if (strcmp(output, "-") == 0) {
1944 for (i = 0; p->env[i] != NULL; i++) {
1945 /* 1. take the string */
1946 ap_cpystrn(env, p->env[i], sizeof(env));
1947 /* 2. expand $N (i.e. backrefs to RewriteRule pattern) */
1948 expand_backref_inbuffer(r->pool, env, sizeof(env), briRR, '$');
1949 /* 3. expand %N (i.e. backrefs to latest RewriteCond pattern) */
1950 expand_backref_inbuffer(r->pool, env, sizeof(env), briRC, '%');
1951 /* 4. expand %{...} (i.e. variables) */
1952 expand_variables_inbuffer(r, env, sizeof(env));
1953 /* 5. expand ${...} (RewriteMap lookups) */
1954 expand_map_lookups(r, env, sizeof(env));
1955 /* and add the variable to Apache's structures */
1956 add_env_variable(r, env);
1958 if (p->forced_mimetype != NULL) {
1959 if (perdir == NULL) {
1960 /* In the per-server context we can force the MIME-type
1961 * the correct way by notifying our MIME-type hook handler
1962 * to do the job when the MIME-type API stage is reached.
1964 rewritelog(r, 2, "remember %s to have MIME-type '%s'",
1965 r->filename, p->forced_mimetype);
1966 ap_table_setn(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
1967 p->forced_mimetype);
1970 /* In per-directory context we operate in the Fixup API hook
1971 * which is after the MIME-type hook, so our MIME-type handler
1972 * has no chance to set r->content_type. And because we are
1973 * in the situation where no substitution takes place no
1974 * sub-request will happen (which could solve the
1975 * restriction). As a workaround we do it ourself now
1976 * immediately although this is not strictly API-conforming.
1977 * But it's the only chance we have...
1979 rewritelog(r, 1, "[per-dir %s] force %s to have MIME-type "
1980 "'%s'", perdir, r->filename, p->forced_mimetype);
1981 r->content_type = p->forced_mimetype;
1988 * Ok, now we finally know all patterns have matched and
1989 * that there is something to replace, so we create the
1990 * substitution URL string in `newuri'.
1992 /* 1. take the output string */
1993 ap_cpystrn(newuri, output, sizeof(newuri));
1994 /* 2. expand $N (i.e. backrefs to RewriteRule pattern) */
1995 expand_backref_inbuffer(r->pool, newuri, sizeof(newuri), briRR, '$');
1996 /* 3. expand %N (i.e. backrefs to latest RewriteCond pattern) */
1997 expand_backref_inbuffer(r->pool, newuri, sizeof(newuri), briRC, '%');
1998 /* 4. expand %{...} (i.e. variables) */
1999 expand_variables_inbuffer(r, newuri, sizeof(newuri));
2000 /* 5. expand ${...} (RewriteMap lookups) */
2001 expand_map_lookups(r, newuri, sizeof(newuri));
2002 /* and log the result... */
2003 if (perdir == NULL) {
2004 rewritelog(r, 2, "rewrite %s -> %s", uri, newuri);
2007 rewritelog(r, 2, "[per-dir %s] rewrite %s -> %s", perdir, uri, newuri);
2011 * Additionally do expansion for the environment variable
2012 * strings (`RewriteRule .. .. [E=<string>]').
2014 for (i = 0; p->env[i] != NULL; i++) {
2015 /* 1. take the string */
2016 ap_cpystrn(env, p->env[i], sizeof(env));
2017 /* 2. expand $N (i.e. backrefs to RewriteRule pattern) */
2018 expand_backref_inbuffer(r->pool, env, sizeof(env), briRR, '$');
2019 /* 3. expand %N (i.e. backrefs to latest RewriteCond pattern) */
2020 expand_backref_inbuffer(r->pool, env, sizeof(env), briRC, '%');
2021 /* 4. expand %{...} (i.e. variables) */
2022 expand_variables_inbuffer(r, env, sizeof(env));
2023 /* 5. expand ${...} (RewriteMap lookups) */
2024 expand_map_lookups(r, env, sizeof(env));
2025 /* and add the variable to Apache's structures */
2026 add_env_variable(r, env);
2030 * Now replace API's knowledge of the current URI:
2031 * Replace r->filename with the new URI string and split out
2032 * an on-the-fly generated QUERY_STRING part into r->args
2034 r->filename = ap_pstrdup(r->pool, newuri);
2035 splitout_queryargs(r, p->flags & RULEFLAG_QSAPPEND);
2038 * Again add the previously stripped per-directory location
2039 * prefix if the new URI is not a new one for this
2040 * location, i.e. if it's not starting with either a slash
2041 * or a fully qualified URL scheme.
2043 i = strlen(r->filename);
2045 && !( r->filename[0] == '/'
2046 || ( (i > 7 && strncasecmp(r->filename, "http://", 7) == 0)
2047 || (i > 8 && strncasecmp(r->filename, "https://", 8) == 0)
2048 || (i > 9 && strncasecmp(r->filename, "gopher://", 9) == 0)
2049 || (i > 6 && strncasecmp(r->filename, "ftp://", 6) == 0)
2050 || (i > 5 && strncasecmp(r->filename, "ldap:", 5) == 0)
2051 || (i > 5 && strncasecmp(r->filename, "news:", 5) == 0)
2052 || (i > 7 && strncasecmp(r->filename, "mailto:", 7) == 0)))) {
2053 rewritelog(r, 3, "[per-dir %s] add per-dir prefix: %s -> %s%s",
2054 perdir, r->filename, perdir, r->filename);
2055 r->filename = ap_pstrcat(r->pool, perdir, r->filename, NULL);
2059 * If this rule is forced for proxy throughput
2060 * (`RewriteRule ... ... [P]') then emulate mod_proxy's
2061 * URL-to-filename handler to be sure mod_proxy is triggered
2062 * for this URL later in the Apache API. But make sure it is
2063 * a fully-qualified URL. (If not it is qualified with
2066 if (p->flags & RULEFLAG_PROXY) {
2067 fully_qualify_uri(r);
2068 if (perdir == NULL) {
2069 rewritelog(r, 2, "forcing proxy-throughput with %s", r->filename);
2072 rewritelog(r, 2, "[per-dir %s] forcing proxy-throughput with %s",
2073 perdir, r->filename);
2075 r->filename = ap_pstrcat(r->pool, "proxy:", r->filename, NULL);
2080 * If this rule is explicitly forced for HTTP redirection
2081 * (`RewriteRule .. .. [R]') then force an external HTTP
2082 * redirect. But make sure it is a fully-qualified URL. (If
2083 * not it is qualified with ourself).
2085 if (p->flags & RULEFLAG_FORCEREDIRECT) {
2086 fully_qualify_uri(r);
2087 if (perdir == NULL) {
2089 "explicitly forcing redirect with %s", r->filename);
2093 "[per-dir %s] explicitly forcing redirect with %s",
2094 perdir, r->filename);
2096 r->status = p->forced_responsecode;
2101 * Special Rewriting Feature: Self-Reduction
2102 * We reduce the URL by stripping a possible
2103 * http[s]://<ourhost>[:<port>] prefix, i.e. a prefix which
2104 * corresponds to ourself. This is to simplify rewrite maps
2105 * and to avoid recursion, etc. When this prefix is not a
2106 * coincidence then the user has to use [R] explicitly (see
2112 * If this rule is still implicitly forced for HTTP
2113 * redirection (`RewriteRule .. <scheme>://...') then
2114 * directly force an external HTTP redirect.
2116 i = strlen(r->filename);
2117 if ( (i > 7 && strncasecmp(r->filename, "http://", 7) == 0)
2118 || (i > 8 && strncasecmp(r->filename, "https://", 8) == 0)
2119 || (i > 9 && strncasecmp(r->filename, "gopher://", 9) == 0)
2120 || (i > 6 && strncasecmp(r->filename, "ftp://", 6) == 0)
2121 || (i > 5 && strncasecmp(r->filename, "ldap:", 5) == 0)
2122 || (i > 5 && strncasecmp(r->filename, "news:", 5) == 0)
2123 || (i > 7 && strncasecmp(r->filename, "mailto:", 7) == 0) ) {
2124 if (perdir == NULL) {
2126 "implicitly forcing redirect (rc=%d) with %s",
2127 p->forced_responsecode, r->filename);
2130 rewritelog(r, 2, "[per-dir %s] implicitly forcing redirect "
2131 "(rc=%d) with %s", perdir, p->forced_responsecode,
2134 r->status = p->forced_responsecode;
2139 * Now we are sure it is not a fully qualified URL. But
2140 * there is still one special case left: A local rewrite in
2141 * per-directory context, i.e. a substitution URL which does
2142 * not start with a slash. Here we add again the initially
2143 * stripped per-directory prefix.
2145 if (prefixstrip && r->filename[0] != '/') {
2146 rewritelog(r, 3, "[per-dir %s] add per-dir prefix: %s -> %s%s",
2147 perdir, r->filename, perdir, r->filename);
2148 r->filename = ap_pstrcat(r->pool, perdir, r->filename, NULL);
2152 * Finally we had to remember if a MIME-type should be
2153 * forced for this URL (`RewriteRule .. .. [T=<type>]')
2154 * Later in the API processing phase this is forced by our
2155 * MIME API-hook function. This time its no problem even for
2156 * the per-directory context (where the MIME-type hook was
2157 * already processed) because a sub-request happens ;-)
2159 if (p->forced_mimetype != NULL) {
2160 ap_table_setn(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
2161 p->forced_mimetype);
2162 if (perdir == NULL) {
2163 rewritelog(r, 2, "remember %s to have MIME-type '%s'",
2164 r->filename, p->forced_mimetype);
2168 "[per-dir %s] remember %s to have MIME-type '%s'",
2169 perdir, r->filename, p->forced_mimetype);
2174 * Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_)
2175 * But now we're done for this particular rule.
2180 static int apply_rewrite_cond(request_rec *r, rewritecond_entry *p,
2181 char *perdir, backrefinfo *briRR,
2184 char input[MAX_STRING_LEN];
2187 regmatch_t regmatch[MAX_NMATCH];
2191 * Construct the string we match against
2194 /* 1. take the string */
2195 ap_cpystrn(input, p->input, sizeof(input));
2196 /* 2. expand $N (i.e. backrefs to RewriteRule pattern) */
2197 expand_backref_inbuffer(r->pool, input, sizeof(input), briRR, '$');
2198 /* 3. expand %N (i.e. backrefs to latest RewriteCond pattern) */
2199 expand_backref_inbuffer(r->pool, input, sizeof(input), briRC, '%');
2200 /* 4. expand %{...} (i.e. variables) */
2201 expand_variables_inbuffer(r, input, sizeof(input));
2202 /* 5. expand ${...} (RewriteMap lookups) */
2203 expand_map_lookups(r, input, sizeof(input));
2206 * Apply the patterns
2210 if (strcmp(p->pattern, "-f") == 0) {
2211 if (stat(input, &sb) == 0) {
2212 if (S_ISREG(sb.st_mode)) {
2217 else if (strcmp(p->pattern, "-s") == 0) {
2218 if (stat(input, &sb) == 0) {
2219 if (S_ISREG(sb.st_mode) && sb.st_size > 0) {
2224 else if (strcmp(p->pattern, "-l") == 0) {
2225 #if !defined(OS2) && !defined(WIN32)
2226 if (lstat(input, &sb) == 0) {
2227 if (S_ISLNK(sb.st_mode)) {
2233 else if (strcmp(p->pattern, "-d") == 0) {
2234 if (stat(input, &sb) == 0) {
2235 if (S_ISDIR(sb.st_mode)) {
2240 else if (strcmp(p->pattern, "-U") == 0) {
2241 /* avoid infinite subrequest recursion */
2242 if (strlen(input) > 0 /* nonempty path, and */
2243 && ( r->main == NULL /* - either not in a subrequest */
2244 || ( r->main->uri != NULL /* - or in a subrequest... */
2245 && r->uri != NULL /* ...and URIs aren't NULL... */
2246 /* ...and sub/main URIs differ */
2247 && strcmp(r->main->uri, r->uri) != 0) ) ) {
2249 /* run a URI-based subrequest */
2250 rsub = ap_sub_req_lookup_uri(input, r);
2252 /* URI exists for any result up to 3xx, redirects allowed */
2253 if (rsub->status < 400)
2257 rewritelog(r, 5, "RewriteCond URI (-U) check: "
2258 "path=%s -> status=%d", input, rsub->status);
2260 /* cleanup by destroying the subrequest */
2261 ap_destroy_sub_req(rsub);
2264 else if (strcmp(p->pattern, "-F") == 0) {
2265 /* avoid infinite subrequest recursion */
2266 if (strlen(input) > 0 /* nonempty path, and */
2267 && ( r->main == NULL /* - either not in a subrequest */
2268 || ( r->main->uri != NULL /* - or in a subrequest... */
2269 && r->uri != NULL /* ...and URIs aren't NULL... */
2270 /* ...and sub/main URIs differ */
2271 && strcmp(r->main->uri, r->uri) != 0) ) ) {
2273 /* process a file-based subrequest:
2274 * this differs from -U in that no path translation is done.
2276 rsub = ap_sub_req_lookup_file(input, r);
2278 /* file exists for any result up to 2xx, no redirects */
2279 if (rsub->status < 300 &&
2280 /* double-check that file exists since default result is 200 */
2281 stat(rsub->filename, &sb) == 0) {
2286 rewritelog(r, 5, "RewriteCond file (-F) check: path=%s "
2287 "-> file=%s status=%d", input, rsub->filename,
2290 /* cleanup by destroying the subrequest */
2291 ap_destroy_sub_req(rsub);
2294 else if (strlen(p->pattern) > 1 && *(p->pattern) == '>') {
2295 rc = (compare_lexicography(input, p->pattern+1) == 1 ? 1 : 0);
2297 else if (strlen(p->pattern) > 1 && *(p->pattern) == '<') {
2298 rc = (compare_lexicography(input, p->pattern+1) == -1 ? 1 : 0);
2300 else if (strlen(p->pattern) > 1 && *(p->pattern) == '=') {
2301 if (strcmp(p->pattern+1, "\"\"") == 0) {
2302 rc = (*input == '\0');
2305 rc = (strcmp(input, p->pattern+1) == 0 ? 1 : 0);
2309 /* it is really a regexp pattern, so apply it */
2310 rc = (ap_regexec(p->regexp, input,
2311 p->regexp->re_nsub+1, regmatch,0) == 0);
2313 /* if it isn't a negated pattern and really matched
2314 we update the passed-through regex subst info structure */
2315 if (rc && !(p->flags & CONDFLAG_NOTMATCH)) {
2316 briRC->source = ap_pstrdup(r->pool, input);
2317 briRC->nsub = p->regexp->re_nsub;
2318 memcpy((void *)(briRC->regmatch), (void *)(regmatch),
2323 /* if this is a non-matching regexp, just negate the result */
2324 if (p->flags & CONDFLAG_NOTMATCH) {
2328 rewritelog(r, 4, "RewriteCond: input='%s' pattern='%s%s' => %s",
2329 input, (p->flags & CONDFLAG_NOTMATCH ? "!" : ""),
2330 p->pattern, rc ? "matched" : "not-matched");
2332 /* end just return the result */
2338 ** +-------------------------------------------------------+
2340 ** | URL transformation functions
2342 ** +-------------------------------------------------------+
2347 ** split out a QUERY_STRING part from
2348 ** the current URI string
2352 static void splitout_queryargs(request_rec *r, int qsappend)
2357 q = strchr(r->filename, '?');
2359 olduri = ap_pstrdup(r->pool, r->filename);
2362 r->args = ap_pstrcat(r->pool, q, "&", r->args, NULL);
2365 r->args = ap_pstrdup(r->pool, q);
2367 if (strlen(r->args) == 0) {
2369 rewritelog(r, 3, "split uri=%s -> uri=%s, args=<none>", olduri,
2373 if (r->args[strlen(r->args)-1] == '&') {
2374 r->args[strlen(r->args)-1] = '\0';
2376 rewritelog(r, 3, "split uri=%s -> uri=%s, args=%s", olduri,
2377 r->filename, r->args);
2386 ** strip 'http[s]://ourhost/' from URI
2390 static void reduce_uri(request_rec *r)
2393 unsigned short port;
2398 char host[LONG_STRING_LEN];
2399 char buf[MAX_STRING_LEN];
2403 cp = (char *)ap_http_method(r);
2405 if ( strlen(r->filename) > l+3
2406 && strncasecmp(r->filename, cp, l) == 0
2407 && r->filename[l] == ':'
2408 && r->filename[l+1] == '/'
2409 && r->filename[l+2] == '/' ) {
2410 /* there was really a rewrite to a remote path */
2412 olduri = ap_pstrdup(r->pool, r->filename); /* save for logging */
2414 /* cut the hostname and port out of the URI */
2415 ap_cpystrn(buf, r->filename+(l+3), sizeof(buf));
2417 for (cp = hostp; *cp != '\0' && *cp != '/' && *cp != ':'; cp++)
2422 ap_cpystrn(host, hostp, sizeof(host));
2425 for (; *cp != '\0' && *cp != '/'; cp++)
2431 /* set remaining url */
2434 else if (*cp == '/') {
2437 ap_cpystrn(host, hostp, sizeof(host));
2440 port = ap_default_port(r);
2441 /* set remaining url */
2446 ap_cpystrn(host, hostp, sizeof(host));
2448 port = ap_default_port(r);
2449 /* set remaining url */
2453 /* now check whether we could reduce it to a local path... */
2454 if (ap_matches_request_vhost(r, host, port)) {
2455 /* this is our host, so only the URL remains */
2456 r->filename = ap_pstrdup(r->pool, url);
2457 rewritelog(r, 3, "reduce %s -> %s", olduri, r->filename);
2466 ** add 'http[s]://ourhost[:ourport]/' to URI
2467 ** if URI is still not fully qualified
2471 static void fully_qualify_uri(request_rec *r)
2475 const char *thisserver;
2479 i = strlen(r->filename);
2480 if (!( (i > 7 && strncasecmp(r->filename, "http://", 7) == 0)
2481 || (i > 8 && strncasecmp(r->filename, "https://", 8) == 0)
2482 || (i > 9 && strncasecmp(r->filename, "gopher://", 9) == 0)
2483 || (i > 6 && strncasecmp(r->filename, "ftp://", 6) == 0)
2484 || (i > 5 && strncasecmp(r->filename, "ldap:", 5) == 0)
2485 || (i > 5 && strncasecmp(r->filename, "news:", 5) == 0)
2486 || (i > 7 && strncasecmp(r->filename, "mailto:", 7) == 0))) {
2488 thisserver = ap_get_server_name(r);
2489 port = ap_get_server_port(r);
2490 if (ap_is_default_port(port,r)) {
2494 ap_snprintf(buf, sizeof(buf), ":%u", port);
2498 if (r->filename[0] == '/') {
2499 r->filename = ap_psprintf(r->pool, "%s://%s%s%s",
2500 ap_http_method(r), thisserver,
2501 thisport, r->filename);
2504 r->filename = ap_psprintf(r->pool, "%s://%s%s/%s",
2505 ap_http_method(r), thisserver,
2506 thisport, r->filename);
2515 ** Expand the %0-%9 or $0-$9 regex backreferences
2519 static void expand_backref_inbuffer(ap_pool_t *p, char *buf, int nbuf,
2520 backrefinfo *bri, char c)
2524 /* protect existing $N and & backrefs and replace <c>N with $N backrefs */
2525 for (i = 0; buf[i] != '\0' && i < nbuf; i++) {
2526 if (buf[i] == '\\' && (buf[i+1] != '\0' && i < (nbuf-1))) {
2527 i++; /* protect next */
2529 else if (buf[i] == '&') {
2532 else if (c != '$' && buf[i] == '$' && (buf[i+1] >= '0' && buf[i+1] <= '9')) {
2536 else if (buf[i] == c && (buf[i+1] >= '0' && buf[i+1] <= '9')) {
2542 /* now apply the standard regex substitution function */
2543 ap_cpystrn(buf, ap_pregsub(p, buf, bri->source,
2544 bri->nsub+1, bri->regmatch), nbuf);
2546 /* restore the original $N and & backrefs */
2547 for (i = 0; buf[i] != '\0' && i < nbuf; i++) {
2548 if (buf[i] == '\001') {
2551 else if (buf[i] == '\002') {
2560 ** Expand tilde-paths (/~user) through
2561 ** Unix /etc/passwd database information
2564 #if !defined(WIN32) && !defined(NETWARE)
2565 static char *expand_tildepaths(request_rec *r, char *uri)
2567 char user[LONG_STRING_LEN];
2573 if (uri != NULL && strlen(uri) > 2 && uri[0] == '/' && uri[1] == '~') {
2574 /* cut out the username */
2575 for (j = 0, i = 2; j < sizeof(user)-1
2577 && uri[i] != '/' ; ) {
2578 user[j++] = uri[i++];
2582 /* lookup username in systems passwd file */
2583 if ((pw = getpwnam(user)) != NULL) {
2584 /* ok, user was found, so expand the ~user string */
2585 if (uri[i] != '\0') {
2586 /* ~user/anything... has to be expanded */
2587 if (pw->pw_dir[strlen(pw->pw_dir)-1] == '/') {
2588 pw->pw_dir[strlen(pw->pw_dir)-1] = '\0';
2590 newuri = ap_pstrcat(r->pool, pw->pw_dir, uri+i, NULL);
2593 /* only ~user has to be expanded */
2594 newuri = ap_pstrdup(r->pool, pw->pw_dir);
2604 ** mapfile expansion support
2605 ** i.e. expansion of MAP lookup directives
2606 ** ${<mapname>:<key>} in RewriteRule rhs
2610 #define limit_length(n) (n > LONG_STRING_LEN-1 ? LONG_STRING_LEN-1 : n)
2612 static void expand_map_lookups(request_rec *r, char *uri, int uri_len)
2614 char newuri[MAX_STRING_LEN];
2620 char mapname[LONG_STRING_LEN];
2621 char mapkey[LONG_STRING_LEN];
2622 char defaultvalue[LONG_STRING_LEN];
2626 cpIE = cpI+strlen(cpI);
2628 while (cpI < cpIE) {
2629 if (cpI+6 < cpIE && strncmp(cpI, "${", 2) == 0) {
2630 /* missing delimiter -> take it as plain text */
2631 if ( strchr(cpI+2, ':') == NULL
2632 || strchr(cpI+2, '}') == NULL) {
2633 memcpy(cpO, cpI, 2);
2640 cpT = strchr(cpI, ':');
2642 memcpy(mapname, cpI, limit_length(n));
2643 mapname[limit_length(n)] = '\0';
2646 cpT2 = strchr(cpI, '|');
2647 cpT = strchr(cpI, '}');
2648 if (cpT2 != NULL && cpT2 < cpT) {
2650 memcpy(mapkey, cpI, limit_length(n));
2651 mapkey[limit_length(n)] = '\0';
2655 memcpy(defaultvalue, cpI, limit_length(n));
2656 defaultvalue[limit_length(n)] = '\0';
2661 memcpy(mapkey, cpI, limit_length(n));
2662 mapkey[limit_length(n)] = '\0';
2665 defaultvalue[0] = '\0';
2668 cpT = lookup_map(r, mapname, mapkey);
2671 if (cpO + n >= newuri + sizeof(newuri)) {
2672 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
2673 0, r, "insufficient space in "
2674 "expand_map_lookups, aborting");
2677 memcpy(cpO, cpT, n);
2681 n = strlen(defaultvalue);
2682 if (cpO + n >= newuri + sizeof(newuri)) {
2683 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
2684 0, r, "insufficient space in "
2685 "expand_map_lookups, aborting");
2688 memcpy(cpO, defaultvalue, n);
2693 cpT = strstr(cpI, "${");
2695 cpT = cpI+strlen(cpI);
2697 if (cpO + n >= newuri + sizeof(newuri)) {
2698 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR,
2699 0, r, "insufficient space in "
2700 "expand_map_lookups, aborting");
2703 memcpy(cpO, cpI, n);
2709 ap_cpystrn(uri, newuri, uri_len);
2718 ** +-------------------------------------------------------+
2720 ** | DBM hashfile support
2722 ** +-------------------------------------------------------+
2726 static char *lookup_map(request_rec *r, char *name, char *key)
2729 rewrite_server_conf *conf;
2730 ap_array_header_t *rewritemaps;
2731 rewritemap_entry *entries;
2732 rewritemap_entry *s;
2737 /* get map configuration */
2738 sconf = r->server->module_config;
2739 conf = (rewrite_server_conf *)ap_get_module_config(sconf,
2741 rewritemaps = conf->rewritemaps;
2743 entries = (rewritemap_entry *)rewritemaps->elts;
2744 for (i = 0; i < rewritemaps->nelts; i++) {
2746 if (strcmp(s->name, name) == 0) {
2747 if (s->type == MAPTYPE_TXT) {
2748 if (stat(s->checkfile, &st) == -1) {
2749 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
2750 "mod_rewrite: can't access text RewriteMap "
2751 "file %s", s->checkfile);
2752 rewritelog(r, 1, "can't open RewriteMap file, "
2756 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2758 if (value == NULL) {
2759 rewritelog(r, 6, "cache lookup FAILED, forcing new "
2762 lookup_map_txtfile(r, s->datafile, key)) != NULL) {
2763 rewritelog(r, 5, "map lookup OK: map=%s key=%s[txt] "
2764 "-> val=%s", s->name, key, value);
2765 set_cache_string(cachep, s->name, CACHEMODE_TS,
2766 st.st_mtime, key, value);
2770 rewritelog(r, 5, "map lookup FAILED: map=%s[txt] "
2771 "key=%s", s->name, key);
2772 set_cache_string(cachep, s->name, CACHEMODE_TS,
2773 st.st_mtime, key, "");
2778 rewritelog(r, 5, "cache lookup OK: map=%s[txt] key=%s "
2779 "-> val=%s", s->name, key, value);
2780 return value[0] != '\0' ? value : NULL;
2783 else if (s->type == MAPTYPE_DBM) {
2784 #ifndef NO_DBM_REWRITEMAP
2785 if (stat(s->checkfile, &st) == -1) {
2786 ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
2787 "mod_rewrite: can't access DBM RewriteMap "
2788 "file %s", s->checkfile);
2789 rewritelog(r, 1, "can't open DBM RewriteMap file, "
2793 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2795 if (value == NULL) {
2797 "cache lookup FAILED, forcing new map lookup");
2799 lookup_map_dbmfile(r, s->datafile, key)) != NULL) {
2800 rewritelog(r, 5, "map lookup OK: map=%s[dbm] key=%s "
2801 "-> val=%s", s->name, key, value);
2802 set_cache_string(cachep, s->name, CACHEMODE_TS,
2803 st.st_mtime, key, value);
2807 rewritelog(r, 5, "map lookup FAILED: map=%s[dbm] "
2808 "key=%s", s->name, key);
2809 set_cache_string(cachep, s->name, CACHEMODE_TS,
2810 st.st_mtime, key, "");
2815 rewritelog(r, 5, "cache lookup OK: map=%s[dbm] key=%s "
2816 "-> val=%s", s->name, key, value);
2817 return value[0] != '\0' ? value : NULL;
2823 else if (s->type == MAPTYPE_PRG) {
2825 lookup_map_program(r, s->fpin, s->fpout, key)) != NULL) {
2826 rewritelog(r, 5, "map lookup OK: map=%s key=%s -> val=%s",
2827 s->name, key, value);
2831 rewritelog(r, 5, "map lookup FAILED: map=%s key=%s",
2835 else if (s->type == MAPTYPE_INT) {
2836 if ((value = lookup_map_internal(r, s->func, key)) != NULL) {
2837 rewritelog(r, 5, "map lookup OK: map=%s key=%s -> val=%s",
2838 s->name, key, value);
2842 rewritelog(r, 5, "map lookup FAILED: map=%s key=%s",
2846 else if (s->type == MAPTYPE_RND) {
2847 if (stat(s->checkfile, &st) == -1) {
2848 ap_log_rerror(APLOG_MARK, APLOG_ERR, errno, r,
2849 "mod_rewrite: can't access text RewriteMap "
2850 "file %s", s->checkfile);
2851 rewritelog(r, 1, "can't open RewriteMap file, "
2855 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2857 if (value == NULL) {
2858 rewritelog(r, 6, "cache lookup FAILED, forcing new "
2861 lookup_map_txtfile(r, s->datafile, key)) != NULL) {
2862 rewritelog(r, 5, "map lookup OK: map=%s key=%s[txt] "
2863 "-> val=%s", s->name, key, value);
2864 set_cache_string(cachep, s->name, CACHEMODE_TS,
2865 st.st_mtime, key, value);
2868 rewritelog(r, 5, "map lookup FAILED: map=%s[txt] "
2869 "key=%s", s->name, key);
2870 set_cache_string(cachep, s->name, CACHEMODE_TS,
2871 st.st_mtime, key, "");
2876 rewritelog(r, 5, "cache lookup OK: map=%s[txt] key=%s "
2877 "-> val=%s", s->name, key, value);
2879 if (value[0] != '\0') {
2880 value = select_random_value_part(r, value);
2881 rewritelog(r, 5, "randomly choosen the subvalue `%s'", value);
2893 static char *lookup_map_txtfile(request_rec *r, char *file, char *key)
2895 ap_file_t *fp = NULL;
2904 rc = ap_open(&fp, file, APR_READ, APR_OS_DEFAULT, r->pool);
2905 if (rc != APR_SUCCESS) {
2909 while (ap_fgets(line, sizeof(line), fp) == APR_SUCCESS) {
2911 continue; /* ignore comments */
2914 skip = strcspn(cpT," \t\r\n");
2916 continue; /* ignore lines that start with a space, tab, CR, or LF */
2919 if (strcmp(curkey, key) != 0)
2920 continue; /* key does not match... */
2922 /* found a matching key; now extract and return the value */
2924 skip = strspn(cpT, " \t\r\n");
2927 skip = strcspn(cpT, " \t\r\n");
2929 continue; /* no value... */
2932 value = ap_pstrdup(r->pool, curval);
2939 #ifndef NO_DBM_REWRITEMAP
2940 static char *lookup_map_dbmfile(request_rec *r, char *file, char *key)
2946 char buf[MAX_STRING_LEN];
2949 dbmkey.dsize = strlen(key);
2950 if ((dbmfp = dbm_open(file, O_RDONLY, 0666)) != NULL) {
2951 dbmval = dbm_fetch(dbmfp, dbmkey);
2952 if (dbmval.dptr != NULL) {
2953 memcpy(buf, dbmval.dptr,
2954 dbmval.dsize < sizeof(buf)-1 ?
2955 dbmval.dsize : sizeof(buf)-1 );
2956 buf[dbmval.dsize] = '\0';
2957 value = ap_pstrdup(r->pool, buf);
2965 static char *lookup_map_program(request_rec *r, ap_file_t *fpin,
2966 ap_file_t *fpout, char *key)
2968 char buf[LONG_STRING_LEN];
2974 struct iovec iova[2];
2978 /* when `RewriteEngine off' was used in the per-server
2979 * context then the rewritemap-programs were not spawned.
2980 * In this case using such a map (usually in per-dir context)
2981 * is useless because it is not available.
2983 if (fpin == NULL || fpout == NULL) {
2989 ap_lock(rewrite_map_lock);
2991 /* write out the request key */
2993 nbytes = strlen(key);
2994 ap_write(fpin, key, &nbytes);
2996 ap_write(fpin, "\n", &nbytes);
2998 iova[0].iov_base = key;
2999 iova[0].iov_len = strlen(key);
3000 iova[1].iov_base = "\n";
3001 iova[1].iov_len = 1;
3004 ap_writev(fpin, iova, niov, &nbytes);
3007 /* read in the response value */
3010 ap_read(fpout, &c, &nbytes);
3011 while (nbytes == 1 && (i < LONG_STRING_LEN-1)) {
3017 ap_read(fpout, &c, &nbytes);
3021 /* give the lock back */
3022 ap_unlock(rewrite_map_lock);
3024 if (strcasecmp(buf, "NULL") == 0) {
3028 return ap_pstrdup(r->pool, buf);
3032 static char *lookup_map_internal(request_rec *r,
3033 char *(*func)(request_rec *, char *),
3036 /* currently we just let the function convert
3037 the key to a corresponding value */
3038 return func(r, key);
3041 static char *rewrite_mapfunc_toupper(request_rec *r, char *key)
3045 for (cp = value = ap_pstrdup(r->pool, key); cp != NULL && *cp != '\0';
3047 *cp = ap_toupper(*cp);
3052 static char *rewrite_mapfunc_tolower(request_rec *r, char *key)
3056 for (cp = value = ap_pstrdup(r->pool, key); cp != NULL && *cp != '\0';
3058 *cp = ap_tolower(*cp);
3063 static char *rewrite_mapfunc_escape(request_rec *r, char *key)
3067 value = ap_escape_uri(r->pool, key);
3071 static char *rewrite_mapfunc_unescape(request_rec *r, char *key)
3075 value = ap_pstrdup(r->pool, key);
3076 ap_unescape_url(value);
3080 static int rewrite_rand_init_done = 0;
3082 static void rewrite_rand_init(void)
3084 if (!rewrite_rand_init_done) {
3085 srand((unsigned)(getpid()));
3086 rewrite_rand_init_done = 1;
3091 static int rewrite_rand(int l, int h)
3093 rewrite_rand_init();
3095 /* Get [0,1) and then scale to the appropriate range. Note that using
3096 * a floating point value ensures that we use all bits of the rand()
3097 * result. Doing an integer modulus would only use the lower-order bits
3098 * which may not be as uniformly random.
3100 return ((double)(rand() % RAND_MAX) / RAND_MAX) * (h - l + 1) + l;
3103 static char *select_random_value_part(request_rec *r, char *value)
3108 /* count number of distinct values */
3109 for (n = 1, i = 0; value[i] != '\0'; i++) {
3110 if (value[i] == '|') {
3115 /* when only one value we have no option to choose */
3120 /* else randomly select one */
3121 k = rewrite_rand(1, n);
3123 /* and grep it out */
3124 for (n = 1, i = 0; value[i] != '\0'; i++) {
3128 if (value[i] == '|') {
3132 buf = ap_pstrdup(r->pool, &value[i]);
3133 for (i = 0; buf[i] != '\0' && buf[i] != '|'; i++)
3141 ** +-------------------------------------------------------+
3143 ** | rewriting logfile support
3145 ** +-------------------------------------------------------+
3149 static void open_rewritelog(server_rec *s, ap_pool_t *p)
3151 rewrite_server_conf *conf;
3155 int rewritelog_flags = ( APR_WRITE | APR_APPEND | APR_CREATE );
3156 mode_t rewritelog_mode = ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD );
3158 conf = ap_get_module_config(s->module_config, &rewrite_module);
3160 if (conf->rewritelogfile == NULL) {
3163 if (*(conf->rewritelogfile) == '\0') {
3166 if (conf->rewritelogfp != NULL) {
3167 return; /* virtual log shared w/ main server */
3170 fname = ap_server_root_relative(p, conf->rewritelogfile);
3172 if (*conf->rewritelogfile == '|') {
3173 if ((pl = ap_open_piped_log(p, conf->rewritelogfile+1)) == NULL) {
3174 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
3175 "mod_rewrite: could not open reliable pipe "
3176 "to RewriteLog filter %s", conf->rewritelogfile+1);
3179 conf->rewritelogfp = ap_piped_log_write_fd(pl);
3181 else if (*conf->rewritelogfile != '\0') {
3182 rc = ap_open(&conf->rewritelogfp, fname, rewritelog_flags, rewritelog_mode, p);
3183 if (rc != APR_SUCCESS) {
3184 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
3185 "mod_rewrite: could not open RewriteLog "
3193 static void rewritelog(request_rec *r, int level, const char *text, ...)
3195 rewrite_server_conf *conf;
3210 conf = ap_get_module_config(r->server->module_config, &rewrite_module);
3211 conn = r->connection;
3213 if (conf->rewritelogfp == NULL) {
3216 if (conf->rewritelogfile == NULL) {
3219 if (*(conf->rewritelogfile) == '\0') {
3223 if (level > conf->rewriteloglevel) {
3227 if (r->user == NULL) {
3230 else if (strlen(r->user) != 0) {
3237 rhost = ap_get_remote_host(conn, r->server->module_config,
3239 if (rhost == NULL) {
3240 rhost = "UNKNOWN-HOST";
3243 str1 = ap_pstrcat(r->pool, rhost, " ",
3244 (conn->remote_logname != NULL ?
3245 conn->remote_logname : "-"), " ",
3247 ap_vsnprintf(str2, sizeof(str2), text, ap);
3249 if (r->main == NULL) {
3250 strcpy(type, "initial");
3253 strcpy(type, "subreq");
3256 for (i = 0, req = r; req->prev != NULL; req = req->prev) {
3263 ap_snprintf(redir, sizeof(redir), "/redir#%d", i);
3266 ap_snprintf(str3, sizeof(str3),
3267 "%s %s [%s/sid#%lx][rid#%lx/%s%s] (%d) %s\n", str1,
3268 current_logtime(r), ap_get_server_name(r),
3269 (unsigned long)(r->server), (unsigned long)r,
3270 type, redir, level, str2);
3272 ap_lock(rewrite_log_lock);
3273 nbytes = strlen(str3);
3274 ap_write(conf->rewritelogfp, str3, &nbytes);
3275 ap_unlock(rewrite_log_lock);
3281 static char *current_logtime(request_rec *r)
3283 ap_exploded_time_t t;
3287 ap_explode_localtime(&t, ap_now());
3289 ap_strftime(tstr, &len, 80, "[%d/%b/%Y:%H:%M:%S ", &t);
3290 ap_snprintf(tstr + strlen(tstr), 80-strlen(tstr), "%c%.2d%.2d]",
3291 t.tm_gmtoff < 0 ? '-' : '+',
3292 t.tm_gmtoff / (60*60), t.tm_gmtoff % (60*60));
3293 return ap_pstrdup(r->pool, tstr);
3300 ** +-------------------------------------------------------+
3302 ** | rewriting lockfile support
3304 ** +-------------------------------------------------------+
3307 #define REWRITELOCK_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD )
3309 static void rewritelock_create(server_rec *s, ap_pool_t *p)
3313 /* only operate if a lockfile is used */
3314 if (lockname == NULL || *(lockname) == '\0') {
3318 /* fixup the path, especially for rewritelock_remove() */
3319 lockname = ap_server_root_relative(p, lockname);
3321 /* create the lockfile */
3322 rc = ap_create_lock (&rewrite_map_lock, APR_MUTEX, APR_LOCKALL, lockname, p);
3323 if (rc != APR_SUCCESS) {
3324 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
3325 "mod_rewrite: Parent could not create RewriteLock "
3326 "file %s", lockname);
3333 static ap_status_t rewritelock_remove(void *data)
3335 /* only operate if a lockfile is used */
3336 if (lockname == NULL || *(lockname) == '\0') {
3340 /* destroy the rewritelock */
3341 ap_destroy_lock (rewrite_map_lock);
3342 rewrite_map_lock = NULL;
3349 ** +-------------------------------------------------------+
3351 ** | program map support
3353 ** +-------------------------------------------------------+
3356 static void run_rewritemap_programs(server_rec *s, ap_pool_t *p)
3358 rewrite_server_conf *conf;
3359 ap_file_t *fpin = NULL;
3360 ap_file_t *fpout = NULL;
3361 ap_file_t *fperr = NULL;
3362 ap_array_header_t *rewritemaps;
3363 rewritemap_entry *entries;
3364 rewritemap_entry *map;
3368 conf = ap_get_module_config(s->module_config, &rewrite_module);
3370 /* If the engine isn't turned on,
3371 * don't even try to do anything.
3373 if (conf->state == ENGINE_DISABLED) {
3377 rewritemaps = conf->rewritemaps;
3378 entries = (rewritemap_entry *)rewritemaps->elts;
3379 for (i = 0; i < rewritemaps->nelts; i++) {
3381 if (map->type != MAPTYPE_PRG) {
3384 if (map->datafile == NULL
3385 || *(map->datafile) == '\0'
3386 || map->fpin != NULL
3387 || map->fpout != NULL ) {
3392 rc = rewritemap_program_child(p, map->datafile,
3393 &fpout, &fpin, &fperr);
3394 if (rc != APR_SUCCESS || fpin == NULL || fpout == NULL) {
3395 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s,
3396 "mod_rewrite: could not fork child for "
3397 "RewriteMap process. %d", rc);
3407 /* child process code */
3408 static int rewritemap_program_child(ap_pool_t *p, char *progname,
3409 ap_file_t **fpout, ap_file_t **fpin,
3413 ap_procattr_t *procattr;
3419 ap_signal(SIGHUP, SIG_IGN);
3423 if ((ap_createprocattr_init(&procattr, p) != APR_SUCCESS) ||
3424 (ap_setprocattr_io(procattr, APR_FULL_BLOCK,
3426 APR_FULL_NONBLOCK) != APR_SUCCESS) ||
3427 (ap_setprocattr_dir(procattr, ap_make_dirstr_parent(p, progname))
3429 (ap_setprocattr_cmdtype(procattr, APR_PROGRAM) != APR_SUCCESS)) {
3430 /* Something bad happened, give up and go away. */
3434 rc = ap_create_process(&procnew, progname, NULL, NULL, procattr, p);
3436 if (rc == APR_SUCCESS) {
3437 ap_note_subprocess(p, procnew, kill_after_timeout);
3440 ap_get_childin(fpin, procnew);
3444 ap_get_childout(fpout, procnew);
3448 ap_get_childerr(fperr, procnew);
3453 ap_unblock_alarms();
3462 ** +-------------------------------------------------------+
3464 ** | environment variable support
3466 ** +-------------------------------------------------------+
3470 static void expand_variables_inbuffer(request_rec *r, char *buf, int buf_len)
3473 newbuf = expand_variables(r, buf);
3474 if (strcmp(newbuf, buf) != 0) {
3475 ap_cpystrn(buf, newbuf, buf_len);
3480 static char *expand_variables(request_rec *r, char *str)
3482 char output[MAX_STRING_LEN];
3483 char input[MAX_STRING_LEN];
3491 ap_cpystrn(input, str, sizeof(input));
3494 endp = output + sizeof(output);
3496 for (cp = input; cp < input+MAX_STRING_LEN; ) {
3497 if ((cp2 = strstr(cp, "%{")) != NULL) {
3498 if ((cp3 = strstr(cp2, "}")) != NULL) {
3500 outp = ap_cpystrn(outp, cp, endp - outp);
3504 outp = ap_cpystrn(outp, lookup_variable(r, cp2), endp - outp);
3511 outp = ap_cpystrn(outp, cp, endp - outp);
3514 return expanded ? ap_pstrdup(r->pool, output) : str;
3517 static char *lookup_variable(request_rec *r, char *var)
3520 char resultbuf[LONG_STRING_LEN];
3521 ap_exploded_time_t tm;
3532 if (strcasecmp(var, "HTTP_USER_AGENT") == 0) {
3533 result = lookup_header(r, "User-Agent");
3535 else if (strcasecmp(var, "HTTP_REFERER") == 0) {
3536 result = lookup_header(r, "Referer");
3538 else if (strcasecmp(var, "HTTP_COOKIE") == 0) {
3539 result = lookup_header(r, "Cookie");
3541 else if (strcasecmp(var, "HTTP_FORWARDED") == 0) {
3542 result = lookup_header(r, "Forwarded");
3544 else if (strcasecmp(var, "HTTP_HOST") == 0) {
3545 result = lookup_header(r, "Host");
3547 else if (strcasecmp(var, "HTTP_PROXY_CONNECTION") == 0) {
3548 result = lookup_header(r, "Proxy-Connection");
3550 else if (strcasecmp(var, "HTTP_ACCEPT") == 0) {
3551 result = lookup_header(r, "Accept");
3553 /* all other headers from which we are still not know about */
3554 else if (strlen(var) > 5 && strncasecmp(var, "HTTP:", 5) == 0) {
3555 result = lookup_header(r, var+5);
3558 /* connection stuff */
3559 else if (strcasecmp(var, "REMOTE_ADDR") == 0) {
3560 result = r->connection->remote_ip;
3562 else if (strcasecmp(var, "REMOTE_HOST") == 0) {
3563 result = (char *)ap_get_remote_host(r->connection,
3564 r->per_dir_config, REMOTE_NAME);
3566 else if (strcasecmp(var, "REMOTE_USER") == 0) {
3569 else if (strcasecmp(var, "REMOTE_IDENT") == 0) {
3570 result = (char *)ap_get_remote_logname(r);
3574 else if (strcasecmp(var, "THE_REQUEST") == 0) { /* non-standard */
3575 result = r->the_request;
3577 else if (strcasecmp(var, "REQUEST_METHOD") == 0) {
3580 else if (strcasecmp(var, "REQUEST_URI") == 0) { /* non-standard */
3583 else if (strcasecmp(var, "SCRIPT_FILENAME") == 0 ||
3584 strcasecmp(var, "REQUEST_FILENAME") == 0 ) {
3585 result = r->filename;
3587 else if (strcasecmp(var, "PATH_INFO") == 0) {
3588 result = r->path_info;
3590 else if (strcasecmp(var, "QUERY_STRING") == 0) {
3593 else if (strcasecmp(var, "AUTH_TYPE") == 0) {
3594 result = r->ap_auth_type;
3596 else if (strcasecmp(var, "IS_SUBREQ") == 0) { /* non-standard */
3597 result = (r->main != NULL ? "true" : "false");
3600 /* internal server stuff */
3601 else if (strcasecmp(var, "DOCUMENT_ROOT") == 0) {
3602 result = ap_document_root(r);
3604 else if (strcasecmp(var, "SERVER_ADMIN") == 0) {
3605 result = r->server->server_admin;
3607 else if (strcasecmp(var, "SERVER_NAME") == 0) {
3608 result = ap_get_server_name(r);
3610 else if (strcasecmp(var, "SERVER_ADDR") == 0) { /* non-standard */
3611 result = r->connection->local_ip;
3613 else if (strcasecmp(var, "SERVER_PORT") == 0) {
3614 ap_snprintf(resultbuf, sizeof(resultbuf), "%u", ap_get_server_port(r));
3617 else if (strcasecmp(var, "SERVER_PROTOCOL") == 0) {
3618 result = r->protocol;
3620 else if (strcasecmp(var, "SERVER_SOFTWARE") == 0) {
3621 result = ap_get_server_version();
3623 else if (strcasecmp(var, "API_VERSION") == 0) { /* non-standard */
3624 ap_snprintf(resultbuf, sizeof(resultbuf), "%d:%d",
3625 MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR);
3629 /* XXX: wow this has gotta be slow if you actually use it for a lot, recalculates exploded time for each variable */
3630 /* underlaying Unix system stuff */
3631 else if (strcasecmp(var, "TIME_YEAR") == 0) {
3632 ap_explode_localtime(&tm, ap_now());
3633 ap_snprintf(resultbuf, sizeof(resultbuf), "%04d", tm.tm_year + 1900);
3636 #define MKTIMESTR(format, tmfield) \
3637 ap_explode_localtime(&tm, ap_now()); \
3638 ap_snprintf(resultbuf, sizeof(resultbuf), format, tm.tmfield); \
3640 else if (strcasecmp(var, "TIME_MON") == 0) {
3641 MKTIMESTR("%02d", tm_mon+1)
3643 else if (strcasecmp(var, "TIME_DAY") == 0) {
3644 MKTIMESTR("%02d", tm_mday)
3646 else if (strcasecmp(var, "TIME_HOUR") == 0) {
3647 MKTIMESTR("%02d", tm_hour)
3649 else if (strcasecmp(var, "TIME_MIN") == 0) {
3650 MKTIMESTR("%02d", tm_min)
3652 else if (strcasecmp(var, "TIME_SEC") == 0) {
3653 MKTIMESTR("%02d", tm_sec)
3655 else if (strcasecmp(var, "TIME_WDAY") == 0) {
3656 MKTIMESTR("%d", tm_wday)
3658 else if (strcasecmp(var, "TIME") == 0) {
3659 ap_explode_localtime(&tm, ap_now());
3660 ap_snprintf(resultbuf, sizeof(resultbuf),
3661 "%04d%02d%02d%02d%02d%02d", tm.tm_year + 1900,
3662 tm.tm_mon+1, tm.tm_mday,
3663 tm.tm_hour, tm.tm_min, tm.tm_sec);
3665 rewritelog(r, 1, "RESULT='%s'", result);
3668 /* all other env-variables from the parent Apache process */
3669 else if (strlen(var) > 4 && strncasecmp(var, "ENV:", 4) == 0) {
3670 /* first try the internal Apache notes structure */
3671 result = ap_table_get(r->notes, var+4);
3672 /* second try the internal Apache env structure */
3673 if (result == NULL) {
3674 result = ap_table_get(r->subprocess_env, var+4);
3676 /* third try the external OS env */
3677 if (result == NULL) {
3678 result = getenv(var+4);
3682 #define LOOKAHEAD(subrecfunc) \
3684 /* filename is safe to use */ \
3685 r->filename != NULL \
3686 /* - and we're either not in a subrequest */ \
3687 && ( r->main == NULL \
3688 /* - or in a subrequest where paths are non-NULL... */ \
3689 || ( r->main->uri != NULL && r->uri != NULL \
3690 /* ...and sub and main paths differ */ \
3691 && strcmp(r->main->uri, r->uri) != 0))) { \
3692 /* process a file-based subrequest */ \
3693 rsub = subrecfunc(r->filename, r); \
3694 /* now recursively lookup the variable in the sub_req */ \
3695 result = lookup_variable(rsub, var+5); \
3696 /* copy it up to our scope before we destroy sub_req's ap_pool_t */ \
3697 result = ap_pstrdup(r->pool, result); \
3698 /* cleanup by destroying the subrequest */ \
3699 ap_destroy_sub_req(rsub); \
3701 rewritelog(r, 5, "lookahead: path=%s var=%s -> val=%s", \
3702 r->filename, var+5, result); \
3703 /* return ourself to prevent re-pstrdup */ \
3704 return (char *)result; \
3707 /* look-ahead for parameter through URI-based sub-request */
3708 else if (strlen(var) > 5 && strncasecmp(var, "LA-U:", 5) == 0) {
3709 LOOKAHEAD(ap_sub_req_lookup_uri)
3711 /* look-ahead for parameter through file-based sub-request */
3712 else if (strlen(var) > 5 && strncasecmp(var, "LA-F:", 5) == 0) {
3713 LOOKAHEAD(ap_sub_req_lookup_file)
3716 #if !defined(WIN32) && !defined(NETWARE)
3717 /* Win32 has a rather different view of file ownerships.
3718 For now, just forget it */
3721 else if (strcasecmp(var, "SCRIPT_USER") == 0) {
3722 result = "<unknown>";
3723 if (r->finfo.protection != 0) {
3724 if ((pw = getpwuid(r->finfo.user)) != NULL) {
3725 result = pw->pw_name;
3729 if (stat(r->filename, &finfo) == 0) {
3730 if ((pw = getpwuid(finfo.st_uid)) != NULL) {
3731 result = pw->pw_name;
3736 else if (strcasecmp(var, "SCRIPT_GROUP") == 0) {
3737 result = "<unknown>";
3738 if (r->finfo.protection != 0) {
3739 if ((gr = getgrgid(r->finfo.group)) != NULL) {
3740 result = gr->gr_name;
3744 if (stat(r->filename, &finfo) == 0) {
3745 if ((gr = getgrgid(finfo.st_gid)) != NULL) {
3746 result = gr->gr_name;
3751 #endif /* ndef WIN32 && NETWARE*/
3753 if (result == NULL) {
3754 return ap_pstrdup(r->pool, "");
3757 return ap_pstrdup(r->pool, result);
3761 static char *lookup_header(request_rec *r, const char *name)
3763 ap_array_header_t *hdrs_arr;
3764 ap_table_entry_t *hdrs;
3767 hdrs_arr = ap_table_elts(r->headers_in);
3768 hdrs = (ap_table_entry_t *)hdrs_arr->elts;
3769 for (i = 0; i < hdrs_arr->nelts; ++i) {
3770 if (hdrs[i].key == NULL) {
3773 if (strcasecmp(hdrs[i].key, name) == 0) {
3774 ap_table_merge(r->notes, VARY_KEY_THIS, name);
3785 ** +-------------------------------------------------------+
3787 ** | caching support
3789 ** +-------------------------------------------------------+
3793 static cache *init_cache(ap_pool_t *p)
3797 c = (cache *)ap_palloc(p, sizeof(cache));
3798 if (ap_create_pool(&c->pool, p) != APR_SUCCESS)
3800 c->lists = ap_make_array(c->pool, 2, sizeof(cachelist));
3804 static void set_cache_string(cache *c, char *res, int mode, time_t t,
3805 char *key, char *value)
3812 store_cache_string(c, res, &ce);
3816 static char *get_cache_string(cache *c, char *res, int mode,
3817 time_t t, char *key)
3821 ce = retrieve_cache_string(c, res, key);
3825 if (mode & CACHEMODE_TS) {
3826 if (t != ce->time) {
3830 else if (mode & CACHEMODE_TTL) {
3835 return ap_pstrdup(c->pool, ce->value);
3838 static int cache_tlb_hash(char *key)
3844 for (p=key; *p != '\0'; ++p) {
3845 n = n * 53711 + 134561 + (unsigned)(*p & 0xff);
3848 return n % CACHE_TLB_ROWS;
3851 static cacheentry *cache_tlb_lookup(cachetlbentry *tlb, cacheentry *elt,
3854 int ix = cache_tlb_hash(key);
3858 for (i=0; i < CACHE_TLB_COLS; ++i) {
3862 if (strcmp(elt[j].key, key) == 0)
3868 static void cache_tlb_replace(cachetlbentry *tlb, cacheentry *elt,
3871 int ix = cache_tlb_hash(e->key);
3876 for (i=1; i < CACHE_TLB_COLS; ++i)
3877 tlb->t[i] = tlb->t[i-1];
3879 tlb->t[0] = e - elt;
3882 static void store_cache_string(cache *c, char *res, cacheentry *ce)
3892 /* first try to edit an existing entry */
3893 for (i = 0; i < c->lists->nelts; i++) {
3894 l = &(((cachelist *)c->lists->elts)[i]);
3895 if (strcmp(l->resource, res) == 0) {
3898 e = cache_tlb_lookup((cachetlbentry *)l->tlb->elts,
3899 (cacheentry *)l->entries->elts, ce->key);
3902 e->value = ap_pstrdup(c->pool, ce->value);
3906 for (j = 0; j < l->entries->nelts; j++) {
3907 e = &(((cacheentry *)l->entries->elts)[j]);
3908 if (strcmp(e->key, ce->key) == 0) {
3910 e->value = ap_pstrdup(c->pool, ce->value);
3911 cache_tlb_replace((cachetlbentry *)l->tlb->elts,
3912 (cacheentry *)l->entries->elts, e);
3919 /* create a needed new list */
3921 l = ap_push_array(c->lists);
3922 l->resource = ap_pstrdup(c->pool, res);
3923 l->entries = ap_make_array(c->pool, 2, sizeof(cacheentry));
3924 l->tlb = ap_make_array(c->pool, CACHE_TLB_ROWS,
3925 sizeof(cachetlbentry));
3926 for (i=0; i<CACHE_TLB_ROWS; ++i) {
3927 t = &((cachetlbentry *)l->tlb->elts)[i];
3928 for (j=0; j<CACHE_TLB_COLS; ++j)
3933 /* create the new entry */
3934 for (i = 0; i < c->lists->nelts; i++) {
3935 l = &(((cachelist *)c->lists->elts)[i]);
3936 if (strcmp(l->resource, res) == 0) {
3937 e = ap_push_array(l->entries);
3939 e->key = ap_pstrdup(c->pool, ce->key);
3940 e->value = ap_pstrdup(c->pool, ce->value);
3941 cache_tlb_replace((cachetlbentry *)l->tlb->elts,
3942 (cacheentry *)l->entries->elts, e);
3947 /* not reached, but when it is no problem... */
3951 static cacheentry *retrieve_cache_string(cache *c, char *res, char *key)
3958 for (i = 0; i < c->lists->nelts; i++) {
3959 l = &(((cachelist *)c->lists->elts)[i]);
3960 if (strcmp(l->resource, res) == 0) {
3962 e = cache_tlb_lookup((cachetlbentry *)l->tlb->elts,
3963 (cacheentry *)l->entries->elts, key);
3967 for (j = 0; j < l->entries->nelts; j++) {
3968 e = &(((cacheentry *)l->entries->elts)[j]);
3969 if (strcmp(e->key, key) == 0) {
3982 ** +-------------------------------------------------------+
3986 ** +-------------------------------------------------------+
3989 static char *subst_prefix_path(request_rec *r, char *input, char *match,
3992 char matchbuf[LONG_STRING_LEN];
3993 char substbuf[LONG_STRING_LEN];
3999 /* first create a match string which always has a trailing slash */
4000 l = ap_cpystrn(matchbuf, match, sizeof(matchbuf)) - matchbuf;
4001 if (matchbuf[l-1] != '/') {
4003 matchbuf[l+1] = '\0';
4006 /* now compare the prefix */
4007 if (strncmp(input, matchbuf, l) == 0) {
4008 rewritelog(r, 5, "strip matching prefix: %s -> %s", output, output+l);
4009 output = ap_pstrdup(r->pool, output+l);
4011 /* and now add the base-URL as replacement prefix */
4012 l = ap_cpystrn(substbuf, subst, sizeof(substbuf)) - substbuf;
4013 if (substbuf[l-1] != '/') {
4015 substbuf[l+1] = '\0';
4018 if (output[0] == '/') {
4019 rewritelog(r, 4, "add subst prefix: %s -> %s%s",
4020 output, substbuf, output+1);
4021 output = ap_pstrcat(r->pool, substbuf, output+1, NULL);
4024 rewritelog(r, 4, "add subst prefix: %s -> %s%s",
4025 output, substbuf, output);
4026 output = ap_pstrcat(r->pool, substbuf, output, NULL);
4035 ** own command line parser which don't have the '\\' problem
4039 static int parseargline(char *str, char **a1, char **a2, char **a3)
4044 #define SKIP_WHITESPACE(cp) \
4045 for ( ; *cp == ' ' || *cp == '\t'; ) { \
4049 #define CHECK_QUOTATION(cp,isquoted) \
4056 #define DETERMINE_NEXTSTRING(cp,isquoted) \
4057 for ( ; *cp != '\0'; cp++) { \
4058 if ( (isquoted && (*cp == ' ' || *cp == '\t')) \
4059 || (*cp == '\\' && (*(cp+1) == ' ' || *(cp+1) == '\t'))) { \
4063 if ( (!isquoted && (*cp == ' ' || *cp == '\t')) \
4064 || (isquoted && *cp == '"') ) { \
4070 SKIP_WHITESPACE(cp);
4072 /* determine first argument */
4073 CHECK_QUOTATION(cp, isquoted);
4075 DETERMINE_NEXTSTRING(cp, isquoted);
4081 SKIP_WHITESPACE(cp);
4083 /* determine second argument */
4084 CHECK_QUOTATION(cp, isquoted);
4086 DETERMINE_NEXTSTRING(cp, isquoted);
4094 SKIP_WHITESPACE(cp);
4096 /* again check if there are only two arguments */
4103 /* determine second argument */
4104 CHECK_QUOTATION(cp, isquoted);
4106 DETERMINE_NEXTSTRING(cp, isquoted);
4113 static void add_env_variable(request_rec *r, char *s)
4115 char var[MAX_STRING_LEN];
4116 char val[MAX_STRING_LEN];
4120 if ((cp = strchr(s, ':')) != NULL) {
4121 n = ((cp-s) > MAX_STRING_LEN-1 ? MAX_STRING_LEN-1 : (cp-s));
4124 ap_cpystrn(val, cp+1, sizeof(val));
4125 ap_table_set(r->subprocess_env, var, val);
4126 rewritelog(r, 5, "setting env variable '%s' to '%s'", var, val);
4134 ** stat() for only the prefix of a path
4138 static int prefix_stat(const char *path, struct stat *sb)
4140 char curpath[LONG_STRING_LEN];
4143 ap_cpystrn(curpath, path, sizeof(curpath));
4144 if (curpath[0] != '/') {
4147 if ((cp = strchr(curpath+1, '/')) != NULL) {
4150 if (stat(curpath, sb) == 0) {
4161 ** Lexicographic Compare
4165 static int compare_lexicography(char *cpNum1, char *cpNum2)
4170 n1 = strlen(cpNum1);
4171 n2 = strlen(cpNum2);
4178 for (i = 0; i < n1; i++) {
4179 if (cpNum1[i] > cpNum2[i]) {
4182 if (cpNum1[i] < cpNum2[i]) {
4190 int main(int argc, char *argv[])
4192 ExitThread(TSR_THREAD, 0);