Skip to content

Commit

Permalink
Send a clearer error message if response is truncated before a chunk
Browse files Browse the repository at this point in the history
If a chunked response is truncated exactly before sending a new chunk
(that is, before the first byte of the chunk size), urllib3 throws
`InvalidChunkLength(got length b'\\n', 0 bytes read)`. This is
confusing as no chunk length was sent (a valid chunk length line would
also be \r\n-terminated and we didn't see those either).

If the chunk length is the empty string, send a different error message.
  • Loading branch information
rnewson committed Dec 25, 2022
1 parent 02f5690 commit ae3ea9b
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 2 deletions.
9 changes: 7 additions & 2 deletions src/urllib3/response.py
Expand Up @@ -1001,9 +1001,14 @@ def _update_chunk_length(self) -> None:
try:
self.chunk_left = int(line, 16)
except ValueError:
# Invalid chunked protocol response, abort.
self.close()
raise InvalidChunkLength(self, line) from None
if line:
# Invalid chunked protocol response, abort.
raise InvalidChunkLength(self, line) from None
else:
# Truncated at start of next chunk
raise ProtocolError("Response ended prematurely") from None


def _handle_chunk(self, amt: int | None) -> bytes:
returned_chunk = None
Expand Down
20 changes: 20 additions & 0 deletions test/test_response.py
Expand Up @@ -1042,6 +1042,21 @@ def test_invalid_chunk_length(self) -> None:
assert isinstance(orig_ex, InvalidChunkLength)
assert orig_ex.length == fp.BAD_LENGTH_LINE.encode()

def test_truncated_before_chunk(self) -> None:
stream = [b"foooo", b"bbbbaaaaar"]
fp = MockChunkedNoChunks(stream)
r = httplib.HTTPResponse(MockSock) # type: ignore[arg-type]
r.fp = fp # type: ignore[assignment]
r.chunked = True
r.chunk_left = None
resp = HTTPResponse(
r, preload_content=False, headers={"transfer-encoding": "chunked"}
)
with pytest.raises(ProtocolError) as ctx:
next(resp.read_chunked())

assert str(ctx.value) == "Response ended prematurely"

def test_chunked_response_without_crlf_on_end(self) -> None:
stream = [b"foo", b"bar", b"baz"]
fp = MockChunkedEncodingWithoutCRLFOnEnd(stream)
Expand Down Expand Up @@ -1300,6 +1315,11 @@ def _encode_chunk(self, chunk: bytes) -> bytes:
return f"{len(chunk):X};asd=qwe\r\n{chunk.decode()}\r\n".encode()


class MockChunkedNoChunks(MockChunkedEncodingResponse):
def _encode_chunk(self, chunk: bytes) -> bytes:
return b""


class MockSock:
@classmethod
def makefile(cls, *args: typing.Any, **kwargs: typing.Any) -> None:
Expand Down

0 comments on commit ae3ea9b

Please sign in to comment.