Add InvalidChunkLength error
MODIFY
src/urllib3/exceptions.py
src/urllib3/exceptions.py
@@ -231,6 +231,28 @@ class IncompleteRead(HTTPError, httplib_IncompleteRead):
)
+class InvalidChunkLength(HTTPError, httplib_IncompleteRead):
+ """Invalid chunk length in a chunked response."""
+
+ def __init__(self, response, length):
+ super(InvalidChunkLength, self).__init__(
+ response.tell(), response.length_remaining
+ )
+ self.response = response
+ self.length = length
+
+ def __repr__(self):
+ if self.expected is not None:
+ e = ", %i more expected" % self.expected
+ else:
+ e = ""
+ return "InvalidChunkLength(got length %r, %i bytes read%s)" % (
+ self.length,
+ self.partial,
+ e,
+ )
+
+
class InvalidHeader(HTTPError):
"The header provided was somehow invalid."
pass

MODIFY
src/urllib3/response.py
src/urllib3/response.py
@@ -19,11 +19,12 @@ from .exceptions import (
ReadTimeoutError,
ResponseNotChunked,
IncompleteRead,
+ InvalidChunkLength,
InvalidHeader,
HTTPError,
)
from .packages.six import string_types as basestring, PY3
-from .packages.six.moves import http_client as httplib
+from .packages.six.moves import http_client as httplib # noqa: F401
from .connection import HTTPException, BaseSSLError
from .util.response import is_fp_closed, is_response_to_head
@@ -697,7 +698,7 @@ class HTTPResponse(io.IOBase):
except ValueError:
# Invalid chunked protocol response, abort.
self.close()
- raise httplib.IncompleteRead(line)
+ raise InvalidChunkLength(self, line)
def _handle_chunk(self, amt):
returned_chunk = None

MODIFY
test/test_response.py
test/test_response.py
@@ -16,6 +16,8 @@ from urllib3.exceptions import (
ResponseNotChunked,
ProtocolError,
InvalidHeader,
+ httplib_IncompleteRead,
+ InvalidChunkLength,
)
from urllib3.packages.six.moves import http_client as httplib
from urllib3.util.retry import Retry, RequestHistory
@@ -758,9 +760,9 @@ class TestResponse(object):
with pytest.raises(ResponseNotChunked):
next(r)
- def test_invalid_chunks(self):
+ def test_incomplete_chunk(self):
stream = [b"foooo", b"bbbbaaaaar"]
- fp = MockChunkedInvalidEncoding(stream)
+ fp = MockChunkedIncompleteRead(stream)
r = httplib.HTTPResponse(MockSock)
r.fp = fp
r.chunked = True
@@ -768,9 +770,29 @@ class TestResponse(object):
resp = HTTPResponse(
r, preload_content=False, headers={"transfer-encoding": "chunked"}
)
- with pytest.raises(ProtocolError):
+ with pytest.raises(ProtocolError) as ctx:
next(resp.read_chunked())
+ orig_ex = ctx.value.args[1]
+ assert isinstance(orig_ex, httplib_IncompleteRead)
+
+ def test_invalid_chunk_length(self):
+ stream = [b"foooo", b"bbbbaaaaar"]
+ fp = MockChunkedInvalidChunkLength(stream)
+ r = httplib.HTTPResponse(MockSock)
+ r.fp = fp
+ 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())
+
+ orig_ex = ctx.value.args[1]
+ assert isinstance(orig_ex, InvalidChunkLength)
+ assert orig_ex.length == six.b(fp.BAD_LENGTH_LINE)
+
def test_chunked_response_without_crlf_on_end(self):
stream = [b"foo", b"bar", b"baz"]
fp = MockChunkedEncodingWithoutCRLFOnEnd(stream)
@@ -971,9 +993,16 @@ class MockChunkedEncodingResponse(object):
self.closed = True
-class MockChunkedInvalidEncoding(MockChunkedEncodingResponse):
+class MockChunkedIncompleteRead(MockChunkedEncodingResponse):
+ def _encode_chunk(self, chunk):
+ return "9999\r\n%s\r\n" % chunk.decode()
+
+
+class MockChunkedInvalidChunkLength(MockChunkedEncodingResponse):
+ BAD_LENGTH_LINE = "ZZZ\r\n"
+
def _encode_chunk(self, chunk):
- return "ZZZ\r\n%s\r\n" % chunk.decode()
+ return "%s%s\r\n" % (self.BAD_LENGTH_LINE, chunk.decode())
class MockChunkedEncodingWithoutCRLFOnEnd(MockChunkedEncodingResponse):

Add InvalidChunkLength error