Skip to content

Commit 6539e67

Browse files
Sanitize request error method and URL context values
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 8972630 commit 6539e67

2 files changed

Lines changed: 81 additions & 12 deletions

File tree

hyperbrowser/transport/error_utils.py

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,35 @@
11
import json
2+
import re
23
from typing import Any
34

45
import httpx
56

7+
_HTTP_METHOD_TOKEN_PATTERN = re.compile(r"^[!#$%&'*+\-.^_`|~0-9A-Z]+$")
8+
9+
10+
def _normalize_request_method(method: Any) -> str:
11+
if not isinstance(method, str) or not method.strip():
12+
return "UNKNOWN"
13+
normalized_method = method.strip().upper()
14+
if not _HTTP_METHOD_TOKEN_PATTERN.fullmatch(normalized_method):
15+
return "UNKNOWN"
16+
return normalized_method
17+
18+
19+
def _normalize_request_url(url: Any) -> str:
20+
if not isinstance(url, str):
21+
return "unknown URL"
22+
normalized_url = url.strip()
23+
if not normalized_url:
24+
return "unknown URL"
25+
if any(character.isspace() for character in normalized_url):
26+
return "unknown URL"
27+
if any(
28+
ord(character) < 32 or ord(character) == 127 for character in normalized_url
29+
):
30+
return "unknown URL"
31+
return normalized_url
32+
633

734
def _stringify_error_value(value: Any, *, _depth: int = 0) -> str:
835
if _depth > 10:
@@ -82,19 +109,13 @@ def extract_request_error_context(error: httpx.RequestError) -> tuple[str, str]:
82109
request_method = request.method
83110
except Exception:
84111
request_method = "UNKNOWN"
85-
if not isinstance(request_method, str) or not request_method.strip():
86-
request_method = "UNKNOWN"
87-
else:
88-
request_method = request_method.strip().upper()
112+
request_method = _normalize_request_method(request_method)
89113

90114
try:
91115
request_url = str(request.url)
92116
except Exception:
93117
request_url = "unknown URL"
94-
if not request_url.strip():
95-
request_url = "unknown URL"
96-
else:
97-
request_url = request_url.strip()
118+
request_url = _normalize_request_url(request_url)
98119
return request_method, request_url
99120

100121

@@ -105,10 +126,8 @@ def format_request_failure_message(
105126
effective_method = (
106127
request_method if request_method != "UNKNOWN" else fallback_method
107128
)
108-
if not isinstance(effective_method, str) or not effective_method.strip():
109-
effective_method = "UNKNOWN"
129+
effective_method = _normalize_request_method(effective_method)
110130

111131
effective_url = request_url if request_url != "unknown URL" else fallback_url
112-
if not isinstance(effective_url, str) or not effective_url.strip():
113-
effective_url = "unknown URL"
132+
effective_url = _normalize_request_url(effective_url)
114133
return f"Request {effective_method} {effective_url} failed"

tests/test_transport_error_utils.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ class _LowercaseMethodRequest:
3232
url = "https://example.com/lowercase"
3333

3434

35+
class _InvalidMethodTokenRequest:
36+
method = "GET /invalid"
37+
url = "https://example.com/invalid-method"
38+
39+
40+
class _WhitespaceInsideUrlRequest:
41+
method = "GET"
42+
url = "https://example.com/with space"
43+
44+
3545
class _RequestErrorWithFailingRequestProperty(httpx.RequestError):
3646
@property
3747
def request(self): # type: ignore[override]
@@ -62,6 +72,18 @@ def request(self): # type: ignore[override]
6272
return _LowercaseMethodRequest()
6373

6474

75+
class _RequestErrorWithInvalidMethodToken(httpx.RequestError):
76+
@property
77+
def request(self): # type: ignore[override]
78+
return _InvalidMethodTokenRequest()
79+
80+
81+
class _RequestErrorWithWhitespaceInsideUrl(httpx.RequestError):
82+
@property
83+
def request(self): # type: ignore[override]
84+
return _WhitespaceInsideUrlRequest()
85+
86+
6587
class _DummyResponse:
6688
def __init__(self, json_value, text: str = "") -> None:
6789
self._json_value = json_value
@@ -123,6 +145,24 @@ def test_extract_request_error_context_normalizes_method_to_uppercase():
123145
assert url == "https://example.com/lowercase"
124146

125147

148+
def test_extract_request_error_context_rejects_invalid_method_tokens():
149+
method, url = extract_request_error_context(
150+
_RequestErrorWithInvalidMethodToken("network down")
151+
)
152+
153+
assert method == "UNKNOWN"
154+
assert url == "https://example.com/invalid-method"
155+
156+
157+
def test_extract_request_error_context_rejects_urls_with_whitespace():
158+
method, url = extract_request_error_context(
159+
_RequestErrorWithWhitespaceInsideUrl("network down")
160+
)
161+
162+
assert method == "GET"
163+
assert url == "unknown URL"
164+
165+
126166
def test_format_request_failure_message_uses_fallback_values():
127167
message = format_request_failure_message(
128168
httpx.RequestError("network down"),
@@ -154,6 +194,16 @@ def test_format_request_failure_message_normalizes_blank_fallback_values():
154194
assert message == "Request UNKNOWN unknown URL failed"
155195

156196

197+
def test_format_request_failure_message_normalizes_lowercase_fallback_method():
198+
message = format_request_failure_message(
199+
httpx.RequestError("network down"),
200+
fallback_method="post",
201+
fallback_url="https://example.com/fallback",
202+
)
203+
204+
assert message == "Request POST https://example.com/fallback failed"
205+
206+
157207
def test_extract_error_message_handles_recursive_list_payloads():
158208
recursive_payload = []
159209
recursive_payload.append(recursive_payload)

0 commit comments

Comments
 (0)