Skip to content

Commit 487da30

Browse files
Wrap base URL decode failures with context
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent bf510d5 commit 487da30

2 files changed

Lines changed: 78 additions & 4 deletions

File tree

hyperbrowser/config.py

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,16 +38,37 @@ def __post_init__(self) -> None:
3838
def _decode_url_component_with_limit(value: str, *, component_label: str) -> str:
3939
decoded_value = value
4040
for _ in range(10):
41-
next_decoded_value = unquote(decoded_value)
41+
next_decoded_value = ClientConfig._safe_unquote(
42+
decoded_value,
43+
component_label=component_label,
44+
)
4245
if next_decoded_value == decoded_value:
4346
return decoded_value
4447
decoded_value = next_decoded_value
45-
if unquote(decoded_value) == decoded_value:
48+
if (
49+
ClientConfig._safe_unquote(
50+
decoded_value,
51+
component_label=component_label,
52+
)
53+
== decoded_value
54+
):
4655
return decoded_value
4756
raise HyperbrowserError(
4857
f"{component_label} contains excessively nested URL encoding"
4958
)
5059

60+
@staticmethod
61+
def _safe_unquote(value: str, *, component_label: str) -> str:
62+
try:
63+
return unquote(value)
64+
except HyperbrowserError:
65+
raise
66+
except Exception as exc:
67+
raise HyperbrowserError(
68+
f"Failed to decode {component_label}",
69+
original_error=exc,
70+
) from exc
71+
5172
@staticmethod
5273
def normalize_base_url(base_url: str) -> str:
5374
if not isinstance(base_url, str):
@@ -136,7 +157,10 @@ def normalize_base_url(base_url: str) -> str:
136157
raise HyperbrowserError(
137158
"base_url host must not contain encoded delimiter characters"
138159
)
139-
next_decoded_base_netloc = unquote(decoded_base_netloc)
160+
next_decoded_base_netloc = ClientConfig._safe_unquote(
161+
decoded_base_netloc,
162+
component_label="base_url host",
163+
)
140164
if next_decoded_base_netloc == decoded_base_netloc:
141165
break
142166
decoded_base_netloc = next_decoded_base_netloc
@@ -145,7 +169,13 @@ def normalize_base_url(base_url: str) -> str:
145169
raise HyperbrowserError(
146170
"base_url host must not contain encoded delimiter characters"
147171
)
148-
if unquote(decoded_base_netloc) != decoded_base_netloc:
172+
if (
173+
ClientConfig._safe_unquote(
174+
decoded_base_netloc,
175+
component_label="base_url host",
176+
)
177+
!= decoded_base_netloc
178+
):
149179
raise HyperbrowserError(
150180
"base_url host contains excessively nested URL encoding"
151181
)

tests/test_config.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,50 @@ def _raise_hyperbrowser_error(_value: str):
459459
assert exc_info.value.original_error is None
460460

461461

462+
def test_client_config_normalize_base_url_wraps_path_decode_runtime_errors(
463+
monkeypatch: pytest.MonkeyPatch,
464+
):
465+
def _raise_runtime_error(_value: str) -> str:
466+
raise RuntimeError("path decode exploded")
467+
468+
monkeypatch.setattr(config_module, "unquote", _raise_runtime_error)
469+
470+
with pytest.raises(HyperbrowserError, match="Failed to decode base_url path") as exc_info:
471+
ClientConfig.normalize_base_url("https://example.local/api")
472+
473+
assert exc_info.value.original_error is not None
474+
475+
476+
def test_client_config_normalize_base_url_wraps_host_decode_runtime_errors(
477+
monkeypatch: pytest.MonkeyPatch,
478+
):
479+
def _conditional_unquote(value: str) -> str:
480+
if value == "example.local":
481+
raise RuntimeError("host decode exploded")
482+
return value
483+
484+
monkeypatch.setattr(config_module, "unquote", _conditional_unquote)
485+
486+
with pytest.raises(HyperbrowserError, match="Failed to decode base_url host") as exc_info:
487+
ClientConfig.normalize_base_url("https://example.local/api")
488+
489+
assert exc_info.value.original_error is not None
490+
491+
492+
def test_client_config_normalize_base_url_preserves_hyperbrowser_decode_errors(
493+
monkeypatch: pytest.MonkeyPatch,
494+
):
495+
def _raise_hyperbrowser_error(_value: str) -> str:
496+
raise HyperbrowserError("custom decode failure")
497+
498+
monkeypatch.setattr(config_module, "unquote", _raise_hyperbrowser_error)
499+
500+
with pytest.raises(HyperbrowserError, match="custom decode failure") as exc_info:
501+
ClientConfig.normalize_base_url("https://example.local/api")
502+
503+
assert exc_info.value.original_error is None
504+
505+
462506
def test_client_config_normalize_base_url_preserves_invalid_port_original_error():
463507
with pytest.raises(
464508
HyperbrowserError, match="base_url must contain a valid port number"

0 commit comments

Comments
 (0)