From 301f3162c88757ab9c6a1410edec00479c00bc53 Mon Sep 17 00:00:00 2001 From: Jan Verbeek Date: Wed, 22 Jan 2025 12:44:04 +0100 Subject: [PATCH] Do not hang when deciding not to compress a file upload This command would hang forever: ```console $ echo test > test.txt $ http -x httpbin.org/post @test.txt ``` HTTPie would read the file, compress it, then discard the compressed data because it's larger than the original. This made `request.body` an exhausted file handle, which `requests` doesn't seem to like. --- httpie/uploads.py | 3 +++ tests/fixtures/__init__.py | 2 ++ tests/fixtures/test_tiny.txt | 1 + tests/test_compress.py | 15 ++++++++++++++- 4 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 tests/fixtures/test_tiny.txt diff --git a/httpie/uploads.py b/httpie/uploads.py index 4a993b3a..cbf7a6ed 100644 --- a/httpie/uploads.py +++ b/httpie/uploads.py @@ -258,6 +258,9 @@ def compress_request( body_bytes = request.body.encode() elif hasattr(request.body, 'read'): body_bytes = request.body.read() + # The body is consumed now, so if we don't end up compressing we should + # use the bytes we just read + request.body = body_bytes else: body_bytes = request.body deflated_data = deflater.compress(body_bytes) diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py index 6e6e7367..d01bead6 100644 --- a/tests/fixtures/__init__.py +++ b/tests/fixtures/__init__.py @@ -22,6 +22,7 @@ FILE_PATH = FIXTURES_ROOT / 'test.txt' JSON_FILE_PATH = FIXTURES_ROOT / 'test.json' JSON_WITH_DUPE_KEYS_FILE_PATH = FIXTURES_ROOT / 'test_with_dupe_keys.json' BIN_FILE_PATH = FIXTURES_ROOT / 'test.bin' +TINY_FILE_PATH = FIXTURES_ROOT / 'test_tiny.txt' XML_FILES_PATH = FIXTURES_ROOT / 'xmldata' XML_FILES_VALID = list((XML_FILES_PATH / 'valid').glob('*_raw.xml')) @@ -39,6 +40,7 @@ SESSION_VARIABLES = { FILE_PATH_ARG = patharg(FILE_PATH) BIN_FILE_PATH_ARG = patharg(BIN_FILE_PATH) JSON_FILE_PATH_ARG = patharg(JSON_FILE_PATH) +TINY_FILE_PATH_ARG = patharg(TINY_FILE_PATH) # Strip because we don't want new lines in the data so that we can # easily count occurrences also when embedded in JSON (where the new diff --git a/tests/fixtures/test_tiny.txt b/tests/fixtures/test_tiny.txt new file mode 100644 index 00000000..9daeafb9 --- /dev/null +++ b/tests/fixtures/test_tiny.txt @@ -0,0 +1 @@ +test diff --git a/tests/test_compress.py b/tests/test_compress.py index de5d8a7c..e7632579 100644 --- a/tests/test_compress.py +++ b/tests/test_compress.py @@ -11,7 +11,7 @@ our zlib-encoded request data. import base64 import zlib -from .fixtures import FILE_PATH, FILE_CONTENT +from .fixtures import FILE_PATH, FILE_CONTENT, TINY_FILE_PATH_ARG from httpie.status import ExitStatus from .utils import StdinBytesIO, http, HTTP_OK, MockEnvironment @@ -138,3 +138,16 @@ def test_compress_file(httpbin_both): 'multipart/form-data; boundary=') assert r.json['files'] == {} assert FILE_CONTENT not in r + + +def test_compress_skip_negative_ratio_file_upload(httpbin): + """Reading the upload body but not compressing it doesn't cause a hang.""" + r = http( + '--compress', + 'PUT', + httpbin + '/put', + f'@{TINY_FILE_PATH_ARG}', + ) + assert HTTP_OK in r + assert 'Content-Encoding' not in r.json['headers'] + assert r.json['data'].strip() == 'test'