You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by ic...@apache.org on 2021/08/10 08:27:19 UTC

svn commit: r1892161 - in /httpd/httpd/branches/2.4.x: ./ docs/manual/developer/ docs/manual/mod/ include/ modules/dav/main/ modules/examples/ modules/generators/ modules/loggers/ modules/lua/ modules/proxy/ server/

Author: icing
Date: Tue Aug 10 08:27:18 2021
New Revision: 1892161

URL: http://svn.apache.org/viewvc?rev=1892161&view=rev
Log:
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.


Modified:
    httpd/httpd/branches/2.4.x/   (props changed)
    httpd/httpd/branches/2.4.x/docs/manual/developer/modguide.xml
    httpd/httpd/branches/2.4.x/docs/manual/mod/mod_log_debug.xml
    httpd/httpd/branches/2.4.x/docs/manual/mod/mod_lua.xml
    httpd/httpd/branches/2.4.x/include/ap_mmn.h
    httpd/httpd/branches/2.4.x/include/http_request.h
    httpd/httpd/branches/2.4.x/include/httpd.h
    httpd/httpd/branches/2.4.x/modules/dav/main/util.c
    httpd/httpd/branches/2.4.x/modules/examples/mod_example_hooks.c
    httpd/httpd/branches/2.4.x/modules/generators/mod_autoindex.c
    httpd/httpd/branches/2.4.x/modules/generators/mod_info.c
    httpd/httpd/branches/2.4.x/modules/loggers/mod_log_debug.c
    httpd/httpd/branches/2.4.x/modules/lua/mod_lua.c
    httpd/httpd/branches/2.4.x/modules/proxy/mod_proxy.c
    httpd/httpd/branches/2.4.x/modules/proxy/mod_proxy.h
    httpd/httpd/branches/2.4.x/server/request.c
    httpd/httpd/branches/2.4.x/server/util.c

Propchange: httpd/httpd/branches/2.4.x/
------------------------------------------------------------------------------
  Merged /httpd/httpd/trunk:r1879074-1879080,1879094-1879095,1879110-1879112,1879114,1879116-1879117,1879137,1879144-1879145,1879147,1879149,1879235,1879360

Modified: httpd/httpd/branches/2.4.x/docs/manual/developer/modguide.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/docs/manual/developer/modguide.xml?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/docs/manual/developer/modguide.xml (original)
+++ httpd/httpd/branches/2.4.x/docs/manual/developer/modguide.xml Tue Aug 10 08:27:18 2021
@@ -237,6 +237,7 @@ can create. Some other ways of hooking a
 <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>

Modified: httpd/httpd/branches/2.4.x/docs/manual/mod/mod_log_debug.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/docs/manual/mod/mod_log_debug.xml?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/docs/manual/mod/mod_log_debug.xml (original)
+++ httpd/httpd/branches/2.4.x/docs/manual/mod/mod_log_debug.xml Tue Aug 10 08:27:18 2021
@@ -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>

Modified: httpd/httpd/branches/2.4.x/docs/manual/mod/mod_lua.xml
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/docs/manual/mod/mod_lua.xml?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/docs/manual/mod/mod_lua.xml (original)
+++ httpd/httpd/branches/2.4.x/docs/manual/mod/mod_lua.xml Tue Aug 10 08:27:18 2021
@@ -216,6 +216,13 @@ performing access control, or setting mi
             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>
@@ -1494,6 +1501,23 @@ end
 </usage>
 </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

Modified: httpd/httpd/branches/2.4.x/include/ap_mmn.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/include/ap_mmn.h?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/include/ap_mmn.h (original)
+++ httpd/httpd/branches/2.4.x/include/ap_mmn.h Tue Aug 10 08:27:18 2021
@@ -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

