blob: 71bc23e615ad97c35f28c2255fb69f5511499988 [file] [log] [blame]
Benjamin Petersonbe17a112008-09-27 21:49:47 +00001"""Test script for ftplib module."""
2
Antoine Pitrouf988cd02009-11-17 20:21:14 +00003# Modified by Giampaolo Rodola' to test FTP class, IPv6 and TLS
4# environment
Benjamin Petersonbe17a112008-09-27 21:49:47 +00005
Guido van Rossumd8faa362007-04-27 19:54:29 +00006import ftplib
Benjamin Petersonbe17a112008-09-27 21:49:47 +00007import asyncore
8import asynchat
9import socket
10import io
Antoine Pitrouf988cd02009-11-17 20:21:14 +000011import errno
12import os
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +000013import time
Antoine Pitrouf988cd02009-11-17 20:21:14 +000014try:
15 import ssl
16except ImportError:
17 ssl = None
Guido van Rossumd8faa362007-04-27 19:54:29 +000018
19from unittest import TestCase
Benjamin Petersonee8712c2008-05-20 21:35:26 +000020from test import support
Benjamin Petersonbe17a112008-09-27 21:49:47 +000021from test.support import HOST
Victor Stinner45df8202010-04-28 22:31:17 +000022threading = support.import_module('threading')
Guido van Rossumd8faa362007-04-27 19:54:29 +000023
Benjamin Petersonbe17a112008-09-27 21:49:47 +000024# the dummy data returned by server over the data channel when
25# RETR, LIST and NLST commands are issued
26RETR_DATA = 'abcde12345\r\n' * 1000
27LIST_DATA = 'foo\r\nbar\r\n'
28NLST_DATA = 'foo\r\nbar\r\n'
Christian Heimes836baa52008-02-26 08:18:30 +000029
Christian Heimes836baa52008-02-26 08:18:30 +000030
Benjamin Petersonbe17a112008-09-27 21:49:47 +000031class DummyDTPHandler(asynchat.async_chat):
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +000032 dtp_conn_closed = False
Benjamin Petersonbe17a112008-09-27 21:49:47 +000033
34 def __init__(self, conn, baseclass):
35 asynchat.async_chat.__init__(self, conn)
36 self.baseclass = baseclass
37 self.baseclass.last_received_data = ''
38
39 def handle_read(self):
Giampaolo Rodolàf96482e2010-08-04 10:36:18 +000040 self.baseclass.last_received_data += self.recv(1024).decode('ascii')
Benjamin Petersonbe17a112008-09-27 21:49:47 +000041
42 def handle_close(self):
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +000043 # XXX: this method can be called many times in a row for a single
44 # connection, including in clear-text (non-TLS) mode.
45 # (behaviour witnessed with test_data_connection)
46 if not self.dtp_conn_closed:
47 self.baseclass.push('226 transfer complete')
48 self.close()
49 self.dtp_conn_closed = True
Benjamin Petersonbe17a112008-09-27 21:49:47 +000050
51 def push(self, what):
Giampaolo Rodolàf96482e2010-08-04 10:36:18 +000052 super(DummyDTPHandler, self).push(what.encode('ascii'))
Benjamin Petersonbe17a112008-09-27 21:49:47 +000053
Giampaolo Rodolàd930b632010-05-06 20:21:57 +000054 def handle_error(self):
55 raise
56
Benjamin Petersonbe17a112008-09-27 21:49:47 +000057
58class DummyFTPHandler(asynchat.async_chat):
59
Antoine Pitrouf988cd02009-11-17 20:21:14 +000060 dtp_handler = DummyDTPHandler
61
Benjamin Petersonbe17a112008-09-27 21:49:47 +000062 def __init__(self, conn):
63 asynchat.async_chat.__init__(self, conn)
Giampaolo Rodola'0b5c21f2011-05-07 19:03:47 +020064 # tells the socket to handle urgent data inline (ABOR command)
65 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_OOBINLINE, 1)
Benjamin Petersonbe17a112008-09-27 21:49:47 +000066 self.set_terminator(b"\r\n")
67 self.in_buffer = []
68 self.dtp = None
69 self.last_received_cmd = None
70 self.last_received_data = ''
71 self.next_response = ''
Antoine Pitrou648bcd72009-11-27 13:23:26 +000072 self.rest = None
Benjamin Petersonbe17a112008-09-27 21:49:47 +000073 self.push('220 welcome')
74
75 def collect_incoming_data(self, data):
76 self.in_buffer.append(data)
77
78 def found_terminator(self):
79 line = b''.join(self.in_buffer).decode('ascii')
80 self.in_buffer = []
81 if self.next_response:
82 self.push(self.next_response)
83 self.next_response = ''
84 cmd = line.split(' ')[0].lower()
85 self.last_received_cmd = cmd
86 space = line.find(' ')
87 if space != -1:
88 arg = line[space + 1:]
89 else:
90 arg = ""
91 if hasattr(self, 'cmd_' + cmd):
92 method = getattr(self, 'cmd_' + cmd)
93 method(arg)
94 else:
95 self.push('550 command "%s" not understood.' %cmd)
96
97 def handle_error(self):
98 raise
99
100 def push(self, data):
101 asynchat.async_chat.push(self, data.encode('ascii') + b'\r\n')
102
103 def cmd_port(self, arg):
104 addr = list(map(int, arg.split(',')))
105 ip = '%d.%d.%d.%d' %tuple(addr[:4])
106 port = (addr[4] * 256) + addr[5]
Antoine Pitroud778e562010-10-14 20:35:26 +0000107 s = socket.create_connection((ip, port), timeout=10)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000108 self.dtp = self.dtp_handler(s, baseclass=self)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000109 self.push('200 active data connection established')
110
111 def cmd_pasv(self, arg):
Brett Cannon918e2d42010-10-29 23:26:25 +0000112 with socket.socket() as sock:
113 sock.bind((self.socket.getsockname()[0], 0))
114 sock.listen(5)
115 sock.settimeout(10)
116 ip, port = sock.getsockname()[:2]
117 ip = ip.replace('.', ','); p1 = port / 256; p2 = port % 256
118 self.push('227 entering passive mode (%s,%d,%d)' %(ip, p1, p2))
119 conn, addr = sock.accept()
120 self.dtp = self.dtp_handler(conn, baseclass=self)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000121
122 def cmd_eprt(self, arg):
123 af, ip, port = arg.split(arg[0])[1:-1]
124 port = int(port)
Antoine Pitroud778e562010-10-14 20:35:26 +0000125 s = socket.create_connection((ip, port), timeout=10)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000126 self.dtp = self.dtp_handler(s, baseclass=self)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000127 self.push('200 active data connection established')
128
129 def cmd_epsv(self, arg):
Brett Cannon918e2d42010-10-29 23:26:25 +0000130 with socket.socket(socket.AF_INET6) as sock:
131 sock.bind((self.socket.getsockname()[0], 0))
132 sock.listen(5)
133 sock.settimeout(10)
134 port = sock.getsockname()[1]
135 self.push('229 entering extended passive mode (|||%d|)' %port)
136 conn, addr = sock.accept()
137 self.dtp = self.dtp_handler(conn, baseclass=self)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000138
139 def cmd_echo(self, arg):
140 # sends back the received string (used by the test suite)
141 self.push(arg)
142
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000143 def cmd_noop(self, arg):
144 self.push('200 noop ok')
145
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000146 def cmd_user(self, arg):
147 self.push('331 username ok')
148
149 def cmd_pass(self, arg):
150 self.push('230 password ok')
151
152 def cmd_acct(self, arg):
153 self.push('230 acct ok')
154
155 def cmd_rnfr(self, arg):
156 self.push('350 rnfr ok')
157
158 def cmd_rnto(self, arg):
159 self.push('250 rnto ok')
160
161 def cmd_dele(self, arg):
162 self.push('250 dele ok')
163
164 def cmd_cwd(self, arg):
165 self.push('250 cwd ok')
166
167 def cmd_size(self, arg):
168 self.push('250 1000')
169
170 def cmd_mkd(self, arg):
171 self.push('257 "%s"' %arg)
172
173 def cmd_rmd(self, arg):
174 self.push('250 rmd ok')
175
176 def cmd_pwd(self, arg):
177 self.push('257 "pwd ok"')
178
179 def cmd_type(self, arg):
Giampaolo Rodolàf96482e2010-08-04 10:36:18 +0000180 self.push('200 type ok')
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000181
182 def cmd_quit(self, arg):
183 self.push('221 quit ok')
184 self.close()
185
Giampaolo Rodola'0b5c21f2011-05-07 19:03:47 +0200186 def cmd_abor(self, arg):
187 self.push('226 abor ok')
188
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000189 def cmd_stor(self, arg):
190 self.push('125 stor ok')
191
Antoine Pitrou648bcd72009-11-27 13:23:26 +0000192 def cmd_rest(self, arg):
193 self.rest = arg
194 self.push('350 rest ok')
195
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000196 def cmd_retr(self, arg):
197 self.push('125 retr ok')
Antoine Pitrou648bcd72009-11-27 13:23:26 +0000198 if self.rest is not None:
199 offset = int(self.rest)
200 else:
201 offset = 0
Giampaolo Rodolàf96482e2010-08-04 10:36:18 +0000202 self.dtp.push(RETR_DATA[offset:])
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000203 self.dtp.close_when_done()
Antoine Pitrou648bcd72009-11-27 13:23:26 +0000204 self.rest = None
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000205
206 def cmd_list(self, arg):
207 self.push('125 list ok')
208 self.dtp.push(LIST_DATA)
209 self.dtp.close_when_done()
210
211 def cmd_nlst(self, arg):
212 self.push('125 nlst ok')
213 self.dtp.push(NLST_DATA)
214 self.dtp.close_when_done()
215
216
217class DummyFTPServer(asyncore.dispatcher, threading.Thread):
218
219 handler = DummyFTPHandler
220
221 def __init__(self, address, af=socket.AF_INET):
222 threading.Thread.__init__(self)
223 asyncore.dispatcher.__init__(self)
224 self.create_socket(af, socket.SOCK_STREAM)
225 self.bind(address)
226 self.listen(5)
227 self.active = False
228 self.active_lock = threading.Lock()
229 self.host, self.port = self.socket.getsockname()[:2]
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000230 self.handler_instance = None
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000231
232 def start(self):
233 assert not self.active
234 self.__flag = threading.Event()
235 threading.Thread.start(self)
236 self.__flag.wait()
237
238 def run(self):
239 self.active = True
240 self.__flag.set()
241 while self.active and asyncore.socket_map:
242 self.active_lock.acquire()
243 asyncore.loop(timeout=0.1, count=1)
244 self.active_lock.release()
245 asyncore.close_all(ignore_all=True)
246
247 def stop(self):
248 assert self.active
249 self.active = False
250 self.join()
251
Giampaolo Rodolà977c7072010-10-04 21:08:36 +0000252 def handle_accepted(self, conn, addr):
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000253 self.handler_instance = self.handler(conn)
Benjamin Petersond06e3b02008-09-28 21:00:42 +0000254
255 def handle_connect(self):
256 self.close()
257 handle_read = handle_connect
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000258
259 def writable(self):
260 return 0
261
262 def handle_error(self):
263 raise
264
265
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000266if ssl is not None:
267
268 CERTFILE = os.path.join(os.path.dirname(__file__), "keycert.pem")
269
270 class SSLConnection(asyncore.dispatcher):
271 """An asyncore.dispatcher subclass supporting TLS/SSL."""
272
273 _ssl_accepting = False
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000274 _ssl_closing = False
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000275
276 def secure_connection(self):
277 self.del_channel()
278 socket = ssl.wrap_socket(self.socket, suppress_ragged_eofs=False,
279 certfile=CERTFILE, server_side=True,
280 do_handshake_on_connect=False,
281 ssl_version=ssl.PROTOCOL_SSLv23)
282 self.set_socket(socket)
283 self._ssl_accepting = True
284
285 def _do_ssl_handshake(self):
286 try:
287 self.socket.do_handshake()
288 except ssl.SSLError as err:
289 if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
290 ssl.SSL_ERROR_WANT_WRITE):
291 return
292 elif err.args[0] == ssl.SSL_ERROR_EOF:
293 return self.handle_close()
294 raise
295 except socket.error as err:
296 if err.args[0] == errno.ECONNABORTED:
297 return self.handle_close()
298 else:
299 self._ssl_accepting = False
300
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000301 def _do_ssl_shutdown(self):
302 self._ssl_closing = True
303 try:
304 self.socket = self.socket.unwrap()
305 except ssl.SSLError as err:
306 if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
307 ssl.SSL_ERROR_WANT_WRITE):
308 return
309 except socket.error as err:
310 # Any "socket error" corresponds to a SSL_ERROR_SYSCALL return
311 # from OpenSSL's SSL_shutdown(), corresponding to a
312 # closed socket condition. See also:
313 # http://www.mail-archive.com/openssl-users@openssl.org/msg60710.html
314 pass
315 self._ssl_closing = False
316 super(SSLConnection, self).close()
317
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000318 def handle_read_event(self):
319 if self._ssl_accepting:
320 self._do_ssl_handshake()
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000321 elif self._ssl_closing:
322 self._do_ssl_shutdown()
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000323 else:
324 super(SSLConnection, self).handle_read_event()
325
326 def handle_write_event(self):
327 if self._ssl_accepting:
328 self._do_ssl_handshake()
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000329 elif self._ssl_closing:
330 self._do_ssl_shutdown()
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000331 else:
332 super(SSLConnection, self).handle_write_event()
333
334 def send(self, data):
335 try:
336 return super(SSLConnection, self).send(data)
337 except ssl.SSLError as err:
Antoine Pitrou5733c082010-03-22 14:49:10 +0000338 if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN,
339 ssl.SSL_ERROR_WANT_READ,
340 ssl.SSL_ERROR_WANT_WRITE):
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000341 return 0
342 raise
343
344 def recv(self, buffer_size):
345 try:
346 return super(SSLConnection, self).recv(buffer_size)
347 except ssl.SSLError as err:
Antoine Pitrou5733c082010-03-22 14:49:10 +0000348 if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
349 ssl.SSL_ERROR_WANT_WRITE):
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000350 return b''
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000351 if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN):
352 self.handle_close()
353 return b''
354 raise
355
356 def handle_error(self):
357 raise
358
359 def close(self):
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000360 if (isinstance(self.socket, ssl.SSLSocket) and
361 self.socket._sslobj is not None):
362 self._do_ssl_shutdown()
Benjamin Peterson1bd93a72010-10-31 19:58:07 +0000363 else:
364 super(SSLConnection, self).close()
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000365
366
367 class DummyTLS_DTPHandler(SSLConnection, DummyDTPHandler):
368 """A DummyDTPHandler subclass supporting TLS/SSL."""
369
370 def __init__(self, conn, baseclass):
371 DummyDTPHandler.__init__(self, conn, baseclass)
372 if self.baseclass.secure_data_channel:
373 self.secure_connection()
374
375
376 class DummyTLS_FTPHandler(SSLConnection, DummyFTPHandler):
377 """A DummyFTPHandler subclass supporting TLS/SSL."""
378
379 dtp_handler = DummyTLS_DTPHandler
380
381 def __init__(self, conn):
382 DummyFTPHandler.__init__(self, conn)
383 self.secure_data_channel = False
384
385 def cmd_auth(self, line):
386 """Set up secure control channel."""
387 self.push('234 AUTH TLS successful')
388 self.secure_connection()
389
390 def cmd_pbsz(self, line):
391 """Negotiate size of buffer for secure data transfer.
392 For TLS/SSL the only valid value for the parameter is '0'.
393 Any other value is accepted but ignored.
394 """
395 self.push('200 PBSZ=0 successful.')
396
397 def cmd_prot(self, line):
398 """Setup un/secure data channel."""
399 arg = line.upper()
400 if arg == 'C':
401 self.push('200 Protection set to Clear')
402 self.secure_data_channel = False
403 elif arg == 'P':
404 self.push('200 Protection set to Private')
405 self.secure_data_channel = True
406 else:
407 self.push("502 Unrecognized PROT type (use C or P).")
408
409
410 class DummyTLS_FTPServer(DummyFTPServer):
411 handler = DummyTLS_FTPHandler
412
413
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000414class TestFTPClass(TestCase):
415
416 def setUp(self):
417 self.server = DummyFTPServer((HOST, 0))
418 self.server.start()
Antoine Pitroud778e562010-10-14 20:35:26 +0000419 self.client = ftplib.FTP(timeout=10)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000420 self.client.connect(self.server.host, self.server.port)
421
422 def tearDown(self):
423 self.client.close()
424 self.server.stop()
425
426 def test_getwelcome(self):
427 self.assertEqual(self.client.getwelcome(), '220 welcome')
428
429 def test_sanitize(self):
430 self.assertEqual(self.client.sanitize('foo'), repr('foo'))
431 self.assertEqual(self.client.sanitize('pass 12345'), repr('pass *****'))
432 self.assertEqual(self.client.sanitize('PASS 12345'), repr('PASS *****'))
433
434 def test_exceptions(self):
435 self.assertRaises(ftplib.error_temp, self.client.sendcmd, 'echo 400')
436 self.assertRaises(ftplib.error_temp, self.client.sendcmd, 'echo 499')
437 self.assertRaises(ftplib.error_perm, self.client.sendcmd, 'echo 500')
438 self.assertRaises(ftplib.error_perm, self.client.sendcmd, 'echo 599')
439 self.assertRaises(ftplib.error_proto, self.client.sendcmd, 'echo 999')
440
441 def test_all_errors(self):
442 exceptions = (ftplib.error_reply, ftplib.error_temp, ftplib.error_perm,
443 ftplib.error_proto, ftplib.Error, IOError, EOFError)
444 for x in exceptions:
445 try:
446 raise x('exception not included in all_errors set')
447 except ftplib.all_errors:
448 pass
449
450 def test_set_pasv(self):
451 # passive mode is supposed to be enabled by default
452 self.assertTrue(self.client.passiveserver)
453 self.client.set_pasv(True)
454 self.assertTrue(self.client.passiveserver)
455 self.client.set_pasv(False)
456 self.assertFalse(self.client.passiveserver)
457
458 def test_voidcmd(self):
459 self.client.voidcmd('echo 200')
460 self.client.voidcmd('echo 299')
461 self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 199')
462 self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 300')
463
464 def test_login(self):
465 self.client.login()
466
467 def test_acct(self):
468 self.client.acct('passwd')
469
470 def test_rename(self):
471 self.client.rename('a', 'b')
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000472 self.server.handler_instance.next_response = '200'
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000473 self.assertRaises(ftplib.error_reply, self.client.rename, 'a', 'b')
474
475 def test_delete(self):
476 self.client.delete('foo')
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000477 self.server.handler_instance.next_response = '199'
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000478 self.assertRaises(ftplib.error_reply, self.client.delete, 'foo')
479
480 def test_size(self):
481 self.client.size('foo')
482
483 def test_mkd(self):
484 dir = self.client.mkd('/foo')
485 self.assertEqual(dir, '/foo')
486
487 def test_rmd(self):
488 self.client.rmd('foo')
489
490 def test_pwd(self):
491 dir = self.client.pwd()
492 self.assertEqual(dir, 'pwd ok')
493
494 def test_quit(self):
495 self.assertEqual(self.client.quit(), '221 quit ok')
496 # Ensure the connection gets closed; sock attribute should be None
497 self.assertEqual(self.client.sock, None)
498
Giampaolo Rodola'0b5c21f2011-05-07 19:03:47 +0200499 def test_abort(self):
500 self.client.abort()
501
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000502 def test_retrbinary(self):
503 def callback(data):
504 received.append(data.decode('ascii'))
505 received = []
506 self.client.retrbinary('retr', callback)
507 self.assertEqual(''.join(received), RETR_DATA)
508
Antoine Pitrou648bcd72009-11-27 13:23:26 +0000509 def test_retrbinary_rest(self):
510 def callback(data):
511 received.append(data.decode('ascii'))
512 for rest in (0, 10, 20):
513 received = []
514 self.client.retrbinary('retr', callback, rest=rest)
515 self.assertEqual(''.join(received), RETR_DATA[rest:],
516 msg='rest test case %d %d %d' % (rest,
517 len(''.join(received)),
518 len(RETR_DATA[rest:])))
519
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000520 def test_retrlines(self):
521 received = []
522 self.client.retrlines('retr', received.append)
Giampaolo Rodolàf96482e2010-08-04 10:36:18 +0000523 self.assertEqual(''.join(received), RETR_DATA.replace('\r\n', ''))
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000524
525 def test_storbinary(self):
526 f = io.BytesIO(RETR_DATA.encode('ascii'))
527 self.client.storbinary('stor', f)
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000528 self.assertEqual(self.server.handler_instance.last_received_data, RETR_DATA)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000529 # test new callback arg
530 flag = []
531 f.seek(0)
532 self.client.storbinary('stor', f, callback=lambda x: flag.append(None))
533 self.assertTrue(flag)
534
Antoine Pitrou648bcd72009-11-27 13:23:26 +0000535 def test_storbinary_rest(self):
536 f = io.BytesIO(RETR_DATA.replace('\r\n', '\n').encode('ascii'))
537 for r in (30, '30'):
538 f.seek(0)
539 self.client.storbinary('stor', f, rest=r)
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000540 self.assertEqual(self.server.handler_instance.rest, str(r))
Antoine Pitrou648bcd72009-11-27 13:23:26 +0000541
Giampaolo Rodolàf96482e2010-08-04 10:36:18 +0000542 def test_storlines(self):
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000543 f = io.BytesIO(RETR_DATA.replace('\r\n', '\n').encode('ascii'))
544 self.client.storlines('stor', f)
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000545 self.assertEqual(self.server.handler_instance.last_received_data, RETR_DATA)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000546 # test new callback arg
547 flag = []
548 f.seek(0)
549 self.client.storlines('stor foo', f, callback=lambda x: flag.append(None))
550 self.assertTrue(flag)
551
552 def test_nlst(self):
553 self.client.nlst()
554 self.assertEqual(self.client.nlst(), NLST_DATA.split('\r\n')[:-1])
555
556 def test_dir(self):
557 l = []
558 self.client.dir(lambda x: l.append(x))
559 self.assertEqual(''.join(l), LIST_DATA.replace('\r\n', ''))
560
Benjamin Peterson3a53fbb2008-09-27 22:04:16 +0000561 def test_makeport(self):
Brett Cannon918e2d42010-10-29 23:26:25 +0000562 with self.client.makeport():
563 # IPv4 is in use, just make sure send_eprt has not been used
564 self.assertEqual(self.server.handler_instance.last_received_cmd,
565 'port')
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000566
567 def test_makepasv(self):
568 host, port = self.client.makepasv()
Antoine Pitroud778e562010-10-14 20:35:26 +0000569 conn = socket.create_connection((host, port), 10)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000570 conn.close()
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000571 # IPv4 is in use, just make sure send_epsv has not been used
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000572 self.assertEqual(self.server.handler_instance.last_received_cmd, 'pasv')
573
574 def test_with_statement(self):
575 self.client.quit()
576
577 def is_client_connected():
578 if self.client.sock is None:
579 return False
580 try:
581 self.client.sendcmd('noop')
582 except (socket.error, EOFError):
583 return False
584 return True
585
586 # base test
Antoine Pitroud778e562010-10-14 20:35:26 +0000587 with ftplib.FTP(timeout=10) as self.client:
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000588 self.client.connect(self.server.host, self.server.port)
589 self.client.sendcmd('noop')
590 self.assertTrue(is_client_connected())
591 self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit')
592 self.assertFalse(is_client_connected())
593
594 # QUIT sent inside the with block
Antoine Pitroud778e562010-10-14 20:35:26 +0000595 with ftplib.FTP(timeout=10) as self.client:
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000596 self.client.connect(self.server.host, self.server.port)
597 self.client.sendcmd('noop')
598 self.client.quit()
599 self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit')
600 self.assertFalse(is_client_connected())
601
602 # force a wrong response code to be sent on QUIT: error_perm
603 # is expected and the connection is supposed to be closed
604 try:
Antoine Pitroud778e562010-10-14 20:35:26 +0000605 with ftplib.FTP(timeout=10) as self.client:
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000606 self.client.connect(self.server.host, self.server.port)
607 self.client.sendcmd('noop')
608 self.server.handler_instance.next_response = '550 error on quit'
609 except ftplib.error_perm as err:
610 self.assertEqual(str(err), '550 error on quit')
611 else:
612 self.fail('Exception not raised')
613 # needed to give the threaded server some time to set the attribute
614 # which otherwise would still be == 'noop'
615 time.sleep(0.1)
616 self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit')
617 self.assertFalse(is_client_connected())
Guido van Rossumd8faa362007-04-27 19:54:29 +0000618
Giampaolo Rodolàbbc47822010-08-23 22:10:32 +0000619 def test_parse257(self):
620 self.assertEqual(ftplib.parse257('257 "/foo/bar"'), '/foo/bar')
621 self.assertEqual(ftplib.parse257('257 "/foo/bar" created'), '/foo/bar')
622 self.assertEqual(ftplib.parse257('257 ""'), '')
623 self.assertEqual(ftplib.parse257('257 "" created'), '')
624 self.assertRaises(ftplib.error_reply, ftplib.parse257, '250 "/foo/bar"')
625 # The 257 response is supposed to include the directory
626 # name and in case it contains embedded double-quotes
627 # they must be doubled (see RFC-959, chapter 7, appendix 2).
628 self.assertEqual(ftplib.parse257('257 "/foo/b""ar"'), '/foo/b"ar')
629 self.assertEqual(ftplib.parse257('257 "/foo/b""ar" created'), '/foo/b"ar')
630
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000631
632class TestIPv6Environment(TestCase):
633
634 def setUp(self):
635 self.server = DummyFTPServer((HOST, 0), af=socket.AF_INET6)
636 self.server.start()
637 self.client = ftplib.FTP()
638 self.client.connect(self.server.host, self.server.port)
639
640 def tearDown(self):
641 self.client.close()
642 self.server.stop()
643
644 def test_af(self):
645 self.assertEqual(self.client.af, socket.AF_INET6)
646
647 def test_makeport(self):
Brett Cannon918e2d42010-10-29 23:26:25 +0000648 with self.client.makeport():
649 self.assertEqual(self.server.handler_instance.last_received_cmd,
650 'eprt')
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000651
652 def test_makepasv(self):
653 host, port = self.client.makepasv()
Antoine Pitroud778e562010-10-14 20:35:26 +0000654 conn = socket.create_connection((host, port), 10)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000655 conn.close()
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000656 self.assertEqual(self.server.handler_instance.last_received_cmd, 'epsv')
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000657
658 def test_transfer(self):
659 def retr():
660 def callback(data):
661 received.append(data.decode('ascii'))
662 received = []
663 self.client.retrbinary('retr', callback)
664 self.assertEqual(''.join(received), RETR_DATA)
665 self.client.set_pasv(True)
666 retr()
667 self.client.set_pasv(False)
668 retr()
669
670
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000671class TestTLS_FTPClassMixin(TestFTPClass):
672 """Repeat TestFTPClass tests starting the TLS layer for both control
673 and data connections first.
674 """
675
676 def setUp(self):
677 self.server = DummyTLS_FTPServer((HOST, 0))
678 self.server.start()
Antoine Pitroud778e562010-10-14 20:35:26 +0000679 self.client = ftplib.FTP_TLS(timeout=10)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000680 self.client.connect(self.server.host, self.server.port)
681 # enable TLS
682 self.client.auth()
683 self.client.prot_p()
684
685
686class TestTLS_FTPClass(TestCase):
687 """Specific TLS_FTP class tests."""
688
689 def setUp(self):
690 self.server = DummyTLS_FTPServer((HOST, 0))
691 self.server.start()
Antoine Pitroud778e562010-10-14 20:35:26 +0000692 self.client = ftplib.FTP_TLS(timeout=10)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000693 self.client.connect(self.server.host, self.server.port)
694
695 def tearDown(self):
696 self.client.close()
697 self.server.stop()
698
699 def test_control_connection(self):
Ezio Melottie9615932010-01-24 19:26:24 +0000700 self.assertNotIsInstance(self.client.sock, ssl.SSLSocket)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000701 self.client.auth()
Ezio Melottie9615932010-01-24 19:26:24 +0000702 self.assertIsInstance(self.client.sock, ssl.SSLSocket)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000703
704 def test_data_connection(self):
705 # clear text
Brett Cannon918e2d42010-10-29 23:26:25 +0000706 with self.client.transfercmd('list') as sock:
707 self.assertNotIsInstance(sock, ssl.SSLSocket)
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000708 self.assertEqual(self.client.voidresp(), "226 transfer complete")
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000709
710 # secured, after PROT P
711 self.client.prot_p()
Brett Cannon918e2d42010-10-29 23:26:25 +0000712 with self.client.transfercmd('list') as sock:
713 self.assertIsInstance(sock, ssl.SSLSocket)
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000714 self.assertEqual(self.client.voidresp(), "226 transfer complete")
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000715
716 # PROT C is issued, the connection must be in cleartext again
717 self.client.prot_c()
Brett Cannon918e2d42010-10-29 23:26:25 +0000718 with self.client.transfercmd('list') as sock:
719 self.assertNotIsInstance(sock, ssl.SSLSocket)
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000720 self.assertEqual(self.client.voidresp(), "226 transfer complete")
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000721
722 def test_login(self):
723 # login() is supposed to implicitly secure the control connection
Ezio Melottie9615932010-01-24 19:26:24 +0000724 self.assertNotIsInstance(self.client.sock, ssl.SSLSocket)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000725 self.client.login()
Ezio Melottie9615932010-01-24 19:26:24 +0000726 self.assertIsInstance(self.client.sock, ssl.SSLSocket)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000727 # make sure that AUTH TLS doesn't get issued again
728 self.client.login()
729
730 def test_auth_issued_twice(self):
731 self.client.auth()
732 self.assertRaises(ValueError, self.client.auth)
733
734 def test_auth_ssl(self):
735 try:
736 self.client.ssl_version = ssl.PROTOCOL_SSLv3
737 self.client.auth()
738 self.assertRaises(ValueError, self.client.auth)
739 finally:
740 self.client.ssl_version = ssl.PROTOCOL_TLSv1
741
Giampaolo Rodolàa67299e2010-05-26 18:06:04 +0000742 def test_context(self):
743 self.client.quit()
744 ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
745 self.assertRaises(ValueError, ftplib.FTP_TLS, keyfile=CERTFILE,
746 context=ctx)
747 self.assertRaises(ValueError, ftplib.FTP_TLS, certfile=CERTFILE,
748 context=ctx)
749 self.assertRaises(ValueError, ftplib.FTP_TLS, certfile=CERTFILE,
750 keyfile=CERTFILE, context=ctx)
751
Antoine Pitroud778e562010-10-14 20:35:26 +0000752 self.client = ftplib.FTP_TLS(context=ctx, timeout=10)
Giampaolo Rodolàa67299e2010-05-26 18:06:04 +0000753 self.client.connect(self.server.host, self.server.port)
754 self.assertNotIsInstance(self.client.sock, ssl.SSLSocket)
755 self.client.auth()
756 self.assertIs(self.client.sock.context, ctx)
757 self.assertIsInstance(self.client.sock, ssl.SSLSocket)
758
759 self.client.prot_p()
Brett Cannon918e2d42010-10-29 23:26:25 +0000760 with self.client.transfercmd('list') as sock:
761 self.assertIs(sock.context, ctx)
762 self.assertIsInstance(sock, ssl.SSLSocket)
Giampaolo Rodolàa67299e2010-05-26 18:06:04 +0000763
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000764
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000765class TestTimeouts(TestCase):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000766
767 def setUp(self):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000768 self.evt = threading.Event()
Christian Heimes5e696852008-04-09 08:37:03 +0000769 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Charles-François Natali4ce2f362011-12-19 16:12:23 +0100770 self.sock.settimeout(10)
Benjamin Petersonee8712c2008-05-20 21:35:26 +0000771 self.port = support.bind_port(self.sock)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000772 threading.Thread(target=self.server, args=(self.evt,self.sock)).start()
Christian Heimes836baa52008-02-26 08:18:30 +0000773 # Wait for the server to be ready.
774 self.evt.wait()
775 self.evt.clear()
Christian Heimes5e696852008-04-09 08:37:03 +0000776 ftplib.FTP.port = self.port
Guido van Rossumd8faa362007-04-27 19:54:29 +0000777
778 def tearDown(self):
779 self.evt.wait()
Brett Cannon918e2d42010-10-29 23:26:25 +0000780 self.sock.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000781
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000782 def server(self, evt, serv):
783 # This method sets the evt 3 times:
784 # 1) when the connection is ready to be accepted.
785 # 2) when it is safe for the caller to close the connection
786 # 3) when we have closed the socket
787 serv.listen(5)
788 # (1) Signal the caller that we are ready to accept the connection.
789 evt.set()
790 try:
791 conn, addr = serv.accept()
792 except socket.timeout:
793 pass
794 else:
795 conn.send(b"1 Hola mundo\n")
796 # (2) Signal the caller that it is safe to close the socket.
797 evt.set()
798 conn.close()
799 finally:
800 serv.close()
801 # (3) Signal the caller that we are done.
802 evt.set()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000803
804 def testTimeoutDefault(self):
Georg Brandlf78e02b2008-06-10 17:40:04 +0000805 # default -- use global socket timeout
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000806 self.assertTrue(socket.getdefaulttimeout() is None)
Georg Brandlf78e02b2008-06-10 17:40:04 +0000807 socket.setdefaulttimeout(30)
808 try:
809 ftp = ftplib.FTP("localhost")
810 finally:
811 socket.setdefaulttimeout(None)
812 self.assertEqual(ftp.sock.gettimeout(), 30)
813 self.evt.wait()
814 ftp.close()
815
816 def testTimeoutNone(self):
817 # no timeout -- do not use global socket timeout
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000818 self.assertTrue(socket.getdefaulttimeout() is None)
Georg Brandlf78e02b2008-06-10 17:40:04 +0000819 socket.setdefaulttimeout(30)
820 try:
821 ftp = ftplib.FTP("localhost", timeout=None)
822 finally:
823 socket.setdefaulttimeout(None)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000824 self.assertTrue(ftp.sock.gettimeout() is None)
Christian Heimes836baa52008-02-26 08:18:30 +0000825 self.evt.wait()
Georg Brandlf78e02b2008-06-10 17:40:04 +0000826 ftp.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000827
828 def testTimeoutValue(self):
829 # a value
Christian Heimes5e696852008-04-09 08:37:03 +0000830 ftp = ftplib.FTP(HOST, timeout=30)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000831 self.assertEqual(ftp.sock.gettimeout(), 30)
Christian Heimes836baa52008-02-26 08:18:30 +0000832 self.evt.wait()
Georg Brandlf78e02b2008-06-10 17:40:04 +0000833 ftp.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000834
835 def testTimeoutConnect(self):
836 ftp = ftplib.FTP()
Christian Heimes5e696852008-04-09 08:37:03 +0000837 ftp.connect(HOST, timeout=30)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000838 self.assertEqual(ftp.sock.gettimeout(), 30)
Christian Heimes836baa52008-02-26 08:18:30 +0000839 self.evt.wait()
Georg Brandlf78e02b2008-06-10 17:40:04 +0000840 ftp.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000841
842 def testTimeoutDifferentOrder(self):
843 ftp = ftplib.FTP(timeout=30)
Christian Heimes5e696852008-04-09 08:37:03 +0000844 ftp.connect(HOST)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000845 self.assertEqual(ftp.sock.gettimeout(), 30)
Christian Heimes836baa52008-02-26 08:18:30 +0000846 self.evt.wait()
Georg Brandlf78e02b2008-06-10 17:40:04 +0000847 ftp.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000848
849 def testTimeoutDirectAccess(self):
850 ftp = ftplib.FTP()
851 ftp.timeout = 30
Christian Heimes5e696852008-04-09 08:37:03 +0000852 ftp.connect(HOST)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000853 self.assertEqual(ftp.sock.gettimeout(), 30)
Christian Heimes836baa52008-02-26 08:18:30 +0000854 self.evt.wait()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000855 ftp.close()
856
857
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000858def test_main():
859 tests = [TestFTPClass, TestTimeouts]
860 if socket.has_ipv6:
861 try:
862 DummyFTPServer((HOST, 0), af=socket.AF_INET6)
863 except socket.error:
864 pass
865 else:
866 tests.append(TestIPv6Environment)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000867
868 if ssl is not None:
869 tests.extend([TestTLS_FTPClassMixin, TestTLS_FTPClass])
870
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000871 thread_info = support.threading_setup()
872 try:
873 support.run_unittest(*tests)
874 finally:
875 support.threading_cleanup(*thread_info)
876
Guido van Rossumd8faa362007-04-27 19:54:29 +0000877
878if __name__ == '__main__':
879 test_main()