Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions httpie/downloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,12 +216,23 @@ def start(
"""
assert not self.status.time_started

# FIXME: some servers still might sent Content-Encoding: gzip
# When Content-Encoding is present (e.g., gzip), requests automatically
# decompresses the response. In this case, Content-Length represents
# the compressed size, but we receive uncompressed bytes. This causes
# a mismatch where downloaded bytes > Content-Length, triggering false
# "Incomplete download" errors. Skip using Content-Length in this case.
# <https://github.com/httpie/cli/issues/1642>
# <https://github.com/httpie/cli/issues/423>
try:
total_size = int(final_response.headers['Content-Length'])
except (KeyError, ValueError, TypeError):
content_encoding = final_response.headers.get('Content-Encoding')
if content_encoding:
# Can't use Content-Length for progress tracking when response
# is encoded, as the decoded size will differ.
total_size = None
else:
try:
total_size = int(final_response.headers['Content-Length'])
except (KeyError, ValueError, TypeError):
total_size = None

if not self._output_file:
self._output_file = self._get_output_file_from_response(
Expand Down
30 changes: 30 additions & 0 deletions tests/test_downloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,36 @@ def test_download_no_Content_Length(self, mock_env, httpbin_both):
downloader.finish()
assert not downloader.interrupted

def test_download_with_Content_Encoding_gzip(self, mock_env, httpbin_both):
"""
Test that when Content-Encoding is present, downloads are not flagged
as interrupted even if the decompressed bytes exceed Content-Length.

This verifies the fix for https://github.com/httpie/cli/issues/1642
where gzipped responses incorrectly triggered "Incomplete download" errors
because Content-Length is the compressed size but requests auto-decompresses.
"""
with open(os.devnull, 'w') as devnull:
downloader = Downloader(mock_env, output_file=devnull)
downloader.start(
initial_url='/',
final_response=Response(
url=httpbin_both.url + '/',
headers={
# Compressed size in Content-Length
'Content-Length': 100,
'Content-Encoding': 'gzip',
}
)
)
# Simulate receiving decompressed data (more than Content-Length)
downloader.chunk_downloaded(b'x' * 500)
downloader.chunk_downloaded(b'y' * 500)
downloader.finish()
# Should NOT be interrupted since Content-Encoding was present
# and Content-Length was correctly ignored
assert not downloader.interrupted

def test_download_output_from_content_disposition(self, mock_env, httpbin_both):
with tempfile.TemporaryDirectory() as tmp_dirname:
orig_cwd = os.getcwd()
Expand Down
Loading