]> granicus.if.org Git - python/commitdiff
Issue #15633: httplib.HTTPResponse is now mark closed when the server sends less...
authorAntoine Pitrou <solipsis@pitrou.net>
Sat, 2 Feb 2013 22:04:56 +0000 (23:04 +0100)
committerAntoine Pitrou <solipsis@pitrou.net>
Sat, 2 Feb 2013 22:04:56 +0000 (23:04 +0100)
1  2 
Lib/http/client.py
Lib/test/test_httplib.py
Misc/NEWS

index 6a4496fb49e35a91b3389268a2a78e524f0f456a,36528dacba7abad17b35315b71d5d3b8ff3f988f..62d9cff889f3b3c95e17e549318268747918fecb
@@@ -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 = []
index 5ebcfcb939d6b8ebb8c647a980fc04b7013dd2cd,5df4b512041cf3723ca37e36c00e3192d2eade3e..a58a59247645efd383d0ad09c6bc0b4125294301
@@@ -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
  
diff --cc Misc/NEWS
Simple merge