Backport of:

From 9bc1917a27a2323e535aadb081e38172ae0e3fc2 Mon Sep 17 00:00:00 2001
From: Stefan Eissing <icing@apache.org>
Date: Mon, 18 Mar 2019 08:49:59 +0000
Subject: [PATCH] Merge of r1855705 from trunk:

core: merge consecutive slashes in the path



git-svn-id: https://svn.apache.org/repos/asf/httpd/httpd/branches/2.4.x@1855737 13f79535-47bb-0310-9956-ffa450edef68
---
 CHANGES                  |  4 ++++
 docs/manual/mod/core.xml | 26 ++++++++++++++++++++++++++
 include/ap_mmn.h         |  4 +++-
 include/http_core.h      |  2 +-
 include/httpd.h          | 14 ++++++++++++--
 server/core.c            | 13 +++++++++++++
 server/request.c         | 25 +++++++++----------------
 server/util.c            | 10 +++++++---
 8 files changed, 75 insertions(+), 23 deletions(-)

#diff --git a/CHANGES b/CHANGES
#index e3e8a98db24..9dd7045c232 100644
#--- a/CHANGES
#+++ b/CHANGES
#@@ -1,6 +1,10 @@
#                                                          -*- coding: utf-8 -*-
# Changes with Apache 2.4.39
# 
#+  *) core: new configuration option 'MergeSlashes on|off' that controls handling of
#+     multiple, consecutive slash ('/') characters in the path component of the request URL.
#+     [Eric Covener]
#+     
#   *) mod_http2: when SSL renegotiation is inhibited and a 403 ErrorDocument is
#      in play, the proper HTTP/2 stream reset did not trigger with H2_ERR_HTTP_1_1_REQUIRED.
#      Fixed. [Michael Kaufmann] 
#diff --git a/docs/manual/mod/core.xml b/docs/manual/mod/core.xml
#index fc664116727..460b4367621 100644
#--- a/docs/manual/mod/core.xml
#+++ b/docs/manual/mod/core.xml
#@@ -5138,4 +5138,30 @@ recognized methods to modules.</p>
# <seealso><directive module="mod_allowmethods">AllowMethods</directive></seealso>
# </directivesynopsis>
# 
#+<directivesynopsis>
#+<name>MergeSlashes</name>
#+<description>Controls whether the server merges consecutive slashes in URLs.
#+</description>
#+<syntax>MergeSlashes ON|OFF</syntax>
#+<default>MergeSlashes ON</default>
#+<contextlist><context>server config</context><context>virtual host</context>
#+</contextlist>
#+<compatibility>Added in 2.5.1</compatibility>
#+
#+<usage>
#+    <p>By default, the server merges (or collapses) multiple consecutive slash
#+    ('/') characters in the path component of the request URL.</p>
#+
#+    <p>When mapping URL's to the filesystem, these multiple slashes are not 
#+    significant.  However, URL's handled other ways, such as by CGI or proxy,
#+    might prefer to retain the significance of multiple consecutive slashes. 
#+    In these cases <directive>MergeSlashes</directive> can be set to 
#+    <em>OFF</em> to retain the multiple consecutive slashes.  In these
#+    configurations, regular expressions used in the configuration file that match
#+    the path component of the URL (<directive>LocationMatch</directive>,
#+    <directive>RewriteRule</directive>, ...) need to take into account multiple 
#+    consecutive slashes.</p>
#+</usage>
#+</directivesynopsis>
#+
# </modulesynopsis>
Index: apache2-2.4.7/include/http_core.h
===================================================================
--- apache2-2.4.7.orig/include/http_core.h	2019-04-03 10:50:22.424728467 -0400
+++ apache2-2.4.7/include/http_core.h	2019-04-03 10:50:22.420728394 -0400
@@ -682,7 +682,7 @@ typedef struct {
 #define AP_HTTP_METHODS_LENIENT       1
 #define AP_HTTP_METHODS_REGISTERED    2
     char http_methods;
-
+    unsigned int merge_slashes;
 } core_server_config;
 
 /* for AddOutputFiltersByType in core.c */
Index: apache2-2.4.7/include/httpd.h
===================================================================
--- apache2-2.4.7.orig/include/httpd.h	2019-04-03 10:50:22.424728467 -0400
+++ apache2-2.4.7/include/httpd.h	2019-04-03 10:50:22.420728394 -0400
@@ -1629,12 +1629,22 @@ AP_DECLARE(int) ap_unescape_url_keep2f(c
 AP_DECLARE(int) ap_unescape_urlencoded(char *query);
 
 /**
- * Convert all double slashes to single slashes
- * @param name The string to convert
+ * Convert all double slashes to single slashes, except where significant
+ * to the filesystem on the current platform.
+ * @param name The string to convert, assumed to be a filesystem path
  */
 AP_DECLARE(void) ap_no2slash(char *name);
 
 /**
+ * Convert all double slashes to single slashes, except where significant
+ * to the filesystem on the current platform.
+ * @param name The string to convert
+ * @param is_fs_path if set to 0, the significance of any double-slashes is 
+ *        ignored.
+ */
+AP_DECLARE(void) ap_no2slash_ex(char *name, int is_fs_path);
+
+/**
  * Remove all ./ and xx/../ substrings from a file name. Also remove
  * any leading ../ or /../ substrings.
  * @param name the file name to parse
Index: apache2-2.4.7/server/core.c
===================================================================
--- apache2-2.4.7.orig/server/core.c	2019-04-03 10:50:22.424728467 -0400
+++ apache2-2.4.7/server/core.c	2019-04-03 10:54:29.241278799 -0400
@@ -473,6 +473,7 @@ static void *create_core_server_config(a
      */
 
     conf->trace_enable = AP_TRACE_UNSET;
+    conf->merge_slashes = 2;
 
     return (void *)conf;
 }
