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