diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:33:50 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-27 06:33:50 +0000 |
commit | fe39ffb8b90ae4e002ed73fe98617cd590abb467 (patch) | |
tree | b80e5956907d8aeaaffe4e4f0c068c0e6157ce8b /modules/mappers/mod_rewrite.c | |
parent | Initial commit. (diff) | |
download | apache2-upstream.tar.xz apache2-upstream.zip |
Adding upstream version 2.4.56.upstream/2.4.56upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | modules/mappers/mod_rewrite.c | 5406 |
1 files changed, 5406 insertions, 0 deletions
diff --git a/modules/mappers/mod_rewrite.c b/modules/mappers/mod_rewrite.c new file mode 100644 index 0000000..5195cee --- /dev/null +++ b/modules/mappers/mod_rewrite.c @@ -0,0 +1,5406 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* _ _ _ + * _ __ ___ ___ __| | _ __ _____ ___ __(_) |_ ___ + * | '_ ` _ \ / _ \ / _` | | '__/ _ \ \ /\ / / '__| | __/ _ \ + * | | | | | | (_) | (_| | | | | __/\ V V /| | | | || __/ + * |_| |_| |_|\___/ \__,_|___|_| \___| \_/\_/ |_| |_|\__\___| + * |_____| + * + * URL Rewriting Module + * + * This module uses a rule-based rewriting engine (based on a + * regular-expression parser) to rewrite requested URLs on the fly. + * + * It supports an unlimited number of additional rule conditions (which can + * operate on a lot of variables, even on HTTP headers) for granular + * matching and even external database lookups (either via plain text + * tables, DBM hash files or even external processes) for advanced URL + * substitution. + * + * It operates on the full URLs (including the PATH_INFO part) both in + * per-server context (httpd.conf) and per-dir context (.htaccess) and even + * can generate QUERY_STRING parts on result. The rewriting result finally + * can lead to internal subprocessing, external request redirection or even + * to internal proxy throughput. + * + * This module was originally written in April 1996 and + * gifted exclusively to the The Apache Software Foundation in July 1997 by + * + * Ralf S. Engelschall + * rse engelschall.com + * www.engelschall.com + */ + +#include "apr.h" +#include "apr_strings.h" +#include "apr_hash.h" +#include "apr_user.h" +#include "apr_lib.h" +#include "apr_signal.h" +#include "apr_global_mutex.h" +#include "apr_dbm.h" +#include "apr_dbd.h" + +#include "apr_version.h" +#if !APR_VERSION_AT_LEAST(2,0,0) +#include "apu_version.h" +#endif + +#include "mod_dbd.h" + +#if APR_HAS_THREADS +#include "apr_thread_mutex.h" +#endif + +#define APR_WANT_MEMFUNC +#define APR_WANT_STRFUNC +#define APR_WANT_IOVEC +#include "apr_want.h" + +/* XXX: Do we really need these headers? */ +#if APR_HAVE_UNISTD_H +#include <unistd.h> +#endif +#if APR_HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif +#if APR_HAVE_STDARG_H +#include <stdarg.h> +#endif +#if APR_HAVE_STDLIB_H +#include <stdlib.h> +#endif +#if APR_HAVE_CTYPE_H +#include <ctype.h> +#endif +#if APR_HAVE_NETINET_IN_H +#include <netinet/in.h> +#endif + +#include "ap_config.h" +#include "httpd.h" +#include "http_config.h" +#include "http_request.h" +#include "http_core.h" +#include "http_log.h" +#include "http_protocol.h" +#include "http_ssl.h" +#include "http_vhost.h" +#include "util_mutex.h" + +#include "mod_rewrite.h" +#include "ap_expr.h" + +static ap_dbd_t *(*dbd_acquire)(request_rec*) = NULL; +static void (*dbd_prepare)(server_rec*, const char*, const char*) = NULL; +static const char* really_last_key = "rewrite_really_last"; + +/* + * in order to improve performance on running production systems, you + * may strip all rewritelog code entirely from mod_rewrite by using the + * -DREWRITELOG_DISABLED compiler option. + * + * DO NOT USE THIS OPTION FOR PUBLIC BINARY RELEASES. Otherwise YOU are + * responsible for answering all the mod_rewrite questions out there. + */ +/* If logging is limited to APLOG_DEBUG or lower, disable rewrite log, too */ +#ifdef APLOG_MAX_LOGLEVEL +#if APLOG_MAX_LOGLEVEL < APLOG_TRACE1 +#ifndef REWRITELOG_DISABLED +#define REWRITELOG_DISABLED +#endif +#endif +#endif + +#ifndef REWRITELOG_DISABLED + +#define rewritelog(x) do_rewritelog x +#define REWRITELOG_MODE ( APR_UREAD | APR_UWRITE | APR_GREAD | APR_WREAD ) +#define REWRITELOG_FLAGS ( APR_WRITE | APR_APPEND | APR_CREATE ) + +#else /* !REWRITELOG_DISABLED */ + +#define rewritelog(x) + +#endif /* REWRITELOG_DISABLED */ + +/* remembered mime-type for [T=...] */ +#define REWRITE_FORCED_MIMETYPE_NOTEVAR "rewrite-forced-mimetype" +#define REWRITE_FORCED_HANDLER_NOTEVAR "rewrite-forced-handler" + +#define ENVVAR_SCRIPT_URL "SCRIPT_URL" +#define REDIRECT_ENVVAR_SCRIPT_URL "REDIRECT_" ENVVAR_SCRIPT_URL +#define ENVVAR_SCRIPT_URI "SCRIPT_URI" + +#define CONDFLAG_NONE (1<<0) +#define CONDFLAG_NOCASE (1<<1) +#define CONDFLAG_NOTMATCH (1<<2) +#define CONDFLAG_ORNEXT (1<<3) +#define CONDFLAG_NOVARY (1<<4) + +#define RULEFLAG_NONE (1<<0) +#define RULEFLAG_FORCEREDIRECT (1<<1) +#define RULEFLAG_LASTRULE (1<<2) +#define RULEFLAG_NEWROUND (1<<3) +#define RULEFLAG_CHAIN (1<<4) +#define RULEFLAG_IGNOREONSUBREQ (1<<5) +#define RULEFLAG_NOTMATCH (1<<6) +#define RULEFLAG_PROXY (1<<7) +#define RULEFLAG_PASSTHROUGH (1<<8) +#define RULEFLAG_QSAPPEND (1<<9) +#define RULEFLAG_NOCASE (1<<10) +#define RULEFLAG_NOESCAPE (1<<11) +#define RULEFLAG_NOSUB (1<<12) +#define RULEFLAG_STATUS (1<<13) +#define RULEFLAG_ESCAPEBACKREF (1<<14) +#define RULEFLAG_DISCARDPATHINFO (1<<15) +#define RULEFLAG_QSDISCARD (1<<16) +#define RULEFLAG_END (1<<17) +#define RULEFLAG_ESCAPENOPLUS (1<<18) +#define RULEFLAG_QSLAST (1<<19) +#define RULEFLAG_QSNONE (1<<20) /* programattic only */ + +/* return code of the rewrite rule + * the result may be escaped - or not + */ +#define ACTION_NORMAL (1<<0) +#define ACTION_NOESCAPE (1<<1) +#define ACTION_STATUS (1<<2) + + +#define MAPTYPE_TXT (1<<0) +#define MAPTYPE_DBM (1<<1) +#define MAPTYPE_PRG (1<<2) +#define MAPTYPE_INT (1<<3) +#define MAPTYPE_RND (1<<4) +#define MAPTYPE_DBD (1<<5) +#define MAPTYPE_DBD_CACHE (1<<6) + +#define ENGINE_DISABLED (1<<0) +#define ENGINE_ENABLED (1<<1) + +#define OPTION_NONE (1<<0) +#define OPTION_INHERIT (1<<1) +#define OPTION_INHERIT_BEFORE (1<<2) +#define OPTION_NOSLASH (1<<3) +#define OPTION_ANYURI (1<<4) +#define OPTION_MERGEBASE (1<<5) +#define OPTION_INHERIT_DOWN (1<<6) +#define OPTION_INHERIT_DOWN_BEFORE (1<<7) +#define OPTION_IGNORE_INHERIT (1<<8) +#define OPTION_IGNORE_CONTEXT_INFO (1<<9) +#define OPTION_LEGACY_PREFIX_DOCROOT (1<<10) + +#ifndef RAND_MAX +#define RAND_MAX 32767 +#endif + +/* max cookie size in rfc 2109 */ +/* XXX: not used at all. We should do a check somewhere and/or cut the cookie */ +#define MAX_COOKIE_LEN 4096 + +/* max line length (incl.\n) in text rewrite maps */ +#ifndef REWRITE_MAX_TXT_MAP_LINE +#define REWRITE_MAX_TXT_MAP_LINE 1024 +#endif + +/* buffer length for prg rewrite maps */ +#ifndef REWRITE_PRG_MAP_BUF +#define REWRITE_PRG_MAP_BUF 1024 +#endif + +/* for better readbility */ +#define LEFT_CURLY '{' +#define RIGHT_CURLY '}' + +/* + * expansion result items on the stack to save some cycles + * + * (5 == about 2 variables like "foo%{var}bar%{var}baz") + */ +#define SMALL_EXPANSION 5 + +/* + * check that a subrequest won't cause infinite recursion + * + * either not in a subrequest, or in a subrequest + * and URIs aren't NULL and sub/main URIs differ + */ +#define subreq_ok(r) (!r->main || \ + (r->main->uri && r->uri && strcmp(r->main->uri, r->uri))) + +#ifndef REWRITE_MAX_ROUNDS +#define REWRITE_MAX_ROUNDS 32000 +#endif + +/* + * +-------------------------------------------------------+ + * | | + * | Types and Structures + * | | + * +-------------------------------------------------------+ + */ + +typedef struct { + const char *datafile; /* filename for map data files */ + const char *dbmtype; /* dbm type for dbm map data files */ + const char *checkfile; /* filename to check for map existence */ + const char *cachename; /* for cached maps (txt/rnd/dbm) */ + int type; /* the type of the map */ + apr_file_t *fpin; /* in file pointer for program maps */ + apr_file_t *fpout; /* out file pointer for program maps */ + apr_file_t *fperr; /* err file pointer for program maps */ + char *(*func)(request_rec *, /* function pointer for internal maps */ + char *); + char **argv; /* argv of the external rewrite map */ + const char *dbdq; /* SQL SELECT statement for rewritemap */ + const char *checkfile2; /* filename to check for map existence + NULL if only one file */ + const char *user; /* run RewriteMap program as this user */ + const char *group; /* run RewriteMap program as this group */ +} rewritemap_entry; + +/* special pattern types for RewriteCond */ +typedef enum { + CONDPAT_REGEX = 0, + CONDPAT_FILE_EXISTS, + CONDPAT_FILE_SIZE, + CONDPAT_FILE_LINK, + CONDPAT_FILE_DIR, + CONDPAT_FILE_XBIT, + CONDPAT_LU_URL, + CONDPAT_LU_FILE, + CONDPAT_STR_LT, + CONDPAT_STR_LE, + CONDPAT_STR_EQ, + CONDPAT_STR_GT, + CONDPAT_STR_GE, + CONDPAT_INT_LT, + CONDPAT_INT_LE, + CONDPAT_INT_EQ, + CONDPAT_INT_GT, + CONDPAT_INT_GE, + CONDPAT_AP_EXPR +} pattern_type; + +typedef struct { + char *input; /* Input string of RewriteCond */ + char *pattern; /* the RegExp pattern string */ + ap_regex_t *regexp; /* the precompiled regexp */ + ap_expr_info_t *expr; /* the compiled ap_expr */ + int flags; /* Flags which control the match */ + pattern_type ptype; /* pattern type */ + int pskip; /* back-index to display pattern */ +} rewritecond_entry; + +/* single linked list for env vars and cookies */ +typedef struct data_item { + struct data_item *next; + char *data; +} data_item; + +typedef struct { + apr_array_header_t *rewriteconds;/* the corresponding RewriteCond entries */ + char *pattern; /* the RegExp pattern string */ + ap_regex_t *regexp; /* the RegExp pattern compilation */ + char *output; /* the Substitution string */ + int flags; /* Flags which control the substitution */ + char *forced_mimetype; /* forced MIME type of substitution */ + char *forced_handler; /* forced content handler of subst. */ + int forced_responsecode; /* forced HTTP response status */ + data_item *env; /* added environment variables */ + data_item *cookie; /* added cookies */ + int skip; /* number of next rules to skip */ + int maxrounds; /* limit on number of loops with N flag */ + char *escapes; /* specific backref escapes */ +} rewriterule_entry; + +typedef struct { + int state; /* the RewriteEngine state */ + int options; /* the RewriteOption state */ + apr_hash_t *rewritemaps; /* the RewriteMap entries */ + apr_array_header_t *rewriteconds; /* the RewriteCond entries (temp.) */ + apr_array_header_t *rewriterules; /* the RewriteRule entries */ + server_rec *server; /* the corresponding server indicator */ + unsigned int state_set:1; + unsigned int options_set:1; +} rewrite_server_conf; + +typedef struct { + int state; /* the RewriteEngine state */ + int options; /* the RewriteOption state */ + apr_array_header_t *rewriteconds; /* the RewriteCond entries (temp.) */ + apr_array_header_t *rewriterules; /* the RewriteRule entries */ + char *directory; /* the directory where it applies */ + const char *baseurl; /* the base-URL where it applies */ + unsigned int state_set:1; + unsigned int options_set:1; + unsigned int baseurl_set:1; +} rewrite_perdir_conf; + +/* the (per-child) cache structures. + */ +typedef struct cache { + apr_pool_t *pool; + apr_hash_t *maps; +#if APR_HAS_THREADS + apr_thread_mutex_t *lock; +#endif +} cache; + +/* cached maps contain an mtime for the whole map and live in a subpool + * of the cachep->pool. That makes it easy to forget them if necessary. + */ +typedef struct { + apr_time_t mtime; + apr_pool_t *pool; + apr_hash_t *entries; +} cachedmap; + +/* the regex structure for the + * substitution of backreferences + */ +typedef struct backrefinfo { + const char *source; + ap_regmatch_t regmatch[AP_MAX_REG_MATCH]; +} backrefinfo; + +/* single linked list used for + * variable expansion + */ +typedef struct result_list { + struct result_list *next; + apr_size_t len; + const char *string; +} result_list; + +/* context structure for variable lookup and expansion + */ +typedef struct { + request_rec *r; + const char *uri; + const char *vary_this; + const char *vary; + char *perdir; + backrefinfo briRR; + backrefinfo briRC; +} rewrite_ctx; + +/* + * +-------------------------------------------------------+ + * | | + * | static module data + * | | + * +-------------------------------------------------------+ + */ + +/* the global module structure */ +module AP_MODULE_DECLARE_DATA rewrite_module; + +/* rewritemap int: handler function registry */ +static apr_hash_t *mapfunc_hash; + +/* the cache */ +static cache *cachep; + +/* whether proxy module is available or not */ +static int proxy_available; + +/* Locks/Mutexes */ +static int rewrite_lock_needed = 0; +static apr_global_mutex_t *rewrite_mapr_lock_acquire = NULL; +static const char *rewritemap_mutex_type = "rewrite-map"; + +/* Optional functions imported from mod_ssl when loaded: */ +static char *escape_backref(apr_pool_t *p, const char *path, const char *escapeme, int noplus); + +/* + * +-------------------------------------------------------+ + * | | + * | rewriting logfile support + * | | + * +-------------------------------------------------------+ + */ + +#ifndef REWRITELOG_DISABLED +static void do_rewritelog(request_rec *r, int level, char *perdir, + const char *fmt, ...) + __attribute__((format(printf,4,5))); + +static void do_rewritelog(request_rec *r, int level, char *perdir, + const char *fmt, ...) +{ + char *logline, *text; + const char *rhost, *rname; + int redir; + request_rec *req; + va_list ap; + + if (!APLOG_R_IS_LEVEL(r, APLOG_DEBUG + level)) + return; + + rhost = ap_get_useragent_host(r, REMOTE_NOLOOKUP, NULL); + rname = ap_get_remote_logname(r); + + for (redir=0, req=r; req->prev; req = req->prev) { + ++redir; + } + + va_start(ap, fmt); + text = apr_pvsprintf(r->pool, fmt, ap); + va_end(ap); + + logline = apr_psprintf(r->pool, "%s %s %s [%s/sid#%pp][rid#%pp/%s%s%s] " + "%s%s%s%s", + rhost ? rhost : "UNKNOWN-HOST", + rname ? rname : "-", + r->user ? (*r->user ? r->user : "\"\"") : "-", + ap_get_server_name(r), + (void *)(r->server), + (void *)r, + r->main ? "subreq" : "initial", + redir ? "/redir#" : "", + redir ? apr_itoa(r->pool, redir) : "", + perdir ? "[perdir " : "", + perdir ? perdir : "", + perdir ? "] ": "", + text); + + AP_REWRITE_LOG((uintptr_t)r, level, r->main ? 0 : 1, (char *)ap_get_server_name(r), logline); + + /* Intentional no APLOGNO */ + ap_log_rerror(APLOG_MARK, APLOG_DEBUG + level, 0, r, "%s", logline); + + return; +} +#endif /* !REWRITELOG_DISABLED */ + + +/* + * +-------------------------------------------------------+ + * | | + * | URI and path functions + * | | + * +-------------------------------------------------------+ + */ + +/* return number of chars of the scheme (incl. '://') + * if the URI is absolute (includes a scheme etc.) + * otherwise 0. + * If supportqs is not NULL, we return a whether or not + * the scheme supports a query string or not. + * + * NOTE: If you add new schemes here, please have a + * look at escape_absolute_uri and splitout_queryargs. + * Not every scheme takes query strings and some schemes + * may be handled in a special way. + * + * XXX: we may consider a scheme registry, perhaps with + * appropriate escape callbacks to allow other modules + * to extend mod_rewrite at runtime. + */ +static unsigned is_absolute_uri(char *uri, int *supportsqs) +{ + int dummy, *sqs; + + sqs = (supportsqs ? supportsqs : &dummy); + *sqs = 0; + /* fast exit */ + if (*uri == '/' || strlen(uri) <= 5) { + return 0; + } + + switch (*uri++) { + case 'a': + case 'A': + if (!ap_cstr_casecmpn(uri, "jp://", 5)) { /* ajp:// */ + *sqs = 1; + return 6; + } + break; + + case 'b': + case 'B': + if (!ap_cstr_casecmpn(uri, "alancer://", 10)) { /* balancer:// */ + *sqs = 1; + return 11; + } + break; + + case 'f': + case 'F': + if (!ap_cstr_casecmpn(uri, "tp://", 5)) { /* ftp:// */ + return 6; + } + if (!ap_cstr_casecmpn(uri, "cgi://", 6)) { /* fcgi:// */ + *sqs = 1; + return 7; + } + break; + + case 'g': + case 'G': + if (!ap_cstr_casecmpn(uri, "opher://", 8)) { /* gopher:// */ + return 9; + } + break; + + case 'h': + case 'H': + if (!ap_cstr_casecmpn(uri, "ttp://", 6)) { /* http:// */ + *sqs = 1; + return 7; + } + else if (!ap_cstr_casecmpn(uri, "ttps://", 7)) { /* https:// */ + *sqs = 1; + return 8; + } + else if (!ap_cstr_casecmpn(uri, "2://", 4)) { /* h2:// */ + *sqs = 1; + return 5; + } + else if (!ap_cstr_casecmpn(uri, "2c://", 5)) { /* h2c:// */ + *sqs = 1; + return 6; + } + break; + + case 'l': + case 'L': + if (!ap_cstr_casecmpn(uri, "dap://", 6)) { /* ldap:// */ + return 7; + } + break; + + case 'm': + case 'M': + if (!ap_cstr_casecmpn(uri, "ailto:", 6)) { /* mailto: */ + *sqs = 1; + return 7; + } + break; + + case 'n': + case 'N': + if (!ap_cstr_casecmpn(uri, "ews:", 4)) { /* news: */ + return 5; + } + else if (!ap_cstr_casecmpn(uri, "ntp://", 6)) { /* nntp:// */ + return 7; + } + break; + + case 's': + case 'S': + if (!ap_cstr_casecmpn(uri, "cgi://", 6)) { /* scgi:// */ + *sqs = 1; + return 7; + } + break; + + case 'w': + case 'W': + if (!ap_cstr_casecmpn(uri, "s://", 4)) { /* ws:// */ + *sqs = 1; + return 5; + } + else if (!ap_cstr_casecmpn(uri, "ss://", 5)) { /* wss:// */ + *sqs = 1; + return 6; + } + break; + + case 'u': + case 'U': + if (!ap_cstr_casecmpn(uri, "nix:", 4)) { /* unix: */ + *sqs = 1; + return (uri[4] == '/' && uri[5] == '/') ? 7 : 5; + } + } + + return 0; +} + +static const char c2x_table[] = "0123456789abcdef"; + +static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix, + unsigned char *where) +{ +#if APR_CHARSET_EBCDIC + what = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)what); +#endif /*APR_CHARSET_EBCDIC*/ + *where++ = prefix; + *where++ = c2x_table[what >> 4]; + *where++ = c2x_table[what & 0xf]; + return where; +} + +/* + * Escapes a backreference in a similar way as php's urlencode does. + * Based on ap_os_escape_path in server/util.c + */ +static char *escape_backref(apr_pool_t *p, const char *path, const char *escapeme, int noplus) { + char *copy = apr_palloc(p, 3 * strlen(path) + 3); + const unsigned char *s = (const unsigned char *)path; + unsigned char *d = (unsigned char *)copy; + unsigned c; + + while ((c = *s)) { + if (!escapeme) { + if (apr_isalnum(c) || c == '_') { + *d++ = c; + } + else if (c == ' ' && !noplus) { + *d++ = '+'; + } + else { + d = c2x(c, '%', d); + } + } + else { + const char *esc = escapeme; + while (*esc) { + if (c == *esc) { + if (c == ' ' && !noplus) { + *d++ = '+'; + } + else { + d = c2x(c, '%', d); + } + break; + } + ++esc; + } + if (!*esc) { + *d++ = c; + } + } + ++s; + } + *d = '\0'; + return copy; +} + +/* + * escape absolute uri, which may or may not be path oriented. + * So let's handle them differently. + */ +static char *escape_absolute_uri(apr_pool_t *p, char *uri, unsigned scheme) +{ + char *cp; + + /* be safe. + * NULL should indicate elsewhere, that something's wrong + */ + if (!scheme || strlen(uri) < scheme) { + return NULL; + } + + cp = uri + scheme; + + /* scheme with authority part? */ + if (cp[-1] == '/') { + /* skip host part */ + while (*cp && *cp != '/') { + ++cp; + } + + /* nothing after the hostpart. ready! */ + if (!*cp || !*++cp) { + return apr_pstrdup(p, uri); + } + + /* remember the hostname stuff */ + scheme = cp - uri; + + /* special thing for ldap. + * The parts are separated by question marks. From RFC 2255: + * ldapurl = scheme "://" [hostport] ["/" + * [dn ["?" [attributes] ["?" [scope] + * ["?" [filter] ["?" extensions]]]]]] + */ + if (!ap_cstr_casecmpn(uri, "ldap", 4)) { + char *token[5]; + int c = 0; + + token[0] = cp = apr_pstrdup(p, cp); + while (*cp && c < 4) { + if (*cp == '?') { + token[++c] = cp + 1; + *cp = '\0'; + } + ++cp; + } + + return apr_pstrcat(p, apr_pstrndup(p, uri, scheme), + ap_escape_uri(p, token[0]), + (c >= 1) ? "?" : NULL, + (c >= 1) ? ap_escape_uri(p, token[1]) : NULL, + (c >= 2) ? "?" : NULL, + (c >= 2) ? ap_escape_uri(p, token[2]) : NULL, + (c >= 3) ? "?" : NULL, + (c >= 3) ? ap_escape_uri(p, token[3]) : NULL, + (c >= 4) ? "?" : NULL, + (c >= 4) ? ap_escape_uri(p, token[4]) : NULL, + NULL); + } + } + + /* Nothing special here. Apply normal escaping. */ + return apr_pstrcat(p, apr_pstrndup(p, uri, scheme), + ap_escape_uri(p, cp), NULL); +} + +/* + * split out a QUERY_STRING part from + * the current URI string + */ +static void splitout_queryargs(request_rec *r, int flags) +{ + char *q; + int split, skip; + int qsappend = flags & RULEFLAG_QSAPPEND; + int qsdiscard = flags & RULEFLAG_QSDISCARD; + int qslast = flags & RULEFLAG_QSLAST; + + if (flags & RULEFLAG_QSNONE) { + rewritelog((r, 2, NULL, "discarding query string, no parse from substitution")); + r->args = NULL; + return; + } + + /* don't touch, unless it's a scheme for which a query string makes sense. + * See RFC 1738 and RFC 2368. + */ + if ((skip = is_absolute_uri(r->filename, &split)) + && !split) { + r->args = NULL; /* forget the query that's still flying around */ + return; + } + + if (qsdiscard) { + r->args = NULL; /* Discard query string */ + rewritelog((r, 2, NULL, "discarding query string")); + } + + q = qslast ? ap_strrchr(r->filename + skip, '?') : ap_strchr(r->filename + skip, '?'); + + if (q != NULL) { + char *olduri; + apr_size_t len; + + olduri = apr_pstrdup(r->pool, r->filename); + *q++ = '\0'; + if (qsappend) { + if (*q) { + r->args = apr_pstrcat(r->pool, q, "&" , r->args, NULL); + } + } + else { + r->args = apr_pstrdup(r->pool, q); + } + + if (r->args) { + len = strlen(r->args); + + if (!len) { + r->args = NULL; + } + else if (r->args[len-1] == '&') { + r->args[len-1] = '\0'; + } + } + + rewritelog((r, 3, NULL, "split uri=%s -> uri=%s, args=%s", olduri, + r->filename, r->args ? r->args : "<none>")); + } +} + +/* + * strip 'http[s]://ourhost/' from URI + */ +static void reduce_uri(request_rec *r) +{ + char *cp; + apr_size_t l; + + cp = (char *)ap_http_scheme(r); + l = strlen(cp); + if ( strlen(r->filename) > l+3 + && ap_cstr_casecmpn(r->filename, cp, l) == 0 + && r->filename[l] == ':' + && r->filename[l+1] == '/' + && r->filename[l+2] == '/' ) { + + unsigned short port; + char *portp, *host, *url, *scratch; + + scratch = apr_pstrdup(r->pool, r->filename); /* our scratchpad */ + + /* cut the hostname and port out of the URI */ + cp = host = scratch + l + 3; /* 3 == strlen("://") */ + while (*cp && *cp != '/' && *cp != ':') { + ++cp; + } + + if (*cp == ':') { /* additional port given */ + *cp++ = '\0'; + portp = cp; + while (*cp && *cp != '/') { + ++cp; + } + *cp = '\0'; + + port = atoi(portp); + url = r->filename + (cp - scratch); + if (!*url) { + url = "/"; + } + } + else if (*cp == '/') { /* default port */ + *cp = '\0'; + + port = ap_default_port(r); + url = r->filename + (cp - scratch); + } + else { + port = ap_default_port(r); + url = "/"; + } + + /* now check whether we could reduce it to a local path... */ + if (ap_matches_request_vhost(r, host, port)) { + rewrite_server_conf *conf = + ap_get_module_config(r->server->module_config, &rewrite_module); + rewritelog((r, 3, NULL, "reduce %s -> %s", r->filename, url)); + r->filename = apr_pstrdup(r->pool, url); + + /* remember that the uri was reduced */ + if(!(conf->options & OPTION_LEGACY_PREFIX_DOCROOT)) { + apr_table_setn(r->notes, "mod_rewrite_uri_reduced", "true"); + } + } + } + + return; +} + +/* + * add 'http[s]://ourhost[:ourport]/' to URI + * if URI is still not fully qualified + */ +static void fully_qualify_uri(request_rec *r) +{ + if (r->method_number == M_CONNECT) { + return; + } + else if (!is_absolute_uri(r->filename, NULL)) { + const char *thisserver; + char *thisport; + int port; + + thisserver = ap_get_server_name_for_url(r); + port = ap_get_server_port(r); + thisport = ap_is_default_port(port, r) + ? "" + : apr_psprintf(r->pool, ":%u", port); + + r->filename = apr_psprintf(r->pool, "%s://%s%s%s%s", + ap_http_scheme(r), thisserver, thisport, + (*r->filename == '/') ? "" : "/", + r->filename); + } + + return; +} + +/* + * stat() only the first segment of a path + */ +static int prefix_stat(const char *path, apr_pool_t *pool) +{ + const char *curpath = path; + const char *root; + const char *slash; + char *statpath; + apr_status_t rv; + + rv = apr_filepath_root(&root, &curpath, APR_FILEPATH_TRUENAME, pool); + + if (rv != APR_SUCCESS) { + return 0; + } + + /* let's recognize slashes only, the mod_rewrite semantics are opaque + * enough. + */ + if ((slash = ap_strchr_c(curpath, '/')) != NULL) { + rv = apr_filepath_merge(&statpath, root, + apr_pstrndup(pool, curpath, + (apr_size_t)(slash - curpath)), + APR_FILEPATH_NOTABOVEROOT | + APR_FILEPATH_NOTRELATIVE, pool); + } + else { + rv = apr_filepath_merge(&statpath, root, curpath, + APR_FILEPATH_NOTABOVEROOT | + APR_FILEPATH_NOTRELATIVE, pool); + } + + if (rv == APR_SUCCESS) { + apr_finfo_t sb; + + if (apr_stat(&sb, statpath, APR_FINFO_MIN, pool) == APR_SUCCESS) { + return 1; + } + } + + return 0; +} + +/* + * substitute the prefix path 'match' in 'input' with 'subst' (RewriteBase) + */ +static char *subst_prefix_path(request_rec *r, char *input, const char *match, + const char *subst) +{ + apr_size_t len = strlen(match); + + if (len && match[len - 1] == '/') { + --len; + } + + if (!strncmp(input, match, len) && input[len++] == '/') { + apr_size_t slen, outlen; + char *output; + + rewritelog((r, 5, NULL, "strip matching prefix: %s -> %s", input, + input+len)); + + slen = strlen(subst); + if (slen && subst[slen - 1] != '/') { + ++slen; + } + + outlen = strlen(input) + slen - len; + output = apr_palloc(r->pool, outlen + 1); /* don't forget the \0 */ + + memcpy(output, subst, slen); + if (slen && !output[slen-1]) { + output[slen-1] = '/'; + } + memcpy(output+slen, input+len, outlen - slen); + output[outlen] = '\0'; + + rewritelog((r, 4, NULL, "add subst prefix: %s -> %s", input+len, + output)); + + return output; + } + + /* prefix didn't match */ + return input; +} + + +/* + * +-------------------------------------------------------+ + * | | + * | caching support + * | | + * +-------------------------------------------------------+ + */ + +static void set_cache_value(const char *name, apr_time_t t, char *key, + char *val) +{ + cachedmap *map; + + if (cachep) { +#if APR_HAS_THREADS + apr_thread_mutex_lock(cachep->lock); +#endif + map = apr_hash_get(cachep->maps, name, APR_HASH_KEY_STRING); + + if (!map) { + apr_pool_t *p; + + if (apr_pool_create(&p, cachep->pool) != APR_SUCCESS) { +#if APR_HAS_THREADS + apr_thread_mutex_unlock(cachep->lock); +#endif + return; + } + apr_pool_tag(p, "rewrite_cachedmap"); + + map = apr_palloc(cachep->pool, sizeof(cachedmap)); + map->pool = p; + map->entries = apr_hash_make(map->pool); + map->mtime = t; + + apr_hash_set(cachep->maps, name, APR_HASH_KEY_STRING, map); + } + else if (map->mtime != t) { + apr_pool_clear(map->pool); + map->entries = apr_hash_make(map->pool); + map->mtime = t; + } + + /* Now we should have a valid map->entries hash, where we + * can store our value. + * + * We need to copy the key and the value into OUR pool, + * so that we don't leave it during the r->pool cleanup. + */ + apr_hash_set(map->entries, + apr_pstrdup(map->pool, key), APR_HASH_KEY_STRING, + apr_pstrdup(map->pool, val)); + +#if APR_HAS_THREADS + apr_thread_mutex_unlock(cachep->lock); +#endif + } + + return; +} + +static char *get_cache_value(const char *name, apr_time_t t, char *key, + apr_pool_t *p) +{ + cachedmap *map; + char *val = NULL; + + if (cachep) { +#if APR_HAS_THREADS + apr_thread_mutex_lock(cachep->lock); +#endif + map = apr_hash_get(cachep->maps, name, APR_HASH_KEY_STRING); + + if (map) { + /* if this map is outdated, forget it. */ + if (map->mtime != t) { + apr_pool_clear(map->pool); + map->entries = apr_hash_make(map->pool); + map->mtime = t; + } + else { + val = apr_hash_get(map->entries, key, APR_HASH_KEY_STRING); + if (val) { + /* copy the cached value into the supplied pool, + * where it belongs (r->pool usually) + */ + val = apr_pstrdup(p, val); + } + } + } + +#if APR_HAS_THREADS + apr_thread_mutex_unlock(cachep->lock); +#endif + } + + return val; +} + +static int init_cache(apr_pool_t *p) +{ + cachep = apr_palloc(p, sizeof(cache)); + if (apr_pool_create(&cachep->pool, p) != APR_SUCCESS) { + cachep = NULL; /* turns off cache */ + return 0; + } + apr_pool_tag(cachep->pool, "rewrite_cachep"); + + cachep->maps = apr_hash_make(cachep->pool); +#if APR_HAS_THREADS + (void)apr_thread_mutex_create(&(cachep->lock), APR_THREAD_MUTEX_DEFAULT, p); +#endif + + return 1; +} + + +/* + * +-------------------------------------------------------+ + * | | + * | Map Functions + * | | + * +-------------------------------------------------------+ + */ + +/* + * General Note: key is already a fresh string, created (expanded) just + * for the purpose to be passed in here. So one can modify key itself. + */ + +static char *rewrite_mapfunc_toupper(request_rec *r, char *key) +{ + ap_str_toupper(key); + + return key; +} + +static char *rewrite_mapfunc_tolower(request_rec *r, char *key) +{ + ap_str_tolower(key); + + return key; +} + +static char *rewrite_mapfunc_escape(request_rec *r, char *key) +{ + return ap_escape_uri(r->pool, key); +} + +static char *rewrite_mapfunc_unescape(request_rec *r, char *key) +{ + ap_unescape_url(key); + + return key; +} + +static char *select_random_value_part(request_rec *r, char *value) +{ + char *p = value; + unsigned n = 1; + + /* count number of distinct values */ + while ((p = ap_strchr(p, '|')) != NULL) { + ++n; + ++p; + } + + if (n > 1) { + n = ap_random_pick(1, n); + + /* extract it from the whole string */ + while (--n && (value = ap_strchr(value, '|')) != NULL) { + ++value; + } + + if (value) { /* should not be NULL, but ... */ + p = ap_strchr(value, '|'); + if (p) { + *p = '\0'; + } + } + } + + return value; +} + +/* child process code */ +static void rewrite_child_errfn(apr_pool_t *p, apr_status_t err, + const char *desc) +{ + ap_log_error(APLOG_MARK, APLOG_ERR, err, NULL, APLOGNO(00653) "%s", desc); +} + +static apr_status_t rewritemap_program_child(apr_pool_t *p, + const char *progname, char **argv, + const char *user, const char *group, + apr_file_t **fpout, + apr_file_t **fpin) +{ + apr_status_t rc; + apr_procattr_t *procattr; + apr_proc_t *procnew; + + if ( APR_SUCCESS == (rc=apr_procattr_create(&procattr, p)) + && APR_SUCCESS == (rc=apr_procattr_io_set(procattr, APR_FULL_BLOCK, + APR_FULL_BLOCK, APR_NO_PIPE)) + && APR_SUCCESS == (rc=apr_procattr_dir_set(procattr, + ap_make_dirstr_parent(p, argv[0]))) + && (!user || APR_SUCCESS == (rc=apr_procattr_user_set(procattr, user, ""))) + && (!group || APR_SUCCESS == (rc=apr_procattr_group_set(procattr, group))) + && APR_SUCCESS == (rc=apr_procattr_cmdtype_set(procattr, APR_PROGRAM)) + && APR_SUCCESS == (rc=apr_procattr_child_errfn_set(procattr, + rewrite_child_errfn)) + && APR_SUCCESS == (rc=apr_procattr_error_check_set(procattr, 1))) { + + procnew = apr_pcalloc(p, sizeof(*procnew)); + rc = apr_proc_create(procnew, argv[0], (const char **)argv, NULL, + procattr, p); + + if (rc == APR_SUCCESS) { + apr_pool_note_subprocess(p, procnew, APR_KILL_AFTER_TIMEOUT); + + if (fpin) { + (*fpin) = procnew->in; + } + + if (fpout) { + (*fpout) = procnew->out; + } + } + } + + return (rc); +} + +static apr_status_t run_rewritemap_programs(server_rec *s, apr_pool_t *p) +{ + rewrite_server_conf *conf; + apr_hash_index_t *hi; + apr_status_t rc; + + conf = ap_get_module_config(s->module_config, &rewrite_module); + + /* If the engine isn't turned on, + * don't even try to do anything. + */ + if (conf->state == ENGINE_DISABLED) { + return APR_SUCCESS; + } + + for (hi = apr_hash_first(p, conf->rewritemaps); hi; hi = apr_hash_next(hi)){ + apr_file_t *fpin = NULL; + apr_file_t *fpout = NULL; + rewritemap_entry *map; + void *val; + + apr_hash_this(hi, NULL, NULL, &val); + map = val; + + if (map->type != MAPTYPE_PRG) { + continue; + } + if (!(map->argv[0]) || !*(map->argv[0]) || map->fpin || map->fpout) { + continue; + } + + rc = rewritemap_program_child(p, map->argv[0], map->argv, + map->user, map->group, + &fpout, &fpin); + if (rc != APR_SUCCESS || fpin == NULL || fpout == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, rc, s, APLOGNO(00654) + "mod_rewrite: could not start RewriteMap " + "program %s", map->checkfile); + return rc; + } + map->fpin = fpin; + map->fpout = fpout; + } + + return APR_SUCCESS; +} + + +/* + * +-------------------------------------------------------+ + * | | + * | Lookup functions + * | | + * +-------------------------------------------------------+ + */ + +static char *lookup_map_txtfile(request_rec *r, const char *file, char *key) +{ + apr_file_t *fp = NULL; + char line[REWRITE_MAX_TXT_MAP_LINE + 1]; /* +1 for \0 */ + char *value, *keylast; + apr_status_t rv; + + if ((rv = apr_file_open(&fp, file, APR_READ|APR_BUFFERED, APR_OS_DEFAULT, + r->pool)) != APR_SUCCESS) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00655) + "mod_rewrite: can't open text RewriteMap file %s", file); + return NULL; + } + + keylast = key + strlen(key); + value = NULL; + while (apr_file_gets(line, sizeof(line), fp) == APR_SUCCESS) { + char *p, *c; + + /* ignore comments and lines starting with whitespaces */ + if (*line == '#' || apr_isspace(*line)) { + continue; + } + + p = line; + c = key; + while (c < keylast && *p == *c && !apr_isspace(*p)) { + ++p; + ++c; + } + + /* key doesn't match - ignore. */ + if (c != keylast || !apr_isspace(*p)) { + continue; + } + + /* jump to the value */ + while (apr_isspace(*p)) { + ++p; + } + + /* no value? ignore */ + if (!*p) { + continue; + } + + /* extract the value and return. */ + c = p; + while (*p && !apr_isspace(*p)) { + ++p; + } + value = apr_pstrmemdup(r->pool, c, p - c); + break; + } + apr_file_close(fp); + + return value; +} + +static char *lookup_map_dbmfile(request_rec *r, const char *file, + const char *dbmtype, char *key) +{ +#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7) + const apr_dbm_driver_t *driver; + const apu_err_t *err; +#endif + apr_dbm_t *dbmfp = NULL; + apr_datum_t dbmkey; + apr_datum_t dbmval; + char *value; + apr_status_t rv; + +#if APU_MAJOR_VERSION > 1 || (APU_MAJOR_VERSION == 1 && APU_MINOR_VERSION >= 7) + if ((rv = apr_dbm_get_driver(&driver, dbmtype, &err, + r->pool)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(10287) + "mod_rewrite: can't load DBM library '%s': %s", + err->reason, err->msg); + return NULL; + } + if ((rv = apr_dbm_open2(&dbmfp, driver, file, APR_DBM_READONLY, + APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00656) + "mod_rewrite: can't open DBM RewriteMap %s", file); + return NULL; + } +#else + if ((rv = apr_dbm_open_ex(&dbmfp, dbmtype, file, APR_DBM_READONLY, + APR_OS_DEFAULT, r->pool)) != APR_SUCCESS) + { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00656) + "mod_rewrite: can't open DBM RewriteMap %s", file); + return NULL; + } +#endif + + dbmkey.dptr = key; + dbmkey.dsize = strlen(key); + + if (apr_dbm_fetch(dbmfp, dbmkey, &dbmval) == APR_SUCCESS && dbmval.dptr) { + value = apr_pstrmemdup(r->pool, dbmval.dptr, dbmval.dsize); + } + else { + value = NULL; + } + + apr_dbm_close(dbmfp); + + return value; +} +static char *lookup_map_dbd(request_rec *r, char *key, const char *label) +{ + apr_status_t rv; + apr_dbd_prepared_t *stmt; + const char *errmsg; + apr_dbd_results_t *res = NULL; + apr_dbd_row_t *row = NULL; + char *ret = NULL; + int n = 0; + ap_dbd_t *db = dbd_acquire(r); + + if (db == NULL) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02963) + "rewritemap: No db handle available! " + "Check your database access"); + return NULL; + } + + stmt = apr_hash_get(db->prepared, label, APR_HASH_KEY_STRING); + + rv = apr_dbd_pvselect(db->driver, r->pool, db->handle, &res, + stmt, 0, key, NULL); + if (rv != 0) { + errmsg = apr_dbd_error(db->driver, db->handle, rv); + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00657) + "rewritemap: error %s querying for %s", errmsg, key); + return NULL; + } + while ((rv = apr_dbd_get_row(db->driver, r->pool, res, &row, -1)) == 0) { + ++n; + if (ret == NULL) { + ret = apr_pstrdup(r->pool, + apr_dbd_get_entry(db->driver, row, 0)); + } + else { + /* randomise crudely amongst multiple results */ + if ((double)rand() < (double)RAND_MAX/(double)n) { + ret = apr_pstrdup(r->pool, + apr_dbd_get_entry(db->driver, row, 0)); + } + } + } + if (rv != -1) { + errmsg = apr_dbd_error(db->driver, db->handle, rv); + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00658) + "rewritemap: error %s looking up %s", errmsg, key); + } + switch (n) { + case 0: + return NULL; + case 1: + return ret; + default: + /* what's a fair rewritelog level for this? */ + rewritelog((r, 3, NULL, "Multiple values found for %s", key)); + return ret; + } +} + +static char *lookup_map_program(request_rec *r, apr_file_t *fpin, + apr_file_t *fpout, char *key) +{ + char *buf; + char c; + apr_size_t i, nbytes, combined_len = 0; + apr_status_t rv; + const char *eol = APR_EOL_STR; + apr_size_t eolc = 0; + int found_nl = 0; + result_list *buflist = NULL, *curbuf = NULL; + +#ifndef NO_WRITEV + struct iovec iova[2]; + apr_size_t niov; +#endif + + /* when `RewriteEngine off' was used in the per-server + * context then the rewritemap-programs were not spawned. + * In this case using such a map (usually in per-dir context) + * is useless because it is not available. + * + * newlines in the key leave bytes in the pipe and cause + * bad things to happen (next map lookup will use the chars + * after the \n instead of the new key etc etc - in other words, + * the Rewritemap falls out of sync with the requests). + */ + if (fpin == NULL || fpout == NULL || ap_strchr(key, '\n')) { + return NULL; + } + + /* take the lock */ + if (rewrite_mapr_lock_acquire) { + rv = apr_global_mutex_lock(rewrite_mapr_lock_acquire); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00659) + "apr_global_mutex_lock(rewrite_mapr_lock_acquire) " + "failed"); + return NULL; /* Maybe this should be fatal? */ + } + } + + /* write out the request key */ +#ifdef NO_WRITEV + nbytes = strlen(key); + /* XXX: error handling */ + apr_file_write_full(fpin, key, nbytes, NULL); + nbytes = 1; + apr_file_write_full(fpin, "\n", nbytes, NULL); +#else + iova[0].iov_base = key; + iova[0].iov_len = strlen(key); + iova[1].iov_base = "\n"; + iova[1].iov_len = 1; + + niov = 2; + /* XXX: error handling */ + apr_file_writev_full(fpin, iova, niov, &nbytes); +#endif + + buf = apr_palloc(r->pool, REWRITE_PRG_MAP_BUF + 1); + + /* read in the response value */ + nbytes = 1; + apr_file_read(fpout, &c, &nbytes); + do { + i = 0; + while (nbytes == 1 && (i < REWRITE_PRG_MAP_BUF)) { + if (c == eol[eolc]) { + if (!eol[++eolc]) { + /* remove eol from the buffer */ + --eolc; + if (i < eolc) { + curbuf->len -= eolc-i; + i = 0; + } + else { + i -= eolc; + } + ++found_nl; + break; + } + } + + /* only partial (invalid) eol sequence -> reset the counter */ + else if (eolc) { + eolc = 0; + } + + /* catch binary mode, e.g. on Win32 */ + else if (c == '\n') { + ++found_nl; + break; + } + + buf[i++] = c; + apr_file_read(fpout, &c, &nbytes); + } + + /* well, if there wasn't a newline yet, we need to read further */ + if (buflist || (nbytes == 1 && !found_nl)) { + if (!buflist) { + curbuf = buflist = apr_palloc(r->pool, sizeof(*buflist)); + } + else if (i) { + curbuf->next = apr_palloc(r->pool, sizeof(*buflist)); + curbuf = curbuf->next; + + } + curbuf->next = NULL; + + if (i) { + curbuf->string = buf; + curbuf->len = i; + combined_len += i; + buf = apr_palloc(r->pool, REWRITE_PRG_MAP_BUF); + } + + if (nbytes == 1 && !found_nl) { + continue; + } + } + + break; + } while (1); + + /* concat the stuff */ + if (buflist) { + char *p; + + p = buf = apr_palloc(r->pool, combined_len + 1); /* \0 */ + while (buflist) { + if (buflist->len) { + memcpy(p, buflist->string, buflist->len); + p += buflist->len; + } + buflist = buflist->next; + } + *p = '\0'; + i = combined_len; + } + else { + buf[i] = '\0'; + } + + /* give the lock back */ + if (rewrite_mapr_lock_acquire) { + rv = apr_global_mutex_unlock(rewrite_mapr_lock_acquire); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00660) + "apr_global_mutex_unlock(rewrite_mapr_lock_acquire) " + "failed"); + return NULL; /* Maybe this should be fatal? */ + } + } + + /* catch the "failed" case */ + if (i == 4 && !strcasecmp(buf, "NULL")) { + return NULL; + } + + return buf; +} + +/* + * generic map lookup + */ +static char *lookup_map(request_rec *r, char *name, char *key) +{ + rewrite_server_conf *conf; + rewritemap_entry *s; + char *value; + apr_finfo_t st; + apr_status_t rv; + + /* get map configuration */ + conf = ap_get_module_config(r->server->module_config, &rewrite_module); + s = apr_hash_get(conf->rewritemaps, name, APR_HASH_KEY_STRING); + + /* map doesn't exist */ + if (!s) { + return NULL; + } + + switch (s->type) { + /* + * Text file map (perhaps random) + */ + case MAPTYPE_RND: + case MAPTYPE_TXT: + rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00661) + "mod_rewrite: can't access text RewriteMap file %s", + s->checkfile); + return NULL; + } + + value = get_cache_value(s->cachename, st.mtime, key, r->pool); + if (!value) { + rewritelog((r, 6, NULL, + "cache lookup FAILED, forcing new map lookup")); + + value = lookup_map_txtfile(r, s->datafile, key); + if (!value) { + rewritelog((r, 5, NULL, "map lookup FAILED: map=%s[txt] key=%s", + name, key)); + set_cache_value(s->cachename, st.mtime, key, ""); + return NULL; + } + + rewritelog((r, 5, NULL,"map lookup OK: map=%s[txt] key=%s -> val=%s", + name, key, value)); + set_cache_value(s->cachename, st.mtime, key, value); + } + else { + rewritelog((r,5,NULL,"cache lookup OK: map=%s[txt] key=%s -> val=%s", + name, key, value)); + } + + if (s->type == MAPTYPE_RND && *value) { + value = select_random_value_part(r, value); + rewritelog((r, 5, NULL, "randomly chosen the subvalue `%s'",value)); + } + + return *value ? value : NULL; + + /* + * DBM file map + */ + case MAPTYPE_DBM: + rv = apr_stat(&st, s->checkfile, APR_FINFO_MIN, r->pool); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00662) + "mod_rewrite: can't access DBM RewriteMap file %s", + s->checkfile); + } + else if(s->checkfile2 != NULL) { + apr_finfo_t st2; + + rv = apr_stat(&st2, s->checkfile2, APR_FINFO_MIN, r->pool); + if (rv != APR_SUCCESS) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(00663) + "mod_rewrite: can't access DBM RewriteMap " + "file %s", s->checkfile2); + } + else if(st2.mtime > st.mtime) { + st.mtime = st2.mtime; + } + } + if(rv != APR_SUCCESS) { + return NULL; + } + + value = get_cache_value(s->cachename, st.mtime, key, r->pool); + if (!value) { + rewritelog((r, 6, NULL, + "cache lookup FAILED, forcing new map lookup")); + + value = lookup_map_dbmfile(r, s->datafile, s->dbmtype, key); + if (!value) { + rewritelog((r, 5, NULL, "map lookup FAILED: map=%s[dbm] key=%s", + name, key)); + set_cache_value(s->cachename, st.mtime, key, ""); + return NULL; + } + + rewritelog((r, 5, NULL, "map lookup OK: map=%s[dbm] key=%s -> " + "val=%s", name, key, value)); + + set_cache_value(s->cachename, st.mtime, key, value); + return value; + } + + rewritelog((r, 5, NULL, "cache lookup OK: map=%s[dbm] key=%s -> val=%s", + name, key, value)); + return *value ? value : NULL; + + /* + * SQL map without cache + */ + case MAPTYPE_DBD: + value = lookup_map_dbd(r, key, s->dbdq); + if (!value) { + rewritelog((r, 5, NULL, "SQL map lookup FAILED: map %s key=%s", + name, key)); + return NULL; + } + + rewritelog((r, 5, NULL, "SQL map lookup OK: map %s key=%s, val=%s", + name, key, value)); + + return value; + + /* + * SQL map with cache + */ + case MAPTYPE_DBD_CACHE: + value = get_cache_value(s->cachename, 0, key, r->pool); + if (!value) { + rewritelog((r, 6, NULL, + "cache lookup FAILED, forcing new map lookup")); + + value = lookup_map_dbd(r, key, s->dbdq); + if (!value) { + rewritelog((r, 5, NULL, "SQL map lookup FAILED: map %s key=%s", + name, key)); + set_cache_value(s->cachename, 0, key, ""); + return NULL; + } + + rewritelog((r, 5, NULL, "SQL map lookup OK: map %s key=%s, val=%s", + name, key, value)); + + set_cache_value(s->cachename, 0, key, value); + return value; + } + + rewritelog((r, 5, NULL, "cache lookup OK: map=%s[SQL] key=%s, val=%s", + name, key, value)); + return *value ? value : NULL; + + /* + * Program file map + */ + case MAPTYPE_PRG: + value = lookup_map_program(r, s->fpin, s->fpout, key); + if (!value) { + rewritelog((r, 5,NULL,"map lookup FAILED: map=%s key=%s", name, + key)); + return NULL; + } + + rewritelog((r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s", + name, key, value)); + return value; + + /* + * Internal Map + */ + case MAPTYPE_INT: + value = s->func(r, key); + if (!value) { + rewritelog((r, 5,NULL,"map lookup FAILED: map=%s key=%s", name, + key)); + return NULL; + } + + rewritelog((r, 5, NULL, "map lookup OK: map=%s key=%s -> val=%s", + name, key, value)); + return value; + } + + return NULL; +} + +/* + * lookup a HTTP header and set VARY note + */ +static const char *lookup_header(const char *name, rewrite_ctx *ctx) +{ + const char *val = apr_table_get(ctx->r->headers_in, name); + + /* Skip the 'Vary: Host' header combination + * as indicated in rfc7231 section-7.1.4 + */ + if (val && strcasecmp(name, "Host") != 0) { + ctx->vary_this = ctx->vary_this + ? apr_pstrcat(ctx->r->pool, ctx->vary_this, ", ", + name, NULL) + : apr_pstrdup(ctx->r->pool, name); + } + + return val; +} + +/* + * lookahead helper function + * Determine the correct URI path in perdir context + */ +static APR_INLINE const char *la_u(rewrite_ctx *ctx) +{ + rewrite_perdir_conf *conf; + + if (*ctx->uri == '/') { + return ctx->uri; + } + + conf = ap_get_module_config(ctx->r->per_dir_config, &rewrite_module); + + return apr_pstrcat(ctx->r->pool, conf->baseurl + ? conf->baseurl : conf->directory, + ctx->uri, NULL); +} + +/* + * generic variable lookup + */ +static char *lookup_variable(char *var, rewrite_ctx *ctx) +{ + const char *result; + request_rec *r = ctx->r; + apr_size_t varlen = strlen(var); + + /* fast exit */ + if (varlen < 4) { + return ""; + } + + result = NULL; + + /* fast tests for variable length variables (sic) first */ + if (var[3] == ':') { + if (var[4] && !strncasecmp(var, "ENV", 3)) { + var += 4; + result = apr_table_get(r->notes, var); + + if (!result) { + result = apr_table_get(r->subprocess_env, var); + } + if (!result) { + result = getenv(var); + } + } + else if (var[4] && !strncasecmp(var, "SSL", 3)) { + result = ap_ssl_var_lookup(r->pool, r->server, r->connection, r, + var + 4); + } + } + else if (var[4] == ':') { + if (var[5]) { + request_rec *rr; + const char *path; + + if (!strncasecmp(var, "HTTP", 4)) { + result = lookup_header(var+5, ctx); + } + else if (!strncasecmp(var, "LA-U", 4)) { + if (ctx->uri && subreq_ok(r)) { + path = ctx->perdir ? la_u(ctx) : ctx->uri; + rr = ap_sub_req_lookup_uri(path, r, NULL); + ctx->r = rr; + result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx)); + ctx->r = r; + ap_destroy_sub_req(rr); + + rewritelog((r, 5, ctx->perdir, "lookahead: path=%s var=%s " + "-> val=%s", path, var+5, result)); + + return (char *)result; + } + } + else if (!strncasecmp(var, "LA-F", 4)) { + if (ctx->uri && subreq_ok(r)) { + path = ctx->uri; + if (ctx->perdir && *path == '/') { + /* sigh, the user wants a file based subrequest, but + * we can't do one, since we don't know what the file + * path is! In this case behave like LA-U. + */ + rr = ap_sub_req_lookup_uri(path, r, NULL); + } + else { + if (ctx->perdir) { + rewrite_perdir_conf *conf; + + conf = ap_get_module_config(r->per_dir_config, + &rewrite_module); + + path = apr_pstrcat(r->pool, conf->directory, path, + NULL); + } + + rr = ap_sub_req_lookup_file(path, r, NULL); + } + + ctx->r = rr; + result = apr_pstrdup(r->pool, lookup_variable(var+5, ctx)); + ctx->r = r; + ap_destroy_sub_req(rr); + + rewritelog((r, 5, ctx->perdir, "lookahead: path=%s var=%s " + "-> val=%s", path, var+5, result)); + + return (char *)result; + } + } + } + } + + /* well, do it the hard way */ + else { + apr_time_exp_t tm; + + /* can't do this above, because of the getenv call */ + ap_str_toupper(var); + + switch (varlen) { + case 4: + if (!strcmp(var, "TIME")) { + apr_time_exp_lt(&tm, apr_time_now()); + result = apr_psprintf(r->pool, "%04d%02d%02d%02d%02d%02d", + tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec); + rewritelog((r, 1, ctx->perdir, "RESULT='%s'", result)); + return (char *)result; + } + else if (!strcmp(var, "IPV6")) { + int flag = FALSE; +#if APR_HAVE_IPV6 + apr_sockaddr_t *addr = r->useragent_addr; + flag = (addr->family == AF_INET6 && + !IN6_IS_ADDR_V4MAPPED((struct in6_addr *)addr->ipaddr_ptr)); + rewritelog((r, 1, ctx->perdir, "IPV6='%s'", flag ? "on" : "off")); +#else + rewritelog((r, 1, ctx->perdir, "IPV6='off' (IPv6 is not enabled)")); +#endif + result = (flag ? "on" : "off"); + } + break; + + case 5: + if (!strcmp(var, "HTTPS")) { + int flag = ap_ssl_conn_is_ssl(r->connection); + return apr_pstrdup(r->pool, flag ? "on" : "off"); + } + break; + + case 8: + switch (var[6]) { + case 'A': + if (!strcmp(var, "TIME_DAY")) { + apr_time_exp_lt(&tm, apr_time_now()); + return apr_psprintf(r->pool, "%02d", tm.tm_mday); + } + break; + + case 'E': + if (!strcmp(var, "TIME_SEC")) { + apr_time_exp_lt(&tm, apr_time_now()); + return apr_psprintf(r->pool, "%02d", tm.tm_sec); + } + break; + + case 'I': + if (!strcmp(var, "TIME_MIN")) { + apr_time_exp_lt(&tm, apr_time_now()); + return apr_psprintf(r->pool, "%02d", tm.tm_min); + } + break; + + case 'O': + if (!strcmp(var, "TIME_MON")) { + apr_time_exp_lt(&tm, apr_time_now()); + return apr_psprintf(r->pool, "%02d", tm.tm_mon+1); + } + break; + } + break; + + case 9: + switch (var[7]) { + case 'A': + if (var[8] == 'Y' && !strcmp(var, "TIME_WDAY")) { + apr_time_exp_lt(&tm, apr_time_now()); + return apr_psprintf(r->pool, "%d", tm.tm_wday); + } + else if (!strcmp(var, "TIME_YEAR")) { + apr_time_exp_lt(&tm, apr_time_now()); + return apr_psprintf(r->pool, "%04d", tm.tm_year+1900); + } + break; + + case 'E': + if (!strcmp(var, "IS_SUBREQ")) { + result = (r->main ? "true" : "false"); + } + break; + + case 'F': + if (!strcmp(var, "PATH_INFO")) { + result = r->path_info; + } + break; + + case 'P': + if (!strcmp(var, "AUTH_TYPE")) { + result = r->ap_auth_type; + } + break; + + case 'S': + if (!strcmp(var, "HTTP_HOST")) { + result = lookup_header("Host", ctx); + } + break; + + case 'U': + if (!strcmp(var, "TIME_HOUR")) { + apr_time_exp_lt(&tm, apr_time_now()); + return apr_psprintf(r->pool, "%02d", tm.tm_hour); + } + break; + } + break; + + case 11: + switch (var[8]) { + case 'A': + if (!strcmp(var, "SERVER_NAME")) { + result = ap_get_server_name_for_url(r); + } + break; + + case 'D': + if (*var == 'R' && !strcmp(var, "REMOTE_ADDR")) { + result = r->useragent_ip; + } + else if (!strcmp(var, "SERVER_ADDR")) { + result = r->connection->local_ip; + } + break; + + case 'E': + if (*var == 'H' && !strcmp(var, "HTTP_ACCEPT")) { + result = lookup_header("Accept", ctx); + } + else if (!strcmp(var, "THE_REQUEST")) { + result = r->the_request; + } + break; + + case 'I': + if (!strcmp(var, "API_VERSION")) { + return apr_psprintf(r->pool, "%d:%d", + MODULE_MAGIC_NUMBER_MAJOR, + MODULE_MAGIC_NUMBER_MINOR); + } + break; + + case 'K': + if (!strcmp(var, "HTTP_COOKIE")) { + result = lookup_header("Cookie", ctx); + } + break; + + case 'O': + if (*var == 'S' && !strcmp(var, "SERVER_PORT")) { + return apr_psprintf(r->pool, "%u", ap_get_server_port(r)); + } + else if (var[7] == 'H' && !strcmp(var, "REMOTE_HOST")) { + result = ap_get_remote_host(r->connection,r->per_dir_config, + REMOTE_NAME, NULL); + } + else if (!strcmp(var, "REMOTE_PORT")) { + return apr_itoa(r->pool, r->useragent_addr->port); + } + break; + + case 'S': + if (*var == 'R' && !strcmp(var, "REMOTE_USER")) { + result = r->user; + } + else if (!strcmp(var, "SCRIPT_USER")) { + result = "<unknown>"; + if (r->finfo.valid & APR_FINFO_USER) { + apr_uid_name_get((char **)&result, r->finfo.user, + r->pool); + } + } + break; + + case 'U': + if (!strcmp(var, "REQUEST_URI")) { + result = r->uri; + } + break; + } + break; + + case 12: + switch (var[3]) { + case 'I': + if (!strcmp(var, "SCRIPT_GROUP")) { + result = "<unknown>"; + if (r->finfo.valid & APR_FINFO_GROUP) { + apr_gid_name_get((char **)&result, r->finfo.group, + r->pool); + } + } + break; + + case 'O': + if (!strcmp(var, "REMOTE_IDENT")) { + result = ap_get_remote_logname(r); + } + break; + + case 'P': + if (!strcmp(var, "HTTP_REFERER")) { + result = lookup_header("Referer", ctx); + } + break; + + case 'R': + if (!strcmp(var, "QUERY_STRING")) { + result = r->args; + } + break; + + case 'V': + if (!strcmp(var, "SERVER_ADMIN")) { + result = r->server->server_admin; + } + break; + } + break; + + case 13: + if (!strcmp(var, "DOCUMENT_ROOT")) { + result = ap_document_root(r); + } + break; + + case 14: + if (*var == 'H' && !strcmp(var, "HTTP_FORWARDED")) { + result = lookup_header("Forwarded", ctx); + } + else if (*var == 'C' && !strcmp(var, "CONTEXT_PREFIX")) { + result = ap_context_prefix(r); + } + else if (var[8] == 'M' && !strcmp(var, "REQUEST_METHOD")) { + result = r->method; + } + else if (!strcmp(var, "REQUEST_SCHEME")) { + result = ap_http_scheme(r); + } + break; + + case 15: + switch (var[7]) { + case 'E': + if (!strcmp(var, "HTTP_USER_AGENT")) { + result = lookup_header("User-Agent", ctx); + } + break; + + case 'F': + if (!strcmp(var, "SCRIPT_FILENAME")) { + result = r->filename; /* same as request_filename (16) */ + } + break; + + case 'P': + if (!strcmp(var, "SERVER_PROTOCOL")) { + result = r->protocol; + } + break; + + case 'S': + if (!strcmp(var, "SERVER_SOFTWARE")) { + result = ap_get_server_banner(); + } + break; + } + break; + + case 16: + if (*var == 'C' && !strcmp(var, "CONN_REMOTE_ADDR")) { + result = r->connection->client_ip; + } + else if (!strcmp(var, "REQUEST_FILENAME")) { + result = r->filename; /* same as script_filename (15) */ + } + break; + + case 21: + if (!strcmp(var, "HTTP_PROXY_CONNECTION")) { + result = lookup_header("Proxy-Connection", ctx); + } + else if (!strcmp(var, "CONTEXT_DOCUMENT_ROOT")) { + result = ap_context_document_root(r); + } + break; + } + } + + return apr_pstrdup(r->pool, result ? result : ""); +} + + +/* + * +-------------------------------------------------------+ + * | | + * | Expansion functions + * | | + * +-------------------------------------------------------+ + */ + +/* + * Bracketed expression handling + * s points after the opening bracket + */ +static APR_INLINE char *find_closing_curly(char *s) +{ + unsigned depth; + + for (depth = 1; *s; ++s) { + if (*s == RIGHT_CURLY && --depth == 0) { + return s; + } + else if (*s == LEFT_CURLY) { + ++depth; + } + } + + return NULL; +} + +static APR_INLINE char *find_char_in_curlies(char *s, int c) +{ + unsigned depth; + + for (depth = 1; *s; ++s) { + if (*s == c && depth == 1) { + return s; + } + else if (*s == RIGHT_CURLY && --depth == 0) { + return NULL; + } + else if (*s == LEFT_CURLY) { + ++depth; + } + } + + return NULL; +} + +/* perform all the expansions on the input string + * putting the result into a new string + * + * for security reasons this expansion must be performed in a + * single pass, otherwise an attacker can arrange for the result + * of an earlier expansion to include expansion specifiers that + * are interpreted by a later expansion, producing results that + * were not intended by the administrator. + */ +static char *do_expand(char *input, rewrite_ctx *ctx, rewriterule_entry *entry) +{ + result_list *result, *current; + result_list sresult[SMALL_EXPANSION]; + unsigned spc = 0; + apr_size_t span, inputlen, outlen; + char *p, *c; + apr_pool_t *pool = ctx->r->pool; + + span = strcspn(input, "\\$%"); + inputlen = strlen(input); + + /* fast exit */ + if (inputlen == span) { + return apr_pstrmemdup(pool, input, inputlen); + } + + /* well, actually something to do */ + result = current = &(sresult[spc++]); + + p = input + span; + current->next = NULL; + current->string = input; + current->len = span; + outlen = span; + + /* loop for specials */ + do { + /* prepare next entry */ + if (current->len) { + current->next = (spc < SMALL_EXPANSION) + ? &(sresult[spc++]) + : (result_list *)apr_palloc(pool, + sizeof(result_list)); + current = current->next; + current->next = NULL; + current->len = 0; + } + + /* escaped character */ + if (*p == '\\') { + current->len = 1; + ++outlen; + if (!p[1]) { + current->string = p; + break; + } + else { + current->string = ++p; + ++p; + } + } + + /* variable or map lookup */ + else if (p[1] == '{') { + char *endp; + + endp = find_closing_curly(p+2); + if (!endp) { + current->len = 2; + current->string = p; + outlen += 2; + p += 2; + } + + /* variable lookup */ + else if (*p == '%') { + p = lookup_variable(apr_pstrmemdup(pool, p+2, endp-p-2), ctx); + + span = strlen(p); + current->len = span; + current->string = p; + outlen += span; + p = endp + 1; + } + + /* map lookup */ + else { /* *p == '$' */ + char *key; + + /* + * To make rewrite maps useful, the lookup key and + * default values must be expanded, so we make + * recursive calls to do the work. For security + * reasons we must never expand a string that includes + * verbatim data from the network. The recursion here + * isn't a problem because the result of expansion is + * only passed to lookup_map() so it cannot be + * re-expanded, only re-looked-up. Another way of + * looking at it is that the recursion is entirely + * driven by the syntax of the nested curly brackets. + */ + + key = find_char_in_curlies(p+2, ':'); + if (!key) { + current->len = 2; + current->string = p; + outlen += 2; + p += 2; + } + else { + char *map, *dflt; + + map = apr_pstrmemdup(pool, p+2, endp-p-2); + key = map + (key-p-2); + *key++ = '\0'; + dflt = find_char_in_curlies(key, '|'); + if (dflt) { + *dflt++ = '\0'; + } + + /* reuse of key variable as result */ + key = lookup_map(ctx->r, map, do_expand(key, ctx, entry)); + + if (!key && dflt && *dflt) { + key = do_expand(dflt, ctx, entry); + } + + if (key) { + span = strlen(key); + current->len = span; + current->string = key; + outlen += span; + } + + p = endp + 1; + } + } + } + + /* backreference */ + else if (apr_isdigit(p[1])) { + int n = p[1] - '0'; + backrefinfo *bri = (*p == '$') ? &ctx->briRR : &ctx->briRC; + + /* see ap_pregsub() in server/util.c */ + if (bri->source && n < AP_MAX_REG_MATCH + && bri->regmatch[n].rm_eo > bri->regmatch[n].rm_so) { + span = bri->regmatch[n].rm_eo - bri->regmatch[n].rm_so; + if (entry && (entry->flags & RULEFLAG_ESCAPEBACKREF)) { + /* escape the backreference */ + char *tmp2, *tmp; + tmp = apr_pstrmemdup(pool, bri->source + bri->regmatch[n].rm_so, span); + tmp2 = escape_backref(pool, tmp, entry->escapes, entry->flags & RULEFLAG_ESCAPENOPLUS); + rewritelog((ctx->r, 5, ctx->perdir, "escaping backreference '%s' to '%s'", + tmp, tmp2)); + + current->len = span = strlen(tmp2); + current->string = tmp2; + } else { + current->len = span; + current->string = bri->source + bri->regmatch[n].rm_so; + } + + outlen += span; + } + + p += 2; + } + + /* not for us, just copy it */ + else { + current->len = 1; + current->string = p++; + ++outlen; + } + + /* check the remainder */ + if (*p && (span = strcspn(p, "\\$%")) > 0) { + if (current->len) { + current->next = (spc < SMALL_EXPANSION) + ? &(sresult[spc++]) + : (result_list *)apr_palloc(pool, + sizeof(result_list)); + current = current->next; + current->next = NULL; + } + + current->len = span; + current->string = p; + p += span; + outlen += span; + } + + } while (p < input+inputlen); + + /* assemble result */ + c = p = apr_palloc(pool, outlen + 1); /* don't forget the \0 */ + do { + if (result->len) { + ap_assert(c+result->len <= p+outlen); /* XXX: can be removed after + * extensive testing and + * review + */ + memcpy(c, result->string, result->len); + c += result->len; + } + result = result->next; + } while (result); + + p[outlen] = '\0'; + + return p; +} + +/* + * perform all the expansions on the environment variables + */ +static void do_expand_env(data_item *env, rewrite_ctx *ctx) +{ + char *name, *val; + + while (env) { + name = do_expand(env->data, ctx, NULL); + if (*name == '!') { + name++; + apr_table_unset(ctx->r->subprocess_env, name); + rewritelog((ctx->r, 5, NULL, "unsetting env variable '%s'", name)); + } + else { + if ((val = ap_strchr(name, ':')) != NULL) { + *val++ = '\0'; + } else { + val = ""; + } + + apr_table_set(ctx->r->subprocess_env, name, val); + rewritelog((ctx->r, 5, NULL, "setting env variable '%s' to '%s'", + name, val)); + } + + env = env->next; + } + + return; +} + +/* + * perform all the expansions on the cookies + * + * TODO: use cached time similar to how logging does it + */ +static void add_cookie(request_rec *r, char *s) +{ + char *var; + char *val; + char *domain; + char *expires; + char *path; + char *secure; + char *httponly; + char *samesite; + + char *tok_cntx; + char *cookie; + /* long-standing default, but can't use ':' in a cookie */ + const char *sep = ":"; + + /* opt-in to ; separator if first character is a ; */ + if (s && *s == ';') { + sep = ";"; + s++; + } + + var = apr_strtok(s, sep, &tok_cntx); + val = apr_strtok(NULL, sep, &tok_cntx); + domain = apr_strtok(NULL, sep, &tok_cntx); + + if (var && val && domain) { + request_rec *rmain = r; + char *notename; + void *data; + + while (rmain->main) { + rmain = rmain->main; + } + + notename = apr_pstrcat(rmain->pool, var, "_rewrite", NULL); + apr_pool_userdata_get(&data, notename, rmain->pool); + if (!data) { + char *exp_time = NULL; + + expires = apr_strtok(NULL, sep, &tok_cntx); + path = expires ? apr_strtok(NULL, sep, &tok_cntx) : NULL; + secure = path ? apr_strtok(NULL, sep, &tok_cntx) : NULL; + httponly = secure ? apr_strtok(NULL, sep, &tok_cntx) : NULL; + samesite = httponly ? apr_strtok(NULL, sep, &tok_cntx) : NULL; + + if (expires) { + apr_time_exp_t tms; + long exp_min; + + exp_min = atol(expires); + if (exp_min) { + apr_time_exp_gmt(&tms, r->request_time + + apr_time_from_sec((60 * exp_min))); + exp_time = apr_psprintf(r->pool, "%s, %.2d-%s-%.4d " + "%.2d:%.2d:%.2d GMT", + apr_day_snames[tms.tm_wday], + tms.tm_mday, + apr_month_snames[tms.tm_mon], + tms.tm_year+1900, + tms.tm_hour, tms.tm_min, tms.tm_sec); + } + } + + cookie = apr_pstrcat(rmain->pool, + var, "=", val, + "; path=", path ? path : "/", + "; domain=", domain, + expires ? (exp_time ? "; expires=" : "") + : NULL, + expires ? (exp_time ? exp_time : "") + : NULL, + (secure && (!ap_cstr_casecmp(secure, "true") + || !strcmp(secure, "1") + || !ap_cstr_casecmp(secure, + "secure"))) ? + "; secure" : NULL, + (httponly && (!ap_cstr_casecmp(httponly, "true") + || !strcmp(httponly, "1") + || !ap_cstr_casecmp(httponly, + "HttpOnly"))) ? + "; HttpOnly" : NULL, + NULL); + + if (samesite && strcmp(samesite, "0") && ap_cstr_casecmp(samesite,"false")) { + cookie = apr_pstrcat(rmain->pool, cookie, "; SameSite=", + samesite, NULL); + } + + apr_table_addn(rmain->err_headers_out, "Set-Cookie", cookie); + apr_pool_userdata_set("set", notename, NULL, rmain->pool); + rewritelog((rmain, 5, NULL, "setting cookie '%s'", cookie)); + } + else { + rewritelog((rmain, 5, NULL, "skipping already set cookie '%s'", + var)); + } + } + + return; +} + +static void do_expand_cookie(data_item *cookie, rewrite_ctx *ctx) +{ + while (cookie) { + add_cookie(ctx->r, do_expand(cookie->data, ctx, NULL)); + cookie = cookie->next; + } + + return; +} + +#if APR_HAS_USER +/* + * Expand tilde-paths (/~user) through Unix /etc/passwd + * database information (or other OS-specific database) + */ +static char *expand_tildepaths(request_rec *r, char *uri) +{ + if (uri && *uri == '/' && uri[1] == '~') { + char *p, *user; + + p = user = uri + 2; + while (*p && *p != '/') { + ++p; + } + + if (p > user) { + char *homedir; + + user = apr_pstrmemdup(r->pool, user, p-user); + if (apr_uid_homepath_get(&homedir, user, r->pool) == APR_SUCCESS) { + if (*p) { + /* reuse of user variable */ + user = homedir + strlen(homedir) - 1; + if (user >= homedir && *user == '/') { + *user = '\0'; + } + + return apr_pstrcat(r->pool, homedir, p, NULL); + } + else { + return homedir; + } + } + } + } + + return uri; +} +#endif /* if APR_HAS_USER */ + + +/* + * +-------------------------------------------------------+ + * | | + * | rewriting lockfile support + * | | + * +-------------------------------------------------------+ + */ + +static apr_status_t rewritelock_create(server_rec *s, apr_pool_t *p) +{ + apr_status_t rc; + + /* create the lockfile */ + rc = ap_global_mutex_create(&rewrite_mapr_lock_acquire, NULL, + rewritemap_mutex_type, NULL, s, p, 0); + if (rc != APR_SUCCESS) { + return rc; + } + + return APR_SUCCESS; +} + +static apr_status_t rewritelock_remove(void *data) +{ + /* destroy the rewritelock */ + if (rewrite_mapr_lock_acquire) { + apr_global_mutex_destroy(rewrite_mapr_lock_acquire); + rewrite_mapr_lock_acquire = NULL; + } + return APR_SUCCESS; +} + + +/* + * +-------------------------------------------------------+ + * | | + * | configuration directive handling + * | | + * +-------------------------------------------------------+ + */ + +/* + * own command line parser for RewriteRule and RewriteCond, + * which doesn't have the '\\' problem. + * (returns true on error) + * + * XXX: what an inclined parser. Seems we have to leave it so + * for backwards compat. *sigh* + */ +static int parseargline(char *str, char **a1, char **a2, char **a2_end, char **a3) +{ + char quote; + + while (apr_isspace(*str)) { + ++str; + } + + /* + * determine first argument + */ + quote = (*str == '"' || *str == '\'') ? *str++ : '\0'; + *a1 = str; + + for (; *str; ++str) { + if ((apr_isspace(*str) && !quote) || (*str == quote)) { + break; + } + if (*str == '\\' && apr_isspace(str[1])) { + ++str; + continue; + } + } + + if (!*str) { + return 1; + } + *str++ = '\0'; + + while (apr_isspace(*str)) { + ++str; + } + + /* + * determine second argument + */ + quote = (*str == '"' || *str == '\'') ? *str++ : '\0'; + *a2 = str; + + for (; *str; ++str) { + if ((apr_isspace(*str) && !quote) || (*str == quote)) { + break; + } + if (*str == '\\' && apr_isspace(str[1])) { + ++str; + continue; + } + } + + if (!*str) { + *a3 = NULL; /* 3rd argument is optional */ + *a2_end = str; + return 0; + } + *a2_end = str; + *str++ = '\0'; + + while (apr_isspace(*str)) { + ++str; + } + + if (!*str) { + *a3 = NULL; /* 3rd argument is still optional */ + return 0; + } + + /* + * determine third argument + */ + quote = (*str == '"' || *str == '\'') ? *str++ : '\0'; + *a3 = str; + for (; *str; ++str) { + if ((apr_isspace(*str) && !quote) || (*str == quote)) { + break; + } + if (*str == '\\' && apr_isspace(str[1])) { + ++str; + continue; + } + } + *str = '\0'; + + return 0; +} + +static void *config_server_create(apr_pool_t *p, server_rec *s) +{ + rewrite_server_conf *a; + + a = (rewrite_server_conf *)apr_pcalloc(p, sizeof(rewrite_server_conf)); + + a->state = ENGINE_DISABLED; + a->options = OPTION_NONE; + a->rewritemaps = apr_hash_make(p); + a->rewriteconds = apr_array_make(p, 2, sizeof(rewritecond_entry)); + a->rewriterules = apr_array_make(p, 2, sizeof(rewriterule_entry)); + a->server = s; + + return (void *)a; +} + +static void *config_server_merge(apr_pool_t *p, void *basev, void *overridesv) +{ + rewrite_server_conf *a, *base, *overrides; + + a = (rewrite_server_conf *)apr_pcalloc(p, + sizeof(rewrite_server_conf)); + base = (rewrite_server_conf *)basev; + overrides = (rewrite_server_conf *)overridesv; + + a->state = (overrides->state_set == 0) ? base->state : overrides->state; + a->state_set = overrides->state_set || base->state_set; + a->options = (overrides->options_set == 0) ? base->options : overrides->options; + a->options_set = overrides->options_set || base->options_set; + + a->server = overrides->server; + + if (a->options & OPTION_INHERIT || + (base->options & OPTION_INHERIT_DOWN && + !(a->options & OPTION_IGNORE_INHERIT))) { + /* + * local directives override + * and anything else is inherited + */ + a->rewritemaps = apr_hash_overlay(p, overrides->rewritemaps, + base->rewritemaps); + a->rewriteconds = apr_array_append(p, overrides->rewriteconds, + base->rewriteconds); + a->rewriterules = apr_array_append(p, overrides->rewriterules, + base->rewriterules); + } + else if (a->options & OPTION_INHERIT_BEFORE || + (base->options & OPTION_INHERIT_DOWN_BEFORE && + !(a->options & OPTION_IGNORE_INHERIT))) { + /* + * local directives override + * and anything else is inherited (preserving order) + */ + a->rewritemaps = apr_hash_overlay(p, base->rewritemaps, + overrides->rewritemaps); + a->rewriteconds = apr_array_append(p, base->rewriteconds, + overrides->rewriteconds); + a->rewriterules = apr_array_append(p, base->rewriterules, + overrides->rewriterules); + } + else { + /* + * local directives override + * and anything else gets defaults + */ + a->rewritemaps = overrides->rewritemaps; + a->rewriteconds = overrides->rewriteconds; + a->rewriterules = overrides->rewriterules; + } + + return (void *)a; +} + +static void *config_perdir_create(apr_pool_t *p, char *path) +{ + rewrite_perdir_conf *a; + + a = (rewrite_perdir_conf *)apr_pcalloc(p, sizeof(rewrite_perdir_conf)); + + a->state = ENGINE_DISABLED; + a->options = OPTION_NONE; + a->baseurl = NULL; + a->rewriteconds = apr_array_make(p, 2, sizeof(rewritecond_entry)); + a->rewriterules = apr_array_make(p, 2, sizeof(rewriterule_entry)); + + if (path == NULL) { + a->directory = NULL; + } + else { + /* make sure it has a trailing slash */ + if (path[strlen(path)-1] == '/') { + a->directory = apr_pstrdup(p, path); + } + else { + a->directory = apr_pstrcat(p, path, "/", NULL); + } + } + + return (void *)a; +} + +static void *config_perdir_merge(apr_pool_t *p, void *basev, void *overridesv) +{ + rewrite_perdir_conf *a, *base, *overrides; + + a = (rewrite_perdir_conf *)apr_pcalloc(p, + sizeof(rewrite_perdir_conf)); + base = (rewrite_perdir_conf *)basev; + overrides = (rewrite_perdir_conf *)overridesv; + + a->state = (overrides->state_set == 0) ? base->state : overrides->state; + a->state_set = overrides->state_set || base->state_set; + a->options = (overrides->options_set == 0) ? base->options : overrides->options; + a->options_set = overrides->options_set || base->options_set; + + if (a->options & OPTION_MERGEBASE) { + a->baseurl = (overrides->baseurl_set == 0) ? base->baseurl : overrides->baseurl; + a->baseurl_set = overrides->baseurl_set || base->baseurl_set; + } + else { + a->baseurl = overrides->baseurl; + } + + a->directory = overrides->directory; + + if (a->options & OPTION_INHERIT || + (base->options & OPTION_INHERIT_DOWN && + !(a->options & OPTION_IGNORE_INHERIT))) { + a->rewriteconds = apr_array_append(p, overrides->rewriteconds, + base->rewriteconds); + a->rewriterules = apr_array_append(p, overrides->rewriterules, + base->rewriterules); + } + else if (a->options & OPTION_INHERIT_BEFORE || + (base->options & OPTION_INHERIT_DOWN_BEFORE && + !(a->options & OPTION_IGNORE_INHERIT))) { + a->rewriteconds = apr_array_append(p, base->rewriteconds, + overrides->rewriteconds); + a->rewriterules = apr_array_append(p, base->rewriterules, + overrides->rewriterules); + } + else { + a->rewriteconds = overrides->rewriteconds; + a->rewriterules = overrides->rewriterules; + } + + return (void *)a; +} + +static const char *cmd_rewriteengine(cmd_parms *cmd, + void *in_dconf, int flag) +{ + rewrite_perdir_conf *dconf = in_dconf; + rewrite_server_conf *sconf; + + sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); + + /* server command? set both global scope and base directory scope */ + if (cmd->path == NULL) { + sconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED); + sconf->state_set = 1; + dconf->state = sconf->state; + dconf->state_set = 1; + } + /* directory command? set directory scope only */ + else { + dconf->state = (flag ? ENGINE_ENABLED : ENGINE_DISABLED); + dconf->state_set = 1; + } + + return NULL; +} + +static const char *cmd_rewriteoptions(cmd_parms *cmd, + void *in_dconf, const char *option) +{ + int options = 0; + + while (*option) { + char *w = ap_getword_conf(cmd->temp_pool, &option); + + if (!strcasecmp(w, "inherit")) { + options |= OPTION_INHERIT; + } + else if (!strcasecmp(w, "inheritbefore")) { + options |= OPTION_INHERIT_BEFORE; + } + else if (!strcasecmp(w, "inheritdown")) { + options |= OPTION_INHERIT_DOWN; + } + else if(!strcasecmp(w, "inheritdownbefore")) { + options |= OPTION_INHERIT_DOWN_BEFORE; + } + else if (!strcasecmp(w, "ignoreinherit")) { + options |= OPTION_IGNORE_INHERIT; + } + else if (!strcasecmp(w, "allownoslash")) { + options |= OPTION_NOSLASH; + } + else if (!strncasecmp(w, "MaxRedirects=", 13)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00664) + "RewriteOptions: MaxRedirects option has been " + "removed in favor of the global " + "LimitInternalRecursion directive and will be " + "ignored."); + } + else if (!strcasecmp(w, "allowanyuri")) { + options |= OPTION_ANYURI; + } + else if (!strcasecmp(w, "mergebase")) { + options |= OPTION_MERGEBASE; + } + else if (!strcasecmp(w, "ignorecontextinfo")) { + options |= OPTION_IGNORE_CONTEXT_INFO; + } + else if (!strcasecmp(w, "legacyprefixdocroot")) { + options |= OPTION_LEGACY_PREFIX_DOCROOT; + } + else { + return apr_pstrcat(cmd->pool, "RewriteOptions: unknown option '", + w, "'", NULL); + } + } + + /* server command? set both global scope and base directory scope */ + if (cmd->path == NULL) { /* is server command */ + rewrite_perdir_conf *dconf = in_dconf; + rewrite_server_conf *sconf = + ap_get_module_config(cmd->server->module_config, + &rewrite_module); + + sconf->options |= options; + sconf->options_set = 1; + dconf->options |= options; + dconf->options_set = 1; + } + /* directory command? set directory scope only */ + else { /* is per-directory command */ + rewrite_perdir_conf *dconf = in_dconf; + + dconf->options |= options; + dconf->options_set = 1; + } + + return NULL; +} + +static const char *cmd_rewritemap(cmd_parms *cmd, void *dconf, const char *a1, + const char *a2, const char *a3) +{ + rewrite_server_conf *sconf; + rewritemap_entry *newmap; + apr_finfo_t st; + const char *fname; + + sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); + + newmap = apr_pcalloc(cmd->pool, sizeof(rewritemap_entry)); + + if (strncasecmp(a2, "txt:", 4) == 0) { + if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) { + return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ", + a2+4, NULL); + } + + newmap->type = MAPTYPE_TXT; + newmap->datafile = fname; + newmap->checkfile = fname; + newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s", + (void *)cmd->server, a1); + } + else if (strncasecmp(a2, "rnd:", 4) == 0) { + if ((fname = ap_server_root_relative(cmd->pool, a2+4)) == NULL) { + return apr_pstrcat(cmd->pool, "RewriteMap: bad path to rnd map: ", + a2+4, NULL); + } + + newmap->type = MAPTYPE_RND; + newmap->datafile = fname; + newmap->checkfile = fname; + newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s", + (void *)cmd->server, a1); + } + else if (strncasecmp(a2, "dbm", 3) == 0) { + apr_status_t rv; + + newmap->type = MAPTYPE_DBM; + fname = NULL; + newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s", + (void *)cmd->server, a1); + + if (a2[3] == ':') { + newmap->dbmtype = "default"; + fname = a2+4; + } + else if (a2[3] == '=') { + const char *colon = ap_strchr_c(a2 + 4, ':'); + + if (colon) { + newmap->dbmtype = apr_pstrndup(cmd->pool, a2 + 4, + colon - (a2 + 3) - 1); + fname = colon + 1; + } + } + + if (!fname) { + return apr_pstrcat(cmd->pool, "RewriteMap: bad map:", + a2, NULL); + } + + if ((newmap->datafile = ap_server_root_relative(cmd->pool, + fname)) == NULL) { + return apr_pstrcat(cmd->pool, "RewriteMap: bad path to dbm map: ", + fname, NULL); + } + + rv = apr_dbm_get_usednames_ex(cmd->pool, newmap->dbmtype, + newmap->datafile, &newmap->checkfile, + &newmap->checkfile2); + if (rv != APR_SUCCESS) { + return apr_pstrcat(cmd->pool, "RewriteMap: dbm type ", + newmap->dbmtype, " is invalid", NULL); + } + } + else if ((strncasecmp(a2, "dbd:", 4) == 0) + || (strncasecmp(a2, "fastdbd:", 8) == 0)) { + if (dbd_prepare == NULL) { + return "RewriteMap types dbd and fastdbd require mod_dbd!"; + } + if ((a2[0] == 'd') || (a2[0] == 'D')) { + newmap->type = MAPTYPE_DBD; + fname = a2+4; + } + else { + newmap->type = MAPTYPE_DBD_CACHE; + fname = a2+8; + newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s", + (void *)cmd->server, a1); + } + newmap->dbdq = a1; + dbd_prepare(cmd->server, fname, newmap->dbdq); + } + else if (strncasecmp(a2, "prg:", 4) == 0) { + apr_tokenize_to_argv(a2 + 4, &newmap->argv, cmd->pool); + + fname = newmap->argv[0]; + if ((newmap->argv[0] = ap_server_root_relative(cmd->pool, + fname)) == NULL) { + return apr_pstrcat(cmd->pool, "RewriteMap: bad path to prg map: ", + fname, NULL); + } + + newmap->type = MAPTYPE_PRG; + newmap->checkfile = newmap->argv[0]; + rewrite_lock_needed = 1; + + if (a3) { + char *tok_cntx; + newmap->user = apr_strtok(apr_pstrdup(cmd->pool, a3), ":", &tok_cntx); + newmap->group = apr_strtok(NULL, ":", &tok_cntx); + } + } + else if (strncasecmp(a2, "int:", 4) == 0) { + newmap->type = MAPTYPE_INT; + newmap->func = (char *(*)(request_rec *,char *)) + apr_hash_get(mapfunc_hash, a2+4, strlen(a2+4)); + if (newmap->func == NULL) { + return apr_pstrcat(cmd->pool, "RewriteMap: internal map not found:", + a2+4, NULL); + } + } + else { + if ((fname = ap_server_root_relative(cmd->pool, a2)) == NULL) { + return apr_pstrcat(cmd->pool, "RewriteMap: bad path to txt map: ", + a2, NULL); + } + + newmap->type = MAPTYPE_TXT; + newmap->datafile = fname; + newmap->checkfile = fname; + newmap->cachename = apr_psprintf(cmd->pool, "%pp:%s", + (void *)cmd->server, a1); + } + + if (newmap->checkfile + && (apr_stat(&st, newmap->checkfile, APR_FINFO_MIN, + cmd->pool) != APR_SUCCESS)) { + return apr_pstrcat(cmd->pool, + "RewriteMap: file for map ", a1, + " not found:", newmap->checkfile, NULL); + } + + apr_hash_set(sconf->rewritemaps, a1, APR_HASH_KEY_STRING, newmap); + + return NULL; +} + +static const char *cmd_rewritebase(cmd_parms *cmd, void *in_dconf, + const char *a1) +{ + rewrite_perdir_conf *dconf = in_dconf; + + if (cmd->path == NULL || dconf == NULL) { + return "RewriteBase: only valid in per-directory config files"; + } + if (a1[0] == '\0') { + return "RewriteBase: empty URL not allowed"; + } + if (a1[0] != '/') { + return "RewriteBase: argument is not a valid URL"; + } + + dconf->baseurl = a1; + dconf->baseurl_set = 1; + + return NULL; +} + +/* + * generic lexer for RewriteRule and RewriteCond flags. + * The parser will be passed in as a function pointer + * and called if a flag was found + */ +static const char *cmd_parseflagfield(apr_pool_t *p, void *cfg, char *key, + const char *(*parse)(apr_pool_t *, + void *, + char *, char *)) +{ + char *val, *nextp, *endp; + const char *err; + + endp = key + strlen(key) - 1; + if (*key != '[' || *endp != ']') { + return "bad flag delimiters"; + } + + *endp = ','; /* for simpler parsing */ + ++key; + + while (*key) { + /* skip leading spaces */ + while (apr_isspace(*key)) { + ++key; + } + + if (!*key || (nextp = ap_strchr(key, ',')) == NULL) { /* NULL should not + * happen, but ... + */ + break; + } + + /* strip trailing spaces */ + endp = nextp - 1; + while (apr_isspace(*endp)) { + --endp; + } + *++endp = '\0'; + + /* split key and val */ + val = ap_strchr(key, '='); + if (val) { + *val++ = '\0'; + } + else { + val = endp; + } + + err = parse(p, cfg, key, val); + if (err) { + return err; + } + + key = nextp + 1; + } + + return NULL; +} + +static const char *cmd_rewritecond_setflag(apr_pool_t *p, void *_cfg, + char *key, char *val) +{ + rewritecond_entry *cfg = _cfg; + + if ( strcasecmp(key, "nocase") == 0 + || strcasecmp(key, "NC") == 0 ) { + cfg->flags |= CONDFLAG_NOCASE; + } + else if ( strcasecmp(key, "ornext") == 0 + || strcasecmp(key, "OR") == 0 ) { + cfg->flags |= CONDFLAG_ORNEXT; + } + else if ( strcasecmp(key, "novary") == 0 + || strcasecmp(key, "NV") == 0 ) { + cfg->flags |= CONDFLAG_NOVARY; + } + else { + return apr_pstrcat(p, "unknown flag '", key, "'", NULL); + } + return NULL; +} + +static const char *cmd_rewritecond(cmd_parms *cmd, void *in_dconf, + const char *in_str) +{ + rewrite_perdir_conf *dconf = in_dconf; + char *str = apr_pstrdup(cmd->pool, in_str); + rewrite_server_conf *sconf; + rewritecond_entry *newcond; + ap_regex_t *regexp; + char *a1 = NULL, *a2 = NULL, *a2_end, *a3 = NULL; + const char *err; + + sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); + + /* make a new entry in the internal temporary rewrite rule list */ + if (cmd->path == NULL) { /* is server command */ + newcond = apr_array_push(sconf->rewriteconds); + } + else { /* is per-directory command */ + newcond = apr_array_push(dconf->rewriteconds); + } + + /* parse the argument line ourself + * a1 .. a3 are substrings of str, which is a fresh copy + * of the argument line. So we can use a1 .. a3 without + * copying them again. + */ + if (parseargline(str, &a1, &a2, &a2_end, &a3)) { + return apr_pstrcat(cmd->pool, "RewriteCond: bad argument line '", str, + "'", NULL); + } + + /* arg1: the input string */ + newcond->input = a1; + + /* arg3: optional flags field + * (this has to be parsed first, because we need to + * know if the regex should be compiled with ICASE!) + */ + newcond->flags = CONDFLAG_NONE; + if (a3 != NULL) { + if ((err = cmd_parseflagfield(cmd->pool, newcond, a3, + cmd_rewritecond_setflag)) != NULL) { + return apr_pstrcat(cmd->pool, "RewriteCond: ", err, NULL); + } + } + + /* arg2: the pattern */ + newcond->pattern = a2; + if (*a2 == '!') { + newcond->flags |= CONDFLAG_NOTMATCH; + ++a2; + } + + /* determine the pattern type */ + newcond->ptype = CONDPAT_REGEX; + if (strcasecmp(a1, "expr") == 0) { + newcond->ptype = CONDPAT_AP_EXPR; + } + else if (*a2 && a2[1]) { + if (*a2 == '-') { + if (!a2[2]) { + switch (a2[1]) { + case 'f': newcond->ptype = CONDPAT_FILE_EXISTS; break; + case 's': newcond->ptype = CONDPAT_FILE_SIZE; break; + case 'd': newcond->ptype = CONDPAT_FILE_DIR; break; + case 'x': newcond->ptype = CONDPAT_FILE_XBIT; break; + case 'h': newcond->ptype = CONDPAT_FILE_LINK; break; + case 'L': newcond->ptype = CONDPAT_FILE_LINK; break; + case 'l': newcond->ptype = CONDPAT_FILE_LINK; break; + case 'U': newcond->ptype = CONDPAT_LU_URL; break; + case 'F': newcond->ptype = CONDPAT_LU_FILE; break; + } + } + else if (a2[3]) { + switch (a2[1]) { + case 'l': + if (a2[2] == 't') { + a2 += 3; + newcond->ptype = CONDPAT_INT_LT; + } + else if (a2[2] == 'e') { + a2 += 3; + newcond->ptype = CONDPAT_INT_LE; + } + break; + + case 'g': + if (a2[2] == 't') { + a2 += 3; + newcond->ptype = CONDPAT_INT_GT; + } + else if (a2[2] == 'e') { + a2 += 3; + newcond->ptype = CONDPAT_INT_GE; + } + break; + + case 'e': + if (a2[2] == 'q') { + a2 += 3; + newcond->ptype = CONDPAT_INT_EQ; + } + break; + + case 'n': + if (a2[2] == 'e') { + /* Inversion, ensure !-ne == -eq */ + a2 += 3; + newcond->ptype = CONDPAT_INT_EQ; + newcond->flags ^= CONDFLAG_NOTMATCH; + } + break; + } + } + } + else { + switch (*a2) { + case '>': if (*++a2 == '=') + ++a2, newcond->ptype = CONDPAT_STR_GE; + else + newcond->ptype = CONDPAT_STR_GT; + break; + + case '<': if (*++a2 == '=') + ++a2, newcond->ptype = CONDPAT_STR_LE; + else + newcond->ptype = CONDPAT_STR_LT; + break; + + case '=': newcond->ptype = CONDPAT_STR_EQ; + /* "" represents an empty string */ + if (*++a2 == '"' && a2[1] == '"' && !a2[2]) + a2 += 2; + break; + } + } + } + + if ((newcond->ptype != CONDPAT_REGEX) && + (newcond->ptype < CONDPAT_STR_LT || newcond->ptype > CONDPAT_STR_GE) && + (newcond->flags & CONDFLAG_NOCASE)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, cmd->server, APLOGNO(00665) + "RewriteCond: NoCase option for non-regex pattern '%s' " + "is not supported and will be ignored. (%s:%d)", a2, + cmd->directive->filename, cmd->directive->line_num); + newcond->flags &= ~CONDFLAG_NOCASE; + } + + newcond->pskip = a2 - newcond->pattern; + newcond->pattern += newcond->pskip; + + if (newcond->ptype == CONDPAT_REGEX) { + regexp = ap_pregcomp(cmd->pool, a2, + AP_REG_EXTENDED | ((newcond->flags & CONDFLAG_NOCASE) + ? AP_REG_ICASE : 0)); + if (!regexp) { + return apr_pstrcat(cmd->pool, "RewriteCond: cannot compile regular " + "expression '", a2, "'", NULL); + } + + newcond->regexp = regexp; + } + else if (newcond->ptype == CONDPAT_AP_EXPR) { + unsigned int flags = newcond->flags & CONDFLAG_NOVARY ? + AP_EXPR_FLAG_DONT_VARY : 0; + newcond->expr = ap_expr_parse_cmd(cmd, a2, flags, &err, NULL); + if (err) + return apr_psprintf(cmd->pool, "RewriteCond: cannot compile " + "expression \"%s\": %s", a2, err); + } + + return NULL; +} + +static const char *cmd_rewriterule_setflag(apr_pool_t *p, void *_cfg, + char *key, char *val) +{ + rewriterule_entry *cfg = _cfg; + int error = 0; + + switch (*key++) { + case 'b': + case 'B': + if (!*key || !strcasecmp(key, "ackrefescaping")) { + cfg->flags |= RULEFLAG_ESCAPEBACKREF; + if (val && *val) { + cfg->escapes = val; + } + } + else if (!strcasecmp(key, "NP") || !strcasecmp(key, "ackrefernoplus")) { + cfg->flags |= RULEFLAG_ESCAPENOPLUS; + } + else { + ++error; + } + break; + case 'c': + case 'C': + if (!*key || !strcasecmp(key, "hain")) { /* chain */ + cfg->flags |= RULEFLAG_CHAIN; + } + else if (((*key == 'O' || *key == 'o') && !key[1]) + || !strcasecmp(key, "ookie")) { /* cookie */ + data_item *cp = cfg->cookie; + + if (!cp) { + cp = cfg->cookie = apr_palloc(p, sizeof(*cp)); + } + else { + while (cp->next) { + cp = cp->next; + } + cp->next = apr_palloc(p, sizeof(*cp)); + cp = cp->next; + } + + cp->next = NULL; + cp->data = val; + } + else { + ++error; + } + break; + case 'd': + case 'D': + if (!*key || !strcasecmp(key, "PI") || !strcasecmp(key,"iscardpath")) { + cfg->flags |= (RULEFLAG_DISCARDPATHINFO); + } + break; + case 'e': + case 'E': + if (!*key || !strcasecmp(key, "nv")) { /* env */ + data_item *cp = cfg->env; + + if (!cp) { + cp = cfg->env = apr_palloc(p, sizeof(*cp)); + } + else { + while (cp->next) { + cp = cp->next; + } + cp->next = apr_palloc(p, sizeof(*cp)); + cp = cp->next; + } + + cp->next = NULL; + cp->data = val; + } + else if (!strcasecmp(key, "nd")) { /* end */ + cfg->flags |= RULEFLAG_END; + } + else { + ++error; + } + break; + + case 'f': + case 'F': + if (!*key || !strcasecmp(key, "orbidden")) { /* forbidden */ + cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB); + cfg->forced_responsecode = HTTP_FORBIDDEN; + } + else { + ++error; + } + break; + + case 'g': + case 'G': + if (!*key || !strcasecmp(key, "one")) { /* gone */ + cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB); + cfg->forced_responsecode = HTTP_GONE; + } + else { + ++error; + } + break; + + case 'h': + case 'H': + if (!*key || !strcasecmp(key, "andler")) { /* handler */ + cfg->forced_handler = val; + } + else { + ++error; + } + break; + case 'l': + case 'L': + if (!*key || !strcasecmp(key, "ast")) { /* last */ + cfg->flags |= RULEFLAG_LASTRULE; + } + else { + ++error; + } + break; + + case 'n': + case 'N': + if (((*key == 'E' || *key == 'e') && !key[1]) + || !strcasecmp(key, "oescape")) { /* noescape */ + cfg->flags |= RULEFLAG_NOESCAPE; + } + else if (!*key || !strcasecmp(key, "ext")) { /* next */ + cfg->flags |= RULEFLAG_NEWROUND; + if (val && *val) { + cfg->maxrounds = atoi(val); + } + + } + else if (((*key == 'S' || *key == 's') && !key[1]) + || !strcasecmp(key, "osubreq")) { /* nosubreq */ + cfg->flags |= RULEFLAG_IGNOREONSUBREQ; + } + else if (((*key == 'C' || *key == 'c') && !key[1]) + || !strcasecmp(key, "ocase")) { /* nocase */ + cfg->flags |= RULEFLAG_NOCASE; + } + else { + ++error; + } + break; + + case 'p': + case 'P': + if (!*key || !strcasecmp(key, "roxy")) { /* proxy */ + cfg->flags |= RULEFLAG_PROXY; + } + else if (((*key == 'T' || *key == 't') && !key[1]) + || !strcasecmp(key, "assthrough")) { /* passthrough */ + cfg->flags |= RULEFLAG_PASSTHROUGH; + } + else { + ++error; + } + break; + + case 'q': + case 'Q': + if ( !strcasecmp(key, "SA") + || !strcasecmp(key, "sappend")) { /* qsappend */ + cfg->flags |= RULEFLAG_QSAPPEND; + } else if ( !strcasecmp(key, "SD") + || !strcasecmp(key, "sdiscard") ) { /* qsdiscard */ + cfg->flags |= RULEFLAG_QSDISCARD; + } else if ( !strcasecmp(key, "SL") + || !strcasecmp(key, "slast") ) { /* qslast */ + cfg->flags |= RULEFLAG_QSLAST; + } + else { + ++error; + } + break; + + case 'r': + case 'R': + if (!*key || !strcasecmp(key, "edirect")) { /* redirect */ + int status = 0; + + cfg->flags |= RULEFLAG_FORCEREDIRECT; + if (*val) { + if (strcasecmp(val, "permanent") == 0) { + status = HTTP_MOVED_PERMANENTLY; + } + else if (strcasecmp(val, "temp") == 0) { + status = HTTP_MOVED_TEMPORARILY; + } + else if (strcasecmp(val, "seeother") == 0) { + status = HTTP_SEE_OTHER; + } + else if (apr_isdigit(*val)) { + status = atoi(val); + if (status != HTTP_INTERNAL_SERVER_ERROR) { + int idx = + ap_index_of_response(HTTP_INTERNAL_SERVER_ERROR); + + if (ap_index_of_response(status) == idx) { + return apr_psprintf(p, "invalid HTTP " + "response code '%s' for " + "flag 'R'", + val); + } + } + if (!ap_is_HTTP_REDIRECT(status)) { + cfg->flags |= (RULEFLAG_STATUS | RULEFLAG_NOSUB); + } + } + cfg->forced_responsecode = status; + } + } + else { + ++error; + } + break; + + case 's': + case 'S': + if (!*key || !strcasecmp(key, "kip")) { /* skip */ + cfg->skip = atoi(val); + } + else { + ++error; + } + break; + + case 't': + case 'T': + if (!*key || !strcasecmp(key, "ype")) { /* type */ + cfg->forced_mimetype = val; + } + else { + ++error; + } + break; + default: + ++error; + break; + } + + if (error) { + return apr_pstrcat(p, "unknown flag '", --key, "'", NULL); + } + + return NULL; +} + +static const char *cmd_rewriterule(cmd_parms *cmd, void *in_dconf, + const char *in_str) +{ + rewrite_perdir_conf *dconf = in_dconf; + char *str = apr_pstrdup(cmd->pool, in_str); + rewrite_server_conf *sconf; + rewriterule_entry *newrule; + ap_regex_t *regexp; + char *a1 = NULL, *a2 = NULL, *a2_end, *a3 = NULL; + const char *err; + + sconf = ap_get_module_config(cmd->server->module_config, &rewrite_module); + + /* make a new entry in the internal rewrite rule list */ + if (cmd->path == NULL) { /* is server command */ + newrule = apr_array_push(sconf->rewriterules); + } + else { /* is per-directory command */ + newrule = apr_array_push(dconf->rewriterules); + } + + /* parse the argument line ourself */ + if (parseargline(str, &a1, &a2, &a2_end, &a3)) { + return apr_pstrcat(cmd->pool, "RewriteRule: bad argument line '", str, + "'", NULL); + } + + /* arg3: optional flags field */ + newrule->forced_mimetype = NULL; + newrule->forced_handler = NULL; + newrule->forced_responsecode = HTTP_MOVED_TEMPORARILY; + newrule->flags = RULEFLAG_NONE; + newrule->env = NULL; + newrule->cookie = NULL; + newrule->skip = 0; + newrule->maxrounds = REWRITE_MAX_ROUNDS; + if (a3 != NULL) { + if ((err = cmd_parseflagfield(cmd->pool, newrule, a3, + cmd_rewriterule_setflag)) != NULL) { + return apr_pstrcat(cmd->pool, "RewriteRule: ", err, NULL); + } + } + + /* arg1: the pattern + * try to compile the regexp to test if is ok + */ + if (*a1 == '!') { + newrule->flags |= RULEFLAG_NOTMATCH; + ++a1; + } + + regexp = ap_pregcomp(cmd->pool, a1, AP_REG_EXTENDED | + ((newrule->flags & RULEFLAG_NOCASE) + ? AP_REG_ICASE : 0)); + if (!regexp) { + return apr_pstrcat(cmd->pool, + "RewriteRule: cannot compile regular expression '", + a1, "'", NULL); + } + + newrule->pattern = a1; + newrule->regexp = regexp; + + /* arg2: the output string */ + newrule->output = a2; + if (*a2 == '-' && !a2[1]) { + newrule->flags |= RULEFLAG_NOSUB; + } + + if (*(a2_end-1) == '?') { + /* a literal ? at the end of the unsubstituted rewrite rule */ + newrule->flags |= RULEFLAG_QSNONE; + } + else if (newrule->flags & RULEFLAG_QSDISCARD) { + if (NULL == ap_strchr(newrule->output, '?')) { + newrule->flags |= RULEFLAG_QSNONE; + } + } + + /* now, if the server or per-dir config holds an + * array of RewriteCond entries, we take it for us + * and clear the array + */ + if (cmd->path == NULL) { /* is server command */ + newrule->rewriteconds = sconf->rewriteconds; + sconf->rewriteconds = apr_array_make(cmd->pool, 2, + sizeof(rewritecond_entry)); + } + else { /* is per-directory command */ + newrule->rewriteconds = dconf->rewriteconds; + dconf->rewriteconds = apr_array_make(cmd->pool, 2, + sizeof(rewritecond_entry)); + } + + return NULL; +} + + +/* + * +-------------------------------------------------------+ + * | | + * | the rewriting engine + * | | + * +-------------------------------------------------------+ + */ + +/* Lexicographic Compare */ +static APR_INLINE int compare_lexicography(char *a, char *b) +{ + apr_size_t i, lena, lenb; + + lena = strlen(a); + lenb = strlen(b); + + if (lena == lenb) { + for (i = 0; i < lena; ++i) { + if (a[i] != b[i]) { + return ((unsigned char)a[i] > (unsigned char)b[i]) ? 1 : -1; + } + } + + return 0; + } + + return ((lena > lenb) ? 1 : -1); +} + +/* + * Apply a single rewriteCond + */ +static int apply_rewrite_cond(rewritecond_entry *p, rewrite_ctx *ctx) +{ + char *input = NULL; + apr_finfo_t sb; + request_rec *rsub, *r = ctx->r; + ap_regmatch_t regmatch[AP_MAX_REG_MATCH]; + int rc = 0; + int basis; + + if (p->ptype != CONDPAT_AP_EXPR) + input = do_expand(p->input, ctx, NULL); + + switch (p->ptype) { + case CONDPAT_FILE_EXISTS: + if ( apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS + && sb.filetype == APR_REG) { + rc = 1; + } + break; + + case CONDPAT_FILE_SIZE: + if ( apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS + && sb.filetype == APR_REG && sb.size > 0) { + rc = 1; + } + break; + + case CONDPAT_FILE_LINK: +#if !defined(OS2) + if ( apr_stat(&sb, input, APR_FINFO_MIN | APR_FINFO_LINK, + r->pool) == APR_SUCCESS + && sb.filetype == APR_LNK) { + rc = 1; + } +#endif + break; + + case CONDPAT_FILE_DIR: + if ( apr_stat(&sb, input, APR_FINFO_MIN, r->pool) == APR_SUCCESS + && sb.filetype == APR_DIR) { + rc = 1; + } + break; + + case CONDPAT_FILE_XBIT: + if ( apr_stat(&sb, input, APR_FINFO_PROT, r->pool) == APR_SUCCESS + && (sb.protection & (APR_UEXECUTE | APR_GEXECUTE | APR_WEXECUTE))) { + rc = 1; + } + break; + + case CONDPAT_LU_URL: + if (*input && subreq_ok(r)) { + rsub = ap_sub_req_lookup_uri(input, r, NULL); + if (rsub->status < 400) { + rc = 1; + } + rewritelog((r, 5, NULL, "RewriteCond URI (-U) check: " + "path=%s -> status=%d", input, rsub->status)); + ap_destroy_sub_req(rsub); + } + break; + + case CONDPAT_LU_FILE: + if (*input && subreq_ok(r)) { + rsub = ap_sub_req_lookup_file(input, r, NULL); + if (rsub->status < 300 && + /* double-check that file exists since default result is 200 */ + apr_stat(&sb, rsub->filename, APR_FINFO_MIN, + r->pool) == APR_SUCCESS) { + rc = 1; + } + rewritelog((r, 5, NULL, "RewriteCond file (-F) check: path=%s " + "-> file=%s status=%d", input, rsub->filename, + rsub->status)); + ap_destroy_sub_req(rsub); + } + break; + + case CONDPAT_STR_GE: + basis = 0; + goto test_str_g; + case CONDPAT_STR_GT: + basis = 1; +test_str_g: + if (p->flags & CONDFLAG_NOCASE) { + rc = (strcasecmp(input, p->pattern) >= basis) ? 1 : 0; + } + else { + rc = (compare_lexicography(input, p->pattern) >= basis) ? 1 : 0; + } + break; + + case CONDPAT_STR_LE: + basis = 0; + goto test_str_l; + case CONDPAT_STR_LT: + basis = -1; +test_str_l: + if (p->flags & CONDFLAG_NOCASE) { + rc = (strcasecmp(input, p->pattern) <= basis) ? 1 : 0; + } + else { + rc = (compare_lexicography(input, p->pattern) <= basis) ? 1 : 0; + } + break; + + case CONDPAT_STR_EQ: + /* Note: the only type where the operator is dropped from p->pattern */ + if (p->flags & CONDFLAG_NOCASE) { + rc = !strcasecmp(input, p->pattern); + } + else { + rc = !strcmp(input, p->pattern); + } + break; + + case CONDPAT_INT_GE: rc = (atoi(input) >= atoi(p->pattern)); break; + case CONDPAT_INT_GT: rc = (atoi(input) > atoi(p->pattern)); break; + + case CONDPAT_INT_LE: rc = (atoi(input) <= atoi(p->pattern)); break; + case CONDPAT_INT_LT: rc = (atoi(input) < atoi(p->pattern)); break; + + case CONDPAT_INT_EQ: rc = (atoi(input) == atoi(p->pattern)); break; + + case CONDPAT_AP_EXPR: + { + const char *err, *source; + rc = ap_expr_exec_re(r, p->expr, AP_MAX_REG_MATCH, regmatch, + &source, &err); + if (rc < 0 || err) { + rewritelog((r, 1, ctx->perdir, + "RewriteCond: expr='%s' evaluation failed: %s", + p->pattern - p->pskip, err)); + rc = 0; + } + /* update briRC backref info */ + if (rc && !(p->flags & CONDFLAG_NOTMATCH)) { + ctx->briRC.source = source; + memcpy(ctx->briRC.regmatch, regmatch, sizeof(regmatch)); + } + } + break; + default: + /* it is really a regexp pattern, so apply it */ + rc = !ap_regexec(p->regexp, input, AP_MAX_REG_MATCH, regmatch, 0); + + /* update briRC backref info */ + if (rc && !(p->flags & CONDFLAG_NOTMATCH)) { + ctx->briRC.source = input; + memcpy(ctx->briRC.regmatch, regmatch, sizeof(regmatch)); + } + break; + } + + if (p->flags & CONDFLAG_NOTMATCH) { + rc = !rc; + } + + rewritelog((r, 4, ctx->perdir, "RewriteCond: input='%s' pattern='%s'%s " + "=> %s", input, p->pattern - p->pskip, + (p->flags & CONDFLAG_NOCASE) ? " [NC]" : "", + rc ? "matched" : "not-matched")); + + return rc; +} + +/* check for forced type and handler */ +static APR_INLINE void force_type_handler(rewriterule_entry *p, + rewrite_ctx *ctx) +{ + char *expanded; + + if (p->forced_mimetype) { + expanded = do_expand(p->forced_mimetype, ctx, p); + + if (*expanded) { + ap_str_tolower(expanded); + + rewritelog((ctx->r, 2, ctx->perdir, "remember %s to have MIME-type " + "'%s'", ctx->r->filename, expanded)); + + apr_table_setn(ctx->r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR, + expanded); + } + } + + if (p->forced_handler) { + expanded = do_expand(p->forced_handler, ctx, p); + + if (*expanded) { + ap_str_tolower(expanded); + + rewritelog((ctx->r, 2, ctx->perdir, "remember %s to have " + "Content-handler '%s'", ctx->r->filename, expanded)); + + apr_table_setn(ctx->r->notes, REWRITE_FORCED_HANDLER_NOTEVAR, + expanded); + } + } +} + +/* + * Apply a single RewriteRule + */ +static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) +{ + ap_regmatch_t regmatch[AP_MAX_REG_MATCH]; + apr_array_header_t *rewriteconds; + rewritecond_entry *conds; + int i, rc; + char *newuri = NULL; + request_rec *r = ctx->r; + int is_proxyreq = 0; + + ctx->uri = r->filename; + + if (ctx->perdir) { + apr_size_t dirlen = strlen(ctx->perdir); + + /* + * Proxy request? + */ + is_proxyreq = ( r->proxyreq && r->filename + && !strncmp(r->filename, "proxy:", 6)); + + /* Since we want to match against the (so called) full URL, we have + * to re-add the PATH_INFO postfix + */ + if (r->path_info && *r->path_info) { + rewritelog((r, 3, ctx->perdir, "add path info postfix: %s -> %s%s", + ctx->uri, ctx->uri, r->path_info)); + ctx->uri = apr_pstrcat(r->pool, ctx->uri, r->path_info, NULL); + } + + /* Additionally we strip the physical path from the url to match + * it independent from the underlying filesystem. + */ + if (!is_proxyreq && strlen(ctx->uri) >= dirlen && + !strncmp(ctx->uri, ctx->perdir, dirlen)) { + + rewritelog((r, 3, ctx->perdir, "strip per-dir prefix: %s -> %s", + ctx->uri, ctx->uri + dirlen)); + ctx->uri = ctx->uri + dirlen; + } + } + + /* Try to match the URI against the RewriteRule pattern + * and exit immediately if it didn't apply. + */ + rewritelog((r, 3, ctx->perdir, "applying pattern '%s' to uri '%s'", + p->pattern, ctx->uri)); + + rc = !ap_regexec(p->regexp, ctx->uri, AP_MAX_REG_MATCH, regmatch, 0); + if (! (( rc && !(p->flags & RULEFLAG_NOTMATCH)) || + (!rc && (p->flags & RULEFLAG_NOTMATCH)) ) ) { + return 0; + } + + /* It matched, wow! Now it's time to prepare the context structure for + * further processing + */ + ctx->vary_this = NULL; + ctx->briRC.source = NULL; + + if (p->flags & RULEFLAG_NOTMATCH) { + ctx->briRR.source = NULL; + } + else { + ctx->briRR.source = apr_pstrdup(r->pool, ctx->uri); + memcpy(ctx->briRR.regmatch, regmatch, sizeof(regmatch)); + } + + /* Ok, we already know the pattern has matched, but we now + * additionally have to check for all existing preconditions + * (RewriteCond) which have to be also true. We do this at + * this very late stage to avoid unnecessary checks which + * would slow down the rewriting engine. + */ + rewriteconds = p->rewriteconds; + conds = (rewritecond_entry *)rewriteconds->elts; + + for (i = 0; i < rewriteconds->nelts; ++i) { + rewritecond_entry *c = &conds[i]; + + rc = apply_rewrite_cond(c, ctx); + /* + * Reset vary_this if the novary flag is set for this condition. + */ + if (c->flags & CONDFLAG_NOVARY) { + ctx->vary_this = NULL; + } + if (c->flags & CONDFLAG_ORNEXT) { + if (!rc) { + /* One condition is false, but another can be still true. */ + ctx->vary_this = NULL; + continue; + } + else { + /* skip the rest of the chained OR conditions */ + while ( i < rewriteconds->nelts + && c->flags & CONDFLAG_ORNEXT) { + c = &conds[++i]; + } + } + } + else if (!rc) { + return 0; + } + + /* If some HTTP header was involved in the condition, remember it + * for later use + */ + if (ctx->vary_this) { + ctx->vary = ctx->vary + ? apr_pstrcat(r->pool, ctx->vary, ", ", ctx->vary_this, + NULL) + : ctx->vary_this; + ctx->vary_this = NULL; + } + } + + /* expand the result */ + if (!(p->flags & RULEFLAG_NOSUB)) { + newuri = do_expand(p->output, ctx, p); + rewritelog((r, 2, ctx->perdir, "rewrite '%s' -> '%s'", ctx->uri, + newuri)); + } + + /* expand [E=var:val] and [CO=<cookie>] */ + do_expand_env(p->env, ctx); + do_expand_cookie(p->cookie, ctx); + + /* non-substitution rules ('RewriteRule <pat> -') end here. */ + if (p->flags & RULEFLAG_NOSUB) { + force_type_handler(p, ctx); + + if (p->flags & RULEFLAG_STATUS) { + rewritelog((r, 2, ctx->perdir, "forcing responsecode %d for %s", + p->forced_responsecode, r->filename)); + + r->status = p->forced_responsecode; + } + + return 2; + } + + /* Now adjust API's knowledge about r->filename and r->args */ + r->filename = newuri; + + if (ctx->perdir && (p->flags & RULEFLAG_DISCARDPATHINFO)) { + r->path_info = NULL; + } + + splitout_queryargs(r, p->flags); + + /* Add the previously stripped per-directory location prefix, unless + * (1) it's an absolute URL path and + * (2) it's a full qualified URL + */ + if ( ctx->perdir && !is_proxyreq && *r->filename != '/' + && !is_absolute_uri(r->filename, NULL)) { + rewritelog((r, 3, ctx->perdir, "add per-dir prefix: %s -> %s%s", + r->filename, ctx->perdir, r->filename)); + + r->filename = apr_pstrcat(r->pool, ctx->perdir, r->filename, NULL); + } + + /* If this rule is forced for proxy throughput + * (`RewriteRule ... ... [P]') then emulate mod_proxy's + * URL-to-filename handler to be sure mod_proxy is triggered + * for this URL later in the Apache API. But make sure it is + * a fully-qualified URL. (If not it is qualified with + * ourself). + */ + if (p->flags & RULEFLAG_PROXY) { + /* For rules evaluated in server context, the mod_proxy fixup + * hook can be relied upon to escape the URI as and when + * necessary, since it occurs later. If in directory context, + * the ordering of the fixup hooks is forced such that + * mod_proxy comes first, so the URI must be escaped here + * instead. See PR 39746, 46428, and other headaches. */ + if (ctx->perdir && (p->flags & RULEFLAG_NOESCAPE) == 0) { + char *old_filename = r->filename; + + r->filename = ap_escape_uri(r->pool, r->filename); + rewritelog((r, 2, ctx->perdir, "escaped URI in per-dir context " + "for proxy, %s -> %s", old_filename, r->filename)); + } + + fully_qualify_uri(r); + + rewritelog((r, 2, ctx->perdir, "forcing proxy-throughput with %s", + r->filename)); + + r->filename = apr_pstrcat(r->pool, "proxy:", r->filename, NULL); + return 1; + } + + /* If this rule is explicitly forced for HTTP redirection + * (`RewriteRule .. .. [R]') then force an external HTTP + * redirect. But make sure it is a fully-qualified URL. (If + * not it is qualified with ourself). + */ + if (p->flags & RULEFLAG_FORCEREDIRECT) { + fully_qualify_uri(r); + + rewritelog((r, 2, ctx->perdir, "explicitly forcing redirect with %s", + r->filename)); + + r->status = p->forced_responsecode; + return 1; + } + + /* Special Rewriting Feature: Self-Reduction + * We reduce the URL by stripping a possible + * http[s]://<ourhost>[:<port>] prefix, i.e. a prefix which + * corresponds to ourself. This is to simplify rewrite maps + * and to avoid recursion, etc. When this prefix is not a + * coincidence then the user has to use [R] explicitly (see + * above). + */ + reduce_uri(r); + + /* If this rule is still implicitly forced for HTTP + * redirection (`RewriteRule .. <scheme>://...') then + * directly force an external HTTP redirect. + */ + if (is_absolute_uri(r->filename, NULL)) { + rewritelog((r, 2, ctx->perdir, "implicitly forcing redirect (rc=%d) " + "with %s", p->forced_responsecode, r->filename)); + + r->status = p->forced_responsecode; + return 1; + } + + /* Finally remember the forced mime-type */ + force_type_handler(p, ctx); + + /* Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_) + * But now we're done for this particular rule. + */ + return 1; +} + +/* + * Apply a complete rule set, + * i.e. a list of rewrite rules + */ +static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules, + char *perdir) +{ + rewriterule_entry *entries; + rewriterule_entry *p; + int i; + int changed; + int rc; + int s; + rewrite_ctx *ctx; + int round = 1; + + ctx = apr_palloc(r->pool, sizeof(*ctx)); + ctx->perdir = perdir; + ctx->r = r; + + /* + * Iterate over all existing rules + */ + entries = (rewriterule_entry *)rewriterules->elts; + changed = 0; + loop: + for (i = 0; i < rewriterules->nelts; i++) { + p = &entries[i]; + + /* + * Ignore this rule on subrequests if we are explicitly + * asked to do so or this is a proxy-throughput or a + * forced redirect rule. + */ + if (r->main != NULL && + (p->flags & RULEFLAG_IGNOREONSUBREQ || + p->flags & RULEFLAG_FORCEREDIRECT )) { + continue; + } + + /* + * Apply the current rule. + */ + ctx->vary = NULL; + rc = apply_rewrite_rule(p, ctx); + + if (rc) { + + /* Catch looping rules with pathinfo growing unbounded */ + if ( strlen( r->filename ) > 2*r->server->limit_req_line ) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, + "RewriteRule '%s' and URI '%s' " + "exceeded maximum length (%d)", + p->pattern, r->uri, 2*r->server->limit_req_line ); + r->status = HTTP_INTERNAL_SERVER_ERROR; + return ACTION_STATUS; + } + + /* Regardless of what we do next, we've found a match. Check to see + * if any of the request header fields were involved, and add them + * to the Vary field of the response. + */ + if (ctx->vary) { + apr_table_merge(r->headers_out, "Vary", ctx->vary); + } + + /* + * The rule sets the response code (implies match-only) + */ + if (p->flags & RULEFLAG_STATUS) { + return ACTION_STATUS; + } + + /* + * Indicate a change if this was not a match-only rule. + */ + if (rc != 2) { + changed = ((p->flags & RULEFLAG_NOESCAPE) + ? ACTION_NOESCAPE : ACTION_NORMAL); + } + + /* + * Pass-Through Feature (`RewriteRule .. .. [PT]'): + * Because the Apache 1.x API is very limited we + * need this hack to pass the rewritten URL to other + * modules like mod_alias, mod_userdir, etc. + */ + if (p->flags & RULEFLAG_PASSTHROUGH) { + rewritelog((r, 2, perdir, "forcing '%s' to get passed through " + "to next API URI-to-filename handler", r->filename)); + r->filename = apr_pstrcat(r->pool, "passthrough:", + r->filename, NULL); + changed = ACTION_NORMAL; + break; + } + + if (p->flags & RULEFLAG_END) { + rewritelog((r, 8, perdir, "Rule has END flag, no further rewriting for this request")); + apr_pool_userdata_set("1", really_last_key, apr_pool_cleanup_null, r->pool); + break; + } + /* + * Stop processing also on proxy pass-through and + * last-rule and new-round flags. + */ + if (p->flags & (RULEFLAG_PROXY | RULEFLAG_LASTRULE)) { + break; + } + + /* + * On "new-round" flag we just start from the top of + * the rewriting ruleset again. + */ + if (p->flags & RULEFLAG_NEWROUND) { + if (++round >= p->maxrounds) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02596) + "RewriteRule '%s' and URI '%s' exceeded " + "maximum number of rounds (%d) via the [N] flag", + p->pattern, r->uri, p->maxrounds); + + r->status = HTTP_INTERNAL_SERVER_ERROR; + return ACTION_STATUS; + } + goto loop; + } + + /* + * If we are forced to skip N next rules, do it now. + */ + if (p->skip > 0) { + s = p->skip; + while ( i < rewriterules->nelts + && s > 0) { + i++; + s--; + } + } + } + else { + /* + * If current rule is chained with next rule(s), + * skip all this next rule(s) + */ + while ( i < rewriterules->nelts + && p->flags & RULEFLAG_CHAIN) { + i++; + p = &entries[i]; + } + } + } + return changed; +} + + +/* + * +-------------------------------------------------------+ + * | | + * | Module Initialization Hooks + * | | + * +-------------------------------------------------------+ + */ + +static int pre_config(apr_pool_t *pconf, + apr_pool_t *plog, + apr_pool_t *ptemp) +{ + APR_OPTIONAL_FN_TYPE(ap_register_rewrite_mapfunc) *map_pfn_register; + + rewrite_lock_needed = 0; + ap_mutex_register(pconf, rewritemap_mutex_type, NULL, APR_LOCK_DEFAULT, 0); + + /* register int: rewritemap handlers */ + map_pfn_register = APR_RETRIEVE_OPTIONAL_FN(ap_register_rewrite_mapfunc); + if (map_pfn_register) { + map_pfn_register("tolower", rewrite_mapfunc_tolower); + map_pfn_register("toupper", rewrite_mapfunc_toupper); + map_pfn_register("escape", rewrite_mapfunc_escape); + map_pfn_register("unescape", rewrite_mapfunc_unescape); + } + dbd_acquire = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_acquire); + dbd_prepare = APR_RETRIEVE_OPTIONAL_FN(ap_dbd_prepare); + return OK; +} + +static int post_config(apr_pool_t *p, + apr_pool_t *plog, + apr_pool_t *ptemp, + server_rec *s) +{ + apr_status_t rv; + + /* check if proxy module is available */ + proxy_available = (ap_find_linked_module("mod_proxy.c") != NULL); + + if (rewrite_lock_needed) { + rv = rewritelock_create(s, p); + if (rv != APR_SUCCESS) { + return HTTP_INTERNAL_SERVER_ERROR; + } + + apr_pool_cleanup_register(p, (void *)s, rewritelock_remove, + apr_pool_cleanup_null); + } + + /* if we are not doing the initial config, step through the servers and + * open the RewriteMap prg:xxx programs, + */ + if (ap_state_query(AP_SQ_MAIN_STATE) == AP_SQ_MS_CREATE_CONFIG) { + for (; s; s = s->next) { + if (run_rewritemap_programs(s, p) != APR_SUCCESS) { + return HTTP_INTERNAL_SERVER_ERROR; + } + } + } + + return OK; +} + +static void init_child(apr_pool_t *p, server_rec *s) +{ + apr_status_t rv = 0; /* get a rid of gcc warning (REWRITELOG_DISABLED) */ + + if (rewrite_mapr_lock_acquire) { + rv = apr_global_mutex_child_init(&rewrite_mapr_lock_acquire, + apr_global_mutex_lockfile(rewrite_mapr_lock_acquire), p); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00666) + "mod_rewrite: could not init rewrite_mapr_lock_acquire" + " in child"); + } + } + + /* create the lookup cache */ + if (!init_cache(p)) { + ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(00667) + "mod_rewrite: could not init map cache in child"); + } +} + + +/* + * +-------------------------------------------------------+ + * | | + * | runtime hooks + * | | + * +-------------------------------------------------------+ + */ + +/* + * URI-to-filename hook + * [deals with RewriteRules in server context] + */ +static int hook_uri2file(request_rec *r) +{ + rewrite_perdir_conf *dconf; + rewrite_server_conf *conf; + const char *saved_rulestatus; + const char *var; + const char *thisserver; + char *thisport; + const char *thisurl; + unsigned int port; + int rulestatus; + void *skipdata; + const char *oargs; + + /* + * retrieve the config structures + */ + conf = ap_get_module_config(r->server->module_config, &rewrite_module); + + dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config, + &rewrite_module); + + /* + * only do something under runtime if the engine is really enabled, + * else return immediately! + */ + if (!dconf || dconf->state == ENGINE_DISABLED) { + return DECLINED; + } + + /* + * check for the ugly API case of a virtual host section where no + * mod_rewrite directives exists. In this situation we became no chance + * by the API to setup our default per-server config so we have to + * on-the-fly assume we have the default config. But because the default + * config has a disabled rewriting engine we are lucky because can + * just stop operating now. + */ + if (conf->server != r->server) { + return DECLINED; + } + + /* END flag was used as a RewriteRule flag on this request */ + apr_pool_userdata_get(&skipdata, really_last_key, r->pool); + if (skipdata != NULL) { + rewritelog((r, 8, NULL, "Declining, no further rewriting due to END flag")); + return DECLINED; + } + + /* Unless the anyuri option is set, ensure that the input to the + * first rule really is a URL-path, avoiding security issues with + * poorly configured rules. See CVE-2011-3368, CVE-2011-4317. */ + if ((dconf->options & OPTION_ANYURI) == 0 + && ((r->unparsed_uri[0] == '*' && r->unparsed_uri[1] == '\0') + || !r->uri || r->uri[0] != '/')) { + rewritelog((r, 8, NULL, "Declining, request-URI '%s' is not a URL-path. " + "Consult the manual entry for the RewriteOptions directive " + "for options and caveats about matching other strings.", + r->uri)); + return DECLINED; + } + + /* + * remember the original query string for later check, since we don't + * want to apply URL-escaping when no substitution has changed it. + */ + oargs = r->args; + + /* + * add the SCRIPT_URL variable to the env. this is a bit complicated + * due to the fact that apache uses subrequests and internal redirects + */ + + if (r->main == NULL) { + var = apr_table_get(r->subprocess_env, REDIRECT_ENVVAR_SCRIPT_URL); + if (var == NULL) { + apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, r->uri); + } + else { + apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var); + } + } + else { + var = apr_table_get(r->main->subprocess_env, ENVVAR_SCRIPT_URL); + apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URL, var); + } + + /* + * create the SCRIPT_URI variable for the env + */ + + /* add the canonical URI of this URL */ + thisserver = ap_get_server_name_for_url(r); + port = ap_get_server_port(r); + if (ap_is_default_port(port, r)) { + thisport = ""; + } + else { + thisport = apr_psprintf(r->pool, ":%u", port); + } + thisurl = apr_table_get(r->subprocess_env, ENVVAR_SCRIPT_URL); + + /* set the variable */ + var = apr_pstrcat(r->pool, ap_http_scheme(r), "://", thisserver, thisport, + thisurl, NULL); + apr_table_setn(r->subprocess_env, ENVVAR_SCRIPT_URI, var); + + if (!(saved_rulestatus = apr_table_get(r->notes,"mod_rewrite_rewritten"))) { + /* if filename was not initially set, + * we start with the requested URI + */ + if (r->filename == NULL) { + r->filename = apr_pstrdup(r->pool, r->uri); + rewritelog((r, 2, NULL, "init rewrite engine with requested uri %s", + r->filename)); + } + else { + rewritelog((r, 2, NULL, "init rewrite engine with passed filename " + "%s. Original uri = %s", r->filename, r->uri)); + } + + /* + * now apply the rules ... + */ + rulestatus = apply_rewrite_list(r, conf->rewriterules, NULL); + apr_table_setn(r->notes, "mod_rewrite_rewritten", + apr_psprintf(r->pool,"%d",rulestatus)); + } + else { + rewritelog((r, 2, NULL, "uri already rewritten. Status %s, Uri %s, " + "r->filename %s", saved_rulestatus, r->uri, r->filename)); + + rulestatus = atoi(saved_rulestatus); + } + + if (rulestatus) { + unsigned skip; + apr_size_t flen; + + if (r->args && *(ap_scan_vchar_obstext(r->args))) { + /* + * We have a raw control character or a ' ' in r->args. + * Correct encoding was missed. + */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10410) + "Rewritten query string contains control " + "characters or spaces"); + return HTTP_FORBIDDEN; + } + + if (ACTION_STATUS == rulestatus) { + int n = r->status; + + r->status = HTTP_OK; + return n; + } + + flen = r->filename ? strlen(r->filename) : 0; + if (flen > 6 && strncmp(r->filename, "proxy:", 6) == 0) { + /* it should be go on as an internal proxy request */ + + /* check if the proxy module is enabled, so + * we can actually use it! + */ + if (!proxy_available) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00669) + "attempt to make remote request from mod_rewrite " + "without proxy enabled: %s", r->filename); + return HTTP_FORBIDDEN; + } + + if (rulestatus == ACTION_NOESCAPE) { + apr_table_setn(r->notes, "proxy-nocanon", "1"); + } + + /* make sure the QUERY_STRING and + * PATH_INFO parts get incorporated + */ + if (r->path_info != NULL) { + r->filename = apr_pstrcat(r->pool, r->filename, + r->path_info, NULL); + } + if ((r->args != NULL) + && ((r->proxyreq == PROXYREQ_PROXY) + || (rulestatus == ACTION_NOESCAPE))) { + /* see proxy_http:proxy_http_canon() */ + r->filename = apr_pstrcat(r->pool, r->filename, + "?", r->args, NULL); + } + + /* now make sure the request gets handled by the proxy handler */ + if (PROXYREQ_NONE == r->proxyreq) { + r->proxyreq = PROXYREQ_REVERSE; + } + r->handler = "proxy-server"; + + rewritelog((r, 1, NULL, "go-ahead with proxy request %s [OK]", + r->filename)); + return OK; + } + else if ((skip = is_absolute_uri(r->filename, NULL)) > 0) { + int n; + + /* it was finally rewritten to a remote URL */ + + if (rulestatus != ACTION_NOESCAPE) { + rewritelog((r, 1, NULL, "escaping %s for redirect", + r->filename)); + r->filename = escape_absolute_uri(r->pool, r->filename, skip); + } + + /* append the QUERY_STRING part */ + if (r->args) { + char *escaped_args = NULL; + int noescape = (rulestatus == ACTION_NOESCAPE || + (oargs && !strcmp(r->args, oargs))); + + r->filename = apr_pstrcat(r->pool, r->filename, "?", + noescape + ? r->args + : (escaped_args = + ap_escape_uri(r->pool, r->args)), + NULL); + + rewritelog((r, 1, NULL, "%s %s to query string for redirect %s", + noescape ? "copying" : "escaping", + r->args , + noescape ? "" : escaped_args)); + } + + /* determine HTTP redirect response code */ + if (ap_is_HTTP_REDIRECT(r->status)) { + n = r->status; + r->status = HTTP_OK; /* make Apache kernel happy */ + } + else { + n = HTTP_MOVED_TEMPORARILY; + } + + /* now do the redirection */ + apr_table_setn(r->headers_out, "Location", r->filename); + rewritelog((r, 1, NULL, "redirect to %s [REDIRECT/%d]", r->filename, + n)); + + return n; + } + else if (flen > 12 && strncmp(r->filename, "passthrough:", 12) == 0) { + /* + * Hack because of underpowered API: passing the current + * rewritten filename through to other URL-to-filename handlers + * just as it were the requested URL. This is to enable + * post-processing by mod_alias, etc. which always act on + * r->uri! The difference here is: We do not try to + * add the document root + */ + r->uri = apr_pstrdup(r->pool, r->filename+12); + return DECLINED; + } + else { + /* it was finally rewritten to a local path */ + const char *uri_reduced = NULL; + + /* expand "/~user" prefix */ +#if APR_HAS_USER + r->filename = expand_tildepaths(r, r->filename); +#endif + rewritelog((r, 2, NULL, "local path result: %s", r->filename)); + + /* the filename must be either an absolute local path or an + * absolute local URL. + */ + if ( *r->filename != '/' + && !ap_os_is_path_absolute(r->pool, r->filename)) { + return HTTP_BAD_REQUEST; + } + + /* if there is no valid prefix, we call + * the translator from the core and + * prefix the filename with document_root + * + * NOTICE: + * We cannot leave out the prefix_stat because + * - when we always prefix with document_root + * then no absolute path can be created, e.g. via + * emulating a ScriptAlias directive, etc. + * - when we always NOT prefix with document_root + * then the files under document_root have to + * be references directly and document_root + * gets never used and will be a dummy parameter - + * this is also bad + * + * BUT: + * Under real Unix systems this is no problem, + * because we only do stat() on the first directory + * and this gets cached by the kernel for along time! + */ + + if(!(conf->options & OPTION_LEGACY_PREFIX_DOCROOT)) { + uri_reduced = apr_table_get(r->notes, "mod_rewrite_uri_reduced"); + } + + if (!prefix_stat(r->filename, r->pool) || uri_reduced != NULL) { + int res; + char *tmp = r->uri; + + r->uri = r->filename; + res = ap_core_translate(r); + r->uri = tmp; + + if (res != OK) { + rewritelog((r, 1, NULL, "prefixing with document_root of %s" + " FAILED", r->filename)); + + return res; + } + + rewritelog((r, 2, NULL, "prefixed with document_root to %s", + r->filename)); + } + + rewritelog((r, 1, NULL, "go-ahead with %s [OK]", r->filename)); + return OK; + } + } + else { + rewritelog((r, 1, NULL, "pass through %s", r->filename)); + return DECLINED; + } +} + +/* + * Fixup hook + * [RewriteRules in directory context] + */ +static int hook_fixup(request_rec *r) +{ + rewrite_perdir_conf *dconf; + char *cp; + char *cp2; + const char *ccp; + apr_size_t l; + int rulestatus; + int n; + char *ofilename, *oargs; + int is_proxyreq; + void *skipdata; + + dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config, + &rewrite_module); + + /* if there is no per-dir config we return immediately */ + if (dconf == NULL) { + return DECLINED; + } + + /* + * only do something under runtime if the engine is really enabled, + * for this directory, else return immediately! + */ + if (dconf->state == ENGINE_DISABLED) { + return DECLINED; + } + + /* if there are no real (i.e. no RewriteRule directives!) + per-dir config of us, we return also immediately */ + if (dconf->directory == NULL) { + return DECLINED; + } + + /* + * Proxy request? + */ + is_proxyreq = ( r->proxyreq && r->filename + && !strncmp(r->filename, "proxy:", 6)); + + /* + * .htaccess file is called before really entering the directory, i.e.: + * URL: http://localhost/foo and .htaccess is located in foo directory + * Ignore such attempts, allowing mod_dir to direct the client to the + * canonical URL. This can be controlled with the AllowNoSlash option. + */ + if (!is_proxyreq && !(dconf->options & OPTION_NOSLASH)) { + l = strlen(dconf->directory) - 1; + if (r->filename && strlen(r->filename) == l && + (dconf->directory)[l] == '/' && + !strncmp(r->filename, dconf->directory, l)) { + return DECLINED; + } + } + + /* END flag was used as a RewriteRule flag on this request */ + apr_pool_userdata_get(&skipdata, really_last_key, r->pool); + if (skipdata != NULL) { + rewritelog((r, 8, dconf->directory, "Declining, no further rewriting due to END flag")); + return DECLINED; + } + + /* + * Do the Options check after engine check, so + * the user is able to explicitly turn RewriteEngine Off. + */ + if (!(ap_allow_options(r) & (OPT_SYM_LINKS | OPT_SYM_OWNER))) { + /* FollowSymLinks is mandatory! */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(00670) + "Options FollowSymLinks and SymLinksIfOwnerMatch are both off, " + "so the RewriteRule directive is also forbidden " + "due to its similar ability to circumvent directory restrictions : " + "%s", r->filename); + return HTTP_FORBIDDEN; + } + + /* + * remember the current filename before rewriting for later check + * to prevent deadlooping because of internal redirects + * on final URL/filename which can be equal to the initial one. + * also, we'll restore original r->filename if we decline this + * request + */ + ofilename = r->filename; + oargs = r->args; + + if (r->filename == NULL) { + r->filename = apr_pstrdup(r->pool, r->uri); + rewritelog((r, 2, dconf->directory, "init rewrite engine with" + " requested uri %s", r->filename)); + } + + /* + * now apply the rules ... + */ + rulestatus = apply_rewrite_list(r, dconf->rewriterules, dconf->directory); + if (rulestatus) { + unsigned skip; + + if (r->args && *(ap_scan_vchar_obstext(r->args))) { + /* + * We have a raw control character or a ' ' in r->args. + * Correct encoding was missed. + */ + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10411) + "Rewritten query string contains control " + "characters or spaces"); + return HTTP_FORBIDDEN; + } + + if (ACTION_STATUS == rulestatus) { + int n = r->status; + + r->status = HTTP_OK; + return n; + } + + l = strlen(r->filename); + if (l > 6 && strncmp(r->filename, "proxy:", 6) == 0) { + /* it should go on as an internal proxy request */ + + /* make sure the QUERY_STRING and + * PATH_INFO parts get incorporated + * (r->path_info was already appended by the + * rewriting engine because of the per-dir context!) + */ + if (r->args != NULL) { + /* see proxy_http:proxy_http_canon() */ + r->filename = apr_pstrcat(r->pool, r->filename, + "?", r->args, NULL); + } + + /* now make sure the request gets handled by the proxy handler */ + if (PROXYREQ_NONE == r->proxyreq) { + r->proxyreq = PROXYREQ_REVERSE; + } + r->handler = "proxy-server"; + + rewritelog((r, 1, dconf->directory, "go-ahead with proxy request " + "%s [OK]", r->filename)); + return OK; + } + else if ((skip = is_absolute_uri(r->filename, NULL)) > 0) { + /* it was finally rewritten to a remote URL */ + + /* because we are in a per-dir context + * first try to replace the directory with its base-URL + * if there is a base-URL available + */ + if (dconf->baseurl != NULL) { + /* skip 'scheme://' */ + cp = r->filename + skip; + + if ((cp = ap_strchr(cp, '/')) != NULL && *(++cp)) { + rewritelog((r, 2, dconf->directory, + "trying to replace prefix %s with %s", + dconf->directory, dconf->baseurl)); + + /* I think, that hack needs an explanation: + * well, here is it: + * mod_rewrite was written for unix systems, were + * absolute file-system paths start with a slash. + * URL-paths _also_ start with slashes, so they + * can be easily compared with system paths. + * + * the following assumes, that the actual url-path + * may be prefixed by the current directory path and + * tries to replace the system path with the RewriteBase + * URL. + * That assumption is true if we use a RewriteRule like + * + * RewriteRule ^foo bar [R] + * + * (see apply_rewrite_rule function) + * However on systems that don't have a / as system + * root this will never match, so we skip the / after the + * hostname and compare/substitute only the stuff after it. + * + * (note that cp was already increased to the right value) + */ + cp2 = subst_prefix_path(r, cp, (*dconf->directory == '/') + ? dconf->directory + 1 + : dconf->directory, + dconf->baseurl + 1); + if (strcmp(cp2, cp) != 0) { + *cp = '\0'; + r->filename = apr_pstrcat(r->pool, r->filename, + cp2, NULL); + } + } + } + + /* now prepare the redirect... */ + if (rulestatus != ACTION_NOESCAPE) { + rewritelog((r, 1, dconf->directory, "escaping %s for redirect", + r->filename)); + r->filename = escape_absolute_uri(r->pool, r->filename, skip); + } + + /* append the QUERY_STRING part */ + if (r->args) { + char *escaped_args = NULL; + int noescape = (rulestatus == ACTION_NOESCAPE || + (oargs && !strcmp(r->args, oargs))); + + r->filename = apr_pstrcat(r->pool, r->filename, "?", + noescape + ? r->args + : (escaped_args = ap_escape_uri(r->pool, r->args)), + NULL); + + rewritelog((r, 1, dconf->directory, "%s %s to query string for redirect %s", + noescape ? "copying" : "escaping", + r->args , + noescape ? "" : escaped_args)); + } + + /* determine HTTP redirect response code */ + if (ap_is_HTTP_REDIRECT(r->status)) { + n = r->status; + r->status = HTTP_OK; /* make Apache kernel happy */ + } + else { + n = HTTP_MOVED_TEMPORARILY; + } + + /* now do the redirection */ + apr_table_setn(r->headers_out, "Location", r->filename); + rewritelog((r, 1, dconf->directory, "redirect to %s [REDIRECT/%d]", + r->filename, n)); + return n; + } + else { + const char *tmpfilename = NULL; + /* it was finally rewritten to a local path */ + + /* if someone used the PASSTHROUGH flag in per-dir + * context we just ignore it. It is only useful + * in per-server context + */ + if (l > 12 && strncmp(r->filename, "passthrough:", 12) == 0) { + r->filename = apr_pstrdup(r->pool, r->filename+12); + } + + /* the filename must be either an absolute local path or an + * absolute local URL. + */ + if ( *r->filename != '/' + && !ap_os_is_path_absolute(r->pool, r->filename)) { + return HTTP_BAD_REQUEST; + } + + /* Check for deadlooping: + * At this point we KNOW that at least one rewriting + * rule was applied, but when the resulting URL is + * the same as the initial URL, we are not allowed to + * use the following internal redirection stuff because + * this would lead to a deadloop. + */ + if (ofilename != NULL && strcmp(r->filename, ofilename) == 0) { + rewritelog((r, 1, dconf->directory, "initial URL equal rewritten" + " URL: %s [IGNORING REWRITE]", r->filename)); + return OK; + } + + tmpfilename = r->filename; + + /* if there is a valid base-URL then substitute + * the per-dir prefix with this base-URL if the + * current filename still is inside this per-dir + * context. If not then treat the result as a + * plain URL + */ + if (dconf->baseurl != NULL) { + rewritelog((r, 2, dconf->directory, "trying to replace prefix " + "%s with %s", dconf->directory, dconf->baseurl)); + + r->filename = subst_prefix_path(r, r->filename, + dconf->directory, + dconf->baseurl); + } + else { + /* if no explicit base-URL exists we assume + * that the directory prefix is also a valid URL + * for this webserver and only try to remove the + * document_root if it is prefix + */ + if ((ccp = ap_document_root(r)) != NULL) { + /* strip trailing slash */ + l = strlen(ccp); + if (ccp[l-1] == '/') { + --l; + } + if (!strncmp(r->filename, ccp, l) && + r->filename[l] == '/') { + rewritelog((r, 2,dconf->directory, "strip document_root" + " prefix: %s -> %s", r->filename, + r->filename+l)); + + r->filename = apr_pstrdup(r->pool, r->filename+l); + } + } + } + + /* No base URL, or r->filename wasn't still under dconf->directory + * or, r->filename wasn't still under the document root. + * If there's a context document root AND a context prefix, and + * the context document root is a prefix of r->filename, replace. + * This allows a relative substitution on a path found by mod_userdir + * or mod_alias without baking in a RewriteBase. + */ + if (tmpfilename == r->filename && + !(dconf->options & OPTION_IGNORE_CONTEXT_INFO)) { + if ((ccp = ap_context_document_root(r)) != NULL) { + const char *prefix = ap_context_prefix(r); + if (prefix != NULL) { + rewritelog((r, 2, dconf->directory, "trying to replace " + "context docroot %s with context prefix %s", + ccp, prefix)); + r->filename = subst_prefix_path(r, r->filename, + ccp, prefix); + } + } + } + + apr_table_setn(r->notes, "redirect-keeps-vary", ""); + + /* now initiate the internal redirect */ + rewritelog((r, 1, dconf->directory, "internal redirect with %s " + "[INTERNAL REDIRECT]", r->filename)); + r->filename = apr_pstrcat(r->pool, "redirect:", r->filename, NULL); + r->handler = REWRITE_REDIRECT_HANDLER_NAME; + return OK; + } + } + else { + rewritelog((r, 1, dconf->directory, "pass through %s", r->filename)); + r->filename = ofilename; + return DECLINED; + } +} + +/* + * MIME-type hook + * [T=...,H=...] execution + */ +static int hook_mimetype(request_rec *r) +{ + const char *t; + + /* type */ + t = apr_table_get(r->notes, REWRITE_FORCED_MIMETYPE_NOTEVAR); + if (t && *t) { + rewritelog((r, 1, NULL, "force filename %s to have MIME-type '%s'", + r->filename, t)); + + ap_set_content_type(r, t); + } + + /* handler */ + t = apr_table_get(r->notes, REWRITE_FORCED_HANDLER_NOTEVAR); + if (t && *t) { + rewritelog((r, 1, NULL, "force filename %s to have the " + "Content-handler '%s'", r->filename, t)); + + r->handler = t; + } + + return OK; +} + + +/* + * "content" handler for internal redirects + */ +static int handler_redirect(request_rec *r) +{ + if (strcmp(r->handler, REWRITE_REDIRECT_HANDLER_NAME)) { + return DECLINED; + } + + /* just make sure that we are really meant! */ + if (strncmp(r->filename, "redirect:", 9) != 0) { + return DECLINED; + } + + /* now do the internal redirect */ + ap_internal_redirect(apr_pstrcat(r->pool, r->filename+9, + r->args ? "?" : NULL, r->args, NULL), r); + + /* and return gracefully */ + return OK; +} + + +/* + * +-------------------------------------------------------+ + * | | + * | Module paraphernalia + * | | + * +-------------------------------------------------------+ + */ + +static const command_rec command_table[] = { + AP_INIT_FLAG( "RewriteEngine", cmd_rewriteengine, NULL, OR_FILEINFO, + "On or Off to enable or disable (default) the whole " + "rewriting engine"), + AP_INIT_ITERATE( "RewriteOptions", cmd_rewriteoptions, NULL, OR_FILEINFO, + "List of option strings to set"), + AP_INIT_TAKE1( "RewriteBase", cmd_rewritebase, NULL, OR_FILEINFO, + "the base URL of the per-directory context"), + AP_INIT_RAW_ARGS("RewriteCond", cmd_rewritecond, NULL, OR_FILEINFO, + "an input string and a to be applied regexp-pattern"), + AP_INIT_RAW_ARGS("RewriteRule", cmd_rewriterule, NULL, OR_FILEINFO, + "an URL-applied regexp-pattern and a substitution URL"), + AP_INIT_TAKE23( "RewriteMap", cmd_rewritemap, NULL, RSRC_CONF, + "a mapname and a filename and options"), + { NULL } +}; + +static void ap_register_rewrite_mapfunc(char *name, rewrite_mapfunc_t *func) +{ + apr_hash_set(mapfunc_hash, name, strlen(name), (const void *)func); +} + +static void register_hooks(apr_pool_t *p) +{ + /* fixup after mod_proxy, so that the proxied url will not + * escaped accidentally by mod_proxy's fixup. + */ + static const char * const aszPre[]={ "mod_proxy.c", NULL }; + + /* make the hashtable before registering the function, so that + * other modules are prevented from accessing uninitialized memory. + */ + mapfunc_hash = apr_hash_make(p); + APR_REGISTER_OPTIONAL_FN(ap_register_rewrite_mapfunc); + + ap_hook_handler(handler_redirect, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_pre_config(pre_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_post_config(post_config, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_child_init(init_child, NULL, NULL, APR_HOOK_MIDDLE); + + ap_hook_fixups(hook_fixup, aszPre, NULL, APR_HOOK_FIRST); + ap_hook_fixups(hook_mimetype, NULL, NULL, APR_HOOK_LAST); + ap_hook_translate_name(hook_uri2file, NULL, NULL, APR_HOOK_FIRST); +} + + /* the main config structure */ +AP_DECLARE_MODULE(rewrite) = { + STANDARD20_MODULE_STUFF, + config_perdir_create, /* create per-dir config structures */ + config_perdir_merge, /* merge per-dir config structures */ + config_server_create, /* create per-server config structures */ + config_server_merge, /* merge per-server config structures */ + command_table, /* table of config file commands */ + register_hooks /* register hooks */ +}; + +/*EOF*/ |