Skip to content

gh-115997: Make HTTPResponse.read1 and readline raise IncompleteRead #115998

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
Open
Prev Previous commit
Next Next commit
Handle a case with a not reached newline
  • Loading branch information
illia-v committed Feb 10, 2025
commit 26b43e16a0580bbeb6902cb15e329138f53756b8
3 changes: 3 additions & 0 deletions Lib/http/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -699,6 +699,9 @@ def readline(self, limit=-1):
self.length -= len(result)
if not self.length:
self._close_conn()
elif len(result) != limit and not result.endswith(b"\n"):
self._close_conn()
raise IncompleteRead(result)
return result

def _read1_chunked(self, n):
Expand Down
33 changes: 32 additions & 1 deletion Lib/test/test_httplib.py
Original file line number Diff line number Diff line change
Expand Up @@ -1675,9 +1675,40 @@ def test_read_incomplete_read(self):
def test_read1_incomplete_read(self):
self._test_incomplete_read(self.resp.read1, expected_none=True)

def test_readline_incomplete_read(self):
def test_readline_incomplete_read_with_complete_line(self):
"""
Test that IncompleteRead is raised when readline finishes
reading a response but the needed content length is not reached.
"""
resp = self.resp
content = resp.fp.read()
# For this test case, we must ensure that the last byte read
# will be a newline. There is a different handling of readline
# not reaching a newline.
content = content[:-1] + b"\n"
resp.fp = io.BytesIO(content)
self._test_incomplete_read(self.resp.readline, expected_none=True)

def test_readline_incomplete_read_with_incomplete_line(self):
"""
Test that IncompleteRead is raised when readline is expected
to read a line fully but a newline is not reached.
"""
resp = self.resp
content = resp.fp.read()
# Truncate the content to the last newline.
content = content[:content.rindex(b"\n") - 1]
resp.fp = io.BytesIO(content)
with self.assertRaises(client.IncompleteRead) as cm:
while True:
data = resp.readline()
if not data:
break
exception = cm.exception
self.assertEqual(exception.partial, content.split(b"\n")[-1])
self.assertIsNone(exception.expected)
self.assertTrue(resp.isclosed())


class ExtendedReadTestChunked(ExtendedReadTest):
"""
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
Make ``http.client.HTTPResponse.read1`` and
``http.client.HTTPResponse.readline`` raise :exc:`IncompleteRead` instead of
returning zero bytes if a connection is closed before an expected number of
bytes has been read. Patch by Illia Volochii.
``http.client.HTTPResponse.readline`` raise :exc:`http.client.IncompleteRead`
instead of returning zero bytes if a connection is closed before an expected
number of bytes has been read. Also, ``http.client.HTTPResponse.readline``
raises when an expected newline has not been reached. Patch by Illia Volochii.
Loading