Skip to content

Commit 27b70b1

Browse files
Merge commit from fork
1 parent e00abcc commit 27b70b1

3 files changed

Lines changed: 35 additions & 5 deletions

File tree

tests/test_helpers.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import pytest
55

6+
from tests.helpers import gzip
67
from usp.exceptions import (
78
GunzipException,
89
SitemapException,
@@ -216,6 +217,19 @@ def test_gunzip():
216217
# noinspection PyTypeChecker
217218
gunzip(b"foo")
218219

220+
assert gunzip(gzip("foo"), max_output_bytes=512 * 1024) == b"foo"
221+
222+
223+
def test_gunzip_above_max_output_bytes():
224+
# Create a gzip-compressed byte string that exceeds the max_output_bytes limit
225+
original_data = b"A" * 1024 * 1024 # 1 MB of data
226+
compressed_data = gzip(original_data)
227+
228+
with pytest.raises(GunzipException) as exc_info:
229+
gunzip(compressed_data, max_output_bytes=512 * 1024) # Limit to 512 KB
230+
231+
assert "Gunzipped data exceeds maximum output size" in str(exc_info.value)
232+
219233

220234
class MockWebClientErrorResponse(WebClientErrorResponse):
221235
pass

usp/fetch_parse.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,11 @@ def sitemap(self) -> AbstractSitemap:
172172

173173
self._url = response_url
174174

175-
response_content = ungzipped_response_content(url=self._url, response=response)
175+
response_content = ungzipped_response_content(
176+
url=self._url,
177+
response=response,
178+
max_uncompressed_bytes=self.__MAX_SITEMAP_SIZE,
179+
)
176180

177181
# MIME types returned in Content-Type are unpredictable, so peek into the content instead
178182
if response_content[:20].strip().startswith("<"):

usp/helpers.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import datetime
44
import gzip as gzip_lib
55
import html
6+
import io
67
import logging
78
import re
89
import sys
@@ -215,7 +216,7 @@ def __response_is_gzipped_data(
215216
return False
216217

217218

218-
def gunzip(data: bytes) -> bytes:
219+
def gunzip(data: bytes, max_output_bytes: int | None = None) -> bytes:
219220
"""
220221
Gunzip data.
221222
@@ -236,7 +237,16 @@ def gunzip(data: bytes) -> bytes:
236237
)
237238

238239
try:
239-
gunzipped_data = gzip_lib.decompress(data)
240+
chunks, total = [], 0
241+
with gzip_lib.GzipFile(fileobj=io.BytesIO(data)) as gz:
242+
while chunk := gz.read(1024 * 1024):
243+
total += len(chunk)
244+
if max_output_bytes is not None and total > max_output_bytes:
245+
raise GunzipException(
246+
f"Gunzipped data exceeds maximum output size of {max_output_bytes} bytes."
247+
)
248+
chunks.append(chunk)
249+
gunzipped_data = b"".join(chunks)
240250
except Exception as ex:
241251
raise GunzipException(f"Unable to gunzip data: {str(ex)}")
242252

@@ -250,7 +260,9 @@ def gunzip(data: bytes) -> bytes:
250260

251261

252262
def ungzipped_response_content(
253-
url: str, response: AbstractWebClientSuccessResponse
263+
url: str,
264+
response: AbstractWebClientSuccessResponse,
265+
max_uncompressed_bytes: int | None = None,
254266
) -> str:
255267
"""
256268
Return HTTP response's decoded content, gunzip it if necessary.
@@ -264,7 +276,7 @@ def ungzipped_response_content(
264276

265277
if __response_is_gzipped_data(url=url, response=response):
266278
try:
267-
data = gunzip(data)
279+
data = gunzip(data, max_output_bytes=max_uncompressed_bytes)
268280
except GunzipException as ex:
269281
# In case of an error, just assume that it's one of the non-gzipped sitemaps with ".gz" extension
270282
log.warning(

0 commit comments

Comments
 (0)