From: Antoine Pitrou Date: Sat, 2 Feb 2013 22:04:56 +0000 (+0100) Subject: Issue #15633: httplib.HTTPResponse is now mark closed when the server sends less... X-Git-Tag: v3.3.1rc1~243 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=6a35e18161c4d06015aeb79c951df04bcea9ca9a;p=python Issue #15633: httplib.HTTPResponse is now mark closed when the server sends less than the advertised Content-Length. --- 6a35e18161c4d06015aeb79c951df04bcea9ca9a diff --cc Lib/http/client.py index 6a4496fb49,36528dacba..62d9cff889 --- a/Lib/http/client.py +++ b/Lib/http/client.py @@@ -531,47 -510,22 +535,48 @@@ class HTTPResponse(io.RawIOBase) # we do not use _safe_read() here because this may be a .will_close # connection, and the user is reading more bytes than will be provided # (for example, reading in 1k chunks) - s = self.fp.read(amt) - if not s: + n = self.fp.readinto(b) - if self.length is not None: ++ if not n: + # Ideally, we would raise IncompleteRead if the content-length + # wasn't satisfied, but it might break compatibility. + self.close() - if self.length is not None: - self.length -= len(s) ++ elif self.length is not None: + self.length -= n if not self.length: self.close() -- else: - if not n: - if not s: -- self.close() + return n - return s + def _read_next_chunk_size(self): + # Read the next chunk size from the file + line = self.fp.readline(_MAXLINE + 1) + if len(line) > _MAXLINE: + raise LineTooLong("chunk size") + i = line.find(b";") + if i >= 0: + line = line[:i] # strip chunk-extensions + try: + return int(line, 16) + except ValueError: + # close the connection as protocol synchronisation is + # probably lost + self.close() + raise - def _read_chunked(self, amt): + def _read_and_discard_trailer(self): + # read and discard trailer up to the CRLF terminator + ### note: we shouldn't have any trailers! + while True: + line = self.fp.readline(_MAXLINE + 1) + if len(line) > _MAXLINE: + raise LineTooLong("trailer line") + if not line: + # a vanishingly small number of sites EOF without + # sending the trailer + break + if line in (b'\r\n', b'\n', b''): + break + + def _readall_chunked(self): assert self.chunked != _UNKNOWN chunk_left = self.chunk_left value = [] diff --cc Lib/test/test_httplib.py index 5ebcfcb939,5df4b51204..a58a592476 --- a/Lib/test/test_httplib.py +++ b/Lib/test/test_httplib.py @@@ -216,25 -199,19 +216,57 @@@ class BasicTest(TestCase) self.assertEqual(resp.read(1), b'') self.assertTrue(resp.isclosed()) + def test_partial_readintos_no_content_length(self): + # when no length is present, the socket should be gracefully closed when + # all data was read + body = "HTTP/1.1 200 Ok\r\n\r\nText" + sock = FakeSocket(body) + resp = client.HTTPResponse(sock) + resp.begin() + b = bytearray(2) + n = resp.readinto(b) + self.assertEqual(n, 2) + self.assertEqual(bytes(b), b'Te') + self.assertFalse(resp.isclosed()) + n = resp.readinto(b) + self.assertEqual(n, 2) + self.assertEqual(bytes(b), b'xt') + n = resp.readinto(b) + self.assertEqual(n, 0) + self.assertTrue(resp.isclosed()) + + def test_partial_reads_incomplete_body(self): + # if the server shuts down the connection before the whole + # content-length is delivered, the socket is gracefully closed + body = "HTTP/1.1 200 Ok\r\nContent-Length: 10\r\n\r\nText" + sock = FakeSocket(body) + resp = client.HTTPResponse(sock) + resp.begin() + self.assertEqual(resp.read(2), b'Te') + self.assertFalse(resp.isclosed()) + self.assertEqual(resp.read(2), b'xt') + self.assertEqual(resp.read(1), b'') + self.assertTrue(resp.isclosed()) + ++ def test_partial_readintos_incomplete_body(self): ++ # if the server shuts down the connection before the whole ++ # content-length is delivered, the socket is gracefully closed ++ body = "HTTP/1.1 200 Ok\r\nContent-Length: 10\r\n\r\nText" ++ sock = FakeSocket(body) ++ resp = client.HTTPResponse(sock) ++ resp.begin() ++ b = bytearray(2) ++ n = resp.readinto(b) ++ self.assertEqual(n, 2) ++ self.assertEqual(bytes(b), b'Te') ++ self.assertFalse(resp.isclosed()) ++ n = resp.readinto(b) ++ self.assertEqual(n, 2) ++ self.assertEqual(bytes(b), b'xt') ++ n = resp.readinto(b) ++ self.assertEqual(n, 0) ++ self.assertTrue(resp.isclosed()) ++ def test_host_port(self): # Check invalid host_port