From f23e4174366e1704c634ea3fe72d15167d424f80 Mon Sep 17 00:00:00 2001 From: Tobias Urdin Date: Mon, 11 Nov 2024 08:49:48 +0100 Subject: [PATCH] Expose redirect path in RequestRedirect 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: #3000 --- src/werkzeug/routing/exceptions.py | 9 ++++++--- src/werkzeug/routing/map.py | 31 +++++++++++++++++++----------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/src/werkzeug/routing/exceptions.py b/src/werkzeug/routing/exceptions.py index eeabd4ed1..1cf24ceb7 100644 --- a/src/werkzeug/routing/exceptions.py +++ b/src/werkzeug/routing/exceptions.py @@ -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, @@ -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), ] ) @@ -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) diff --git a/src/werkzeug/routing/map.py b/src/werkzeug/routing/map.py index 4d15e8824..134b2da34 100644 --- a/src/werkzeug/routing/map.py +++ b/src/werkzeug/routing/map.py @@ -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: @@ -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( @@ -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 @@ -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): @@ -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) @@ -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: @@ -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):