Blob Blame History Raw
diff --git a/docs/manual/mod/core.html.en b/docs/manual/mod/core.html.en
index 86d9bee..e08034b 100644
--- a/docs/manual/mod/core.html.en
+++ b/docs/manual/mod/core.html.en
@@ -90,6 +90,7 @@ available</td></tr>
 <li><img alt="" src="../images/down.gif" /> <a href="#maxrangeoverlaps">MaxRangeOverlaps</a></li>
 <li><img alt="" src="../images/down.gif" /> <a href="#maxrangereversals">MaxRangeReversals</a></li>
 <li><img alt="" src="../images/down.gif" /> <a href="#maxranges">MaxRanges</a></li>
+<li><img alt="" src="../images/down.gif" /> <a href="#mergeslashes">MergeSlashes</a></li>
 <li><img alt="" src="../images/down.gif" /> <a href="#mutex">Mutex</a></li>
 <li><img alt="" src="../images/down.gif" /> <a href="#namevirtualhost">NameVirtualHost</a></li>
 <li><img alt="" src="../images/down.gif" /> <a href="#options">Options</a></li>
@@ -3170,6 +3171,30 @@ resource </td></tr>
 
 </div>
 <div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
+<div class="directive-section"><h2><a name="MergeSlashes" id="MergeSlashes">MergeSlashes</a> <a name="mergeslashes" id="mergeslashes">Directive</a></h2>
+<table class="directive">
+<tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Controls whether the server merges consecutive slashes in URLs. </td></tr>
+<tr><th><a href="directive-dict.html#Syntax">Syntax:</a></th><td><code>MergeSlashes ON | OFF</code></td></tr>
+<tr><th><a href="directive-dict.html#Default">Default:</a></th><td><code>MergeSlashes ON</code></td></tr>
+<tr><th><a href="directive-dict.html#Context">Context:</a></th><td>server config, virtual host</td></tr>
+<tr><th><a href="directive-dict.html#Status">Status:</a></th><td>Core</td></tr>
+<tr><th><a href="directive-dict.html#Module">Module:</a></th><td>core</td></tr>
+<tr><th><a href="directive-dict.html#Compatibility">Compatibility:</a></th><td>Available in Apache HTTP Server 2.4.6 in Red Hat Enterprise Linux 7</td></tr>
+</table>
+    <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 <code class="directive">MergeSlashes</code> 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 (<code class="directive">LocationMatch</code>,
+       <code class="directive">RewriteRule</code>, ...) need to take into account multiple 
+       consecutive slashes.</p>
+</div>
+<div class="top"><a href="#page-header"><img alt="top" src="../images/up.gif" /></a></div>
 <div class="directive-section"><h2><a name="Mutex" id="Mutex">Mutex</a> <a name="mutex" id="mutex">Directive</a></h2>
 <table class="directive">
 <tr><th><a href="directive-dict.html#Description">Description:</a></th><td>Configures mutex mechanism and lock file directory for all
diff --git a/include/http_core.h b/include/http_core.h
index c05d06e..76bf5a4 100644
--- a/include/http_core.h
+++ b/include/http_core.h
@@ -465,6 +465,17 @@ typedef unsigned long etag_components_t;
 /* This is the default value used */
 #define ETAG_BACKWARD (ETAG_MTIME | ETAG_SIZE)
 
+/* Generic ON/OFF/UNSET for unsigned int foo :2 */
+#define AP_CORE_CONFIG_OFF   (0)
+#define AP_CORE_CONFIG_ON    (1)
+#define AP_CORE_CONFIG_UNSET (2)
+
+/* Generic merge of flag */
+#define AP_CORE_MERGE_FLAG(field, to, base, over) to->field = \
+               over->field != AP_CORE_CONFIG_UNSET            \
+               ? over->field                                  \
+               : base->field
+
 /**
  * @brief Server Signature Enumeration
  */
@@ -682,7 +693,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 */
diff --git a/include/httpd.h b/include/httpd.h
index 176ef5e..a552358 100644
--- a/include/httpd.h
+++ b/include/httpd.h
@@ -1622,11 +1622,21 @@ AP_DECLARE(int) ap_unescape_url_keep2f(char *url, int decode_slashes);
 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.
diff --git a/server/core.c b/server/core.c
index 0e69f8c..67efd7e 100644
--- a/server/core.c
+++ b/server/core.c
@@ -476,6 +476,7 @@ static void *create_core_server_config(apr_pool_t *a, server_rec *s)
      */
 
     conf->trace_enable = AP_TRACE_UNSET;
+    conf->merge_slashes = AP_CORE_CONFIG_UNSET;
 
     return (void *)conf;
 }
@@ -536,6 +537,8 @@ static void *merge_core_server_configs(apr_pool_t *p, void *basev, void *virtv)
                            ? virt->merge_trailers
                            : base->merge_trailers;
 
+    AP_CORE_MERGE_FLAG(merge_slashes, conf, base, virt);
+
     return conf;
 }
 
@@ -1673,6 +1676,13 @@ static const char *set_override(cmd_parms *cmd, void *d_, const char *l)
     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_;
@@ -4216,6 +4226,10 @@ AP_INIT_ITERATE("HttpProtocolOptions", set_http_protocol_options, NULL, RSRC_CON
 ,
 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 }
 };
 
diff --git a/server/request.c b/server/request.c
index 4eef097..cba3891 100644
--- a/server/request.c
+++ b/server/request.c
@@ -167,6 +167,8 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r)
     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,12 @@ AP_DECLARE(int) ap_process_request_internal(request_rec *r)
     }
 
     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);
+        }
+     }
 
     /* 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 +1391,7 @@ AP_DECLARE(int) ap_location_walk(request_rec *r)
 
     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 +1444,7 @@ AP_DECLARE(int) ap_location_walk(request_rec *r)
              * 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)
diff --git a/server/util.c b/server/util.c
index f9e3b51..4eac462 100644
--- a/server/util.c
+++ b/server/util.c
@@ -561,16 +561,20 @@ AP_DECLARE(void) ap_getparents(char *name)
         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;
 
+    if (!*name) {
+        return;
+    }
+
     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 +591,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