Modified: httpd/httpd/branches/2.4.x/include/http_request.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/include/http_request.h?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/include/http_request.h (original)
+++ httpd/httpd/branches/2.4.x/include/http_request.h Tue Aug 10 08:27:18 2021
@@ -364,6 +364,18 @@ AP_DECLARE_HOOK(int,create_request,(requ
 
 /**
  * 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

Modified: httpd/httpd/branches/2.4.x/include/httpd.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/include/httpd.h?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/include/httpd.h (original)
+++ httpd/httpd/branches/2.4.x/include/httpd.h Tue Aug 10 08:27:18 2021
@@ -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.

Modified: httpd/httpd/branches/2.4.x/modules/dav/main/util.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/dav/main/util.c?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/dav/main/util.c (original)
+++ httpd/httpd/branches/2.4.x/modules/dav/main/util.c Tue Aug 10 08:27:18 2021
@@ -664,7 +664,13 @@ static dav_error * dav_process_if_header
             /* 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) {

Modified: httpd/httpd/branches/2.4.x/modules/examples/mod_example_hooks.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/examples/mod_example_hooks.c?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/examples/mod_example_hooks.c (original)
+++ httpd/httpd/branches/2.4.x/modules/examples/mod_example_hooks.c Tue Aug 10 08:27:18 2021
@@ -1175,6 +1175,22 @@ static int x_post_read_request(request_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
     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);

Modified: httpd/httpd/branches/2.4.x/modules/generators/mod_autoindex.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/generators/mod_autoindex.c?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/generators/mod_autoindex.c (original)
+++ httpd/httpd/branches/2.4.x/modules/generators/mod_autoindex.c Tue Aug 10 08:27:18 2021
@@ -1266,8 +1266,9 @@ static struct ent *make_parent_entry(apr
     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);
     }
 

Modified: httpd/httpd/branches/2.4.x/modules/generators/mod_info.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/generators/mod_info.c?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/generators/mod_info.c (original)
+++ httpd/httpd/branches/2.4.x/modules/generators/mod_info.c Tue Aug 10 08:27:18 2021
@@ -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},

Modified: httpd/httpd/branches/2.4.x/modules/loggers/mod_log_debug.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/loggers/mod_log_debug.c?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/loggers/mod_log_debug.c (original)
+++ httpd/httpd/branches/2.4.x/modules/loggers/mod_log_debug.c Tue Aug 10 08:27:18 2021
@@ -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
     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);

Modified: httpd/httpd/branches/2.4.x/modules/lua/mod_lua.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/lua/mod_lua.c?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/lua/mod_lua.c (original)
+++ httpd/httpd/branches/2.4.x/modules/lua/mod_lua.c Tue Aug 10 08:27:18 2021
@@ -1202,6 +1202,11 @@ static int lua_check_user_id_harness_las
 }
 */
 
+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
     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_
                            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,

Modified: httpd/httpd/branches/2.4.x/modules/proxy/mod_proxy.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/proxy/mod_proxy.c?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/proxy/mod_proxy.c (original)
+++ httpd/httpd/branches/2.4.x/modules/proxy/mod_proxy.c Tue Aug 10 08:27:18 2021
@@ -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,
     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(
     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(
         }
     }
     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(
          */
         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(
             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_po
     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_poo
     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_
                                              : 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 */

Modified: httpd/httpd/branches/2.4.x/modules/proxy/mod_proxy.h
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/modules/proxy/mod_proxy.h?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/modules/proxy/mod_proxy.h (original)
+++ httpd/httpd/branches/2.4.x/modules/proxy/mod_proxy.h Tue Aug 10 08:27:18 2021
@@ -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_syn
  * @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,

Modified: httpd/httpd/branches/2.4.x/server/request.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/server/request.c?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/server/request.c (original)
+++ httpd/httpd/branches/2.4.x/server/request.c Tue Aug 10 08:27:18 2021
@@ -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(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(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_inter
             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_inter
 
     /* 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(reques
     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
         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

Modified: httpd/httpd/branches/2.4.x/server/util.c
URL: http://svn.apache.org/viewvc/httpd/httpd/branches/2.4.x/server/util.c?rev=1892161&r1=1892160&r2=1892161&view=diff
==============================================================================
--- httpd/httpd/branches/2.4.x/server/util.c (original)
+++ httpd/httpd/branches/2.4.x/server/util.c Tue Aug 10 08:27:18 2021
@@ -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(a
     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';
-
-    /* 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--;
+    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;
+                }
             }
-            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)
 {