| /* 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 "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_vhost.h" |
| #include "util_mutex.h" |
| |
| #include "mod_ssl.h" |
| |
| #include "mod_rewrite.h" |
| #include "ap_expr.h" |
| |
| #if APR_CHARSET_EBCDIC |
| #include "util_charset.h" |
| #endif |
| |
| 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) |
| |
| /* 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) |
| |
| #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 10000 |
| #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 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 APR_OPTIONAL_FN_TYPE(ssl_var_lookup) *rewrite_ssl_lookup = NULL; |
| static APR_OPTIONAL_FN_TYPE(ssl_is_https) *rewrite_is_https = NULL; |
| 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; |
| } |
| |
| 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 qsappend, int qsdiscard, |
| int qslast) |
| { |
| char *q; |
| int split; |
| |
| /* don't touch, unless it's a scheme for which a query string makes sense. |
| * See RFC 1738 and RFC 2368. |
| */ |
| if (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, '?') : ap_strchr(r->filename, '?'); |
| |
| 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>")); |
| } |
| |
| return; |
| } |
| |
| /* |
| * 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)) { |
| rewritelog((r, 3, NULL, "reduce %s -> %s", r->filename, url)); |
| r->filename = apr_pstrdup(r->pool, url); |
| } |
| } |
| |
| 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; |
| } |
| |
| 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; |
| } |
| |
| 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) |
| { |
| apr_dbm_t *dbmfp = NULL; |
| apr_datum_t dbmkey; |
| apr_datum_t dbmval; |
| char *value; |
| apr_status_t rv; |
| |
| 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; |
| } |
| |
| 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); |
| |
| if (val) { |
| 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) && rewrite_ssl_lookup) { |
| result = rewrite_ssl_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 = rewrite_is_https && rewrite_is_https(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_useragent_host(r, 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 *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; |
| |
| 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); |
| |
| 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 */ |
| /* XXX See if there are any rewrite map programs before creating |
| * the mutex. |
| */ |
| 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 char *parseargline(apr_pool_t *p, char *str, char **a1, char **a2, 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 "bad argument line: at least two arguments required"; |
| } |
| *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 */ |
| return NULL; |
| } |
| *str++ = '\0'; |
| |
| while (apr_isspace(*str)) { |
| ++str; |
| } |
| |
| if (!*str) { |
| *a3 = NULL; /* 3rd argument is still optional */ |
| return NULL; |
| } |
| |
| /* |
| * 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'; |
| |
| if (**a3 != '[') { |
| return apr_psprintf(p, "bad flag delimiters: third argument must begin " |
| "with '[' but found '%c' - too many arguments or rogue " |
| "whitespace?", **a3); |
| } |
| else if ((*a3)[strlen(*a3)-1] != ']') { |
| return apr_psprintf(p, "bad flag delimiters: third argument must end " |
| "with ']' but found '%c' - unintended whitespace within the " |
| "flags definition?", (*a3)[strlen(*a3)-1]); |
| } |
| return NULL; |
| } |
| |
| 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 { |
| 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]; |
| 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; |
| /* This should have been checked before, but just in case... */ |
| 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, *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 ((err = parseargline(cmd->pool, str, &a1, &a2, &a3))) { |
| return apr_psprintf(cmd->pool, "RewriteCond: %s " |
| "(TestString=%s, CondPattern=%s, flags=%s)", |
| err, a1, a2, a3); |
| } |
| |
| /* 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, *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 ((err = parseargline(cmd->pool, str, &a1, &a2, &a3))) { |
| return apr_psprintf(cmd->pool, "RewriteRule: %s " |
| "(pattern='%s', substitution='%s', flags='%s')", |
| err, a1, a2, a3); |
| } |
| |
| /* 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; |
| } |
| |
| /* 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 underlaying 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 unnessesary 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 & RULEFLAG_QSAPPEND, |
| p->flags & RULEFLAG_QSDISCARD, |
| p->flags & RULEFLAG_QSLAST); |
| |
| /* 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) { |
| /* 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; |
| |
| 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); |
| |
| 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; |
| } |
| } |
| } |
| |
| rewrite_ssl_lookup = APR_RETRIEVE_OPTIONAL_FN(ssl_var_lookup); |
| rewrite_is_https = APR_RETRIEVE_OPTIONAL_FN(ssl_is_https); |
| |
| 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 (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 */ |
| |
| /* 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 (!prefix_stat(r->filename, r->pool)) { |
| 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 explicitely 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 inital 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 (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); |
| } |
| |
| 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; |
| } |
| |
| /* 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); |
| } |
| } |
| } |
| |
| /* 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*/ |