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"
99 #include "apr_strings.h"
100 #include "apr_user.h"
102 #if !defined(OS2) && !defined(WIN32)
108 #ifdef HAVE_SYS_TYPES_H
109 #include <sys/types.h>
112 #if APR_HAVE_SYS_UIO_H
125 #ifdef HAVE_STRINGS_H
130 ** +-------------------------------------------------------+
132 ** | static module configuration
134 ** +-------------------------------------------------------+
139 ** Our interface to the Apache server kernel:
141 ** o Runtime logic of a request is as following:
142 ** while(request or subrequest)
143 ** foreach(stage #0...#9)
144 ** foreach(module) (**)
147 ** o the order of modules at (**) is the inverted order as
148 ** given in the "Configuration" file, i.e. the last module
149 ** specified is the first one called for each hook!
150 ** The core module is always the last!
152 ** o there are two different types of result checking and
153 ** continue processing:
154 ** for hook #0,#1,#4,#5,#6,#8:
155 ** hook run loop stops on first modules which gives
156 ** back a result != DECLINED, i.e. it usually returns OK
157 ** which says "OK, module has handled this _stage_" and for #1
158 ** this have not to mean "Ok, the filename is now valid".
159 ** for hook #2,#3,#7,#9:
160 ** all hooks are run, independend of result
162 ** o at the last stage, the core module always
163 ** - says "HTTP_BAD_REQUEST" if r->filename does not begin with "/"
164 ** - prefix URL with document_root or replaced server_root
165 ** with document_root and sets r->filename
166 ** - always return a "OK" independed if the file really exists
170 /* The section for the Configure script:
171 * XXX: this needs updating for apache-2.0 configuration method
172 * MODULE-DEFINITION-START
173 * Name: rewrite_module
175 . ./helpers/find-dbm-lib
176 if [ "x$found_dbm" = "x1" ]; then
177 echo " enabling DBM support for mod_rewrite"
179 echo " disabling DBM support for mod_rewrite"
180 echo " (perhaps you need to add -ldbm, -lndbm or -lgdbm to EXTRA_LIBS)"
181 CFLAGS="$CFLAGS -DNO_DBM_REWRITEMAP"
184 * MODULE-DEFINITION-END
187 /* the apr_table_t of commands we provide */
188 static const command_rec command_table[] = {
189 AP_INIT_FLAG( "RewriteEngine", cmd_rewriteengine, NULL, OR_FILEINFO,
190 "On or Off to enable or disable (default) the whole "
192 AP_INIT_ITERATE( "RewriteOptions", cmd_rewriteoptions, NULL, OR_FILEINFO,
193 "List of option strings to set"),
194 AP_INIT_TAKE1( "RewriteBase", cmd_rewritebase, NULL, OR_FILEINFO,
195 "the base URL of the per-directory context"),
196 AP_INIT_RAW_ARGS("RewriteCond", cmd_rewritecond, NULL, OR_FILEINFO,
197 "an input string and a to be applied regexp-pattern"),
198 AP_INIT_RAW_ARGS("RewriteRule", cmd_rewriterule, NULL, OR_FILEINFO,
199 "an URL-applied regexp-pattern and a substitution URL"),
200 AP_INIT_TAKE2( "RewriteMap", cmd_rewritemap, NULL, RSRC_CONF,
201 "a mapname and a filename"),
202 AP_INIT_TAKE1( "RewriteLock", cmd_rewritelock, NULL, RSRC_CONF,
203 "the filename of a lockfile used for inter-process "
205 AP_INIT_TAKE1( "RewriteLog", cmd_rewritelog, NULL, RSRC_CONF,
206 "the filename of the rewriting logfile"),
207 AP_INIT_TAKE1( "RewriteLogLevel", cmd_rewriteloglevel, NULL, RSRC_CONF,
208 "the level of the rewriting logfile verbosity "
209 "(0=none, 1=std, .., 9=max)"),
213 /* the apr_table_t of content handlers we provide */
214 static const handler_rec handler_table[] = {
215 { "redirect-handler", handler_redirect },
219 static void register_hooks(void)
221 ap_hook_post_config(init_module,NULL,NULL,AP_HOOK_MIDDLE);
222 ap_hook_child_init(init_child,NULL,NULL,AP_HOOK_MIDDLE);
224 ap_hook_fixups(hook_fixup,NULL,NULL,AP_HOOK_FIRST);
225 ap_hook_translate_name(hook_uri2file,NULL,NULL,AP_HOOK_FIRST);
226 ap_hook_type_checker(hook_mimetype,NULL,NULL,AP_HOOK_MIDDLE);
229 /* the main config structure */
230 module AP_MODULE_DECLARE_DATA rewrite_module = {
231 STANDARD20_MODULE_STUFF,
232 config_perdir_create, /* create per-dir config structures */
233 config_perdir_merge, /* merge per-dir config structures */
234 config_server_create, /* create per-server config structures */
235 config_server_merge, /* merge per-server config structures */
236 command_table, /* apr_table_t of config file commands */
237 handler_table, /* [#8] MIME-typed-dispatched handlers */
238 register_hooks /* register hooks */
242 static cache *cachep;
244 /* whether proxy module is available or not */
245 static int proxy_available;
247 static const char *lockname;
248 static apr_lock_t *rewrite_mapr_lock = NULL;
249 static apr_lock_t *rewrite_log_lock = NULL;
252 ** +-------------------------------------------------------+
254 ** | configuration directive handling
256 ** +-------------------------------------------------------+
261 ** per-server configuration structure handling
265 static void *config_server_create(apr_pool_t *p, server_rec *s)
267 rewrite_server_conf *a;
269 a = (rewrite_server_conf *)apr_pcalloc(p, sizeof(rewrite_server_conf));
271 a->state = ENGINE_DISABLED;
272 a->options = OPTION_NONE;
273 a->rewritelogfile = NULL;
274 a->rewritelogfp = NULL;
275 a->rewriteloglevel = 0;
276 a->rewritemaps = apr_make_array(p, 2, sizeof(rewritemap_entry));
277 a->rewriteconds = apr_make_array(p, 2, sizeof(rewritecond_entry));
278 a->rewriterules = apr_make_array(p, 2, sizeof(rewriterule_entry));
284 static void *config_server_merge(apr_pool_t *p, void *basev, void *overridesv)
286 rewrite_server_conf *a, *base, *overrides;
288 a = (rewrite_server_conf *)apr_pcalloc(p, sizeof(rewrite_server_conf));
289 base = (rewrite_server_conf *)basev;
290 overrides = (rewrite_server_conf *)overridesv;
292 a->state = overrides->state;
293 a->options = overrides->options;
294 a->server = overrides->server;
296 if (a->options & OPTION_INHERIT) {
298 * local directives override
299 * and anything else is inherited
301 a->rewriteloglevel = overrides->rewriteloglevel != 0
302 ? overrides->rewriteloglevel
303 : base->rewriteloglevel;
304 a->rewritelogfile = overrides->rewritelogfile != NULL
305 ? overrides->rewritelogfile
306 : base->rewritelogfile;
307 a->rewritelogfp = overrides->rewritelogfp != NULL
308 ? overrides->rewritelogfp
309 : base->rewritelogfp;
310 a->rewritemaps = apr_append_arrays(p, overrides->rewritemaps,
312 a->rewriteconds = apr_append_arrays(p, overrides->rewriteconds,
314 a->rewriterules = apr_append_arrays(p, overrides->rewriterules,
319 * local directives override
320 * and anything else gets defaults
322 a->rewriteloglevel = overrides->rewriteloglevel;
323 a->rewritelogfile = overrides->rewritelogfile;
324 a->rewritelogfp = overrides->rewritelogfp;
325 a->rewritemaps = overrides->rewritemaps;
326 a->rewriteconds = overrides->rewriteconds;
327 a->rewriterules = overrides->rewriterules;
336 ** per-directory configuration structure handling
340 static void *config_perdir_create(apr_pool_t *p, char *path)
342 rewrite_perdir_conf *a;
344 a = (rewrite_perdir_conf *)apr_pcalloc(p, sizeof(rewrite_perdir_conf));
346 a->state = ENGINE_DISABLED;
347 a->options = OPTION_NONE;
349 a->rewriteconds = apr_make_array(p, 2, sizeof(rewritecond_entry));
350 a->rewriterules = apr_make_array(p, 2, sizeof(rewriterule_entry));
356 /* make sure it has a trailing slash */
357 if (path[strlen(path)-1] == '/') {
358 a->directory = apr_pstrdup(p, path);
361 a->directory = apr_pstrcat(p, path, "/", NULL);
368 static void *config_perdir_merge(apr_pool_t *p, void *basev, void *overridesv)
370 rewrite_perdir_conf *a, *base, *overrides;
372 a = (rewrite_perdir_conf *)apr_pcalloc(p,
373 sizeof(rewrite_perdir_conf));
374 base = (rewrite_perdir_conf *)basev;
375 overrides = (rewrite_perdir_conf *)overridesv;
377 a->state = overrides->state;
378 a->options = overrides->options;
379 a->directory = overrides->directory;
380 a->baseurl = overrides->baseurl;
382 if (a->options & OPTION_INHERIT) {
383 a->rewriteconds = apr_append_arrays(p, overrides->rewriteconds,
385 a->rewriterules = apr_append_arrays(p, overrides->rewriterules,
389 a->rewriteconds = overrides->rewriteconds;
390 a->rewriterules = overrides->rewriterules;
399 ** the configuration commands
403 static const char *cmd_rewriteengine(cmd_parms *cmd,
404 void *in_dconf, int flag)
406 rewrite_perdir_conf *dconf = in_dconf;
407 rewrite_server_conf *sconf;
410 (rewrite_server_conf *)ap_get_module_config(cmd->server->module_config,
413 if (cmd->path == NULL) { /* is server command */
414 sconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
416 else /* is per-directory command */ {
417 dconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED);
423 static const char *cmd_rewriteoptions(cmd_parms *cmd,
424 void *in_dconf, const char *option)
426 rewrite_perdir_conf *dconf = in_dconf;
427 rewrite_server_conf *sconf;
430 sconf = (rewrite_server_conf *)
431 ap_get_module_config(cmd->server->module_config, &rewrite_module);
433 if (cmd->path == NULL) { /* is server command */
434 err = cmd_rewriteoptions_setoption(cmd->pool,
435 &(sconf->options), option);
437 else { /* is per-directory command */
438 err = cmd_rewriteoptions_setoption(cmd->pool,
439 &(dconf->options), option);
445 static const char *cmd_rewriteoptions_setoption(apr_pool_t *p, int *options,
448 if (strcasecmp(name, "inherit") == 0) {
449 *options |= OPTION_INHERIT;
452 return apr_pstrcat(p, "RewriteOptions: unknown option '",
458 static const char *cmd_rewritelog(cmd_parms *cmd, void *dconf, const char *a1)
460 rewrite_server_conf *sconf;
462 sconf = (rewrite_server_conf *)
463 ap_get_module_config(cmd->server->module_config, &rewrite_module);
465 sconf->rewritelogfile = a1;
470 static const char *cmd_rewriteloglevel(cmd_parms *cmd, void *dconf, const char *a1)
472 rewrite_server_conf *sconf;
474 sconf = (rewrite_server_conf *)
475 ap_get_module_config(cmd->server->module_config, &rewrite_module);
477 sconf->rewriteloglevel = atoi(a1);
482 static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, const char *a1,
485 rewrite_server_conf *sconf;
486 rewritemap_entry *newmap;
489 sconf = (rewrite_server_conf *)
490 ap_get_module_config(cmd->server->module_config, &rewrite_module);
492 newmap = apr_push_array(sconf->rewritemaps);
496 if (strncmp(a2, "txt:", 4) == 0) {
497 newmap->type = MAPTYPE_TXT;
498 newmap->datafile = a2+4;
499 newmap->checkfile = a2+4;
501 else if (strncmp(a2, "rnd:", 4) == 0) {
502 newmap->type = MAPTYPE_RND;
503 newmap->datafile = a2+4;
504 newmap->checkfile = a2+4;
506 else if (strncmp(a2, "dbm:", 4) == 0) {
507 #ifndef NO_DBM_REWRITEMAP
508 newmap->type = MAPTYPE_DBM;
509 newmap->datafile = a2+4;
510 newmap->checkfile = apr_pstrcat(cmd->pool, a2+4, NDBM_FILE_SUFFIX, NULL);
512 return apr_pstrdup(cmd->pool, "RewriteMap: cannot use NDBM mapfile, "
513 "because no NDBM support is compiled in");
516 else if (strncmp(a2, "prg:", 4) == 0) {
517 newmap->type = MAPTYPE_PRG;
518 newmap->datafile = a2+4;
519 newmap->checkfile = a2+4;
521 else if (strncmp(a2, "int:", 4) == 0) {
522 newmap->type = MAPTYPE_INT;
523 newmap->datafile = NULL;
524 newmap->checkfile = NULL;
525 if (strcmp(a2+4, "tolower") == 0) {
526 newmap->func = rewrite_mapfunc_tolower;
528 else if (strcmp(a2+4, "toupper") == 0) {
529 newmap->func = rewrite_mapfunc_toupper;
531 else if (strcmp(a2+4, "escape") == 0) {
532 newmap->func = rewrite_mapfunc_escape;
534 else if (strcmp(a2+4, "unescape") == 0) {
535 newmap->func = rewrite_mapfunc_unescape;
537 else if (sconf->state == ENGINE_ENABLED) {
538 return apr_pstrcat(cmd->pool, "RewriteMap: internal map not found:",
543 newmap->type = MAPTYPE_TXT;
544 newmap->datafile = a2;
545 newmap->checkfile = a2;
548 newmap->fpout = NULL;
550 if (newmap->checkfile && (sconf->state == ENGINE_ENABLED)
551 && (apr_stat(&st, newmap->checkfile, cmd->pool) != APR_SUCCESS)) {
552 return apr_pstrcat(cmd->pool,
553 "RewriteMap: map file or program not found:",
554 newmap->checkfile, NULL);
560 static const char *cmd_rewritelock(cmd_parms *cmd, void *dconf, const char *a1)
564 if ((error = ap_check_cmd_context(cmd, GLOBAL_ONLY)) != NULL)
572 static const char *cmd_rewritebase(cmd_parms *cmd, void *in_dconf,
575 rewrite_perdir_conf *dconf = in_dconf;
577 if (cmd->path == NULL || dconf == NULL) {
578 return "RewriteBase: only valid in per-directory config files";
581 return "RewriteBase: empty URL not allowed";
584 return "RewriteBase: argument is not a valid URL";
592 static const char *cmd_rewritecond(cmd_parms *cmd, void *in_dconf,
595 rewrite_perdir_conf *dconf = in_dconf;
596 char *str = apr_pstrdup(cmd->pool, in_str);
597 rewrite_server_conf *sconf;
598 rewritecond_entry *newcond;
607 sconf = (rewrite_server_conf *)
608 ap_get_module_config(cmd->server->module_config, &rewrite_module);
610 /* make a new entry in the internal temporary rewrite rule list */
611 if (cmd->path == NULL) { /* is server command */
612 newcond = apr_push_array(sconf->rewriteconds);
614 else { /* is per-directory command */
615 newcond = apr_push_array(dconf->rewriteconds);
618 /* parse the argument line ourself */
619 if (parseargline(str, &a1, &a2, &a3)) {
620 return apr_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str,
624 /* arg1: the input string */
625 newcond->input = apr_pstrdup(cmd->pool, a1);
627 /* arg3: optional flags field
628 (this have to be first parsed, because we need to
629 know if the regex should be compiled with ICASE!) */
630 newcond->flags = CONDFLAG_NONE;
632 if ((err = cmd_rewritecond_parseflagfield(cmd->pool, newcond,
639 try to compile the regexp to test if is ok */
642 newcond->flags |= CONDFLAG_NOTMATCH;
646 /* now be careful: Under the POSIX regex library
647 we can compile the pattern for case insensitive matching,
648 under the old V8 library we have to do it self via a hack */
649 if (newcond->flags & CONDFLAG_NOCASE) {
650 rc = ((regexp = ap_pregcomp(cmd->pool, cp, REG_EXTENDED|REG_ICASE))
654 rc = ((regexp = ap_pregcomp(cmd->pool, cp, REG_EXTENDED)) == NULL);
657 return apr_pstrcat(cmd->pool,
658 "RewriteCond: cannot compile regular expression '",
662 newcond->pattern = apr_pstrdup(cmd->pool, cp);
663 newcond->regexp = regexp;
668 static const char *cmd_rewritecond_parseflagfield(apr_pool_t *p,
669 rewritecond_entry *cfg,
680 if (str[0] != '[' || str[strlen(str)-1] != ']') {
681 return "RewriteCond: bad flag delimiters";
685 str[strlen(str)-1] = ','; /* for simpler parsing */
686 for ( ; *cp != '\0'; ) {
687 /* skip whitespaces */
688 for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++)
694 if ((cp2 = strchr(cp, ',')) != NULL) {
696 for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--)
699 if ((cp3 = strchr(cp1, '=')) != NULL) {
708 if ((err = cmd_rewritecond_setflag(p, cfg, key, val)) != NULL) {
720 static const char *cmd_rewritecond_setflag(apr_pool_t *p, rewritecond_entry *cfg,
721 char *key, char *val)
723 if ( strcasecmp(key, "nocase") == 0
724 || strcasecmp(key, "NC") == 0 ) {
725 cfg->flags |= CONDFLAG_NOCASE;
727 else if ( strcasecmp(key, "ornext") == 0
728 || strcasecmp(key, "OR") == 0 ) {
729 cfg->flags |= CONDFLAG_ORNEXT;
732 return apr_pstrcat(p, "RewriteCond: unknown flag '", key, "'", NULL);
737 static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf,
740 rewrite_perdir_conf *dconf = in_dconf;
741 char *str = apr_pstrdup(cmd->pool, in_str);
742 rewrite_server_conf *sconf;
743 rewriterule_entry *newrule;
752 sconf = (rewrite_server_conf *)
753 ap_get_module_config(cmd->server->module_config, &rewrite_module);
755 /* make a new entry in the internal rewrite rule list */
756 if (cmd->path == NULL) { /* is server command */
757 newrule = apr_push_array(sconf->rewriterules);
759 else { /* is per-directory command */
760 newrule = apr_push_array(dconf->rewriterules);
763 /* parse the argument line ourself */
764 if (parseargline(str, &a1, &a2, &a3)) {
765 return apr_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str,
769 /* arg3: optional flags field */
770 newrule->forced_mimetype = NULL;
771 newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY;
772 newrule->flags = RULEFLAG_NONE;
773 newrule->env[0] = NULL;
776 if ((err = cmd_rewriterule_parseflagfield(cmd->pool, newrule,
783 * try to compile the regexp to test if is ok
787 newrule->flags |= RULEFLAG_NOTMATCH;
791 if (newrule->flags & RULEFLAG_NOCASE) {
794 if ((regexp = ap_pregcomp(cmd->pool, cp, mode)) == NULL) {
795 return apr_pstrcat(cmd->pool,
796 "RewriteRule: cannot compile regular expression '",
799 newrule->pattern = apr_pstrdup(cmd->pool, cp);
800 newrule->regexp = regexp;
802 /* arg2: the output string
803 * replace the $<N> by \<n> which is needed by the currently
804 * used Regular Expression library
806 * TODO: Is this still required for PCRE? If not, does it *work* with PCRE?
808 newrule->output = apr_pstrdup(cmd->pool, a2);
810 /* now, if the server or per-dir config holds an
811 * array of RewriteCond entries, we take it for us
812 * and clear the array
814 if (cmd->path == NULL) { /* is server command */
815 newrule->rewriteconds = sconf->rewriteconds;
816 sconf->rewriteconds = apr_make_array(cmd->pool, 2,
817 sizeof(rewritecond_entry));
819 else { /* is per-directory command */
820 newrule->rewriteconds = dconf->rewriteconds;
821 dconf->rewriteconds = apr_make_array(cmd->pool, 2,
822 sizeof(rewritecond_entry));
828 static const char *cmd_rewriterule_parseflagfield(apr_pool_t *p,
829 rewriterule_entry *cfg,
840 if (str[0] != '[' || str[strlen(str)-1] != ']') {
841 return "RewriteRule: bad flag delimiters";
845 str[strlen(str)-1] = ','; /* for simpler parsing */
846 for ( ; *cp != '\0'; ) {
847 /* skip whitespaces */
848 for ( ; (*cp == ' ' || *cp == '\t') && *cp != '\0'; cp++)
854 if ((cp2 = strchr(cp, ',')) != NULL) {
856 for ( ; (*(cp2-1) == ' ' || *(cp2-1) == '\t'); cp2--)
859 if ((cp3 = strchr(cp1, '=')) != NULL) {
868 if ((err = cmd_rewriterule_setflag(p, cfg, key, val)) != NULL) {
880 static const char *cmd_rewriterule_setflag(apr_pool_t *p, rewriterule_entry *cfg,
881 char *key, char *val)
886 if ( strcasecmp(key, "redirect") == 0
887 || strcasecmp(key, "R") == 0 ) {
888 cfg->flags |= RULEFLAG_FORCEREDIRECT;
889 if (strlen(val) > 0) {
890 if (strcasecmp(val, "permanent") == 0) {
891 status = HTTP_MOVED_PERMANENTLY;
893 else if (strcasecmp(val, "temp") == 0) {
894 status = HTTP_MOVED_TEMPORARILY;
896 else if (strcasecmp(val, "seeother") == 0) {
897 status = HTTP_SEE_OTHER;
899 else if (apr_isdigit(*val)) {
902 if (!ap_is_HTTP_REDIRECT(status)) {
903 return "RewriteRule: invalid HTTP response code "
906 cfg->forced_responsecode = status;
909 else if ( strcasecmp(key, "last") == 0
910 || strcasecmp(key, "L") == 0 ) {
911 cfg->flags |= RULEFLAG_LASTRULE;
913 else if ( strcasecmp(key, "next") == 0
914 || strcasecmp(key, "N") == 0 ) {
915 cfg->flags |= RULEFLAG_NEWROUND;
917 else if ( strcasecmp(key, "chain") == 0
918 || strcasecmp(key, "C") == 0 ) {
919 cfg->flags |= RULEFLAG_CHAIN;
921 else if ( strcasecmp(key, "type") == 0
922 || strcasecmp(key, "T") == 0 ) {
923 cfg->forced_mimetype = apr_pstrdup(p, val);
924 ap_str_tolower(cfg->forced_mimetype);
926 else if ( strcasecmp(key, "env") == 0
927 || strcasecmp(key, "E") == 0 ) {
928 for (i = 0; (cfg->env[i] != NULL) && (i < MAX_ENV_FLAGS); i++)
930 if (i < MAX_ENV_FLAGS) {
931 cfg->env[i] = apr_pstrdup(p, val);
932 cfg->env[i+1] = NULL;
935 return "RewriteRule: too many environment flags 'E'";
938 else if ( strcasecmp(key, "nosubreq") == 0
939 || strcasecmp(key, "NS") == 0 ) {
940 cfg->flags |= RULEFLAG_IGNOREONSUBREQ;
942 else if ( strcasecmp(key, "proxy") == 0
943 || strcasecmp(key, "P") == 0 ) {
944 cfg->flags |= RULEFLAG_PROXY;
946 else if ( strcasecmp(key, "passthrough") == 0
947 || strcasecmp(key, "PT") == 0 ) {
948 cfg->flags |= RULEFLAG_PASSTHROUGH;
950 else if ( strcasecmp(key, "skip") == 0
951 || strcasecmp(key, "S") == 0 ) {
952 cfg->skip = atoi(val);
954 else if ( strcasecmp(key, "forbidden") == 0
955 || strcasecmp(key, "F") == 0 ) {
956 cfg->flags |= RULEFLAG_FORBIDDEN;
958 else if ( strcasecmp(key, "gone") == 0
959 || strcasecmp(key, "G") == 0 ) {
960 cfg->flags |= RULEFLAG_GONE;
962 else if ( strcasecmp(key, "qsappend") == 0
963 || strcasecmp(key, "QSA") == 0 ) {
964 cfg->flags |= RULEFLAG_QSAPPEND;
966 else if ( strcasecmp(key, "nocase") == 0
967 || strcasecmp(key, "NC") == 0 ) {
968 cfg->flags |= RULEFLAG_NOCASE;
971 return apr_pstrcat(p, "RewriteRule: unknown flag '", key, "'", NULL);
979 ** Global Module Initialization
980 ** [called from read_config() after all
981 ** config commands were already called]
985 static void init_module(apr_pool_t *p,
993 const char *userdata_key = "rewrite_init_module";
995 apr_get_userdata(&data, userdata_key, s->process->pool);
998 apr_set_userdata((const void *)1, userdata_key,
999 apr_null_cleanup, s->process->pool);
1002 /* check if proxy module is available */
1003 proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL);
1005 /* create the rewriting lockfiles in the parent */
1006 if ((rv = apr_create_lock (&rewrite_log_lock, APR_MUTEX, APR_LOCKALL,
1007 NULL, p)) != APR_SUCCESS) {
1008 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
1009 "mod_rewrite: could not create rewrite_log_lock");
1013 rewritelock_create(s, p);
1014 apr_register_cleanup(p, (void *)s, rewritelock_remove, apr_null_cleanup);
1016 /* step through the servers and
1017 * - open each rewriting logfile
1018 * - open the RewriteMap prg:xxx programs
1020 for (; s; s = s->next) {
1021 open_rewritelog(s, p);
1023 run_rewritemap_programs(s, p);
1030 ** Per-Child Module Initialization
1031 ** [called after a child process is spawned]
1035 static void init_child(apr_pool_t *p, server_rec *s)
1039 if (lockname != NULL && *(lockname) != '\0')
1041 rv = apr_child_init_lock (&rewrite_mapr_lock, lockname, p);
1042 if (rv != APR_SUCCESS) {
1043 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s,
1044 "mod_rewrite: could not init rewrite_mapr_lock "
1049 /* create the lookup cache */
1050 cachep = init_cache(p);
1055 ** +-------------------------------------------------------+
1059 ** +-------------------------------------------------------+
1064 ** URI-to-filename hook
1066 ** [used for the rewriting engine triggered by
1067 ** the per-server 'RewriteRule' directives]
1071 static int hook_uri2file(request_rec *r)
1074 rewrite_server_conf *conf;
1076 const char *thisserver;
1078 const char *thisurl;
1089 * retrieve the config structures
1091 sconf = r->server->module_config;
1092 conf = (rewrite_server_conf *)ap_get_module_config(sconf,
1096 * only do something under runtime if the engine is really enabled,
1097 * else return immediately!
1099 if (conf->state == ENGINE_DISABLED) {
1104 * check for the ugly API case of a virtual host section where no
1105 * mod_rewrite directives exists. In this situation we became no chance
1106 * by the API to setup our default per-server config so we have to
1107 * on-the-fly assume we have the default config. But because the default
1108 * config has a disabled rewriting engine we are lucky because can
1109 * just stop operating now.
1111 if (conf->server != r->server) {
1116 * add the SCRIPT_URL variable to the env. this is a bit complicated
1117 * due to the fact that apache uses subrequests and internal redirects
1120 if (r->main == NULL) {
1121 var = apr_pstrcat(r->pool, "REDIRECT_", ENVVAR_SCRIPT_URL, NULL);
1122 var = apr_table_get(r->subprocess_env, var);
1124 apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, r->uri);
1127 apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
1131 var = apr_table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL);
1132 apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var);
1136 * create the SCRIPT_URI variable for the env
1139 /* add the canonical URI of this URL */
1140 thisserver = ap_get_server_name(r);
1141 port = ap_get_server_port(r);
1142 if (ap_is_default_port(port, r)) {
1146 apr_snprintf(buf, sizeof(buf), ":%u", port);
1149 thisurl = apr_table_get(r->subprocess_env, ENVVAR_SCRIPT_URL);
1151 /* set the variable */
1152 var = apr_pstrcat(r->pool, ap_http_method(r), "://", thisserver, thisport,
1154 apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URI, var);
1156 /* if filename was not initially set,
1157 * we start with the requested URI
1159 if (r->filename == NULL) {
1160 r->filename = apr_pstrdup(r->pool, r->uri);
1161 rewritelog(r, 2, "init rewrite engine with requested uri %s",
1166 * now apply the rules ...
1168 if (apply_rewrite_list(r, conf->rewriterules, NULL)) {
1170 if (strlen(r->filename) > 6 &&
1171 strncmp(r->filename, "proxy:", 6) == 0) {
1172 /* it should be go on as an internal proxy request */
1174 /* check if the proxy module is enabled, so
1175 * we can actually use it!
1177 if (!proxy_available) {
1178 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1179 "attempt to make remote request from mod_rewrite "
1180 "without proxy enabled: %s", r->filename);
1181 return HTTP_FORBIDDEN;
1184 /* make sure the QUERY_STRING and
1185 * PATH_INFO parts get incorporated
1187 if (r->path_info != NULL) {
1188 r->filename = apr_pstrcat(r->pool, r->filename,
1189 r->path_info, NULL);
1191 if (r->args != NULL &&
1192 r->uri == r->unparsed_uri) {
1193 /* see proxy_http:proxy_http_canon() */
1194 r->filename = apr_pstrcat(r->pool, r->filename,
1195 "?", r->args, NULL);
1198 /* now make sure the request gets handled by the proxy handler */
1200 r->handler = "proxy-server";
1202 rewritelog(r, 1, "go-ahead with proxy request %s [OK]",
1206 else if (is_absolute_uri(r->filename)) {
1207 /* it was finally rewritten to a remote URL */
1209 /* skip 'scheme:' */
1210 for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1214 /* skip host part */
1215 for ( ; *cp != '/' && *cp != '\0'; cp++)
1218 rewritelog(r, 1, "escaping %s for redirect", r->filename);
1219 cp2 = ap_escape_uri(r->pool, cp);
1221 r->filename = apr_pstrcat(r->pool, r->filename, cp2, NULL);
1224 /* append the QUERY_STRING part */
1225 if (r->args != NULL) {
1226 r->filename = apr_pstrcat(r->pool, r->filename, "?",
1227 ap_escape_uri(r->pool, r->args), NULL);
1230 /* determine HTTP redirect response code */
1231 if (ap_is_HTTP_REDIRECT(r->status)) {
1233 r->status = HTTP_OK; /* make Apache kernel happy */
1236 n = HTTP_MOVED_TEMPORARILY;
1239 /* now do the redirection */
1240 apr_table_setn(r->headers_out, "Location", r->filename);
1241 rewritelog(r, 1, "redirect to %s [REDIRECT/%d]", r->filename, n);
1244 else if (strlen(r->filename) > 10 &&
1245 strncmp(r->filename, "forbidden:", 10) == 0) {
1246 /* This URLs is forced to be forbidden for the requester */
1247 return HTTP_FORBIDDEN;
1249 else if (strlen(r->filename) > 5 &&
1250 strncmp(r->filename, "gone:", 5) == 0) {
1251 /* This URLs is forced to be gone */
1254 else if (strlen(r->filename) > 12 &&
1255 strncmp(r->filename, "passthrough:", 12) == 0) {
1257 * Hack because of underpowered API: passing the current
1258 * rewritten filename through to other URL-to-filename handlers
1259 * just as it were the requested URL. This is to enable
1260 * post-processing by mod_alias, etc. which always act on
1261 * r->uri! The difference here is: We do not try to
1262 * add the document root
1264 r->uri = apr_pstrdup(r->pool, r->filename+12);
1268 /* it was finally rewritten to a local path */
1270 /* expand "/~user" prefix */
1272 r->filename = expand_tildepaths(r, r->filename);
1274 rewritelog(r, 2, "local path result: %s", r->filename);
1276 /* the filename has to start with a slash! */
1277 if (r->filename[0] != '/') {
1278 return HTTP_BAD_REQUEST;
1281 /* if there is no valid prefix, we have
1282 * to emulate the translator from the core and
1283 * prefix the filename with document_root
1286 * We cannot leave out the prefix_stat because
1287 * - when we always prefix with document_root
1288 * then no absolute path can be created, e.g. via
1289 * emulating a ScriptAlias directive, etc.
1290 * - when we always NOT prefix with document_root
1291 * then the files under document_root have to
1292 * be references directly and document_root
1293 * gets never used and will be a dummy parameter -
1297 * Under real Unix systems this is no problem,
1298 * because we only do stat() on the first directory
1299 * and this gets cached by the kernel for along time!
1301 n = prefix_stat(r->filename, &finfo);
1303 if ((ccp = ap_document_root(r)) != NULL) {
1304 l = apr_cpystrn(docroot, ccp, sizeof(docroot)) - docroot;
1306 /* always NOT have a trailing slash */
1307 if (docroot[l-1] == '/') {
1308 docroot[l-1] = '\0';
1311 && !strncmp(r->filename, r->server->path,
1312 r->server->pathlen)) {
1313 r->filename = apr_pstrcat(r->pool, docroot,
1315 r->server->pathlen), NULL);
1318 r->filename = apr_pstrcat(r->pool, docroot,
1321 rewritelog(r, 2, "prefixed with document_root to %s",
1326 rewritelog(r, 1, "go-ahead with %s [OK]", r->filename);
1331 rewritelog(r, 1, "pass through %s", r->filename);
1341 ** [used to support the forced-MIME-type feature]
1345 static int hook_mimetype(request_rec *r)
1349 /* now check if we have to force a MIME-type */
1350 t = apr_table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR);
1355 rewritelog(r, 1, "force filename %s to have MIME-type '%s'",
1357 r->content_type = t;
1367 ** [used for the rewriting engine triggered by
1368 ** the per-directory 'RewriteRule' directives]
1372 static int hook_fixup(request_rec *r)
1374 rewrite_perdir_conf *dconf;
1383 dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config,
1386 /* if there is no per-dir config we return immediately */
1387 if (dconf == NULL) {
1391 /* we shouldn't do anything in subrequests */
1392 if (r->main != NULL) {
1396 /* if there are no real (i.e. no RewriteRule directives!)
1397 per-dir config of us, we return also immediately */
1398 if (dconf->directory == NULL) {
1403 * only do something under runtime if the engine is really enabled,
1404 * for this directory, else return immediately!
1406 if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) {
1407 /* FollowSymLinks is mandatory! */
1408 ap_log_rerror(APLOG_MARK, APLOG_NOERRNO|APLOG_ERR, 0, r,
1409 "Options FollowSymLinks or SymLinksIfOwnerMatch is off "
1410 "which implies that RewriteRule directive is forbidden: "
1412 return HTTP_FORBIDDEN;
1415 /* FollowSymLinks is given, but the user can
1416 * still turn off the rewriting engine
1418 if (dconf->state == ENGINE_DISABLED) {
1424 * remember the current filename before rewriting for later check
1425 * to prevent deadlooping because of internal redirects
1426 * on final URL/filename which can be equal to the inital one.
1428 ofilename = r->filename;
1431 * now apply the rules ...
1433 if (apply_rewrite_list(r, dconf->rewriterules, dconf->directory)) {
1435 if (strlen(r->filename) > 6 &&
1436 strncmp(r->filename, "proxy:", 6) == 0) {
1437 /* it should go on as an internal proxy request */
1439 /* make sure the QUERY_STRING and
1440 * PATH_INFO parts get incorporated
1441 * (r->path_info was already appended by the
1442 * rewriting engine because of the per-dir context!)
1444 if (r->args != NULL) {
1445 r->filename = apr_pstrcat(r->pool, r->filename,
1446 "?", r->args, NULL);
1449 /* now make sure the request gets handled by the proxy handler */
1451 r->handler = "proxy-server";
1453 rewritelog(r, 1, "[per-dir %s] go-ahead with proxy request "
1454 "%s [OK]", dconf->directory, r->filename);
1457 else if (is_absolute_uri(r->filename)) {
1458 /* it was finally rewritten to a remote URL */
1460 /* because we are in a per-dir context
1461 * first try to replace the directory with its base-URL
1462 * if there is a base-URL available
1464 if (dconf->baseurl != NULL) {
1465 /* skip 'scheme:' */
1466 for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1470 if ((cp = strchr(cp, '/')) != NULL) {
1472 "[per-dir %s] trying to replace "
1473 "prefix %s with %s",
1474 dconf->directory, dconf->directory,
1476 cp2 = subst_prefix_path(r, cp, dconf->directory,
1478 if (strcmp(cp2, cp) != 0) {
1480 r->filename = apr_pstrcat(r->pool, r->filename,
1486 /* now prepare the redirect... */
1488 /* skip 'scheme:' */
1489 for (cp = r->filename; *cp != ':' && *cp != '\0'; cp++)
1493 /* skip host part */
1494 for ( ; *cp != '/' && *cp != '\0'; cp++)
1497 rewritelog(r, 1, "[per-dir %s] escaping %s for redirect",
1498 dconf->directory, r->filename);
1499 cp2 = ap_escape_uri(r->pool, cp);
1501 r->filename = apr_pstrcat(r->pool, r->filename, cp2, NULL);
1504 /* append the QUERY_STRING part */
1505 if (r->args != NULL) {
1506 r->filename = apr_pstrcat(r->pool, r->filename, "?",
1507 ap_escape_uri(r->pool, r->args), NULL);
1510 /* determine HTTP redirect response code */
1511 if (ap_is_HTTP_REDIRECT(r->status)) {
1513 r->status = HTTP_OK; /* make Apache kernel happy */
1516 n = HTTP_MOVED_TEMPORARILY;
1519 /* now do the redirection */
1520 apr_table_setn(r->headers_out, "Location", r->filename);
1521 rewritelog(r, 1, "[per-dir %s] redirect to %s [REDIRECT/%d]",
1522 dconf->directory, r->filename, n);
1525 else if (strlen(r->filename) > 10 &&
1526 strncmp(r->filename, "forbidden:", 10) == 0) {
1527 /* This URL is forced to be forbidden for the requester */
1528 return HTTP_FORBIDDEN;
1530 else if (strlen(r->filename) > 5 &&
1531 strncmp(r->filename, "gone:", 5) == 0) {
1532 /* This URL is forced to be gone */
1536 /* it was finally rewritten to a local path */
1538 /* if someone used the PASSTHROUGH flag in per-dir
1539 * context we just ignore it. It is only useful
1540 * in per-server context
1542 if (strlen(r->filename) > 12 &&
1543 strncmp(r->filename, "passthrough:", 12) == 0) {
1544 r->filename = apr_pstrdup(r->pool, r->filename+12);
1547 /* the filename has to start with a slash! */
1548 if (r->filename[0] != '/') {
1549 return HTTP_BAD_REQUEST;
1552 /* Check for deadlooping:
1553 * At this point we KNOW that at least one rewriting
1554 * rule was applied, but when the resulting URL is
1555 * the same as the initial URL, we are not allowed to
1556 * use the following internal redirection stuff because
1557 * this would lead to a deadloop.
1559 if (strcmp(r->filename, ofilename) == 0) {
1560 rewritelog(r, 1, "[per-dir %s] initial URL equal rewritten "
1561 "URL: %s [IGNORING REWRITE]",
1562 dconf->directory, r->filename);
1566 /* if there is a valid base-URL then substitute
1567 * the per-dir prefix with this base-URL if the
1568 * current filename still is inside this per-dir
1569 * context. If not then treat the result as a
1572 if (dconf->baseurl != NULL) {
1574 "[per-dir %s] trying to replace prefix %s with %s",
1575 dconf->directory, dconf->directory, dconf->baseurl);
1576 r->filename = subst_prefix_path(r, r->filename,
1581 /* if no explicit base-URL exists we assume
1582 * that the directory prefix is also a valid URL
1583 * for this webserver and only try to remove the
1584 * document_root if it is prefix
1586 if ((ccp = ap_document_root(r)) != NULL) {
1587 prefix = apr_pstrdup(r->pool, ccp);
1588 /* always NOT have a trailing slash */
1590 if (prefix[l-1] == '/') {
1594 if (strncmp(r->filename, prefix, l) == 0) {
1596 "[per-dir %s] strip document_root "
1598 dconf->directory, r->filename,
1600 r->filename = apr_pstrdup(r->pool, r->filename+l);
1605 /* now initiate the internal redirect */
1606 rewritelog(r, 1, "[per-dir %s] internal redirect with %s "
1607 "[INTERNAL REDIRECT]", dconf->directory, r->filename);
1608 r->filename = apr_pstrcat(r->pool, "redirect:", r->filename, NULL);
1609 r->handler = "redirect-handler";
1614 rewritelog(r, 1, "[per-dir %s] pass through %s",
1615 dconf->directory, r->filename);
1625 ** [used for redirect support]
1629 static int handler_redirect(request_rec *r)
1631 /* just make sure that we are really meant! */
1632 if (strncmp(r->filename, "redirect:", 9) != 0) {
1636 /* now do the internal redirect */
1637 ap_internal_redirect(apr_pstrcat(r->pool, r->filename+9,
1638 r->args ? "?" : NULL, r->args, NULL), r);
1640 /* and return gracefully */
1646 ** +-------------------------------------------------------+
1648 ** | the rewriting engine
1650 ** +-------------------------------------------------------+
1654 * Apply a complete rule set,
1655 * i.e. a list of rewrite rules
1657 static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules,
1660 rewriterule_entry *entries;
1661 rewriterule_entry *p;
1668 * Iterate over all existing rules
1670 entries = (rewriterule_entry *)rewriterules->elts;
1673 for (i = 0; i < rewriterules->nelts; i++) {
1677 * Ignore this rule on subrequests if we are explicitly
1678 * asked to do so or this is a proxy-throughput or a
1679 * forced redirect rule.
1681 if (r->main != NULL &&
1682 (p->flags & RULEFLAG_IGNOREONSUBREQ ||
1683 p->flags & RULEFLAG_PROXY ||
1684 p->flags & RULEFLAG_FORCEREDIRECT )) {
1689 * Apply the current rule.
1691 rc = apply_rewrite_rule(r, p, perdir);
1694 * Indicate a change if this was not a match-only rule.
1701 * Pass-Through Feature (`RewriteRule .. .. [PT]'):
1702 * Because the Apache 1.x API is very limited we
1703 * need this hack to pass the rewritten URL to other
1704 * modules like mod_alias, mod_userdir, etc.
1706 if (p->flags & RULEFLAG_PASSTHROUGH) {
1707 rewritelog(r, 2, "forcing '%s' to get passed through "
1708 "to next API URI-to-filename handler", r->filename);
1709 r->filename = apr_pstrcat(r->pool, "passthrough:",
1716 * Rule has the "forbidden" flag set which means that
1717 * we stop processing and indicate this to the caller.
1719 if (p->flags & RULEFLAG_FORBIDDEN) {
1720 rewritelog(r, 2, "forcing '%s' to be forbidden", r->filename);
1721 r->filename = apr_pstrcat(r->pool, "forbidden:",
1728 * Rule has the "gone" flag set which means that
1729 * we stop processing and indicate this to the caller.
1731 if (p->flags & RULEFLAG_GONE) {
1732 rewritelog(r, 2, "forcing '%s' to be gone", r->filename);
1733 r->filename = apr_pstrcat(r->pool, "gone:", r->filename, NULL);
1739 * Stop processing also on proxy pass-through and
1740 * last-rule and new-round flags.
1742 if (p->flags & RULEFLAG_PROXY) {
1745 if (p->flags & RULEFLAG_LASTRULE) {
1750 * On "new-round" flag we just start from the top of
1751 * the rewriting ruleset again.
1753 if (p->flags & RULEFLAG_NEWROUND) {
1758 * If we are forced to skip N next rules, do it now.
1762 while ( i < rewriterules->nelts
1772 * If current rule is chained with next rule(s),
1773 * skip all this next rule(s)
1775 while ( i < rewriterules->nelts
1776 && p->flags & RULEFLAG_CHAIN) {
1786 * Apply a single(!) rewrite rule
1788 static int apply_rewrite_rule(request_rec *r, rewriterule_entry *p,
1794 char newuri[MAX_STRING_LEN];
1796 regmatch_t regmatch[MAX_NMATCH];
1797 backrefinfo *briRR = NULL;
1798 backrefinfo *briRC = NULL;
1801 apr_array_header_t *rewriteconds;
1802 rewritecond_entry *conds;
1803 rewritecond_entry *c;
1815 * Add (perhaps splitted away) PATH_INFO postfix to URL to
1816 * make sure we really match against the complete URL.
1818 if (perdir != NULL && r->path_info != NULL && r->path_info[0] != '\0') {
1819 rewritelog(r, 3, "[per-dir %s] add path info postfix: %s -> %s%s",
1820 perdir, uri, uri, r->path_info);
1821 uri = apr_pstrcat(r->pool, uri, r->path_info, NULL);
1825 * On per-directory context (.htaccess) strip the location
1826 * prefix from the URL to make sure patterns apply only to
1827 * the local part. Additionally indicate this special
1828 * threatment in the logfile.
1831 if (perdir != NULL) {
1832 if ( strlen(uri) >= strlen(perdir)
1833 && strncmp(uri, perdir, strlen(perdir)) == 0) {
1834 rewritelog(r, 3, "[per-dir %s] strip per-dir prefix: %s -> %s",
1835 perdir, uri, uri+strlen(perdir));
1836 uri = uri+strlen(perdir);
1842 * Try to match the URI against the RewriteRule pattern
1843 * and exit immeddiately if it didn't apply.
1845 if (perdir == NULL) {
1846 rewritelog(r, 3, "applying pattern '%s' to uri '%s'",
1850 rewritelog(r, 3, "[per-dir %s] applying pattern '%s' to uri '%s'",
1851 perdir, p->pattern, uri);
1853 rc = (ap_regexec(regexp, uri, regexp->re_nsub+1, regmatch, 0) == 0);
1854 if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) ||
1855 (!rc && (p->flags & RULEFLAG_NOTMATCH)) ) ) {
1860 * Else create the RewriteRule `regsubinfo' structure which
1861 * holds the substitution information.
1863 briRR = (backrefinfo *)apr_palloc(r->pool, sizeof(backrefinfo));
1864 if (!rc && (p->flags & RULEFLAG_NOTMATCH)) {
1865 /* empty info on negative patterns */
1870 briRR->source = apr_pstrdup(r->pool, uri);
1871 briRR->nsub = regexp->re_nsub;
1872 memcpy((void *)(briRR->regmatch), (void *)(regmatch),
1877 * Initiallally create the RewriteCond backrefinfo with
1878 * empty backrefinfo, i.e. not subst parts
1879 * (this one is adjusted inside apply_rewrite_cond() later!!)
1881 briRC = (backrefinfo *)apr_pcalloc(r->pool, sizeof(backrefinfo));
1886 * Ok, we already know the pattern has matched, but we now
1887 * additionally have to check for all existing preconditions
1888 * (RewriteCond) which have to be also true. We do this at
1889 * this very late stage to avoid unnessesary checks which
1890 * would slow down the rewriting engine!!
1892 rewriteconds = p->rewriteconds;
1893 conds = (rewritecond_entry *)rewriteconds->elts;
1895 for (i = 0; i < rewriteconds->nelts; i++) {
1897 rc = apply_rewrite_cond(r, c, perdir, briRR, briRC);
1898 if (c->flags & CONDFLAG_ORNEXT) {
1903 /* One condition is false, but another can be
1904 * still true, so we have to continue...
1906 apr_table_unset(r->notes, VARY_KEY_THIS);
1910 /* One true condition is enough in "or" case, so
1911 * skip the other conditions which are "ornext"
1914 while ( i < rewriteconds->nelts
1915 && c->flags & CONDFLAG_ORNEXT) {
1924 * The "AND" case, i.e. no "or" flag,
1925 * so a single failure means total failure.
1932 vary = apr_table_get(r->notes, VARY_KEY_THIS);
1934 apr_table_merge(r->notes, VARY_KEY, vary);
1935 apr_table_unset(r->notes, VARY_KEY_THIS);
1938 /* if any condition fails the complete rule fails */
1940 apr_table_unset(r->notes, VARY_KEY);
1941 apr_table_unset(r->notes, VARY_KEY_THIS);
1946 * Regardless of what we do next, we've found a match. Check to see
1947 * if any of the request header fields were involved, and add them
1948 * to the Vary field of the response.
1950 if ((vary = apr_table_get(r->notes, VARY_KEY)) != NULL) {
1951 apr_table_merge(r->headers_out, "Vary", vary);
1952 apr_table_unset(r->notes, VARY_KEY);
1956 * If this is a pure matching rule (`RewriteRule <pat> -')
1957 * we stop processing and return immediately. The only thing
1958 * we have not to forget are the environment variables
1959 * (`RewriteRule <pat> - [E=...]')
1961 if (strcmp(output, "-") == 0) {
1962 do_expand_env(r, p->env, briRR, briRC);
1963 if (p->forced_mimetype != NULL) {
1964 if (perdir == NULL) {
1965 /* In the per-server context we can force the MIME-type
1966 * the correct way by notifying our MIME-type hook handler
1967 * to do the job when the MIME-type API stage is reached.
1969 rewritelog(r, 2, "remember %s to have MIME-type '%s'",
1970 r->filename, p->forced_mimetype);
1971 apr_table_setn(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
1972 p->forced_mimetype);
1975 /* In per-directory context we operate in the Fixup API hook
1976 * which is after the MIME-type hook, so our MIME-type handler
1977 * has no chance to set r->content_type. And because we are
1978 * in the situation where no substitution takes place no
1979 * sub-request will happen (which could solve the
1980 * restriction). As a workaround we do it ourself now
1981 * immediately although this is not strictly API-conforming.
1982 * But it's the only chance we have...
1984 rewritelog(r, 1, "[per-dir %s] force %s to have MIME-type "
1985 "'%s'", perdir, r->filename, p->forced_mimetype);
1986 r->content_type = p->forced_mimetype;
1993 * Ok, now we finally know all patterns have matched and
1994 * that there is something to replace, so we create the
1995 * substitution URL string in `newuri'.
1997 do_expand(r, output, newuri, sizeof(newuri), briRR, briRC);
1998 if (perdir == NULL) {
1999 rewritelog(r, 2, "rewrite %s -> %s", uri, newuri);
2002 rewritelog(r, 2, "[per-dir %s] rewrite %s -> %s", perdir, uri, newuri);
2006 * Additionally do expansion for the environment variable
2007 * strings (`RewriteRule .. .. [E=<string>]').
2009 do_expand_env(r, p->env, briRR, briRC);
2012 * Now replace API's knowledge of the current URI:
2013 * Replace r->filename with the new URI string and split out
2014 * an on-the-fly generated QUERY_STRING part into r->args
2016 r->filename = apr_pstrdup(r->pool, newuri);
2017 splitout_queryargs(r, p->flags & RULEFLAG_QSAPPEND);
2020 * Again add the previously stripped per-directory location
2021 * prefix if the new URI is not a new one for this
2022 * location, i.e. if it's not starting with either a slash
2023 * or a fully qualified URL scheme.
2025 if (prefixstrip && r->filename[0] != '/'
2026 && !is_absolute_uri(r->filename)) {
2027 rewritelog(r, 3, "[per-dir %s] add per-dir prefix: %s -> %s%s",
2028 perdir, r->filename, perdir, r->filename);
2029 r->filename = apr_pstrcat(r->pool, perdir, r->filename, NULL);
2033 * If this rule is forced for proxy throughput
2034 * (`RewriteRule ... ... [P]') then emulate mod_proxy's
2035 * URL-to-filename handler to be sure mod_proxy is triggered
2036 * for this URL later in the Apache API. But make sure it is
2037 * a fully-qualified URL. (If not it is qualified with
2040 if (p->flags & RULEFLAG_PROXY) {
2041 fully_qualify_uri(r);
2042 if (perdir == NULL) {
2043 rewritelog(r, 2, "forcing proxy-throughput with %s", r->filename);
2046 rewritelog(r, 2, "[per-dir %s] forcing proxy-throughput with %s",
2047 perdir, r->filename);
2049 r->filename = apr_pstrcat(r->pool, "proxy:", r->filename, NULL);
2054 * If this rule is explicitly forced for HTTP redirection
2055 * (`RewriteRule .. .. [R]') then force an external HTTP
2056 * redirect. But make sure it is a fully-qualified URL. (If
2057 * not it is qualified with ourself).
2059 if (p->flags & RULEFLAG_FORCEREDIRECT) {
2060 fully_qualify_uri(r);
2061 if (perdir == NULL) {
2063 "explicitly forcing redirect with %s", r->filename);
2067 "[per-dir %s] explicitly forcing redirect with %s",
2068 perdir, r->filename);
2070 r->status = p->forced_responsecode;
2075 * Special Rewriting Feature: Self-Reduction
2076 * We reduce the URL by stripping a possible
2077 * http[s]://<ourhost>[:<port>] prefix, i.e. a prefix which
2078 * corresponds to ourself. This is to simplify rewrite maps
2079 * and to avoid recursion, etc. When this prefix is not a
2080 * coincidence then the user has to use [R] explicitly (see
2086 * If this rule is still implicitly forced for HTTP
2087 * redirection (`RewriteRule .. <scheme>://...') then
2088 * directly force an external HTTP redirect.
2090 if (is_absolute_uri(r->filename)) {
2091 if (perdir == NULL) {
2093 "implicitly forcing redirect (rc=%d) with %s",
2094 p->forced_responsecode, r->filename);
2097 rewritelog(r, 2, "[per-dir %s] implicitly forcing redirect "
2098 "(rc=%d) with %s", perdir, p->forced_responsecode,
2101 r->status = p->forced_responsecode;
2106 * Now we are sure it is not a fully qualified URL. But
2107 * there is still one special case left: A local rewrite in
2108 * per-directory context, i.e. a substitution URL which does
2109 * not start with a slash. Here we add again the initially
2110 * stripped per-directory prefix.
2112 if (prefixstrip && r->filename[0] != '/') {
2113 rewritelog(r, 3, "[per-dir %s] add per-dir prefix: %s -> %s%s",
2114 perdir, r->filename, perdir, r->filename);
2115 r->filename = apr_pstrcat(r->pool, perdir, r->filename, NULL);
2119 * Finally we had to remember if a MIME-type should be
2120 * forced for this URL (`RewriteRule .. .. [T=<type>]')
2121 * Later in the API processing phase this is forced by our
2122 * MIME API-hook function. This time its no problem even for
2123 * the per-directory context (where the MIME-type hook was
2124 * already processed) because a sub-request happens ;-)
2126 if (p->forced_mimetype != NULL) {
2127 apr_table_setn(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR,
2128 p->forced_mimetype);
2129 if (perdir == NULL) {
2130 rewritelog(r, 2, "remember %s to have MIME-type '%s'",
2131 r->filename, p->forced_mimetype);
2135 "[per-dir %s] remember %s to have MIME-type '%s'",
2136 perdir, r->filename, p->forced_mimetype);
2141 * Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_)
2142 * But now we're done for this particular rule.
2147 static int apply_rewrite_cond(request_rec *r, rewritecond_entry *p,
2148 char *perdir, backrefinfo *briRR,
2151 char input[MAX_STRING_LEN];
2154 regmatch_t regmatch[MAX_NMATCH];
2158 * Construct the string we match against
2161 do_expand(r, p->input, input, sizeof(input), briRR, briRC);
2164 * Apply the patterns
2168 if (strcmp(p->pattern, "-f") == 0) {
2169 if (apr_stat(&sb, input, r->pool) == APR_SUCCESS) {
2170 if (sb.filetype == APR_REG) {
2175 else if (strcmp(p->pattern, "-s") == 0) {
2176 if (apr_stat(&sb, input, r->pool) == APR_SUCCESS) {
2177 if ((sb.filetype == APR_REG) && sb.size > 0) {
2182 else if (strcmp(p->pattern, "-l") == 0) {
2184 if (apr_lstat(&sb, input, r->pool) == APR_SUCCESS) {
2185 if (sb.filetype == APR_LNK) {
2191 else if (strcmp(p->pattern, "-d") == 0) {
2192 if (apr_stat(&sb, input, r->pool) == APR_SUCCESS) {
2193 if (sb.filetype == APR_DIR) {
2198 else if (strcmp(p->pattern, "-U") == 0) {
2199 /* avoid infinite subrequest recursion */
2200 if (strlen(input) > 0 && subreq_ok(r)) {
2202 /* run a URI-based subrequest */
2203 rsub = ap_sub_req_lookup_uri(input, r, NULL);
2205 /* URI exists for any result up to 3xx, redirects allowed */
2206 if (rsub->status < 400)
2210 rewritelog(r, 5, "RewriteCond URI (-U) check: "
2211 "path=%s -> status=%d", input, rsub->status);
2213 /* cleanup by destroying the subrequest */
2214 ap_destroy_sub_req(rsub);
2217 else if (strcmp(p->pattern, "-F") == 0) {
2218 /* avoid infinite subrequest recursion */
2219 if (strlen(input) > 0 && subreq_ok(r)) {
2221 /* process a file-based subrequest:
2222 * this differs from -U in that no path translation is done.
2224 rsub = ap_sub_req_lookup_file(input, r, NULL);
2226 /* file exists for any result up to 2xx, no redirects */
2227 if (rsub->status < 300 &&
2228 /* double-check that file exists since default result is 200 */
2229 apr_stat(&sb, rsub->filename, r->pool) == APR_SUCCESS) {
2234 rewritelog(r, 5, "RewriteCond file (-F) check: path=%s "
2235 "-> file=%s status=%d", input, rsub->filename,
2238 /* cleanup by destroying the subrequest */
2239 ap_destroy_sub_req(rsub);
2242 else if (strlen(p->pattern) > 1 && *(p->pattern) == '>') {
2243 rc = (compare_lexicography(input, p->pattern+1) == 1 ? 1 : 0);
2245 else if (strlen(p->pattern) > 1 && *(p->pattern) == '<') {
2246 rc = (compare_lexicography(input, p->pattern+1) == -1 ? 1 : 0);
2248 else if (strlen(p->pattern) > 1 && *(p->pattern) == '=') {
2249 if (strcmp(p->pattern+1, "\"\"") == 0) {
2250 rc = (*input == '\0');
2253 rc = (strcmp(input, p->pattern+1) == 0 ? 1 : 0);
2257 /* it is really a regexp pattern, so apply it */
2258 rc = (ap_regexec(p->regexp, input,
2259 p->regexp->re_nsub+1, regmatch,0) == 0);
2261 /* if it isn't a negated pattern and really matched
2262 we update the passed-through regex subst info structure */
2263 if (rc && !(p->flags & CONDFLAG_NOTMATCH)) {
2264 briRC->source = apr_pstrdup(r->pool, input);
2265 briRC->nsub = p->regexp->re_nsub;
2266 memcpy((void *)(briRC->regmatch), (void *)(regmatch),
2271 /* if this is a non-matching regexp, just negate the result */
2272 if (p->flags & CONDFLAG_NOTMATCH) {
2276 rewritelog(r, 4, "RewriteCond: input='%s' pattern='%s%s' => %s",
2277 input, (p->flags & CONDFLAG_NOTMATCH ? "!" : ""),
2278 p->pattern, rc ? "matched" : "not-matched");
2280 /* end just return the result */
2286 ** +-------------------------------------------------------+
2288 ** | URL transformation functions
2290 ** +-------------------------------------------------------+
2296 ** perform all the expansions on the input string
2297 ** leaving the result in the supplied buffer
2301 static void do_expand(request_rec *r, char *input, char *buffer, int nbuf,
2302 backrefinfo *briRR, backrefinfo *briRC)
2308 * for security reasons this expansion must be perfomed in a
2309 * single pass, otherwise an attacker can arrange for the result
2310 * of an earlier expansion to include expansion specifiers that
2311 * are interpreted by a later expansion, producing results that
2312 * were not intended by the administrator.
2317 space = nbuf - 1; /* room for '\0' */
2320 span = strcspn(inp, "$%");
2324 memcpy(outp, inp, span);
2328 if (space == 0 || *inp == '\0') {
2331 /* now we have a '$' or a '%' */
2332 if (inp[1] == '{') {
2334 endp = find_closing_bracket(inp+2, '{', '}');
2339 if (inp[0] == '$') {
2340 /* ${...} map lookup expansion */
2342 * To make rewrite maps useful the lookup key and
2343 * default values must be expanded, so we make
2344 * recursive calls to do the work. For security
2345 * reasons we must never expand a string that includes
2346 * verbatim data from the network. The recursion here
2347 * isn't a problem because the result of expansion is
2348 * only passed to lookup_map() so it cannot be
2349 * re-expanded, only re-looked-up. Another way of
2350 * looking at it is that the recursion is entirely
2351 * driven by the syntax of the nested curly brackets.
2353 char *key, *dflt, *result;
2354 char xkey[MAX_STRING_LEN];
2355 char xdflt[MAX_STRING_LEN];
2357 key = strchr(inp, ':');
2363 dflt = strchr(key, '|');
2370 do_expand(r, key, xkey, sizeof(xkey), briRR, briRC);
2371 do_expand(r, dflt, xdflt, sizeof(xdflt), briRR, briRC);
2372 result = lookup_map(r, inp+2, xkey);
2373 if (result == NULL) {
2376 span = apr_cpystrn(outp, result, space) - outp;
2378 if (dflt != empty) {
2382 else if (inp[0] == '%') {
2383 /* %{...} variable lookup expansion */
2384 span = apr_cpystrn(outp, lookup_variable(r, inp+2), space) - outp;
2395 else if (apr_isdigit(inp[1])) {
2396 int n = inp[1] - '0';
2397 backrefinfo *bri = NULL;
2398 if (inp[0] == '$') {
2399 /* $N RewriteRule regexp backref expansion */
2402 else if (inp[0] == '%') {
2403 /* %N RewriteCond regexp backref expansion */
2406 /* see ap_pregsub() in src/main/util.c */
2407 if (bri && n <= bri->nsub &&
2408 bri->regmatch[n].rm_eo > bri->regmatch[n].rm_so) {
2409 span = bri->regmatch[n].rm_eo - bri->regmatch[n].rm_so;
2413 memcpy(outp, bri->source + bri->regmatch[n].rm_so, span);
2430 ** perform all the expansions on the environment variables
2434 static void do_expand_env(request_rec *r, char *env[],
2435 backrefinfo *briRR, backrefinfo *briRC)
2438 char buf[MAX_STRING_LEN];
2440 for (i = 0; env[i] != NULL; i++) {
2441 do_expand(r, env[i], buf, sizeof(buf), briRR, briRC);
2442 add_env_variable(r, buf);
2449 ** split out a QUERY_STRING part from
2450 ** the current URI string
2454 static void splitout_queryargs(request_rec *r, int qsappend)
2459 q = strchr(r->filename, '?');
2461 olduri = apr_pstrdup(r->pool, r->filename);
2464 r->args = apr_pstrcat(r->pool, q, "&", r->args, NULL);
2467 r->args = apr_pstrdup(r->pool, q);
2469 if (strlen(r->args) == 0) {
2471 rewritelog(r, 3, "split uri=%s -> uri=%s, args=<none>", olduri,
2475 if (r->args[strlen(r->args)-1] == '&') {
2476 r->args[strlen(r->args)-1] = '\0';
2478 rewritelog(r, 3, "split uri=%s -> uri=%s, args=%s", olduri,
2479 r->filename, r->args);
2488 ** strip 'http[s]://ourhost/' from URI
2492 static void reduce_uri(request_rec *r)
2495 unsigned short port;
2500 char host[LONG_STRING_LEN];
2501 char buf[MAX_STRING_LEN];
2505 cp = (char *)ap_http_method(r);
2507 if ( strlen(r->filename) > l+3
2508 && strncasecmp(r->filename, cp, l) == 0
2509 && r->filename[l] == ':'
2510 && r->filename[l+1] == '/'
2511 && r->filename[l+2] == '/' ) {
2512 /* there was really a rewrite to a remote path */
2514 olduri = apr_pstrdup(r->pool, r->filename); /* save for logging */
2516 /* cut the hostname and port out of the URI */
2517 apr_cpystrn(buf, r->filename+(l+3), sizeof(buf));
2519 for (cp = hostp; *cp != '\0' && *cp != '/' && *cp != ':'; cp++)
2524 apr_cpystrn(host, hostp, sizeof(host));
2527 for (; *cp != '\0' && *cp != '/'; cp++)
2533 /* set remaining url */
2536 else if (*cp == '/') {
2539 apr_cpystrn(host, hostp, sizeof(host));
2542 port = ap_default_port(r);
2543 /* set remaining url */
2548 apr_cpystrn(host, hostp, sizeof(host));
2550 port = ap_default_port(r);
2551 /* set remaining url */
2555 /* now check whether we could reduce it to a local path... */
2556 if (ap_matches_request_vhost(r, host, port)) {
2557 /* this is our host, so only the URL remains */
2558 r->filename = apr_pstrdup(r->pool, url);
2559 rewritelog(r, 3, "reduce %s -> %s", olduri, r->filename);
2568 ** add 'http[s]://ourhost[:ourport]/' to URI
2569 ** if URI is still not fully qualified
2573 static void fully_qualify_uri(request_rec *r)
2576 const char *thisserver;
2580 if (!is_absolute_uri(r->filename)) {
2582 thisserver = ap_get_server_name(r);
2583 port = ap_get_server_port(r);
2584 if (ap_is_default_port(port,r)) {
2588 apr_snprintf(buf, sizeof(buf), ":%u", port);
2592 if (r->filename[0] == '/') {
2593 r->filename = apr_psprintf(r->pool, "%s://%s%s%s",
2594 ap_http_method(r), thisserver,
2595 thisport, r->filename);
2598 r->filename = apr_psprintf(r->pool, "%s://%s%s/%s",
2599 ap_http_method(r), thisserver,
2600 thisport, r->filename);
2609 ** return non-zero if the URI is absolute (includes a scheme etc.)
2613 static int is_absolute_uri(char *uri)
2615 int i = strlen(uri);
2616 if ( (i > 7 && strncasecmp(uri, "http://", 7) == 0)
2617 || (i > 8 && strncasecmp(uri, "https://", 8) == 0)
2618 || (i > 9 && strncasecmp(uri, "gopher://", 9) == 0)
2619 || (i > 6 && strncasecmp(uri, "ftp://", 6) == 0)
2620 || (i > 5 && strncasecmp(uri, "ldap:", 5) == 0)
2621 || (i > 5 && strncasecmp(uri, "news:", 5) == 0)
2622 || (i > 7 && strncasecmp(uri, "mailto:", 7) == 0) ) {
2633 ** Expand tilde-paths (/~user) through Unix /etc/passwd
2634 ** database information (or other OS-specific database)
2638 static char *expand_tildepaths(request_rec *r, char *uri)
2640 char user[LONG_STRING_LEN];
2646 if (uri != NULL && strlen(uri) > 2 && uri[0] == '/' && uri[1] == '~') {
2647 /* cut out the username */
2648 for (j = 0, i = 2; j < sizeof(user)-1
2650 && uri[i] != '/' ; ) {
2651 user[j++] = uri[i++];
2655 /* lookup username in systems passwd file */
2656 if (apr_get_home_directory(&homedir, user, r->pool) == APR_SUCCESS) {
2657 /* ok, user was found, so expand the ~user string */
2658 if (uri[i] != '\0') {
2659 /* ~user/anything... has to be expanded */
2660 if (homedir[strlen(homedir)-1] == '/') {
2661 homedir[strlen(homedir)-1] = '\0';
2663 newuri = apr_pstrcat(r->pool, homedir, uri+i, NULL);
2666 /* only ~user has to be expanded */
2673 #endif /* if APR_HAS_USER */
2678 ** +-------------------------------------------------------+
2680 ** | DBM hashfile support
2682 ** +-------------------------------------------------------+
2686 static char *lookup_map(request_rec *r, char *name, char *key)
2689 rewrite_server_conf *conf;
2690 apr_array_header_t *rewritemaps;
2691 rewritemap_entry *entries;
2692 rewritemap_entry *s;
2698 /* get map configuration */
2699 sconf = r->server->module_config;
2700 conf = (rewrite_server_conf *)ap_get_module_config(sconf,
2702 rewritemaps = conf->rewritemaps;
2704 entries = (rewritemap_entry *)rewritemaps->elts;
2705 for (i = 0; i < rewritemaps->nelts; i++) {
2707 if (strcmp(s->name, name) == 0) {
2708 if (s->type == MAPTYPE_TXT) {
2709 if ((rv = apr_stat(&st, s->checkfile, r->pool)) != APR_SUCCESS) {
2710 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
2711 "mod_rewrite: can't access text RewriteMap "
2712 "file %s", s->checkfile);
2713 rewritelog(r, 1, "can't open RewriteMap file, "
2717 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2719 if (value == NULL) {
2720 rewritelog(r, 6, "cache lookup FAILED, forcing new "
2723 lookup_map_txtfile(r, s->datafile, key)) != NULL) {
2724 rewritelog(r, 5, "map lookup OK: map=%s key=%s[txt] "
2725 "-> val=%s", s->name, key, value);
2726 set_cache_string(cachep, s->name, CACHEMODE_TS,
2727 st.mtime, key, value);
2731 rewritelog(r, 5, "map lookup FAILED: map=%s[txt] "
2732 "key=%s", s->name, key);
2733 set_cache_string(cachep, s->name, CACHEMODE_TS,
2739 rewritelog(r, 5, "cache lookup OK: map=%s[txt] key=%s "
2740 "-> val=%s", s->name, key, value);
2741 return value[0] != '\0' ? value : NULL;
2744 else if (s->type == MAPTYPE_DBM) {
2745 #ifndef NO_DBM_REWRITEMAP
2746 if ((rv = apr_stat(&st, s->checkfile, r->pool)) != APR_SUCCESS) {
2747 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
2748 "mod_rewrite: can't access DBM RewriteMap "
2749 "file %s", s->checkfile);
2750 rewritelog(r, 1, "can't open DBM RewriteMap file, "
2754 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2756 if (value == NULL) {
2758 "cache lookup FAILED, forcing new map lookup");
2760 lookup_map_dbmfile(r, s->datafile, key)) != NULL) {
2761 rewritelog(r, 5, "map lookup OK: map=%s[dbm] key=%s "
2762 "-> val=%s", s->name, key, value);
2763 set_cache_string(cachep, s->name, CACHEMODE_TS,
2764 st.mtime, key, value);
2768 rewritelog(r, 5, "map lookup FAILED: map=%s[dbm] "
2769 "key=%s", s->name, key);
2770 set_cache_string(cachep, s->name, CACHEMODE_TS,
2776 rewritelog(r, 5, "cache lookup OK: map=%s[dbm] key=%s "
2777 "-> val=%s", s->name, key, value);
2778 return value[0] != '\0' ? value : NULL;
2784 else if (s->type == MAPTYPE_PRG) {
2786 lookup_map_program(r, s->fpin, s->fpout, key)) != NULL) {
2787 rewritelog(r, 5, "map lookup OK: map=%s key=%s -> val=%s",
2788 s->name, key, value);
2792 rewritelog(r, 5, "map lookup FAILED: map=%s key=%s",
2796 else if (s->type == MAPTYPE_INT) {
2797 if ((value = lookup_map_internal(r, s->func, key)) != NULL) {
2798 rewritelog(r, 5, "map lookup OK: map=%s key=%s -> val=%s",
2799 s->name, key, value);
2803 rewritelog(r, 5, "map lookup FAILED: map=%s key=%s",
2807 else if (s->type == MAPTYPE_RND) {
2808 if ((rv = apr_stat(&st, s->checkfile, r->pool)) != APR_SUCCESS) {
2809 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r,
2810 "mod_rewrite: can't access text RewriteMap "
2811 "file %s", s->checkfile);
2812 rewritelog(r, 1, "can't open RewriteMap file, "
2816 value = get_cache_string(cachep, s->name, CACHEMODE_TS,
2818 if (value == NULL) {
2819 rewritelog(r, 6, "cache lookup FAILED, forcing new "
2822 lookup_map_txtfile(r, s->datafile, key)) != NULL) {
2823 rewritelog(r, 5, "map lookup OK: map=%s key=%s[txt] "
2824 "-> val=%s", s->name, key, value);
2825 set_cache_string(cachep, s->name, CACHEMODE_TS,
2826 st.mtime, key, value);
2829 rewritelog(r, 5, "map lookup FAILED: map=%s[txt] "
2830 "key=%s", s->name, key);
2831 set_cache_string(cachep, s->name, CACHEMODE_TS,
2837 rewritelog(r, 5, "cache lookup OK: map=%s[txt] key=%s "
2838 "-> val=%s", s->name, key, value);
2840 if (value[0] != '\0') {
2841 value = select_random_value_part(r, value);
2842 rewritelog(r, 5, "randomly choosen the subvalue `%s'", value);
2854 static char *lookup_map_txtfile(request_rec *r, const char *file, char *key)
2856 apr_file_t *fp = NULL;
2865 rc = apr_open(&fp, file, APR_READ, APR_OS_DEFAULT, r->pool);
2866 if (rc != APR_SUCCESS) {
2870 while (apr_fgets(line, sizeof(line), fp) == APR_SUCCESS) {
2872 continue; /* ignore comments */
2875 skip = strcspn(cpT," \t\r\n");
2877 continue; /* ignore lines that start with a space, tab, CR, or LF */
2880 if (strcmp(curkey, key) != 0)
2881 continue; /* key does not match... */
2883 /* found a matching key; now extract and return the value */
2885 skip = strspn(cpT, " \t\r\n");
2888 skip = strcspn(cpT, " \t\r\n");
2890 continue; /* no value... */
2893 value = apr_pstrdup(r->pool, curval);
2900 #ifndef NO_DBM_REWRITEMAP
2901 static char *lookup_map_dbmfile(request_rec *r, const char *file, char *key)
2907 char buf[MAX_STRING_LEN];
2910 dbmkey.dsize = strlen(key);
2911 if ((dbmfp = dbm_open(file, O_RDONLY, 0666)) != NULL) {
2912 dbmval = dbm_fetch(dbmfp, dbmkey);
2913 if (dbmval.dptr != NULL) {
2914 memcpy(buf, dbmval.dptr,
2915 dbmval.dsize < sizeof(buf)-1 ?
2916 dbmval.dsize : sizeof(buf)-1 );
2917 buf[dbmval.dsize] = '\0';
2918 value = apr_pstrdup(r->pool, buf);
2926 static char *lookup_map_program(request_rec *r, apr_file_t *fpin,
2927 apr_file_t *fpout, char *key)
2929 char buf[LONG_STRING_LEN];
2935 struct iovec iova[2];
2939 /* when `RewriteEngine off' was used in the per-server
2940 * context then the rewritemap-programs were not spawned.
2941 * In this case using such a map (usually in per-dir context)
2942 * is useless because it is not available.
2944 if (fpin == NULL || fpout == NULL) {
2950 if (rewrite_mapr_lock) {
2951 apr_lock(rewrite_mapr_lock);
2954 /* write out the request key */
2956 nbytes = strlen(key);
2957 apr_write(fpin, key, &nbytes);
2959 apr_write(fpin, "\n", &nbytes);
2961 iova[0].iov_base = key;
2962 iova[0].iov_len = strlen(key);
2963 iova[1].iov_base = "\n";
2964 iova[1].iov_len = 1;
2967 apr_writev(fpin, iova, niov, &nbytes);
2970 /* read in the response value */
2973 apr_read(fpout, &c, &nbytes);
2974 while (nbytes == 1 && (i < LONG_STRING_LEN-1)) {
2980 apr_read(fpout, &c, &nbytes);
2984 /* give the lock back */
2985 if (rewrite_mapr_lock) {
2986 apr_unlock(rewrite_mapr_lock);
2989 if (strcasecmp(buf, "NULL") == 0) {
2993 return apr_pstrdup(r->pool, buf);
2997 static char *lookup_map_internal(request_rec *r,
2998 char *(*func)(request_rec *, char *),
3001 /* currently we just let the function convert
3002 the key to a corresponding value */
3003 return func(r, key);
3006 static char *rewrite_mapfunc_toupper(request_rec *r, char *key)
3010 for (cp = value = apr_pstrdup(r->pool, key); cp != NULL && *cp != '\0';
3012 *cp = apr_toupper(*cp);
3017 static char *rewrite_mapfunc_tolower(request_rec *r, char *key)
3021 for (cp = value = apr_pstrdup(r->pool, key); cp != NULL && *cp != '\0';
3023 *cp = apr_tolower(*cp);
3028 static char *rewrite_mapfunc_escape(request_rec *r, char *key)
3032 value = ap_escape_uri(r->pool, key);
3036 static char *rewrite_mapfunc_unescape(request_rec *r, char *key)
3040 value = apr_pstrdup(r->pool, key);
3041 ap_unescape_url(value);
3045 static int rewrite_rand_init_done = 0;
3047 static void rewrite_rand_init(void)
3049 if (!rewrite_rand_init_done) {
3050 srand((unsigned)(getpid()));
3051 rewrite_rand_init_done = 1;
3056 static int rewrite_rand(int l, int h)
3058 rewrite_rand_init();
3060 /* Get [0,1) and then scale to the appropriate range. Note that using
3061 * a floating point value ensures that we use all bits of the rand()
3062 * result. Doing an integer modulus would only use the lower-order bits
3063 * which may not be as uniformly random.
3065 return ((double)(rand() % RAND_MAX) / RAND_MAX) * (h - l + 1) + l;
3068 static char *select_random_value_part(request_rec *r, char *value)
3073 /* count number of distinct values */
3074 for (n = 1, i = 0; value[i] != '\0'; i++) {
3075 if (value[i] == '|') {
3080 /* when only one value we have no option to choose */
3085 /* else randomly select one */
3086 k = rewrite_rand(1, n);
3088 /* and grep it out */
3089 for (n = 1, i = 0; value[i] != '\0'; i++) {
3093 if (value[i] == '|') {
3097 buf = apr_pstrdup(r->pool, &value[i]);
3098 for (i = 0; buf[i] != '\0' && buf[i] != '|'; i++)
3106 ** +-------------------------------------------------------+
3108 ** | rewriting logfile support
3110 ** +-------------------------------------------------------+
3114 static void open_rewritelog(server_rec *s, apr_pool_t *p)
3116 rewrite_server_conf *conf;
3120 int rewritelog_flags = ( APR_WRITE | APR_APPEND | APR_CREATE );
3121 mode_t rewritelog_mode = ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD );
3123 conf = ap_get_module_config(s->module_config, &rewrite_module);
3125 if (conf->rewritelogfile == NULL) {
3128 if (*(conf->rewritelogfile) == '\0') {
3131 if (conf->rewritelogfp != NULL) {
3132 return; /* virtual log shared w/ main server */
3135 fname = ap_server_root_relative(p, conf->rewritelogfile);
3137 if (*conf->rewritelogfile == '|') {
3138 if ((pl = ap_open_piped_log(p, conf->rewritelogfile+1)) == NULL) {
3139 ap_log_error(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, 0, s,
3140 "mod_rewrite: could not open reliable pipe "
3141 "to RewriteLog filter %s", conf->rewritelogfile+1);
3144 conf->rewritelogfp = ap_piped_log_write_fd(pl);
3146 else if (*conf->rewritelogfile != '\0') {
3147 rc = apr_open(&conf->rewritelogfp, fname, rewritelog_flags, rewritelog_mode, p);
3148 if (rc != APR_SUCCESS) {
3149 ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
3150 "mod_rewrite: could not open RewriteLog "
3158 static void rewritelog(request_rec *r, int level, const char *text, ...)
3160 rewrite_server_conf *conf;
3175 conf = ap_get_module_config(r->server->module_config, &rewrite_module);
3176 conn = r->connection;
3178 if (conf->rewritelogfp == NULL) {
3181 if (conf->rewritelogfile == NULL) {
3184 if (*(conf->rewritelogfile) == '\0') {
3188 if (level > conf->rewriteloglevel) {
3192 if (r->user == NULL) {
3195 else if (strlen(r->user) != 0) {
3202 rhost = ap_get_remote_host(conn, r->server->module_config,
3204 if (rhost == NULL) {
3205 rhost = "UNKNOWN-HOST";
3208 str1 = apr_pstrcat(r->pool, rhost, " ",
3209 (conn->remote_logname != NULL ?
3210 conn->remote_logname : "-"), " ",
3212 apr_vsnprintf(str2, sizeof(str2), text, ap);
3214 if (r->main == NULL) {
3215 strcpy(type, "initial");
3218 strcpy(type, "subreq");
3221 for (i = 0, req = r; req->prev != NULL; req = req->prev) {
3228 apr_snprintf(redir, sizeof(redir), "/redir#%d", i);
3231 apr_snprintf(str3, sizeof(str3),
3232 "%s %s [%s/sid#%lx][rid#%lx/%s%s] (%d) %s\n", str1,
3233 current_logtime(r), ap_get_server_name(r),
3234 (unsigned long)(r->server), (unsigned long)r,
3235 type, redir, level, str2);
3237 apr_lock(rewrite_log_lock);
3238 nbytes = strlen(str3);
3239 apr_write(conf->rewritelogfp, str3, &nbytes);
3240 apr_unlock(rewrite_log_lock);
3246 static char *current_logtime(request_rec *r)
3248 apr_exploded_time_t t;
3252 apr_explode_localtime(&t, apr_now());
3254 apr_strftime(tstr, &len, 80, "[%d/%b/%Y:%H:%M:%S ", &t);
3255 apr_snprintf(tstr + strlen(tstr), 80-strlen(tstr), "%c%.2d%.2d]",
3256 t.tm_gmtoff < 0 ? '-' : '+',
3257 t.tm_gmtoff / (60*60), t.tm_gmtoff % (60*60));
3258 return apr_pstrdup(r->pool, tstr);
3265 ** +-------------------------------------------------------+
3267 ** | rewriting lockfile support
3269 ** +-------------------------------------------------------+
3272 #define REWRITELOCK_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD )
3274 static void rewritelock_create(server_rec *s, apr_pool_t *p)
3278 /* only operate if a lockfile is used */
3279 if (lockname == NULL || *(lockname) == '\0') {
3283 /* fixup the path, especially for rewritelock_remove() */
3284 lockname = ap_server_root_relative(p, lockname);
3286 /* create the lockfile */
3287 rc = apr_create_lock (&rewrite_mapr_lock, APR_MUTEX, APR_LOCKALL, lockname, p);
3288 if (rc != APR_SUCCESS) {
3289 ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
3290 "mod_rewrite: Parent could not create RewriteLock "
3291 "file %s", lockname);
3298 static apr_status_t rewritelock_remove(void *data)
3300 /* only operate if a lockfile is used */
3301 if (lockname == NULL || *(lockname) == '\0') {
3305 /* destroy the rewritelock */
3306 apr_destroy_lock (rewrite_mapr_lock);
3307 rewrite_mapr_lock = NULL;
3314 ** +-------------------------------------------------------+
3316 ** | program map support
3318 ** +-------------------------------------------------------+
3321 static void run_rewritemap_programs(server_rec *s, apr_pool_t *p)
3323 rewrite_server_conf *conf;
3324 apr_file_t *fpin = NULL;
3325 apr_file_t *fpout = NULL;
3326 apr_file_t *fperr = NULL;
3327 apr_array_header_t *rewritemaps;
3328 rewritemap_entry *entries;
3329 rewritemap_entry *map;
3333 conf = ap_get_module_config(s->module_config, &rewrite_module);
3335 /* If the engine isn't turned on,
3336 * don't even try to do anything.
3338 if (conf->state == ENGINE_DISABLED) {
3342 rewritemaps = conf->rewritemaps;
3343 entries = (rewritemap_entry *)rewritemaps->elts;
3344 for (i = 0; i < rewritemaps->nelts; i++) {
3346 if (map->type != MAPTYPE_PRG) {
3349 if (map->datafile == NULL
3350 || *(map->datafile) == '\0'
3351 || map->fpin != NULL
3352 || map->fpout != NULL ) {
3357 rc = rewritemap_program_child(p, map->datafile,
3358 &fpout, &fpin, &fperr);
3359 if (rc != APR_SUCCESS || fpin == NULL || fpout == NULL) {
3360 ap_log_error(APLOG_MARK, APLOG_ERR, rc, s,
3361 "mod_rewrite: could not fork child for "
3362 "RewriteMap process");
3372 /* child process code */
3373 static apr_status_t rewritemap_program_child(apr_pool_t *p, const char *progname,
3374 apr_file_t **fpout, apr_file_t **fpin,
3378 apr_procattr_t *procattr;
3379 apr_proc_t *procnew;
3382 apr_signal(SIGHUP, SIG_IGN);
3386 if (((rc = apr_createprocattr_init(&procattr, p)) != APR_SUCCESS) ||
3387 ((rc = apr_setprocattr_io(procattr, APR_FULL_BLOCK,
3389 APR_FULL_NONBLOCK)) != APR_SUCCESS) ||
3390 ((rc = apr_setprocattr_dir(procattr,
3391 ap_make_dirstr_parent(p, progname)))
3393 ((rc = apr_setprocattr_cmdtype(procattr, APR_PROGRAM)) != APR_SUCCESS)) {
3394 /* Something bad happened, give up and go away. */
3397 procnew = apr_pcalloc(p, sizeof(*procnew));
3398 rc = apr_create_process(procnew, progname, NULL, NULL, procattr, p);
3400 if (rc == APR_SUCCESS) {
3401 apr_note_subprocess(p, procnew, kill_after_timeout);
3404 (*fpin) = procnew->in;
3408 (*fpout) = procnew->out;
3412 (*fperr) = procnew->err;
3424 ** +-------------------------------------------------------+
3426 ** | environment variable support
3428 ** +-------------------------------------------------------+
3432 static char *lookup_variable(request_rec *r, char *var)
3435 char resultbuf[LONG_STRING_LEN];
3436 apr_exploded_time_t tm;
3447 if (strcasecmp(var, "HTTP_USER_AGENT") == 0) {
3448 result = lookup_header(r, "User-Agent");
3450 else if (strcasecmp(var, "HTTP_REFERER") == 0) {
3451 result = lookup_header(r, "Referer");
3453 else if (strcasecmp(var, "HTTP_COOKIE") == 0) {
3454 result = lookup_header(r, "Cookie");
3456 else if (strcasecmp(var, "HTTP_FORWARDED") == 0) {
3457 result = lookup_header(r, "Forwarded");
3459 else if (strcasecmp(var, "HTTP_HOST") == 0) {
3460 result = lookup_header(r, "Host");
3462 else if (strcasecmp(var, "HTTP_PROXY_CONNECTION") == 0) {
3463 result = lookup_header(r, "Proxy-Connection");
3465 else if (strcasecmp(var, "HTTP_ACCEPT") == 0) {
3466 result = lookup_header(r, "Accept");
3468 /* all other headers from which we are still not know about */
3469 else if (strlen(var) > 5 && strncasecmp(var, "HTTP:", 5) == 0) {
3470 result = lookup_header(r, var+5);
3473 /* connection stuff */
3474 else if (strcasecmp(var, "REMOTE_ADDR") == 0) {
3475 result = r->connection->remote_ip;
3477 else if (strcasecmp(var, "REMOTE_HOST") == 0) {
3478 result = (char *)ap_get_remote_host(r->connection,
3479 r->per_dir_config, REMOTE_NAME);
3481 else if (strcasecmp(var, "REMOTE_USER") == 0) {
3484 else if (strcasecmp(var, "REMOTE_IDENT") == 0) {
3485 result = (char *)ap_get_remote_logname(r);
3489 else if (strcasecmp(var, "THE_REQUEST") == 0) { /* non-standard */
3490 result = r->the_request;
3492 else if (strcasecmp(var, "REQUEST_METHOD") == 0) {
3495 else if (strcasecmp(var, "REQUEST_URI") == 0) { /* non-standard */
3498 else if (strcasecmp(var, "SCRIPT_FILENAME") == 0 ||
3499 strcasecmp(var, "REQUEST_FILENAME") == 0 ) {
3500 result = r->filename;
3502 else if (strcasecmp(var, "PATH_INFO") == 0) {
3503 result = r->path_info;
3505 else if (strcasecmp(var, "QUERY_STRING") == 0) {
3508 else if (strcasecmp(var, "AUTH_TYPE") == 0) {
3509 result = r->ap_auth_type;
3511 else if (strcasecmp(var, "IS_SUBREQ") == 0) { /* non-standard */
3512 result = (r->main != NULL ? "true" : "false");
3515 /* internal server stuff */
3516 else if (strcasecmp(var, "DOCUMENT_ROOT") == 0) {
3517 result = ap_document_root(r);
3519 else if (strcasecmp(var, "SERVER_ADMIN") == 0) {
3520 result = r->server->server_admin;
3522 else if (strcasecmp(var, "SERVER_NAME") == 0) {
3523 result = ap_get_server_name(r);
3525 else if (strcasecmp(var, "SERVER_ADDR") == 0) { /* non-standard */
3526 result = r->connection->local_ip;
3528 else if (strcasecmp(var, "SERVER_PORT") == 0) {
3529 apr_snprintf(resultbuf, sizeof(resultbuf), "%u", ap_get_server_port(r));
3532 else if (strcasecmp(var, "SERVER_PROTOCOL") == 0) {
3533 result = r->protocol;
3535 else if (strcasecmp(var, "SERVER_SOFTWARE") == 0) {
3536 result = ap_get_server_version();
3538 else if (strcasecmp(var, "API_VERSION") == 0) { /* non-standard */
3539 apr_snprintf(resultbuf, sizeof(resultbuf), "%d:%d",
3540 MODULE_MAGIC_NUMBER_MAJOR, MODULE_MAGIC_NUMBER_MINOR);
3544 /* XXX: wow this has gotta be slow if you actually use it for a lot, recalculates exploded time for each variable */
3545 /* underlaying Unix system stuff */
3546 else if (strcasecmp(var, "TIME_YEAR") == 0) {
3547 apr_explode_localtime(&tm, apr_now());
3548 apr_snprintf(resultbuf, sizeof(resultbuf), "%04d", tm.tm_year + 1900);
3551 #define MKTIMESTR(format, tmfield) \
3552 apr_explode_localtime(&tm, apr_now()); \
3553 apr_snprintf(resultbuf, sizeof(resultbuf), format, tm.tmfield); \
3555 else if (strcasecmp(var, "TIME_MON") == 0) {
3556 MKTIMESTR("%02d", tm_mon+1)
3558 else if (strcasecmp(var, "TIME_DAY") == 0) {
3559 MKTIMESTR("%02d", tm_mday)
3561 else if (strcasecmp(var, "TIME_HOUR") == 0) {
3562 MKTIMESTR("%02d", tm_hour)
3564 else if (strcasecmp(var, "TIME_MIN") == 0) {
3565 MKTIMESTR("%02d", tm_min)
3567 else if (strcasecmp(var, "TIME_SEC") == 0) {
3568 MKTIMESTR("%02d", tm_sec)
3570 else if (strcasecmp(var, "TIME_WDAY") == 0) {
3571 MKTIMESTR("%d", tm_wday)
3573 else if (strcasecmp(var, "TIME") == 0) {
3574 apr_explode_localtime(&tm, apr_now());
3575 apr_snprintf(resultbuf, sizeof(resultbuf),
3576 "%04d%02d%02d%02d%02d%02d", tm.tm_year + 1900,
3577 tm.tm_mon+1, tm.tm_mday,
3578 tm.tm_hour, tm.tm_min, tm.tm_sec);
3580 rewritelog(r, 1, "RESULT='%s'", result);
3583 /* all other env-variables from the parent Apache process */
3584 else if (strlen(var) > 4 && strncasecmp(var, "ENV:", 4) == 0) {
3585 /* first try the internal Apache notes structure */
3586 result = apr_table_get(r->notes, var+4);
3587 /* second try the internal Apache env structure */
3588 if (result == NULL) {
3589 result = apr_table_get(r->subprocess_env, var+4);
3591 /* third try the external OS env */
3592 if (result == NULL) {
3593 result = getenv(var+4);
3597 #define LOOKAHEAD(subrecfunc) \
3599 /* filename is safe to use */ \
3600 r->filename != NULL \
3601 /* - and we're either not in a subrequest */ \
3602 && ( r->main == NULL \
3603 /* - or in a subrequest where paths are non-NULL... */ \
3604 || ( r->main->uri != NULL && r->uri != NULL \
3605 /* ...and sub and main paths differ */ \
3606 && strcmp(r->main->uri, r->uri) != 0))) { \
3607 /* process a file-based subrequest */ \
3608 rsub = subrecfunc(r->filename, r, NULL); \
3609 /* now recursively lookup the variable in the sub_req */ \
3610 result = lookup_variable(rsub, var+5); \
3611 /* copy it up to our scope before we destroy sub_req's apr_pool_t */ \
3612 result = apr_pstrdup(r->pool, result); \
3613 /* cleanup by destroying the subrequest */ \
3614 ap_destroy_sub_req(rsub); \
3616 rewritelog(r, 5, "lookahead: path=%s var=%s -> val=%s", \
3617 r->filename, var+5, result); \
3618 /* return ourself to prevent re-pstrdup */ \
3619 return (char *)result; \
3622 /* look-ahead for parameter through URI-based sub-request */
3623 else if (strlen(var) > 5 && strncasecmp(var, "LA-U:", 5) == 0) {
3624 LOOKAHEAD(ap_sub_req_lookup_uri)
3626 /* look-ahead for parameter through file-based sub-request */
3627 else if (strlen(var) > 5 && strncasecmp(var, "LA-F:", 5) == 0) {
3628 LOOKAHEAD(ap_sub_req_lookup_file)
3631 #if !defined(WIN32) && !defined(NETWARE)
3632 /* Win32 has a rather different view of file ownerships.
3633 For now, just forget it */
3636 else if (strcasecmp(var, "SCRIPT_USER") == 0) {
3637 result = "<unknown>";
3638 if (r->finfo.protection != 0) {
3639 if ((pw = getpwuid(r->finfo.user)) != NULL) {
3640 result = pw->pw_name;
3644 if (apr_stat(&finfo, r->filename, r->pool) == APR_SUCCESS) {
3645 if ((pw = getpwuid(finfo.user)) != NULL) {
3646 result = pw->pw_name;
3651 else if (strcasecmp(var, "SCRIPT_GROUP") == 0) {
3652 result = "<unknown>";
3653 if (r->finfo.protection != 0) {
3654 if ((gr = getgrgid(r->finfo.group)) != NULL) {
3655 result = gr->gr_name;
3659 if (apr_stat(&finfo, r->filename, r->pool) == 0) {
3660 if ((gr = getgrgid(finfo.group)) != NULL) {
3661 result = gr->gr_name;
3666 #endif /* ndef WIN32 && NETWARE*/
3668 if (result == NULL) {
3669 return apr_pstrdup(r->pool, "");
3672 return apr_pstrdup(r->pool, result);
3676 static char *lookup_header(request_rec *r, const char *name)
3678 apr_array_header_t *hdrs_arr;
3679 apr_table_entry_t *hdrs;
3682 hdrs_arr = apr_table_elts(r->headers_in);
3683 hdrs = (apr_table_entry_t *)hdrs_arr->elts;
3684 for (i = 0; i < hdrs_arr->nelts; ++i) {
3685 if (hdrs[i].key == NULL) {
3688 if (strcasecmp(hdrs[i].key, name) == 0) {
3689 apr_table_merge(r->notes, VARY_KEY_THIS, name);
3700 ** +-------------------------------------------------------+
3702 ** | caching support
3704 ** +-------------------------------------------------------+
3708 static cache *init_cache(apr_pool_t *p)
3712 c = (cache *)apr_palloc(p, sizeof(cache));
3713 if (apr_create_pool(&c->pool, p) != APR_SUCCESS)
3715 c->lists = apr_make_array(c->pool, 2, sizeof(cachelist));
3719 static void set_cache_string(cache *c, const char *res, int mode, time_t t,
3720 char *key, char *value)
3727 store_cache_string(c, res, &ce);
3731 static char *get_cache_string(cache *c, const char *res, int mode,
3732 time_t t, char *key)
3736 ce = retrieve_cache_string(c, res, key);
3740 if (mode & CACHEMODE_TS) {
3741 if (t != ce->time) {
3745 else if (mode & CACHEMODE_TTL) {
3750 return apr_pstrdup(c->pool, ce->value);
3753 static int cache_tlb_hash(char *key)
3759 for (p = key; *p != '\0'; p++) {
3760 n = ((n << 5) + n) ^ (unsigned long)(*p++);
3763 return n % CACHE_TLB_ROWS;
3766 static cacheentry *cache_tlb_lookup(cachetlbentry *tlb, cacheentry *elt,
3769 int ix = cache_tlb_hash(key);
3773 for (i=0; i < CACHE_TLB_COLS; ++i) {
3777 if (strcmp(elt[j].key, key) == 0)
3783 static void cache_tlb_replace(cachetlbentry *tlb, cacheentry *elt,
3786 int ix = cache_tlb_hash(e->key);
3791 for (i=1; i < CACHE_TLB_COLS; ++i)
3792 tlb->t[i] = tlb->t[i-1];
3794 tlb->t[0] = e - elt;
3797 static void store_cache_string(cache *c, const char *res, cacheentry *ce)
3807 /* first try to edit an existing entry */
3808 for (i = 0; i < c->lists->nelts; i++) {
3809 l = &(((cachelist *)c->lists->elts)[i]);
3810 if (strcmp(l->resource, res) == 0) {
3813 e = cache_tlb_lookup((cachetlbentry *)l->tlb->elts,
3814 (cacheentry *)l->entries->elts, ce->key);
3817 e->value = apr_pstrdup(c->pool, ce->value);
3821 for (j = 0; j < l->entries->nelts; j++) {
3822 e = &(((cacheentry *)l->entries->elts)[j]);
3823 if (strcmp(e->key, ce->key) == 0) {
3825 e->value = apr_pstrdup(c->pool, ce->value);
3826 cache_tlb_replace((cachetlbentry *)l->tlb->elts,
3827 (cacheentry *)l->entries->elts, e);
3834 /* create a needed new list */
3836 l = apr_push_array(c->lists);
3837 l->resource = apr_pstrdup(c->pool, res);
3838 l->entries = apr_make_array(c->pool, 2, sizeof(cacheentry));
3839 l->tlb = apr_make_array(c->pool, CACHE_TLB_ROWS,
3840 sizeof(cachetlbentry));
3841 for (i=0; i<CACHE_TLB_ROWS; ++i) {
3842 t = &((cachetlbentry *)l->tlb->elts)[i];
3843 for (j=0; j<CACHE_TLB_COLS; ++j)
3848 /* create the new entry */
3849 for (i = 0; i < c->lists->nelts; i++) {
3850 l = &(((cachelist *)c->lists->elts)[i]);
3851 if (strcmp(l->resource, res) == 0) {
3852 e = apr_push_array(l->entries);
3854 e->key = apr_pstrdup(c->pool, ce->key);
3855 e->value = apr_pstrdup(c->pool, ce->value);
3856 cache_tlb_replace((cachetlbentry *)l->tlb->elts,
3857 (cacheentry *)l->entries->elts, e);
3862 /* not reached, but when it is no problem... */
3866 static cacheentry *retrieve_cache_string(cache *c, const char *res, char *key)
3873 for (i = 0; i < c->lists->nelts; i++) {
3874 l = &(((cachelist *)c->lists->elts)[i]);
3875 if (strcmp(l->resource, res) == 0) {
3877 e = cache_tlb_lookup((cachetlbentry *)l->tlb->elts,
3878 (cacheentry *)l->entries->elts, key);
3882 for (j = 0; j < l->entries->nelts; j++) {
3883 e = &(((cacheentry *)l->entries->elts)[j]);
3884 if (strcmp(e->key, key) == 0) {
3897 ** +-------------------------------------------------------+
3901 ** +-------------------------------------------------------+
3904 static char *subst_prefix_path(request_rec *r, char *input, char *match,
3907 char matchbuf[LONG_STRING_LEN];
3908 char substbuf[LONG_STRING_LEN];
3914 /* first create a match string which always has a trailing slash */
3915 l = apr_cpystrn(matchbuf, match, sizeof(matchbuf)) - matchbuf;
3916 if (matchbuf[l-1] != '/') {
3918 matchbuf[l+1] = '\0';
3921 /* now compare the prefix */
3922 if (strncmp(input, matchbuf, l) == 0) {
3923 rewritelog(r, 5, "strip matching prefix: %s -> %s", output, output+l);
3924 output = apr_pstrdup(r->pool, output+l);
3926 /* and now add the base-URL as replacement prefix */
3927 l = apr_cpystrn(substbuf, subst, sizeof(substbuf)) - substbuf;
3928 if (substbuf[l-1] != '/') {
3930 substbuf[l+1] = '\0';
3933 if (output[0] == '/') {
3934 rewritelog(r, 4, "add subst prefix: %s -> %s%s",
3935 output, substbuf, output+1);
3936 output = apr_pstrcat(r->pool, substbuf, output+1, NULL);
3939 rewritelog(r, 4, "add subst prefix: %s -> %s%s",
3940 output, substbuf, output);
3941 output = apr_pstrcat(r->pool, substbuf, output, NULL);
3950 ** own command line parser which don't have the '\\' problem
3954 static int parseargline(char *str, char **a1, char **a2, char **a3)
3959 #define SKIP_WHITESPACE(cp) \
3960 for ( ; *cp == ' ' || *cp == '\t'; ) { \
3964 #define CHECK_QUOTATION(cp,isquoted) \
3971 #define DETERMINE_NEXTSTRING(cp,isquoted) \
3972 for ( ; *cp != '\0'; cp++) { \
3973 if ( (isquoted && (*cp == ' ' || *cp == '\t')) \
3974 || (*cp == '\\' && (*(cp+1) == ' ' || *(cp+1) == '\t'))) { \
3978 if ( (!isquoted && (*cp == ' ' || *cp == '\t')) \
3979 || (isquoted && *cp == '"') ) { \
3985 SKIP_WHITESPACE(cp);
3987 /* determine first argument */
3988 CHECK_QUOTATION(cp, isquoted);
3990 DETERMINE_NEXTSTRING(cp, isquoted);
3996 SKIP_WHITESPACE(cp);
3998 /* determine second argument */
3999 CHECK_QUOTATION(cp, isquoted);
4001 DETERMINE_NEXTSTRING(cp, isquoted);
4009 SKIP_WHITESPACE(cp);
4011 /* again check if there are only two arguments */
4018 /* determine second argument */
4019 CHECK_QUOTATION(cp, isquoted);
4021 DETERMINE_NEXTSTRING(cp, isquoted);
4028 static void add_env_variable(request_rec *r, char *s)
4030 char var[MAX_STRING_LEN];
4031 char val[MAX_STRING_LEN];
4035 if ((cp = strchr(s, ':')) != NULL) {
4036 n = ((cp-s) > MAX_STRING_LEN-1 ? MAX_STRING_LEN-1 : (cp-s));
4039 apr_cpystrn(val, cp+1, sizeof(val));
4040 apr_table_set(r->subprocess_env, var, val);
4041 rewritelog(r, 5, "setting env variable '%s' to '%s'", var, val);
4048 ** check that a subrequest won't cause infinite recursion
4052 static int subreq_ok(request_rec *r)
4055 * either not in a subrequest, or in a subrequest
4056 * and URIs aren't NULL and sub/main URIs differ
4058 return (r->main == NULL ||
4059 (r->main->uri != NULL && r->uri != NULL &&
4060 strcmp(r->main->uri, r->uri) != 0));
4066 ** stat() for only the prefix of a path
4070 static int prefix_stat(const char *path, apr_finfo_t *sb)
4072 char curpath[LONG_STRING_LEN];
4075 apr_cpystrn(curpath, path, sizeof(curpath));
4076 if (curpath[0] != '/') {
4079 if ((cp = strchr(curpath+1, '/')) != NULL) {
4082 if (apr_stat(sb, curpath, NULL) == 0) {
4093 ** Lexicographic Compare
4097 static int compare_lexicography(char *cpNum1, char *cpNum2)
4102 n1 = strlen(cpNum1);
4103 n2 = strlen(cpNum2);
4110 for (i = 0; i < n1; i++) {
4111 if (cpNum1[i] > cpNum2[i]) {
4114 if (cpNum1[i] < cpNum2[i]) {
4123 ** Find end of bracketed expression
4124 ** s points after the opening bracket
4128 static char *find_closing_bracket(char *s, int left, int right)
4132 for (depth = 1; *s; ++s) {
4133 if (*s == right && --depth == 0) {
4136 else if (*s == left) {
4144 int main(int argc, char *argv[])
4146 ExitThread(TSR_THREAD, 0);