diff --git a/h11/_readers.py b/h11/_readers.py index 41a9fbb..f5a4863 100644 --- a/h11/_readers.py +++ b/h11/_readers.py @@ -17,7 +17,7 @@ # - or, for body readers, a dict of per-framing reader factories import re -from ._util import LocalProtocolError, validate +from ._util import LocalProtocolError, RemoteProtocolError, validate from ._state import * from ._events import * @@ -177,16 +177,22 @@ def maybe_read_from_SEND_RESPONSE_server(buf): class ContentLengthReader: def __init__(self, length): self._length = length + self._remaining = length def __call__(self, buf): - if self._length == 0: + if self._remaining == 0: return EndOfMessage() - data = buf.maybe_extract_at_most(self._length) + data = buf.maybe_extract_at_most(self._remaining) if data is None: return None - self._length -= len(data) + self._remaining -= len(data) return Data(data=data) + def read_eof(self): + raise RemoteProtocolError( + "peer closed connection without sending complete message body " + "(received {} bytes, expected {})" + .format(self._length - self._remaining, self._length)) HEXDIG = r"[0-9A-Fa-f]" # Actually @@ -260,6 +266,11 @@ def __call__(self, buf): chunk_end = False return Data(data=data, chunk_start=chunk_start, chunk_end=chunk_end) + def read_eof(self): + raise RemoteProtocolError( + "peer closed connection without sending complete message body " + "(incomplete chunked read)") + class Http10Reader(object): def __call__(self, buf): diff --git a/h11/tests/test_connection.py b/h11/tests/test_connection.py index 22a7e1a..3093d7d 100644 --- a/h11/tests/test_connection.py +++ b/h11/tests/test_connection.py @@ -915,3 +915,31 @@ def setup(method, http_version): ("Transfer-Encoding", "chunked")])) == b"HTTP/1.1 200 \r\n" b"transfer-encoding: chunked\r\n\r\n") + +def test_special_exceptions_for_lost_connection_in_message_body(): + c = Connection(SERVER) + c.receive_data(b"POST / HTTP/1.1\r\n" + b"Host: example.com\r\n" + b"Content-Length: 100\r\n\r\n") + assert type(c.next_event()) is Request + assert c.next_event() is NEED_DATA + c.receive_data(b"12345") + assert c.next_event() == Data(data=b"12345") + c.receive_data(b"") + with pytest.raises(RemoteProtocolError) as excinfo: + c.next_event() + assert "received 5 bytes" in str(excinfo.value) + assert "expected 100" in str(excinfo.value) + + c = Connection(SERVER) + c.receive_data(b"POST / HTTP/1.1\r\n" + b"Host: example.com\r\n" + b"Transfer-Encoding: chunked\r\n\r\n") + assert type(c.next_event()) is Request + assert c.next_event() is NEED_DATA + c.receive_data(b"8\r\n012345") + assert c.next_event().data == b"012345" + c.receive_data(b"") + with pytest.raises(RemoteProtocolError) as excinfo: + c.next_event() + assert "incomplete chunked read" in str(excinfo.value)