diff options
Diffstat (limited to 'Lib/test/test_ftplib.py')
| -rw-r--r-- | Lib/test/test_ftplib.py | 49 | 
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  | 
