Skip to content

Commit 1dea4ef

Browse files
Limit bytes-like status normalization by payload length
Co-authored-by: Shri Sukhani <shrisukhani@users.noreply.github.com>
1 parent 7f6fc8e commit 1dea4ef

2 files changed

Lines changed: 109 additions & 1 deletion

File tree

hyperbrowser/client/polling.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,9 +176,15 @@ def _is_executor_shutdown_runtime_error(exc: Exception) -> bool:
176176

177177
def _decode_ascii_bytes_like(value: object) -> Optional[str]:
178178
try:
179-
return memoryview(value).tobytes().decode("ascii")
179+
status_buffer = memoryview(value)
180180
except (TypeError, ValueError, UnicodeDecodeError):
181181
return None
182+
if status_buffer.nbytes > _MAX_STATUS_CODE_TEXT_LENGTH:
183+
return None
184+
try:
185+
return status_buffer.tobytes().decode("ascii")
186+
except UnicodeDecodeError:
187+
return None
182188

183189

184190
def _normalize_status_code_for_retry(status_code: object) -> Optional[int]:

tests/test_polling.py

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,31 @@ def get_status() -> str:
217217
assert attempts["count"] == 3
218218

219219

220+
def test_poll_until_terminal_status_retries_overlong_numeric_bytes_status_codes():
221+
attempts = {"count": 0}
222+
223+
def get_status() -> str:
224+
attempts["count"] += 1
225+
if attempts["count"] < 3:
226+
raise HyperbrowserError(
227+
"oversized status metadata",
228+
status_code=b"4000000000000", # type: ignore[arg-type]
229+
)
230+
return "completed"
231+
232+
status = poll_until_terminal_status(
233+
operation_name="sync poll oversized numeric-bytes status retries",
234+
get_status=get_status,
235+
is_terminal_status=lambda value: value == "completed",
236+
poll_interval_seconds=0.0001,
237+
max_wait_seconds=1.0,
238+
max_status_failures=5,
239+
)
240+
241+
assert status == "completed"
242+
assert attempts["count"] == 3
243+
244+
220245
def test_poll_until_terminal_status_does_not_retry_stop_iteration_errors():
221246
attempts = {"count": 0}
222247

@@ -802,6 +827,29 @@ def operation() -> str:
802827
assert attempts["count"] == 3
803828

804829

830+
def test_retry_operation_retries_overlong_numeric_bytes_status_codes():
831+
attempts = {"count": 0}
832+
833+
def operation() -> str:
834+
attempts["count"] += 1
835+
if attempts["count"] < 3:
836+
raise HyperbrowserError(
837+
"oversized status metadata",
838+
status_code=b"4000000000000", # type: ignore[arg-type]
839+
)
840+
return "ok"
841+
842+
result = retry_operation(
843+
operation_name="sync retry oversized numeric-bytes status code",
844+
operation=operation,
845+
max_attempts=5,
846+
retry_delay_seconds=0.0001,
847+
)
848+
849+
assert result == "ok"
850+
assert attempts["count"] == 3
851+
852+
805853
def test_retry_operation_retries_bytes_like_rate_limit_errors():
806854
attempts = {"count": 0}
807855

@@ -1362,6 +1410,34 @@ async def get_status() -> str:
13621410
asyncio.run(run())
13631411

13641412

1413+
def test_poll_until_terminal_status_async_retries_overlong_numeric_bytes_status_codes():
1414+
async def run() -> None:
1415+
attempts = {"count": 0}
1416+
1417+
async def get_status() -> str:
1418+
attempts["count"] += 1
1419+
if attempts["count"] < 3:
1420+
raise HyperbrowserError(
1421+
"oversized status metadata",
1422+
status_code=b"4000000000000", # type: ignore[arg-type]
1423+
)
1424+
return "completed"
1425+
1426+
status = await poll_until_terminal_status_async(
1427+
operation_name="async poll oversized numeric-bytes status retries",
1428+
get_status=get_status,
1429+
is_terminal_status=lambda value: value == "completed",
1430+
poll_interval_seconds=0.0001,
1431+
max_wait_seconds=1.0,
1432+
max_status_failures=5,
1433+
)
1434+
1435+
assert status == "completed"
1436+
assert attempts["count"] == 3
1437+
1438+
asyncio.run(run())
1439+
1440+
13651441
def test_poll_until_terminal_status_async_does_not_retry_stop_async_iteration_errors():
13661442
async def run() -> None:
13671443
attempts = {"count": 0}
@@ -1641,6 +1717,32 @@ async def operation() -> str:
16411717
asyncio.run(run())
16421718

16431719

1720+
def test_retry_operation_async_retries_overlong_numeric_bytes_status_codes():
1721+
async def run() -> None:
1722+
attempts = {"count": 0}
1723+
1724+
async def operation() -> str:
1725+
attempts["count"] += 1
1726+
if attempts["count"] < 3:
1727+
raise HyperbrowserError(
1728+
"oversized status metadata",
1729+
status_code=b"4000000000000", # type: ignore[arg-type]
1730+
)
1731+
return "ok"
1732+
1733+
result = await retry_operation_async(
1734+
operation_name="async retry oversized numeric-bytes status code",
1735+
operation=operation,
1736+
max_attempts=5,
1737+
retry_delay_seconds=0.0001,
1738+
)
1739+
1740+
assert result == "ok"
1741+
assert attempts["count"] == 3
1742+
1743+
asyncio.run(run())
1744+
1745+
16441746
def test_retry_operation_async_retries_bytes_like_rate_limit_errors():
16451747
async def run() -> None:
16461748
attempts = {"count": 0}

0 commit comments

Comments
 (0)