1 """Test script for ftplib module."""
3 # Modified by Giampaolo Rodola' to test FTP class, IPv6 and TLS
18 from unittest
import TestCase
19 from test
import test_support
20 from test
.test_support
import HOST
21 threading
= test_support
.import_module('threading')
24 # the dummy data returned by server over the data channel when
25 # RETR, LIST and NLST commands are issued
26 RETR_DATA
= 'abcde12345\r\n' * 1000
27 LIST_DATA
= 'foo\r\nbar\r\n'
28 NLST_DATA
= 'foo\r\nbar\r\n'
31 class DummyDTPHandler(asynchat
.async_chat
):
32 dtp_conn_closed
= False
34 def __init__(self
, conn
, baseclass
):
35 asynchat
.async_chat
.__init
__(self
, conn
)
36 self
.baseclass
= baseclass
37 self
.baseclass
.last_received_data
= ''
39 def handle_read(self
):
40 self
.baseclass
.last_received_data
+= self
.recv(1024)
42 def handle_close(self
):
43 # 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')
49 self
.dtp_conn_closed
= True
51 def handle_error(self
):
55 class DummyFTPHandler(asynchat
.async_chat
):
57 dtp_handler
= DummyDTPHandler
59 def __init__(self
, conn
):
60 asynchat
.async_chat
.__init
__(self
, conn
)
61 self
.set_terminator("\r\n")
64 self
.last_received_cmd
= None
65 self
.last_received_data
= ''
66 self
.next_response
= ''
68 self
.push('220 welcome')
70 def collect_incoming_data(self
, data
):
71 self
.in_buffer
.append(data
)
73 def found_terminator(self
):
74 line
= ''.join(self
.in_buffer
)
76 if self
.next_response
:
77 self
.push(self
.next_response
)
78 self
.next_response
= ''
79 cmd
= line
.split(' ')[0].lower()
80 self
.last_received_cmd
= cmd
81 space
= line
.find(' ')
83 arg
= line
[space
+ 1:]
86 if hasattr(self
, 'cmd_' + cmd
):
87 method
= getattr(self
, 'cmd_' + cmd
)
90 self
.push('550 command "%s" not understood.' %cmd
)
92 def handle_error(self
):
96 asynchat
.async_chat
.push(self
, data
+ '\r\n')
98 def cmd_port(self
, arg
):
99 addr
= map(int, arg
.split(','))
100 ip
= '%d.%d.%d.%d' %tuple(addr
[:4])
101 port
= (addr
[4] * 256) + addr
[5]
102 s
= socket
.create_connection((ip
, port
), timeout
=2)
103 self
.dtp
= self
.dtp_handler(s
, baseclass
=self
)
104 self
.push('200 active data connection established')
106 def cmd_pasv(self
, arg
):
107 sock
= socket
.socket()
108 sock
.bind((self
.socket
.getsockname()[0], 0))
111 ip
, port
= sock
.getsockname()[:2]
112 ip
= ip
.replace('.', ',')
113 p1
, p2
= divmod(port
, 256)
114 self
.push('227 entering passive mode (%s,%d,%d)' %(ip
, p1
, p2
))
115 conn
, addr
= sock
.accept()
116 self
.dtp
= self
.dtp_handler(conn
, baseclass
=self
)
118 def cmd_eprt(self
, arg
):
119 af
, ip
, port
= arg
.split(arg
[0])[1:-1]
121 s
= socket
.create_connection((ip
, port
), timeout
=2)
122 self
.dtp
= self
.dtp_handler(s
, baseclass
=self
)
123 self
.push('200 active data connection established')
125 def cmd_epsv(self
, arg
):
126 sock
= socket
.socket(socket
.AF_INET6
)
127 sock
.bind((self
.socket
.getsockname()[0], 0))
130 port
= sock
.getsockname()[1]
131 self
.push('229 entering extended passive mode (|||%d|)' %port
)
132 conn
, addr
= sock
.accept()
133 self
.dtp
= self
.dtp_handler(conn
, baseclass
=self
)
135 def cmd_echo(self
, arg
):
136 # sends back the received string (used by the test suite)
139 def cmd_user(self
, arg
):
140 self
.push('331 username ok')
142 def cmd_pass(self
, arg
):
143 self
.push('230 password ok')
145 def cmd_acct(self
, arg
):
146 self
.push('230 acct ok')
148 def cmd_rnfr(self
, arg
):
149 self
.push('350 rnfr ok')
151 def cmd_rnto(self
, arg
):
152 self
.push('250 rnto ok')
154 def cmd_dele(self
, arg
):
155 self
.push('250 dele ok')
157 def cmd_cwd(self
, arg
):
158 self
.push('250 cwd ok')
160 def cmd_size(self
, arg
):
161 self
.push('250 1000')
163 def cmd_mkd(self
, arg
):
164 self
.push('257 "%s"' %arg
)
166 def cmd_rmd(self
, arg
):
167 self
.push('250 rmd ok')
169 def cmd_pwd(self
, arg
):
170 self
.push('257 "pwd ok"')
172 def cmd_type(self
, arg
):
173 self
.push('200 type ok')
175 def cmd_quit(self
, arg
):
176 self
.push('221 quit ok')
179 def cmd_stor(self
, arg
):
180 self
.push('125 stor ok')
182 def cmd_rest(self
, arg
):
184 self
.push('350 rest ok')
186 def cmd_retr(self
, arg
):
187 self
.push('125 retr ok')
188 if self
.rest
is not None:
189 offset
= int(self
.rest
)
192 self
.dtp
.push(RETR_DATA
[offset
:])
193 self
.dtp
.close_when_done()
196 def cmd_list(self
, arg
):
197 self
.push('125 list ok')
198 self
.dtp
.push(LIST_DATA
)
199 self
.dtp
.close_when_done()
201 def cmd_nlst(self
, arg
):
202 self
.push('125 nlst ok')
203 self
.dtp
.push(NLST_DATA
)
204 self
.dtp
.close_when_done()
207 class DummyFTPServer(asyncore
.dispatcher
, threading
.Thread
):
209 handler
= DummyFTPHandler
211 def __init__(self
, address
, af
=socket
.AF_INET
):
212 threading
.Thread
.__init
__(self
)
213 asyncore
.dispatcher
.__init
__(self
)
214 self
.create_socket(af
, socket
.SOCK_STREAM
)
218 self
.active_lock
= threading
.Lock()
219 self
.host
, self
.port
= self
.socket
.getsockname()[:2]
222 assert not self
.active
223 self
.__flag
= threading
.Event()
224 threading
.Thread
.start(self
)
230 while self
.active
and asyncore
.socket_map
:
231 self
.active_lock
.acquire()
232 asyncore
.loop(timeout
=0.1, count
=1)
233 self
.active_lock
.release()
234 asyncore
.close_all(ignore_all
=True)
241 def handle_accept(self
):
242 conn
, addr
= self
.accept()
243 self
.handler
= self
.handler(conn
)
246 def handle_connect(self
):
248 handle_read
= handle_connect
253 def handle_error(self
):
259 CERTFILE
= os
.path
.join(os
.path
.dirname(__file__
), "keycert.pem")
261 class SSLConnection(object, asyncore
.dispatcher
):
262 """An asyncore.dispatcher subclass supporting TLS/SSL."""
264 _ssl_accepting
= False
267 def secure_connection(self
):
268 self
.socket
= ssl
.wrap_socket(self
.socket
, suppress_ragged_eofs
=False,
269 certfile
=CERTFILE
, server_side
=True,
270 do_handshake_on_connect
=False,
271 ssl_version
=ssl
.PROTOCOL_SSLv23
)
272 self
._ssl
_accepting
= True
274 def _do_ssl_handshake(self
):
276 self
.socket
.do_handshake()
277 except ssl
.SSLError
, err
:
278 if err
.args
[0] in (ssl
.SSL_ERROR_WANT_READ
,
279 ssl
.SSL_ERROR_WANT_WRITE
):
281 elif err
.args
[0] == ssl
.SSL_ERROR_EOF
:
282 return self
.handle_close()
284 except socket
.error
, err
:
285 if err
.args
[0] == errno
.ECONNABORTED
:
286 return self
.handle_close()
288 self
._ssl
_accepting
= False
290 def _do_ssl_shutdown(self
):
291 self
._ssl
_closing
= True
293 self
.socket
= self
.socket
.unwrap()
294 except ssl
.SSLError
, err
:
295 if err
.args
[0] in (ssl
.SSL_ERROR_WANT_READ
,
296 ssl
.SSL_ERROR_WANT_WRITE
):
298 except socket
.error
, err
:
299 # Any "socket error" corresponds to a SSL_ERROR_SYSCALL return
300 # from OpenSSL's SSL_shutdown(), corresponding to a
301 # closed socket condition. See also:
302 # http://www.mail-archive.com/openssl-users@openssl.org/msg60710.html
304 self
._ssl
_closing
= False
305 super(SSLConnection
, self
).close()
307 def handle_read_event(self
):
308 if self
._ssl
_accepting
:
309 self
._do
_ssl
_handshake
()
310 elif self
._ssl
_closing
:
311 self
._do
_ssl
_shutdown
()
313 super(SSLConnection
, self
).handle_read_event()
315 def handle_write_event(self
):
316 if self
._ssl
_accepting
:
317 self
._do
_ssl
_handshake
()
318 elif self
._ssl
_closing
:
319 self
._do
_ssl
_shutdown
()
321 super(SSLConnection
, self
).handle_write_event()
323 def send(self
, data
):
325 return super(SSLConnection
, self
).send(data
)
326 except ssl
.SSLError
, err
:
327 if err
.args
[0] in (ssl
.SSL_ERROR_EOF
, ssl
.SSL_ERROR_ZERO_RETURN
,
328 ssl
.SSL_ERROR_WANT_READ
,
329 ssl
.SSL_ERROR_WANT_WRITE
):
333 def recv(self
, buffer_size
):
335 return super(SSLConnection
, self
).recv(buffer_size
)
336 except ssl
.SSLError
, err
:
337 if err
.args
[0] in (ssl
.SSL_ERROR_WANT_READ
,
338 ssl
.SSL_ERROR_WANT_WRITE
):
340 if err
.args
[0] in (ssl
.SSL_ERROR_EOF
, ssl
.SSL_ERROR_ZERO_RETURN
):
345 def handle_error(self
):
349 if (isinstance(self
.socket
, ssl
.SSLSocket
) and
350 self
.socket
._sslobj
is not None):
351 self
._do
_ssl
_shutdown
()
354 class DummyTLS_DTPHandler(SSLConnection
, DummyDTPHandler
):
355 """A DummyDTPHandler subclass supporting TLS/SSL."""
357 def __init__(self
, conn
, baseclass
):
358 DummyDTPHandler
.__init
__(self
, conn
, baseclass
)
359 if self
.baseclass
.secure_data_channel
:
360 self
.secure_connection()
363 class DummyTLS_FTPHandler(SSLConnection
, DummyFTPHandler
):
364 """A DummyFTPHandler subclass supporting TLS/SSL."""
366 dtp_handler
= DummyTLS_DTPHandler
368 def __init__(self
, conn
):
369 DummyFTPHandler
.__init
__(self
, conn
)
370 self
.secure_data_channel
= False
372 def cmd_auth(self
, line
):
373 """Set up secure control channel."""
374 self
.push('234 AUTH TLS successful')
375 self
.secure_connection()
377 def cmd_pbsz(self
, line
):
378 """Negotiate size of buffer for secure data transfer.
379 For TLS/SSL the only valid value for the parameter is '0'.
380 Any other value is accepted but ignored.
382 self
.push('200 PBSZ=0 successful.')
384 def cmd_prot(self
, line
):
385 """Setup un/secure data channel."""
388 self
.push('200 Protection set to Clear')
389 self
.secure_data_channel
= False
391 self
.push('200 Protection set to Private')
392 self
.secure_data_channel
= True
394 self
.push("502 Unrecognized PROT type (use C or P).")
397 class DummyTLS_FTPServer(DummyFTPServer
):
398 handler
= DummyTLS_FTPHandler
401 class TestFTPClass(TestCase
):
404 self
.server
= DummyFTPServer((HOST
, 0))
406 self
.client
= ftplib
.FTP(timeout
=2)
407 self
.client
.connect(self
.server
.host
, self
.server
.port
)
413 def test_getwelcome(self
):
414 self
.assertEqual(self
.client
.getwelcome(), '220 welcome')
416 def test_sanitize(self
):
417 self
.assertEqual(self
.client
.sanitize('foo'), repr('foo'))
418 self
.assertEqual(self
.client
.sanitize('pass 12345'), repr('pass *****'))
419 self
.assertEqual(self
.client
.sanitize('PASS 12345'), repr('PASS *****'))
421 def test_exceptions(self
):
422 self
.assertRaises(ftplib
.error_temp
, self
.client
.sendcmd
, 'echo 400')
423 self
.assertRaises(ftplib
.error_temp
, self
.client
.sendcmd
, 'echo 499')
424 self
.assertRaises(ftplib
.error_perm
, self
.client
.sendcmd
, 'echo 500')
425 self
.assertRaises(ftplib
.error_perm
, self
.client
.sendcmd
, 'echo 599')
426 self
.assertRaises(ftplib
.error_proto
, self
.client
.sendcmd
, 'echo 999')
428 def test_all_errors(self
):
429 exceptions
= (ftplib
.error_reply
, ftplib
.error_temp
, ftplib
.error_perm
,
430 ftplib
.error_proto
, ftplib
.Error
, IOError, EOFError)
433 raise x('exception not included in all_errors set')
434 except ftplib
.all_errors
:
437 def test_set_pasv(self
):
438 # passive mode is supposed to be enabled by default
439 self
.assertTrue(self
.client
.passiveserver
)
440 self
.client
.set_pasv(True)
441 self
.assertTrue(self
.client
.passiveserver
)
442 self
.client
.set_pasv(False)
443 self
.assertFalse(self
.client
.passiveserver
)
445 def test_voidcmd(self
):
446 self
.client
.voidcmd('echo 200')
447 self
.client
.voidcmd('echo 299')
448 self
.assertRaises(ftplib
.error_reply
, self
.client
.voidcmd
, 'echo 199')
449 self
.assertRaises(ftplib
.error_reply
, self
.client
.voidcmd
, 'echo 300')
451 def test_login(self
):
455 self
.client
.acct('passwd')
457 def test_rename(self
):
458 self
.client
.rename('a', 'b')
459 self
.server
.handler
.next_response
= '200'
460 self
.assertRaises(ftplib
.error_reply
, self
.client
.rename
, 'a', 'b')
462 def test_delete(self
):
463 self
.client
.delete('foo')
464 self
.server
.handler
.next_response
= '199'
465 self
.assertRaises(ftplib
.error_reply
, self
.client
.delete
, 'foo')
468 self
.client
.size('foo')
471 dir = self
.client
.mkd('/foo')
472 self
.assertEqual(dir, '/foo')
475 self
.client
.rmd('foo')
478 dir = self
.client
.pwd()
479 self
.assertEqual(dir, 'pwd ok')
482 self
.assertEqual(self
.client
.quit(), '221 quit ok')
483 # Ensure the connection gets closed; sock attribute should be None
484 self
.assertEqual(self
.client
.sock
, None)
486 def test_retrbinary(self
):
488 self
.client
.retrbinary('retr', received
.append
)
489 self
.assertEqual(''.join(received
), RETR_DATA
)
491 def test_retrbinary_rest(self
):
492 for rest
in (0, 10, 20):
494 self
.client
.retrbinary('retr', received
.append
, rest
=rest
)
495 self
.assertEqual(''.join(received
), RETR_DATA
[rest
:],
496 msg
='rest test case %d %d %d' % (rest
,
497 len(''.join(received
)),
498 len(RETR_DATA
[rest
:])))
500 def test_retrlines(self
):
502 self
.client
.retrlines('retr', received
.append
)
503 self
.assertEqual(''.join(received
), RETR_DATA
.replace('\r\n', ''))
505 def test_storbinary(self
):
506 f
= StringIO
.StringIO(RETR_DATA
)
507 self
.client
.storbinary('stor', f
)
508 self
.assertEqual(self
.server
.handler
.last_received_data
, RETR_DATA
)
509 # test new callback arg
512 self
.client
.storbinary('stor', f
, callback
=lambda x
: flag
.append(None))
513 self
.assertTrue(flag
)
515 def test_storbinary_rest(self
):
516 f
= StringIO
.StringIO(RETR_DATA
)
519 self
.client
.storbinary('stor', f
, rest
=r
)
520 self
.assertEqual(self
.server
.handler
.rest
, str(r
))
522 def test_storlines(self
):
523 f
= StringIO
.StringIO(RETR_DATA
.replace('\r\n', '\n'))
524 self
.client
.storlines('stor', f
)
525 self
.assertEqual(self
.server
.handler
.last_received_data
, RETR_DATA
)
526 # test new callback arg
529 self
.client
.storlines('stor foo', f
, callback
=lambda x
: flag
.append(None))
530 self
.assertTrue(flag
)
534 self
.assertEqual(self
.client
.nlst(), NLST_DATA
.split('\r\n')[:-1])
538 self
.client
.dir(lambda x
: l
.append(x
))
539 self
.assertEqual(''.join(l
), LIST_DATA
.replace('\r\n', ''))
541 def test_makeport(self
):
542 self
.client
.makeport()
543 # IPv4 is in use, just make sure send_eprt has not been used
544 self
.assertEqual(self
.server
.handler
.last_received_cmd
, 'port')
546 def test_makepasv(self
):
547 host
, port
= self
.client
.makepasv()
548 conn
= socket
.create_connection((host
, port
), 2)
550 # IPv4 is in use, just make sure send_epsv has not been used
551 self
.assertEqual(self
.server
.handler
.last_received_cmd
, 'pasv')
554 class TestIPv6Environment(TestCase
):
557 self
.server
= DummyFTPServer((HOST
, 0), af
=socket
.AF_INET6
)
559 self
.client
= ftplib
.FTP()
560 self
.client
.connect(self
.server
.host
, self
.server
.port
)
567 self
.assertEqual(self
.client
.af
, socket
.AF_INET6
)
569 def test_makeport(self
):
570 self
.client
.makeport()
571 self
.assertEqual(self
.server
.handler
.last_received_cmd
, 'eprt')
573 def test_makepasv(self
):
574 host
, port
= self
.client
.makepasv()
575 conn
= socket
.create_connection((host
, port
), 2)
577 self
.assertEqual(self
.server
.handler
.last_received_cmd
, 'epsv')
579 def test_transfer(self
):
582 self
.client
.retrbinary('retr', received
.append
)
583 self
.assertEqual(''.join(received
), RETR_DATA
)
584 self
.client
.set_pasv(True)
586 self
.client
.set_pasv(False)
590 class TestTLS_FTPClassMixin(TestFTPClass
):
591 """Repeat TestFTPClass tests starting the TLS layer for both control
592 and data connections first.
596 self
.server
= DummyTLS_FTPServer((HOST
, 0))
598 self
.client
= ftplib
.FTP_TLS(timeout
=2)
599 self
.client
.connect(self
.server
.host
, self
.server
.port
)
605 class TestTLS_FTPClass(TestCase
):
606 """Specific TLS_FTP class tests."""
609 self
.server
= DummyTLS_FTPServer((HOST
, 0))
611 self
.client
= ftplib
.FTP_TLS(timeout
=2)
612 self
.client
.connect(self
.server
.host
, self
.server
.port
)
618 def test_control_connection(self
):
619 self
.assertNotIsInstance(self
.client
.sock
, ssl
.SSLSocket
)
621 self
.assertIsInstance(self
.client
.sock
, ssl
.SSLSocket
)
623 def test_data_connection(self
):
625 sock
= self
.client
.transfercmd('list')
626 self
.assertNotIsInstance(sock
, ssl
.SSLSocket
)
628 self
.assertEqual(self
.client
.voidresp(), "226 transfer complete")
630 # secured, after PROT P
632 sock
= self
.client
.transfercmd('list')
633 self
.assertIsInstance(sock
, ssl
.SSLSocket
)
635 self
.assertEqual(self
.client
.voidresp(), "226 transfer complete")
637 # PROT C is issued, the connection must be in cleartext again
639 sock
= self
.client
.transfercmd('list')
640 self
.assertNotIsInstance(sock
, ssl
.SSLSocket
)
642 self
.assertEqual(self
.client
.voidresp(), "226 transfer complete")
644 def test_login(self
):
645 # login() is supposed to implicitly secure the control connection
646 self
.assertNotIsInstance(self
.client
.sock
, ssl
.SSLSocket
)
648 self
.assertIsInstance(self
.client
.sock
, ssl
.SSLSocket
)
649 # make sure that AUTH TLS doesn't get issued again
652 def test_auth_issued_twice(self
):
654 self
.assertRaises(ValueError, self
.client
.auth
)
656 def test_auth_ssl(self
):
658 self
.client
.ssl_version
= ssl
.PROTOCOL_SSLv3
660 self
.assertRaises(ValueError, self
.client
.auth
)
662 self
.client
.ssl_version
= ssl
.PROTOCOL_TLSv1
665 class TestTimeouts(TestCase
):
668 self
.evt
= threading
.Event()
669 self
.sock
= socket
.socket(socket
.AF_INET
, socket
.SOCK_STREAM
)
670 self
.sock
.settimeout(3)
671 self
.port
= test_support
.bind_port(self
.sock
)
672 threading
.Thread(target
=self
.server
, args
=(self
.evt
,self
.sock
)).start()
673 # Wait for the server to be ready.
676 ftplib
.FTP
.port
= self
.port
681 def server(self
, evt
, serv
):
682 # This method sets the evt 3 times:
683 # 1) when the connection is ready to be accepted.
684 # 2) when it is safe for the caller to close the connection
685 # 3) when we have closed the socket
687 # (1) Signal the caller that we are ready to accept the connection.
690 conn
, addr
= serv
.accept()
691 except socket
.timeout
:
694 conn
.send("1 Hola mundo\n")
695 # (2) Signal the caller that it is safe to close the socket.
700 # (3) Signal the caller that we are done.
703 def testTimeoutDefault(self
):
704 # default -- use global socket timeout
705 self
.assertTrue(socket
.getdefaulttimeout() is None)
706 socket
.setdefaulttimeout(30)
708 ftp
= ftplib
.FTP("localhost")
710 socket
.setdefaulttimeout(None)
711 self
.assertEqual(ftp
.sock
.gettimeout(), 30)
715 def testTimeoutNone(self
):
716 # no timeout -- do not use global socket timeout
717 self
.assertTrue(socket
.getdefaulttimeout() is None)
718 socket
.setdefaulttimeout(30)
720 ftp
= ftplib
.FTP("localhost", timeout
=None)
722 socket
.setdefaulttimeout(None)
723 self
.assertTrue(ftp
.sock
.gettimeout() is None)
727 def testTimeoutValue(self
):
729 ftp
= ftplib
.FTP(HOST
, timeout
=30)
730 self
.assertEqual(ftp
.sock
.gettimeout(), 30)
734 def testTimeoutConnect(self
):
736 ftp
.connect(HOST
, timeout
=30)
737 self
.assertEqual(ftp
.sock
.gettimeout(), 30)
741 def testTimeoutDifferentOrder(self
):
742 ftp
= ftplib
.FTP(timeout
=30)
744 self
.assertEqual(ftp
.sock
.gettimeout(), 30)
748 def testTimeoutDirectAccess(self
):
752 self
.assertEqual(ftp
.sock
.gettimeout(), 30)
758 tests
= [TestFTPClass
, TestTimeouts
]
761 DummyFTPServer((HOST
, 0), af
=socket
.AF_INET6
)
765 tests
.append(TestIPv6Environment
)
768 tests
.extend([TestTLS_FTPClassMixin
, TestTLS_FTPClass
])
770 thread_info
= test_support
.threading_setup()
772 test_support
.run_unittest(*tests
)
774 test_support
.threading_cleanup(*thread_info
)
777 if __name__
== '__main__':