Skip to content

Commit cd6502c

Browse files
committed
Fallback on 403 for presigned URL probe
1 parent 39e11f4 commit cd6502c

2 files changed

Lines changed: 31 additions & 2 deletions

File tree

tests/test_external_fetch.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,33 @@ def _range_ignored(url_: Any, **kwargs: Any) -> CallbackResult:
262262
assert result == data
263263
assert seen_ranges == ["bytes=0-0"]
264264

265+
def test_presigned_probe_403_falls_back_to_plain_get(self) -> None:
266+
"""If the probe is forbidden (403), the fetch falls back to plain GET."""
267+
data = b"probe forbidden"
268+
url = (
269+
"https://storage.googleapis.com/object"
270+
"?X-Goog-Credential=test%2F20260224%2Fauto%2Fstorage%2Fgoog4_request"
271+
"&X-Goog-Signature=abc123"
272+
)
273+
274+
seen_ranges: list[str] = []
275+
276+
def _probe_forbidden(url_: Any, **kwargs: Any) -> CallbackResult:
277+
headers = kwargs.get("headers", {})
278+
range_header = headers.get("Range", "")
279+
if range_header:
280+
seen_ranges.append(range_header)
281+
return CallbackResult(status=403)
282+
return CallbackResult(status=200, body=data, headers={"Content-Length": str(len(data))})
283+
284+
with FetchConfig(parallel_threshold_bytes=10_000) as config:
285+
with aioresponses() as mock:
286+
mock.get(url, callback=_probe_forbidden, repeat=True)
287+
result = fetch_url(url, config)
288+
289+
assert result == data
290+
assert seen_ranges == ["bytes=0-0"]
291+
265292

266293
class TestFetchParallelChunks:
267294
"""Tests for parallel range-request fetching."""

vgi_rpc/external_fetch.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -410,13 +410,15 @@ async def _range_probe(url: str, client: aiohttp.ClientSession) -> tuple[int | N
410410
if resp.status == 200:
411411
return None, "", content_encoding
412412

413-
if resp.status in (405, 501):
413+
# Some signed URLs are method/header constrained and may reject
414+
# the probe even though a normal GET is valid.
415+
if resp.status in (403, 405, 501):
414416
return None, "", ""
415417

416418
resp.raise_for_status()
417419
return None, "", content_encoding # pragma: no cover
418420
except _aiohttp.ClientResponseError as exc:
419-
if exc.status in (405, 501):
421+
if exc.status in (403, 405, 501):
420422
return None, "", ""
421423
raise
422424

0 commit comments

Comments
 (0)