blob: ce9a2d3829c3309e32cb79aa7c4300cbf07eec28 [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
Giampaolo Rodolàb9392352010-08-04 10:12:00 +000027RETR_TEXT = 'abcd\xe912345\r\n' * 1000
Benjamin Petersonbe17a112008-09-27 21:49:47 +000028LIST_DATA = 'foo\r\nbar\r\n'
29NLST_DATA = 'foo\r\nbar\r\n'
Christian Heimes836baa52008-02-26 08:18:30 +000030
Christian Heimes836baa52008-02-26 08:18:30 +000031
Benjamin Petersonbe17a112008-09-27 21:49:47 +000032class DummyDTPHandler(asynchat.async_chat):
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +000033 dtp_conn_closed = False
Benjamin Petersonbe17a112008-09-27 21:49:47 +000034
35 def __init__(self, conn, baseclass):
36 asynchat.async_chat.__init__(self, conn)
37 self.baseclass = baseclass
38 self.baseclass.last_received_data = ''
39
40 def handle_read(self):
Giampaolo Rodolàb9392352010-08-04 10:12:00 +000041 self.baseclass.last_received_data += self.recv(1024).decode('latin-1')
Benjamin Petersonbe17a112008-09-27 21:49:47 +000042
43 def handle_close(self):
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +000044 # XXX: this method can be called many times in a row for a single
45 # connection, including in clear-text (non-TLS) mode.
46 # (behaviour witnessed with test_data_connection)
47 if not self.dtp_conn_closed:
48 self.baseclass.push('226 transfer complete')
49 self.close()
50 self.dtp_conn_closed = True
Benjamin Petersonbe17a112008-09-27 21:49:47 +000051
52 def push(self, what):
Giampaolo Rodolàb9392352010-08-04 10:12:00 +000053 super(DummyDTPHandler, self).push(what.encode('latin-1'))
Benjamin Petersonbe17a112008-09-27 21:49:47 +000054
Giampaolo Rodolàd930b632010-05-06 20:21:57 +000055 def handle_error(self):
56 raise
57
Benjamin Petersonbe17a112008-09-27 21:49:47 +000058
59class DummyFTPHandler(asynchat.async_chat):
60
Antoine Pitrouf988cd02009-11-17 20:21:14 +000061 dtp_handler = DummyDTPHandler
62
Benjamin Petersonbe17a112008-09-27 21:49:47 +000063 def __init__(self, conn):
64 asynchat.async_chat.__init__(self, conn)
65 self.set_terminator(b"\r\n")
66 self.in_buffer = []
67 self.dtp = None
68 self.last_received_cmd = None
69 self.last_received_data = ''
70 self.next_response = ''
Antoine Pitrou648bcd72009-11-27 13:23:26 +000071 self.rest = None
Giampaolo Rodolàb9392352010-08-04 10:12:00 +000072 self.current_type = 'a'
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]
107 s = socket.create_connection((ip, port), timeout=2)
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):
112 sock = socket.socket()
113 sock.bind((self.socket.getsockname()[0], 0))
114 sock.listen(5)
115 sock.settimeout(2)
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()
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000120 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)
125 s = socket.create_connection((ip, port), timeout=2)
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):
130 sock = socket.socket(socket.AF_INET6)
131 sock.bind((self.socket.getsockname()[0], 0))
132 sock.listen(5)
133 sock.settimeout(2)
134 port = sock.getsockname()[1]
135 self.push('229 entering extended passive mode (|||%d|)' %port)
136 conn, addr = sock.accept()
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000137 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àb9392352010-08-04 10:12:00 +0000180 # ASCII type
181 if arg.lower() == 'a':
182 self.current_type = 'a'
183 self.push('200 type ok')
184 # Binary type
185 elif arg.lower() == 'i':
186 self.current_type = 'i'
187 self.push('200 type ok')
188 else:
189 self.push('504 unsupported type')
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000190
191 def cmd_quit(self, arg):
192 self.push('221 quit ok')
193 self.close()
194
195 def cmd_stor(self, arg):
196 self.push('125 stor ok')
197
Antoine Pitrou648bcd72009-11-27 13:23:26 +0000198 def cmd_rest(self, arg):
199 self.rest = arg
200 self.push('350 rest ok')
201
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000202 def cmd_retr(self, arg):
203 self.push('125 retr ok')
Antoine Pitrou648bcd72009-11-27 13:23:26 +0000204 if self.rest is not None:
205 offset = int(self.rest)
206 else:
207 offset = 0
Giampaolo Rodolàb9392352010-08-04 10:12:00 +0000208 if self.current_type == 'i':
209 self.dtp.push(RETR_DATA[offset:])
210 else:
211 self.dtp.push(RETR_TEXT[offset:])
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000212 self.dtp.close_when_done()
Antoine Pitrou648bcd72009-11-27 13:23:26 +0000213 self.rest = None
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000214
215 def cmd_list(self, arg):
216 self.push('125 list ok')
217 self.dtp.push(LIST_DATA)
218 self.dtp.close_when_done()
219
220 def cmd_nlst(self, arg):
221 self.push('125 nlst ok')
222 self.dtp.push(NLST_DATA)
223 self.dtp.close_when_done()
224
225
226class DummyFTPServer(asyncore.dispatcher, threading.Thread):
227
228 handler = DummyFTPHandler
229
230 def __init__(self, address, af=socket.AF_INET):
231 threading.Thread.__init__(self)
232 asyncore.dispatcher.__init__(self)
233 self.create_socket(af, socket.SOCK_STREAM)
234 self.bind(address)
235 self.listen(5)
236 self.active = False
237 self.active_lock = threading.Lock()
238 self.host, self.port = self.socket.getsockname()[:2]
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000239 self.handler_instance = None
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000240
241 def start(self):
242 assert not self.active
243 self.__flag = threading.Event()
244 threading.Thread.start(self)
245 self.__flag.wait()
246
247 def run(self):
248 self.active = True
249 self.__flag.set()
250 while self.active and asyncore.socket_map:
251 self.active_lock.acquire()
252 asyncore.loop(timeout=0.1, count=1)
253 self.active_lock.release()
254 asyncore.close_all(ignore_all=True)
255
256 def stop(self):
257 assert self.active
258 self.active = False
259 self.join()
260
261 def handle_accept(self):
262 conn, addr = self.accept()
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000263 self.handler_instance = self.handler(conn)
Benjamin Petersond06e3b02008-09-28 21:00:42 +0000264
265 def handle_connect(self):
266 self.close()
267 handle_read = handle_connect
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000268
269 def writable(self):
270 return 0
271
272 def handle_error(self):
273 raise
274
275
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000276if ssl is not None:
277
278 CERTFILE = os.path.join(os.path.dirname(__file__), "keycert.pem")
279
280 class SSLConnection(asyncore.dispatcher):
281 """An asyncore.dispatcher subclass supporting TLS/SSL."""
282
283 _ssl_accepting = False
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000284 _ssl_closing = False
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000285
286 def secure_connection(self):
287 self.del_channel()
288 socket = ssl.wrap_socket(self.socket, suppress_ragged_eofs=False,
289 certfile=CERTFILE, server_side=True,
290 do_handshake_on_connect=False,
291 ssl_version=ssl.PROTOCOL_SSLv23)
292 self.set_socket(socket)
293 self._ssl_accepting = True
294
295 def _do_ssl_handshake(self):
296 try:
297 self.socket.do_handshake()
298 except ssl.SSLError as err:
299 if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
300 ssl.SSL_ERROR_WANT_WRITE):
301 return
302 elif err.args[0] == ssl.SSL_ERROR_EOF:
303 return self.handle_close()
304 raise
305 except socket.error as err:
306 if err.args[0] == errno.ECONNABORTED:
307 return self.handle_close()
308 else:
309 self._ssl_accepting = False
310
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000311 def _do_ssl_shutdown(self):
312 self._ssl_closing = True
313 try:
314 self.socket = self.socket.unwrap()
315 except ssl.SSLError as err:
316 if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
317 ssl.SSL_ERROR_WANT_WRITE):
318 return
319 except socket.error as err:
320 # Any "socket error" corresponds to a SSL_ERROR_SYSCALL return
321 # from OpenSSL's SSL_shutdown(), corresponding to a
322 # closed socket condition. See also:
323 # http://www.mail-archive.com/openssl-users@openssl.org/msg60710.html
324 pass
325 self._ssl_closing = False
326 super(SSLConnection, self).close()
327
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000328 def handle_read_event(self):
329 if self._ssl_accepting:
330 self._do_ssl_handshake()
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000331 elif self._ssl_closing:
332 self._do_ssl_shutdown()
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000333 else:
334 super(SSLConnection, self).handle_read_event()
335
336 def handle_write_event(self):
337 if self._ssl_accepting:
338 self._do_ssl_handshake()
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000339 elif self._ssl_closing:
340 self._do_ssl_shutdown()
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000341 else:
342 super(SSLConnection, self).handle_write_event()
343
344 def send(self, data):
345 try:
346 return super(SSLConnection, self).send(data)
347 except ssl.SSLError as err:
Antoine Pitrou5733c082010-03-22 14:49:10 +0000348 if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN,
349 ssl.SSL_ERROR_WANT_READ,
350 ssl.SSL_ERROR_WANT_WRITE):
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000351 return 0
352 raise
353
354 def recv(self, buffer_size):
355 try:
356 return super(SSLConnection, self).recv(buffer_size)
357 except ssl.SSLError as err:
Antoine Pitrou5733c082010-03-22 14:49:10 +0000358 if err.args[0] in (ssl.SSL_ERROR_WANT_READ,
359 ssl.SSL_ERROR_WANT_WRITE):
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000360 return b''
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000361 if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN):
362 self.handle_close()
363 return b''
364 raise
365
366 def handle_error(self):
367 raise
368
369 def close(self):
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000370 if (isinstance(self.socket, ssl.SSLSocket) and
371 self.socket._sslobj is not None):
372 self._do_ssl_shutdown()
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000373
374
375 class DummyTLS_DTPHandler(SSLConnection, DummyDTPHandler):
376 """A DummyDTPHandler subclass supporting TLS/SSL."""
377
378 def __init__(self, conn, baseclass):
379 DummyDTPHandler.__init__(self, conn, baseclass)
380 if self.baseclass.secure_data_channel:
381 self.secure_connection()
382
383
384 class DummyTLS_FTPHandler(SSLConnection, DummyFTPHandler):
385 """A DummyFTPHandler subclass supporting TLS/SSL."""
386
387 dtp_handler = DummyTLS_DTPHandler
388
389 def __init__(self, conn):
390 DummyFTPHandler.__init__(self, conn)
391 self.secure_data_channel = False
392
393 def cmd_auth(self, line):
394 """Set up secure control channel."""
395 self.push('234 AUTH TLS successful')
396 self.secure_connection()
397
398 def cmd_pbsz(self, line):
399 """Negotiate size of buffer for secure data transfer.
400 For TLS/SSL the only valid value for the parameter is '0'.
401 Any other value is accepted but ignored.
402 """
403 self.push('200 PBSZ=0 successful.')
404
405 def cmd_prot(self, line):
406 """Setup un/secure data channel."""
407 arg = line.upper()
408 if arg == 'C':
409 self.push('200 Protection set to Clear')
410 self.secure_data_channel = False
411 elif arg == 'P':
412 self.push('200 Protection set to Private')
413 self.secure_data_channel = True
414 else:
415 self.push("502 Unrecognized PROT type (use C or P).")
416
417
418 class DummyTLS_FTPServer(DummyFTPServer):
419 handler = DummyTLS_FTPHandler
420
421
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000422class TestFTPClass(TestCase):
423
424 def setUp(self):
425 self.server = DummyFTPServer((HOST, 0))
426 self.server.start()
427 self.client = ftplib.FTP(timeout=2)
428 self.client.connect(self.server.host, self.server.port)
429
430 def tearDown(self):
431 self.client.close()
432 self.server.stop()
433
434 def test_getwelcome(self):
435 self.assertEqual(self.client.getwelcome(), '220 welcome')
436
437 def test_sanitize(self):
438 self.assertEqual(self.client.sanitize('foo'), repr('foo'))
439 self.assertEqual(self.client.sanitize('pass 12345'), repr('pass *****'))
440 self.assertEqual(self.client.sanitize('PASS 12345'), repr('PASS *****'))
441
442 def test_exceptions(self):
443 self.assertRaises(ftplib.error_temp, self.client.sendcmd, 'echo 400')
444 self.assertRaises(ftplib.error_temp, self.client.sendcmd, 'echo 499')
445 self.assertRaises(ftplib.error_perm, self.client.sendcmd, 'echo 500')
446 self.assertRaises(ftplib.error_perm, self.client.sendcmd, 'echo 599')
447 self.assertRaises(ftplib.error_proto, self.client.sendcmd, 'echo 999')
448
449 def test_all_errors(self):
450 exceptions = (ftplib.error_reply, ftplib.error_temp, ftplib.error_perm,
451 ftplib.error_proto, ftplib.Error, IOError, EOFError)
452 for x in exceptions:
453 try:
454 raise x('exception not included in all_errors set')
455 except ftplib.all_errors:
456 pass
457
458 def test_set_pasv(self):
459 # passive mode is supposed to be enabled by default
460 self.assertTrue(self.client.passiveserver)
461 self.client.set_pasv(True)
462 self.assertTrue(self.client.passiveserver)
463 self.client.set_pasv(False)
464 self.assertFalse(self.client.passiveserver)
465
466 def test_voidcmd(self):
467 self.client.voidcmd('echo 200')
468 self.client.voidcmd('echo 299')
469 self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 199')
470 self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 300')
471
472 def test_login(self):
473 self.client.login()
474
475 def test_acct(self):
476 self.client.acct('passwd')
477
478 def test_rename(self):
479 self.client.rename('a', 'b')
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000480 self.server.handler_instance.next_response = '200'
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000481 self.assertRaises(ftplib.error_reply, self.client.rename, 'a', 'b')
482
483 def test_delete(self):
484 self.client.delete('foo')
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000485 self.server.handler_instance.next_response = '199'
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000486 self.assertRaises(ftplib.error_reply, self.client.delete, 'foo')
487
488 def test_size(self):
489 self.client.size('foo')
490
491 def test_mkd(self):
492 dir = self.client.mkd('/foo')
493 self.assertEqual(dir, '/foo')
494
495 def test_rmd(self):
496 self.client.rmd('foo')
497
498 def test_pwd(self):
499 dir = self.client.pwd()
500 self.assertEqual(dir, 'pwd ok')
501
502 def test_quit(self):
503 self.assertEqual(self.client.quit(), '221 quit ok')
504 # Ensure the connection gets closed; sock attribute should be None
505 self.assertEqual(self.client.sock, None)
506
507 def test_retrbinary(self):
508 def callback(data):
509 received.append(data.decode('ascii'))
510 received = []
511 self.client.retrbinary('retr', callback)
512 self.assertEqual(''.join(received), RETR_DATA)
513
Antoine Pitrou648bcd72009-11-27 13:23:26 +0000514 def test_retrbinary_rest(self):
515 def callback(data):
516 received.append(data.decode('ascii'))
517 for rest in (0, 10, 20):
518 received = []
519 self.client.retrbinary('retr', callback, rest=rest)
520 self.assertEqual(''.join(received), RETR_DATA[rest:],
521 msg='rest test case %d %d %d' % (rest,
522 len(''.join(received)),
523 len(RETR_DATA[rest:])))
524
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000525 def test_retrlines(self):
526 received = []
527 self.client.retrlines('retr', received.append)
Giampaolo Rodolàb9392352010-08-04 10:12:00 +0000528 self.assertEqual(''.join(received), RETR_TEXT.replace('\r\n', ''))
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000529
530 def test_storbinary(self):
531 f = io.BytesIO(RETR_DATA.encode('ascii'))
532 self.client.storbinary('stor', f)
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000533 self.assertEqual(self.server.handler_instance.last_received_data, RETR_DATA)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000534 # test new callback arg
535 flag = []
536 f.seek(0)
537 self.client.storbinary('stor', f, callback=lambda x: flag.append(None))
538 self.assertTrue(flag)
539
Antoine Pitrou648bcd72009-11-27 13:23:26 +0000540 def test_storbinary_rest(self):
541 f = io.BytesIO(RETR_DATA.replace('\r\n', '\n').encode('ascii'))
542 for r in (30, '30'):
543 f.seek(0)
544 self.client.storbinary('stor', f, rest=r)
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000545 self.assertEqual(self.server.handler_instance.rest, str(r))
Antoine Pitrou648bcd72009-11-27 13:23:26 +0000546
Giampaolo Rodolàb9392352010-08-04 10:12:00 +0000547 def test_storlines_bytes(self):
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000548 f = io.BytesIO(RETR_DATA.replace('\r\n', '\n').encode('ascii'))
549 self.client.storlines('stor', f)
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000550 self.assertEqual(self.server.handler_instance.last_received_data, RETR_DATA)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000551 # test new callback arg
552 flag = []
553 f.seek(0)
554 self.client.storlines('stor foo', f, callback=lambda x: flag.append(None))
555 self.assertTrue(flag)
556
Giampaolo Rodolàb9392352010-08-04 10:12:00 +0000557 def test_storlines_str(self):
558 f = io.StringIO(RETR_TEXT.replace('\r\n', '\n'))
559 self.client.storlines('stor', f)
560 self.assertEqual(self.server.handler_instance.last_received_data, RETR_TEXT)
561 # test new callback arg
562 flag = []
563 f.seek(0)
564 self.client.storlines('stor foo', f, callback=lambda x: flag.append(None))
565 self.assertTrue(flag)
566
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000567 def test_nlst(self):
568 self.client.nlst()
569 self.assertEqual(self.client.nlst(), NLST_DATA.split('\r\n')[:-1])
570
571 def test_dir(self):
572 l = []
573 self.client.dir(lambda x: l.append(x))
574 self.assertEqual(''.join(l), LIST_DATA.replace('\r\n', ''))
575
Benjamin Peterson3a53fbb2008-09-27 22:04:16 +0000576 def test_makeport(self):
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000577 self.client.makeport()
578 # IPv4 is in use, just make sure send_eprt has not been used
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000579 self.assertEqual(self.server.handler_instance.last_received_cmd, 'port')
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000580
581 def test_makepasv(self):
582 host, port = self.client.makepasv()
583 conn = socket.create_connection((host, port), 2)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000584 conn.close()
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000585 # IPv4 is in use, just make sure send_epsv has not been used
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000586 self.assertEqual(self.server.handler_instance.last_received_cmd, 'pasv')
587
588 def test_with_statement(self):
589 self.client.quit()
590
591 def is_client_connected():
592 if self.client.sock is None:
593 return False
594 try:
595 self.client.sendcmd('noop')
596 except (socket.error, EOFError):
597 return False
598 return True
599
600 # base test
601 with ftplib.FTP(timeout=2) as self.client:
602 self.client.connect(self.server.host, self.server.port)
603 self.client.sendcmd('noop')
604 self.assertTrue(is_client_connected())
605 self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit')
606 self.assertFalse(is_client_connected())
607
608 # QUIT sent inside the with block
609 with ftplib.FTP(timeout=2) as self.client:
610 self.client.connect(self.server.host, self.server.port)
611 self.client.sendcmd('noop')
612 self.client.quit()
613 self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit')
614 self.assertFalse(is_client_connected())
615
616 # force a wrong response code to be sent on QUIT: error_perm
617 # is expected and the connection is supposed to be closed
618 try:
619 with ftplib.FTP(timeout=2) as self.client:
620 self.client.connect(self.server.host, self.server.port)
621 self.client.sendcmd('noop')
622 self.server.handler_instance.next_response = '550 error on quit'
623 except ftplib.error_perm as err:
624 self.assertEqual(str(err), '550 error on quit')
625 else:
626 self.fail('Exception not raised')
627 # needed to give the threaded server some time to set the attribute
628 # which otherwise would still be == 'noop'
629 time.sleep(0.1)
630 self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit')
631 self.assertFalse(is_client_connected())
Guido van Rossumd8faa362007-04-27 19:54:29 +0000632
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000633
634class TestIPv6Environment(TestCase):
635
636 def setUp(self):
637 self.server = DummyFTPServer((HOST, 0), af=socket.AF_INET6)
638 self.server.start()
639 self.client = ftplib.FTP()
640 self.client.connect(self.server.host, self.server.port)
641
642 def tearDown(self):
643 self.client.close()
644 self.server.stop()
645
646 def test_af(self):
647 self.assertEqual(self.client.af, socket.AF_INET6)
648
649 def test_makeport(self):
650 self.client.makeport()
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000651 self.assertEqual(self.server.handler_instance.last_received_cmd, 'eprt')
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000652
653 def test_makepasv(self):
654 host, port = self.client.makepasv()
655 conn = socket.create_connection((host, port), 2)
656 conn.close()
Giampaolo Rodolàbd576b72010-05-10 14:53:29 +0000657 self.assertEqual(self.server.handler_instance.last_received_cmd, 'epsv')
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000658
659 def test_transfer(self):
660 def retr():
661 def callback(data):
662 received.append(data.decode('ascii'))
663 received = []
664 self.client.retrbinary('retr', callback)
665 self.assertEqual(''.join(received), RETR_DATA)
666 self.client.set_pasv(True)
667 retr()
668 self.client.set_pasv(False)
669 retr()
670
671
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000672class TestTLS_FTPClassMixin(TestFTPClass):
673 """Repeat TestFTPClass tests starting the TLS layer for both control
674 and data connections first.
675 """
676
677 def setUp(self):
678 self.server = DummyTLS_FTPServer((HOST, 0))
679 self.server.start()
680 self.client = ftplib.FTP_TLS(timeout=2)
681 self.client.connect(self.server.host, self.server.port)
682 # enable TLS
683 self.client.auth()
684 self.client.prot_p()
685
686
687class TestTLS_FTPClass(TestCase):
688 """Specific TLS_FTP class tests."""
689
690 def setUp(self):
691 self.server = DummyTLS_FTPServer((HOST, 0))
692 self.server.start()
693 self.client = ftplib.FTP_TLS(timeout=2)
694 self.client.connect(self.server.host, self.server.port)
695
696 def tearDown(self):
697 self.client.close()
698 self.server.stop()
699
700 def test_control_connection(self):
Ezio Melottie9615932010-01-24 19:26:24 +0000701 self.assertNotIsInstance(self.client.sock, ssl.SSLSocket)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000702 self.client.auth()
Ezio Melottie9615932010-01-24 19:26:24 +0000703 self.assertIsInstance(self.client.sock, ssl.SSLSocket)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000704
705 def test_data_connection(self):
706 # clear text
707 sock = self.client.transfercmd('list')
Ezio Melottie9615932010-01-24 19:26:24 +0000708 self.assertNotIsInstance(sock, ssl.SSLSocket)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000709 sock.close()
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000710 self.assertEqual(self.client.voidresp(), "226 transfer complete")
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000711
712 # secured, after PROT P
713 self.client.prot_p()
714 sock = self.client.transfercmd('list')
Ezio Melottie9615932010-01-24 19:26:24 +0000715 self.assertIsInstance(sock, ssl.SSLSocket)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000716 sock.close()
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000717 self.assertEqual(self.client.voidresp(), "226 transfer complete")
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000718
719 # PROT C is issued, the connection must be in cleartext again
720 self.client.prot_c()
721 sock = self.client.transfercmd('list')
Ezio Melottie9615932010-01-24 19:26:24 +0000722 self.assertNotIsInstance(sock, ssl.SSLSocket)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000723 sock.close()
Antoine Pitrou2c4f98b2010-04-23 00:16:21 +0000724 self.assertEqual(self.client.voidresp(), "226 transfer complete")
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000725
726 def test_login(self):
727 # login() is supposed to implicitly secure the control connection
Ezio Melottie9615932010-01-24 19:26:24 +0000728 self.assertNotIsInstance(self.client.sock, ssl.SSLSocket)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000729 self.client.login()
Ezio Melottie9615932010-01-24 19:26:24 +0000730 self.assertIsInstance(self.client.sock, ssl.SSLSocket)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000731 # make sure that AUTH TLS doesn't get issued again
732 self.client.login()
733
734 def test_auth_issued_twice(self):
735 self.client.auth()
736 self.assertRaises(ValueError, self.client.auth)
737
738 def test_auth_ssl(self):
739 try:
740 self.client.ssl_version = ssl.PROTOCOL_SSLv3
741 self.client.auth()
742 self.assertRaises(ValueError, self.client.auth)
743 finally:
744 self.client.ssl_version = ssl.PROTOCOL_TLSv1
745
Giampaolo Rodolàa67299e2010-05-26 18:06:04 +0000746 def test_context(self):
747 self.client.quit()
748 ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
749 self.assertRaises(ValueError, ftplib.FTP_TLS, keyfile=CERTFILE,
750 context=ctx)
751 self.assertRaises(ValueError, ftplib.FTP_TLS, certfile=CERTFILE,
752 context=ctx)
753 self.assertRaises(ValueError, ftplib.FTP_TLS, certfile=CERTFILE,
754 keyfile=CERTFILE, context=ctx)
755
756 self.client = ftplib.FTP_TLS(context=ctx, timeout=2)
757 self.client.connect(self.server.host, self.server.port)
758 self.assertNotIsInstance(self.client.sock, ssl.SSLSocket)
759 self.client.auth()
760 self.assertIs(self.client.sock.context, ctx)
761 self.assertIsInstance(self.client.sock, ssl.SSLSocket)
762
763 self.client.prot_p()
764 sock = self.client.transfercmd('list')
Giampaolo Rodolà6da11e52010-05-26 18:21:26 +0000765 self.assertIs(sock.context, ctx)
Giampaolo Rodolàa67299e2010-05-26 18:06:04 +0000766 self.assertIsInstance(sock, ssl.SSLSocket)
767 sock.close()
768
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000769
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000770class TestTimeouts(TestCase):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000771
772 def setUp(self):
Guido van Rossumd8faa362007-04-27 19:54:29 +0000773 self.evt = threading.Event()
Christian Heimes5e696852008-04-09 08:37:03 +0000774 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
775 self.sock.settimeout(3)
Benjamin Petersonee8712c2008-05-20 21:35:26 +0000776 self.port = support.bind_port(self.sock)
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000777 threading.Thread(target=self.server, args=(self.evt,self.sock)).start()
Christian Heimes836baa52008-02-26 08:18:30 +0000778 # Wait for the server to be ready.
779 self.evt.wait()
780 self.evt.clear()
Christian Heimes5e696852008-04-09 08:37:03 +0000781 ftplib.FTP.port = self.port
Guido van Rossumd8faa362007-04-27 19:54:29 +0000782
783 def tearDown(self):
784 self.evt.wait()
785
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000786 def server(self, evt, serv):
787 # This method sets the evt 3 times:
788 # 1) when the connection is ready to be accepted.
789 # 2) when it is safe for the caller to close the connection
790 # 3) when we have closed the socket
791 serv.listen(5)
792 # (1) Signal the caller that we are ready to accept the connection.
793 evt.set()
794 try:
795 conn, addr = serv.accept()
796 except socket.timeout:
797 pass
798 else:
799 conn.send(b"1 Hola mundo\n")
800 # (2) Signal the caller that it is safe to close the socket.
801 evt.set()
802 conn.close()
803 finally:
804 serv.close()
805 # (3) Signal the caller that we are done.
806 evt.set()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000807
808 def testTimeoutDefault(self):
Georg Brandlf78e02b2008-06-10 17:40:04 +0000809 # default -- use global socket timeout
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000810 self.assertTrue(socket.getdefaulttimeout() is None)
Georg Brandlf78e02b2008-06-10 17:40:04 +0000811 socket.setdefaulttimeout(30)
812 try:
813 ftp = ftplib.FTP("localhost")
814 finally:
815 socket.setdefaulttimeout(None)
816 self.assertEqual(ftp.sock.gettimeout(), 30)
817 self.evt.wait()
818 ftp.close()
819
820 def testTimeoutNone(self):
821 # no timeout -- do not use global socket timeout
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000822 self.assertTrue(socket.getdefaulttimeout() is None)
Georg Brandlf78e02b2008-06-10 17:40:04 +0000823 socket.setdefaulttimeout(30)
824 try:
825 ftp = ftplib.FTP("localhost", timeout=None)
826 finally:
827 socket.setdefaulttimeout(None)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000828 self.assertTrue(ftp.sock.gettimeout() is None)
Christian Heimes836baa52008-02-26 08:18:30 +0000829 self.evt.wait()
Georg Brandlf78e02b2008-06-10 17:40:04 +0000830 ftp.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000831
832 def testTimeoutValue(self):
833 # a value
Christian Heimes5e696852008-04-09 08:37:03 +0000834 ftp = ftplib.FTP(HOST, timeout=30)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000835 self.assertEqual(ftp.sock.gettimeout(), 30)
Christian Heimes836baa52008-02-26 08:18:30 +0000836 self.evt.wait()
Georg Brandlf78e02b2008-06-10 17:40:04 +0000837 ftp.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000838
839 def testTimeoutConnect(self):
840 ftp = ftplib.FTP()
Christian Heimes5e696852008-04-09 08:37:03 +0000841 ftp.connect(HOST, timeout=30)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000842 self.assertEqual(ftp.sock.gettimeout(), 30)
Christian Heimes836baa52008-02-26 08:18:30 +0000843 self.evt.wait()
Georg Brandlf78e02b2008-06-10 17:40:04 +0000844 ftp.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000845
846 def testTimeoutDifferentOrder(self):
847 ftp = ftplib.FTP(timeout=30)
Christian Heimes5e696852008-04-09 08:37:03 +0000848 ftp.connect(HOST)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000849 self.assertEqual(ftp.sock.gettimeout(), 30)
Christian Heimes836baa52008-02-26 08:18:30 +0000850 self.evt.wait()
Georg Brandlf78e02b2008-06-10 17:40:04 +0000851 ftp.close()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000852
853 def testTimeoutDirectAccess(self):
854 ftp = ftplib.FTP()
855 ftp.timeout = 30
Christian Heimes5e696852008-04-09 08:37:03 +0000856 ftp.connect(HOST)
Guido van Rossumd8faa362007-04-27 19:54:29 +0000857 self.assertEqual(ftp.sock.gettimeout(), 30)
Christian Heimes836baa52008-02-26 08:18:30 +0000858 self.evt.wait()
Guido van Rossumd8faa362007-04-27 19:54:29 +0000859 ftp.close()
860
861
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000862def test_main():
863 tests = [TestFTPClass, TestTimeouts]
864 if socket.has_ipv6:
865 try:
866 DummyFTPServer((HOST, 0), af=socket.AF_INET6)
867 except socket.error:
868 pass
869 else:
870 tests.append(TestIPv6Environment)
Antoine Pitrouf988cd02009-11-17 20:21:14 +0000871
872 if ssl is not None:
873 tests.extend([TestTLS_FTPClassMixin, TestTLS_FTPClass])
874
Benjamin Petersonbe17a112008-09-27 21:49:47 +0000875 thread_info = support.threading_setup()
876 try:
877 support.run_unittest(*tests)
878 finally:
879 support.threading_cleanup(*thread_info)
880
Guido van Rossumd8faa362007-04-27 19:54:29 +0000881
882if __name__ == '__main__':
883 test_main()