From e795efc845140cc312892d6a623f6963504ef608 Mon Sep 17 00:00:00 2001 From: Jean-Frederic Clere Date: Tue, 8 Jun 2021 11:29:17 +0200 Subject: [PATCH 1/8] back port r1879074 --- include/ap_mmn.h | 3 +- include/httpd.h | 15 ++++ modules/dav/main/util.c | 7 +- modules/generators/mod_autoindex.c | 5 +- server/util.c | 112 ++++++++++++++++++++++++++++- 5 files changed, 137 insertions(+), 5 deletions(-) diff --git a/include/ap_mmn.h b/include/ap_mmn.h index a2bf24527a7..6d327a7f415 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -562,6 +562,7 @@ * 20120211.106 (2.4.49-dev) Add ap_create_request(). * 20120211.107 (2.4.49-dev) Add ap_parse_request_line() and * ap_check_request_header() + * 20120211.108 (2.4.49-dev) Add ap_normalize_path() */ #define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */ @@ -569,7 +570,7 @@ #ifndef MODULE_MAGIC_NUMBER_MAJOR #define MODULE_MAGIC_NUMBER_MAJOR 20120211 #endif -#define MODULE_MAGIC_NUMBER_MINOR 107 /* 0...n */ +#define MODULE_MAGIC_NUMBER_MINOR 108 /* 0...n */ /** * Determine if the server's current MODULE_MAGIC_NUMBER is at least a diff --git a/include/httpd.h b/include/httpd.h index 14f15b5c9b7..02cde256ed6 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 e21f6260680..8cf3fe5234e 100644 --- a/modules/dav/main/util.c +++ b/modules/dav/main/util.c @@ -664,7 +664,12 @@ 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_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/generators/mod_autoindex.c b/modules/generators/mod_autoindex.c index 28ed85a8f3e..cb4460357c9 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/server/util.c b/server/util.c index 90ee58190d1..c28633c3d96 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,6 +491,116 @@ 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)) + +/* + * Inspired by mod_jk's jk_servlet_normalize(). + */ +AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags) +{ + int ret = 1; + apr_size_t l = 1, w = 1; + + 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 = 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; + } + } + + if ((flags & AP_NORMALIZE_DROP_PARAMETERS) && path[l] == ';') { + do { + l++; + } while (!IS_SLASH_OR_NUL(path[l])); + continue; + } + + 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; + } + + 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; + } + } + } + + path[w++] = path[l++]; + } + path[w] = '\0'; + + return ret; +} + /* * Parse .. so we don't compromise security */ From 707ec018419ddbb492848205a7edb04d244abb42 Mon Sep 17 00:00:00 2001 From: Jean-Frederic Clere Date: Tue, 8 Jun 2021 11:42:01 +0200 Subject: [PATCH 2/8] back port 1879075 and r1879076. --- include/ap_mmn.h | 3 +- include/http_request.h | 10 +++++++ server/request.c | 3 ++ server/util.c | 65 ++---------------------------------------- 4 files changed, 17 insertions(+), 64 deletions(-) diff --git a/include/ap_mmn.h b/include/ap_mmn.h index 6d327a7f415..2064ac8423f 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -562,7 +562,8 @@ * 20120211.106 (2.4.49-dev) Add ap_create_request(). * 20120211.107 (2.4.49-dev) Add ap_parse_request_line() and * ap_check_request_header() - * 20120211.108 (2.4.49-dev) Add ap_normalize_path() + * 20120211.108 (2.4.49-dev) Add ap_normalize_path() and + * pre_translate_name hook. */ #define MODULE_MAGIC_COOKIE 0x41503234UL /* "AP24" */ diff --git a/include/http_request.h b/include/http_request.h index 5f056681772..e8da3330f58 100644 --- a/include/http_request.h +++ b/include/http_request.h @@ -362,6 +362,16 @@ AP_DECLARE(apr_status_t) ap_check_pipeline(conn_rec *c, apr_bucket_brigade *bb, */ 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. + * rules will be followed. + * @param r The current request + * @return OK, DECLINED, or HTTP_... + * @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 diff --git a/server/request.c b/server/request.c index 299eae04d3f..8e8189d720b 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, diff --git a/server/util.c b/server/util.c index c28633c3d96..de417d86f0a 100644 --- a/server/util.c +++ b/server/util.c @@ -606,70 +606,9 @@ AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags) */ AP_DECLARE(void) ap_getparents(char *name) { - char *next; - int l, w, first_dot; - - /* Four paseses, as per RFC 1808 */ - /* a) remove ./ path segments */ - for (next = name; *next && (*next != '.'); next++) { - } - - 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++]; - } - - /* 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'; - - /* c) remove all xx/../ segments. (including leading ../ and /../) */ - l = first_dot; - - 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; - - l = l - 2; - if (l >= 0) { - while (l >= 0 && !IS_SLASH(name[l])) - l--; - l++; - } - else - l = 0; - n = l; - while ((name[n] = name[m])) - (++n, ++m); - } - else - ++l; - } - - /* d) remove trailing xx/.. segment. */ - if (l == 2 && name[0] == '.' && name[1] == '.') - 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'; - } + (void)ap_normalize_path(name, AP_NORMALIZE_ALLOW_RELATIVE); } + AP_DECLARE(void) ap_no2slash_ex(char *name, int is_fs_path) { From 0996f8f42a3071dc0037e9b7bdff26d5c2538db6 Mon Sep 17 00:00:00 2001 From: Jean-Frederic Clere Date: Tue, 8 Jun 2021 11:57:06 +0200 Subject: [PATCH 3/8] back port r1879077. --- docs/manual/developer/modguide.xml | 1 + docs/manual/mod/mod_log_debug.xml | 1 + docs/manual/mod/mod_lua.xml | 28 +++++++++++++++++++++++-- modules/examples/mod_example_hooks.c | 17 +++++++++++++++ modules/generators/mod_info.c | 1 + modules/loggers/mod_log_debug.c | 8 +++++++ modules/lua/mod_lua.c | 31 ++++++++++++++++++++++++++++ 7 files changed, 85 insertions(+), 2 deletions(-) diff --git a/docs/manual/developer/modguide.xml b/docs/manual/developer/modguide.xml index 63f3d0a9d32..4ebd7fa79ba 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:
  • ap_hook_child_init: Place a hook that executes when a child process is spawned (commonly used for initializing modules after the server has forked)
  • ap_hook_pre_config: Place a hook that executes before any configuration data has been read (very early hook)
  • ap_hook_post_config: Place a hook that executes after configuration has been parsed, but before the server has forked
  • +
  • ap_hook_pre_translate_name: Place a hook that executes when a URI needs to be translated into a filename on the server, before decoding
  • ap_hook_translate_name: Place a hook that executes when a URI needs to be translated into a filename on the server (think mod_rewrite)
  • ap_hook_quick_handler: Similar to ap_hook_handler, except it is run before any other request hooks (translation, auth, fixups etc)
  • ap_hook_log_transaction: Place a hook that executes when the server is about to add a log entry of the current request
  • diff --git a/docs/manual/mod/mod_log_debug.xml b/docs/manual/mod/mod_log_debug.xml index e9925334617..c2280b4d0a8 100644 --- a/docs/manual/mod/mod_log_debug.xml +++ b/docs/manual/mod/mod_log_debug.xml @@ -103,6 +103,7 @@ + diff --git a/docs/manual/mod/mod_lua.xml b/docs/manual/mod/mod_lua.xml index 3d853f9101f..8c1195f8de0 100644 --- a/docs/manual/mod/mod_lua.xml +++ b/docs/manual/mod/mod_lua.xml @@ -215,6 +215,13 @@ performing access control, or setting mime types:

    + + + + + @@ -439,7 +446,7 @@ end @@ -538,7 +545,7 @@ end + the post_read_request/pre_translate_name/translate_name phase of a request. @@ -1494,6 +1501,23 @@ end + +LuaHookPreTranslate +Provide a hook for the pre_translate phase of a request +processing +LuaHookPreTranslate /path/to/lua/script.lua hook_function_name +server configvirtual host +directory.htaccess + +All + +

    + Just like LuaHookTranslateName, but executed at the pre_translate phase, + where the URI-path is not percent decoded. +

    +
    +
    + LuaHookFixups Provide a hook for the fixups phase of a request diff --git a/modules/examples/mod_example_hooks.c b/modules/examples/mod_example_hooks.c index 2ab945eb3a6..f7ef5a5ccf7 100644 --- a/modules/examples/mod_example_hooks.c +++ b/modules/examples/mod_example_hooks.c @@ -1173,6 +1173,22 @@ static int x_post_read_request(request_rec *r) return DECLINED; } +/* + * 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 @@ -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_info.c b/modules/generators/mod_info.c index cf1a1fd8c5c..b044273062e 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 8a6c1244f5e..3f27a958de2 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 23114304b35..665e2cddc7b 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(" Date: Tue, 8 Jun 2021 13:26:50 +0200 Subject: [PATCH 4/8] Add: r1879079 r1879080 r1879094 r1879095 r1879110 r1879111 r1879112 r1879114 to the back port. --- include/http_request.h | 1 - modules/proxy/mod_proxy.c | 254 ++++++++++++++++++++++++++++++++++++-- modules/proxy/mod_proxy.h | 4 + server/request.c | 60 +++++++-- 4 files changed, 296 insertions(+), 23 deletions(-) diff --git a/include/http_request.h b/include/http_request.h index e8da3330f58..e0d37607650 100644 --- a/include/http_request.h +++ b/include/http_request.h @@ -365,7 +365,6 @@ 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. - * rules will be followed. * @param r The current request * @return OK, DECLINED, or HTTP_... * @ingroup hooks diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c index 0f787a6b45a..8507553f9f7 100644 --- a/modules/proxy/mod_proxy.c +++ b/modules/proxy/mod_proxy.c @@ -560,6 +560,198 @@ 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 *uri, + const char *alias) +{ + char *map; + 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--; + } + 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 @@ -730,7 +922,18 @@ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent, } } else { - len = alias_match(r->uri, fake); + /* Ignore servlet mapping if r->uri was decoded already, or we + * might consider for instance that an original %3B is a delimiter + * for path parameters (which is not). + */ + if (dconf->use_original_uri == 1 + && (ent->flags & PROXYPASS_MAPPING_SERVLET)) { + nocanon = 0; /* ignored since servlet's normalization applies */ + len = alias_match_servlet(r->pool, r->uri, fake); + } + else { + len = alias_match(r->uri, fake); + } if (len != 0) { if ((real[0] == '!') && (real[1] == '\0')) { @@ -785,7 +988,7 @@ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent, return DONE; } -static int proxy_trans(request_rec *r) +static int proxy_trans(request_rec *r, int pre_trans) { int i; struct proxy_alias *ent; @@ -799,6 +1002,20 @@ static int proxy_trans(request_rec *r) return 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); + + /* Do the work from the hook corresponding to the ProxyUseOriginalURI + * configuration (off/default: translate hook, on: pre_translate hook). + */ + if (pre_trans ^ (dconf->use_original_uri == 1)) { + return DECLINED; + } + if ((r->unparsed_uri[0] == '*' && r->unparsed_uri[1] == '\0') || !r->uri || r->uri[0] != '/') { return DECLINED; @@ -808,13 +1025,6 @@ 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); @@ -836,9 +1046,19 @@ static int proxy_trans(request_rec *r) } } } + 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, @@ -1583,6 +1803,7 @@ static void *create_proxy_dir_config(apr_pool_t *p, char *dummy) new->add_forwarded_headers_set = 0; new->forward_100_continue = 1; new->forward_100_continue_set = 0; + new->use_original_uri = -1; /* unset (off by default) */ return (void *) new; } @@ -1641,6 +1862,10 @@ static void *merge_proxy_dir_config(apr_pool_t *p, void *basev, void *addv) new->forward_100_continue_set = add->forward_100_continue_set || base->forward_100_continue_set; + new->use_original_uri = (add->use_original_uri == -1) + ? base->use_original_uri + : add->use_original_uri; + return new; } @@ -1796,6 +2021,9 @@ static const char * else if (!strcasecmp(word,"noquery")) { flags |= PROXYPASS_NOQUERY; } + else if (!strcasecmp(word,"mapping=servlet")) { + flags |= PROXYPASS_MAPPING_SERVLET; + } else { char *val = strchr(word, '='); if (!val) { @@ -2806,6 +3034,9 @@ static const command_rec proxy_cmds[] = AP_INIT_FLAG("Proxy100Continue", forward_100_continue, NULL, RSRC_CONF|ACCESS_CONF, "on if 100-Continue should be forwarded to the origin server, off if the " "proxy should handle it by itself"), + AP_INIT_FLAG("ProxyUseOriginalURI", ap_set_flag_slot_char, + (void*)APR_OFFSETOF(proxy_dir_conf, use_original_uri), RSRC_CONF|ACCESS_CONF, + "Whether to use the original/encoded URI-path for mapping"), {NULL} }; @@ -3142,7 +3373,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 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 56461707cd2..76aa32f7980 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -124,6 +124,7 @@ struct proxy_remote { #define PROXYPASS_NOCANON 0x01 #define PROXYPASS_INTERPOLATE 0x02 #define PROXYPASS_NOQUERY 0x04 +#define PROXYPASS_MAPPING_SERVLET 0x08 struct proxy_alias { const char *real; const char *fake; @@ -244,6 +245,9 @@ typedef struct { unsigned int forward_100_continue_set:1; apr_array_header_t *error_override_codes; + + /** Whether to use original/encoded URI-path or not (default) */ + signed char use_original_uri; } proxy_dir_conf; /* if we interpolate env vars per-request, we'll need a per-request diff --git a/server/request.c b/server/request.c index 8e8189d720b..e27a0b24a57 100644 --- a/server/request.c +++ b/server/request.c @@ -172,12 +172,41 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) core_dir_config *d; core_server_config *sconf = ap_get_core_module_config(r->server->module_config); + unsigned int normalize_flags = 0; - /* Ignore embedded %2F's in path for proxy requests */ - if (!r->proxyreq && r->parsed_uri.path) { + if (r->main) { + /* Lookup 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() + "invalid URI path (%s)", r->uri); + return HTTP_BAD_REQUEST; + } + } + + /* Let pre_translate_name hooks work with non-decoded URIs, + * and eventually apply their own transformations (return OK). + */ + access_status = ap_run_pre_translate_name(r); + if (access_status != OK && access_status != DECLINED) { + return access_status; + } + + /* Ignore URL unescaping for translated URIs already */ + if (access_status == DECLINED && 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); @@ -188,20 +217,27 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(00026) "found %%2f (encoded '/') in URI " "(decoded='%s'), returning 404", - r->parsed_uri.path); + r->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) { + if (d->allow_encoded_slashes && d->decode_encoded_slashes) { + /* Decoding slashes might have created new /./ and /../ + * segments (e.g. "/.%2F/"), so re-normalize. If asked to, + * merge slashes while at it. + */ + if (sconf->merge_slashes != AP_CORE_CONFIG_OFF) { + normalize_flags |= AP_NORMALIZE_MERGE_SLASHES; + } + ap_normalize_path(r->parsed_uri.path, normalize_flags); + } + else if (sconf->merge_slashes != AP_CORE_CONFIG_OFF) { + /* We still didn't merged slashes yet, do it now. */ ap_no2slash(r->parsed_uri.path); } - } + } /* All file subrequests are a huge pain... they cannot bubble through the * next several steps. Only file subrequests are allowed an empty uri, @@ -1373,7 +1409,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); @@ -1470,7 +1506,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 From dff655a304249d0dbdc9c5db2aeb6d80cfdb0733 Mon Sep 17 00:00:00 2001 From: Jean-Frederic Clere Date: Tue, 8 Jun 2021 14:18:33 +0200 Subject: [PATCH 5/8] back port r1879116 --- server/request.c | 90 ++++++++++++++++++++++++++++-------------------- 1 file changed, 53 insertions(+), 37 deletions(-) diff --git a/server/request.c b/server/request.c index e27a0b24a57..86fbc2c6f50 100644 --- a/server/request.c +++ b/server/request.c @@ -160,6 +160,27 @@ AP_DECLARE(int) ap_some_authn_required(request_rec *r) return rv; } +static int walk_location_and_if(request_rec *r) +{ + int access_status; + + if ((access_status = ap_location_walk(r))) { + return access_status; + } + if ((access_status = ap_if_walk(r))) { + return access_status; + } + + /* Don't set per-dir loglevel if LogLevelOverride is set */ + if (!r->connection->log) { + core_dir_config *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 @@ -167,15 +188,14 @@ 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 = 0; - if (r->main) { - /* Lookup subrequests can have a relative path. */ + if (file_req) { + /* File subrequests can have a relative path. */ normalize_flags = AP_NORMALIZE_ALLOW_RELATIVE; } @@ -187,23 +207,39 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) if (!ap_normalize_path(r->parsed_uri.path, normalize_flags | AP_NORMALIZE_DECODE_UNRESERVED)) { - ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO() - "invalid URI path (%s)", r->uri); + ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(10244) + "invalid URI path (%s)", r->unparsed_uri); return HTTP_BAD_REQUEST; } } - /* Let pre_translate_name hooks work with non-decoded URIs, - * and eventually apply their own transformations (return OK). + /* 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. */ - access_status = ap_run_pre_translate_name(r); - if (access_status != OK && access_status != DECLINED) { - return access_status; + 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 apply their own transformations (return OK). + */ + access_status = ap_run_pre_translate_name(r); + if (access_status != OK && access_status != DECLINED) { + 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 == DECLINED && r->parsed_uri.path) { - d = ap_get_core_module_config(r->per_dir_config); + core_dir_config *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); @@ -215,9 +251,8 @@ 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->uri); + "found %%2f (encoded '/') in URI path (%s), " + "returning 404", r->unparsed_uri); } } return access_status; @@ -239,22 +274,11 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) } } - /* 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))) { + 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_translate_name(r))) { return decl_die(access_status, "translate", r); @@ -272,17 +296,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; From f06ba0f1c3f76c6d2057f3009d94b3f6f634bc4a Mon Sep 17 00:00:00 2001 From: Jean-Frederic Clere Date: Tue, 8 Jun 2021 17:57:25 +0200 Subject: [PATCH 6/8] back port: r1879117 r1879137 r1879144 r1879145. --- include/ap_mmn.h | 6 ++- include/http_request.h | 5 ++- modules/proxy/mod_proxy.c | 90 +++++++++++++++++++++++---------------- modules/proxy/mod_proxy.h | 8 ++-- server/request.c | 25 +++++------ server/util.c | 5 ++- 6 files changed, 80 insertions(+), 59 deletions(-) diff --git a/include/ap_mmn.h b/include/ap_mmn.h index 2064ac8423f..34d807df2e1 100644 --- a/include/ap_mmn.h +++ b/include/ap_mmn.h @@ -562,8 +562,10 @@ * 20120211.106 (2.4.49-dev) Add ap_create_request(). * 20120211.107 (2.4.49-dev) Add ap_parse_request_line() and * ap_check_request_header() - * 20120211.108 (2.4.49-dev) Add ap_normalize_path() and - * pre_translate_name hook. + * 20120211.108 (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" */ diff --git a/include/http_request.h b/include/http_request.h index e0d37607650..7e8bfad122e 100644 --- a/include/http_request.h +++ b/include/http_request.h @@ -366,7 +366,10 @@ 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 OK, DECLINED, or HTTP_... + * @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)) diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c index 8507553f9f7..4906af141a2 100644 --- a/modules/proxy/mod_proxy.c +++ b/modules/proxy/mod_proxy.c @@ -922,12 +922,7 @@ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent, } } else { - /* Ignore servlet mapping if r->uri was decoded already, or we - * might consider for instance that an original %3B is a delimiter - * for path parameters (which is not). - */ - if (dconf->use_original_uri == 1 - && (ent->flags & PROXYPASS_MAPPING_SERVLET)) { + if ((ent->flags & PROXYPASS_MAP_SERVLET) == PROXYPASS_MAP_SERVLET) { nocanon = 0; /* ignored since servlet's normalization applies */ len = alias_match_servlet(r->pool, r->uri, fake); } @@ -990,16 +985,17 @@ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent, 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 @@ -1008,11 +1004,17 @@ static int proxy_trans(request_rec *r, int pre_trans) */ 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); - /* Do the work from the hook corresponding to the ProxyUseOriginalURI - * configuration (off/default: translate hook, on: pre_translate hook). + /* 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 ^ (dconf->use_original_uri == 1)) { + if ((pre_trans && !conf->map_encoded_one) + || (!pre_trans && conf->map_encoded_all)) { + /* Fast path, nothing at this stage */ return DECLINED; } @@ -1027,20 +1029,21 @@ static int proxy_trans(request_rec *r, int pre_trans) /* 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 (DONE != rv) { + 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); + 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 (DONE != rv) { return rv; } @@ -1054,6 +1057,7 @@ 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); @@ -1579,6 +1583,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 */ @@ -1736,6 +1742,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; @@ -1803,7 +1812,6 @@ static void *create_proxy_dir_config(apr_pool_t *p, char *dummy) new->add_forwarded_headers_set = 0; new->forward_100_continue = 1; new->forward_100_continue_set = 0; - new->use_original_uri = -1; /* unset (off by default) */ return (void *) new; } @@ -1861,10 +1869,6 @@ 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; - - new->use_original_uri = (add->use_original_uri == -1) - ? base->use_original_uri - : add->use_original_uri; return new; } @@ -2021,9 +2025,6 @@ static const char * else if (!strcasecmp(word,"noquery")) { flags |= PROXYPASS_NOQUERY; } - else if (!strcasecmp(word,"mapping=servlet")) { - flags |= PROXYPASS_MAPPING_SERVLET; - } else { char *val = strchr(word, '='); if (!val) { @@ -2042,11 +2043,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"; @@ -3034,9 +3055,6 @@ static const command_rec proxy_cmds[] = AP_INIT_FLAG("Proxy100Continue", forward_100_continue, NULL, RSRC_CONF|ACCESS_CONF, "on if 100-Continue should be forwarded to the origin server, off if the " "proxy should handle it by itself"), - AP_INIT_FLAG("ProxyUseOriginalURI", ap_set_flag_slot_char, - (void*)APR_OFFSETOF(proxy_dir_conf, use_original_uri), RSRC_CONF|ACCESS_CONF, - "Whether to use the original/encoded URI-path for mapping"), {NULL} }; diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h index 76aa32f7980..853ca0fcbb3 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -124,7 +124,8 @@ struct proxy_remote { #define PROXYPASS_NOCANON 0x01 #define PROXYPASS_INTERPOLATE 0x02 #define PROXYPASS_NOQUERY 0x04 -#define PROXYPASS_MAPPING_SERVLET 0x08 +#define PROXYPASS_MAP_ENCODED 0x08 +#define PROXYPASS_MAP_SERVLET 0x18 /* + MAP_ENCODED */ struct proxy_alias { const char *real; const char *fake; @@ -201,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 { @@ -245,9 +248,6 @@ typedef struct { unsigned int forward_100_continue_set:1; apr_array_header_t *error_override_codes; - - /** Whether to use original/encoded URI-path or not (default) */ - signed char use_original_uri; } proxy_dir_conf; /* if we interpolate env vars per-request, we'll need a per-request diff --git a/server/request.c b/server/request.c index 86fbc2c6f50..60aa9263d34 100644 --- a/server/request.c +++ b/server/request.c @@ -196,7 +196,10 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) if (file_req) { /* File subrequests can have a relative path. */ - normalize_flags = AP_NORMALIZE_ALLOW_RELATIVE; + normalize_flags |= AP_NORMALIZE_ALLOW_RELATIVE; + } + if (sconf->merge_slashes != AP_CORE_CONFIG_OFF) { + normalize_flags |= AP_NORMALIZE_MERGE_SLASHES; } if (r->parsed_uri.path) { @@ -224,11 +227,11 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) return access_status; } - /* Let pre_translate_name hooks work with non-decoded URIs, - * and eventually apply their own transformations (return OK). + /* 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 (access_status != OK && access_status != DECLINED) { + if (ap_is_HTTP_ERROR(access_status)) { return access_status; } @@ -237,7 +240,7 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) } /* Ignore URL unescaping for translated URIs already */ - if (access_status == DECLINED && r->parsed_uri.path) { + if (access_status != DONE && r->parsed_uri.path) { core_dir_config *d = ap_get_core_module_config(r->per_dir_config); if (d->allow_encoded_slashes) { @@ -259,19 +262,11 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) } if (d->allow_encoded_slashes && d->decode_encoded_slashes) { - /* Decoding slashes might have created new /./ and /../ - * segments (e.g. "/.%2F/"), so re-normalize. If asked to, - * merge slashes while at it. + /* Decoding slashes might have created new // or /./ or /../ + * segments (e.g. "/.%2F/"), so re-normalize. */ - if (sconf->merge_slashes != AP_CORE_CONFIG_OFF) { - normalize_flags |= AP_NORMALIZE_MERGE_SLASHES; - } ap_normalize_path(r->parsed_uri.path, normalize_flags); } - else if (sconf->merge_slashes != AP_CORE_CONFIG_OFF) { - /* We still didn't merged slashes yet, do it now. */ - ap_no2slash(r->parsed_uri.path); - } } /* Same, translate_name is not suited for file subrequests */ diff --git a/server/util.c b/server/util.c index de417d86f0a..143c8fc69a4 100644 --- a/server/util.c +++ b/server/util.c @@ -606,7 +606,10 @@ AP_DECLARE(int) ap_normalize_path(char *path, unsigned int flags) */ AP_DECLARE(void) ap_getparents(char *name) { - (void)ap_normalize_path(name, AP_NORMALIZE_ALLOW_RELATIVE); + if (!ap_normalize_path(name, AP_NORMALIZE_NOT_ABOVE_ROOT | + AP_NORMALIZE_ALLOW_RELATIVE)) { + name[0] = '\0'; + } } AP_DECLARE(void) ap_no2slash_ex(char *name, int is_fs_path) From 1ae07ee159682b504c29833e70b8996c15780613 Mon Sep 17 00:00:00 2001 From: Jean-Frederic Clere Date: Tue, 8 Jun 2021 18:11:41 +0200 Subject: [PATCH 7/8] Add: r1879147 r1879149 r1879235 r1879360 --- modules/dav/main/util.c | 1 + modules/proxy/mod_proxy.c | 34 +++++++++++++++++++++++++++------- modules/proxy/mod_proxy.h | 6 +++++- server/request.c | 9 +++++---- 4 files changed, 38 insertions(+), 12 deletions(-) diff --git a/modules/dav/main/util.c b/modules/dav/main/util.c index 8cf3fe5234e..08ebe2764e6 100644 --- a/modules/dav/main/util.c +++ b/modules/dav/main/util.c @@ -665,6 +665,7 @@ static dav_error * dav_process_if_header(request_rec *r, dav_if_header **p_ih) /* clean up the URI a bit */ 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, diff --git a/modules/proxy/mod_proxy.c b/modules/proxy/mod_proxy.c index 4906af141a2..e8b0f173b53 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" @@ -564,10 +565,11 @@ static int alias_match(const char *uri, const char *alias_fakename) * Inspired by mod_jk's jk_servlet_normalize(). */ static int alias_match_servlet(apr_pool_t *p, - const char *uri, + 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; @@ -749,6 +751,8 @@ static int alias_match_servlet(apr_pool_t *p, if (alias[alias_pos - 1] != '/' && uri[uri_pos - 1] == '/') { uri_pos--; } + + *urip = map; return uri_pos; } @@ -862,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); @@ -923,8 +928,9 @@ PROXY_DECLARE(int) ap_proxy_trans_match(request_rec *r, struct proxy_alias *ent, } else { 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 */ - len = alias_match_servlet(r->pool, r->uri, fake); } else { len = alias_match(r->uri, fake); @@ -959,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; @@ -973,14 +979,28 @@ 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 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, int pre_trans) @@ -1032,7 +1052,7 @@ static int proxy_trans(request_rec *r, int pre_trans) enc = (dconf->alias->flags & PROXYPASS_MAP_ENCODED) != 0; if (!(pre_trans ^ enc)) { int rv = ap_proxy_trans_match(r, dconf->alias, dconf); - if (DONE != rv) { + if (rv != HTTP_CONTINUE) { return rv; } } @@ -1044,7 +1064,7 @@ static int proxy_trans(request_rec *r, int pre_trans) enc = (ent->flags & PROXYPASS_MAP_ENCODED) != 0; if (!(pre_trans ^ enc)) { int rv = ap_proxy_trans_match(r, ent, dconf); - if (DONE != rv) { + if (rv != HTTP_CONTINUE) { return rv; } } diff --git a/modules/proxy/mod_proxy.h b/modules/proxy/mod_proxy.h index 853ca0fcbb3..1f660f60ac9 100644 --- a/modules/proxy/mod_proxy.h +++ b/modules/proxy/mod_proxy.h @@ -1175,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 60aa9263d34..cfdb3ee692e 100644 --- a/server/request.c +++ b/server/request.c @@ -192,15 +192,16 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r) int file_req = (r->main && r->filename); core_server_config *sconf = ap_get_core_module_config(r->server->module_config); - unsigned int normalize_flags = 0; + 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 (sconf->merge_slashes != AP_CORE_CONFIG_OFF) { - normalize_flags |= AP_NORMALIZE_MERGE_SLASHES; - } if (r->parsed_uri.path) { /* Normalize: remove /./ and shrink /../ segments, plus From 3a67f7acfd15961332f4541f8ab3dfbfed177fca Mon Sep 17 00:00:00 2001 From: Jean-Frederic Clere Date: Wed, 9 Jun 2021 11:33:51 +0200 Subject: [PATCH 8/8] Adjust the r->connection->log that doesn't belong to 2.4.x --- server/request.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/server/request.c b/server/request.c index cfdb3ee692e..eb30f6b4781 100644 --- a/server/request.c +++ b/server/request.c @@ -163,6 +163,7 @@ AP_DECLARE(int) ap_some_authn_required(request_rec *r) 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; @@ -171,12 +172,9 @@ static int walk_location_and_if(request_rec *r) return access_status; } - /* Don't set per-dir loglevel if LogLevelOverride is set */ - if (!r->connection->log) { - core_dir_config *d = ap_get_core_module_config(r->per_dir_config); - if (d->log) - r->log = d->log; - } + d = ap_get_core_module_config(r->per_dir_config); + if (d->log) + r->log = d->log; return OK; }
    Name
    pre_translate_name
    translate_name
    type_checker
    quick_handler
    This is the first hook that will be called after a request has been mapped to a host or virtual host
    Pre-Translate nameLuaHookPreTranslateNameThis phase translates the requested URI into a filename on the + system, before decoding occurs. Modules such as mod_proxy + can operate in this phase.
    Translate name LuaHookTranslateNamestring yes 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.
    string yes 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.
    range