Repository: pallets/werkzeug

Issue:
Double slashes aren't being handled correctly
When a request is made with double slashes and merge_slashes is set to false the request is returning a 200 redirect (308). This is only true if the route is currently defined in the app. This seems to be a change in behavior starting in version 2.2.0. Previously which seems expected it was returning a 404. Checking the latest version of Werkzueg this behavior still seems to be happening (returning the redirect instead of 404).



This is a basic flask app configuration that can be used. 
```
from flask import Flask

app = Flask(__name__)
app.url_map.merge_slashes = False


@app.route('/api')
def api():
    return 'Hello, World!'


@app.route('/api/path')
def packages():
    return 'Hello, Welcome!'


```

making this curl request returns a redirect with any version of werkzueg >=2.2.0, previously it returned a 404.

```
curl http://127.0.0.1:5000/api//path
```

If you make a request to a path that isn't created in the app with double slashes then it returns a 404.
```
curl http://127.0.0.1:5000/api//something-else

```



The request with the double slashes in the url should be returning a 404 since the merge_slashes is set to False and it shouldn't be trying to redirect.


Environment:

- Python version: 3.11
- Werkzeug version: 2.2.0



Discussion:

Resolving patch:
diff --git a/src/werkzeug/routing/map.py b/src/werkzeug/routing/map.py
--- a/src/werkzeug/routing/map.py
+++ b/src/werkzeug/routing/map.py
@@ -109,7 +109,6 @@ def __init__(
 
         self.default_subdomain = default_subdomain
         self.strict_slashes = strict_slashes
-        self.merge_slashes = merge_slashes
         self.redirect_defaults = redirect_defaults
         self.host_matching = host_matching
 
@@ -123,6 +122,14 @@ def __init__(
         for rulefactory in rules or ():
             self.add(rulefactory)
 
+    @property
+    def merge_slashes(self) -> bool:
+        return self._matcher.merge_slashes
+
+    @merge_slashes.setter
+    def merge_slashes(self, value: bool) -> None:
+        self._matcher.merge_slashes = value
+
     def is_endpoint_expecting(self, endpoint: str, *arguments: str) -> bool:
         """Iterate over all rules and check if the endpoint expects
         the arguments provided.  This is for example useful if you have
diff --git a/src/werkzeug/routing/matcher.py b/src/werkzeug/routing/matcher.py
--- a/src/werkzeug/routing/matcher.py
+++ b/src/werkzeug/routing/matcher.py
@@ -177,7 +177,7 @@ def _match(
                 rv = _match(self._root, [domain, *path.split("/")], [])
             except SlashRequired:
                 raise RequestPath(f"{path}/") from None
-            if rv is None:
+            if rv is None or rv[0].merge_slashes is False:
                 raise NoMatch(have_match_for, websocket_mismatch)
             else:
                 raise RequestPath(f"{path}")


Test patch:
diff --git a/tests/test_routing.py b/tests/test_routing.py
--- a/tests/test_routing.py
+++ b/tests/test_routing.py
@@ -95,6 +95,7 @@ def test_merge_slashes_match():
             r.Rule("/yes/tail/", endpoint="yes_tail"),
             r.Rule("/with/<path:path>", endpoint="with_path"),
             r.Rule("/no//merge", endpoint="no_merge", merge_slashes=False),
+            r.Rule("/no/merging", endpoint="no_merging", merge_slashes=False),
         ]
     )
     adapter = url_map.bind("localhost", "/")
@@ -124,6 +125,9 @@ def test_merge_slashes_match():
 
     assert adapter.match("/no//merge")[0] == "no_merge"
 
+    assert adapter.match("/no/merging")[0] == "no_merging"
+    pytest.raises(NotFound, lambda: adapter.match("/no//merging"))
+
 
 @pytest.mark.parametrize(
     ("path", "expected"),