Skip to content

Commit 0b47237

Browse files
committed
Fix issue with repeated-slash requests redirecting
Previously if a request had repeated slashes it could match a single slash route and hence return a redirect response even if merge_slashes was False. Additionally setting the merge_slashes attribute of the map after initialisation had no affect, compounding this problem.
1 parent f516c40 commit 0b47237

File tree

4 files changed

+15
-2
lines changed

4 files changed

+15
-2
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ Version 3.0.2
55

66
Unreleased
77

8+
- Ensure setting merge_slashes to False results in NotFound for
9+
repeated-slash requests against single slash routes. :issue:`2834`
810
- Fix handling of TypeError in TypeConversionDict.get() to match
911
ValueErrors. :issue:`2843`
1012
- Fix response_wrapper type check in test client. :issue:`2831`

src/werkzeug/routing/map.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ def __init__(
109109

110110
self.default_subdomain = default_subdomain
111111
self.strict_slashes = strict_slashes
112-
self.merge_slashes = merge_slashes
113112
self.redirect_defaults = redirect_defaults
114113
self.host_matching = host_matching
115114

@@ -123,6 +122,14 @@ def __init__(
123122
for rulefactory in rules or ():
124123
self.add(rulefactory)
125124

125+
@property
126+
def merge_slashes(self) -> bool:
127+
return self._matcher.merge_slashes
128+
129+
@merge_slashes.setter
130+
def merge_slashes(self, value: bool) -> None:
131+
self._matcher.merge_slashes = value
132+
126133
def is_endpoint_expecting(self, endpoint: str, *arguments: str) -> bool:
127134
"""Iterate over all rules and check if the endpoint expects
128135
the arguments provided. This is for example useful if you have

src/werkzeug/routing/matcher.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ def _match(
177177
rv = _match(self._root, [domain, *path.split("/")], [])
178178
except SlashRequired:
179179
raise RequestPath(f"{path}/") from None
180-
if rv is None:
180+
if rv is None or rv[0].merge_slashes is False:
181181
raise NoMatch(have_match_for, websocket_mismatch)
182182
else:
183183
raise RequestPath(f"{path}")

tests/test_routing.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ def test_merge_slashes_match():
9595
r.Rule("/yes/tail/", endpoint="yes_tail"),
9696
r.Rule("/with/<path:path>", endpoint="with_path"),
9797
r.Rule("/no//merge", endpoint="no_merge", merge_slashes=False),
98+
r.Rule("/no/merging", endpoint="no_merging", merge_slashes=False),
9899
]
99100
)
100101
adapter = url_map.bind("localhost", "/")
@@ -124,6 +125,9 @@ def test_merge_slashes_match():
124125

125126
assert adapter.match("/no//merge")[0] == "no_merge"
126127

128+
assert adapter.match("/no/merging")[0] == "no_merging"
129+
pytest.raises(NotFound, lambda: adapter.match("/no//merging"))
130+
127131

128132
@pytest.mark.parametrize(
129133
("path", "expected"),

0 commit comments

Comments
 (0)