aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/Lib/test/test_ftplib.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/test/test_ftplib.py')
-rw-r--r--Lib/test/test_ftplib.py255
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__':