summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_ftplib.py
diff options
context:
space:
mode:
authorAntoine Pitrou <solipsis@pitrou.net>2010-04-23 00:16:21 (GMT)
committerAntoine Pitrou <solipsis@pitrou.net>2010-04-23 00:16:21 (GMT)
commit2c4f98b3c58365845539f0d921c9d12b466b5e30 (patch)
tree2cd4c117bd36284ea749776aa1c2088817c28508 /Lib/test/test_ftplib.py
parent582c0a6ac2cba5fd41222db04b94e7fc51389a08 (diff)
downloadcpython-2c4f98b3c58365845539f0d921c9d12b466b5e30.zip
cpython-2c4f98b3c58365845539f0d921c9d12b466b5e30.tar.gz
cpython-2c4f98b3c58365845539f0d921c9d12b466b5e30.tar.bz2
Merged revisions 80392 via svnmerge from
svn+ssh://pythondev@svn.python.org/python/trunk ........ r80392 | antoine.pitrou | 2010-04-23 01:33:02 +0200 (ven., 23 avril 2010) | 9 lines Issue #8108: Fix the unwrap() method of SSL objects when the socket has a non-infinite timeout. Also make that method friendlier with applications wanting to continue using the socket in clear-text mode, by disabling OpenSSL's internal readahead. Thanks to Darryl Miles for guidance. Issue #8108: test_ftplib's non-blocking SSL server now has proper handling of SSL shutdowns. ........
Diffstat (limited to 'Lib/test/test_ftplib.py')
-rw-r--r--Lib/test/test_ftplib.py49
1 files changed, 37 insertions, 12 deletions
diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py
index 29f7f7d..ab03faf 100644
--- a/Lib/test/test_ftplib.py
+++ b/Lib/test/test_ftplib.py
@@ -28,6 +28,7 @@ NLST_DATA = 'foo\r\nbar\r\n'
class DummyDTPHandler(asynchat.async_chat):
+ dtp_conn_closed = False
def __init__(self, conn, baseclass):
asynchat.async_chat.__init__(self, conn)
@@ -38,8 +39,13 @@ class DummyDTPHandler(asynchat.async_chat):
self.baseclass.last_received_data += self.recv(1024).decode('ascii')
def handle_close(self):
- self.baseclass.push('226 transfer complete')
- self.close()
+ # XXX: this method can be called many times in a row for a single
+ # connection, including in clear-text (non-TLS) mode.
+ # (behaviour witnessed with test_data_connection)
+ if not self.dtp_conn_closed:
+ self.baseclass.push('226 transfer complete')
+ self.close()
+ self.dtp_conn_closed = True
def push(self, what):
super(DummyDTPHandler, self).push(what.encode('ascii'))
@@ -254,6 +260,7 @@ if ssl is not None:
"""An asyncore.dispatcher subclass supporting TLS/SSL."""
_ssl_accepting = False
+ _ssl_closing = False
def secure_connection(self):
self.del_channel()
@@ -280,15 +287,36 @@ if ssl is not None:
else:
self._ssl_accepting = False
+ def _do_ssl_shutdown(self):
+ self._ssl_closing = True
+ try:
+ self.socket = self.socket.unwrap()
+ except ssl.SSLError as err:
+ if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
+ ssl.SSL_ERROR_WANT_WRITE):
+ return
+ except socket.error as err:
+ # Any "socket error" corresponds to a SSL_ERROR_SYSCALL return
+ # from OpenSSL's SSL_shutdown(), corresponding to a
+ # closed socket condition. See also:
+ # http://www.mail-archive.com/openssl-users@openssl.org/msg60710.html
+ pass
+ self._ssl_closing = False
+ super(SSLConnection, self).close()
+
def handle_read_event(self):
if self._ssl_accepting:
self._do_ssl_handshake()
+ elif self._ssl_closing:
+ self._do_ssl_shutdown()
else:
super(SSLConnection, self).handle_read_event()
def handle_write_event(self):
if self._ssl_accepting:
self._do_ssl_handshake()
+ elif self._ssl_closing:
+ self._do_ssl_shutdown()
else:
super(SSLConnection, self).handle_write_event()
@@ -308,7 +336,7 @@ if ssl is not None:
except ssl.SSLError as err:
if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
ssl.SSL_ERROR_WANT_WRITE):
- return ''
+ return b''
if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN):
self.handle_close()
return b''
@@ -318,12 +346,9 @@ if ssl is not None:
raise
def close(self):
- try:
- if isinstance(self.socket, ssl.SSLSocket):
- if self.socket._sslobj is not None:
- self.socket.unwrap()
- finally:
- super(SSLConnection, self).close()
+ if (isinstance(self.socket, ssl.SSLSocket) and
+ self.socket._sslobj is not None):
+ self._do_ssl_shutdown()
class DummyTLS_DTPHandler(SSLConnection, DummyDTPHandler):
@@ -606,21 +631,21 @@ class TestTLS_FTPClass(TestCase):
sock = self.client.transfercmd('list')
self.assertNotIsInstance(sock, ssl.SSLSocket)
sock.close()
- self.client.voidresp()
+ self.assertEqual(self.client.voidresp(), "226 transfer complete")
# secured, after PROT P
self.client.prot_p()
sock = self.client.transfercmd('list')
self.assertIsInstance(sock, ssl.SSLSocket)
sock.close()
- self.client.voidresp()
+ self.assertEqual(self.client.voidresp(), "226 transfer complete")
# PROT C is issued, the connection must be in cleartext again
self.client.prot_c()
sock = self.client.transfercmd('list')
self.assertNotIsInstance(sock, ssl.SSLSocket)
sock.close()
- self.client.voidresp()
+ self.assertEqual(self.client.voidresp(), "226 transfer complete")
def test_login(self):
# login() is supposed to implicitly secure the control connection