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