1/* Licensed to the Apache Software Foundation (ASF) under one or more 2 * contributor license agreements. See the NOTICE file distributed with 3 * this work for additional information regarding copyright ownership. 4 * The ASF licenses this file to You under the Apache License, Version 2.0 5 * (the "License"); you may not use this file except in compliance with 6 * the License. You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17/* _ _ _ 18 * _ __ ___ ___ __| | _ __ _____ ___ __(_) |_ ___ 19 * | '_ ` _ \ / _ \ / _` | | '__/ _ \ \ /\ / / '__| | __/ _ \ 20 * | | | | | | (_) | (_| | | | | __/\ V V /| | | | || __/ 21 * |_| |_| |_|\___/ \__,_|___|_| \___| \_/\_/ |_| |_|\__\___| 22 * |_____| 23 * 24 * URL Rewriting Module 25 * 26 * This module uses a rule-based rewriting engine (based on a 27 * regular-expression parser) to rewrite requested URLs on the fly. 28 * 29 * It supports an unlimited number of additional rule conditions (which can 30 * operate on a lot of variables, even on HTTP headers) for granular 31 * matching and even external database lookups (either via plain text 32 * tables, DBM hash files or even external processes) for advanced URL 33 * substitution. 34 * 35 * It operates on the full URLs (including the PATH_INFO part) both in 36 * per-server context (httpd.conf) and per-dir context (.htaccess) and even 37 * can generate QUERY_STRING parts on result. The rewriting result finally 38 * can lead to internal subprocessing, external request redirection or even 39 * to internal proxy throughput. 40 * 41 * This module was originally written in April 1996 and 42 * gifted exclusively to the The Apache Software Foundation in July 1997 by 43 * 44 * Ralf S. Engelschall 45 * rse engelschall.com 46 * www.engelschall.com 47 */ 48 49#include "apr.h" 50#include "apr_strings.h" 51#include "apr_hash.h" 52#include "apr_user.h" 53#include "apr_lib.h" 54#include "apr_signal.h" 55#include "apr_global_mutex.h" 56#include "apr_dbm.h" 57 58#if APR_HAS_THREADS 59#include "apr_thread_mutex.h" 60#endif 61 62#define APR_WANT_MEMFUNC 63#define APR_WANT_STRFUNC 64#define APR_WANT_IOVEC 65#include "apr_want.h" 66 67/* XXX: Do we really need these headers? */ 68#if APR_HAVE_UNISTD_H 69#include <unistd.h> 70#endif 71#if APR_HAVE_SYS_TYPES_H 72#include <sys/types.h> 73#endif 74#if APR_HAVE_STDARG_H 75#include <stdarg.h> 76#endif 77#if APR_HAVE_STDLIB_H 78#include <stdlib.h> 79#endif 80#if APR_HAVE_CTYPE_H 81#include <ctype.h> 82#endif 83 84#include "ap_config.h" 85#include "httpd.h" 86#include "http_config.h" 87#include "http_request.h" 88#include "http_core.h" 89#include "http_log.h" 90#include "http_protocol.h" 91#include "http_vhost.h" 92 93#include "mod_ssl.h" 94 95#include "mod_rewrite.h" 96 97#ifdef AP_NEED_SET_MUTEX_PERMS 98#include "unixd.h" 99#endif 100 101/* 102 * in order to improve performance on running production systems, you 103 * may strip all rewritelog code entirely from mod_rewrite by using the 104 * -DREWRITELOG_DISABLED compiler option. 105 * 106 * DO NOT USE THIS OPTION FOR PUBLIC BINARY RELEASES. Otherwise YOU are 107 * responsible for answering all the mod_rewrite questions out there. 108 */ 109#ifndef REWRITELOG_DISABLED 110 111#define rewritelog(x) do_rewritelog x 112#define REWRITELOG_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD ) 113#define REWRITELOG_FLAGS ( APR_WRITE | APR_APPEND | APR_CREATE ) 114 115#else /* !REWRITELOG_DISABLED */ 116 117#define rewritelog(x) 118 119#endif /* REWRITELOG_DISABLED */ 120 121/* remembered mime-type for [T=...] */ 122#define REWRITE_FORCED_MIMETYPE_NOTEVAR "rewrite-forced-mimetype" 123#define REWRITE_FORCED_HANDLER_NOTEVAR "rewrite-forced-handler" 124 125#define ENVVAR_SCRIPT_URL "SCRIPT_URL" 126#define REDIRECT_ENVVAR_SCRIPT_URL "REDIRECT_" ENVVAR_SCRIPT_URL 127#define ENVVAR_SCRIPT_URI "SCRIPT_URI" 128 129#define CONDFLAG_NONE 1<<0 130#define CONDFLAG_NOCASE 1<<1 131#define CONDFLAG_NOTMATCH 1<<2 132#define CONDFLAG_ORNEXT 1<<3 133#define CONDFLAG_NOVARY 1<<4 134 135#define RULEFLAG_NONE 1<<0 136#define RULEFLAG_FORCEREDIRECT 1<<1 137#define RULEFLAG_LASTRULE 1<<2 138#define RULEFLAG_NEWROUND 1<<3 139#define RULEFLAG_CHAIN 1<<4 140#define RULEFLAG_IGNOREONSUBREQ 1<<5 141#define RULEFLAG_NOTMATCH 1<<6 142#define RULEFLAG_PROXY 1<<7 143#define RULEFLAG_PASSTHROUGH 1<<8 144#define RULEFLAG_QSAPPEND 1<<9 145#define RULEFLAG_NOCASE 1<<10 146#define RULEFLAG_NOESCAPE 1<<11 147#define RULEFLAG_NOSUB 1<<12 148#define RULEFLAG_STATUS 1<<13 149#define RULEFLAG_ESCAPEBACKREF 1<<14 150#define RULEFLAG_DISCARDPATHINFO 1<<15 151 152/* return code of the rewrite rule 153 * the result may be escaped - or not 154 */ 155#define ACTION_NORMAL 1<<0 156#define ACTION_NOESCAPE 1<<1 157#define ACTION_STATUS 1<<2 158 159 160#define MAPTYPE_TXT 1<<0 161#define MAPTYPE_DBM 1<<1 162#define MAPTYPE_PRG 1<<2 163#define MAPTYPE_INT 1<<3 164#define MAPTYPE_RND 1<<4 165 166#define ENGINE_DISABLED 1<<0 167#define ENGINE_ENABLED 1<<1 168 169#define OPTION_NONE 1<<0 170#define OPTION_INHERIT 1<<1 171#define OPTION_ANYURI 1<<4 172#define OPTION_MERGEBASE 1<<5 173 174#ifndef RAND_MAX 175#define RAND_MAX 32767 176#endif 177 178/* max cookie size in rfc 2109 */ 179/* XXX: not used at all. We should do a check somewhere and/or cut the cookie */ 180#define MAX_COOKIE_LEN 4096 181 182/* max line length (incl.\n) in text rewrite maps */ 183#ifndef REWRITE_MAX_TXT_MAP_LINE 184#define REWRITE_MAX_TXT_MAP_LINE 1024 185#endif 186 187/* buffer length for prg rewrite maps */ 188#ifndef REWRITE_PRG_MAP_BUF 189#define REWRITE_PRG_MAP_BUF 1024 190#endif 191 192/* for better readbility */ 193#define LEFT_CURLY '{' 194#define RIGHT_CURLY '}' 195 196/* 197 * expansion result items on the stack to save some cycles 198 * 199 * (5 == about 2 variables like "foo%{var}bar%{var}baz") 200 */ 201#define SMALL_EXPANSION 5 202 203/* 204 * check that a subrequest won't cause infinite recursion 205 * 206 * either not in a subrequest, or in a subrequest 207 * and URIs aren't NULL and sub/main URIs differ 208 */ 209#define subreq_ok(r) (!r->main || \ 210 (r->main->uri && r->uri && strcmp(r->main->uri, r->uri))) 211 212 213/* 214 * +-------------------------------------------------------+ 215 * | | 216 * | Types and Structures 217 * | | 218 * +-------------------------------------------------------+ 219 */ 220 221typedef struct { 222 const char *datafile; /* filename for map data files */ 223 const char *dbmtype; /* dbm type for dbm map data files */ 224 const char *checkfile; /* filename to check for map existence */ 225 const char *cachename; /* for cached maps (txt/rnd/dbm) */ 226 int type; /* the type of the map */ 227 apr_file_t *fpin; /* in file pointer for program maps */ 228 apr_file_t *fpout; /* out file pointer for program maps */ 229 apr_file_t *fperr; /* err file pointer for program maps */ 230 char *(*func)(request_rec *, /* function pointer for internal maps */ 231 char *); 232 char **argv; /* argv of the external rewrite map */ 233 const char *checkfile2; /* filename to check for map existence 234 NULL if only one file */ 235} rewritemap_entry; 236 237/* special pattern types for RewriteCond */ 238typedef enum { 239 CONDPAT_REGEX = 0, 240 CONDPAT_FILE_EXISTS, 241 CONDPAT_FILE_SIZE, 242 CONDPAT_FILE_LINK, 243 CONDPAT_FILE_DIR, 244 CONDPAT_FILE_XBIT, 245 CONDPAT_LU_URL, 246 CONDPAT_LU_FILE, 247 CONDPAT_STR_GT, 248 CONDPAT_STR_LT, 249 CONDPAT_STR_EQ 250} pattern_type; 251 252typedef struct { 253 char *input; /* Input string of RewriteCond */ 254 char *pattern; /* the RegExp pattern string */ 255 ap_regex_t *regexp; /* the precompiled regexp */ 256 int flags; /* Flags which control the match */ 257 pattern_type ptype; /* pattern type */ 258} rewritecond_entry; 259 260/* single linked list for env vars and cookies */ 261typedef struct data_item { 262 struct data_item *next; 263 char *data; 264} data_item; 265 266typedef struct { 267 apr_array_header_t *rewriteconds;/* the corresponding RewriteCond entries */ 268 char *pattern; /* the RegExp pattern string */ 269 ap_regex_t *regexp; /* the RegExp pattern compilation */ 270 char *output; /* the Substitution string */ 271 int flags; /* Flags which control the substitution */ 272 char *forced_mimetype; /* forced MIME type of substitution */ 273 char *forced_handler; /* forced content handler of subst. */ 274 int forced_responsecode; /* forced HTTP response status */ 275 data_item *env; /* added environment variables */ 276 data_item *cookie; /* added cookies */ 277 int skip; /* number of next rules to skip */ 278} rewriterule_entry; 279 280typedef struct { 281 int state; /* the RewriteEngine state */ 282 int options; /* the RewriteOption state */ 283#ifndef REWRITELOG_DISABLED 284 const char *rewritelogfile; /* the RewriteLog filename */ 285 apr_file_t *rewritelogfp; /* the RewriteLog open filepointer */ 286 int rewriteloglevel; /* the RewriteLog level of verbosity */ 287#endif 288 apr_hash_t *rewritemaps; /* the RewriteMap entries */ 289 apr_array_header_t *rewriteconds; /* the RewriteCond entries (temp.) */ 290 apr_array_header_t *rewriterules; /* the RewriteRule entries */ 291 server_rec *server; /* the corresponding server indicator */ 292 unsigned int state_set:1; 293 unsigned int options_set:1; 294} rewrite_server_conf; 295 296typedef struct { 297 int state; /* the RewriteEngine state */ 298 int options; /* the RewriteOption state */ 299 apr_array_header_t *rewriteconds; /* the RewriteCond entries (temp.) */ 300 apr_array_header_t *rewriterules; /* the RewriteRule entries */ 301 char *directory; /* the directory where it applies */ 302 const char *baseurl; /* the base-URL where it applies */ 303 unsigned int state_set:1; 304 unsigned int options_set:1; 305 unsigned int baseurl_set:1; 306} rewrite_perdir_conf; 307 308/* the (per-child) cache structures. 309 */ 310typedef struct cache { 311 apr_pool_t *pool; 312 apr_hash_t *maps; 313#if APR_HAS_THREADS 314 apr_thread_mutex_t *lock; 315#endif 316} cache; 317 318/* cached maps contain an mtime for the whole map and live in a subpool 319 * of the cachep->pool. That makes it easy to forget them if necessary. 320 */ 321typedef struct { 322 apr_time_t mtime; 323 apr_pool_t *pool; 324 apr_hash_t *entries; 325} cachedmap; 326 327/* the regex structure for the 328 * substitution of backreferences 329 */ 330typedef struct backrefinfo { 331 char *source; 332 int nsub; 333 ap_regmatch_t regmatch[AP_MAX_REG_MATCH]; 334} backrefinfo; 335 336/* single linked list used for 337 * variable expansion 338 */ 339typedef struct result_list { 340 struct result_list *next; 341 apr_size_t len; 342 const char *string; 343} result_list; 344 345/* context structure for variable lookup and expansion 346 */ 347typedef struct { 348 request_rec *r; 349 const char *uri; 350 const char *vary_this; 351 const char *vary; 352 char *perdir; 353 backrefinfo briRR; 354 backrefinfo briRC; 355} rewrite_ctx; 356 357/* 358 * +-------------------------------------------------------+ 359 * | | 360 * | static module data 361 * | | 362 * +-------------------------------------------------------+ 363 */ 364 365/* the global module structure */ 366module AP_MODULE_DECLARE_DATA rewrite_module; 367 368/* rewritemap int: handler function registry */ 369static apr_hash_t *mapfunc_hash; 370 371/* the cache */ 372static cache *cachep; 373 374/* whether proxy module is available or not */ 375static int proxy_available; 376 377/* whether random seed can be reaped */ 378static int rewrite_rand_init_done = 0; 379 380/* Locks/Mutexes */ 381static const char *lockname; 382static apr_global_mutex_t *rewrite_mapr_lock_acquire = NULL; 383 384/* Optional functions imported from mod_ssl when loaded: */ 385static APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *rewrite_ssl_lookup = NULL; 386static APR_OPTIONAL_FN_TYPE(ssl_is_https) *rewrite_is_https = NULL; 387static char *escape_uri(apr_pool_t *p, const char *path); 388 389/* 390 * +-------------------------------------------------------+ 391 * | | 392 * | rewriting logfile support 393 * | | 394 * +-------------------------------------------------------+ 395 */ 396 397#ifndef REWRITELOG_DISABLED 398static char *current_logtime(request_rec *r) 399{ 400 apr_time_exp_t t; 401 char tstr[80]; 402 apr_size_t len; 403 404 apr_time_exp_lt(&t, apr_time_now()); 405 406 apr_strftime(tstr, &len, sizeof(tstr), "[%d/%b/%Y:%H:%M:%S ", &t); 407 apr_snprintf(tstr+len, sizeof(tstr)-len, "%c%.2d%.2d]", 408 t.tm_gmtoff < 0 ? '-' : '+', 409 t.tm_gmtoff / (60*60), t.tm_gmtoff % (60*60)); 410 411 return apr_pstrdup(r->pool, tstr); 412} 413 414static int open_rewritelog(server_rec *s, apr_pool_t *p) 415{ 416 rewrite_server_conf *conf; 417 const char *fname; 418 419 conf = ap_get_module_config(s->module_config, &rewrite_module); 420 421 /* - no logfile configured 422 * - logfilename empty 423 * - virtual log shared w/ main server 424 */ 425 if (!conf->rewritelogfile || !*conf->rewritelogfile || conf->rewritelogfp) { 426 return 1; 427 } 428 429 if (*conf->rewritelogfile == '|') { 430 piped_log *pl; 431 432 fname = ap_server_root_relative(p, conf->rewritelogfile+1); 433 if (!fname) { 434 ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s, 435 "mod_rewrite: Invalid RewriteLog " 436 "path %s", conf->rewritelogfile+1); 437 return 0; 438 } 439 440 if ((pl = ap_open_piped_log(p, fname)) == NULL) { 441 ap_log_error(APLOG_MARK, APLOG_ERR, 0, s, 442 "mod_rewrite: could not open reliable pipe " 443 "to RewriteLog filter %s", fname); 444 return 0; 445 } 446 conf->rewritelogfp = ap_piped_log_write_fd(pl); 447 } 448 else { 449 apr_status_t rc; 450 451 fname = ap_server_root_relative(p, conf->rewritelogfile); 452 if (!fname) { 453 ap_log_error(APLOG_MARK, APLOG_ERR, APR_EBADPATH, s, 454 "mod_rewrite: Invalid RewriteLog " 455 "path %s", conf->rewritelogfile); 456 return 0; 457 } 458 459 if ((rc = apr_file_open(&conf->rewritelogfp, fname, 460 REWRITELOG_FLAGS, REWRITELOG_MODE, p)) 461 != APR_SUCCESS) { 462 ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, 463 "mod_rewrite: could not open RewriteLog " 464 "file %s", fname); 465 return 0; 466 } 467 } 468 469 return 1; 470} 471 472static void do_rewritelog(request_rec *r, int level, char *perdir, 473 const char *fmt, ...) 474{ 475 rewrite_server_conf *conf; 476 char *logline, *text; 477 const char *rhost, *rname; 478 apr_size_t nbytes; 479 int redir; 480 request_rec *req; 481 va_list ap; 482 483 conf = ap_get_module_config(r->server->module_config, &rewrite_module); 484 485 if (!conf->rewritelogfp || level > conf->rewriteloglevel) { 486 return; 487 } 488 489 rhost = ap_get_remote_host(r->connection, r->per_dir_config, 490 REMOTE_NOLOOKUP, NULL); 491 rname = ap_get_remote_logname(r); 492 493 for (redir=0, req=r; req->prev; req = req->prev) { 494 ++redir; 495 } 496 497 va_start(ap, fmt); 498 text = apr_pvsprintf(r->pool, fmt, ap); 499 va_end(ap); 500 501 logline = apr_psprintf(r->pool, "%s %s %s %s [%s/sid#%pp][rid#%pp/%s%s%s] " 502 "(%d) %s%s%s%s" APR_EOL_STR, 503 rhost ? ap_escape_logitem(r->pool, rhost) : "UNKNOWN-HOST", 504 rname ? ap_escape_logitem(r->pool, rname) : "-", 505 r->user ? (*r->user ? ap_escape_logitem(r->pool, r->user) : "\"\"") : "-", 506 current_logtime(r), 507 ap_escape_logitem(r->pool, ap_get_server_name(r)), 508 (void *)(r->server), 509 (void *)r, 510 r->main ? "subreq" : "initial", 511 redir ? "/redir#" : "", 512 redir ? apr_itoa(r->pool, redir) : "", 513 level, 514 perdir ? "[perdir " : "", 515 perdir ? perdir : "", 516 perdir ? "] ": "", 517 ap_escape_logitem(r->pool, text)); 518 519 nbytes = strlen(logline); 520 apr_file_write(conf->rewritelogfp, logline, &nbytes); 521 522 return; 523} 524#endif /* !REWRITELOG_DISABLED */ 525 526 527/* 528 * +-------------------------------------------------------+ 529 * | | 530 * | URI and path functions 531 * | | 532 * +-------------------------------------------------------+ 533 */ 534 535/* return number of chars of the scheme (incl. '://') 536 * if the URI is absolute (includes a scheme etc.) 537 * otherwise 0. 538 * 539 * NOTE: If you add new schemes here, please have a 540 * look at escape_absolute_uri and splitout_queryargs. 541 * Not every scheme takes query strings and some schemes 542 * may be handled in a special way. 543 * 544 * XXX: we may consider a scheme registry, perhaps with 545 * appropriate escape callbacks to allow other modules 546 * to extend mod_rewrite at runtime. 547 */ 548static unsigned is_absolute_uri(char *uri) 549{ 550 /* fast exit */ 551 if (*uri == '/' || strlen(uri) <= 5) { 552 return 0; 553 } 554 555 switch (*uri++) { 556 case 'a': 557 case 'A': 558 if (!strncasecmp(uri, "jp://", 5)) { /* ajp:// */ 559 return 6; 560 } 561 562 case 'b': 563 case 'B': 564 if (!strncasecmp(uri, "alancer://", 10)) { /* balancer:// */ 565 return 11; 566 } 567 break; 568 569 case 'f': 570 case 'F': 571 if (!strncasecmp(uri, "tp://", 5)) { /* ftp:// */ 572 return 6; 573 } 574 break; 575 576 case 'g': 577 case 'G': 578 if (!strncasecmp(uri, "opher://", 8)) { /* gopher:// */ 579 return 9; 580 } 581 break; 582 583 case 'h': 584 case 'H': 585 if (!strncasecmp(uri, "ttp://", 6)) { /* http:// */ 586 return 7; 587 } 588 else if (!strncasecmp(uri, "ttps://", 7)) { /* https:// */ 589 return 8; 590 } 591 break; 592 593 case 'l': 594 case 'L': 595 if (!strncasecmp(uri, "dap://", 6)) { /* ldap:// */ 596 return 7; 597 } 598 break; 599 600 case 'm': 601 case 'M': 602 if (!strncasecmp(uri, "ailto:", 6)) { /* mailto: */ 603 return 7; 604 } 605 break; 606 607 case 'n': 608 case 'N': 609 if (!strncasecmp(uri, "ews:", 4)) { /* news: */ 610 return 5; 611 } 612 else if (!strncasecmp(uri, "ntp://", 6)) { /* nntp:// */ 613 return 7; 614 } 615 break; 616 617 case 's': 618 case 'S': 619 if (!strncasecmp(uri, "cgi://", 6)) { /* scgi:// */ 620 return 7; 621 } 622 break; 623 } 624 625 return 0; 626} 627 628static const char c2x_table[] = "0123456789abcdef"; 629 630static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix, 631 unsigned char *where) 632{ 633#if APR_CHARSET_EBCDIC 634 what = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)what); 635#endif /*APR_CHARSET_EBCDIC*/ 636 *where++ = prefix; 637 *where++ = c2x_table[what >> 4]; 638 *where++ = c2x_table[what & 0xf]; 639 return where; 640} 641 642/* 643 * Escapes a uri in a similar way as php's urlencode does. 644 * Based on ap_os_escape_path in server/util.c 645 */ 646static char *escape_uri(apr_pool_t *p, const char *path) { 647 char *copy = apr_palloc(p, 3 * strlen(path) + 3); 648 const unsigned char *s = (const unsigned char *)path; 649 unsigned char *d = (unsigned char *)copy; 650 unsigned c; 651 652 while ((c = *s)) { 653 if (apr_isalnum(c) || c == '_') { 654 *d++ = c; 655 } 656 else if (c == ' ') { 657 *d++ = '+'; 658 } 659 else { 660 d = c2x(c, '%', d); 661 } 662 ++s; 663 } 664 *d = '\0'; 665 return copy; 666} 667 668/* 669 * escape absolute uri, which may or may not be path oriented. 670 * So let's handle them differently. 671 */ 672static char *escape_absolute_uri(apr_pool_t *p, char *uri, unsigned scheme) 673{ 674 char *cp; 675 676 /* be safe. 677 * NULL should indicate elsewhere, that something's wrong 678 */ 679 if (!scheme || strlen(uri) < scheme) { 680 return NULL; 681 } 682 683 cp = uri + scheme; 684 685 /* scheme with authority part? */ 686 if (cp[-1] == '/') { 687 /* skip host part */ 688 while (*cp && *cp != '/') { 689 ++cp; 690 } 691 692 /* nothing after the hostpart. ready! */ 693 if (!*cp || !*++cp) { 694 return apr_pstrdup(p, uri); 695 } 696 697 /* remember the hostname stuff */ 698 scheme = cp - uri; 699 700 /* special thing for ldap. 701 * The parts are separated by question marks. From RFC 2255: 702 * ldapurl = scheme "://" [hostport] ["/" 703 * [dn ["?" [attributes] ["?" [scope] 704 * ["?" [filter] ["?" extensions]]]]]] 705 */ 706 if (!strncasecmp(uri, "ldap", 4)) { 707 char *token[5]; 708 int c = 0; 709 710 token[0] = cp = apr_pstrdup(p, cp); 711 while (*cp && c < 4) { 712 if (*cp == '?') { 713 token[++c] = cp + 1; 714 *cp = '\0'; 715 } 716 ++cp; 717 } 718 719 return apr_pstrcat(p, apr_pstrndup(p, uri, scheme), 720 ap_escape_uri(p, token[0]), 721 (c >= 1) ? "?" : NULL, 722 (c >= 1) ? ap_escape_uri(p, token[1]) : NULL, 723 (c >= 2) ? "?" : NULL, 724 (c >= 2) ? ap_escape_uri(p, token[2]) : NULL, 725 (c >= 3) ? "?" : NULL, 726 (c >= 3) ? ap_escape_uri(p, token[3]) : NULL, 727 (c >= 4) ? "?" : NULL, 728 (c >= 4) ? ap_escape_uri(p, token[4]) : NULL, 729 NULL); 730 } 731 } 732 733 /* Nothing special here. Apply normal escaping. */ 734 return apr_pstrcat(p, apr_pstrndup(p, uri, scheme), 735 ap_escape_uri(p, cp), NULL); 736} 737 738/* 739 * split out a QUERY_STRING part from 740 * the current URI string 741 */ 742static void splitout_queryargs(request_rec *r, int qsappend) 743{ 744 char *q; 745 746 /* don't touch, unless it's an http or mailto URL. 747 * See RFC 1738 and RFC 2368. 748 */ 749 if (is_absolute_uri(r->filename) 750 && strncasecmp(r->filename, "ajp", 3) 751 && strncasecmp(r->filename, "balancer", 8) 752 && strncasecmp(r->filename, "http", 4) 753 && strncasecmp(r->filename, "mailto", 6)) { 754 r->args = NULL; /* forget the query that's still flying around */ 755 return; 756 } 757 758 q = ap_strchr(r->filename, '?'); 759 if (q != NULL) { 760 char *olduri; 761 apr_size_t len; 762 763 olduri = apr_pstrdup(r->pool, r->filename); 764 *q++ = '\0'; 765 if (qsappend) { 766 r->args = apr_pstrcat(r->pool, q, "&", r->args, NULL); 767 } 768 else { 769 r->args = apr_pstrdup(r->pool, q); 770 } 771 772 len = strlen(r->args); 773 if (!len) { 774 r->args = NULL; 775 } 776 else if (r->args[len-1] == '&') { 777 r->args[len-1] = '\0'; 778 } 779 780 rewritelog((r, 3, NULL, "split uri=%s -> uri=%s, args=%s", olduri, 781 r->filename, r->args ? r->args : "<none>")); 782 } 783 784 return; 785} 786 787/* 788 * strip 'http[s]://ourhost/' from URI 789 */ 790static void reduce_uri(request_rec *r) 791{ 792 char *cp; 793 apr_size_t l; 794 795 cp = (char *)ap_http_scheme(r); 796 l = strlen(cp); 797 if ( strlen(r->filename) > l+3 798 && strncasecmp(r->filename, cp, l) == 0 799 && r->filename[l] == ':' 800 && r->filename[l+1] == '/' 801 && r->filename[l+2] == '/' ) { 802 803 unsigned short port; 804 char *portp, *host, *url, *scratch; 805 806 scratch = apr_pstrdup(r->pool, r->filename); /* our scratchpad */ 807 808 /* cut the hostname and port out of the URI */ 809 cp = host = scratch + l + 3; /* 3 == strlen("://") */ 810 while (*cp && *cp != '/' && *cp != ':') { 811 ++cp; 812 } 813 814 if (*cp == ':') { /* additional port given */ 815 *cp++ = '\0'; 816 portp = cp; 817 while (*cp && *cp != '/') { 818 ++cp; 819 } 820 *cp = '\0'; 821 822 port = atoi(portp); 823 url = r->filename + (cp - scratch); 824 if (!*url) { 825 url = "/"; 826 } 827 } 828 else if (*cp == '/') { /* default port */ 829 *cp = '\0'; 830 831 port = ap_default_port(r); 832 url = r->filename + (cp - scratch); 833 } 834 else { 835 port = ap_default_port(r); 836 url = "/"; 837 } 838 839 /* now check whether we could reduce it to a local path... */ 840 if (ap_matches_request_vhost(r, host, port)) { 841 rewritelog((r, 3, NULL, "reduce %s -> %s", r->filename, url)); 842 r->filename = apr_pstrdup(r->pool, url); 843 } 844 } 845 846 return; 847} 848 849/* 850 * add 'http[s]://ourhost[:ourport]/' to URI 851 * if URI is still not fully qualified 852 */ 853static void fully_qualify_uri(request_rec *r) 854{ 855 if (r->method_number == M_CONNECT) { 856 return; 857 } 858 else if (!is_absolute_uri(r->filename)) { 859 const char *thisserver; 860 char *thisport; 861 int port; 862 863 thisserver = ap_get_server_name(r); 864 port = ap_get_server_port(r); 865 thisport = ap_is_default_port(port, r) 866 ? "" 867 : apr_psprintf(r->pool, ":%u", port); 868 869 r->filename = apr_psprintf(r->pool, "%s://%s%s%s%s", 870 ap_http_scheme(r), thisserver, thisport, 871 (*r->filename == '/') ? "" : "/", 872 r->filename); 873 } 874 875 return; 876} 877 878/* 879 * stat() only the first segment of a path 880 */ 881static int prefix_stat(const char *path, apr_pool_t *pool) 882{ 883 const char *curpath = path; 884 const char *root; 885 const char *slash; 886 char *statpath; 887 apr_status_t rv; 888 889 rv = apr_filepath_root(&root, &curpath, APR_FILEPATH_TRUENAME, pool); 890 891 if (rv != APR_SUCCESS) { 892 return 0; 893 } 894 895 /* let's recognize slashes only, the mod_rewrite semantics are opaque 896 * enough. 897 */ 898 if ((slash = ap_strchr_c(curpath, '/')) != NULL) { 899 rv = apr_filepath_merge(&statpath, root, 900 apr_pstrndup(pool, curpath, 901 (apr_size_t)(slash - curpath)), 902 APR_FILEPATH_NOTABOVEROOT | 903 APR_FILEPATH_NOTRELATIVE, pool); 904 } 905 else { 906 rv = apr_filepath_merge(&statpath, root, curpath, 907 APR_FILEPATH_NOTABOVEROOT | 908 APR_FILEPATH_NOTRELATIVE, pool); 909 } 910 911 if (rv == APR_SUCCESS) { 912 apr_finfo_t sb; 913 914 if (apr_stat(&sb, statpath, APR_FINFO_MIN, pool) == APR_SUCCESS) { 915 return 1; 916 } 917 } 918 919 return 0; 920} 921 922/* 923 * substitute the prefix path 'match' in 'input' with 'subst' (RewriteBase) 924 */ 925static char *subst_prefix_path(request_rec *r, char *input, char *match, 926 const char *subst) 927{ 928 apr_size_t len = strlen(match); 929 930 if (len && match[len - 1] == '/') { 931 --len; 932 } 933 934 if (!strncmp(input, match, len) && input[len++] == '/') { 935 apr_size_t slen, outlen; 936 char *output; 937 938 rewritelog((r, 5, NULL, "strip matching prefix: %s -> %s", input, 939 input+len)); 940 941 slen = strlen(subst); 942 if (slen && subst[slen - 1] != '/') { 943 ++slen; 944 } 945 946 outlen = strlen(input) + slen - len; 947 output = apr_palloc(r->pool, outlen + 1); /* don't forget the \0 */ 948 949 memcpy(output, subst, slen); 950 if (slen && !output[slen-1]) { 951 output[slen-1] = '/'; 952 } 953 memcpy(output+slen, input+len, outlen - slen); 954 output[outlen] = '\0'; 955 956 rewritelog((r, 4, NULL, "add subst prefix: %s -> %s", input+len, 957 output)); 958 959 return output; 960 } 961 962 /* prefix didn't match */ 963 return input; 964} 965 966 967/* 968 * +-------------------------------------------------------+ 969 * | | 970 * | caching support 971 * | | 972 * +-------------------------------------------------------+ 973 */ 974 975static void set_cache_value(const char *name, apr_time_t t, char *key, 976 char *val) 977{ 978 cachedmap *map; 979 980 if (cachep) { 981#if APR_HAS_THREADS 982 apr_thread_mutex_lock(cachep->lock); 983#endif 984 map = apr_hash_get(cachep->maps, name, APR_HASH_KEY_STRING); 985 986 if (!map) { 987 apr_pool_t *p; 988 989 if (apr_pool_create(&p, cachep->pool) != APR_SUCCESS) { 990#if APR_HAS_THREADS 991 apr_thread_mutex_unlock(cachep->lock); 992#endif 993 return; 994 } 995 996 map = apr_palloc(cachep->pool, sizeof(cachedmap)); 997 map->pool = p; 998 map->entries = apr_hash_make(map->pool); 999 map->mtime = t; 1000 1001 apr_hash_set(cachep->maps, name, APR_HASH_KEY_STRING, map); 1002 } 1003 else if (map->mtime != t) { 1004 apr_pool_clear(map->pool); 1005 map->entries = apr_hash_make(map->pool); 1006 map->mtime = t; 1007 } 1008 1009 /* Now we should have a valid map->entries hash, where we 1010 * can store our value. 1011 * 1012 * We need to copy the key and the value into OUR pool, 1013 * so that we don't leave it during the r->pool cleanup. 1014 */ 1015 apr_hash_set(map->entries, 1016 apr_pstrdup(map->pool, key), APR_HASH_KEY_STRING, 1017 apr_pstrdup(map->pool, val)); 1018 1019#if APR_HAS_THREADS 1020 apr_thread_mutex_unlock(cachep->lock); 1021#endif 1022 } 1023 1024 return; 1025} 1026 1027static char *get_cache_value(const char *name, apr_time_t t, char *key, 1028 apr_pool_t *p) 1029{ 1030 cachedmap *map; 1031 char *val = NULL; 1032 1033 if (cachep) { 1034#if APR_HAS_THREADS 1035 apr_thread_mutex_lock(cachep->lock); 1036#endif 1037 map = apr_hash_get(cachep->maps, name, APR_HASH_KEY_STRING); 1038 1039 if (map) { 1040 /* if this map is outdated, forget it. */ 1041 if (map->mtime != t) { 1042 apr_pool_clear(map->pool); 1043 map->entries = apr_hash_make(map->pool); 1044 map->mtime = t; 1045 } 1046 else { 1047 val = apr_hash_get(map->entries, key, APR_HASH_KEY_STRING); 1048 if (val) { 1049 /* copy the cached value into the supplied pool, 1050 * where it belongs (r->pool usually) 1051 */ 1052 val = apr_pstrdup(p, val); 1053 } 1054 } 1055 } 1056 1057#if APR_HAS_THREADS 1058 apr_thread_mutex_unlock(cachep->lock); 1059#endif 1060 } 1061 1062 return val; 1063} 1064 1065static int init_cache(apr_pool_t *p) 1066{ 1067 cachep = apr_palloc(p, sizeof(cache)); 1068 if (apr_pool_create(&cachep->pool, p) != APR_SUCCESS) { 1069 cachep = NULL; /* turns off cache */ 1070 return 0; 1071 } 1072 1073 cachep->maps = apr_hash_make(cachep->pool); 1074#if APR_HAS_THREADS 1075 (void)apr_thread_mutex_create(&(cachep->lock), APR_THREAD_MUTEX_DEFAULT, p); 1076#endif 1077 1078 return 1; 1079} 1080 1081 1082/* 1083 * +-------------------------------------------------------+ 1084 * | | 1085 * | Map Functions 1086 * | | 1087 * +-------------------------------------------------------+ 1088 */ 1089 1090/* 1091 * General Note: key is already a fresh string, created (expanded) just 1092 * for the purpose to be passed in here. So one can modify key itself. 1093 */ 1094 1095static char *rewrite_mapfunc_toupper(request_rec *r, char *key) 1096{ 1097 char *p; 1098 1099 for (p = key; *p; ++p) { 1100 *p = apr_toupper(*p); 1101 } 1102 1103 return key; 1104} 1105 1106static char *rewrite_mapfunc_tolower(request_rec *r, char *key) 1107{ 1108 ap_str_tolower(key); 1109 1110 return key; 1111} 1112 1113static char *rewrite_mapfunc_escape(request_rec *r, char *key) 1114{ 1115 return ap_escape_uri(r->pool, key); 1116} 1117 1118static char *rewrite_mapfunc_unescape(request_rec *r, char *key) 1119{ 1120 ap_unescape_url(key); 1121 1122 return key; 1123} 1124 1125static char *select_random_value_part(request_rec *r, char *value) 1126{ 1127 char *p = value; 1128 unsigned n = 1; 1129 1130 /* count number of distinct values */ 1131 while ((p = ap_strchr(p, '|')) != NULL) { 1132 ++n; 1133 ++p; 1134 } 1135 1136 if (n > 1) { 1137 /* initialize random generator 1138 * 1139 * XXX: Probably this should be wrapped into a thread mutex, 1140 * shouldn't it? Is it worth the effort? 1141 */ 1142 if (!rewrite_rand_init_done) { 1143 srand((unsigned)(getpid())); 1144 rewrite_rand_init_done = 1; 1145 } 1146 1147 /* select a random subvalue */ 1148 n = (int)(((double)(rand() % RAND_MAX) / RAND_MAX) * n + 1); 1149 1150 /* extract it from the whole string */ 1151 while (--n && (value = ap_strchr(value, '|')) != NULL) { 1152 ++value; 1153 } 1154 1155 if (value) { /* should not be NULL, but ... */ 1156 p = ap_strchr(value, '|'); 1157 if (p) { 1158 *p = '\0'; 1159 } 1160 } 1161 } 1162 1163 return value; 1164} 1165 1166/* child process code */ 1167static void rewrite_child_errfn(apr_pool_t *p, apr_status_t err, 1168 const char *desc) 1169{ 1170 ap_log_error(APLOG_MARK, APLOG_ERR, err, NULL, "%s", desc); 1171} 1172 1173static apr_status_t rewritemap_program_child(apr_pool_t *p, 1174 const char *progname, char **argv, 1175 apr_file_t **fpout, 1176 apr_file_t **fpin) 1177{ 1178 apr_status_t rc; 1179 apr_procattr_t *procattr; 1180 apr_proc_t *procnew; 1181 1182 if ( APR_SUCCESS == (rc=apr_procattr_create(&procattr, p)) 1183 && APR_SUCCESS == (rc=apr_procattr_io_set(procattr, APR_FULL_BLOCK, 1184 APR_FULL_BLOCK, APR_NO_PIPE)) 1185 && APR_SUCCESS == (rc=apr_procattr_dir_set(procattr, 1186 ap_make_dirstr_parent(p, argv[0]))) 1187 && APR_SUCCESS == (rc=apr_procattr_cmdtype_set(procattr, APR_PROGRAM)) 1188 && APR_SUCCESS == (rc=apr_procattr_child_errfn_set(procattr, 1189 rewrite_child_errfn)) 1190 && APR_SUCCESS == (rc=apr_procattr_error_check_set(procattr, 1))) { 1191 1192 procnew = apr_pcalloc(p, sizeof(*procnew)); 1193 rc = apr_proc_create(procnew, argv[0], (const char **)argv, NULL, 1194 procattr, p); 1195 1196 if (rc == APR_SUCCESS) { 1197 apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT); 1198 1199 if (fpin) { 1200 (*fpin) = procnew->in; 1201 } 1202 1203 if (fpout) { 1204 (*fpout) = procnew->out; 1205 } 1206 } 1207 } 1208 1209 return (rc); 1210} 1211 1212static apr_status_t run_rewritemap_programs(server_rec *s, apr_pool_t *p) 1213{ 1214 rewrite_server_conf *conf; 1215 apr_hash_index_t *hi; 1216 apr_status_t rc; 1217 int lock_warning_issued = 0; 1218 1219 conf = ap_get_module_config(s->module_config, &rewrite_module); 1220 1221 /* If the engine isn't turned on, 1222 * don't even try to do anything. 1223 */ 1224 if (conf->state == ENGINE_DISABLED) { 1225 return APR_SUCCESS; 1226 } 1227 1228 for (hi = apr_hash_first(p, conf->rewritemaps); hi; hi = apr_hash_next(hi)){ 1229 apr_file_t *fpin = NULL; 1230 apr_file_t *fpout = NULL; 1231 rewritemap_entry *map; 1232 void *val; 1233 1234 apr_hash_this(hi, NULL, NULL, &val); 1235 map = val; 1236 1237 if (map->type != MAPTYPE_PRG) { 1238 continue; 1239 } 1240 if (!(map->argv[0]) || !*(map->argv[0]) || map->fpin || map->fpout) { 1241 continue; 1242 } 1243 1244 if (!lock_warning_issued && (!lockname || !*lockname)) { 1245 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, s, 1246 "mod_rewrite: Running external rewrite maps " 1247 "without defining a RewriteLock is DANGEROUS!"); 1248 ++lock_warning_issued; 1249 } 1250 1251 rc = rewritemap_program_child(p, map->argv[0], map->argv, 1252 &fpout, &fpin); 1253 if (rc != APR_SUCCESS || fpin == NULL || fpout == NULL) { 1254 ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, 1255 "mod_rewrite: could not start RewriteMap " 1256 "program %s", map->checkfile); 1257 return rc; 1258 } 1259 map->fpin = fpin; 1260 map->fpout = fpout; 1261 } 1262 1263 return APR_SUCCESS; 1264} 1265 1266 1267/* 1268 * +-------------------------------------------------------+ 1269 * | | 1270 * | Lookup functions 1271 * | | 1272 * +-------------------------------------------------------+ 1273 */ 1274 1275static char *lookup_map_txtfile(request_rec *r, const char *file, char *key) 1276{ 1277 apr_file_t *fp = NULL; 1278 char line[REWRITE_MAX_TXT_MAP_LINE + 1]; /* +1 for \0 */ 1279 char *value, *keylast; 1280 1281 if (apr_file_open(&fp, file, APR_READ|APR_BUFFERED, APR_OS_DEFAULT, 1282 r->pool) != APR_SUCCESS) { 1283 return NULL; 1284 } 1285 1286 keylast = key + strlen(key); 1287 value = NULL; 1288 while (apr_file_gets(line, sizeof(line), fp) == APR_SUCCESS) { 1289 char *p, *c; 1290 1291 /* ignore comments and lines starting with whitespaces */ 1292 if (*line == '#' || apr_isspace(*line)) { 1293 continue; 1294 } 1295 1296 p = line; 1297 c = key; 1298 while (c < keylast && *p == *c && !apr_isspace(*p)) { 1299 ++p; 1300 ++c; 1301 } 1302 1303 /* key doesn't match - ignore. */ 1304 if (c != keylast || !apr_isspace(*p)) { 1305 continue; 1306 } 1307 1308 /* jump to the value */ 1309 while (*p && apr_isspace(*p)) { 1310 ++p; 1311 } 1312 1313 /* no value? ignore */ 1314 if (!*p) { 1315 continue; 1316 } 1317 1318 /* extract the value and return. */ 1319 c = p; 1320 while (*p && !apr_isspace(*p)) { 1321 ++p; 1322 } 1323 value = apr_pstrmemdup(r->pool, c, p - c); 1324 break; 1325 } 1326 apr_file_close(fp); 1327 1328 return value; 1329} 1330 1331static char *lookup_map_dbmfile(request_rec *r, const char *file, 1332 const char *dbmtype, char *key) 1333{ 1334 apr_dbm_t *dbmfp = NULL; 1335 apr_datum_t dbmkey; 1336 apr_datum_t dbmval; 1337 char *value; 1338 1339 if (apr_dbm_open_ex(&dbmfp, dbmtype, file, APR_DBM_READONLY, APR_OS_DEFAULT, 1340 r->pool) != APR_SUCCESS) { 1341 return NULL; 1342 } 1343 1344 dbmkey.dptr = key; 1345 dbmkey.dsize = strlen(key); 1346 1347 if (apr_dbm_fetch(dbmfp, dbmkey, &dbmval) == APR_SUCCESS && dbmval.dptr) { 1348 value = apr_pstrmemdup(r->pool, dbmval.dptr, dbmval.dsize); 1349 } 1350 else { 1351 value = NULL; 1352 } 1353 1354 apr_dbm_close(dbmfp); 1355 1356 return value; 1357} 1358 1359static char *lookup_map_program(request_rec *r, apr_file_t *fpin, 1360 apr_file_t *fpout, char *key) 1361{ 1362 char *buf; 1363 char c; 1364 apr_size_t i, nbytes, combined_len = 0; 1365 apr_status_t rv; 1366 const char *eol = APR_EOL_STR; 1367 apr_size_t eolc = 0; 1368 int found_nl = 0; 1369 result_list *buflist = NULL, *curbuf = NULL; 1370 1371#ifndef NO_WRITEV 1372 struct iovec iova[2]; 1373 apr_size_t niov; 1374#endif 1375 1376 /* when `RewriteEngine off' was used in the per-server 1377 * context then the rewritemap-programs were not spawned. 1378 * In this case using such a map (usually in per-dir context) 1379 * is useless because it is not available. 1380 * 1381 * newlines in the key leave bytes in the pipe and cause 1382 * bad things to happen (next map lookup will use the chars 1383 * after the \n instead of the new key etc etc - in other words, 1384 * the Rewritemap falls out of sync with the requests). 1385 */ 1386 if (fpin == NULL || fpout == NULL || ap_strchr(key, '\n')) { 1387 return NULL; 1388 } 1389 1390 /* take the lock */ 1391 if (rewrite_mapr_lock_acquire) { 1392 rv = apr_global_mutex_lock(rewrite_mapr_lock_acquire); 1393 if (rv != APR_SUCCESS) { 1394 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, 1395 "apr_global_mutex_lock(rewrite_mapr_lock_acquire) " 1396 "failed"); 1397 return NULL; /* Maybe this should be fatal? */ 1398 } 1399 } 1400 1401 /* write out the request key */ 1402#ifdef NO_WRITEV 1403 nbytes = strlen(key); 1404 apr_file_write(fpin, key, &nbytes); 1405 nbytes = 1; 1406 apr_file_write(fpin, "\n", &nbytes); 1407#else 1408 iova[0].iov_base = key; 1409 iova[0].iov_len = strlen(key); 1410 iova[1].iov_base = "\n"; 1411 iova[1].iov_len = 1; 1412 1413 niov = 2; 1414 apr_file_writev(fpin, iova, niov, &nbytes); 1415#endif 1416 1417 buf = apr_palloc(r->pool, REWRITE_PRG_MAP_BUF + 1); 1418 1419 /* read in the response value */ 1420 nbytes = 1; 1421 apr_file_read(fpout, &c, &nbytes); 1422 do { 1423 i = 0; 1424 while (nbytes == 1 && (i < REWRITE_PRG_MAP_BUF)) { 1425 if (c == eol[eolc]) { 1426 if (!eol[++eolc]) { 1427 /* remove eol from the buffer */ 1428 --eolc; 1429 if (i < eolc) { 1430 curbuf->len -= eolc-i; 1431 i = 0; 1432 } 1433 else { 1434 i -= eolc; 1435 } 1436 ++found_nl; 1437 break; 1438 } 1439 } 1440 1441 /* only partial (invalid) eol sequence -> reset the counter */ 1442 else if (eolc) { 1443 eolc = 0; 1444 } 1445 1446 /* catch binary mode, e.g. on Win32 */ 1447 else if (c == '\n') { 1448 ++found_nl; 1449 break; 1450 } 1451 1452 buf[i++] = c; 1453 apr_file_read(fpout, &c, &nbytes); 1454 } 1455 1456 /* well, if there wasn't a newline yet, we need to read further */ 1457 if (buflist || (nbytes == 1 && !found_nl)) { 1458 if (!buflist) { 1459 curbuf = buflist = apr_palloc(r->pool, sizeof(*buflist)); 1460 } 1461 else if (i) { 1462 curbuf->next = apr_palloc(r->pool, sizeof(*buflist)); 1463 curbuf = curbuf->next; 1464 1465 } 1466 curbuf->next = NULL; 1467 1468 if (i) { 1469 curbuf->string = buf; 1470 curbuf->len = i; 1471 combined_len += i; 1472 buf = apr_palloc(r->pool, REWRITE_PRG_MAP_BUF); 1473 } 1474 1475 if (nbytes == 1 && !found_nl) { 1476 i = 0; 1477 continue; 1478 } 1479 } 1480 1481 break; 1482 } while (1); 1483 1484 /* concat the stuff */ 1485 if (buflist) { 1486 char *p; 1487 1488 p = buf = apr_palloc(r->pool, combined_len + 1); /* \0 */ 1489 while (buflist) { 1490 if (buflist->len) { 1491 memcpy(p, buflist->string, buflist->len); 1492 p += buflist->len; 1493 } 1494 buflist = buflist->next; 1495 } 1496 *p = '\0'; 1497 i = combined_len; 1498 } 1499 else { 1500 buf[i] = '\0'; 1501 } 1502 1503 /* give the lock back */ 1504 if (rewrite_mapr_lock_acquire) { 1505 rv = apr_global_mutex_unlock(rewrite_mapr_lock_acquire); 1506 if (rv != APR_SUCCESS) { 1507 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, 1508 "apr_global_mutex_unlock(rewrite_mapr_lock_acquire) " 1509 "failed"); 1510 return NULL; /* Maybe this should be fatal? */ 1511 } 1512 } 1513 1514 /* catch the "failed" case */ 1515 if (i == 4 && !strcasecmp(buf, "NULL")) { 1516 return NULL; 1517 } 1518 1519 return buf; 1520} 1521 1522/* 1523 * generic map lookup 1524 */ 1525static char *lookup_map(request_rec *r, char *name, char *key) 1526{ 1527 rewrite_server_conf *conf; 1528 rewritemap_entry *s; 1529 char *value; 1530 apr_finfo_t st; 1531 apr_status_t rv; 1532 1533 /* get map configuration */ 1534 conf = ap_get_module_config(r->server->module_config, &rewrite_module); 1535 s = apr_hash_get(conf->rewritemaps, name, APR_HASH_KEY_STRING); 1536 1537 /* map doesn't exist */ 1538 if (!s) { 1539 return NULL; 1540 } 1541 1542 switch (s->type) { 1543 /* 1544 * Text file map (perhaps random) 1545 */ 1546 case MAPTYPE_RND: 1547 case MAPTYPE_TXT: 1548 rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool); 1549 if (rv != APR_SUCCESS) { 1550 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, 1551 "mod_rewrite: can't access text RewriteMap file %s", 1552 s->checkfile); 1553 rewritelog((r, 1, NULL, 1554 "can't open RewriteMap file, see error log")); 1555 return NULL; 1556 } 1557 1558 value = get_cache_value(s->cachename, st.mtime, key, r->pool); 1559 if (!value) { 1560 rewritelog((r, 6, NULL, 1561 "cache lookup FAILED, forcing new map lookup")); 1562 1563 value = lookup_map_txtfile(r, s->datafile, key); 1564 if (!value) { 1565 rewritelog((r, 5, NULL, "map lookup FAILED: map=%s[txt] key=%s", 1566 name, key)); 1567 set_cache_value(s->cachename, st.mtime, key, ""); 1568 return NULL; 1569 } 1570 1571 rewritelog((r, 5, NULL,"map lookup OK: map=%s[txt] key=%s -> val=%s", 1572 name, key, value)); 1573 set_cache_value(s->cachename, st.mtime, key, value); 1574 } 1575 else { 1576 rewritelog((r,5,NULL,"cache lookup OK: map=%s[txt] key=%s -> val=%s", 1577 name, key, value)); 1578 } 1579 1580 if (s->type == MAPTYPE_RND && *value) { 1581 value = select_random_value_part(r, value); 1582 rewritelog((r, 5, NULL, "randomly chosen the subvalue `%s'",value)); 1583 } 1584 1585 return *value ? value : NULL; 1586 1587 /* 1588 * DBM file map 1589 */ 1590 case MAPTYPE_DBM: 1591 rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool); 1592 if (rv != APR_SUCCESS) { 1593 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, 1594 "mod_rewrite: can't access DBM RewriteMap file %s", 1595 s->checkfile); 1596 } 1597 else if(s->checkfile2 != NULL) { 1598 apr_finfo_t st2; 1599 1600 rv = apr_stat(&st2, s->checkfile2, APR_FINFO_MIN, r->pool); 1601 if (rv != APR_SUCCESS) { 1602 ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, 1603 "mod_rewrite: can't access DBM RewriteMap " 1604 "file %s", s->checkfile2); 1605 } 1606 else if(st2.mtime > st.mtime) { 1607 st.mtime = st2.mtime; 1608 } 1609 } 1610 if(rv != APR_SUCCESS) { 1611 rewritelog((r, 1, NULL, 1612 "can't open DBM RewriteMap file, see error log")); 1613 return NULL; 1614 } 1615 1616 value = get_cache_value(s->cachename, st.mtime, key, r->pool); 1617 if (!value) { 1618 rewritelog((r, 6, NULL, 1619 "cache lookup FAILED, forcing new map lookup")); 1620 1621 value = lookup_map_dbmfile(r, s->datafile, s->dbmtype, key); 1622 if (!value) { 1623 rewritelog((r, 5, NULL, "map lookup FAILED: map=%s[dbm] key=%s", 1624 name, key)); 1625 set_cache_value(s->cachename, st.mtime, key, ""); 1626 return NULL; 1627 } 1628 1629 rewritelog((r, 5, NULL, "map lookup OK: map=%s[dbm] key=%s -> " 1630 "val=%s", name, key, value)); 1631 1632 set_cache_value(s->cachename, st.mtime, key, value); 1633 return value; 1634 } 1635 1636 rewritelog((r, 5, NULL, "cache lookup OK: map=%s[dbm] key=%s -> val=%s", 1637 name, key, value)); 1638 return *value ? value : NULL; 1639 1640 /* 1641 * Program file map 1642 */ 1643 case MAPTYPE_PRG: 1644 value = lookup_map_program(r, s->fpin, s->fpout, key); 1645 if (!value) { 1646 rewritelog((r, 5,NULL,"map lookup FAILED: map=%s key=%s", name, 1647 key)); 1648 return NULL; 1649 } 1650 1651 rewritelog((r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s", 1652 name, key, value)); 1653 return value; 1654 1655 /* 1656 * Internal Map 1657 */ 1658 case MAPTYPE_INT: 1659 value = s->func(r, key); 1660 if (!value) { 1661 rewritelog((r, 5,NULL,"map lookup FAILED: map=%s key=%s", name, 1662 key)); 1663 return NULL; 1664 } 1665 1666 rewritelog((r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s", 1667 name, key, value)); 1668 return value; 1669 } 1670 1671 return NULL; 1672} 1673 1674/* 1675 * lookup a HTTP header and set VARY note 1676 */ 1677static const char *lookup_header(const char *name, rewrite_ctx *ctx) 1678{ 1679 const char *val = apr_table_get(ctx->r->headers_in, name); 1680 1681 if (val) { 1682 ctx->vary_this = ctx->vary_this 1683 ? apr_pstrcat(ctx->r->pool, ctx->vary_this, ", ", 1684 name, NULL) 1685 : apr_pstrdup(ctx->r->pool, name); 1686 } 1687 1688 return val; 1689} 1690 1691/* 1692 * lookahead helper function 1693 * Determine the correct URI path in perdir context 1694 */ 1695static APR_INLINE const char *la_u(rewrite_ctx *ctx) 1696{ 1697 rewrite_perdir_conf *conf; 1698 1699 if (*ctx->uri == '/') { 1700 return ctx->uri; 1701 } 1702 1703 conf = ap_get_module_config(ctx->r->per_dir_config, &rewrite_module); 1704 1705 return apr_pstrcat(ctx->r->pool, conf->baseurl 1706 ? conf->baseurl : conf->directory, 1707 ctx->uri, NULL); 1708} 1709 1710/* 1711 * generic variable lookup 1712 */ 1713static char *lookup_variable(char *var, rewrite_ctx *ctx) 1714{ 1715 const char *result; 1716 request_rec *r = ctx->r; 1717 apr_size_t varlen = strlen(var); 1718 1719 /* fast exit */ 1720 if (varlen < 4) { 1721 return apr_pstrdup(r->pool, ""); 1722 } 1723 1724 result = NULL; 1725 1726 /* fast tests for variable length variables (sic) first */ 1727 if (var[3] == ':') { 1728 if (var[4] && !strncasecmp(var, "ENV", 3)) { 1729 var += 4; 1730 result = apr_table_get(r->notes, var); 1731 1732 if (!result) { 1733 result = apr_table_get(r->subprocess_env, var); 1734 } 1735 if (!result) { 1736 result = getenv(var); 1737 } 1738 } 1739 else if (var[4] && !strncasecmp(var, "SSL", 3) && rewrite_ssl_lookup) { 1740 result = rewrite_ssl_lookup(r->pool, r->server, r->connection, r, 1741 var + 4); 1742 } 1743 } 1744 else if (var[4] == ':') { 1745 if (var[5]) { 1746 request_rec *rr; 1747 const char *path; 1748 1749 if (!strncasecmp(var, "HTTP", 4)) { 1750 result = lookup_header(var+5, ctx); 1751 } 1752 else if (!strncasecmp(var, "LA-U", 4)) { 1753 if (ctx->uri && subreq_ok(r)) { 1754 path = ctx->perdir ? la_u(ctx) : ctx->uri; 1755 rr = ap_sub_req_lookup_uri(path, r, NULL); 1756 ctx->r = rr; 1757 result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx)); 1758 ctx->r = r; 1759 ap_destroy_sub_req(rr); 1760 1761 rewritelog((r, 5, ctx->perdir, "lookahead: path=%s var=%s " 1762 "-> val=%s", path, var+5, result)); 1763 1764 return (char *)result; 1765 } 1766 } 1767 else if (!strncasecmp(var, "LA-F", 4)) { 1768 if (ctx->uri && subreq_ok(r)) { 1769 path = ctx->uri; 1770 if (ctx->perdir && *path == '/') { 1771 /* sigh, the user wants a file based subrequest, but 1772 * we can't do one, since we don't know what the file 1773 * path is! In this case behave like LA-U. 1774 */ 1775 rr = ap_sub_req_lookup_uri(path, r, NULL); 1776 } 1777 else { 1778 if (ctx->perdir) { 1779 rewrite_perdir_conf *conf; 1780 1781 conf = ap_get_module_config(r->per_dir_config, 1782 &rewrite_module); 1783 1784 path = apr_pstrcat(r->pool, conf->directory, path, 1785 NULL); 1786 } 1787 1788 rr = ap_sub_req_lookup_file(path, r, NULL); 1789 } 1790 1791 ctx->r = rr; 1792 result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx)); 1793 ctx->r = r; 1794 ap_destroy_sub_req(rr); 1795 1796 rewritelog((r, 5, ctx->perdir, "lookahead: path=%s var=%s " 1797 "-> val=%s", path, var+5, result)); 1798 1799 return (char *)result; 1800 } 1801 } 1802 } 1803 } 1804 1805 /* well, do it the hard way */ 1806 else { 1807 char *p; 1808 apr_time_exp_t tm; 1809 1810 /* can't do this above, because of the getenv call */ 1811 for (p = var; *p; ++p) { 1812 *p = apr_toupper(*p); 1813 } 1814 1815 switch (varlen) { 1816 case 4: 1817 if (!strcmp(var, "TIME")) { 1818 apr_time_exp_lt(&tm, apr_time_now()); 1819 result = apr_psprintf(r->pool, "%04d%02d%02d%02d%02d%02d", 1820 tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, 1821 tm.tm_hour, tm.tm_min, tm.tm_sec); 1822 rewritelog((r, 1, ctx->perdir, "RESULT='%s'", result)); 1823 return (char *)result; 1824 } 1825 break; 1826 1827 case 5: 1828 if (!strcmp(var, "HTTPS")) { 1829 int flag = rewrite_is_https && rewrite_is_https(r->connection); 1830 return apr_pstrdup(r->pool, flag ? "on" : "off"); 1831 } 1832 break; 1833 1834 case 8: 1835 switch (var[6]) { 1836 case 'A': 1837 if (!strcmp(var, "TIME_DAY")) { 1838 apr_time_exp_lt(&tm, apr_time_now()); 1839 return apr_psprintf(r->pool, "%02d", tm.tm_mday); 1840 } 1841 break; 1842 1843 case 'E': 1844 if (!strcmp(var, "TIME_SEC")) { 1845 apr_time_exp_lt(&tm, apr_time_now()); 1846 return apr_psprintf(r->pool, "%02d", tm.tm_sec); 1847 } 1848 break; 1849 1850 case 'I': 1851 if (!strcmp(var, "TIME_MIN")) { 1852 apr_time_exp_lt(&tm, apr_time_now()); 1853 return apr_psprintf(r->pool, "%02d", tm.tm_min); 1854 } 1855 break; 1856 1857 case 'O': 1858 if (!strcmp(var, "TIME_MON")) { 1859 apr_time_exp_lt(&tm, apr_time_now()); 1860 return apr_psprintf(r->pool, "%02d", tm.tm_mon+1); 1861 } 1862 break; 1863 } 1864 break; 1865 1866 case 9: 1867 switch (var[7]) { 1868 case 'A': 1869 if (var[8] == 'Y' && !strcmp(var, "TIME_WDAY")) { 1870 apr_time_exp_lt(&tm, apr_time_now()); 1871 return apr_psprintf(r->pool, "%d", tm.tm_wday); 1872 } 1873 else if (!strcmp(var, "TIME_YEAR")) { 1874 apr_time_exp_lt(&tm, apr_time_now()); 1875 return apr_psprintf(r->pool, "%04d", tm.tm_year+1900); 1876 } 1877 break; 1878 1879 case 'E': 1880 if (!strcmp(var, "IS_SUBREQ")) { 1881 result = (r->main ? "true" : "false"); 1882 } 1883 break; 1884 1885 case 'F': 1886 if (!strcmp(var, "PATH_INFO")) { 1887 result = r->path_info; 1888 } 1889 break; 1890 1891 case 'P': 1892 if (!strcmp(var, "AUTH_TYPE")) { 1893 result = r->ap_auth_type; 1894 } 1895 break; 1896 1897 case 'S': 1898 if (!strcmp(var, "HTTP_HOST")) { 1899 result = lookup_header("Host", ctx); 1900 } 1901 break; 1902 1903 case 'U': 1904 if (!strcmp(var, "TIME_HOUR")) { 1905 apr_time_exp_lt(&tm, apr_time_now()); 1906 return apr_psprintf(r->pool, "%02d", tm.tm_hour); 1907 } 1908 break; 1909 } 1910 break; 1911 1912 case 11: 1913 switch (var[8]) { 1914 case 'A': 1915 if (!strcmp(var, "SERVER_NAME")) { 1916 result = ap_get_server_name(r); 1917 } 1918 break; 1919 1920 case 'D': 1921 if (*var == 'R' && !strcmp(var, "REMOTE_ADDR")) { 1922 result = r->connection->remote_ip; 1923 } 1924 else if (!strcmp(var, "SERVER_ADDR")) { 1925 result = r->connection->local_ip; 1926 } 1927 break; 1928 1929 case 'E': 1930 if (*var == 'H' && !strcmp(var, "HTTP_ACCEPT")) { 1931 result = lookup_header("Accept", ctx); 1932 } 1933 else if (!strcmp(var, "THE_REQUEST")) { 1934 result = r->the_request; 1935 } 1936 break; 1937 1938 case 'I': 1939 if (!strcmp(var, "API_VERSION")) { 1940 return apr_psprintf(r->pool, "%d:%d", 1941 MODULE_MAGIC_NUMBER_MAJOR, 1942 MODULE_MAGIC_NUMBER_MINOR); 1943 } 1944 break; 1945 1946 case 'K': 1947 if (!strcmp(var, "HTTP_COOKIE")) { 1948 result = lookup_header("Cookie", ctx); 1949 } 1950 break; 1951 1952 case 'O': 1953 if (*var == 'S' && !strcmp(var, "SERVER_PORT")) { 1954 return apr_psprintf(r->pool, "%u", ap_get_server_port(r)); 1955 } 1956 else if (var[7] == 'H' && !strcmp(var, "REMOTE_HOST")) { 1957 result = ap_get_remote_host(r->connection,r->per_dir_config, 1958 REMOTE_NAME, NULL); 1959 } 1960 else if (!strcmp(var, "REMOTE_PORT")) { 1961 return apr_itoa(r->pool, r->connection->remote_addr->port); 1962 } 1963 break; 1964 1965 case 'S': 1966 if (*var == 'R' && !strcmp(var, "REMOTE_USER")) { 1967 result = r->user; 1968 } 1969 else if (!strcmp(var, "SCRIPT_USER")) { 1970 result = "<unknown>"; 1971 if (r->finfo.valid & APR_FINFO_USER) { 1972 apr_uid_name_get((char **)&result, r->finfo.user, 1973 r->pool); 1974 } 1975 } 1976 break; 1977 1978 case 'U': 1979 if (!strcmp(var, "REQUEST_URI")) { 1980 result = r->uri; 1981 } 1982 break; 1983 } 1984 break; 1985 1986 case 12: 1987 switch (var[3]) { 1988 case 'I': 1989 if (!strcmp(var, "SCRIPT_GROUP")) { 1990 result = "<unknown>"; 1991 if (r->finfo.valid & APR_FINFO_GROUP) { 1992 apr_gid_name_get((char **)&result, r->finfo.group, 1993 r->pool); 1994 } 1995 } 1996 break; 1997 1998 case 'O': 1999 if (!strcmp(var, "REMOTE_IDENT")) { 2000 result = ap_get_remote_logname(r); 2001 } 2002 break; 2003 2004 case 'P': 2005 if (!strcmp(var, "HTTP_REFERER")) { 2006 result = lookup_header("Referer", ctx); 2007 } 2008 break; 2009 2010 case 'R': 2011 if (!strcmp(var, "QUERY_STRING")) { 2012 result = r->args; 2013 } 2014 break; 2015 2016 case 'V': 2017 if (!strcmp(var, "SERVER_ADMIN")) { 2018 result = r->server->server_admin; 2019 } 2020 break; 2021 } 2022 break; 2023 2024 case 13: 2025 if (!strcmp(var, "DOCUMENT_ROOT")) { 2026 result = ap_document_root(r); 2027 } 2028 break; 2029 2030 case 14: 2031 if (*var == 'H' && !strcmp(var, "HTTP_FORWARDED")) { 2032 result = lookup_header("Forwarded", ctx); 2033 } 2034 else if (!strcmp(var, "REQUEST_METHOD")) { 2035 result = r->method; 2036 } 2037 break; 2038 2039 case 15: 2040 switch (var[7]) { 2041 case 'E': 2042 if (!strcmp(var, "HTTP_USER_AGENT")) { 2043 result = lookup_header("User-Agent", ctx); 2044 } 2045 break; 2046 2047 case 'F': 2048 if (!strcmp(var, "SCRIPT_FILENAME")) { 2049 result = r->filename; /* same as request_filename (16) */ 2050 } 2051 break; 2052 2053 case 'P': 2054 if (!strcmp(var, "SERVER_PROTOCOL")) { 2055 result = r->protocol; 2056 } 2057 break; 2058 2059 case 'S': 2060 if (!strcmp(var, "SERVER_SOFTWARE")) { 2061 result = ap_get_server_banner(); 2062 } 2063 break; 2064 } 2065 break; 2066 2067 case 16: 2068 if (!strcmp(var, "REQUEST_FILENAME")) { 2069 result = r->filename; /* same as script_filename (15) */ 2070 } 2071 break; 2072 2073 case 21: 2074 if (!strcmp(var, "HTTP_PROXY_CONNECTION")) { 2075 result = lookup_header("Proxy-Connection", ctx); 2076 } 2077 break; 2078 } 2079 } 2080 2081 return apr_pstrdup(r->pool, result ? result : ""); 2082} 2083 2084 2085/* 2086 * +-------------------------------------------------------+ 2087 * | | 2088 * | Expansion functions 2089 * | | 2090 * +-------------------------------------------------------+ 2091 */ 2092 2093/* 2094 * Bracketed expression handling 2095 * s points after the opening bracket 2096 */ 2097static APR_INLINE char *find_closing_curly(char *s) 2098{ 2099 unsigned depth; 2100 2101 for (depth = 1; *s; ++s) { 2102 if (*s == RIGHT_CURLY && --depth == 0) { 2103 return s; 2104 } 2105 else if (*s == LEFT_CURLY) { 2106 ++depth; 2107 } 2108 } 2109 2110 return NULL; 2111} 2112 2113static APR_INLINE char *find_char_in_curlies(char *s, int c) 2114{ 2115 unsigned depth; 2116 2117 for (depth = 1; *s; ++s) { 2118 if (*s == c && depth == 1) { 2119 return s; 2120 } 2121 else if (*s == RIGHT_CURLY && --depth == 0) { 2122 return NULL; 2123 } 2124 else if (*s == LEFT_CURLY) { 2125 ++depth; 2126 } 2127 } 2128 2129 return NULL; 2130} 2131 2132/* perform all the expansions on the input string 2133 * putting the result into a new string 2134 * 2135 * for security reasons this expansion must be performed in a 2136 * single pass, otherwise an attacker can arrange for the result 2137 * of an earlier expansion to include expansion specifiers that 2138 * are interpreted by a later expansion, producing results that 2139 * were not intended by the administrator. 2140 */ 2141static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry) 2142{ 2143 result_list *result, *current; 2144 result_list sresult[SMALL_EXPANSION]; 2145 unsigned spc = 0; 2146 apr_size_t span, inputlen, outlen; 2147 char *p, *c; 2148 apr_pool_t *pool = ctx->r->pool; 2149 2150 span = strcspn(input, "\\$%"); 2151 inputlen = strlen(input); 2152 2153 /* fast exit */ 2154 if (inputlen == span) { 2155 return apr_pstrdup(pool, input); 2156 } 2157 2158 /* well, actually something to do */ 2159 result = current = &(sresult[spc++]); 2160 2161 p = input + span; 2162 current->next = NULL; 2163 current->string = input; 2164 current->len = span; 2165 outlen = span; 2166 2167 /* loop for specials */ 2168 do { 2169 /* prepare next entry */ 2170 if (current->len) { 2171 current->next = (spc < SMALL_EXPANSION) 2172 ? &(sresult[spc++]) 2173 : (result_list *)apr_palloc(pool, 2174 sizeof(result_list)); 2175 current = current->next; 2176 current->next = NULL; 2177 current->len = 0; 2178 } 2179 2180 /* escaped character */ 2181 if (*p == '\\') { 2182 current->len = 1; 2183 ++outlen; 2184 if (!p[1]) { 2185 current->string = p; 2186 break; 2187 } 2188 else { 2189 current->string = ++p; 2190 ++p; 2191 } 2192 } 2193 2194 /* variable or map lookup */ 2195 else if (p[1] == '{') { 2196 char *endp; 2197 2198 endp = find_closing_curly(p+2); 2199 if (!endp) { 2200 current->len = 2; 2201 current->string = p; 2202 outlen += 2; 2203 p += 2; 2204 } 2205 2206 /* variable lookup */ 2207 else if (*p == '%') { 2208 p = lookup_variable(apr_pstrmemdup(pool, p+2, endp-p-2), ctx); 2209 2210 span = strlen(p); 2211 current->len = span; 2212 current->string = p; 2213 outlen += span; 2214 p = endp + 1; 2215 } 2216 2217 /* map lookup */ 2218 else { /* *p == '$' */ 2219 char *key; 2220 2221 /* 2222 * To make rewrite maps useful, the lookup key and 2223 * default values must be expanded, so we make 2224 * recursive calls to do the work. For security 2225 * reasons we must never expand a string that includes 2226 * verbatim data from the network. The recursion here 2227 * isn't a problem because the result of expansion is 2228 * only passed to lookup_map() so it cannot be 2229 * re-expanded, only re-looked-up. Another way of 2230 * looking at it is that the recursion is entirely 2231 * driven by the syntax of the nested curly brackets. 2232 */ 2233 2234 key = find_char_in_curlies(p+2, ':'); 2235 if (!key) { 2236 current->len = 2; 2237 current->string = p; 2238 outlen += 2; 2239 p += 2; 2240 } 2241 else { 2242 char *map, *dflt; 2243 2244 map = apr_pstrmemdup(pool, p+2, endp-p-2); 2245 key = map + (key-p-2); 2246 *key++ = '\0'; 2247 dflt = find_char_in_curlies(key, '|'); 2248 if (dflt) { 2249 *dflt++ = '\0'; 2250 } 2251 2252 /* reuse of key variable as result */ 2253 key = lookup_map(ctx->r, map, do_expand(key, ctx, entry)); 2254 2255 if (!key && dflt && *dflt) { 2256 key = do_expand(dflt, ctx, entry); 2257 } 2258 2259 if (key) { 2260 span = strlen(key); 2261 current->len = span; 2262 current->string = key; 2263 outlen += span; 2264 } 2265 2266 p = endp + 1; 2267 } 2268 } 2269 } 2270 2271 /* backreference */ 2272 else if (apr_isdigit(p[1])) { 2273 int n = p[1] - '0'; 2274 backrefinfo *bri = (*p == '$') ? &ctx->briRR : &ctx->briRC; 2275 2276 /* see ap_pregsub() in server/util.c */ 2277 if (bri->source && n < AP_MAX_REG_MATCH 2278 && bri->regmatch[n].rm_eo > bri->regmatch[n].rm_so) { 2279 span = bri->regmatch[n].rm_eo - bri->regmatch[n].rm_so; 2280 if (entry && (entry->flags & RULEFLAG_ESCAPEBACKREF)) { 2281 /* escape the backreference */ 2282 char *tmp2, *tmp; 2283 tmp = apr_palloc(pool, span + 1); 2284 strncpy(tmp, bri->source + bri->regmatch[n].rm_so, span); 2285 tmp[span] = '\0'; 2286 tmp2 = escape_uri(pool, tmp); 2287 rewritelog((ctx->r, 5, ctx->perdir, "escaping backreference '%s' to '%s'", 2288 tmp, tmp2)); 2289 2290 current->len = span = strlen(tmp2); 2291 current->string = tmp2; 2292 } else { 2293 current->len = span; 2294 current->string = bri->source + bri->regmatch[n].rm_so; 2295 } 2296 2297 outlen += span; 2298 } 2299 2300 p += 2; 2301 } 2302 2303 /* not for us, just copy it */ 2304 else { 2305 current->len = 1; 2306 current->string = p++; 2307 ++outlen; 2308 } 2309 2310 /* check the remainder */ 2311 if (*p && (span = strcspn(p, "\\$%")) > 0) { 2312 if (current->len) { 2313 current->next = (spc < SMALL_EXPANSION) 2314 ? &(sresult[spc++]) 2315 : (result_list *)apr_palloc(pool, 2316 sizeof(result_list)); 2317 current = current->next; 2318 current->next = NULL; 2319 } 2320 2321 current->len = span; 2322 current->string = p; 2323 p += span; 2324 outlen += span; 2325 } 2326 2327 } while (p < input+inputlen); 2328 2329 /* assemble result */ 2330 c = p = apr_palloc(pool, outlen + 1); /* don't forget the \0 */ 2331 do { 2332 if (result->len) { 2333 ap_assert(c+result->len <= p+outlen); /* XXX: can be removed after 2334 * extensive testing and 2335 * review 2336 */ 2337 memcpy(c, result->string, result->len); 2338 c += result->len; 2339 } 2340 result = result->next; 2341 } while (result); 2342 2343 p[outlen] = '\0'; 2344 2345 return p; 2346} 2347 2348/* 2349 * perform all the expansions on the environment variables 2350 */ 2351static void do_expand_env(data_item *env, rewrite_ctx *ctx) 2352{ 2353 char *name, *val; 2354 2355 while (env) { 2356 name = do_expand(env->data, ctx, NULL); 2357 if (*name == '!') { 2358 name++; 2359 apr_table_unset(ctx->r->subprocess_env, name); 2360 rewritelog((ctx->r, 5, NULL, "unsetting env variable '%s'", name)); 2361 } 2362 else { 2363 if ((val = ap_strchr(name, ':')) != NULL) { 2364 *val++ = '\0'; 2365 } else { 2366 val = ""; 2367 } 2368 2369 apr_table_set(ctx->r->subprocess_env, name, val); 2370 rewritelog((ctx->r, 5, NULL, "setting env variable '%s' to '%s'", 2371 name, val)); 2372 } 2373 2374 env = env->next; 2375 } 2376 2377 return; 2378} 2379 2380/* 2381 * perform all the expansions on the cookies 2382 * 2383 * TODO: use cached time similar to how logging does it 2384 */ 2385static void add_cookie(request_rec *r, char *s) 2386{ 2387 char *var; 2388 char *val; 2389 char *domain; 2390 char *expires; 2391 char *path; 2392 char *secure; 2393 char *httponly; 2394 2395 char *tok_cntx; 2396 char *cookie; 2397 2398 var = apr_strtok(s, ":", &tok_cntx); 2399 val = apr_strtok(NULL, ":", &tok_cntx); 2400 domain = apr_strtok(NULL, ":", &tok_cntx); 2401 2402 if (var && val && domain) { 2403 request_rec *rmain = r; 2404 char *notename; 2405 void *data; 2406 2407 while (rmain->main) { 2408 rmain = rmain->main; 2409 } 2410 2411 notename = apr_pstrcat(rmain->pool, var, "_rewrite", NULL); 2412 apr_pool_userdata_get(&data, notename, rmain->pool); 2413 if (!data) { 2414 char *exp_time = NULL; 2415 2416 expires = apr_strtok(NULL, ":", &tok_cntx); 2417 path = expires ? apr_strtok(NULL, ":", &tok_cntx) : NULL; 2418 secure = path ? apr_strtok(NULL, ":", &tok_cntx) : NULL; 2419 httponly = secure ? apr_strtok(NULL, ":", &tok_cntx) : NULL; 2420 2421 if (expires) { 2422 apr_time_exp_t tms; 2423 apr_time_exp_gmt(&tms, r->request_time 2424 + apr_time_from_sec((60 * atol(expires)))); 2425 exp_time = apr_psprintf(r->pool, "%s, %.2d-%s-%.4d " 2426 "%.2d:%.2d:%.2d GMT", 2427 apr_day_snames[tms.tm_wday], 2428 tms.tm_mday, 2429 apr_month_snames[tms.tm_mon], 2430 tms.tm_year+1900, 2431 tms.tm_hour, tms.tm_min, tms.tm_sec); 2432 } 2433 2434 cookie = apr_pstrcat(rmain->pool, 2435 var, "=", val, 2436 "; path=", path ? path : "/", 2437 "; domain=", domain, 2438 expires ? "; expires=" : NULL, 2439 expires ? exp_time : NULL, 2440 (secure && (!strcasecmp(secure, "true") 2441 || !strcmp(secure, "1") 2442 || !strcasecmp(secure, 2443 "secure"))) ? 2444 "; secure" : NULL, 2445 (httponly && (!strcasecmp(httponly, "true") 2446 || !strcmp(httponly, "1") 2447 || !strcasecmp(httponly, 2448 "HttpOnly"))) ? 2449 "; HttpOnly" : NULL, 2450 NULL); 2451 2452 apr_table_addn(rmain->err_headers_out, "Set-Cookie", cookie); 2453 apr_pool_userdata_set("set", notename, NULL, rmain->pool); 2454 rewritelog((rmain, 5, NULL, "setting cookie '%s'", cookie)); 2455 } 2456 else { 2457 rewritelog((rmain, 5, NULL, "skipping already set cookie '%s'", 2458 var)); 2459 } 2460 } 2461 2462 return; 2463} 2464 2465static void do_expand_cookie(data_item *cookie, rewrite_ctx *ctx) 2466{ 2467 while (cookie) { 2468 add_cookie(ctx->r, do_expand(cookie->data, ctx, NULL)); 2469 cookie = cookie->next; 2470 } 2471 2472 return; 2473} 2474 2475#if APR_HAS_USER 2476/* 2477 * Expand tilde-paths (/~user) through Unix /etc/passwd 2478 * database information (or other OS-specific database) 2479 */ 2480static char *expand_tildepaths(request_rec *r, char *uri) 2481{ 2482 if (uri && *uri == '/' && uri[1] == '~') { 2483 char *p, *user; 2484 2485 p = user = uri + 2; 2486 while (*p && *p != '/') { 2487 ++p; 2488 } 2489 2490 if (p > user) { 2491 char *homedir; 2492 2493 user = apr_pstrmemdup(r->pool, user, p-user); 2494 if (apr_uid_homepath_get(&homedir, user, r->pool) == APR_SUCCESS) { 2495 if (*p) { 2496 /* reuse of user variable */ 2497 user = homedir + strlen(homedir) - 1; 2498 if (user >= homedir && *user == '/') { 2499 *user = '\0'; 2500 } 2501 2502 return apr_pstrcat(r->pool, homedir, p, NULL); 2503 } 2504 else { 2505 return homedir; 2506 } 2507 } 2508 } 2509 } 2510 2511 return uri; 2512} 2513#endif /* if APR_HAS_USER */ 2514 2515 2516/* 2517 * +-------------------------------------------------------+ 2518 * | | 2519 * | rewriting lockfile support 2520 * | | 2521 * +-------------------------------------------------------+ 2522 */ 2523 2524static apr_status_t rewritelock_create(server_rec *s, apr_pool_t *p) 2525{ 2526 apr_status_t rc; 2527 2528 /* only operate if a lockfile is used */ 2529 if (lockname == NULL || *(lockname) == '\0') { 2530 return APR_SUCCESS; 2531 } 2532 2533 /* create the lockfile */ 2534 rc = apr_global_mutex_create(&rewrite_mapr_lock_acquire, lockname, 2535 APR_LOCK_DEFAULT, p); 2536 if (rc != APR_SUCCESS) { 2537 ap_log_error(APLOG_MARK, APLOG_CRIT, rc, s, 2538 "mod_rewrite: Parent could not create RewriteLock " 2539 "file %s", lockname); 2540 return rc; 2541 } 2542 2543#ifdef AP_NEED_SET_MUTEX_PERMS 2544 rc = unixd_set_global_mutex_perms(rewrite_mapr_lock_acquire); 2545 if (rc != APR_SUCCESS) { 2546 ap_log_error(APLOG_MARK, APLOG_CRIT, rc, s, 2547 "mod_rewrite: Parent could not set permissions " 2548 "on RewriteLock; check User and Group directives"); 2549 return rc; 2550 } 2551#endif 2552 2553 return APR_SUCCESS; 2554} 2555 2556static apr_status_t rewritelock_remove(void *data) 2557{ 2558 /* only operate if a lockfile is used */ 2559 if (lockname == NULL || *(lockname) == '\0') { 2560 return APR_SUCCESS; 2561 } 2562 2563 /* destroy the rewritelock */ 2564 apr_global_mutex_destroy (rewrite_mapr_lock_acquire); 2565 rewrite_mapr_lock_acquire = NULL; 2566 lockname = NULL; 2567 return(0); 2568} 2569 2570 2571/* 2572 * +-------------------------------------------------------+ 2573 * | | 2574 * | configuration directive handling 2575 * | | 2576 * +-------------------------------------------------------+ 2577 */ 2578 2579/* 2580 * own command line parser for RewriteRule and RewriteCond, 2581 * which doesn't have the '\\' problem. 2582 * (returns true on error) 2583 * 2584 * XXX: what an inclined parser. Seems we have to leave it so 2585 * for backwards compat. *sigh* 2586 */ 2587static int parseargline(char *str, char **a1, char **a2, char **a3) 2588{ 2589 char quote; 2590 2591 while (apr_isspace(*str)) { 2592 ++str; 2593 } 2594 2595 /* 2596 * determine first argument 2597 */ 2598 quote = (*str == '"' || *str == '\'') ? *str++ : '\0'; 2599 *a1 = str; 2600 2601 for (; *str; ++str) { 2602 if ((apr_isspace(*str) && !quote) || (*str == quote)) { 2603 break; 2604 } 2605 if (*str == '\\' && apr_isspace(str[1])) { 2606 ++str; 2607 continue; 2608 } 2609 } 2610 2611 if (!*str) { 2612 return 1; 2613 } 2614 *str++ = '\0'; 2615 2616 while (apr_isspace(*str)) { 2617 ++str; 2618 } 2619 2620 /* 2621 * determine second argument 2622 */ 2623 quote = (*str == '"' || *str == '\'') ? *str++ : '\0'; 2624 *a2 = str; 2625 2626 for (; *str; ++str) { 2627 if ((apr_isspace(*str) && !quote) || (*str == quote)) { 2628 break; 2629 } 2630 if (*str == '\\' && apr_isspace(str[1])) { 2631 ++str; 2632 continue; 2633 } 2634 } 2635 2636 if (!*str) { 2637 *a3 = NULL; /* 3rd argument is optional */ 2638 return 0; 2639 } 2640 *str++ = '\0'; 2641 2642 while (apr_isspace(*str)) { 2643 ++str; 2644 } 2645 2646 if (!*str) { 2647 *a3 = NULL; /* 3rd argument is still optional */ 2648 return 0; 2649 } 2650 2651 /* 2652 * determine third argument 2653 */ 2654 quote = (*str == '"' || *str == '\'') ? *str++ : '\0'; 2655 *a3 = str; 2656 for (; *str; ++str) { 2657 if ((apr_isspace(*str) && !quote) || (*str == quote)) { 2658 break; 2659 } 2660 if (*str == '\\' && apr_isspace(str[1])) { 2661 ++str; 2662 continue; 2663 } 2664 } 2665 *str = '\0'; 2666 2667 return 0; 2668} 2669 2670static void *config_server_create(apr_pool_t *p, server_rec *s) 2671{ 2672 rewrite_server_conf *a; 2673 2674 a = (rewrite_server_conf *)apr_pcalloc(p, sizeof(rewrite_server_conf)); 2675 2676 a->state = ENGINE_DISABLED; 2677 a->options = OPTION_NONE; 2678#ifndef REWRITELOG_DISABLED 2679 a->rewritelogfile = NULL; 2680 a->rewritelogfp = NULL; 2681 a->rewriteloglevel = 0; 2682#endif 2683 a->rewritemaps = apr_hash_make(p); 2684 a->rewriteconds = apr_array_make(p, 2, sizeof(rewritecond_entry)); 2685 a->rewriterules = apr_array_make(p, 2, sizeof(rewriterule_entry)); 2686 a->server = s; 2687 2688 return (void *)a; 2689} 2690 2691static void *config_server_merge(apr_pool_t *p, void *basev, void *overridesv) 2692{ 2693 rewrite_server_conf *a, *base, *overrides; 2694 2695 a = (rewrite_server_conf *)apr_pcalloc(p, 2696 sizeof(rewrite_server_conf)); 2697 base = (rewrite_server_conf *)basev; 2698 overrides = (rewrite_server_conf *)overridesv; 2699 2700 a->state = (overrides->state_set == 0) ? base->state : overrides->state; 2701 a->state_set = overrides->state_set || base->state_set; 2702 a->options = (overrides->options_set == 0) ? base->options : overrides->options; 2703 a->options_set = overrides->options_set || base->options_set; 2704 2705 a->server = overrides->server; 2706 2707 if (a->options & OPTION_INHERIT) { 2708 /* 2709 * local directives override 2710 * and anything else is inherited 2711 */ 2712#ifndef REWRITELOG_DISABLED 2713 a->rewriteloglevel = overrides->rewriteloglevel != 0 2714 ? overrides->rewriteloglevel 2715 : base->rewriteloglevel; 2716 a->rewritelogfile = overrides->rewritelogfile != NULL 2717 ? overrides->rewritelogfile 2718 : base->rewritelogfile; 2719 a->rewritelogfp = overrides->rewritelogfp != NULL 2720 ? overrides->rewritelogfp 2721 : base->rewritelogfp; 2722#endif 2723 a->rewritemaps = apr_hash_overlay(p, overrides->rewritemaps, 2724 base->rewritemaps); 2725 a->rewriteconds = apr_array_append(p, overrides->rewriteconds, 2726 base->rewriteconds); 2727 a->rewriterules = apr_array_append(p, overrides->rewriterules, 2728 base->rewriterules); 2729 } 2730 else { 2731 /* 2732 * local directives override 2733 * and anything else gets defaults 2734 */ 2735#ifndef REWRITELOG_DISABLED 2736 a->rewriteloglevel = overrides->rewriteloglevel; 2737 a->rewritelogfile = overrides->rewritelogfile; 2738 a->rewritelogfp = overrides->rewritelogfp; 2739#endif 2740 a->rewritemaps = overrides->rewritemaps; 2741 a->rewriteconds = overrides->rewriteconds; 2742 a->rewriterules = overrides->rewriterules; 2743 } 2744 2745 return (void *)a; 2746} 2747 2748static void *config_perdir_create(apr_pool_t *p, char *path) 2749{ 2750 rewrite_perdir_conf *a; 2751 2752 a = (rewrite_perdir_conf *)apr_pcalloc(p, sizeof(rewrite_perdir_conf)); 2753 2754 a->state = ENGINE_DISABLED; 2755 a->options = OPTION_NONE; 2756 a->baseurl = NULL; 2757 a->rewriteconds = apr_array_make(p, 2, sizeof(rewritecond_entry)); 2758 a->rewriterules = apr_array_make(p, 2, sizeof(rewriterule_entry)); 2759 2760 if (path == NULL) { 2761 a->directory = NULL; 2762 } 2763 else { 2764 /* make sure it has a trailing slash */ 2765 if (path[strlen(path)-1] == '/') { 2766 a->directory = apr_pstrdup(p, path); 2767 } 2768 else { 2769 a->directory = apr_pstrcat(p, path, "/", NULL); 2770 } 2771 } 2772 2773 return (void *)a; 2774} 2775 2776static void *config_perdir_merge(apr_pool_t *p, void *basev, void *overridesv) 2777{ 2778 rewrite_perdir_conf *a, *base, *overrides; 2779 2780 a = (rewrite_perdir_conf *)apr_pcalloc(p, 2781 sizeof(rewrite_perdir_conf)); 2782 base = (rewrite_perdir_conf *)basev; 2783 overrides = (rewrite_perdir_conf *)overridesv; 2784 2785 a->state = (overrides->state_set == 0) ? base->state : overrides->state; 2786 a->state_set = overrides->state_set || base->state_set; 2787 a->options = (overrides->options_set == 0) ? base->options : overrides->options; 2788 a->options_set = overrides->options_set || base->options_set; 2789 2790 if (a->options & OPTION_MERGEBASE) { 2791 a->baseurl = (overrides->baseurl_set == 0) ? base->baseurl : overrides->baseurl; 2792 a->baseurl_set = overrides->baseurl_set || base->baseurl_set; 2793 } 2794 else { 2795 a->baseurl = overrides->baseurl; 2796 } 2797 2798 a->directory = overrides->directory; 2799 2800 if (a->options & OPTION_INHERIT) { 2801 a->rewriteconds = apr_array_append(p, overrides->rewriteconds, 2802 base->rewriteconds); 2803 a->rewriterules = apr_array_append(p, overrides->rewriterules, 2804 base->rewriterules); 2805 } 2806 else { 2807 a->rewriteconds = overrides->rewriteconds; 2808 a->rewriterules = overrides->rewriterules; 2809 } 2810 2811 return (void *)a; 2812} 2813 2814static const char *cmd_rewriteengine(cmd_parms *cmd, 2815 void *in_dconf, int flag) 2816{ 2817 rewrite_perdir_conf *dconf = in_dconf; 2818 rewrite_server_conf *sconf; 2819 2820 sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); 2821 2822 /* server command? set both global scope and base directory scope */ 2823 if (cmd->path == NULL) { 2824 sconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED); 2825 sconf->state_set = 1; 2826 dconf->state = sconf->state; 2827 dconf->state_set = 1; 2828 } 2829 /* directory command? set directory scope only */ 2830 else { 2831 dconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED); 2832 dconf->state_set = 1; 2833 } 2834 2835 return NULL; 2836} 2837 2838static const char *cmd_rewriteoptions(cmd_parms *cmd, 2839 void *in_dconf, const char *option) 2840{ 2841 int options = 0; 2842 char *w; 2843 2844 while (*option) { 2845 w = ap_getword_conf(cmd->pool, &option); 2846 2847 if (!strcasecmp(w, "inherit")) { 2848 options |= OPTION_INHERIT; 2849 } 2850 else if (!strncasecmp(w, "MaxRedirects=", 13)) { 2851 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, 2852 "RewriteOptions: MaxRedirects option has been " 2853 "removed in favor of the global " 2854 "LimitInternalRecursion directive and will be " 2855 "ignored."); 2856 } 2857 else if (!strcasecmp(w, "allowanyuri")) { 2858 options |= OPTION_ANYURI; 2859 } 2860 else if (!strcasecmp(w, "mergebase")) { 2861 options |= OPTION_MERGEBASE; 2862 } 2863 else { 2864 return apr_pstrcat(cmd->pool, "RewriteOptions: unknown option '", 2865 w, "'", NULL); 2866 } 2867 } 2868 2869 /* server command? set both global scope and base directory scope */ 2870 if (cmd->path == NULL) { /* is server command */ 2871 rewrite_perdir_conf *dconf = in_dconf; 2872 rewrite_server_conf *sconf = 2873 ap_get_module_config(cmd->server->module_config, 2874 &rewrite_module); 2875 2876 sconf->options |= options; 2877 sconf->options_set = 1; 2878 dconf->options |= options; 2879 dconf->options_set = 1; 2880 } 2881 /* directory command? set directory scope only */ 2882 else { /* is per-directory command */ 2883 rewrite_perdir_conf *dconf = in_dconf; 2884 2885 dconf->options |= options; 2886 dconf->options_set = 1; 2887 } 2888 2889 return NULL; 2890} 2891 2892#ifndef REWRITELOG_DISABLED 2893static const char *cmd_rewritelog(cmd_parms *cmd, void *dconf, const char *a1) 2894{ 2895 rewrite_server_conf *sconf; 2896 2897 sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); 2898 sconf->rewritelogfile = a1; 2899 2900 return NULL; 2901} 2902 2903static const char *cmd_rewriteloglevel(cmd_parms *cmd, void *dconf, 2904 const char *a1) 2905{ 2906 rewrite_server_conf *sconf; 2907 2908 sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); 2909 sconf->rewriteloglevel = atoi(a1); 2910 2911 return NULL; 2912} 2913#endif /* rewritelog */ 2914 2915static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, const char *a1, 2916 const char *a2) 2917{ 2918 rewrite_server_conf *sconf; 2919 rewritemap_entry *newmap; 2920 apr_finfo_t st; 2921 const char *fname; 2922 2923 sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); 2924 2925 newmap = apr_palloc(cmd->pool, sizeof(rewritemap_entry)); 2926 newmap->func = NULL; 2927 2928 if (strncasecmp(a2, "txt:", 4) == 0) { 2929 if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) { 2930 return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ", 2931 a2+4, NULL); 2932 } 2933 2934 newmap->type = MAPTYPE_TXT; 2935 newmap->datafile = fname; 2936 newmap->checkfile = fname; 2937 newmap->checkfile2= NULL; 2938 newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s", 2939 (void *)cmd->server, a1); 2940 } 2941 else if (strncasecmp(a2, "rnd:", 4) == 0) { 2942 if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) { 2943 return apr_pstrcat(cmd->pool, "RewriteMap: bad path to rnd map: ", 2944 a2+4, NULL); 2945 } 2946 2947 newmap->type = MAPTYPE_RND; 2948 newmap->datafile = fname; 2949 newmap->checkfile = fname; 2950 newmap->checkfile2= NULL; 2951 newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s", 2952 (void *)cmd->server, a1); 2953 } 2954 else if (strncasecmp(a2, "dbm", 3) == 0) { 2955 apr_status_t rv; 2956 2957 newmap->type = MAPTYPE_DBM; 2958 fname = NULL; 2959 newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s", 2960 (void *)cmd->server, a1); 2961 2962 if (a2[3] == ':') { 2963 newmap->dbmtype = "default"; 2964 fname = a2+4; 2965 } 2966 else if (a2[3] == '=') { 2967 const char *colon = ap_strchr_c(a2 + 4, ':'); 2968 2969 if (colon) { 2970 newmap->dbmtype = apr_pstrndup(cmd->pool, a2 + 4, 2971 colon - (a2 + 3) - 1); 2972 fname = colon + 1; 2973 } 2974 } 2975 2976 if (!fname) { 2977 return apr_pstrcat(cmd->pool, "RewriteMap: bad map:", 2978 a2, NULL); 2979 } 2980 2981 if ((newmap->datafile = ap_server_root_relative(cmd->pool, 2982 fname)) == NULL) { 2983 return apr_pstrcat(cmd->pool, "RewriteMap: bad path to dbm map: ", 2984 fname, NULL); 2985 } 2986 2987 rv = apr_dbm_get_usednames_ex(cmd->pool, newmap->dbmtype, 2988 newmap->datafile, &newmap->checkfile, 2989 &newmap->checkfile2); 2990 if (rv != APR_SUCCESS) { 2991 return apr_pstrcat(cmd->pool, "RewriteMap: dbm type ", 2992 newmap->dbmtype, " is invalid", NULL); 2993 } 2994 } 2995 else if (strncasecmp(a2, "prg:", 4) == 0) { 2996 apr_tokenize_to_argv(a2 + 4, &newmap->argv, cmd->pool); 2997 2998 fname = newmap->argv[0]; 2999 if ((newmap->argv[0] = ap_server_root_relative(cmd->pool, 3000 fname)) == NULL) { 3001 return apr_pstrcat(cmd->pool, "RewriteMap: bad path to prg map: ", 3002 fname, NULL); 3003 } 3004 3005 newmap->type = MAPTYPE_PRG; 3006 newmap->datafile = NULL; 3007 newmap->checkfile = newmap->argv[0]; 3008 newmap->checkfile2= NULL; 3009 newmap->cachename = NULL; 3010 } 3011 else if (strncasecmp(a2, "int:", 4) == 0) { 3012 newmap->type = MAPTYPE_INT; 3013 newmap->datafile = NULL; 3014 newmap->checkfile = NULL; 3015 newmap->checkfile2= NULL; 3016 newmap->cachename = NULL; 3017 newmap->func = (char *(*)(request_rec *,char *)) 3018 apr_hash_get(mapfunc_hash, a2+4, strlen(a2+4)); 3019 if (newmap->func == NULL) { 3020 return apr_pstrcat(cmd->pool, "RewriteMap: internal map not found:", 3021 a2+4, NULL); 3022 } 3023 } 3024 else { 3025 if ((fname = ap_server_root_relative(cmd->pool, a2)) == NULL) { 3026 return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ", 3027 a2, NULL); 3028 } 3029 3030 newmap->type = MAPTYPE_TXT; 3031 newmap->datafile = fname; 3032 newmap->checkfile = fname; 3033 newmap->checkfile2= NULL; 3034 newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s", 3035 (void *)cmd->server, a1); 3036 } 3037 newmap->fpin = NULL; 3038 newmap->fpout = NULL; 3039 3040 if (newmap->checkfile 3041 && (apr_stat(&st, newmap->checkfile, APR_FINFO_MIN, 3042 cmd->pool) != APR_SUCCESS)) { 3043 return apr_pstrcat(cmd->pool, 3044 "RewriteMap: file for map ", a1, 3045 " not found:", newmap->checkfile, NULL); 3046 } 3047 3048 apr_hash_set(sconf->rewritemaps, a1, APR_HASH_KEY_STRING, newmap); 3049 3050 return NULL; 3051} 3052 3053static const char *cmd_rewritelock(cmd_parms *cmd, void *dconf, const char *a1) 3054{ 3055 const char *error; 3056 3057 if ((error = ap_check_cmd_context(cmd, GLOBAL_ONLY)) != NULL) 3058 return error; 3059 3060 /* fixup the path, especially for rewritelock_remove() */ 3061 lockname = ap_server_root_relative(cmd->pool, a1); 3062 3063 if (!lockname) { 3064 return apr_pstrcat(cmd->pool, "Invalid RewriteLock path ", a1, NULL); 3065 } 3066 3067 return NULL; 3068} 3069 3070static const char *cmd_rewritebase(cmd_parms *cmd, void *in_dconf, 3071 const char *a1) 3072{ 3073 rewrite_perdir_conf *dconf = in_dconf; 3074 3075 if (cmd->path == NULL || dconf == NULL) { 3076 return "RewriteBase: only valid in per-directory config files"; 3077 } 3078 if (a1[0] == '\0') { 3079 return "RewriteBase: empty URL not allowed"; 3080 } 3081 if (a1[0] != '/') { 3082 return "RewriteBase: argument is not a valid URL"; 3083 } 3084 3085 dconf->baseurl = a1; 3086 dconf->baseurl_set = 1; 3087 3088 return NULL; 3089} 3090 3091/* 3092 * generic lexer for RewriteRule and RewriteCond flags. 3093 * The parser will be passed in as a function pointer 3094 * and called if a flag was found 3095 */ 3096static const char *cmd_parseflagfield(apr_pool_t *p, void *cfg, char *key, 3097 const char *(*parse)(apr_pool_t *, 3098 void *, 3099 char *, char *)) 3100{ 3101 char *val, *nextp, *endp; 3102 const char *err; 3103 3104 endp = key + strlen(key) - 1; 3105 if (*key != '[' || *endp != ']') { 3106 return "bad flag delimiters"; 3107 } 3108 3109 *endp = ','; /* for simpler parsing */ 3110 ++key; 3111 3112 while (*key) { 3113 /* skip leading spaces */ 3114 while (apr_isspace(*key)) { 3115 ++key; 3116 } 3117 3118 if (!*key || (nextp = ap_strchr(key, ',')) == NULL) { /* NULL should not 3119 * happen, but ... 3120 */ 3121 break; 3122 } 3123 3124 /* strip trailing spaces */ 3125 endp = nextp - 1; 3126 while (apr_isspace(*endp)) { 3127 --endp; 3128 } 3129 *++endp = '\0'; 3130 3131 /* split key and val */ 3132 val = ap_strchr(key, '='); 3133 if (val) { 3134 *val++ = '\0'; 3135 } 3136 else { 3137 val = endp; 3138 } 3139 3140 err = parse(p, cfg, key, val); 3141 if (err) { 3142 return err; 3143 } 3144 3145 key = nextp + 1; 3146 } 3147 3148 return NULL; 3149} 3150 3151static const char *cmd_rewritecond_setflag(apr_pool_t *p, void *_cfg, 3152 char *key, char *val) 3153{ 3154 rewritecond_entry *cfg = _cfg; 3155 3156 if ( strcasecmp(key, "nocase") == 0 3157 || strcasecmp(key, "NC") == 0 ) { 3158 cfg->flags |= CONDFLAG_NOCASE; 3159 } 3160 else if ( strcasecmp(key, "ornext") == 0 3161 || strcasecmp(key, "OR") == 0 ) { 3162 cfg->flags |= CONDFLAG_ORNEXT; 3163 } 3164 else if ( strcasecmp(key, "novary") == 0 3165 || strcasecmp(key, "NV") == 0 ) { 3166 cfg->flags |= CONDFLAG_NOVARY; 3167 } 3168 else { 3169 return apr_pstrcat(p, "unknown flag '", key, "'", NULL); 3170 } 3171 return NULL; 3172} 3173 3174static const char *cmd_rewritecond(cmd_parms *cmd, void *in_dconf, 3175 const char *in_str) 3176{ 3177 rewrite_perdir_conf *dconf = in_dconf; 3178 char *str = apr_pstrdup(cmd->pool, in_str); 3179 rewrite_server_conf *sconf; 3180 rewritecond_entry *newcond; 3181 ap_regex_t *regexp; 3182 char *a1; 3183 char *a2; 3184 char *a3; 3185 const char *err; 3186 3187 sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); 3188 3189 /* make a new entry in the internal temporary rewrite rule list */ 3190 if (cmd->path == NULL) { /* is server command */ 3191 newcond = apr_array_push(sconf->rewriteconds); 3192 } 3193 else { /* is per-directory command */ 3194 newcond = apr_array_push(dconf->rewriteconds); 3195 } 3196 3197 /* parse the argument line ourself 3198 * a1 .. a3 are substrings of str, which is a fresh copy 3199 * of the argument line. So we can use a1 .. a3 without 3200 * copying them again. 3201 */ 3202 if (parseargline(str, &a1, &a2, &a3)) { 3203 return apr_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str, 3204 "'", NULL); 3205 } 3206 3207 /* arg1: the input string */ 3208 newcond->input = a1; 3209 3210 /* arg3: optional flags field 3211 * (this has to be parsed first, because we need to 3212 * know if the regex should be compiled with ICASE!) 3213 */ 3214 newcond->flags = CONDFLAG_NONE; 3215 if (a3 != NULL) { 3216 if ((err = cmd_parseflagfield(cmd->pool, newcond, a3, 3217 cmd_rewritecond_setflag)) != NULL) { 3218 return apr_pstrcat(cmd->pool, "RewriteCond: ", err, NULL); 3219 } 3220 } 3221 3222 /* arg2: the pattern */ 3223 if (*a2 == '!') { 3224 newcond->flags |= CONDFLAG_NOTMATCH; 3225 ++a2; 3226 } 3227 3228 /* determine the pattern type */ 3229 newcond->ptype = CONDPAT_REGEX; 3230 if (*a2 && a2[1]) { 3231 if (!a2[2] && *a2 == '-') { 3232 switch (a2[1]) { 3233 case 'f': newcond->ptype = CONDPAT_FILE_EXISTS; break; 3234 case 's': newcond->ptype = CONDPAT_FILE_SIZE; break; 3235 case 'l': newcond->ptype = CONDPAT_FILE_LINK; break; 3236 case 'd': newcond->ptype = CONDPAT_FILE_DIR; break; 3237 case 'x': newcond->ptype = CONDPAT_FILE_XBIT; break; 3238 case 'U': newcond->ptype = CONDPAT_LU_URL; break; 3239 case 'F': newcond->ptype = CONDPAT_LU_FILE; break; 3240 } 3241 } 3242 else { 3243 switch (*a2) { 3244 case '>': newcond->ptype = CONDPAT_STR_GT; break; 3245 case '<': newcond->ptype = CONDPAT_STR_LT; break; 3246 case '=': newcond->ptype = CONDPAT_STR_EQ; 3247 /* "" represents an empty string */ 3248 if (*++a2 == '"' && a2[1] == '"' && !a2[2]) { 3249 a2 += 2; 3250 } 3251 break; 3252 } 3253 } 3254 } 3255 3256 if (newcond->ptype && newcond->ptype != CONDPAT_STR_EQ && 3257 (newcond->flags & CONDFLAG_NOCASE)) { 3258 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, 3259 "RewriteCond: NoCase option for non-regex pattern '%s' " 3260 "is not supported and will be ignored.", a2); 3261 newcond->flags &= ~CONDFLAG_NOCASE; 3262 } 3263 3264 newcond->pattern = a2; 3265 3266 if (!newcond->ptype) { 3267 regexp = ap_pregcomp(cmd->pool, a2, 3268 AP_REG_EXTENDED | ((newcond->flags & CONDFLAG_NOCASE) 3269 ? AP_REG_ICASE : 0)); 3270 if (!regexp) { 3271 return apr_pstrcat(cmd->pool, "RewriteCond: cannot compile regular " 3272 "expression '", a2, "'", NULL); 3273 } 3274 3275 newcond->regexp = regexp; 3276 } 3277 3278 return NULL; 3279} 3280 3281static const char *cmd_rewriterule_setflag(apr_pool_t *p, void *_cfg, 3282 char *key, char *val) 3283{ 3284 rewriterule_entry *cfg = _cfg; 3285 int error = 0; 3286 3287 switch (*key++) { 3288 case 'b': 3289 case 'B': 3290 if (!*key || !strcasecmp(key, "ackrefescaping")) { 3291 cfg->flags |= RULEFLAG_ESCAPEBACKREF; 3292 } 3293 else { 3294 ++error; 3295 } 3296 break; 3297 case 'c': 3298 case 'C': 3299 if (!*key || !strcasecmp(key, "hain")) { /* chain */ 3300 cfg->flags |= RULEFLAG_CHAIN; 3301 } 3302 else if (((*key == 'O' || *key == 'o') && !key[1]) 3303 || !strcasecmp(key, "ookie")) { /* cookie */ 3304 data_item *cp = cfg->cookie; 3305 3306 if (!cp) { 3307 cp = cfg->cookie = apr_palloc(p, sizeof(*cp)); 3308 } 3309 else { 3310 while (cp->next) { 3311 cp = cp->next; 3312 } 3313 cp->next = apr_palloc(p, sizeof(*cp)); 3314 cp = cp->next; 3315 } 3316 3317 cp->next = NULL; 3318 cp->data = val; 3319 } 3320 else { 3321 ++error; 3322 } 3323 break; 3324 case 'd': 3325 case 'D': 3326 if (!*key || !strcasecmp(key, "PI") || !strcasecmp(key,"iscardpath")) { 3327 cfg->flags |= (RULEFLAG_DISCARDPATHINFO); 3328 } 3329 break; 3330 case 'e': 3331 case 'E': 3332 if (!*key || !strcasecmp(key, "nv")) { /* env */ 3333 data_item *cp = cfg->env; 3334 3335 if (!cp) { 3336 cp = cfg->env = apr_palloc(p, sizeof(*cp)); 3337 } 3338 else { 3339 while (cp->next) { 3340 cp = cp->next; 3341 } 3342 cp->next = apr_palloc(p, sizeof(*cp)); 3343 cp = cp->next; 3344 } 3345 3346 cp->next = NULL; 3347 cp->data = val; 3348 } 3349 else { 3350 ++error; 3351 } 3352 break; 3353 3354 case 'f': 3355 case 'F': 3356 if (!*key || !strcasecmp(key, "orbidden")) { /* forbidden */ 3357 cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB); 3358 cfg->forced_responsecode = HTTP_FORBIDDEN; 3359 } 3360 else { 3361 ++error; 3362 } 3363 break; 3364 3365 case 'g': 3366 case 'G': 3367 if (!*key || !strcasecmp(key, "one")) { /* gone */ 3368 cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB); 3369 cfg->forced_responsecode = HTTP_GONE; 3370 } 3371 else { 3372 ++error; 3373 } 3374 break; 3375 3376 case 'h': 3377 case 'H': 3378 if (!*key || !strcasecmp(key, "andler")) { /* handler */ 3379 cfg->forced_handler = val; 3380 } 3381 else { 3382 ++error; 3383 } 3384 break; 3385 case 'l': 3386 case 'L': 3387 if (!*key || !strcasecmp(key, "ast")) { /* last */ 3388 cfg->flags |= RULEFLAG_LASTRULE; 3389 } 3390 else { 3391 ++error; 3392 } 3393 break; 3394 3395 case 'n': 3396 case 'N': 3397 if (((*key == 'E' || *key == 'e') && !key[1]) 3398 || !strcasecmp(key, "oescape")) { /* noescape */ 3399 cfg->flags |= RULEFLAG_NOESCAPE; 3400 } 3401 else if (!*key || !strcasecmp(key, "ext")) { /* next */ 3402 cfg->flags |= RULEFLAG_NEWROUND; 3403 } 3404 else if (((*key == 'S' || *key == 's') && !key[1]) 3405 || !strcasecmp(key, "osubreq")) { /* nosubreq */ 3406 cfg->flags |= RULEFLAG_IGNOREONSUBREQ; 3407 } 3408 else if (((*key == 'C' || *key == 'c') && !key[1]) 3409 || !strcasecmp(key, "ocase")) { /* nocase */ 3410 cfg->flags |= RULEFLAG_NOCASE; 3411 } 3412 else { 3413 ++error; 3414 } 3415 break; 3416 3417 case 'p': 3418 case 'P': 3419 if (!*key || !strcasecmp(key, "roxy")) { /* proxy */ 3420 cfg->flags |= RULEFLAG_PROXY; 3421 } 3422 else if (((*key == 'T' || *key == 't') && !key[1]) 3423 || !strcasecmp(key, "assthrough")) { /* passthrough */ 3424 cfg->flags |= RULEFLAG_PASSTHROUGH; 3425 } 3426 else { 3427 ++error; 3428 } 3429 break; 3430 3431 case 'q': 3432 case 'Q': 3433 if ( !strcasecmp(key, "SA") 3434 || !strcasecmp(key, "sappend")) { /* qsappend */ 3435 cfg->flags |= RULEFLAG_QSAPPEND; 3436 } 3437 else { 3438 ++error; 3439 } 3440 break; 3441 3442 case 'r': 3443 case 'R': 3444 if (!*key || !strcasecmp(key, "edirect")) { /* redirect */ 3445 int status = 0; 3446 3447 cfg->flags |= RULEFLAG_FORCEREDIRECT; 3448 if (strlen(val) > 0) { 3449 if (strcasecmp(val, "permanent") == 0) { 3450 status = HTTP_MOVED_PERMANENTLY; 3451 } 3452 else if (strcasecmp(val, "temp") == 0) { 3453 status = HTTP_MOVED_TEMPORARILY; 3454 } 3455 else if (strcasecmp(val, "seeother") == 0) { 3456 status = HTTP_SEE_OTHER; 3457 } 3458 else if (apr_isdigit(*val)) { 3459 status = atoi(val); 3460 if (status != HTTP_INTERNAL_SERVER_ERROR) { 3461 int idx = 3462 ap_index_of_response(HTTP_INTERNAL_SERVER_ERROR); 3463 3464 if (ap_index_of_response(status) == idx) { 3465 return apr_psprintf(p, "invalid HTTP " 3466 "response code '%s' for " 3467 "flag 'R'", 3468 val); 3469 } 3470 } 3471 if (!ap_is_HTTP_REDIRECT(status)) { 3472 cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB); 3473 } 3474 } 3475 cfg->forced_responsecode = status; 3476 } 3477 } 3478 else { 3479 ++error; 3480 } 3481 break; 3482 3483 case 's': 3484 case 'S': 3485 if (!*key || !strcasecmp(key, "kip")) { /* skip */ 3486 cfg->skip = atoi(val); 3487 } 3488 else { 3489 ++error; 3490 } 3491 break; 3492 3493 case 't': 3494 case 'T': 3495 if (!*key || !strcasecmp(key, "ype")) { /* type */ 3496 cfg->forced_mimetype = val; 3497 } 3498 else { 3499 ++error; 3500 } 3501 break; 3502 default: 3503 ++error; 3504 break; 3505 } 3506 3507 if (error) { 3508 return apr_pstrcat(p, "unknown flag '", --key, "'", NULL); 3509 } 3510 3511 return NULL; 3512} 3513 3514static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf, 3515 const char *in_str) 3516{ 3517 rewrite_perdir_conf *dconf = in_dconf; 3518 char *str = apr_pstrdup(cmd->pool, in_str); 3519 rewrite_server_conf *sconf; 3520 rewriterule_entry *newrule; 3521 ap_regex_t *regexp; 3522 char *a1; 3523 char *a2; 3524 char *a3; 3525 const char *err; 3526 3527 sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); 3528 3529 /* make a new entry in the internal rewrite rule list */ 3530 if (cmd->path == NULL) { /* is server command */ 3531 newrule = apr_array_push(sconf->rewriterules); 3532 } 3533 else { /* is per-directory command */ 3534 newrule = apr_array_push(dconf->rewriterules); 3535 } 3536 3537 /* parse the argument line ourself */ 3538 if (parseargline(str, &a1, &a2, &a3)) { 3539 return apr_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str, 3540 "'", NULL); 3541 } 3542 3543 /* arg3: optional flags field */ 3544 newrule->forced_mimetype = NULL; 3545 newrule->forced_handler = NULL; 3546 newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY; 3547 newrule->flags = RULEFLAG_NONE; 3548 newrule->env = NULL; 3549 newrule->cookie = NULL; 3550 newrule->skip = 0; 3551 if (a3 != NULL) { 3552 if ((err = cmd_parseflagfield(cmd->pool, newrule, a3, 3553 cmd_rewriterule_setflag)) != NULL) { 3554 return apr_pstrcat(cmd->pool, "RewriteRule: ", err, NULL); 3555 } 3556 } 3557 3558 /* arg1: the pattern 3559 * try to compile the regexp to test if is ok 3560 */ 3561 if (*a1 == '!') { 3562 newrule->flags |= RULEFLAG_NOTMATCH; 3563 ++a1; 3564 } 3565 3566 regexp = ap_pregcomp(cmd->pool, a1, AP_REG_EXTENDED | 3567 ((newrule->flags & RULEFLAG_NOCASE) 3568 ? AP_REG_ICASE : 0)); 3569 if (!regexp) { 3570 return apr_pstrcat(cmd->pool, 3571 "RewriteRule: cannot compile regular expression '", 3572 a1, "'", NULL); 3573 } 3574 3575 newrule->pattern = a1; 3576 newrule->regexp = regexp; 3577 3578 /* arg2: the output string */ 3579 newrule->output = a2; 3580 if (*a2 == '-' && !a2[1]) { 3581 newrule->flags |= RULEFLAG_NOSUB; 3582 } 3583 3584 /* now, if the server or per-dir config holds an 3585 * array of RewriteCond entries, we take it for us 3586 * and clear the array 3587 */ 3588 if (cmd->path == NULL) { /* is server command */ 3589 newrule->rewriteconds = sconf->rewriteconds; 3590 sconf->rewriteconds = apr_array_make(cmd->pool, 2, 3591 sizeof(rewritecond_entry)); 3592 } 3593 else { /* is per-directory command */ 3594 newrule->rewriteconds = dconf->rewriteconds; 3595 dconf->rewriteconds = apr_array_make(cmd->pool, 2, 3596 sizeof(rewritecond_entry)); 3597 } 3598 3599 return NULL; 3600} 3601 3602 3603/* 3604 * +-------------------------------------------------------+ 3605 * | | 3606 * | the rewriting engine 3607 * | | 3608 * +-------------------------------------------------------+ 3609 */ 3610 3611/* Lexicographic Compare */ 3612static APR_INLINE int compare_lexicography(char *a, char *b) 3613{ 3614 apr_size_t i, lena, lenb; 3615 3616 lena = strlen(a); 3617 lenb = strlen(b); 3618 3619 if (lena == lenb) { 3620 for (i = 0; i < lena; ++i) { 3621 if (a[i] != b[i]) { 3622 return ((unsigned char)a[i] > (unsigned char)b[i]) ? 1 : -1; 3623 } 3624 } 3625 3626 return 0; 3627 } 3628 3629 return ((lena > lenb) ? 1 : -1); 3630} 3631 3632/* 3633 * Apply a single rewriteCond 3634 */ 3635static int apply_rewrite_cond(rewritecond_entry *p, rewrite_ctx *ctx) 3636{ 3637 char *input = do_expand(p->input, ctx, NULL); 3638 apr_finfo_t sb; 3639 request_rec *rsub, *r = ctx->r; 3640 ap_regmatch_t regmatch[AP_MAX_REG_MATCH]; 3641 int rc = 0; 3642 3643 switch (p->ptype) { 3644 case CONDPAT_FILE_EXISTS: 3645 if ( apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS 3646 && sb.filetype == APR_REG) { 3647 rc = 1; 3648 } 3649 break; 3650 3651 case CONDPAT_FILE_SIZE: 3652 if ( apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS 3653 && sb.filetype == APR_REG && sb.size > 0) { 3654 rc = 1; 3655 } 3656 break; 3657 3658 case CONDPAT_FILE_LINK: 3659#if !defined(OS2) 3660 if ( apr_stat(&sb, input, APR_FINFO_MIN | APR_FINFO_LINK, 3661 r->pool) == APR_SUCCESS 3662 && sb.filetype == APR_LNK) { 3663 rc = 1; 3664 } 3665#endif 3666 break; 3667 3668 case CONDPAT_FILE_DIR: 3669 if ( apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS 3670 && sb.filetype == APR_DIR) { 3671 rc = 1; 3672 } 3673 break; 3674 3675 case CONDPAT_FILE_XBIT: 3676 if ( apr_stat(&sb, input, APR_FINFO_PROT, r->pool) == APR_SUCCESS 3677 && (sb.protection & (APR_UEXECUTE | APR_GEXECUTE | APR_WEXECUTE))) { 3678 rc = 1; 3679 } 3680 break; 3681 3682 case CONDPAT_LU_URL: 3683 if (*input && subreq_ok(r)) { 3684 rsub = ap_sub_req_lookup_uri(input, r, NULL); 3685 if (rsub->status < 400) { 3686 rc = 1; 3687 } 3688 rewritelog((r, 5, NULL, "RewriteCond URI (-U) check: " 3689 "path=%s -> status=%d", input, rsub->status)); 3690 ap_destroy_sub_req(rsub); 3691 } 3692 break; 3693 3694 case CONDPAT_LU_FILE: 3695 if (*input && subreq_ok(r)) { 3696 rsub = ap_sub_req_lookup_file(input, r, NULL); 3697 if (rsub->status < 300 && 3698 /* double-check that file exists since default result is 200 */ 3699 apr_stat(&sb, rsub->filename, APR_FINFO_MIN, 3700 r->pool) == APR_SUCCESS) { 3701 rc = 1; 3702 } 3703 rewritelog((r, 5, NULL, "RewriteCond file (-F) check: path=%s " 3704 "-> file=%s status=%d", input, rsub->filename, 3705 rsub->status)); 3706 ap_destroy_sub_req(rsub); 3707 } 3708 break; 3709 3710 case CONDPAT_STR_GT: 3711 rc = (compare_lexicography(input, p->pattern+1) == 1) ? 1 : 0; 3712 break; 3713 3714 case CONDPAT_STR_LT: 3715 rc = (compare_lexicography(input, p->pattern+1) == -1) ? 1 : 0; 3716 break; 3717 3718 case CONDPAT_STR_EQ: 3719 if (p->flags & CONDFLAG_NOCASE) { 3720 rc = !strcasecmp(input, p->pattern); 3721 } 3722 else { 3723 rc = !strcmp(input, p->pattern); 3724 } 3725 break; 3726 3727 default: 3728 /* it is really a regexp pattern, so apply it */ 3729 rc = !ap_regexec(p->regexp, input, AP_MAX_REG_MATCH, regmatch, 0); 3730 3731 /* update briRC backref info */ 3732 if (rc && !(p->flags & CONDFLAG_NOTMATCH)) { 3733 ctx->briRC.source = input; 3734 ctx->briRC.nsub = p->regexp->re_nsub; 3735 memcpy(ctx->briRC.regmatch, regmatch, sizeof(regmatch)); 3736 } 3737 break; 3738 } 3739 3740 if (p->flags & CONDFLAG_NOTMATCH) { 3741 rc = !rc; 3742 } 3743 3744 rewritelog((r, 4, ctx->perdir, "RewriteCond: input='%s' pattern='%s%s%s'%s " 3745 "=> %s", input, (p->flags & CONDFLAG_NOTMATCH) ? "!" : "", 3746 (p->ptype == CONDPAT_STR_EQ) ? "=" : "", p->pattern, 3747 (p->flags & CONDFLAG_NOCASE) ? " [NC]" : "", 3748 rc ? "matched" : "not-matched")); 3749 3750 return rc; 3751} 3752 3753/* check for forced type and handler */ 3754static APR_INLINE void force_type_handler(rewriterule_entry *p, 3755 rewrite_ctx *ctx) 3756{ 3757 char *expanded; 3758 3759 if (p->forced_mimetype) { 3760 expanded = do_expand(p->forced_mimetype, ctx, p); 3761 3762 if (*expanded) { 3763 ap_str_tolower(expanded); 3764 3765 rewritelog((ctx->r, 2, ctx->perdir, "remember %s to have MIME-type " 3766 "'%s'", ctx->r->filename, expanded)); 3767 3768 apr_table_setn(ctx->r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR, 3769 expanded); 3770 } 3771 } 3772 3773 if (p->forced_handler) { 3774 expanded = do_expand(p->forced_handler, ctx, p); 3775 3776 if (*expanded) { 3777 ap_str_tolower(expanded); 3778 3779 rewritelog((ctx->r, 2, ctx->perdir, "remember %s to have " 3780 "Content-handler '%s'", ctx->r->filename, expanded)); 3781 3782 apr_table_setn(ctx->r->notes, REWRITE_FORCED_HANDLER_NOTEVAR, 3783 expanded); 3784 } 3785 } 3786} 3787 3788/* 3789 * Apply a single RewriteRule 3790 */ 3791static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) 3792{ 3793 ap_regmatch_t regmatch[AP_MAX_REG_MATCH]; 3794 apr_array_header_t *rewriteconds; 3795 rewritecond_entry *conds; 3796 int i, rc; 3797 char *newuri = NULL; 3798 request_rec *r = ctx->r; 3799 int is_proxyreq = 0; 3800 3801 ctx->uri = r->filename; 3802 3803 if (ctx->perdir) { 3804 apr_size_t dirlen = strlen(ctx->perdir); 3805 3806 /* 3807 * Proxy request? 3808 */ 3809 is_proxyreq = ( r->proxyreq && r->filename 3810 && !strncmp(r->filename, "proxy:", 6)); 3811 3812 /* Since we want to match against the (so called) full URL, we have 3813 * to re-add the PATH_INFO postfix 3814 */ 3815 if (r->path_info && *r->path_info) { 3816 rewritelog((r, 3, ctx->perdir, "add path info postfix: %s -> %s%s", 3817 ctx->uri, ctx->uri, r->path_info)); 3818 ctx->uri = apr_pstrcat(r->pool, ctx->uri, r->path_info, NULL); 3819 } 3820 3821 /* Additionally we strip the physical path from the url to match 3822 * it independent from the underlaying filesystem. 3823 */ 3824 if (!is_proxyreq && strlen(ctx->uri) >= dirlen && 3825 !strncmp(ctx->uri, ctx->perdir, dirlen)) { 3826 3827 rewritelog((r, 3, ctx->perdir, "strip per-dir prefix: %s -> %s", 3828 ctx->uri, ctx->uri + dirlen)); 3829 ctx->uri = ctx->uri + dirlen; 3830 } 3831 } 3832 3833 /* Try to match the URI against the RewriteRule pattern 3834 * and exit immediately if it didn't apply. 3835 */ 3836 rewritelog((r, 3, ctx->perdir, "applying pattern '%s' to uri '%s'", 3837 p->pattern, ctx->uri)); 3838 3839 rc = !ap_regexec(p->regexp, ctx->uri, AP_MAX_REG_MATCH, regmatch, 0); 3840 if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) || 3841 (!rc && (p->flags & RULEFLAG_NOTMATCH)) ) ) { 3842 return 0; 3843 } 3844 3845 /* It matched, wow! Now it's time to prepare the context structure for 3846 * further processing 3847 */ 3848 ctx->vary_this = NULL; 3849 ctx->briRC.source = NULL; 3850 3851 if (p->flags & RULEFLAG_NOTMATCH) { 3852 ctx->briRR.source = NULL; 3853 } 3854 else { 3855 ctx->briRR.source = apr_pstrdup(r->pool, ctx->uri); 3856 ctx->briRR.nsub = p->regexp->re_nsub; 3857 memcpy(ctx->briRR.regmatch, regmatch, sizeof(regmatch)); 3858 } 3859 3860 /* Ok, we already know the pattern has matched, but we now 3861 * additionally have to check for all existing preconditions 3862 * (RewriteCond) which have to be also true. We do this at 3863 * this very late stage to avoid unnessesary checks which 3864 * would slow down the rewriting engine. 3865 */ 3866 rewriteconds = p->rewriteconds; 3867 conds = (rewritecond_entry *)rewriteconds->elts; 3868 3869 for (i = 0; i < rewriteconds->nelts; ++i) { 3870 rewritecond_entry *c = &conds[i]; 3871 3872 rc = apply_rewrite_cond(c, ctx); 3873 /* 3874 * Reset vary_this if the novary flag is set for this condition. 3875 */ 3876 if (c->flags & CONDFLAG_NOVARY) { 3877 ctx->vary_this = NULL; 3878 } 3879 if (c->flags & CONDFLAG_ORNEXT) { 3880 if (!rc) { 3881 /* One condition is false, but another can be still true. */ 3882 ctx->vary_this = NULL; 3883 continue; 3884 } 3885 else { 3886 /* skip the rest of the chained OR conditions */ 3887 while ( i < rewriteconds->nelts 3888 && c->flags & CONDFLAG_ORNEXT) { 3889 c = &conds[++i]; 3890 } 3891 } 3892 } 3893 else if (!rc) { 3894 return 0; 3895 } 3896 3897 /* If some HTTP header was involved in the condition, remember it 3898 * for later use 3899 */ 3900 if (ctx->vary_this) { 3901 ctx->vary = ctx->vary 3902 ? apr_pstrcat(r->pool, ctx->vary, ", ", ctx->vary_this, 3903 NULL) 3904 : ctx->vary_this; 3905 ctx->vary_this = NULL; 3906 } 3907 } 3908 3909 /* expand the result */ 3910 if (!(p->flags & RULEFLAG_NOSUB)) { 3911 newuri = do_expand(p->output, ctx, p); 3912 rewritelog((r, 2, ctx->perdir, "rewrite '%s' -> '%s'", ctx->uri, 3913 newuri)); 3914 } 3915 3916 /* expand [E=var:val] and [CO=<cookie>] */ 3917 do_expand_env(p->env, ctx); 3918 do_expand_cookie(p->cookie, ctx); 3919 3920 /* non-substitution rules ('RewriteRule <pat> -') end here. */ 3921 if (p->flags & RULEFLAG_NOSUB) { 3922 force_type_handler(p, ctx); 3923 3924 if (p->flags & RULEFLAG_STATUS) { 3925 rewritelog((r, 2, ctx->perdir, "forcing responsecode %d for %s", 3926 p->forced_responsecode, r->filename)); 3927 3928 r->status = p->forced_responsecode; 3929 } 3930 3931 return 2; 3932 } 3933 3934 /* Now adjust API's knowledge about r->filename and r->args */ 3935 r->filename = newuri; 3936 3937 if (ctx->perdir && (p->flags & RULEFLAG_DISCARDPATHINFO)) { 3938 r->path_info = NULL; 3939 } 3940 3941 splitout_queryargs(r, p->flags & RULEFLAG_QSAPPEND); 3942 3943 /* Add the previously stripped per-directory location prefix, unless 3944 * (1) it's an absolute URL path and 3945 * (2) it's a full qualified URL 3946 */ 3947 if ( ctx->perdir && !is_proxyreq && *r->filename != '/' 3948 && !is_absolute_uri(r->filename)) { 3949 rewritelog((r, 3, ctx->perdir, "add per-dir prefix: %s -> %s%s", 3950 r->filename, ctx->perdir, r->filename)); 3951 3952 r->filename = apr_pstrcat(r->pool, ctx->perdir, r->filename, NULL); 3953 } 3954 3955 /* If this rule is forced for proxy throughput 3956 * (`RewriteRule ... ... [P]') then emulate mod_proxy's 3957 * URL-to-filename handler to be sure mod_proxy is triggered 3958 * for this URL later in the Apache API. But make sure it is 3959 * a fully-qualified URL. (If not it is qualified with 3960 * ourself). 3961 */ 3962 if (p->flags & RULEFLAG_PROXY) { 3963 /* For rules evaluated in server context, the mod_proxy fixup 3964 * hook can be relied upon to escape the URI as and when 3965 * necessary, since it occurs later. If in directory context, 3966 * the ordering of the fixup hooks is forced such that 3967 * mod_proxy comes first, so the URI must be escaped here 3968 * instead. See PR 39746, 46428, and other headaches. */ 3969 if (ctx->perdir && (p->flags & RULEFLAG_NOESCAPE) == 0) { 3970 char *old_filename = r->filename; 3971 3972 r->filename = ap_escape_uri(r->pool, r->filename); 3973 rewritelog((r, 2, ctx->perdir, "escaped URI in per-dir context " 3974 "for proxy, %s -> %s", old_filename, r->filename)); 3975 } 3976 3977 fully_qualify_uri(r); 3978 3979 rewritelog((r, 2, ctx->perdir, "forcing proxy-throughput with %s", 3980 r->filename)); 3981 3982 r->filename = apr_pstrcat(r->pool, "proxy:", r->filename, NULL); 3983 return 1; 3984 } 3985 3986 /* If this rule is explicitly forced for HTTP redirection 3987 * (`RewriteRule .. .. [R]') then force an external HTTP 3988 * redirect. But make sure it is a fully-qualified URL. (If 3989 * not it is qualified with ourself). 3990 */ 3991 if (p->flags & RULEFLAG_FORCEREDIRECT) { 3992 fully_qualify_uri(r); 3993 3994 rewritelog((r, 2, ctx->perdir, "explicitly forcing redirect with %s", 3995 r->filename)); 3996 3997 r->status = p->forced_responsecode; 3998 return 1; 3999 } 4000 4001 /* Special Rewriting Feature: Self-Reduction 4002 * We reduce the URL by stripping a possible 4003 * http[s]://<ourhost>[:<port>] prefix, i.e. a prefix which 4004 * corresponds to ourself. This is to simplify rewrite maps 4005 * and to avoid recursion, etc. When this prefix is not a 4006 * coincidence then the user has to use [R] explicitly (see 4007 * above). 4008 */ 4009 reduce_uri(r); 4010 4011 /* If this rule is still implicitly forced for HTTP 4012 * redirection (`RewriteRule .. <scheme>://...') then 4013 * directly force an external HTTP redirect. 4014 */ 4015 if (is_absolute_uri(r->filename)) { 4016 rewritelog((r, 2, ctx->perdir, "implicitly forcing redirect (rc=%d) " 4017 "with %s", p->forced_responsecode, r->filename)); 4018 4019 r->status = p->forced_responsecode; 4020 return 1; 4021 } 4022 4023 /* Finally remember the forced mime-type */ 4024 force_type_handler(p, ctx); 4025 4026 /* Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_) 4027 * But now we're done for this particular rule. 4028 */ 4029 return 1; 4030} 4031 4032/* 4033 * Apply a complete rule set, 4034 * i.e. a list of rewrite rules 4035 */ 4036static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules, 4037 char *perdir) 4038{ 4039 rewriterule_entry *entries; 4040 rewriterule_entry *p; 4041 int i; 4042 int changed; 4043 int rc; 4044 int s; 4045 rewrite_ctx *ctx; 4046 4047 ctx = apr_palloc(r->pool, sizeof(*ctx)); 4048 ctx->perdir = perdir; 4049 ctx->r = r; 4050 4051 /* 4052 * Iterate over all existing rules 4053 */ 4054 entries = (rewriterule_entry *)rewriterules->elts; 4055 changed = 0; 4056 loop: 4057 for (i = 0; i < rewriterules->nelts; i++) { 4058 p = &entries[i]; 4059 4060 /* 4061 * Ignore this rule on subrequests if we are explicitly 4062 * asked to do so or this is a proxy-throughput or a 4063 * forced redirect rule. 4064 */ 4065 if (r->main != NULL && 4066 (p->flags & RULEFLAG_IGNOREONSUBREQ || 4067 p->flags & RULEFLAG_FORCEREDIRECT )) { 4068 continue; 4069 } 4070 4071 /* 4072 * Apply the current rule. 4073 */ 4074 ctx->vary = NULL; 4075 rc = apply_rewrite_rule(p, ctx); 4076 4077 if (rc) { 4078 /* Regardless of what we do next, we've found a match. Check to see 4079 * if any of the request header fields were involved, and add them 4080 * to the Vary field of the response. 4081 */ 4082 if (ctx->vary) { 4083 apr_table_merge(r->headers_out, "Vary", ctx->vary); 4084 } 4085 4086 /* 4087 * The rule sets the response code (implies match-only) 4088 */ 4089 if (p->flags & RULEFLAG_STATUS) { 4090 return ACTION_STATUS; 4091 } 4092 4093 /* 4094 * Indicate a change if this was not a match-only rule. 4095 */ 4096 if (rc != 2) { 4097 changed = ((p->flags & RULEFLAG_NOESCAPE) 4098 ? ACTION_NOESCAPE : ACTION_NORMAL); 4099 } 4100 4101 /* 4102 * Pass-Through Feature (`RewriteRule .. .. [PT]'): 4103 * Because the Apache 1.x API is very limited we 4104 * need this hack to pass the rewritten URL to other 4105 * modules like mod_alias, mod_userdir, etc. 4106 */ 4107 if (p->flags & RULEFLAG_PASSTHROUGH) { 4108 rewritelog((r, 2, perdir, "forcing '%s' to get passed through " 4109 "to next API URI-to-filename handler", r->filename)); 4110 r->filename = apr_pstrcat(r->pool, "passthrough:", 4111 r->filename, NULL); 4112 changed = ACTION_NORMAL; 4113 break; 4114 } 4115 4116 /* 4117 * Stop processing also on proxy pass-through and 4118 * last-rule and new-round flags. 4119 */ 4120 if (p->flags & (RULEFLAG_PROXY | RULEFLAG_LASTRULE)) { 4121 break; 4122 } 4123 4124 /* 4125 * On "new-round" flag we just start from the top of 4126 * the rewriting ruleset again. 4127 */ 4128 if (p->flags & RULEFLAG_NEWROUND) { 4129 goto loop; 4130 } 4131 4132 /* 4133 * If we are forced to skip N next rules, do it now. 4134 */ 4135 if (p->skip > 0) { 4136 s = p->skip; 4137 while ( i < rewriterules->nelts 4138 && s > 0) { 4139 i++; 4140 p = &entries[i]; 4141 s--; 4142 } 4143 } 4144 } 4145 else { 4146 /* 4147 * If current rule is chained with next rule(s), 4148 * skip all this next rule(s) 4149 */ 4150 while ( i < rewriterules->nelts 4151 && p->flags & RULEFLAG_CHAIN) { 4152 i++; 4153 p = &entries[i]; 4154 } 4155 } 4156 } 4157 return changed; 4158} 4159 4160 4161/* 4162 * +-------------------------------------------------------+ 4163 * | | 4164 * | Module Initialization Hooks 4165 * | | 4166 * +-------------------------------------------------------+ 4167 */ 4168 4169static int pre_config(apr_pool_t *pconf, 4170 apr_pool_t *plog, 4171 apr_pool_t *ptemp) 4172{ 4173 APR_OPTIONAL_FN_TYPE(ap_register_rewrite_mapfunc) *map_pfn_register; 4174 4175 /* register int: rewritemap handlers */ 4176 map_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_rewrite_mapfunc); 4177 if (map_pfn_register) { 4178 map_pfn_register("tolower", rewrite_mapfunc_tolower); 4179 map_pfn_register("toupper", rewrite_mapfunc_toupper); 4180 map_pfn_register("escape", rewrite_mapfunc_escape); 4181 map_pfn_register("unescape", rewrite_mapfunc_unescape); 4182 } 4183 return OK; 4184} 4185 4186static int post_config(apr_pool_t *p, 4187 apr_pool_t *plog, 4188 apr_pool_t *ptemp, 4189 server_rec *s) 4190{ 4191 apr_status_t rv; 4192 void *data; 4193 int first_time = 0; 4194 const char *userdata_key = "rewrite_init_module"; 4195 4196 apr_pool_userdata_get(&data, userdata_key, s->process->pool); 4197 if (!data) { 4198 first_time = 1; 4199 apr_pool_userdata_set((const void *)1, userdata_key, 4200 apr_pool_cleanup_null, s->process->pool); 4201 } 4202 4203 /* check if proxy module is available */ 4204 proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL); 4205 4206 rv = rewritelock_create(s, p); 4207 if (rv != APR_SUCCESS) { 4208 return HTTP_INTERNAL_SERVER_ERROR; 4209 } 4210 4211 apr_pool_cleanup_register(p, (void *)s, rewritelock_remove, 4212 apr_pool_cleanup_null); 4213 4214 /* step through the servers and 4215 * - open each rewriting logfile 4216 * - open the RewriteMap prg:xxx programs 4217 */ 4218 for (; s; s = s->next) { 4219#ifndef REWRITELOG_DISABLED 4220 if (!open_rewritelog(s, p)) { 4221 return HTTP_INTERNAL_SERVER_ERROR; 4222 } 4223#endif 4224 4225 if (!first_time) { 4226 if (run_rewritemap_programs(s, p) != APR_SUCCESS) { 4227 return HTTP_INTERNAL_SERVER_ERROR; 4228 } 4229 } 4230 } 4231 4232 rewrite_ssl_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); 4233 rewrite_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); 4234 4235 return OK; 4236} 4237 4238static void init_child(apr_pool_t *p, server_rec *s) 4239{ 4240 apr_status_t rv = 0; /* get a rid of gcc warning (REWRITELOG_DISABLED) */ 4241 4242 if (lockname != NULL && *(lockname) != '\0') { 4243 rv = apr_global_mutex_child_init(&rewrite_mapr_lock_acquire, 4244 lockname, p); 4245 if (rv != APR_SUCCESS) { 4246 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, 4247 "mod_rewrite: could not init rewrite_mapr_lock_acquire" 4248 " in child"); 4249 } 4250 } 4251 4252 /* create the lookup cache */ 4253 if (!init_cache(p)) { 4254 ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, 4255 "mod_rewrite: could not init map cache in child"); 4256 } 4257} 4258 4259 4260/* 4261 * +-------------------------------------------------------+ 4262 * | | 4263 * | runtime hooks 4264 * | | 4265 * +-------------------------------------------------------+ 4266 */ 4267 4268/* 4269 * URI-to-filename hook 4270 * [deals with RewriteRules in server context] 4271 */ 4272static int hook_uri2file(request_rec *r) 4273{ 4274 rewrite_perdir_conf *dconf; 4275 rewrite_server_conf *conf; 4276 const char *saved_rulestatus; 4277 const char *var; 4278 const char *thisserver; 4279 char *thisport; 4280 const char *thisurl; 4281 unsigned int port; 4282 int rulestatus; 4283 4284 /* 4285 * retrieve the config structures 4286 */ 4287 conf = ap_get_module_config(r->server->module_config, &rewrite_module); 4288 4289 dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config, 4290 &rewrite_module); 4291 4292 /* 4293 * only do something under runtime if the engine is really enabled, 4294 * else return immediately! 4295 */ 4296 if (!dconf || dconf->state == ENGINE_DISABLED) { 4297 return DECLINED; 4298 } 4299 4300 /* 4301 * check for the ugly API case of a virtual host section where no 4302 * mod_rewrite directives exists. In this situation we became no chance 4303 * by the API to setup our default per-server config so we have to 4304 * on-the-fly assume we have the default config. But because the default 4305 * config has a disabled rewriting engine we are lucky because can 4306 * just stop operating now. 4307 */ 4308 if (conf->server != r->server) { 4309 return DECLINED; 4310 } 4311 4312 /* Unless the anyuri option is set, ensure that the input to the 4313 * first rule really is a URL-path, avoiding security issues with 4314 * poorly configured rules. See CVE-2011-3368, CVE-2011-4317. */ 4315 if ((dconf->options & OPTION_ANYURI) == 0 4316 && ((r->unparsed_uri[0] == '*' && r->unparsed_uri[1] == '\0') 4317 || !r->uri || r->uri[0] != '/')) { 4318 rewritelog((r, 8, NULL, "Declining, request-URI '%s' is not a URL-path. " 4319 "Consult the manual entry for the RewriteOptions directive " 4320 "for options and caveats about matching other strings.", 4321 r->uri)); 4322 return DECLINED; 4323 } 4324 4325 /* 4326 * add the SCRIPT_URL variable to the env. this is a bit complicated 4327 * due to the fact that apache uses subrequests and internal redirects 4328 */ 4329 4330 if (r->main == NULL) { 4331 var = apr_table_get(r->subprocess_env, REDIRECT_ENVVAR_SCRIPT_URL); 4332 if (var == NULL) { 4333 apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, r->uri); 4334 } 4335 else { 4336 apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var); 4337 } 4338 } 4339 else { 4340 var = apr_table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL); 4341 apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var); 4342 } 4343 4344 /* 4345 * create the SCRIPT_URI variable for the env 4346 */ 4347 4348 /* add the canonical URI of this URL */ 4349 thisserver = ap_get_server_name(r); 4350 port = ap_get_server_port(r); 4351 if (ap_is_default_port(port, r)) { 4352 thisport = ""; 4353 } 4354 else { 4355 thisport = apr_psprintf(r->pool, ":%u", port); 4356 } 4357 thisurl = apr_table_get(r->subprocess_env, ENVVAR_SCRIPT_URL); 4358 4359 /* set the variable */ 4360 var = apr_pstrcat(r->pool, ap_http_scheme(r), "://", thisserver, thisport, 4361 thisurl, NULL); 4362 apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URI, var); 4363 4364 if (!(saved_rulestatus = apr_table_get(r->notes,"mod_rewrite_rewritten"))) { 4365 /* if filename was not initially set, 4366 * we start with the requested URI 4367 */ 4368 if (r->filename == NULL) { 4369 r->filename = apr_pstrdup(r->pool, r->uri); 4370 rewritelog((r, 2, NULL, "init rewrite engine with requested uri %s", 4371 r->filename)); 4372 } 4373 else { 4374 rewritelog((r, 2, NULL, "init rewrite engine with passed filename " 4375 "%s. Original uri = %s", r->filename, r->uri)); 4376 } 4377 4378 /* 4379 * now apply the rules ... 4380 */ 4381 rulestatus = apply_rewrite_list(r, conf->rewriterules, NULL); 4382 apr_table_set(r->notes,"mod_rewrite_rewritten", 4383 apr_psprintf(r->pool,"%d",rulestatus)); 4384 } 4385 else { 4386 rewritelog((r, 2, NULL, "uri already rewritten. Status %s, Uri %s, " 4387 "r->filename %s", saved_rulestatus, r->uri, r->filename)); 4388 4389 rulestatus = atoi(saved_rulestatus); 4390 } 4391 4392 if (rulestatus) { 4393 unsigned skip; 4394 apr_size_t flen; 4395 4396 if (ACTION_STATUS == rulestatus) { 4397 int n = r->status; 4398 4399 r->status = HTTP_OK; 4400 return n; 4401 } 4402 4403 flen = r->filename ? strlen(r->filename) : 0; 4404 if (flen > 6 && strncmp(r->filename, "proxy:", 6) == 0) { 4405 /* it should be go on as an internal proxy request */ 4406 4407 /* check if the proxy module is enabled, so 4408 * we can actually use it! 4409 */ 4410 if (!proxy_available) { 4411 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 4412 "attempt to make remote request from mod_rewrite " 4413 "without proxy enabled: %s", r->filename); 4414 return HTTP_FORBIDDEN; 4415 } 4416 4417 if (rulestatus == ACTION_NOESCAPE) { 4418 apr_table_setn(r->notes, "proxy-nocanon", "1"); 4419 } 4420 4421 /* make sure the QUERY_STRING and 4422 * PATH_INFO parts get incorporated 4423 */ 4424 if (r->path_info != NULL) { 4425 r->filename = apr_pstrcat(r->pool, r->filename, 4426 r->path_info, NULL); 4427 } 4428 if ((r->args != NULL) 4429 && ((r->proxyreq == PROXYREQ_PROXY) 4430 || (rulestatus == ACTION_NOESCAPE))) { 4431 /* see proxy_http:proxy_http_canon() */ 4432 r->filename = apr_pstrcat(r->pool, r->filename, 4433 "?", r->args, NULL); 4434 } 4435 4436 /* now make sure the request gets handled by the proxy handler */ 4437 if (PROXYREQ_NONE == r->proxyreq) { 4438 r->proxyreq = PROXYREQ_REVERSE; 4439 } 4440 r->handler = "proxy-server"; 4441 4442 rewritelog((r, 1, NULL, "go-ahead with proxy request %s [OK]", 4443 r->filename)); 4444 return OK; 4445 } 4446 else if ((skip = is_absolute_uri(r->filename)) > 0) { 4447 int n; 4448 4449 /* it was finally rewritten to a remote URL */ 4450 4451 if (rulestatus != ACTION_NOESCAPE) { 4452 rewritelog((r, 1, NULL, "escaping %s for redirect", 4453 r->filename)); 4454 r->filename = escape_absolute_uri(r->pool, r->filename, skip); 4455 } 4456 4457 /* append the QUERY_STRING part */ 4458 if (r->args) { 4459 r->filename = apr_pstrcat(r->pool, r->filename, "?", 4460 (rulestatus == ACTION_NOESCAPE) 4461 ? r->args 4462 : ap_escape_uri(r->pool, r->args), 4463 NULL); 4464 } 4465 4466 /* determine HTTP redirect response code */ 4467 if (ap_is_HTTP_REDIRECT(r->status)) { 4468 n = r->status; 4469 r->status = HTTP_OK; /* make Apache kernel happy */ 4470 } 4471 else { 4472 n = HTTP_MOVED_TEMPORARILY; 4473 } 4474 4475 /* now do the redirection */ 4476 apr_table_setn(r->headers_out, "Location", r->filename); 4477 rewritelog((r, 1, NULL, "redirect to %s [REDIRECT/%d]", r->filename, 4478 n)); 4479 4480 return n; 4481 } 4482 else if (flen > 12 && strncmp(r->filename, "passthrough:", 12) == 0) { 4483 /* 4484 * Hack because of underpowered API: passing the current 4485 * rewritten filename through to other URL-to-filename handlers 4486 * just as it were the requested URL. This is to enable 4487 * post-processing by mod_alias, etc. which always act on 4488 * r->uri! The difference here is: We do not try to 4489 * add the document root 4490 */ 4491 r->uri = apr_pstrdup(r->pool, r->filename+12); 4492 return DECLINED; 4493 } 4494 else { 4495 /* it was finally rewritten to a local path */ 4496 4497 /* expand "/~user" prefix */ 4498#if APR_HAS_USER 4499 r->filename = expand_tildepaths(r, r->filename); 4500#endif 4501 rewritelog((r, 2, NULL, "local path result: %s", r->filename)); 4502 4503 /* the filename must be either an absolute local path or an 4504 * absolute local URL. 4505 */ 4506 if ( *r->filename != '/' 4507 && !ap_os_is_path_absolute(r->pool, r->filename)) { 4508 return HTTP_BAD_REQUEST; 4509 } 4510 4511 /* if there is no valid prefix, we call 4512 * the translator from the core and 4513 * prefix the filename with document_root 4514 * 4515 * NOTICE: 4516 * We cannot leave out the prefix_stat because 4517 * - when we always prefix with document_root 4518 * then no absolute path can be created, e.g. via 4519 * emulating a ScriptAlias directive, etc. 4520 * - when we always NOT prefix with document_root 4521 * then the files under document_root have to 4522 * be references directly and document_root 4523 * gets never used and will be a dummy parameter - 4524 * this is also bad 4525 * 4526 * BUT: 4527 * Under real Unix systems this is no problem, 4528 * because we only do stat() on the first directory 4529 * and this gets cached by the kernel for along time! 4530 */ 4531 if (!prefix_stat(r->filename, r->pool)) { 4532 int res; 4533 char *tmp = r->uri; 4534 4535 r->uri = r->filename; 4536 res = ap_core_translate(r); 4537 r->uri = tmp; 4538 4539 if (res != OK) { 4540 rewritelog((r, 1, NULL, "prefixing with document_root of %s" 4541 " FAILED", r->filename)); 4542 4543 return res; 4544 } 4545 4546 rewritelog((r, 2, NULL, "prefixed with document_root to %s", 4547 r->filename)); 4548 } 4549 4550 rewritelog((r, 1, NULL, "go-ahead with %s [OK]", r->filename)); 4551 return OK; 4552 } 4553 } 4554 else { 4555 rewritelog((r, 1, NULL, "pass through %s", r->filename)); 4556 return DECLINED; 4557 } 4558} 4559 4560/* 4561 * Fixup hook 4562 * [RewriteRules in directory context] 4563 */ 4564static int hook_fixup(request_rec *r) 4565{ 4566 rewrite_perdir_conf *dconf; 4567 char *cp; 4568 char *cp2; 4569 const char *ccp; 4570 apr_size_t l; 4571 int rulestatus; 4572 int n; 4573 char *ofilename; 4574 int is_proxyreq; 4575 4576 dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config, 4577 &rewrite_module); 4578 4579 /* if there is no per-dir config we return immediately */ 4580 if (dconf == NULL) { 4581 return DECLINED; 4582 } 4583 4584 /* if there are no real (i.e. no RewriteRule directives!) 4585 per-dir config of us, we return also immediately */ 4586 if (dconf->directory == NULL) { 4587 return DECLINED; 4588 } 4589 4590 /* 4591 * Proxy request? 4592 */ 4593 is_proxyreq = ( r->proxyreq && r->filename 4594 && !strncmp(r->filename, "proxy:", 6)); 4595 4596 /* 4597 * .htaccess file is called before really entering the directory, i.e.: 4598 * URL: http://localhost/foo and .htaccess is located in foo directory 4599 * Ignore such attempts, since they may lead to undefined behaviour. 4600 */ 4601 if (!is_proxyreq) { 4602 l = strlen(dconf->directory) - 1; 4603 if (r->filename && strlen(r->filename) == l && 4604 (dconf->directory)[l] == '/' && 4605 !strncmp(r->filename, dconf->directory, l)) { 4606 return DECLINED; 4607 } 4608 } 4609 4610 /* 4611 * only do something under runtime if the engine is really enabled, 4612 * for this directory, else return immediately! 4613 */ 4614 if (!dconf || dconf->state == ENGINE_DISABLED) { 4615 return DECLINED; 4616 } 4617 4618 /* 4619 * Do the Options check after engine check, so 4620 * the user is able to explicitely turn RewriteEngine Off. 4621 */ 4622 if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) { 4623 /* FollowSymLinks is mandatory! */ 4624 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, 4625 "Options FollowSymLinks or SymLinksIfOwnerMatch is off " 4626 "which implies that RewriteRule directive is forbidden: " 4627 "%s", r->filename); 4628 return HTTP_FORBIDDEN; 4629 } 4630 4631 /* 4632 * remember the current filename before rewriting for later check 4633 * to prevent deadlooping because of internal redirects 4634 * on final URL/filename which can be equal to the inital one. 4635 * also, we'll restore original r->filename if we decline this 4636 * request 4637 */ 4638 ofilename = r->filename; 4639 4640 if (r->filename == NULL) { 4641 r->filename = apr_pstrdup(r->pool, r->uri); 4642 rewritelog((r, 2, "init rewrite engine with requested uri %s", 4643 r->filename)); 4644 } 4645 4646 /* 4647 * now apply the rules ... 4648 */ 4649 rulestatus = apply_rewrite_list(r, dconf->rewriterules, dconf->directory); 4650 if (rulestatus) { 4651 unsigned skip; 4652 4653 if (ACTION_STATUS == rulestatus) { 4654 int n = r->status; 4655 4656 r->status = HTTP_OK; 4657 return n; 4658 } 4659 4660 l = strlen(r->filename); 4661 if (l > 6 && strncmp(r->filename, "proxy:", 6) == 0) { 4662 /* it should go on as an internal proxy request */ 4663 4664 /* make sure the QUERY_STRING and 4665 * PATH_INFO parts get incorporated 4666 * (r->path_info was already appended by the 4667 * rewriting engine because of the per-dir context!) 4668 */ 4669 if (r->args != NULL) { 4670 r->filename = apr_pstrcat(r->pool, r->filename, 4671 "?", r->args, NULL); 4672 } 4673 4674 /* now make sure the request gets handled by the proxy handler */ 4675 if (PROXYREQ_NONE == r->proxyreq) { 4676 r->proxyreq = PROXYREQ_REVERSE; 4677 } 4678 r->handler = "proxy-server"; 4679 4680 rewritelog((r, 1, dconf->directory, "go-ahead with proxy request " 4681 "%s [OK]", r->filename)); 4682 return OK; 4683 } 4684 else if ((skip = is_absolute_uri(r->filename)) > 0) { 4685 /* it was finally rewritten to a remote URL */ 4686 4687 /* because we are in a per-dir context 4688 * first try to replace the directory with its base-URL 4689 * if there is a base-URL available 4690 */ 4691 if (dconf->baseurl != NULL) { 4692 /* skip 'scheme://' */ 4693 cp = r->filename + skip; 4694 4695 if ((cp = ap_strchr(cp, '/')) != NULL && *(++cp)) { 4696 rewritelog((r, 2, dconf->directory, 4697 "trying to replace prefix %s with %s", 4698 dconf->directory, dconf->baseurl)); 4699 4700 /* I think, that hack needs an explanation: 4701 * well, here is it: 4702 * mod_rewrite was written for unix systems, were 4703 * absolute file-system paths start with a slash. 4704 * URL-paths _also_ start with slashes, so they 4705 * can be easily compared with system paths. 4706 * 4707 * the following assumes, that the actual url-path 4708 * may be prefixed by the current directory path and 4709 * tries to replace the system path with the RewriteBase 4710 * URL. 4711 * That assumption is true if we use a RewriteRule like 4712 * 4713 * RewriteRule ^foo bar [R] 4714 * 4715 * (see apply_rewrite_rule function) 4716 * However on systems that don't have a / as system 4717 * root this will never match, so we skip the / after the 4718 * hostname and compare/substitute only the stuff after it. 4719 * 4720 * (note that cp was already increased to the right value) 4721 */ 4722 cp2 = subst_prefix_path(r, cp, (*dconf->directory == '/') 4723 ? dconf->directory + 1 4724 : dconf->directory, 4725 dconf->baseurl + 1); 4726 if (strcmp(cp2, cp) != 0) { 4727 *cp = '\0'; 4728 r->filename = apr_pstrcat(r->pool, r->filename, 4729 cp2, NULL); 4730 } 4731 } 4732 } 4733 4734 /* now prepare the redirect... */ 4735 if (rulestatus != ACTION_NOESCAPE) { 4736 rewritelog((r, 1, dconf->directory, "escaping %s for redirect", 4737 r->filename)); 4738 r->filename = escape_absolute_uri(r->pool, r->filename, skip); 4739 } 4740 4741 /* append the QUERY_STRING part */ 4742 if (r->args) { 4743 r->filename = apr_pstrcat(r->pool, r->filename, "?", 4744 (rulestatus == ACTION_NOESCAPE) 4745 ? r->args 4746 : ap_escape_uri(r->pool, r->args), 4747 NULL); 4748 } 4749 4750 /* determine HTTP redirect response code */ 4751 if (ap_is_HTTP_REDIRECT(r->status)) { 4752 n = r->status; 4753 r->status = HTTP_OK; /* make Apache kernel happy */ 4754 } 4755 else { 4756 n = HTTP_MOVED_TEMPORARILY; 4757 } 4758 4759 /* now do the redirection */ 4760 apr_table_setn(r->headers_out, "Location", r->filename); 4761 rewritelog((r, 1, dconf->directory, "redirect to %s [REDIRECT/%d]", 4762 r->filename, n)); 4763 return n; 4764 } 4765 else { 4766 /* it was finally rewritten to a local path */ 4767 4768 /* if someone used the PASSTHROUGH flag in per-dir 4769 * context we just ignore it. It is only useful 4770 * in per-server context 4771 */ 4772 if (l > 12 && strncmp(r->filename, "passthrough:", 12) == 0) { 4773 r->filename = apr_pstrdup(r->pool, r->filename+12); 4774 } 4775 4776 /* the filename must be either an absolute local path or an 4777 * absolute local URL. 4778 */ 4779 if ( *r->filename != '/' 4780 && !ap_os_is_path_absolute(r->pool, r->filename)) { 4781 return HTTP_BAD_REQUEST; 4782 } 4783 4784 /* Check for deadlooping: 4785 * At this point we KNOW that at least one rewriting 4786 * rule was applied, but when the resulting URL is 4787 * the same as the initial URL, we are not allowed to 4788 * use the following internal redirection stuff because 4789 * this would lead to a deadloop. 4790 */ 4791 if (ofilename != NULL && strcmp(r->filename, ofilename) == 0) { 4792 rewritelog((r, 1, dconf->directory, "initial URL equal rewritten" 4793 " URL: %s [IGNORING REWRITE]", r->filename)); 4794 return OK; 4795 } 4796 4797 /* if there is a valid base-URL then substitute 4798 * the per-dir prefix with this base-URL if the 4799 * current filename still is inside this per-dir 4800 * context. If not then treat the result as a 4801 * plain URL 4802 */ 4803 if (dconf->baseurl != NULL) { 4804 rewritelog((r, 2, dconf->directory, "trying to replace prefix " 4805 "%s with %s", dconf->directory, dconf->baseurl)); 4806 4807 r->filename = subst_prefix_path(r, r->filename, 4808 dconf->directory, 4809 dconf->baseurl); 4810 } 4811 else { 4812 /* if no explicit base-URL exists we assume 4813 * that the directory prefix is also a valid URL 4814 * for this webserver and only try to remove the 4815 * document_root if it is prefix 4816 */ 4817 if ((ccp = ap_document_root(r)) != NULL) { 4818 /* strip trailing slash */ 4819 l = strlen(ccp); 4820 if (ccp[l-1] == '/') { 4821 --l; 4822 } 4823 if (!strncmp(r->filename, ccp, l) && 4824 r->filename[l] == '/') { 4825 rewritelog((r, 2,dconf->directory, "strip document_root" 4826 " prefix: %s -> %s", r->filename, 4827 r->filename+l)); 4828 4829 r->filename = apr_pstrdup(r->pool, r->filename+l); 4830 } 4831 } 4832 } 4833 4834 /* now initiate the internal redirect */ 4835 rewritelog((r, 1, dconf->directory, "internal redirect with %s " 4836 "[INTERNAL REDIRECT]", r->filename)); 4837 r->filename = apr_pstrcat(r->pool, "redirect:", r->filename, NULL); 4838 r->handler = "redirect-handler"; 4839 return OK; 4840 } 4841 } 4842 else { 4843 rewritelog((r, 1, dconf->directory, "pass through %s", r->filename)); 4844 r->filename = ofilename; 4845 return DECLINED; 4846 } 4847} 4848 4849/* 4850 * MIME-type hook 4851 * [T=...,H=...] execution 4852 */ 4853static int hook_mimetype(request_rec *r) 4854{ 4855 const char *t; 4856 4857 /* type */ 4858 t = apr_table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR); 4859 if (t && *t) { 4860 rewritelog((r, 1, NULL, "force filename %s to have MIME-type '%s'", 4861 r->filename, t)); 4862 4863 ap_set_content_type(r, t); 4864 } 4865 4866 /* handler */ 4867 t = apr_table_get(r->notes, REWRITE_FORCED_HANDLER_NOTEVAR); 4868 if (t && *t) { 4869 rewritelog((r, 1, NULL, "force filename %s to have the " 4870 "Content-handler '%s'", r->filename, t)); 4871 4872 r->handler = t; 4873 } 4874 4875 return OK; 4876} 4877 4878 4879/* 4880 * "content" handler for internal redirects 4881 */ 4882static int handler_redirect(request_rec *r) 4883{ 4884 if (strcmp(r->handler, "redirect-handler")) { 4885 return DECLINED; 4886 } 4887 4888 /* just make sure that we are really meant! */ 4889 if (strncmp(r->filename, "redirect:", 9) != 0) { 4890 return DECLINED; 4891 } 4892 4893 /* now do the internal redirect */ 4894 ap_internal_redirect(apr_pstrcat(r->pool, r->filename+9, 4895 r->args ? "?" : NULL, r->args, NULL), r); 4896 4897 /* and return gracefully */ 4898 return OK; 4899} 4900 4901 4902/* 4903 * +-------------------------------------------------------+ 4904 * | | 4905 * | Module paraphernalia 4906 * | | 4907 * +-------------------------------------------------------+ 4908 */ 4909 4910#ifdef REWRITELOG_DISABLED 4911static const char *fake_rewritelog(cmd_parms *cmd, void *dummy, const char *a1) 4912{ 4913 return "RewriteLog and RewriteLogLevel are not supported by this build " 4914 "of mod_rewrite because it was compiled using the " 4915 "-DREWRITELOG_DISABLED compiler option. You have to recompile " 4916 "mod_rewrite WITHOUT this option in order to use the rewrite log."; 4917} 4918#endif 4919 4920static const command_rec command_table[] = { 4921 AP_INIT_FLAG( "RewriteEngine", cmd_rewriteengine, NULL, OR_FILEINFO, 4922 "On or Off to enable or disable (default) the whole " 4923 "rewriting engine"), 4924 AP_INIT_ITERATE( "RewriteOptions", cmd_rewriteoptions, NULL, OR_FILEINFO, 4925 "List of option strings to set"), 4926 AP_INIT_TAKE1( "RewriteBase", cmd_rewritebase, NULL, OR_FILEINFO, 4927 "the base URL of the per-directory context"), 4928 AP_INIT_RAW_ARGS("RewriteCond", cmd_rewritecond, NULL, OR_FILEINFO, 4929 "an input string and a to be applied regexp-pattern"), 4930 AP_INIT_RAW_ARGS("RewriteRule", cmd_rewriterule, NULL, OR_FILEINFO, 4931 "an URL-applied regexp-pattern and a substitution URL"), 4932 AP_INIT_TAKE2( "RewriteMap", cmd_rewritemap, NULL, RSRC_CONF, 4933 "a mapname and a filename"), 4934 AP_INIT_TAKE1( "RewriteLock", cmd_rewritelock, NULL, RSRC_CONF, 4935 "the filename of a lockfile used for inter-process " 4936 "synchronization"), 4937#ifndef REWRITELOG_DISABLED 4938 AP_INIT_TAKE1( "RewriteLog", cmd_rewritelog, NULL, RSRC_CONF, 4939 "the filename of the rewriting logfile"), 4940 AP_INIT_TAKE1( "RewriteLogLevel", cmd_rewriteloglevel, NULL, RSRC_CONF, 4941 "the level of the rewriting logfile verbosity " 4942 "(0=none, 1=std, .., 9=max)"), 4943#else 4944 AP_INIT_TAKE1( "RewriteLog", fake_rewritelog, NULL, RSRC_CONF, 4945 "[DISABLED] the filename of the rewriting logfile"), 4946 AP_INIT_TAKE1( "RewriteLogLevel", fake_rewritelog, NULL, RSRC_CONF, 4947 "[DISABLED] the level of the rewriting logfile verbosity"), 4948#endif 4949 { NULL } 4950}; 4951 4952static void ap_register_rewrite_mapfunc(char *name, rewrite_mapfunc_t *func) 4953{ 4954 apr_hash_set(mapfunc_hash, name, strlen(name), (const void *)func); 4955} 4956 4957static void register_hooks(apr_pool_t *p) 4958{ 4959 /* fixup after mod_proxy, so that the proxied url will not 4960 * escaped accidentally by mod_proxy's fixup. 4961 */ 4962 static const char * const aszPre[]={ "mod_proxy.c", NULL }; 4963 4964 /* make the hashtable before registering the function, so that 4965 * other modules are prevented from accessing uninitialized memory. 4966 */ 4967 mapfunc_hash = apr_hash_make(p); 4968 APR_REGISTER_OPTIONAL_FN(ap_register_rewrite_mapfunc); 4969 4970 ap_hook_handler(handler_redirect, NULL, NULL, APR_HOOK_MIDDLE); 4971 ap_hook_pre_config(pre_config, NULL, NULL, APR_HOOK_MIDDLE); 4972 ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_MIDDLE); 4973 ap_hook_child_init(init_child, NULL, NULL, APR_HOOK_MIDDLE); 4974 4975 ap_hook_fixups(hook_fixup, aszPre, NULL, APR_HOOK_FIRST); 4976 ap_hook_fixups(hook_mimetype, NULL, NULL, APR_HOOK_LAST); 4977 ap_hook_translate_name(hook_uri2file, NULL, NULL, APR_HOOK_FIRST); 4978} 4979 4980 /* the main config structure */ 4981module AP_MODULE_DECLARE_DATA rewrite_module = { 4982 STANDARD20_MODULE_STUFF, 4983 config_perdir_create, /* create per-dir config structures */ 4984 config_perdir_merge, /* merge per-dir config structures */ 4985 config_server_create, /* create per-server config structures */ 4986 config_server_merge, /* merge per-server config structures */ 4987 command_table, /* table of config file commands */ 4988 register_hooks /* register hooks */ 4989}; 4990 4991/*EOF*/ 4992