From 75e271cab4a5797b1ade0e232693bec12c95ea9d Mon Sep 17 00:00:00 2001
From: "Nathaniel J. Smith" <njs@pobox.com>
Date: Sat, 3 Dec 2016 02:39:25 -0800
Subject: [PATCH] More detailed errors when connection closed during message
 body

Intended as at least a first step towards gh-23.
---
 h11/_readers.py              | 19 +++++++++++++++----
 h11/tests/test_connection.py | 28 ++++++++++++++++++++++++++++
 2 files changed, 43 insertions(+), 4 deletions(-)

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)