Skip to content

Commit e934581

Browse files
Wrap header character validation boundary failures
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent f89ea1f commit e934581

File tree

2 files changed

+78
-10
lines changed

2 files changed

+78
-10
lines changed

hyperbrowser/header_utils.py

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,35 @@ def normalize_headers(
7272
raise HyperbrowserError(
7373
"header names must contain only valid HTTP token characters"
7474
)
75-
if (
76-
"\n" in normalized_key
77-
or "\r" in normalized_key
78-
or "\n" in value
79-
or "\r" in value
80-
):
75+
try:
76+
contains_newline = (
77+
"\n" in normalized_key
78+
or "\r" in normalized_key
79+
or "\n" in value
80+
or "\r" in value
81+
)
82+
except HyperbrowserError:
83+
raise
84+
except Exception as exc:
85+
raise HyperbrowserError(
86+
"Failed to validate header characters",
87+
original_error=exc,
88+
) from exc
89+
if contains_newline:
8190
raise HyperbrowserError("headers must not contain newline characters")
82-
if any(
83-
ord(character) < 32 or ord(character) == 127
84-
for character in f"{normalized_key}{value}"
85-
):
91+
try:
92+
contains_control_character = any(
93+
ord(character) < 32 or ord(character) == 127
94+
for character in f"{normalized_key}{value}"
95+
)
96+
except HyperbrowserError:
97+
raise
98+
except Exception as exc:
99+
raise HyperbrowserError(
100+
"Failed to validate header characters",
101+
original_error=exc,
102+
) from exc
103+
if contains_control_character:
86104
raise HyperbrowserError("headers must not contain control characters")
87105
try:
88106
canonical_header_name = normalized_key.lower()

tests/test_header_utils.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,17 @@ def strip(self, chars=None): # type: ignore[override]
7272
return object()
7373

7474

75+
class _BrokenHeaderValueContains(str):
76+
def __contains__(self, item): # type: ignore[override]
77+
_ = item
78+
raise RuntimeError("header value contains exploded")
79+
80+
81+
class _BrokenHeaderValueStringify(str):
82+
def __str__(self) -> str:
83+
raise RuntimeError("header value stringify exploded")
84+
85+
7586
def test_normalize_headers_trims_header_names():
7687
headers = normalize_headers(
7788
{" X-Correlation-Id ": "abc123"},
@@ -328,6 +339,45 @@ def test_normalize_headers_rejects_control_characters():
328339
)
329340

330341

342+
def test_normalize_headers_wraps_header_character_validation_contains_failures():
343+
with pytest.raises(
344+
HyperbrowserError, match="Failed to validate header characters"
345+
) as exc_info:
346+
normalize_headers(
347+
{"X-Trace-Id": _BrokenHeaderValueContains("value")},
348+
mapping_error_message="headers must be a mapping of string pairs",
349+
)
350+
351+
assert isinstance(exc_info.value.original_error, RuntimeError)
352+
353+
354+
def test_normalize_headers_preserves_header_character_validation_contains_hyperbrowser_failures():
355+
class _BrokenHeaderValueContains(str):
356+
def __contains__(self, item): # type: ignore[override]
357+
_ = item
358+
raise HyperbrowserError("custom contains failure")
359+
360+
with pytest.raises(HyperbrowserError, match="custom contains failure") as exc_info:
361+
normalize_headers(
362+
{"X-Trace-Id": _BrokenHeaderValueContains("value")},
363+
mapping_error_message="headers must be a mapping of string pairs",
364+
)
365+
366+
assert exc_info.value.original_error is None
367+
368+
369+
def test_normalize_headers_wraps_header_character_validation_stringify_failures():
370+
with pytest.raises(
371+
HyperbrowserError, match="Failed to validate header characters"
372+
) as exc_info:
373+
normalize_headers(
374+
{"X-Trace-Id": _BrokenHeaderValueStringify("value")},
375+
mapping_error_message="headers must be a mapping of string pairs",
376+
)
377+
378+
assert isinstance(exc_info.value.original_error, RuntimeError)
379+
380+
331381
def test_parse_headers_env_json_rejects_control_characters():
332382
with pytest.raises(
333383
HyperbrowserError, match="headers must not contain control characters"

0 commit comments

Comments
 (0)