Skip to content

Commit

Permalink
Handle empty zstd responses (#3412)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomchristie authored Nov 22, 2024
1 parent 189fc4b commit 47f4a96
Show file tree
Hide file tree
Showing 5 changed files with 26 additions and 5 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

## [Unreleased]
## 0.28.0 (...)

The 0.28 release includes a limited set of backwards incompatible changes.

Expand Down
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@
</a>
</p>

HTTPX is a fully featured HTTP client library for Python 3. It includes **an integrated
command line client**, has support for both **HTTP/1.1 and HTTP/2**, and provides both **sync
and async APIs**.
HTTPX is a fully featured HTTP client library for Python 3. It includes **an integrated command line client**, has support for both **HTTP/1.1 and HTTP/2**, and provides both **sync and async APIs**.

---

Expand Down
2 changes: 1 addition & 1 deletion httpx/__version__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
__title__ = "httpx"
__description__ = "A next generation HTTP client, for Python 3."
__version__ = "0.27.2"
__version__ = "0.28.0"
4 changes: 4 additions & 0 deletions httpx/_decoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,9 +175,11 @@ def __init__(self) -> None:
) from None

self.decompressor = zstandard.ZstdDecompressor().decompressobj()
self.seen_data = False

def decode(self, data: bytes) -> bytes:
assert zstandard is not None
self.seen_data = True
output = io.BytesIO()
try:
output.write(self.decompressor.decompress(data))
Expand All @@ -190,6 +192,8 @@ def decode(self, data: bytes) -> bytes:
return output.getvalue()

def flush(self) -> bytes:
if not self.seen_data:
return b""
ret = self.decompressor.flush() # note: this is a no-op
if not self.decompressor.eof:
raise DecodingError("Zstandard data is incomplete") # pragma: no cover
Expand Down
19 changes: 19 additions & 0 deletions tests/test_decoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,25 @@ def test_zstd_decoding_error():
)


def test_zstd_empty():
headers = [(b"Content-Encoding", b"zstd")]
response = httpx.Response(200, headers=headers, content=b"")
assert response.content == b""


def test_zstd_truncated():
body = b"test 123"
compressed_body = zstd.compress(body)

headers = [(b"Content-Encoding", b"zstd")]
with pytest.raises(httpx.DecodingError):
httpx.Response(
200,
headers=headers,
content=compressed_body[1:3],
)


def test_zstd_multiframe():
# test inspired by urllib3 test suite
data = (
Expand Down

0 comments on commit 47f4a96

Please sign in to comment.