|
19 | 19 | import uuid |
20 | 20 | from abc import ABC, abstractmethod |
21 | 21 | from typing import Any, Awaitable, Callable, Dict, List, Optional, Tuple |
22 | | -from urllib.parse import parse_qs |
| 22 | +from urllib.parse import parse_qs, urlsplit, urlunsplit, quote, quote_plus |
23 | 23 |
|
24 | 24 | try: |
25 | 25 | import orjson as json # Use `orjson` if installed as it is faster |
@@ -1044,18 +1044,31 @@ async def _send_websocket_close( |
1044 | 1044 | "reason": reason |
1045 | 1045 | }) |
1046 | 1046 |
|
1047 | | - def _redirect(self, location: str, extra_headers: list = None) -> Tuple[int, str, List[Tuple[str, str]]]: |
| 1047 | + def _encode_redirect_url(self, url: str) -> str: |
| 1048 | + """ |
| 1049 | + Make a URL safe to put in an HTTP Location header. |
| 1050 | +
|
| 1051 | + Key rule: Location must be ASCII/latin-1 -> percent-encode non-ASCII. |
| 1052 | + We percent-encode the PATH but we do NOT touch the query string. |
| 1053 | + """ |
| 1054 | + p = urlsplit(url) |
| 1055 | + safe_path = quote(p.path, safe="/%") |
| 1056 | + return urlunsplit((p.scheme, p.netloc, safe_path, p.query, p.fragment)) |
| 1057 | + |
| 1058 | + def _redirect( |
| 1059 | + self, |
| 1060 | + location: str, |
| 1061 | + extra_headers: list | None = None, |
| 1062 | + ) -> Tuple[int, str, List[Tuple[str, str]]]: |
1048 | 1063 | """ |
1049 | 1064 | Generate an HTTP redirect response. |
1050 | | - Args: |
1051 | | - location: The URL to redirect to. |
1052 | | - extra_headers: Optional list of tuples (header_name, header_value) to include in the response. |
1053 | | - Returns: |
1054 | | - A tuple containing the HTTP status code, the HTML body, and headers list. |
1055 | 1065 | """ |
1056 | | - headers = [("Location", location)] |
| 1066 | + safe_location = self._encode_redirect_url(location) |
| 1067 | + |
| 1068 | + headers = [("Location", safe_location)] |
1057 | 1069 | if extra_headers: |
1058 | 1070 | headers.extend(extra_headers) |
| 1071 | + |
1059 | 1072 | return 302, "", headers |
1060 | 1073 |
|
1061 | 1074 | async def _render_template(self, name: str, **kwargs: Any) -> str: |
|
0 commit comments