Skip to content

Commit 3ff17c3

Browse files
committed
Replace response.iter_content by response.raw in RequestsFetcher._chunks.
This prevents requests/urllib3 from automatically decoding files with Content-Encoding gzip or deflate.
1 parent b5e952c commit 3ff17c3

File tree

2 files changed

+30
-8
lines changed

2 files changed

+30
-8
lines changed

tests/test_fetcher_ng.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from unittest.mock import Mock, patch
1818

1919
import requests
20+
from requests.models import ReadTimeoutError # actually from urllib3
2021

2122
from tests import utils
2223
from tuf.api import exceptions
@@ -109,18 +110,20 @@ def test_http_error(self) -> None:
109110
# Response read timeout error
110111
@patch.object(requests.Session, "get")
111112
def test_response_read_timeout(self, mock_session_get: Any) -> None:
112-
mock_response = Mock()
113+
# from urllib3.connectionpool import ConnectionPool
114+
# dummy_pool = ConnectionPool("dummy")
115+
mock_response = Mock(raw=Mock())
113116
attr = {
114-
"iter_content.side_effect": requests.exceptions.ConnectionError(
115-
"Simulated timeout"
117+
"read.side_effect": ReadTimeoutError(
118+
None, None, "Simulated timeout"
116119
)
117120
}
118-
mock_response.configure_mock(**attr)
121+
mock_response.raw.configure_mock(**attr)
119122
mock_session_get.return_value = mock_response
120123

121124
with self.assertRaises(exceptions.SlowRetrievalError):
122125
next(self.fetcher.fetch(self.url))
123-
mock_response.iter_content.assert_called_once()
126+
mock_response.raw.read.assert_called_once()
124127

125128
# Read/connect session timeout error
126129
@patch.object(
@@ -171,6 +174,14 @@ def test_download_file_length_mismatch(self) -> Iterator[Any]:
171174
# context manager and returns Iterator[IO]
172175
yield self.fetcher.download_file(self.url, self.file_length - 4)
173176

177+
# Download a file with Content-Encoding gzip (or deflate)
178+
def test_download_file_content_encoding(self):
179+
# todo:
180+
# - enable custom headers in simple_server.py, so we can return a
181+
# file with Content-Encoding header
182+
# - serve a gzipped file (in TestFetcher.setUpClass?)
183+
pass
184+
174185

175186
# Run unit test.
176187
if __name__ == "__main__":

tuf/ngclient/_internal/requests_fetcher.py

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

1212
# Imports
1313
import requests
14+
from requests.models import ReadTimeoutError # todo: import directly from urllib3?
1415

1516
import tuf
1617
from tuf.api import exceptions
@@ -19,6 +20,7 @@
1920
# Globals
2021
logger = logging.getLogger(__name__)
2122

23+
2224
# Classes
2325
class RequestsFetcher(FetcherInterface):
2426
"""An implementation of ``FetcherInterface`` based on the requests library.
@@ -96,11 +98,20 @@ def _chunks(self, response: "requests.Response") -> Iterator[bytes]:
9698
download."""
9799

98100
try:
99-
for data in response.iter_content(self.chunk_size):
101+
while True:
102+
# Requests already calls `urlopen(..., decode_content=False)`,
103+
# but we explicitly disable decode_content here, to be safe.
104+
data = response.raw.read(
105+
amt=self.chunk_size, decode_content=False
106+
)
107+
if not data:
108+
break
100109
yield data
101110
except (
102-
requests.exceptions.ConnectionError,
103-
requests.exceptions.Timeout,
111+
# Catch urllib3 exceptions instead of requests exceptions.
112+
ReadTimeoutError,
113+
# todo: Is this sufficient? Do we need to catch IncompleteRead too?
114+
# https://github.com/urllib3/urllib3/blob/43c372f6eb9f9f94848c7b7645ad19ebf6c047c5/src/urllib3/response.py#L734
104115
) as e:
105116
raise exceptions.SlowRetrievalError from e
106117

0 commit comments

Comments
 (0)