--- server/util.c (revision 577564) +++ server/util.c (working copy) @@ -1589,6 +1589,65 @@ return OK; } +AP_DECLARE(int) ap_unescape_url_keepenc(char *url, char* enc) +{ + register int badesc, badpath; + char *x, *y; +#ifdef CASE_BLIND_FILESYSTEM + int slash_allowed = enc && strchr(enc, '/') && strchr(enc,'\\'); +#else + int slash_allowed = enc && strchr(enc,'/'); +#endif + + badesc = 0; + badpath = 0; + /* Initial scan for first '%'. Don't bother writing values before + * seeing a '%' */ + y = strchr(url, '%'); + if (y == NULL) { + return OK; + } + for (x = y; *y; ++x, ++y) { + if (*y != '%') { + *x = *y; + } + else { + if (!apr_isxdigit(*(y + 1)) || !apr_isxdigit(*(y + 2))) { + badesc = 1; + *x = '%'; + } + else { + char decoded; + decoded = x2c(y + 1); + if (decoded == '\0') { + badpath = 1; + } + if (enc && (strchr(enc,decoded) != 0)) { + int j; + for (j=0; j<3; ++j) + *x++ = *y++; + } + else { + *x = decoded; + y += 2; + if (!slash_allowed && IS_SLASH(*x)) + badpath = 1; + } + } + } + } + *x = '\0'; + if (badesc) { + return HTTP_BAD_REQUEST; + } + else if (badpath) { + return HTTP_NOT_FOUND; + } + else { + return OK; + } +} + AP_DECLARE(int) ap_unescape_url_keep2f(char *url) { register int badesc, badpath; --- server/core.c (revision 577564) +++ server/core.c (working copy) @@ -155,6 +155,7 @@ conf->enable_mmap = ENABLE_MMAP_UNSET; conf->enable_sendfile = ENABLE_SENDFILE_UNSET; conf->allow_encoded_slashes = 0; + conf->allow_encoded_chars = 0; return (void *)conf; } @@ -412,6 +413,9 @@ } conf->allow_encoded_slashes = new->allow_encoded_slashes; + /* No merging for now */ + if (new->allow_encoded_chars) + conf->allow_encoded_chars = new->allow_encoded_chars; return (void*)conf; } @@ -2377,6 +2381,20 @@ return NULL; } +static const char *set_allow_encchars(cmd_parms *cmd, void *d_, + const char *arg) +{ + core_dir_config *d = d_; + const char *err = ap_check_cmd_context(cmd, NOT_IN_LIMIT); + + if (err != NULL) { + return err; + } + if (arg) + d->allow_encoded_chars = apr_pstrdup(cmd->pool, arg); + return NULL; +} + static const char *set_hostname_lookups(cmd_parms *cmd, void *d_, const char *arg) { @@ -3340,6 +3358,8 @@ "output filter name followed by one or more content-types"), AP_INIT_FLAG("AllowEncodedSlashes", set_allow2f, NULL, RSRC_CONF, "Allow URLs containing '/' encoded as '%2F'"), +AP_INIT_TAKE1("AllowEncodedChars", set_allow_encchars, NULL, RSRC_CONF, + "Allow URLs containing encoded chars to pass through"), /* * These are default configuration directives that mpms can/should --- server/request.c (revision 577564) +++ server/request.c (working copy) @@ -94,6 +94,16 @@ } } +static char* merge_escapes(int encoded_slashes, char* encoded_chars, + apr_pool_t *p) { + char* echars = encoded_slashes ? "\\/" : 0; + if (encoded_chars) { + echars = echars ? + apr_pstrcat(p, echars, encoded_chars) : encoded_chars; + } + return echars; +} + /* This is the master logic for processing requests. Do NOT duplicate * this logic elsewhere, or the security model will be broken by future * API changes. Each phase must be individually optimized to pick up @@ -106,22 +116,16 @@ /* Ignore embedded %2F's in path for proxy requests */ if (!r->proxyreq && r->parsed_uri.path) { - core_dir_config *d; - d = ap_get_module_config(r->per_dir_config, &core_module); - if (d->allow_encoded_slashes) { - access_status = ap_unescape_url_keep2f(r->parsed_uri.path); - } - else { - access_status = ap_unescape_url(r->parsed_uri.path); - } + core_dir_config *d = ap_get_module_config(r->per_dir_config, &core_module); + char* echars = merge_escapes(d->allow_encoded_slashes, + d->allow_encoded_chars, r->pool); + access_status = ap_unescape_url_keepenc(r->parsed_uri.path, echars); if (access_status) { if (access_status == HTTP_NOT_FOUND) { - if (! d->allow_encoded_slashes) { - ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, - "found %%2f (encoded '/') in URI " - "(decoded='%s'), returning 404", - r->parsed_uri.path); - } + ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, + "found illegal chars %%2f (encoded '/') or \\0 in URI " + "(decoded='%s'), returning 404", + r->parsed_uri.path); } return access_status; } --- include/httpd.h (revision 577564) +++ include/httpd.h (working copy) @@ -1438,6 +1438,14 @@ AP_DECLARE(int) ap_unescape_url_keep2f(char *url); /** + * Unescape a URL, but leaving specified chars escaped + * @param url The url to unescape + * @param enc The chars to keep + * @return 0 on success, non-zero otherwise + */ +AP_DECLARE(int) ap_unescape_url_keepenc(char *url, char *enc); + +/** * Convert all double slashes to single slashes * @param name The string to convert */ --- include/http_core.h (revision 577564) +++ include/http_core.h (working copy) @@ -540,6 +540,9 @@ #define USE_CANONICAL_PHYS_PORT_ON (1) #define USE_CANONICAL_PHYS_PORT_UNSET (2) unsigned use_canonical_phys_port : 2; + + char *allow_encoded_chars; /* Allow URLs containing given encoded chars + to pass through */ } core_dir_config;