mirror of
https://github.com/python/cpython.git
synced 2025-09-17 14:16:02 +00:00
Issue #10883: Fix socket leaks in urllib.request.
* ftpwrapper now uses reference counting to ensure that the underlying socket is closed when the ftpwrapper object is no longer in use * ftplib.FTP.ntransfercmd() now closes the socket if an error occurs Initial patch by Victor Stinner.
This commit is contained in:
parent
578617ad45
commit
b42c53e442
6 changed files with 65 additions and 31 deletions
|
@ -325,33 +325,39 @@ class FTP:
|
||||||
if self.passiveserver:
|
if self.passiveserver:
|
||||||
host, port = self.makepasv()
|
host, port = self.makepasv()
|
||||||
conn = socket.create_connection((host, port), self.timeout)
|
conn = socket.create_connection((host, port), self.timeout)
|
||||||
if rest is not None:
|
try:
|
||||||
self.sendcmd("REST %s" % rest)
|
if rest is not None:
|
||||||
resp = self.sendcmd(cmd)
|
self.sendcmd("REST %s" % rest)
|
||||||
# Some servers apparently send a 200 reply to
|
resp = self.sendcmd(cmd)
|
||||||
# a LIST or STOR command, before the 150 reply
|
# Some servers apparently send a 200 reply to
|
||||||
# (and way before the 226 reply). This seems to
|
# a LIST or STOR command, before the 150 reply
|
||||||
# be in violation of the protocol (which only allows
|
# (and way before the 226 reply). This seems to
|
||||||
# 1xx or error messages for LIST), so we just discard
|
# be in violation of the protocol (which only allows
|
||||||
# this response.
|
# 1xx or error messages for LIST), so we just discard
|
||||||
if resp[0] == '2':
|
# this response.
|
||||||
resp = self.getresp()
|
if resp[0] == '2':
|
||||||
if resp[0] != '1':
|
resp = self.getresp()
|
||||||
raise error_reply, resp
|
if resp[0] != '1':
|
||||||
|
raise error_reply, resp
|
||||||
|
except:
|
||||||
|
conn.close()
|
||||||
|
raise
|
||||||
else:
|
else:
|
||||||
sock = self.makeport()
|
sock = self.makeport()
|
||||||
if rest is not None:
|
try:
|
||||||
self.sendcmd("REST %s" % rest)
|
if rest is not None:
|
||||||
resp = self.sendcmd(cmd)
|
self.sendcmd("REST %s" % rest)
|
||||||
# See above.
|
resp = self.sendcmd(cmd)
|
||||||
if resp[0] == '2':
|
# See above.
|
||||||
resp = self.getresp()
|
if resp[0] == '2':
|
||||||
if resp[0] != '1':
|
resp = self.getresp()
|
||||||
raise error_reply, resp
|
if resp[0] != '1':
|
||||||
conn, sockaddr = sock.accept()
|
raise error_reply, resp
|
||||||
if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT:
|
conn, sockaddr = sock.accept()
|
||||||
conn.settimeout(self.timeout)
|
if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT:
|
||||||
sock.close()
|
conn.settimeout(self.timeout)
|
||||||
|
finally:
|
||||||
|
sock.close()
|
||||||
if resp[:3] == '150':
|
if resp[:3] == '150':
|
||||||
# this is conditional in case we received a 125
|
# this is conditional in case we received a 125
|
||||||
size = parse150(resp)
|
size = parse150(resp)
|
||||||
|
|
|
@ -611,6 +611,7 @@ class HandlerTests(unittest.TestCase):
|
||||||
def retrfile(self, filename, filetype):
|
def retrfile(self, filename, filetype):
|
||||||
self.filename, self.filetype = filename, filetype
|
self.filename, self.filetype = filename, filetype
|
||||||
return StringIO.StringIO(self.data), len(self.data)
|
return StringIO.StringIO(self.data), len(self.data)
|
||||||
|
def close(self): pass
|
||||||
|
|
||||||
class NullFTPHandler(urllib2.FTPHandler):
|
class NullFTPHandler(urllib2.FTPHandler):
|
||||||
def __init__(self, data): self.data = data
|
def __init__(self, data): self.data = data
|
||||||
|
|
|
@ -231,6 +231,7 @@ class OtherNetworkTests(unittest.TestCase):
|
||||||
handlers = []
|
handlers = []
|
||||||
|
|
||||||
cfh = urllib2.CacheFTPHandler()
|
cfh = urllib2.CacheFTPHandler()
|
||||||
|
self.addCleanup(cfh.clear_cache)
|
||||||
cfh.setTimeout(1)
|
cfh.setTimeout(1)
|
||||||
handlers.append(cfh)
|
handlers.append(cfh)
|
||||||
|
|
||||||
|
|
|
@ -850,13 +850,16 @@ class ftpwrapper:
|
||||||
"""Class used by open_ftp() for cache of open FTP connections."""
|
"""Class used by open_ftp() for cache of open FTP connections."""
|
||||||
|
|
||||||
def __init__(self, user, passwd, host, port, dirs,
|
def __init__(self, user, passwd, host, port, dirs,
|
||||||
timeout=socket._GLOBAL_DEFAULT_TIMEOUT):
|
timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
|
||||||
|
persistent=False):
|
||||||
self.user = user
|
self.user = user
|
||||||
self.passwd = passwd
|
self.passwd = passwd
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
self.dirs = dirs
|
self.dirs = dirs
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
|
self.refcount = 0
|
||||||
|
self.keepalive = persistent
|
||||||
self.init()
|
self.init()
|
||||||
|
|
||||||
def init(self):
|
def init(self):
|
||||||
|
@ -883,7 +886,7 @@ class ftpwrapper:
|
||||||
# Try to retrieve as a file
|
# Try to retrieve as a file
|
||||||
try:
|
try:
|
||||||
cmd = 'RETR ' + file
|
cmd = 'RETR ' + file
|
||||||
conn = self.ftp.ntransfercmd(cmd)
|
conn, retrlen = self.ftp.ntransfercmd(cmd)
|
||||||
except ftplib.error_perm, reason:
|
except ftplib.error_perm, reason:
|
||||||
if str(reason)[:3] != '550':
|
if str(reason)[:3] != '550':
|
||||||
raise IOError, ('ftp error', reason), sys.exc_info()[2]
|
raise IOError, ('ftp error', reason), sys.exc_info()[2]
|
||||||
|
@ -903,11 +906,14 @@ class ftpwrapper:
|
||||||
cmd = 'LIST ' + file
|
cmd = 'LIST ' + file
|
||||||
else:
|
else:
|
||||||
cmd = 'LIST'
|
cmd = 'LIST'
|
||||||
conn = self.ftp.ntransfercmd(cmd)
|
conn, retrlen = self.ftp.ntransfercmd(cmd)
|
||||||
self.busy = 1
|
self.busy = 1
|
||||||
|
ftpobj = addclosehook(conn.makefile('rb'), self.file_close)
|
||||||
|
self.refcount += 1
|
||||||
|
conn.close()
|
||||||
# Pass back both a suitably decorated object and a retrieval length
|
# Pass back both a suitably decorated object and a retrieval length
|
||||||
return (addclosehook(conn[0].makefile('rb'),
|
return (ftpobj, retrlen)
|
||||||
self.endtransfer), conn[1])
|
|
||||||
def endtransfer(self):
|
def endtransfer(self):
|
||||||
if not self.busy:
|
if not self.busy:
|
||||||
return
|
return
|
||||||
|
@ -918,6 +924,17 @@ class ftpwrapper:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
self.keepalive = False
|
||||||
|
if self.refcount <= 0:
|
||||||
|
self.real_close()
|
||||||
|
|
||||||
|
def file_close(self):
|
||||||
|
self.endtransfer()
|
||||||
|
self.refcount -= 1
|
||||||
|
if self.refcount <= 0 and not self.keepalive:
|
||||||
|
self.real_close()
|
||||||
|
|
||||||
|
def real_close(self):
|
||||||
self.endtransfer()
|
self.endtransfer()
|
||||||
try:
|
try:
|
||||||
self.ftp.close()
|
self.ftp.close()
|
||||||
|
|
|
@ -1399,7 +1399,8 @@ class FTPHandler(BaseHandler):
|
||||||
raise URLError, ('ftp error: %s' % msg), sys.exc_info()[2]
|
raise URLError, ('ftp error: %s' % msg), sys.exc_info()[2]
|
||||||
|
|
||||||
def connect_ftp(self, user, passwd, host, port, dirs, timeout):
|
def connect_ftp(self, user, passwd, host, port, dirs, timeout):
|
||||||
fw = ftpwrapper(user, passwd, host, port, dirs, timeout)
|
fw = ftpwrapper(user, passwd, host, port, dirs, timeout,
|
||||||
|
persistent=False)
|
||||||
## fw.ftp.set_debuglevel(1)
|
## fw.ftp.set_debuglevel(1)
|
||||||
return fw
|
return fw
|
||||||
|
|
||||||
|
@ -1448,3 +1449,9 @@ class CacheFTPHandler(FTPHandler):
|
||||||
del self.timeout[k]
|
del self.timeout[k]
|
||||||
break
|
break
|
||||||
self.soonest = min(self.timeout.values())
|
self.soonest = min(self.timeout.values())
|
||||||
|
|
||||||
|
def clear_cache(self):
|
||||||
|
for conn in self.cache.values():
|
||||||
|
conn.close()
|
||||||
|
self.cache.clear()
|
||||||
|
self.timeout.clear()
|
||||||
|
|
|
@ -37,6 +37,8 @@ Core and Builtins
|
||||||
Library
|
Library
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
- Issue #10883: Fix socket leaks in urllib.request when using FTP.
|
||||||
|
|
||||||
- Issue #12592: Make Python build on OpenBSD 5 (and future major releases).
|
- Issue #12592: Make Python build on OpenBSD 5 (and future major releases).
|
||||||
|
|
||||||
- Issue #12372: POSIX semaphores are broken on AIX: don't use them.
|
- Issue #12372: POSIX semaphores are broken on AIX: don't use them.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue