diff options
Diffstat (limited to 'modules/mappers')
-rw-r--r-- | modules/mappers/mod_actions.c | 6 | ||||
-rw-r--r-- | modules/mappers/mod_negotiation.c | 8 | ||||
-rw-r--r-- | modules/mappers/mod_rewrite.c | 203 |
3 files changed, 170 insertions, 47 deletions
diff --git a/modules/mappers/mod_actions.c b/modules/mappers/mod_actions.c index ac9c3b7..5e398b5 100644 --- a/modules/mappers/mod_actions.c +++ b/modules/mappers/mod_actions.c @@ -182,8 +182,10 @@ static int action_handler(request_rec *r) return DECLINED; /* Second, check for actions (which override the method scripts) */ - action = r->handler ? r->handler : - ap_field_noparam(r->pool, r->content_type); + action = r->handler; + if (!action && AP_REQUEST_IS_TRUSTED_CT(r)) { + action = ap_field_noparam(r->pool, r->content_type); + } if (action && (t = apr_table_get(conf->action_types, action))) { int virtual = (*t++ == '0' ? 0 : 1); diff --git a/modules/mappers/mod_negotiation.c b/modules/mappers/mod_negotiation.c index c056b28..a528f81 100644 --- a/modules/mappers/mod_negotiation.c +++ b/modules/mappers/mod_negotiation.c @@ -1167,7 +1167,7 @@ static int read_types_multi(negotiation_state *neg) * might be doing. */ if (sub_req->handler && !sub_req->content_type) { - ap_set_content_type(sub_req, CGI_MAGIC_TYPE); + ap_set_content_type_ex(sub_req, CGI_MAGIC_TYPE, 1); } /* @@ -3003,14 +3003,14 @@ static int handle_map_file(request_rec *r) /* set MIME type and charset as negotiated */ if (best->mime_type && *best->mime_type) { if (best->content_charset && *best->content_charset) { - ap_set_content_type(r, apr_pstrcat(r->pool, + ap_set_content_type_ex(r, apr_pstrcat(r->pool, best->mime_type, "; charset=", best->content_charset, - NULL)); + NULL), 1); } else { - ap_set_content_type(r, apr_pstrdup(r->pool, best->mime_type)); + ap_set_content_type_ex(r, apr_pstrdup(r->pool, best->mime_type), 1); } } diff --git a/modules/mappers/mod_rewrite.c b/modules/mappers/mod_rewrite.c index bbcc11b..3fc2baf 100644 --- a/modules/mappers/mod_rewrite.c +++ b/modules/mappers/mod_rewrite.c @@ -177,6 +177,8 @@ static const char* really_last_key = "rewrite_really_last"; #define RULEFLAG_QSLAST (1<<19) #define RULEFLAG_QSNONE (1<<20) /* programattic only */ #define RULEFLAG_ESCAPECTLS (1<<21) +#define RULEFLAG_UNSAFE_PREFIX_STAT (1<<22) +#define RULEFLAG_UNSAFE_ALLOW3F (1<<23) /* return code of the rewrite rule * the result may be escaped - or not @@ -184,7 +186,7 @@ static const char* really_last_key = "rewrite_really_last"; #define ACTION_NORMAL (1<<0) #define ACTION_NOESCAPE (1<<1) #define ACTION_STATUS (1<<2) - +#define ACTION_STATUS_SET (1<<3) #define MAPTYPE_TXT (1<<0) #define MAPTYPE_DBM (1<<1) @@ -208,6 +210,7 @@ static const char* really_last_key = "rewrite_really_last"; #define OPTION_IGNORE_INHERIT (1<<8) #define OPTION_IGNORE_CONTEXT_INFO (1<<9) #define OPTION_LEGACY_PREFIX_DOCROOT (1<<10) +#define OPTION_UNSAFE_PREFIX_STAT (1<<12) #ifndef RAND_MAX #define RAND_MAX 32767 @@ -301,6 +304,14 @@ typedef enum { CONDPAT_AP_EXPR } pattern_type; +typedef enum { + RULE_RC_NOMATCH = 0, /* the rule didn't match */ + RULE_RC_MATCH = 1, /* a matching rule w/ substitution */ + RULE_RC_NOSUB = 2, /* a matching rule w/ no substitution */ + RULE_RC_STATUS_SET = 3 /* a matching rule that has set an HTTP error + to be returned in r->status */ +} rule_return_type; + typedef struct { char *input; /* Input string of RewriteCond */ char *pattern; /* the RegExp pattern string */ @@ -642,6 +653,16 @@ static unsigned is_absolute_uri(char *uri, int *supportsqs) return 0; } +static int is_absolute_path(const char *path) +{ +#ifndef CASE_BLIND_FILESYSTEM + return (path[0] == '/'); +#else + return ((AP_IS_SLASH(path[0]) && path[1] == path[0]) + || (apr_isalpha(path[0]) && path[1] == ':' && AP_IS_SLASH(path[2]))); +#endif +} + static const char c2x_table[] = "0123456789abcdef"; static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix, @@ -927,10 +948,15 @@ static void fully_qualify_uri(request_rec *r) return; } +static int startsWith(request_rec *r, const char *haystack, const char *needle) { + int rc = (ap_strstr_c(haystack, needle) == haystack); + rewritelog((r, 5, NULL, "prefix_stat startsWith(%s, %s) %d", haystack, needle, rc)); + return rc; +} /* - * stat() only the first segment of a path + * stat() only the first segment of a path, and only if it matches the output of the last matching rule */ -static int prefix_stat(const char *path, apr_pool_t *pool) +static int prefix_stat(request_rec *r, const char *path, apr_pool_t *pool, rewriterule_entry *lastsub) { const char *curpath = path; const char *root; @@ -964,10 +990,36 @@ static int prefix_stat(const char *path, apr_pool_t *pool) apr_finfo_t sb; if (apr_stat(&sb, statpath, APR_FINFO_MIN, pool) == APR_SUCCESS) { - return 1; + if (!lastsub) { + rewritelog((r, 3, NULL, "prefix_stat no lastsub subst prefix %s", statpath)); + return 1; + } + + rewritelog((r, 3, NULL, "prefix_stat compare statpath %s and lastsub output %s STATOK %d ", + statpath, lastsub->output, lastsub->flags & RULEFLAG_UNSAFE_PREFIX_STAT)); + if (lastsub->flags & RULEFLAG_UNSAFE_PREFIX_STAT) { + return 1; + } + else { + const char *docroot = ap_document_root(r); + const char *context_docroot = ap_context_document_root(r); + /* + * As an example, path (r->filename) is /var/foo/bar/baz.html + * even if the flag is not set, we can accept a rule that + * began with a literal /var (stapath), or if the entire path + * starts with the docroot or context document root + */ + if (startsWith(r, lastsub->output, statpath) || + startsWith(r, path, docroot) || + ((docroot != context_docroot) && + startsWith(r, path, context_docroot))) { + return 1; + } + } } } + /* prefix will be added */ return 0; } @@ -3072,6 +3124,9 @@ static const char *cmd_rewriteoptions(cmd_parms *cmd, else if (!strcasecmp(w, "legacyprefixdocroot")) { options |= OPTION_LEGACY_PREFIX_DOCROOT; } + else if (!strcasecmp(w, "UnsafePrefixStat")) { + options |= OPTION_UNSAFE_PREFIX_STAT; + } else { return apr_pstrcat(cmd->pool, "RewriteOptions: unknown option '", w, "'", NULL); @@ -3780,6 +3835,18 @@ static const char *cmd_rewriterule_setflag(apr_pool_t *p, void *_cfg, ++error; } break; + case 'u': + case 'U': + if (!strcasecmp(key, "nsafePrefixStat")){ + cfg->flags |= (RULEFLAG_UNSAFE_PREFIX_STAT); + } + else if(!strcasecmp(key, "nsafeAllow3F")) { + cfg->flags |= RULEFLAG_UNSAFE_ALLOW3F; + } + else { + ++error; + } + break; default: ++error; break; @@ -4138,7 +4205,8 @@ static APR_INLINE void force_type_handler(rewriterule_entry *p, /* * Apply a single RewriteRule */ -static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) +static rule_return_type apply_rewrite_rule(rewriterule_entry *p, + rewrite_ctx *ctx) { ap_regmatch_t regmatch[AP_MAX_REG_MATCH]; apr_array_header_t *rewriteconds; @@ -4189,7 +4257,7 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) 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; + return RULE_RC_NOMATCH; } /* It matched, wow! Now it's time to prepare the context structure for @@ -4240,7 +4308,7 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) } } else if (!rc) { - return 0; + return RULE_RC_NOMATCH; } /* If some HTTP header was involved in the condition, remember it @@ -4260,6 +4328,15 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) newuri = do_expand(p->output, ctx, p); rewritelog((r, 2, ctx->perdir, "rewrite '%s' -> '%s'", ctx->uri, newuri)); + if (!(p->flags & RULEFLAG_UNSAFE_ALLOW3F) && + ap_strcasestr(r->unparsed_uri, "%3f") && + ap_strchr_c(newuri, '?')) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10508) + "Unsafe URL with %%3f URL rewritten without " + "UnsafeAllow3F"); + r->status = HTTP_FORBIDDEN; + return RULE_RC_STATUS_SET; + } } /* expand [E=var:val] and [CO=<cookie>] */ @@ -4277,7 +4354,35 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) r->status = p->forced_responsecode; } - return 2; + return RULE_RC_NOSUB; + } + + /* 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 (!is_proxyreq + && !is_absolute_path(newuri) + && !AP_IS_SLASH(*newuri) + && !is_absolute_uri(newuri, NULL)) { + if (ctx->perdir) { + rewritelog((r, 3, ctx->perdir, "add per-dir prefix: %s -> %s%s", + newuri, ctx->perdir, newuri)); + newuri = apr_pstrcat(r->pool, ctx->perdir, newuri, NULL); + } + else if (!(p->flags & (RULEFLAG_PROXY | RULEFLAG_FORCEREDIRECT))) { + /* Not an absolute URI-path and the scheme (if any) is unknown, + * and it won't be passed to fully_qualify_uri() below either, + * so add an implicit '/' prefix. This avoids potentially a common + * rule like "RewriteRule ^/some/path(.*) $1" that is given a path + * like "/some/pathscheme:..." to produce the fully qualified URL + * "scheme:..." which could be misinterpreted later. + */ + rewritelog((r, 3, ctx->perdir, "add root prefix: %s -> /%s", + newuri, newuri)); + + newuri = apr_pstrcat(r->pool, "/", newuri, NULL); + } } /* Now adjust API's knowledge about r->filename and r->args */ @@ -4289,18 +4394,6 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) splitout_queryargs(r, p->flags); - /* Add the previously stripped per-directory location prefix, unless - * (1) it's an absolute URL path and - * (2) it's a full qualified URL - */ - if ( ctx->perdir && !is_proxyreq && *r->filename != '/' - && !is_absolute_uri(r->filename, NULL)) { - rewritelog((r, 3, ctx->perdir, "add per-dir prefix: %s -> %s%s", - r->filename, ctx->perdir, r->filename)); - - r->filename = apr_pstrcat(r->pool, ctx->perdir, r->filename, NULL); - } - /* If this rule is forced for proxy throughput * (`RewriteRule ... ... [P]') then emulate mod_proxy's * URL-to-filename handler to be sure mod_proxy is triggered @@ -4329,7 +4422,7 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) r->filename)); r->filename = apr_pstrcat(r->pool, "proxy:", r->filename, NULL); - return 1; + return RULE_RC_MATCH; } /* If this rule is explicitly forced for HTTP redirection @@ -4344,7 +4437,7 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) r->filename)); r->status = p->forced_responsecode; - return 1; + return RULE_RC_MATCH; } /* Special Rewriting Feature: Self-Reduction @@ -4366,7 +4459,7 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) "with %s", p->forced_responsecode, r->filename)); r->status = p->forced_responsecode; - return 1; + return RULE_RC_MATCH; } /* Finally remember the forced mime-type */ @@ -4375,7 +4468,7 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) /* Puuhhhhhhhh... WHAT COMPLICATED STUFF ;_) * But now we're done for this particular rule. */ - return 1; + return RULE_RC_MATCH; } /* @@ -4383,13 +4476,13 @@ static int apply_rewrite_rule(rewriterule_entry *p, rewrite_ctx *ctx) * i.e. a list of rewrite rules */ static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules, - char *perdir) + char *perdir, rewriterule_entry **lastsub) { rewriterule_entry *entries; rewriterule_entry *p; int i; int changed; - int rc; + rule_return_type rc; int s; rewrite_ctx *ctx; int round = 1; @@ -4397,6 +4490,7 @@ static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules, ctx = apr_palloc(r->pool, sizeof(*ctx)); ctx->perdir = perdir; ctx->r = r; + *lastsub = NULL; /* * Iterate over all existing rules @@ -4424,7 +4518,12 @@ static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules, ctx->vary = NULL; rc = apply_rewrite_rule(p, ctx); - if (rc) { + if (rc != RULE_RC_NOMATCH) { + + if (!(p->flags & RULEFLAG_NOSUB)) { + rewritelog((r, 2, perdir, "setting lastsub to rule with output %s", p->output)); + *lastsub = p; + } /* Catch looping rules with pathinfo growing unbounded */ if ( strlen( r->filename ) > 2*r->server->limit_req_line ) { @@ -4444,6 +4543,12 @@ static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules, apr_table_merge(r->headers_out, "Vary", ctx->vary); } + + /* Error while evaluating rule, r->status set */ + if (RULE_RC_STATUS_SET == rc) { + return ACTION_STATUS_SET; + } + /* * The rule sets the response code (implies match-only) */ @@ -4454,7 +4559,7 @@ static int apply_rewrite_list(request_rec *r, apr_array_header_t *rewriterules, /* * Indicate a change if this was not a match-only rule. */ - if (rc != 2) { + if (rc != RULE_RC_NOSUB) { changed = ((p->flags & RULEFLAG_NOESCAPE) ? ACTION_NOESCAPE : ACTION_NORMAL); } @@ -4643,6 +4748,7 @@ static int hook_uri2file(request_rec *r) int rulestatus; void *skipdata; const char *oargs; + rewriterule_entry *lastsub = NULL; /* * retrieve the config structures @@ -4754,7 +4860,7 @@ static int hook_uri2file(request_rec *r) /* * now apply the rules ... */ - rulestatus = apply_rewrite_list(r, conf->rewriterules, NULL); + rulestatus = apply_rewrite_list(r, conf->rewriterules, NULL, &lastsub); apr_table_setn(r->notes, "mod_rewrite_rewritten", apr_psprintf(r->pool,"%d",rulestatus)); } @@ -4792,6 +4898,9 @@ static int hook_uri2file(request_rec *r) r->status = HTTP_OK; return n; } + else if (ACTION_STATUS_SET == rulestatus) { + return r->status; + } if (to_proxyreq) { /* it should be go on as an internal proxy request */ @@ -4911,23 +5020,29 @@ static int hook_uri2file(request_rec *r) return HTTP_BAD_REQUEST; } - /* if there is no valid prefix, we call - * the translator from the core and - * prefix the filename with document_root + /* We have r->filename as a path in a server-context rewrite without + * the PT flag. The historical behavior is to treat it as a verbatim + * filesystem path iff the first component of the path exists and is + * readable by httpd. Otherwise, it is interpreted as DocumentRoot + * relative. * * 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 + * - If we always prefix with document_root + * then no absolute path can could ever be used in + * a substitution. e.g. emulating an Alias. + * - If we never 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 + * this is also bad. + * - Later addition: This part is questionable. + * If we had never prefixed, users would just + * need %{DOCUMENT_ROOT} in substitutions or the + * [PT] flag. * * BUT: - * Under real Unix systems this is no problem, + * Under real Unix systems this is no perf problem, * because we only do stat() on the first directory * and this gets cached by the kernel for along time! */ @@ -4936,7 +5051,9 @@ static int hook_uri2file(request_rec *r) uri_reduced = apr_table_get(r->notes, "mod_rewrite_uri_reduced"); } - if (!prefix_stat(r->filename, r->pool) || uri_reduced != NULL) { + if (!prefix_stat(r, r->filename, r->pool, + conf->options & OPTION_UNSAFE_PREFIX_STAT ? NULL : lastsub) + || uri_reduced != NULL) { int res; char *tmp = r->uri; @@ -4981,6 +5098,7 @@ static int hook_fixup(request_rec *r) char *ofilename, *oargs; int is_proxyreq; void *skipdata; + rewriterule_entry *lastsub; dconf = (rewrite_perdir_conf *)ap_get_module_config(r->per_dir_config, &rewrite_module); @@ -5065,7 +5183,7 @@ static int hook_fixup(request_rec *r) /* * now apply the rules ... */ - rulestatus = apply_rewrite_list(r, dconf->rewriterules, dconf->directory); + rulestatus = apply_rewrite_list(r, dconf->rewriterules, dconf->directory, &lastsub); if (rulestatus) { unsigned skip_absolute = is_absolute_uri(r->filename, NULL); int to_proxyreq = 0; @@ -5094,6 +5212,9 @@ static int hook_fixup(request_rec *r) r->status = HTTP_OK; return n; } + else if (ACTION_STATUS_SET == rulestatus) { + return r->status; + } if (to_proxyreq) { /* it should go on as an internal proxy request */ @@ -5333,7 +5454,7 @@ static int hook_mimetype(request_rec *r) rewritelog((r, 1, NULL, "force filename %s to have MIME-type '%s'", r->filename, t)); - ap_set_content_type(r, t); + ap_set_content_type_ex(r, t, 1); } /* handler */ |