Commit f54c3501 authored by Serhiy Storchaka's avatar Serhiy Storchaka

Issue #19524: Fixed resource leak in the HTTP connection when an invalid

response is received.  Patch by Martin Panter.
parent 1d52096d
...@@ -48,43 +48,48 @@ def urlopen(url, data=None, proxies=None): ...@@ -48,43 +48,48 @@ def urlopen(url, data=None, proxies=None):
return opener.open(url, data) return opener.open(url, data)
class FakeHTTPMixin(object): def fakehttp(fakedata):
def fakehttp(self, fakedata): class FakeSocket(io.BytesIO):
class FakeSocket(io.BytesIO): io_refs = 1
io_refs = 1
def sendall(self, data): def sendall(self, data):
FakeHTTPConnection.buf = data FakeHTTPConnection.buf = data
def makefile(self, *args, **kwds): def makefile(self, *args, **kwds):
self.io_refs += 1 self.io_refs += 1
return self return self
def read(self, amt=None): def read(self, amt=None):
if self.closed: if self.closed:
return b"" return b""
return io.BytesIO.read(self, amt) return io.BytesIO.read(self, amt)
def readline(self, length=None): def readline(self, length=None):
if self.closed: if self.closed:
return b"" return b""
return io.BytesIO.readline(self, length) return io.BytesIO.readline(self, length)
def close(self): def close(self):
self.io_refs -= 1 self.io_refs -= 1
if self.io_refs == 0: if self.io_refs == 0:
io.BytesIO.close(self) io.BytesIO.close(self)
class FakeHTTPConnection(http.client.HTTPConnection):
class FakeHTTPConnection(http.client.HTTPConnection): # buffer to store data for verification in urlopen tests.
buf = None
fakesock = FakeSocket(fakedata)
# buffer to store data for verification in urlopen tests. def connect(self):
buf = None self.sock = self.fakesock
def connect(self): return FakeHTTPConnection
self.sock = FakeSocket(fakedata)
class FakeHTTPMixin(object):
def fakehttp(self, fakedata):
self._connection_class = http.client.HTTPConnection self._connection_class = http.client.HTTPConnection
http.client.HTTPConnection = FakeHTTPConnection http.client.HTTPConnection = fakehttp(fakedata)
def unfakehttp(self): def unfakehttp(self):
http.client.HTTPConnection = self._connection_class http.client.HTTPConnection = self._connection_class
......
import unittest import unittest
from test import support from test import support
from test import test_urllib
import os import os
import io import io
...@@ -13,6 +14,7 @@ import urllib.request ...@@ -13,6 +14,7 @@ import urllib.request
from urllib.request import Request, OpenerDirector, _parse_proxy, _proxy_bypass_macosx_sysconf from urllib.request import Request, OpenerDirector, _parse_proxy, _proxy_bypass_macosx_sysconf
from urllib.parse import urlparse from urllib.parse import urlparse
import urllib.error import urllib.error
import http.client
# XXX # XXX
# Request # Request
...@@ -1393,6 +1395,33 @@ class HandlerTests(unittest.TestCase): ...@@ -1393,6 +1395,33 @@ class HandlerTests(unittest.TestCase):
self.assertEqual(len(http_handler.requests), 1) self.assertEqual(len(http_handler.requests), 1)
self.assertFalse(http_handler.requests[0].has_header(auth_header)) self.assertFalse(http_handler.requests[0].has_header(auth_header))
def test_http_closed(self):
"""Test the connection is cleaned up when the response is closed"""
for (transfer, data) in (
("Connection: close", b"data"),
("Transfer-Encoding: chunked", b"4\r\ndata\r\n0\r\n\r\n"),
("Content-Length: 4", b"data"),
):
header = "HTTP/1.1 200 OK\r\n{}\r\n\r\n".format(transfer)
conn = test_urllib.fakehttp(header.encode() + data)
handler = urllib.request.AbstractHTTPHandler()
req = Request("http://dummy/")
req.timeout = None
with handler.do_open(conn, req) as resp:
resp.read()
self.assertTrue(conn.fakesock.closed,
"Connection not closed with {!r}".format(transfer))
def test_invalid_closed(self):
"""Test the connection is cleaned up after an invalid response"""
conn = test_urllib.fakehttp(b"")
handler = urllib.request.AbstractHTTPHandler()
req = Request("http://dummy/")
req.timeout = None
with self.assertRaises(http.client.BadStatusLine):
handler.do_open(conn, req)
self.assertTrue(conn.fakesock.closed, "Connection not closed")
class MiscTests(unittest.TestCase): class MiscTests(unittest.TestCase):
......
...@@ -1170,18 +1170,21 @@ class AbstractHTTPHandler(BaseHandler): ...@@ -1170,18 +1170,21 @@ class AbstractHTTPHandler(BaseHandler):
h.set_tunnel(req._tunnel_host, headers=tunnel_headers) h.set_tunnel(req._tunnel_host, headers=tunnel_headers)
try: try:
h.request(req.get_method(), req.selector, req.data, headers) try:
except OSError as err: # timeout error h.request(req.get_method(), req.selector, req.data, headers)
h.close() except OSError as err: # timeout error
raise URLError(err) raise URLError(err)
else:
r = h.getresponse() r = h.getresponse()
# If the server does not send us a 'Connection: close' header, except:
# HTTPConnection assumes the socket should be left open. Manually h.close()
# mark the socket to be closed when this response object goes away. raise
if h.sock:
h.sock.close() # If the server does not send us a 'Connection: close' header,
h.sock = None # HTTPConnection assumes the socket should be left open. Manually
# mark the socket to be closed when this response object goes away.
if h.sock:
h.sock.close()
h.sock = None
r.url = req.get_full_url() r.url = req.get_full_url()
# This line replaces the .msg attribute of the HTTPResponse # This line replaces the .msg attribute of the HTTPResponse
......
...@@ -1003,6 +1003,7 @@ Mike Pall ...@@ -1003,6 +1003,7 @@ Mike Pall
Todd R. Palmer Todd R. Palmer
Juan David Ibáñez Palomar Juan David Ibáñez Palomar
Jan Palus Jan Palus
Martin Panter
Mathias Panzenböck Mathias Panzenböck
M. Papillon M. Papillon
Peter Parente Peter Parente
......
...@@ -32,6 +32,9 @@ Core and Builtins ...@@ -32,6 +32,9 @@ Core and Builtins
Library Library
------- -------
- Issue #19524: Fixed resource leak in the HTTP connection when an invalid
response is received. Patch by Martin Panter.
- Issue #22051: turtledemo no longer reloads examples to re-run them. - Issue #22051: turtledemo no longer reloads examples to re-run them.
Initialization of variables and gui setup should be done in main(), Initialization of variables and gui setup should be done in main(),
which is called each time a demo is run, but not on import. which is called each time a demo is run, but not on import.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment