Skip to content

Commit b3e60a2

Browse files
Guard base URL component access failures
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 1cc5c26 commit b3e60a2

2 files changed

Lines changed: 83 additions & 10 deletions

File tree

hyperbrowser/config.py

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,12 +100,25 @@ def normalize_base_url(base_url: str) -> str:
100100
"Failed to parse base_url",
101101
original_error=exc,
102102
) from exc
103+
try:
104+
parsed_base_url_scheme = parsed_base_url.scheme
105+
parsed_base_url_netloc = parsed_base_url.netloc
106+
parsed_base_url_path = parsed_base_url.path
107+
parsed_base_url_query = parsed_base_url.query
108+
parsed_base_url_fragment = parsed_base_url.fragment
109+
except HyperbrowserError:
110+
raise
111+
except Exception as exc:
112+
raise HyperbrowserError(
113+
"Failed to parse base_url components",
114+
original_error=exc,
115+
) from exc
103116
if (
104-
not isinstance(parsed_base_url.scheme, str)
105-
or not isinstance(parsed_base_url.netloc, str)
106-
or not isinstance(parsed_base_url.path, str)
107-
or not isinstance(parsed_base_url.query, str)
108-
or not isinstance(parsed_base_url.fragment, str)
117+
not isinstance(parsed_base_url_scheme, str)
118+
or not isinstance(parsed_base_url_netloc, str)
119+
or not isinstance(parsed_base_url_path, str)
120+
or not isinstance(parsed_base_url_query, str)
121+
or not isinstance(parsed_base_url_fragment, str)
109122
):
110123
raise HyperbrowserError("base_url parser returned invalid URL components")
111124
try:
@@ -122,8 +135,8 @@ def normalize_base_url(base_url: str) -> str:
122135
):
123136
raise HyperbrowserError("base_url parser returned invalid URL components")
124137
if (
125-
parsed_base_url.scheme not in {"https", "http"}
126-
or not parsed_base_url.netloc
138+
parsed_base_url_scheme not in {"https", "http"}
139+
or not parsed_base_url_netloc
127140
):
128141
raise HyperbrowserError(
129142
"base_url must start with 'https://' or 'http://' and include a host"
@@ -132,7 +145,7 @@ def normalize_base_url(base_url: str) -> str:
132145
raise HyperbrowserError(
133146
"base_url must start with 'https://' or 'http://' and include a host"
134147
)
135-
if parsed_base_url.query or parsed_base_url.fragment:
148+
if parsed_base_url_query or parsed_base_url_fragment:
136149
raise HyperbrowserError(
137150
"base_url must not include query parameters or fragments"
138151
)
@@ -177,7 +190,7 @@ def normalize_base_url(base_url: str) -> str:
177190
raise HyperbrowserError("base_url parser returned invalid URL components")
178191

179192
decoded_base_path = ClientConfig._decode_url_component_with_limit(
180-
parsed_base_url.path, component_label="base_url path"
193+
parsed_base_url_path, component_label="base_url path"
181194
)
182195
if "\\" in decoded_base_path:
183196
raise HyperbrowserError("base_url must not contain backslashes")
@@ -198,7 +211,7 @@ def normalize_base_url(base_url: str) -> str:
198211
"base_url path must not contain encoded query or fragment delimiters"
199212
)
200213

201-
decoded_base_netloc = parsed_base_url.netloc
214+
decoded_base_netloc = parsed_base_url_netloc
202215
for _ in range(10):
203216
if _ENCODED_HOST_DELIMITER_PATTERN.search(decoded_base_netloc):
204217
raise HyperbrowserError(

tests/test_config.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,66 @@ 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_component_access_errors(
463+
monkeypatch: pytest.MonkeyPatch,
464+
):
465+
class _ParsedURL:
466+
@property
467+
def scheme(self):
468+
raise RuntimeError("scheme parser exploded")
469+
470+
netloc = "example.local"
471+
hostname = "example.local"
472+
query = ""
473+
fragment = ""
474+
username = None
475+
password = None
476+
path = "/api"
477+
478+
@property
479+
def port(self) -> int:
480+
return 443
481+
482+
monkeypatch.setattr(config_module, "urlparse", lambda _value: _ParsedURL())
483+
484+
with pytest.raises(
485+
HyperbrowserError, match="Failed to parse base_url components"
486+
) as exc_info:
487+
ClientConfig.normalize_base_url("https://example.local")
488+
489+
assert exc_info.value.original_error is not None
490+
491+
492+
def test_client_config_normalize_base_url_preserves_hyperbrowser_component_errors(
493+
monkeypatch: pytest.MonkeyPatch,
494+
):
495+
class _ParsedURL:
496+
@property
497+
def scheme(self):
498+
raise HyperbrowserError("custom component failure")
499+
500+
netloc = "example.local"
501+
hostname = "example.local"
502+
query = ""
503+
fragment = ""
504+
username = None
505+
password = None
506+
path = "/api"
507+
508+
@property
509+
def port(self) -> int:
510+
return 443
511+
512+
monkeypatch.setattr(config_module, "urlparse", lambda _value: _ParsedURL())
513+
514+
with pytest.raises(
515+
HyperbrowserError, match="custom component failure"
516+
) as exc_info:
517+
ClientConfig.normalize_base_url("https://example.local")
518+
519+
assert exc_info.value.original_error is None
520+
521+
462522
def test_client_config_normalize_base_url_rejects_invalid_urlparse_component_types(
463523
monkeypatch: pytest.MonkeyPatch,
464524
):

0 commit comments

Comments
 (0)