diff options
Diffstat (limited to 'Lib/test/test_ftplib.py')
-rw-r--r-- | Lib/test/test_ftplib.py | 255 |
1 files changed, 178 insertions, 77 deletions
diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py index c82e8a69bf3..71bc23e615a 100644 --- a/Lib/test/test_ftplib.py +++ b/Lib/test/test_ftplib.py @@ -7,19 +7,19 @@ import ftplib import asyncore import asynchat import socket -import StringIO +import io import errno import os +import time try: import ssl except ImportError: ssl = None from unittest import TestCase -from test import test_support -from test.test_support import HOST -threading = test_support.import_module('threading') - +from test import support +from test.support import HOST +threading = support.import_module('threading') # the dummy data returned by server over the data channel when # RETR, LIST and NLST commands are issued @@ -37,7 +37,7 @@ class DummyDTPHandler(asynchat.async_chat): self.baseclass.last_received_data = '' def handle_read(self): - self.baseclass.last_received_data += self.recv(1024) + self.baseclass.last_received_data += self.recv(1024).decode('ascii') def handle_close(self): # XXX: this method can be called many times in a row for a single @@ -48,6 +48,9 @@ class DummyDTPHandler(asynchat.async_chat): self.close() self.dtp_conn_closed = True + def push(self, what): + super(DummyDTPHandler, self).push(what.encode('ascii')) + def handle_error(self): raise @@ -58,7 +61,9 @@ class DummyFTPHandler(asynchat.async_chat): def __init__(self, conn): asynchat.async_chat.__init__(self, conn) - self.set_terminator("\r\n") + # tells the socket to handle urgent data inline (ABOR command) + self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_OOBINLINE, 1) + self.set_terminator(b"\r\n") self.in_buffer = [] self.dtp = None self.last_received_cmd = None @@ -71,7 +76,7 @@ class DummyFTPHandler(asynchat.async_chat): self.in_buffer.append(data) def found_terminator(self): - line = ''.join(self.in_buffer) + line = b''.join(self.in_buffer).decode('ascii') self.in_buffer = [] if self.next_response: self.push(self.next_response) @@ -93,10 +98,10 @@ class DummyFTPHandler(asynchat.async_chat): raise def push(self, data): - asynchat.async_chat.push(self, data + '\r\n') + asynchat.async_chat.push(self, data.encode('ascii') + b'\r\n') def cmd_port(self, arg): - addr = map(int, arg.split(',')) + addr = list(map(int, arg.split(','))) ip = '%d.%d.%d.%d' %tuple(addr[:4]) port = (addr[4] * 256) + addr[5] s = socket.create_connection((ip, port), timeout=10) @@ -104,16 +109,15 @@ class DummyFTPHandler(asynchat.async_chat): self.push('200 active data connection established') def cmd_pasv(self, arg): - sock = socket.socket() - sock.bind((self.socket.getsockname()[0], 0)) - sock.listen(5) - sock.settimeout(10) - ip, port = sock.getsockname()[:2] - ip = ip.replace('.', ',') - p1, p2 = divmod(port, 256) - self.push('227 entering passive mode (%s,%d,%d)' %(ip, p1, p2)) - conn, addr = sock.accept() - self.dtp = self.dtp_handler(conn, baseclass=self) + with socket.socket() as sock: + sock.bind((self.socket.getsockname()[0], 0)) + sock.listen(5) + sock.settimeout(10) + ip, port = sock.getsockname()[:2] + ip = ip.replace('.', ','); p1 = port / 256; p2 = port % 256 + self.push('227 entering passive mode (%s,%d,%d)' %(ip, p1, p2)) + conn, addr = sock.accept() + self.dtp = self.dtp_handler(conn, baseclass=self) def cmd_eprt(self, arg): af, ip, port = arg.split(arg[0])[1:-1] @@ -123,19 +127,22 @@ class DummyFTPHandler(asynchat.async_chat): self.push('200 active data connection established') def cmd_epsv(self, arg): - sock = socket.socket(socket.AF_INET6) - sock.bind((self.socket.getsockname()[0], 0)) - sock.listen(5) - sock.settimeout(10) - port = sock.getsockname()[1] - self.push('229 entering extended passive mode (|||%d|)' %port) - conn, addr = sock.accept() - self.dtp = self.dtp_handler(conn, baseclass=self) + with socket.socket(socket.AF_INET6) as sock: + sock.bind((self.socket.getsockname()[0], 0)) + sock.listen(5) + sock.settimeout(10) + port = sock.getsockname()[1] + self.push('229 entering extended passive mode (|||%d|)' %port) + conn, addr = sock.accept() + self.dtp = self.dtp_handler(conn, baseclass=self) def cmd_echo(self, arg): # sends back the received string (used by the test suite) self.push(arg) + def cmd_noop(self, arg): + self.push('200 noop ok') + def cmd_user(self, arg): self.push('331 username ok') @@ -176,6 +183,9 @@ class DummyFTPHandler(asynchat.async_chat): self.push('221 quit ok') self.close() + def cmd_abor(self, arg): + self.push('226 abor ok') + def cmd_stor(self, arg): self.push('125 stor ok') @@ -217,6 +227,7 @@ class DummyFTPServer(asyncore.dispatcher, threading.Thread): self.active = False self.active_lock = threading.Lock() self.host, self.port = self.socket.getsockname()[:2] + self.handler_instance = None def start(self): assert not self.active @@ -238,10 +249,8 @@ class DummyFTPServer(asyncore.dispatcher, threading.Thread): self.active = False self.join() - def handle_accept(self): - conn, addr = self.accept() - self.handler = self.handler(conn) - self.close() + def handle_accepted(self, conn, addr): + self.handler_instance = self.handler(conn) def handle_connect(self): self.close() @@ -258,30 +267,32 @@ if ssl is not None: CERTFILE = os.path.join(os.path.dirname(__file__), "keycert.pem") - class SSLConnection(object, asyncore.dispatcher): + class SSLConnection(asyncore.dispatcher): """An asyncore.dispatcher subclass supporting TLS/SSL.""" _ssl_accepting = False _ssl_closing = False def secure_connection(self): - self.socket = ssl.wrap_socket(self.socket, suppress_ragged_eofs=False, - certfile=CERTFILE, server_side=True, - do_handshake_on_connect=False, - ssl_version=ssl.PROTOCOL_SSLv23) + self.del_channel() + socket = ssl.wrap_socket(self.socket, suppress_ragged_eofs=False, + certfile=CERTFILE, server_side=True, + do_handshake_on_connect=False, + ssl_version=ssl.PROTOCOL_SSLv23) + self.set_socket(socket) self._ssl_accepting = True def _do_ssl_handshake(self): try: self.socket.do_handshake() - except ssl.SSLError, err: + except ssl.SSLError as err: if err.args[0] in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): return elif err.args[0] == ssl.SSL_ERROR_EOF: return self.handle_close() raise - except socket.error, err: + except socket.error as err: if err.args[0] == errno.ECONNABORTED: return self.handle_close() else: @@ -291,11 +302,11 @@ if ssl is not None: self._ssl_closing = True try: self.socket = self.socket.unwrap() - except ssl.SSLError, err: + except ssl.SSLError as err: if err.args[0] in (ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): return - except socket.error, err: + 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: @@ -323,7 +334,7 @@ if ssl is not None: def send(self, data): try: return super(SSLConnection, self).send(data) - except ssl.SSLError, err: + except ssl.SSLError as err: if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN, ssl.SSL_ERROR_WANT_READ, ssl.SSL_ERROR_WANT_WRITE): @@ -333,13 +344,13 @@ if ssl is not None: def recv(self, buffer_size): try: return super(SSLConnection, self).recv(buffer_size) - except ssl.SSLError, err: + 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 '' + return b'' raise def handle_error(self): @@ -349,6 +360,8 @@ if ssl is not None: if (isinstance(self.socket, ssl.SSLSocket) and self.socket._sslobj is not None): self._do_ssl_shutdown() + else: + super(SSLConnection, self).close() class DummyTLS_DTPHandler(SSLConnection, DummyDTPHandler): @@ -456,12 +469,12 @@ class TestFTPClass(TestCase): def test_rename(self): self.client.rename('a', 'b') - self.server.handler.next_response = '200' + self.server.handler_instance.next_response = '200' self.assertRaises(ftplib.error_reply, self.client.rename, 'a', 'b') def test_delete(self): self.client.delete('foo') - self.server.handler.next_response = '199' + self.server.handler_instance.next_response = '199' self.assertRaises(ftplib.error_reply, self.client.delete, 'foo') def test_size(self): @@ -483,15 +496,22 @@ class TestFTPClass(TestCase): # Ensure the connection gets closed; sock attribute should be None self.assertEqual(self.client.sock, None) + def test_abort(self): + self.client.abort() + def test_retrbinary(self): + def callback(data): + received.append(data.decode('ascii')) received = [] - self.client.retrbinary('retr', received.append) + self.client.retrbinary('retr', callback) self.assertEqual(''.join(received), RETR_DATA) def test_retrbinary_rest(self): + def callback(data): + received.append(data.decode('ascii')) for rest in (0, 10, 20): received = [] - self.client.retrbinary('retr', received.append, rest=rest) + self.client.retrbinary('retr', callback, rest=rest) self.assertEqual(''.join(received), RETR_DATA[rest:], msg='rest test case %d %d %d' % (rest, len(''.join(received)), @@ -503,9 +523,9 @@ class TestFTPClass(TestCase): self.assertEqual(''.join(received), RETR_DATA.replace('\r\n', '')) def test_storbinary(self): - f = StringIO.StringIO(RETR_DATA) + f = io.BytesIO(RETR_DATA.encode('ascii')) self.client.storbinary('stor', f) - self.assertEqual(self.server.handler.last_received_data, RETR_DATA) + self.assertEqual(self.server.handler_instance.last_received_data, RETR_DATA) # test new callback arg flag = [] f.seek(0) @@ -513,16 +533,16 @@ class TestFTPClass(TestCase): self.assertTrue(flag) def test_storbinary_rest(self): - f = StringIO.StringIO(RETR_DATA) + f = io.BytesIO(RETR_DATA.replace('\r\n', '\n').encode('ascii')) for r in (30, '30'): f.seek(0) self.client.storbinary('stor', f, rest=r) - self.assertEqual(self.server.handler.rest, str(r)) + self.assertEqual(self.server.handler_instance.rest, str(r)) def test_storlines(self): - f = StringIO.StringIO(RETR_DATA.replace('\r\n', '\n')) + f = io.BytesIO(RETR_DATA.replace('\r\n', '\n').encode('ascii')) self.client.storlines('stor', f) - self.assertEqual(self.server.handler.last_received_data, RETR_DATA) + self.assertEqual(self.server.handler_instance.last_received_data, RETR_DATA) # test new callback arg flag = [] f.seek(0) @@ -539,16 +559,74 @@ class TestFTPClass(TestCase): self.assertEqual(''.join(l), LIST_DATA.replace('\r\n', '')) def test_makeport(self): - self.client.makeport() - # IPv4 is in use, just make sure send_eprt has not been used - self.assertEqual(self.server.handler.last_received_cmd, 'port') + with self.client.makeport(): + # IPv4 is in use, just make sure send_eprt has not been used + self.assertEqual(self.server.handler_instance.last_received_cmd, + 'port') def test_makepasv(self): host, port = self.client.makepasv() conn = socket.create_connection((host, port), 10) conn.close() # IPv4 is in use, just make sure send_epsv has not been used - self.assertEqual(self.server.handler.last_received_cmd, 'pasv') + self.assertEqual(self.server.handler_instance.last_received_cmd, 'pasv') + + def test_with_statement(self): + self.client.quit() + + def is_client_connected(): + if self.client.sock is None: + return False + try: + self.client.sendcmd('noop') + except (socket.error, EOFError): + return False + return True + + # base test + with ftplib.FTP(timeout=10) as self.client: + self.client.connect(self.server.host, self.server.port) + self.client.sendcmd('noop') + self.assertTrue(is_client_connected()) + self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit') + self.assertFalse(is_client_connected()) + + # QUIT sent inside the with block + with ftplib.FTP(timeout=10) as self.client: + self.client.connect(self.server.host, self.server.port) + self.client.sendcmd('noop') + self.client.quit() + self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit') + self.assertFalse(is_client_connected()) + + # force a wrong response code to be sent on QUIT: error_perm + # is expected and the connection is supposed to be closed + try: + with ftplib.FTP(timeout=10) as self.client: + self.client.connect(self.server.host, self.server.port) + self.client.sendcmd('noop') + self.server.handler_instance.next_response = '550 error on quit' + except ftplib.error_perm as err: + self.assertEqual(str(err), '550 error on quit') + else: + self.fail('Exception not raised') + # needed to give the threaded server some time to set the attribute + # which otherwise would still be == 'noop' + time.sleep(0.1) + self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit') + self.assertFalse(is_client_connected()) + + def test_parse257(self): + self.assertEqual(ftplib.parse257('257 "/foo/bar"'), '/foo/bar') + self.assertEqual(ftplib.parse257('257 "/foo/bar" created'), '/foo/bar') + self.assertEqual(ftplib.parse257('257 ""'), '') + self.assertEqual(ftplib.parse257('257 "" created'), '') + self.assertRaises(ftplib.error_reply, ftplib.parse257, '250 "/foo/bar"') + # The 257 response is supposed to include the directory + # name and in case it contains embedded double-quotes + # they must be doubled (see RFC-959, chapter 7, appendix 2). + self.assertEqual(ftplib.parse257('257 "/foo/b""ar"'), '/foo/b"ar') + self.assertEqual(ftplib.parse257('257 "/foo/b""ar" created'), '/foo/b"ar') class TestIPv6Environment(TestCase): @@ -567,19 +645,22 @@ class TestIPv6Environment(TestCase): self.assertEqual(self.client.af, socket.AF_INET6) def test_makeport(self): - self.client.makeport() - self.assertEqual(self.server.handler.last_received_cmd, 'eprt') + with self.client.makeport(): + self.assertEqual(self.server.handler_instance.last_received_cmd, + 'eprt') def test_makepasv(self): host, port = self.client.makepasv() conn = socket.create_connection((host, port), 10) conn.close() - self.assertEqual(self.server.handler.last_received_cmd, 'epsv') + self.assertEqual(self.server.handler_instance.last_received_cmd, 'epsv') def test_transfer(self): def retr(): + def callback(data): + received.append(data.decode('ascii')) received = [] - self.client.retrbinary('retr', received.append) + self.client.retrbinary('retr', callback) self.assertEqual(''.join(received), RETR_DATA) self.client.set_pasv(True) retr() @@ -622,23 +703,20 @@ class TestTLS_FTPClass(TestCase): def test_data_connection(self): # clear text - sock = self.client.transfercmd('list') - self.assertNotIsInstance(sock, ssl.SSLSocket) - sock.close() + with self.client.transfercmd('list') as sock: + self.assertNotIsInstance(sock, ssl.SSLSocket) 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() + with self.client.transfercmd('list') as sock: + self.assertIsInstance(sock, ssl.SSLSocket) 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() + with self.client.transfercmd('list') as sock: + self.assertNotIsInstance(sock, ssl.SSLSocket) self.assertEqual(self.client.voidresp(), "226 transfer complete") def test_login(self): @@ -661,6 +739,28 @@ class TestTLS_FTPClass(TestCase): finally: self.client.ssl_version = ssl.PROTOCOL_TLSv1 + def test_context(self): + self.client.quit() + ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1) + self.assertRaises(ValueError, ftplib.FTP_TLS, keyfile=CERTFILE, + context=ctx) + self.assertRaises(ValueError, ftplib.FTP_TLS, certfile=CERTFILE, + context=ctx) + self.assertRaises(ValueError, ftplib.FTP_TLS, certfile=CERTFILE, + keyfile=CERTFILE, context=ctx) + + self.client = ftplib.FTP_TLS(context=ctx, timeout=10) + self.client.connect(self.server.host, self.server.port) + self.assertNotIsInstance(self.client.sock, ssl.SSLSocket) + self.client.auth() + self.assertIs(self.client.sock.context, ctx) + self.assertIsInstance(self.client.sock, ssl.SSLSocket) + + self.client.prot_p() + with self.client.transfercmd('list') as sock: + self.assertIs(sock.context, ctx) + self.assertIsInstance(sock, ssl.SSLSocket) + class TestTimeouts(TestCase): @@ -668,7 +768,7 @@ class TestTimeouts(TestCase): self.evt = threading.Event() self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.sock.settimeout(10) - self.port = test_support.bind_port(self.sock) + self.port = support.bind_port(self.sock) threading.Thread(target=self.server, args=(self.evt,self.sock)).start() # Wait for the server to be ready. self.evt.wait() @@ -677,6 +777,7 @@ class TestTimeouts(TestCase): def tearDown(self): self.evt.wait() + self.sock.close() def server(self, evt, serv): # This method sets the evt 3 times: @@ -691,7 +792,7 @@ class TestTimeouts(TestCase): except socket.timeout: pass else: - conn.send("1 Hola mundo\n") + conn.send(b"1 Hola mundo\n") # (2) Signal the caller that it is safe to close the socket. evt.set() conn.close() @@ -767,11 +868,11 @@ def test_main(): if ssl is not None: tests.extend([TestTLS_FTPClassMixin, TestTLS_FTPClass]) - thread_info = test_support.threading_setup() + thread_info = support.threading_setup() try: - test_support.run_unittest(*tests) + support.run_unittest(*tests) finally: - test_support.threading_cleanup(*thread_info) + support.threading_cleanup(*thread_info) if __name__ == '__main__': |