Skip to content

Commit

Permalink
Expose redirect path in RequestRedirect
Browse files Browse the repository at this point in the history
When we raise the RequestRedirect exception
we have already computed the new url that
should be redirected to in the exception
(new_url) but we don't pass the the
new path that we used to compute the url.

This causes another layer of redirection
in depending code that needs to urlparse()
the url to get the path we already have
the data for.

This adds new_path to the RequestRedirect
exception and populates it with the path
used when computing new_url.

Fixes: pallets#3000
  • Loading branch information
tobias-urdin committed Nov 11, 2024
1 parent 9a69323 commit f23e417
Show file tree
Hide file tree
Showing 2 changed files with 26 additions and 14 deletions.
9 changes: 6 additions & 3 deletions src/werkzeug/routing/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,10 @@ class RequestRedirect(HTTPException, RoutingException):

code = 308

def __init__(self, new_url: str) -> None:
def __init__(self, new_url: str, new_path: t.Optional[str] = None) -> None:
super().__init__(new_url)
self.new_url = new_url
self.new_path = new_path

def get_response(
self,
Expand Down Expand Up @@ -98,7 +99,8 @@ def _score_rule(rule: Rule) -> float:
str(rule.endpoint),
str(self.endpoint),
).ratio(),
0.01 * bool(set(self.values or ()).issubset(rule.arguments)),
0.01 * bool(set(self.values or ()
).issubset(rule.arguments)),
0.01 * bool(rule.methods and self.method in rule.methods),
]
)
Expand Down Expand Up @@ -134,7 +136,8 @@ def __str__(self) -> str:
f" Did you forget to specify values {sorted(missing_values)!r}?"
)
else:
message.append(f" Did you mean {self.suggested.endpoint!r} instead?")
message.append(
f" Did you mean {self.suggested.endpoint!r} instead?")
return "".join(message)


Expand Down
31 changes: 20 additions & 11 deletions src/werkzeug/routing/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ def bind(
server_name = server_name.lower()
if self.host_matching:
if subdomain is not None:
raise RuntimeError("host matching enabled and a subdomain was provided")
raise RuntimeError(
"host matching enabled and a subdomain was provided")
elif subdomain is None:
subdomain = self.default_subdomain
if script_name is None:
Expand Down Expand Up @@ -602,12 +603,14 @@ def match(
path_part = f"/{path_info.lstrip('/')}" if path_info else ""

try:
result = self.map._matcher.match(domain_part, path_part, method, websocket)
result = self.map._matcher.match(
domain_part, path_part, method, websocket)
except RequestPath as e:
# safe = https://url.spec.whatwg.org/#url-path-segment-string
new_path = quote(e.path_info, safe="!$&'()*+,/:;=@")
raise RequestRedirect(
self.make_redirect_url(new_path, query_args)
self.make_redirect_url(new_path, query_args),
new_path,
) from None
except RequestAliasRedirect as e:
raise RequestRedirect(
Expand All @@ -617,11 +620,13 @@ def match(
e.matched_values,
method,
query_args,
)
),
path_part,
) from None
except NoMatch as e:
if e.have_match_for:
raise MethodNotAllowed(valid_methods=list(e.have_match_for)) from None
raise MethodNotAllowed(
valid_methods=list(e.have_match_for)) from None

if e.websocket_mismatch:
raise WebsocketMismatch() from None
Expand All @@ -631,9 +636,10 @@ def match(
rule, rv = result

if self.map.redirect_defaults:
redirect_url = self.get_default_redirect(rule, method, rv, query_args)
(redirect_url, redirect_path) = self.get_default_redirect(
rule, method, rv, query_args)
if redirect_url is not None:
raise RequestRedirect(redirect_url)
raise RequestRedirect(redirect_url, redirect_path)

if rule.redirect_to is not None:
if isinstance(rule.redirect_to, str):
Expand All @@ -642,7 +648,8 @@ def _handle_match(match: t.Match[str]) -> str:
value = rv[match.group(1)]
return rule._converters[match.group(1)].to_url(value)

redirect_url = _simple_rule_re.sub(_handle_match, rule.redirect_to)
redirect_url = _simple_rule_re.sub(
_handle_match, rule.redirect_to)
else:
redirect_url = rule.redirect_to(self, **rv)

Expand All @@ -655,7 +662,8 @@ def _handle_match(match: t.Match[str]) -> str:
urljoin(
f"{self.url_scheme or 'http'}://{netloc}{self.script_name}",
redirect_url,
)
),
redirect_url,
)

if return_rule:
Expand Down Expand Up @@ -736,8 +744,9 @@ def get_default_redirect(
if r.provides_defaults_for(rule) and r.suitable_for(values, method):
values.update(r.defaults) # type: ignore
domain_part, path = r.build(values) # type: ignore
return self.make_redirect_url(path, query_args, domain_part=domain_part)
return None
return self.make_redirect_url(path, query_args,
domain_part=domain_part), path
return None, None

def encode_query_args(self, query_args: t.Mapping[str, t.Any] | str) -> str:
if not isinstance(query_args, str):
Expand Down

0 comments on commit f23e417

Please sign in to comment.