diff --git a/httpie/downloads.py b/httpie/downloads.py index 9c4b895e6f..57e2b722fa 100644 --- a/httpie/downloads.py +++ b/httpie/downloads.py @@ -218,8 +218,17 @@ def start( # FIXME: some servers still might sent Content-Encoding: gzip # + # Skip Content-Length validation when Content-Encoding is present. + # Per RFC 9110, Content-Length reflects the encoded (compressed) size, + # but the requests library transparently decompresses, making written + # bytes exceed Content-Length. Skip the check to match curl/browser behaviour. + # See: https://github.com/httpie/cli/issues/1642 try: - total_size = int(final_response.headers['Content-Length']) + content_encoding = final_response.headers.get('Content-Encoding') + if content_encoding: + total_size = None # cannot reliably compare compressed vs decompressed bytes + else: + total_size = int(final_response.headers['Content-Length']) except (KeyError, ValueError, TypeError): total_size = None diff --git a/tests/test_downloads.py b/tests/test_downloads.py index b646a0e6a5..61e090f9ec 100644 --- a/tests/test_downloads.py +++ b/tests/test_downloads.py @@ -12,6 +12,7 @@ parse_content_range, filename_from_content_disposition, filename_from_url, get_unique_filename, ContentRangeError, Downloader, PARTIAL_CONTENT ) +from httpie.status import ExitStatus from .utils import http, MockEnvironment @@ -259,3 +260,17 @@ def test_download_with_redirect_original_url_used_for_filename(self, httpbin): assert os.listdir('.') == [expected_filename] finally: os.chdir(orig_cwd) + + def test_download_gzip_no_false_incomplete(self, httpbin): + # Regression test for https://github.com/httpie/cli/issues/1642 + # Skip Content-Length validation when Content-Encoding is present. + # Per RFC 9110, Content-Length reflects the encoded (compressed) size, + # but the requests library transparently decompresses, making written + # bytes exceed Content-Length. Skip the check to match curl/browser behaviour. + r = http( + '--download', + httpbin + '/gzip', # returns gzip-compressed JSON + env=MockEnvironment(), + ) + assert 'Incomplete download' not in r.stderr + assert r.exit_status == ExitStatus.SUCCESS