diff options
author | Stefan Eissing <icing@apache.org> | 2021-08-10 08:27:18 +0000 |
---|---|---|
committer | Stefan Eissing <icing@apache.org> | 2021-08-10 08:27:18 +0000 |
commit | 6a5d3e006b8dc8aca1a267a8607864e1c3607f61 (patch) | |
tree | e83ad495a2a46d6f08ee77859d95eb3a9432ddc7 | |
parent | 8dc8b50fe67111c2f18a3842fc25d0183061638c (diff) | |
download | httpd-6a5d3e006b8dc8aca1a267a8607864e1c3607f61.tar.gz |
Merged /httpd/httpd/trunk:r1879074-1879080,1879094-1879095,1879110-1879112,1879114,1879116-1879117,1879137,1879144-1879145,1879147,1879149,1879235,1879360
back port the mapping=servlet proxy logic.
git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1892161 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r-- | docs/manual/developer/modguide.xml | 1 | ||||
-rw-r--r-- | docs/manual/mod/mod_log_debug.xml | 1 | ||||
-rw-r--r-- | docs/manual/mod/mod_lua.xml | 28 | ||||
-rw-r--r-- | include/ap_mmn.h | 6 | ||||
-rw-r--r-- | include/http_request.h | 12 | ||||
-rw-r--r-- | include/httpd.h | 15 | ||||
-rw-r--r-- | modules/dav/main/util.c | 8 | ||||
-rw-r--r-- | modules/examples/mod_example_hooks.c | 17 | ||||
-rw-r--r-- | modules/generators/mod_autoindex.c | 5 | ||||
-rw-r--r-- | modules/generators/mod_info.c | 1 | ||||
-rw-r--r-- | modules/loggers/mod_log_debug.c | 8 | ||||
-rw-r--r-- | modules/lua/mod_lua.c | 31 | ||||
-rw-r--r-- | modules/proxy/mod_proxy.c | 334 | ||||
-rw-r--r-- | modules/proxy/mod_proxy.h | 10 | ||||
-rw-r--r-- | server/request.c | 129 | ||||
-rw-r--r-- | server/util.c | 158 |
16 files changed, 633 insertions, 131 deletions
diff --git a/docs/manual/developer/modguide.xml b/docs/manual/developer/modguide.xml index 63f3d0a9d3..4ebd7fa79b 100644 --- a/docs/manual/developer/modguide.xml +++ b/docs/manual/developer/modguide.xml @@ -237,6 +237,7 @@ can create. Some other ways of hooking are: <li><code>ap_hook_child_init</code>: Place a hook that executes when a child process is spawned (commonly used for initializing modules after the server has forked)</li> <li><code>ap_hook_pre_config</code>: Place a hook that executes before any configuration data has been read (very early hook)</li> <li><code>ap_hook_post_config</code>: Place a hook that executes after configuration has been parsed, but before the server has forked</li> +<li><code>ap_hook_pre_translate_name</code>: Place a hook that executes when a URI needs to be translated into a filename on the server, before decoding</li> <li><code>ap_hook_translate_name</code>: Place a hook that executes when a URI needs to be translated into a filename on the server (think <code>mod_rewrite</code>)</li> <li><code>ap_hook_quick_handler</code>: Similar to <code>ap_hook_handler</code>, except it is run before any other request hooks (translation, auth, fixups etc)</li> <li><code>ap_hook_log_transaction</code>: Place a hook that executes when the server is about to add a log entry of the current request</li> diff --git a/docs/manual/mod/mod_log_debug.xml b/docs/manual/mod/mod_log_debug.xml index e992533461..c2280b4d0a 100644 --- a/docs/manual/mod/mod_log_debug.xml +++ b/docs/manual/mod/mod_log_debug.xml @@ -103,6 +103,7 @@ <table border="1" style="zebra"> <columnspec><column width="1"/></columnspec> <tr><th>Name</th></tr> + <tr><td><code>pre_translate_name</code></td></tr> <tr><td><code>translate_name</code></td></tr> <tr><td><code>type_checker</code></td></tr> <tr><td><code>quick_handler</code></td></tr> diff --git a/docs/manual/mod/mod_lua.xml b/docs/manual/mod/mod_lua.xml index 3d853f9101..8c1195f8de 100644 --- a/docs/manual/mod/mod_lua.xml +++ b/docs/manual/mod/mod_lua.xml @@ -216,6 +216,13 @@ performing access control, or setting mime types:</p> been mapped to a host or virtual host</td> </tr> <tr> + <td>Pre-Translate name</td> + <td><directive module="mod_lua">LuaHookPreTranslateName</directive></td> + <td>This phase translates the requested URI into a filename on the + system, before decoding occurs. Modules such as <module>mod_proxy</module> + can operate in this phase.</td> + </tr> + <tr> <td>Translate name</td> <td><directive module="mod_lua">LuaHookTranslateName</directive></td> <td>This phase translates the requested URI into a filename on the @@ -439,7 +446,7 @@ end <td>string</td> <td>yes</td> <td>The file name that the request maps to, f.x. /www/example.com/foo.txt. This can be - changed in the translate-name or map-to-storage phases of a request to allow the + changed in the pre-translate-name, translate-name or map-to-storage phases of a request to allow the default handler (or script handlers) to serve a different file than what was requested.</td> </tr> <tr> @@ -538,7 +545,7 @@ end <td>string</td> <td>yes</td> <td>Denotes whether this is a proxy request or not. This value is generally set in - the post_read_request/translate_name phase of a request.</td> + the post_read_request/pre_translate_name/translate_name phase of a request.</td> </tr> <tr> <td><code>range</code></td> @@ -1495,6 +1502,23 @@ end </directivesynopsis> <directivesynopsis> +<name>LuaHookPreTranslate</name> +<description>Provide a hook for the pre_translate phase of a request +processing</description> +<syntax>LuaHookPreTranslate /path/to/lua/script.lua hook_function_name</syntax> +<contextlist><context>server config</context><context>virtual host</context> +<context>directory</context><context>.htaccess</context> +</contextlist> +<override>All</override> +<usage> +<p> + Just like LuaHookTranslateName, but executed at the pre_translate phase, + where the URI-path is not percent decoded. +</p> +</usage> +</directivesynopsis> + +<directivesynopsis> <name>LuaHookFixups</name> <description>Provide a hook for the fixups phase of a request processing</description> diff --git a/include/ap_mmn.h b/include/ap_mmn.h index 239273762f..a156e4041c 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -563,6 +563,10 @@ * 20120211.107 (2.4.49-dev) Add ap_parse_request_line() and * ap_check_request_header() * 20120211.108 (2.4.49-dev) Add ajp_handle_cping_cpong + * 20120211.109 (2.4.49-dev) Add ap_normalize_path(), + * pre_translate_name hook and + * Add map_encoded_one and map_encoded_all bits to + * proxy_server_conf. */ #define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */ @@ -570,7 +574,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 20120211 #endif -#define MODULE_MAGIC_NUMBER_MINOR 108 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 109 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a diff --git a/include/http_request.h b/include/http_request.h index 5f05668177..7e8bfad122 100644 --- a/include/http_request.h +++ b/include/http_request.h @@ -364,6 +364,18 @@ AP_DECLARE_HOOK(int,create_request,(request_rec *r)) /** * This hook allow modules an opportunity to translate the URI into an + * actual filename, before URL decoding happens. + * @param r The current request + * @return DECLINED to let other modules handle the pre-translation, + * OK if it was handled and no other module should process it, + * DONE if no further transformation should happen on the URI, + * HTTP_... in case of error. + * @ingroup hooks + */ +AP_DECLARE_HOOK(int,pre_translate_name,(request_rec *r)) + +/** + * This hook allow modules an opportunity to translate the URI into an * actual filename. If no modules do anything special, the server's default * rules will be followed. * @param r The current request diff --git a/include/httpd.h b/include/httpd.h index 14f15b5c9b..02cde256ed 100644 --- a/include/httpd.h +++ b/include/httpd.h @@ -1762,6 +1762,21 @@ AP_DECLARE(void) ap_no2slash(char *name); */ AP_DECLARE(void) ap_no2slash_ex(char *name, int is_fs_path); +#define AP_NORMALIZE_ALLOW_RELATIVE (1u << 0) +#define AP_NORMALIZE_NOT_ABOVE_ROOT (1u << 1) +#define AP_NORMALIZE_DECODE_UNRESERVED (1u << 2) +#define AP_NORMALIZE_MERGE_SLASHES (1u << 3) +#define AP_NORMALIZE_DROP_PARAMETERS (1u << 4) + +/** + * Remove all ////, /./ and /xx/../ substrings from a path, and more + * depending on passed in flags. + * @param path The path to normalize + * @param flags bitmask of AP_NORMALIZE_* flags + * @return non-zero on success + */ +AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags); + /** * Remove all ./ and xx/../ substrings from a file name. Also remove * any leading ../ or /../ substrings. diff --git a/modules/dav/main/util.c b/modules/dav/main/util.c index e21f626068..08ebe2764e 100644 --- a/modules/dav/main/util.c +++ b/modules/dav/main/util.c @@ -664,7 +664,13 @@ static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih) /* note that parsed_uri.path is allocated; we can trash it */ /* clean up the URI a bit */ - ap_getparents(parsed_uri.path); + if (!ap_normalize_path(parsed_uri.path, + AP_NORMALIZE_NOT_ABOVE_ROOT | + AP_NORMALIZE_DECODE_UNRESERVED)) { + return dav_new_error(r->pool, HTTP_BAD_REQUEST, + DAV_ERR_IF_TAGGED, rv, + "Invalid URI path tagged If-header."); + } /* the resources we will compare to have unencoded paths */ if (ap_unescape_url(parsed_uri.path) != OK) { diff --git a/modules/examples/mod_example_hooks.c b/modules/examples/mod_example_hooks.c index 2ab945eb3a..f7ef5a5ccf 100644 --- a/modules/examples/mod_example_hooks.c +++ b/modules/examples/mod_example_hooks.c @@ -1175,6 +1175,22 @@ static int x_post_read_request(request_rec *r) /* * This routine gives our module an opportunity to translate the URI into an + * actual filename, before URL decoding happens. + * + * This is a RUN_FIRST hook. + */ +static int x_pre_translate_name(request_rec *r) +{ + /* + * We don't actually *do* anything here, except note the fact that we were + * called. + */ + trace_request(r, "x_pre_translate_name()"); + return DECLINED; +} + +/* + * This routine gives our module an opportunity to translate the URI into an * actual filename. If we don't do anything special, the server's default * rules (Alias directives and the like) will continue to be followed. * @@ -1467,6 +1483,7 @@ static void x_register_hooks(apr_pool_t *p) ap_hook_log_transaction(x_log_transaction, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_http_scheme(x_http_scheme, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_default_port(x_default_port, NULL, NULL, APR_HOOK_MIDDLE); + ap_hook_pre_translate_name(x_pre_translate_name, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_translate_name(x_translate_name, NULL, NULL, APR_HOOK_MIDDLE); ap_hook_map_to_storage(x_map_to_storage, NULL,NULL, APR_HOOK_MIDDLE); ap_hook_header_parser(x_header_parser, NULL, NULL, APR_HOOK_MIDDLE); diff --git a/modules/generators/mod_autoindex.c b/modules/generators/mod_autoindex.c index 28ed85a8f3..cb4460357c 100644 --- a/modules/generators/mod_autoindex.c +++ b/modules/generators/mod_autoindex.c @@ -1266,8 +1266,9 @@ static struct ent *make_parent_entry(apr_int32_t autoindex_opts, if (!(p->name = ap_make_full_path(r->pool, r->uri, "../"))) { return (NULL); } - ap_getparents(p->name); - if (!*p->name) { + if (!ap_normalize_path(p->name, AP_NORMALIZE_ALLOW_RELATIVE | + AP_NORMALIZE_NOT_ABOVE_ROOT) + || p->name[0] == '\0') { return (NULL); } diff --git a/modules/generators/mod_info.c b/modules/generators/mod_info.c index cf1a1fd8c5..b044273062 100644 --- a/modules/generators/mod_info.c +++ b/modules/generators/mod_info.c @@ -322,6 +322,7 @@ static const hook_lookup_t request_hooks[] = { {"HTTP Scheme", ap_hook_get_http_scheme}, {"Default Port", ap_hook_get_default_port}, {"Quick Handler", ap_hook_get_quick_handler}, + {"Pre-Translate Name", ap_hook_get_pre_translate_name}, {"Translate Name", ap_hook_get_translate_name}, {"Map to Storage", ap_hook_get_map_to_storage}, {"Check Access", ap_hook_get_access_checker_ex}, diff --git a/modules/loggers/mod_log_debug.c b/modules/loggers/mod_log_debug.c index 8a6c1244f5..3f27a958de 100644 --- a/modules/loggers/mod_log_debug.c +++ b/modules/loggers/mod_log_debug.c @@ -49,6 +49,7 @@ static const char * const hooks[] = { "check_authn", /* 9 */ "check_authz", /* 10 */ "insert_filter", /* 11 */ + "pre_translate_name", /* 12 */ NULL }; @@ -109,6 +110,12 @@ static int log_debug_handler(request_rec *r) return DECLINED; } +static int log_debug_pre_translate_name(request_rec *r) +{ + do_debug_log(r, hooks[12]); + return DECLINED; +} + static int log_debug_translate_name(request_rec *r) { do_debug_log(r, hooks[3]); @@ -263,6 +270,7 @@ static void register_hooks(apr_pool_t *p) ap_hook_log_transaction(log_debug_log_transaction, NULL, NULL, APR_HOOK_FIRST); ap_hook_quick_handler(log_debug_quick_handler, NULL, NULL, APR_HOOK_FIRST); ap_hook_handler(log_debug_handler, NULL, NULL, APR_HOOK_FIRST); + ap_hook_pre_translate_name(log_debug_pre_translate_name, NULL, NULL, APR_HOOK_FIRST); ap_hook_translate_name(log_debug_translate_name, NULL, NULL, APR_HOOK_FIRST); ap_hook_map_to_storage(log_debug_map_to_storage, NULL, NULL, APR_HOOK_FIRST); ap_hook_fixups(log_debug_fixups, NULL, NULL, APR_HOOK_FIRST); diff --git a/modules/lua/mod_lua.c b/modules/lua/mod_lua.c index 23114304b3..665e2cddc7 100644 --- a/modules/lua/mod_lua.c +++ b/modules/lua/mod_lua.c @@ -1202,6 +1202,11 @@ static int lua_check_user_id_harness_last(request_rec *r) } */ +static int lua_pre_trans_name_harness(request_rec *r) +{ + return lua_request_rec_hook_harness(r, "pre_translate_name", APR_HOOK_MIDDLE); +} + static int lua_translate_name_harness_first(request_rec *r) { return lua_request_rec_hook_harness(r, "translate_name", AP_LUA_HOOK_FIRST); @@ -1274,6 +1279,21 @@ static int lua_quick_harness(request_rec *r, int lookup) return lua_request_rec_hook_harness(r, "quick", APR_HOOK_MIDDLE); } +static const char *register_pre_trans_name_hook(cmd_parms *cmd, void *_cfg, + const char *file, + const char *function) +{ + return register_named_file_function_hook("pre_translate_name", cmd, _cfg, file, + function, APR_HOOK_MIDDLE); +} + +static const char *register_pre_trans_name_block(cmd_parms *cmd, void *_cfg, + const char *line) +{ + return register_named_block_function_hook("pre_translate_name", cmd, _cfg, + line); +} + static const char *register_translate_name_hook(cmd_parms *cmd, void *_cfg, const char *file, const char *function, @@ -1842,6 +1862,14 @@ static const command_rec lua_commands[] = { AP_INIT_TAKE3("LuaAuthzProvider", register_authz_provider, NULL, RSRC_CONF|EXEC_ON_READ, "Provide an authorization provider"), + AP_INIT_TAKE2("LuaHookPreTranslateName", register_pre_trans_name_hook, NULL, + OR_ALL, + "Provide a hook for the pre_translate name phase of request processing"), + + AP_INIT_RAW_ARGS("<LuaHookPreTranslateName", register_pre_trans_name_block, NULL, + EXEC_ON_READ | OR_ALL, + "Provide a hook for the pre_translate name phase of request processing"), + AP_INIT_TAKE23("LuaHookTranslateName", register_translate_name_hook, NULL, OR_ALL, "Provide a hook for the translate name phase of request processing"), @@ -2092,6 +2120,9 @@ static void lua_register_hooks(apr_pool_t *p) APR_HOOK_MIDDLE); /* http_request.h hooks */ + ap_hook_pre_translate_name(lua_pre_trans_name_harness, NULL, NULL, + APR_HOOK_MIDDLE); + ap_hook_translate_name(lua_translate_name_harness_first, NULL, NULL, AP_LUA_HOOK_FIRST); ap_hook_translate_name(lua_translate_name_harness, NULL, NULL, diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c index 0f787a6b45..e8b0f173b5 100644 --- a/modules/proxy/mod_proxy.c +++ b/modules/proxy/mod_proxy.c @@ -17,6 +17,7 @@ #include "mod_proxy.h" #include "mod_core.h" #include "apr_optional.h" +#include "apr_strings.h" #include "scoreboard.h" #include "mod_status.h" #include "proxy_util.h" @@ -560,6 +561,201 @@ static int alias_match(const char *uri, const char *alias_fakename) return urip - uri; } +/* + * Inspired by mod_jk's jk_servlet_normalize(). + */ +static int alias_match_servlet(apr_pool_t *p, + const char **urip, + const char *alias) +{ + char *map; + const char *uri = *urip; + apr_array_header_t *stack; + int map_pos, uri_pos, alias_pos, first_pos; + int alias_depth = 0, depth; + + /* Both uri and alias should start with '/' */ + if (uri[0] != '/' || alias[0] != '/') { + return 0; + } + + stack = apr_array_make(p, 5, sizeof(int)); + map = apr_palloc(p, strlen(uri) + 1); + map[0] = '/'; + map[1] = '\0'; + + map_pos = uri_pos = alias_pos = first_pos = 1; + while (uri[uri_pos] != '\0') { + /* Remove path parameters ;foo=bar/ from any path segment */ + if (uri[uri_pos] == ';') { + do { + uri_pos++; + } while (uri[uri_pos] != '/' && uri[uri_pos] != '\0'); + continue; + } + + if (map[map_pos - 1] == '/') { + /* Collapse ///// sequences to / */ + if (uri[uri_pos] == '/') { + do { + uri_pos++; + } while (uri[uri_pos] == '/'); + continue; + } + + if (uri[uri_pos] == '.') { + /* Remove /./ segments */ + if (uri[uri_pos + 1] == '/' + || uri[uri_pos + 1] == ';' + || uri[uri_pos + 1] == '\0') { + uri_pos++; + if (uri[uri_pos] == '/') { + uri_pos++; + } + continue; + } + + /* Remove /xx/../ segments */ + if (uri[uri_pos + 1] == '.' + && (uri[uri_pos + 2] == '/' + || uri[uri_pos + 2] == ';' + || uri[uri_pos + 2] == '\0')) { + /* Wind map segment back the previous one */ + if (map_pos == 1) { + /* Above root */ + return 0; + } + do { + map_pos--; + } while (map[map_pos - 1] != '/'); + map[map_pos] = '\0'; + + /* Wind alias segment back, unless in deeper segment */ + if (alias_depth == stack->nelts) { + if (alias[alias_pos] == '\0') { + alias_pos--; + } + while (alias_pos > 0 && alias[alias_pos] == '/') { + alias_pos--; + } + while (alias_pos > 0 && alias[alias_pos - 1] != '/') { + alias_pos--; + } + AP_DEBUG_ASSERT(alias_pos > 0); + alias_depth--; + } + apr_array_pop(stack); + + /* Move uri forward to the next segment */ + uri_pos += 2; + if (uri[uri_pos] == '/') { + uri_pos++; + } + first_pos = 0; + continue; + } + } + if (first_pos) { + while (uri[first_pos] == '/') { + first_pos++; + } + } + + /* New segment */ + APR_ARRAY_PUSH(stack, int) = first_pos ? first_pos : uri_pos; + if (alias[alias_pos] != '\0') { + if (alias[alias_pos - 1] != '/') { + /* Remain in pair with uri segments */ + do { + alias_pos++; + } while (alias[alias_pos - 1] != '/' && alias[alias_pos]); + } + while (alias[alias_pos] == '/') { + alias_pos++; + } + if (alias[alias_pos] != '\0') { + alias_depth++; + } + } + } + + if (alias[alias_pos] != '\0') { + int *match = &APR_ARRAY_IDX(stack, alias_depth - 1, int); + if (*match) { + if (alias[alias_pos] != uri[uri_pos]) { + /* Current segment does not match */ + *match = 0; + } + else if (alias[alias_pos + 1] == '\0' + && alias[alias_pos] != '/') { + if (uri[uri_pos + 1] == ';') { + /* We'll preserve the parameters of the last + * segment if it does not end with '/', so mark + * the match as negative for below handling. + */ + *match = -(uri_pos + 1); + } + else if (uri[uri_pos + 1] != '/' + && uri[uri_pos + 1] != '\0') { + /* Last segment does not match all the way */ + *match = 0; + } + } + } + /* Don't go past the segment if the uri isn't there yet */ + if (alias[alias_pos] != '/' || uri[uri_pos] == '/') { + alias_pos++; + } + } + + if (uri[uri_pos] == '/') { + first_pos = uri_pos + 1; + } + map[map_pos++] = uri[uri_pos++]; + map[map_pos] = '\0'; + } + + /* Can't reach the end of uri before the end of the alias, + * for example if uri is "/" and alias is "/examples" + */ + if (alias[alias_pos] != '\0') { + return 0; + } + + /* Check whether each alias segment matched */ + for (depth = 0; depth < alias_depth; ++depth) { + if (!APR_ARRAY_IDX(stack, depth, int)) { + return 0; + } + } + + /* If alias_depth == stack->nelts we have a full match, i.e. + * uri == alias so we can return uri_pos as is (the end of uri) + */ + if (alias_depth < stack->nelts) { + /* Return the segment following the alias */ + uri_pos = APR_ARRAY_IDX(stack, alias_depth, int); + if (alias_depth) { + /* But if the last segment of the alias does not end with '/' + * and the corresponding segment of the uri has parameters, + * we want to forward those parameters (see above for the + * negative pos trick/mark). + */ + int pos = APR_ARRAY_IDX(stack, alias_depth - 1, int); + if (pos < 0) { + uri_pos = -pos; + } + } + } + /* If the alias lacks a trailing slash, take it from the uri (if any) */ + if (alias[alias_pos - 1] != '/' && uri[uri_pos - 1] == '/') { + uri_pos--; + } + + *urip = map; + return uri_pos; +} + /* Detect if an absoluteURI should be proxied or not. Note that we * have to do this during this phase because later phases are * "short-circuiting"... i.e. translate_names will end when the first @@ -670,6 +866,7 @@ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent, int mismatch = 0; unsigned int nocanon = ent->flags & PROXYPASS_NOCANON; const char *use_uri = nocanon ? r->unparsed_uri : r->uri; + const char *servlet_uri = NULL; if (dconf && (dconf->interpolate_env == 1) && (ent->flags & PROXYPASS_INTERPOLATE)) { fake = proxy_interpolate(r, ent->fake); @@ -730,7 +927,14 @@ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent, } } else { - len = alias_match(r->uri, fake); + if ((ent->flags & PROXYPASS_MAP_SERVLET) == PROXYPASS_MAP_SERVLET) { + servlet_uri = r->uri; + len = alias_match_servlet(r->pool, &servlet_uri, fake); + nocanon = 0; /* ignored since servlet's normalization applies */ + } + else { + len = alias_match(r->uri, fake); + } if (len != 0) { if ((real[0] == '!') && (real[1] == '\0')) { @@ -761,7 +965,7 @@ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent, */ int rc = proxy_run_check_trans(r, found + 6); if (rc != OK && rc != DECLINED) { - return DONE; + return HTTP_CONTINUE; } r->filename = found; @@ -775,28 +979,63 @@ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent, apr_table_setn(r->notes, "proxy-noquery", "1"); } + if (servlet_uri) { + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(10248) + "Servlet path '%s' (%s) matches proxy handler '%s'", + r->uri, servlet_uri, found); + /* Apply servlet normalization to r->uri so that <Location> or any + * directory context match does not have to handle path parameters. + * We change r->uri in-place so that r->parsed_uri.path is updated + * too. Since normalized servlet_uri is necessarily shorter than + * the original r->uri, strcpy() is fine. + */ + AP_DEBUG_ASSERT(strlen(r->uri) >= strlen(servlet_uri)); + strcpy(r->uri, servlet_uri); + return DONE; + } + ap_log_rerror(APLOG_MARK, APLOG_TRACE1, 0, r, APLOGNO(03464) "URI path '%s' matches proxy handler '%s'", r->uri, found); - return OK; } - return DONE; + return HTTP_CONTINUE; } -static int proxy_trans(request_rec *r) +static int proxy_trans(request_rec *r, int pre_trans) { - int i; + int i, enc; struct proxy_alias *ent; proxy_dir_conf *dconf; proxy_server_conf *conf; if (r->proxyreq) { /* someone has already set up the proxy, it was possibly ourselves - * in proxy_detect + * in proxy_detect (DONE will prevent further decoding of r->uri, + * only if proxyreq is set before pre_trans already). */ - return OK; + return pre_trans ? DONE : OK; + } + + /* In early pre_trans hook, r->uri was not manipulated yet so we are + * compliant with RFC1945 at this point. Otherwise, it probably isn't + * an issue because this is a hybrid proxy/origin server. + */ + + dconf = ap_get_module_config(r->per_dir_config, &proxy_module); + conf = (proxy_server_conf *) ap_get_module_config(r->server->module_config, + &proxy_module); + + /* Always and only do PROXY_MAP_ENCODED mapping in pre_trans, when + * r->uri is still encoded, or we might consider for instance that + * a decoded sub-delim is now a delimiter (e.g. "%3B" => ';' for + * path parameters), which it's not. + */ + if ((pre_trans && !conf->map_encoded_one) + || (!pre_trans && conf->map_encoded_all)) { + /* Fast path, nothing at this stage */ + return DECLINED; } if ((r->unparsed_uri[0] == '*' && r->unparsed_uri[1] == '\0') @@ -808,37 +1047,42 @@ static int proxy_trans(request_rec *r) return DECLINED; } - /* XXX: since r->uri has been manipulated already we're not really - * compliant with RFC1945 at this point. But this probably isn't - * an issue because this is a hybrid proxy/origin server. - */ - - dconf = ap_get_module_config(r->per_dir_config, &proxy_module); - /* short way - this location is reverse proxied? */ if (dconf->alias) { - int rv = ap_proxy_trans_match(r, dconf->alias, dconf); - if (DONE != rv) { - return rv; + enc = (dconf->alias->flags & PROXYPASS_MAP_ENCODED) != 0; + if (!(pre_trans ^ enc)) { + int rv = ap_proxy_trans_match(r, dconf->alias, dconf); + if (rv != HTTP_CONTINUE) { + return rv; + } } } - conf = (proxy_server_conf *) ap_get_module_config(r->server->module_config, - &proxy_module); - /* long way - walk the list of aliases, find a match */ - if (conf->aliases->nelts) { - ent = (struct proxy_alias *) conf->aliases->elts; - for (i = 0; i < conf->aliases->nelts; i++) { - int rv = ap_proxy_trans_match(r, &ent[i], dconf); - if (DONE != rv) { + for (i = 0; i < conf->aliases->nelts; i++) { + ent = &((struct proxy_alias *)conf->aliases->elts)[i]; + enc = (ent->flags & PROXYPASS_MAP_ENCODED) != 0; + if (!(pre_trans ^ enc)) { + int rv = ap_proxy_trans_match(r, ent, dconf); + if (rv != HTTP_CONTINUE) { return rv; } } } + return DECLINED; } +static int proxy_pre_translate_name(request_rec *r) +{ + return proxy_trans(r, 1); +} + +static int proxy_translate_name(request_rec *r) +{ + return proxy_trans(r, 0); +} + static int proxy_walk(request_rec *r) { proxy_server_conf *sconf = ap_get_module_config(r->server->module_config, @@ -1359,6 +1603,8 @@ static void * create_proxy_config(apr_pool_t *p, server_rec *s) ps->forward = NULL; ps->reverse = NULL; ps->domain = NULL; + ps->map_encoded_one = 0; + ps->map_encoded_all = 1; ps->id = apr_psprintf(p, "p%x", 1); /* simply for storage size */ ps->viaopt = via_off; /* initially backward compatible with 1.3.1 */ ps->viaopt_set = 0; /* 0 means default */ @@ -1516,6 +1762,9 @@ static void * merge_proxy_config(apr_pool_t *p, void *basev, void *overridesv) ps->forward = overrides->forward ? overrides->forward : base->forward; ps->reverse = overrides->reverse ? overrides->reverse : base->reverse; + ps->map_encoded_one = overrides->map_encoded_one || base->map_encoded_one; + ps->map_encoded_all = overrides->map_encoded_all && base->map_encoded_all; + ps->domain = (overrides->domain == NULL) ? base->domain : overrides->domain; ps->id = (overrides->id == NULL) ? base->id : overrides->id; ps->viaopt = (overrides->viaopt_set == 0) ? base->viaopt : overrides->viaopt; @@ -1640,7 +1889,7 @@ static void *merge_proxy_dir_config(apr_pool_t *p, void *basev, void *addv) : add->forward_100_continue; new->forward_100_continue_set = add->forward_100_continue_set || base->forward_100_continue_set; - + return new; } @@ -1814,11 +2063,31 @@ static const char * "in the form 'key=value'."; } } - else + else { *val++ = '\0'; - apr_table_setn(params, word, val); + } + if (!strcasecmp(word, "mapping")) { + if (!strcasecmp(val, "encoded")) { + flags |= PROXYPASS_MAP_ENCODED; + } + else if (!strcasecmp(val, "servlet")) { + flags |= PROXYPASS_MAP_SERVLET; + } + else { + return "unknown mapping"; + } + } + else { + apr_table_setn(params, word, val); + } } - }; + } + if (flags & PROXYPASS_MAP_ENCODED) { + conf->map_encoded_one = 1; + } + else { + conf->map_encoded_all = 0; + } if (r == NULL) { return "ProxyPass|ProxyPassMatch needs a path when not defined in a location"; @@ -3142,7 +3411,10 @@ static void register_hooks(apr_pool_t *p) /* handler */ ap_hook_handler(proxy_handler, NULL, NULL, APR_HOOK_FIRST); /* filename-to-URI translation */ - ap_hook_translate_name(proxy_trans, aszSucc, NULL, APR_HOOK_FIRST); + ap_hook_pre_translate_name(proxy_pre_translate_name, NULL, NULL, + APR_HOOK_MIDDLE); + ap_hook_translate_name(proxy_translate_name, aszSucc, NULL, + APR_HOOK_FIRST); /* walk <Proxy > entries and suppress default TRACE behavior */ ap_hook_map_to_storage(proxy_map_location, NULL,NULL, APR_HOOK_FIRST); /* fixups */ diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h index c1c61068bc..bf6bbd0fb3 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -124,6 +124,8 @@ struct proxy_remote { #define PROXYPASS_NOCANON 0x01 #define PROXYPASS_INTERPOLATE 0x02 #define PROXYPASS_NOQUERY 0x04 +#define PROXYPASS_MAP_ENCODED 0x08 +#define PROXYPASS_MAP_SERVLET 0x18 /* + MAP_ENCODED */ struct proxy_alias { const char *real; const char *fake; @@ -200,6 +202,8 @@ typedef struct { unsigned int inherit_set:1; unsigned int ppinherit:1; unsigned int ppinherit_set:1; + unsigned int map_encoded_one:1; + unsigned int map_encoded_all:1; } proxy_server_conf; typedef struct { @@ -1171,7 +1175,11 @@ PROXY_DECLARE(apr_status_t) ap_proxy_sync_balancer(proxy_balancer *b, * @param r request * @param ent proxy_alias record * @param dconf per-dir config or NULL - * @return DECLINED, DONE or OK if matched + * @return OK if the alias matched, + * DONE if the alias matched and r->uri was normalized so + * no further transformation should happen on it, + * DECLINED if proxying is disabled for this alias, + * HTTP_CONTINUE if the alias did not match */ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent, diff --git a/server/request.c b/server/request.c index 299eae04d3..eb30f6b478 100644 --- a/server/request.c +++ b/server/request.c @@ -59,6 +59,7 @@ #define APLOG_MODULE_INDEX AP_CORE_MODULE_INDEX APR_HOOK_STRUCT( + APR_HOOK_LINK(pre_translate_name) APR_HOOK_LINK(translate_name) APR_HOOK_LINK(map_to_storage) APR_HOOK_LINK(check_user_id) @@ -74,6 +75,8 @@ APR_HOOK_STRUCT( APR_HOOK_LINK(force_authn) ) +AP_IMPLEMENT_HOOK_RUN_FIRST(int,pre_translate_name, + (request_rec *r), (r), DECLINED) AP_IMPLEMENT_HOOK_RUN_FIRST(int,translate_name, (request_rec *r), (r), DECLINED) AP_IMPLEMENT_HOOK_RUN_FIRST(int,map_to_storage, @@ -157,6 +160,25 @@ AP_DECLARE(int) ap_some_authn_required(request_rec *r) return rv; } +static int walk_location_and_if(request_rec *r) +{ + int access_status; + core_dir_config *d; + + if ((access_status = ap_location_walk(r))) { + return access_status; + } + if ((access_status = ap_if_walk(r))) { + return access_status; + } + + d = ap_get_core_module_config(r->per_dir_config); + if (d->log) + r->log = d->log; + + return OK; +} + /* 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 @@ -164,17 +186,65 @@ AP_DECLARE(int) ap_some_authn_required(request_rec *r) */ AP_DECLARE(int) ap_process_request_internal(request_rec *r) { + int access_status = DECLINED; int file_req = (r->main && r->filename); - int access_status; - core_dir_config *d; core_server_config *sconf = ap_get_core_module_config(r->server->module_config); + unsigned int normalize_flags; + + normalize_flags = AP_NORMALIZE_NOT_ABOVE_ROOT; + if (sconf->merge_slashes != AP_CORE_CONFIG_OFF) { + normalize_flags |= AP_NORMALIZE_MERGE_SLASHES; + } + if (file_req) { + /* File subrequests can have a relative path. */ + normalize_flags |= AP_NORMALIZE_ALLOW_RELATIVE; + } + + if (r->parsed_uri.path) { + /* Normalize: remove /./ and shrink /../ segments, plus + * decode unreserved chars (first time only to avoid + * double decoding after ap_unescape_url() below). + */ + if (!ap_normalize_path(r->parsed_uri.path, + normalize_flags | + AP_NORMALIZE_DECODE_UNRESERVED)) { + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10244) + "invalid URI path (%s)", r->unparsed_uri); + return HTTP_BAD_REQUEST; + } + } + + /* All file subrequests are a huge pain... they cannot bubble through the + * next several steps. Only file subrequests are allowed an empty uri, + * otherwise let (pre_)translate_name kill the request. + */ + if (!file_req) { + ap_conf_vector_t *per_dir_config = r->per_dir_config; + + if ((access_status = walk_location_and_if(r))) { + return access_status; + } + + /* Let pre_translate_name hooks work with non-decoded URIs, and + * eventually prevent further URI transformations (return DONE). + */ + access_status = ap_run_pre_translate_name(r); + if (ap_is_HTTP_ERROR(access_status)) { + return access_status; + } + + /* Throw away pre_trans only merging */ + r->per_dir_config = per_dir_config; + } + + /* Ignore URL unescaping for translated URIs already */ + if (access_status != DONE && r->parsed_uri.path) { + core_dir_config *d = ap_get_core_module_config(r->per_dir_config); - /* Ignore embedded %2F's in path for proxy requests */ - if (!r->proxyreq && r->parsed_uri.path) { - d = ap_get_core_module_config(r->per_dir_config); if (d->allow_encoded_slashes) { - access_status = ap_unescape_url_keep2f(r->parsed_uri.path, d->decode_encoded_slashes); + access_status = ap_unescape_url_keep2f(r->parsed_uri.path, + d->decode_encoded_slashes); } else { access_status = ap_unescape_url(r->parsed_uri.path); @@ -183,40 +253,27 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) if (access_status == HTTP_NOT_FOUND) { if (! d->allow_encoded_slashes) { ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00026) - "found %%2f (encoded '/') in URI " - "(decoded='%s'), returning 404", - r->parsed_uri.path); + "found %%2f (encoded '/') in URI path (%s), " + "returning 404", r->unparsed_uri); } } return access_status; } - } - ap_getparents(r->uri); /* OK --- shrinking transformations... */ - if (sconf->merge_slashes != AP_CORE_CONFIG_OFF) { - ap_no2slash(r->uri); - if (r->parsed_uri.path) { - ap_no2slash(r->parsed_uri.path); + if (d->allow_encoded_slashes && d->decode_encoded_slashes) { + /* Decoding slashes might have created new // or /./ or /../ + * segments (e.g. "/.%2F/"), so re-normalize. + */ + ap_normalize_path(r->parsed_uri.path, normalize_flags); } - } + } - /* All file subrequests are a huge pain... they cannot bubble through the - * next several steps. Only file subrequests are allowed an empty uri, - * otherwise let translate_name kill the request. - */ + /* Same, translate_name is not suited for file subrequests */ if (!file_req) { - if ((access_status = ap_location_walk(r))) { - return access_status; - } - if ((access_status = ap_if_walk(r))) { + if ((access_status = walk_location_and_if(r))) { return access_status; } - d = ap_get_core_module_config(r->per_dir_config); - if (d->log) { - r->log = d->log; - } - if ((access_status = ap_run_translate_name(r))) { return decl_die(access_status, "translate", r); } @@ -233,17 +290,9 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) /* Rerun the location walk, which overrides any map_to_storage config. */ - if ((access_status = ap_location_walk(r))) { + if ((access_status = walk_location_and_if(r))) { return access_status; } - if ((access_status = ap_if_walk(r))) { - return access_status; - } - - d = ap_get_core_module_config(r->per_dir_config); - if (d->log) { - r->log = d->log; - } if ((access_status = ap_run_post_perdir_config(r))) { return access_status; @@ -1370,7 +1419,7 @@ AP_DECLARE(int) ap_directory_walk(request_rec *r) r->canonical_filename = r->filename; if (r->finfo.filetype == APR_DIR) { - cache->cached = r->filename; + cache->cached = apr_pstrdup(r->pool, r->filename); } else { cache->cached = ap_make_dirstr_parent(r->pool, r->filename); @@ -1467,7 +1516,7 @@ AP_DECLARE(int) ap_location_walk(request_rec *r) apr_pool_t *rxpool = NULL; cached &= auth_internal_per_conf; - cache->cached = entry_uri; + cache->cached = apr_pstrdup(r->pool, entry_uri); /* Go through the location entries, and check for matches. * We apply the directive sections in given order, we should diff --git a/server/util.c b/server/util.c index 90ee58190d..143c8fc69a 100644 --- a/server/util.c +++ b/server/util.c @@ -76,7 +76,7 @@ #include "test_char.h" /* Win32/NetWare/OS2 need to check for both forward and back slashes - * in ap_getparents() and ap_escape_url. + * in ap_normalize_path() and ap_escape_url(). */ #ifdef CASE_BLIND_FILESYSTEM #define IS_SLASH(s) ((s == '/') || (s == '\\')) @@ -491,75 +491,127 @@ AP_DECLARE(apr_status_t) ap_pregsub_ex(apr_pool_t *p, char **result, return rc; } +/* Forward declare */ +static char x2c(const char *what); + +#define IS_SLASH_OR_NUL(s) (s == '\0' || IS_SLASH(s)) + /* - * Parse .. so we don't compromise security + * Inspired by mod_jk's jk_servlet_normalize(). */ -AP_DECLARE(void) ap_getparents(char *name) +AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags) { - char *next; - int l, w, first_dot; + int ret = 1; + apr_size_t l = 1, w = 1; - /* Four paseses, as per RFC 1808 */ - /* a) remove ./ path segments */ - for (next = name; *next && (*next != '.'); next++) { - } + if (!IS_SLASH(path[0])) { + /* Besides "OPTIONS *", a request-target should start with '/' + * per RFC 7230 section 5.3, so anything else is invalid. + */ + if (path[0] == '*' && path[1] == '\0') { + return 1; + } + /* However, AP_NORMALIZE_ALLOW_RELATIVE can be used to bypass + * this restriction (e.g. for subrequest file lookups). + */ + if (!(flags & AP_NORMALIZE_ALLOW_RELATIVE) || path[0] == '\0') { + return 0; + } - l = w = first_dot = next - name; - while (name[l] != '\0') { - if (name[l] == '.' && IS_SLASH(name[l + 1]) - && (l == 0 || IS_SLASH(name[l - 1]))) - l += 2; - else - name[w++] = name[l++]; + l = w = 0; } - /* b) remove trailing . path, segment */ - if (w == 1 && name[0] == '.') - w--; - else if (w > 1 && name[w - 1] == '.' && IS_SLASH(name[w - 2])) - w--; - name[w] = '\0'; + while (path[l] != '\0') { + /* RFC-3986 section 2.3: + * For consistency, percent-encoded octets in the ranges of + * ALPHA (%41-%5A and %61-%7A), DIGIT (%30-%39), hyphen (%2D), + * period (%2E), underscore (%5F), or tilde (%7E) should [...] + * be decoded to their corresponding unreserved characters by + * URI normalizers. + */ + if ((flags & AP_NORMALIZE_DECODE_UNRESERVED) + && path[l] == '%' && apr_isxdigit(path[l + 1]) + && apr_isxdigit(path[l + 2])) { + const char c = x2c(&path[l + 1]); + if (apr_isalnum(c) || (c && strchr("-._~", c))) { + /* Replace last char and fall through as the current + * read position */ + l += 2; + path[l] = c; + } + } - /* c) remove all xx/../ segments. (including leading ../ and /../) */ - l = first_dot; + if ((flags & AP_NORMALIZE_DROP_PARAMETERS) && path[l] == ';') { + do { + l++; + } while (!IS_SLASH_OR_NUL(path[l])); + continue; + } - while (name[l] != '\0') { - if (name[l] == '.' && name[l + 1] == '.' && IS_SLASH(name[l + 2]) - && (l == 0 || IS_SLASH(name[l - 1]))) { - int m = l + 3, n; + if (w == 0 || IS_SLASH(path[w - 1])) { + /* Collapse ///// sequences to / */ + if ((flags & AP_NORMALIZE_MERGE_SLASHES) && IS_SLASH(path[l])) { + do { + l++; + } while (IS_SLASH(path[l])); + continue; + } - l = l - 2; - if (l >= 0) { - while (l >= 0 && !IS_SLASH(name[l])) - l--; - l++; + if (path[l] == '.') { + /* Remove /./ segments */ + if (IS_SLASH_OR_NUL(path[l + 1])) { + l++; + if (path[l]) { + l++; + } + continue; + } + + /* Remove /xx/../ segments */ + if (path[l + 1] == '.' && IS_SLASH_OR_NUL(path[l + 2])) { + /* Wind w back to remove the previous segment */ + if (w > 1) { + do { + w--; + } while (w && !IS_SLASH(path[w - 1])); + } + else { + /* Already at root, ignore and return a failure + * if asked to. + */ + if (flags & AP_NORMALIZE_NOT_ABOVE_ROOT) { + ret = 0; + } + } + + /* Move l forward to the next segment */ + l += 2; + if (path[l]) { + l++; + } + continue; + } } - else - l = 0; - n = l; - while ((name[n] = name[m])) - (++n, ++m); } - else - ++l; + + path[w++] = path[l++]; } + path[w] = '\0'; + + return ret; +} - /* d) remove trailing xx/.. segment. */ - if (l == 2 && name[0] == '.' && name[1] == '.') +/* + * Parse .. so we don't compromise security + */ +AP_DECLARE(void) ap_getparents(char *name) +{ + if (!ap_normalize_path(name, AP_NORMALIZE_NOT_ABOVE_ROOT | + AP_NORMALIZE_ALLOW_RELATIVE)) { name[0] = '\0'; - else if (l > 2 && name[l - 1] == '.' && name[l - 2] == '.' - && IS_SLASH(name[l - 3])) { - l = l - 4; - if (l >= 0) { - while (l >= 0 && !IS_SLASH(name[l])) - l--; - l++; - } - else - l = 0; - name[l] = '\0'; } } + AP_DECLARE(void) ap_no2slash_ex(char *name, int is_fs_path) { |