Skip to content

Commit

Permalink
Fix issue with repeated-slash requests redirecting
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
pgjones committed Mar 5, 2024
1 parent f516c40 commit 0b47237
Show file tree
Hide file tree
Showing 4 changed files with 15 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Version 3.0.2

Unreleased

- Ensure setting merge_slashes to False results in NotFound for
repeated-slash requests against single slash routes. :issue:`2834`
- Fix handling of TypeError in TypeConversionDict.get() to match
ValueErrors. :issue:`2843`
- Fix response_wrapper type check in test client. :issue:`2831`
Expand Down
9 changes: 8 additions & 1 deletion src/werkzeug/routing/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/werkzeug/routing/matcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand Down
4 changes: 4 additions & 0 deletions tests/test_routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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", "/")
Expand Down Expand Up @@ -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"),
Expand Down

0 comments on commit 0b47237

Please sign in to comment.