@@ -532,6 +533,9 @@ static void *merge_core_server_configs(a
     conf->merge_trailers = (virt->merge_trailers != AP_MERGE_TRAILERS_UNSET)
                            ? virt->merge_trailers
                            : base->merge_trailers;
+    conf->merge_slashes = (virt->merge_slashes != 2)
+                          ? virt->merge_slashes
+                          : base->merge_slashes;
 
     return conf;
 }
@@ -1668,6 +1672,13 @@ static const char *set_override(cmd_parm
     return NULL;
 }
 
+static const char *set_core_server_flag(cmd_parms *cmd, void *s_, int flag)
+{
+    core_server_config *conf =
+        ap_get_core_module_config(cmd->server->module_config);
+    return ap_set_flag_slot(cmd, conf, flag);
+}
+
 static const char *set_override_list(cmd_parms *cmd, void *d_, int argc, char *const argv[])
 {
     core_dir_config *d = d_;
@@ -4258,6 +4269,10 @@ AP_INIT_ITERATE("HttpProtocolOptions", s
                 "'Unsafe' or 'Strict' (default). Sets HTTP acceptance rules"),
 AP_INIT_ITERATE("RegisterHttpMethod", set_http_method, NULL, RSRC_CONF,
                 "Registers non-standard HTTP methods"),
+AP_INIT_FLAG("MergeSlashes", set_core_server_flag, 
+             (void *)APR_OFFSETOF(core_server_config, merge_slashes),  
+             RSRC_CONF,
+             "Controls whether consecutive slashes in the URI path are merged"),
 { NULL }
 };
 
Index: apache2-2.4.7/server/request.c
===================================================================
--- apache2-2.4.7.orig/server/request.c	2019-04-03 10:50:22.424728467 -0400
+++ apache2-2.4.7/server/request.c	2019-04-03 10:50:22.424728467 -0400
@@ -167,6 +167,8 @@ AP_DECLARE(int) ap_process_request_inter
     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);
 
     /* Ignore embedded %2F's in path for proxy requests */
     if (!r->proxyreq && r->parsed_uri.path) {
@@ -191,6 +193,10 @@ AP_DECLARE(int) ap_process_request_inter
     }
 
     ap_getparents(r->uri);     /* OK --- shrinking transformations... */
+    if (sconf->merge_slashes != 0) { 
+        ap_no2slash(r->uri);
+        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,
@@ -1383,20 +1389,7 @@ AP_DECLARE(int) ap_location_walk(request
 
     cache = prep_walk_cache(AP_NOTE_LOCATION_WALK, r);
     cached = (cache->cached != NULL);
-
-    /* Location and LocationMatch differ on their behaviour w.r.t. multiple
-     * slashes.  Location matches multiple slashes with a single slash,
-     * LocationMatch doesn't.  An exception, for backwards brokenness is
-     * absoluteURIs... in which case neither match multiple slashes.
-     */
-    if (r->uri[0] != '/') {
-        entry_uri = r->uri;
-    }
-    else {
-        char *uri = apr_pstrdup(r->pool, r->uri);
-        ap_no2slash(uri);
-        entry_uri = uri;
-    }
+    entry_uri = r->uri;
 
     /* If we have an cache->cached location that matches r->uri,
      * and the vhost's list of locations hasn't changed, we can skip
@@ -1449,7 +1442,7 @@ AP_DECLARE(int) ap_location_walk(request
              * terminated (or at the end of the string) to match.
              */
             if (entry_core->r
-                ? ap_regexec(entry_core->r, r->uri, 0, NULL, 0)
+                ? ap_regexec(entry_core->r, entry_uri, 0, NULL, 0)
                 : (entry_core->d_is_fnmatch
                    ? apr_fnmatch(entry_core->d, cache->cached, APR_FNM_PATHNAME)
                    : (strncmp(entry_core->d, cache->cached, len)
Index: apache2-2.4.7/server/util.c
===================================================================
--- apache2-2.4.7.orig/server/util.c	2019-04-03 10:50:22.424728467 -0400
+++ apache2-2.4.7/server/util.c	2019-04-03 10:50:22.424728467 -0400
@@ -561,16 +561,16 @@ AP_DECLARE(void) ap_getparents(char *nam
         name[l] = '\0';
     }
 }
-
-AP_DECLARE(void) ap_no2slash(char *name)
+AP_DECLARE(void) ap_no2slash_ex(char *name, int is_fs_path)
 {
+
     char *d, *s;
 
     s = d = name;
 
 #ifdef HAVE_UNC_PATHS
     /* Check for UNC names.  Leave leading two slashes. */
-    if (s[0] == '/' && s[1] == '/')
+    if (is_fs_path && s[0] == '/' && s[1] == '/')
         *d++ = *s++;
 #endif
 
@@ -587,6 +587,10 @@ AP_DECLARE(void) ap_no2slash(char *name)
     *d = '\0';
 }
 
+AP_DECLARE(void) ap_no2slash(char *name)
+{
+    ap_no2slash_ex(name, 1);
+}
 
 /*
  * copy at most n leading directories of s into d
