Changeset 391 for python/trunk/Lib/test/test_ftplib.py
- Timestamp:
- Mar 19, 2014, 11:31:01 PM (11 years ago)
- Location:
- python/trunk
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
python/trunk
-
Property svn:mergeinfo
set to
/python/vendor/Python-2.7.6 merged eligible /python/vendor/current merged eligible
-
Property svn:mergeinfo
set to
-
python/trunk/Lib/test/test_ftplib.py
r2 r391 1 1 """Test script for ftplib module.""" 2 2 3 # Modified by Giampaolo Rodola' to test FTP class and IPv6 environment 3 # Modified by Giampaolo Rodola' to test FTP class, IPv6 and TLS 4 # environment 4 5 5 6 import ftplib 6 import threading7 7 import asyncore 8 8 import asynchat 9 9 import socket 10 10 import StringIO 11 import errno 12 import os 13 try: 14 import ssl 15 except ImportError: 16 ssl = None 11 17 12 18 from unittest import TestCase 13 19 from test import test_support 14 from test.test_support import HOST 20 from test.test_support import HOST, HOSTv6 21 threading = test_support.import_module('threading') 15 22 16 23 … … 23 30 24 31 class DummyDTPHandler(asynchat.async_chat): 32 dtp_conn_closed = False 25 33 26 34 def __init__(self, conn, baseclass): … … 33 41 34 42 def handle_close(self): 35 self.baseclass.push('226 transfer complete') 36 self.close() 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') 48 self.close() 49 self.dtp_conn_closed = True 50 51 def handle_error(self): 52 raise 37 53 38 54 39 55 class DummyFTPHandler(asynchat.async_chat): 56 57 dtp_handler = DummyDTPHandler 40 58 41 59 def __init__(self, conn): … … 47 65 self.last_received_data = '' 48 66 self.next_response = '' 67 self.rest = None 68 self.next_retr_data = RETR_DATA 49 69 self.push('220 welcome') 50 70 … … 81 101 ip = '%d.%d.%d.%d' %tuple(addr[:4]) 82 102 port = (addr[4] * 256) + addr[5] 83 s = socket.create_connection((ip, port), timeout= 2)84 self.dtp = DummyDTPHandler(s, baseclass=self)103 s = socket.create_connection((ip, port), timeout=10) 104 self.dtp = self.dtp_handler(s, baseclass=self) 85 105 self.push('200 active data connection established') 86 106 … … 89 109 sock.bind((self.socket.getsockname()[0], 0)) 90 110 sock.listen(5) 91 sock.settimeout( 2)111 sock.settimeout(10) 92 112 ip, port = sock.getsockname()[:2] 93 ip = ip.replace('.', ','); p1 = port / 256; p2 = port % 256 113 ip = ip.replace('.', ',') 114 p1, p2 = divmod(port, 256) 94 115 self.push('227 entering passive mode (%s,%d,%d)' %(ip, p1, p2)) 95 116 conn, addr = sock.accept() 96 self.dtp = DummyDTPHandler(conn, baseclass=self)117 self.dtp = self.dtp_handler(conn, baseclass=self) 97 118 98 119 def cmd_eprt(self, arg): 99 120 af, ip, port = arg.split(arg[0])[1:-1] 100 121 port = int(port) 101 s = socket.create_connection((ip, port), timeout= 2)102 self.dtp = DummyDTPHandler(s, baseclass=self)122 s = socket.create_connection((ip, port), timeout=10) 123 self.dtp = self.dtp_handler(s, baseclass=self) 103 124 self.push('200 active data connection established') 104 125 … … 107 128 sock.bind((self.socket.getsockname()[0], 0)) 108 129 sock.listen(5) 109 sock.settimeout( 2)130 sock.settimeout(10) 110 131 port = sock.getsockname()[1] 111 132 self.push('229 entering extended passive mode (|||%d|)' %port) 112 133 conn, addr = sock.accept() 113 self.dtp = DummyDTPHandler(conn, baseclass=self)134 self.dtp = self.dtp_handler(conn, baseclass=self) 114 135 115 136 def cmd_echo(self, arg): … … 160 181 self.push('125 stor ok') 161 182 183 def cmd_rest(self, arg): 184 self.rest = arg 185 self.push('350 rest ok') 186 162 187 def cmd_retr(self, arg): 163 188 self.push('125 retr ok') 164 self.dtp.push(RETR_DATA) 189 if self.rest is not None: 190 offset = int(self.rest) 191 else: 192 offset = 0 193 self.dtp.push(self.next_retr_data[offset:]) 165 194 self.dtp.close_when_done() 195 self.rest = None 166 196 167 197 def cmd_list(self, arg): … … 174 204 self.dtp.push(NLST_DATA) 175 205 self.dtp.close_when_done() 206 207 def cmd_setlongretr(self, arg): 208 # For testing. Next RETR will return long line. 209 self.next_retr_data = 'x' * int(arg) 210 self.push('125 setlongretr ok') 176 211 177 212 … … 226 261 227 262 263 if ssl is not None: 264 265 CERTFILE = os.path.join(os.path.dirname(__file__), "keycert.pem") 266 267 class SSLConnection(object, asyncore.dispatcher): 268 """An asyncore.dispatcher subclass supporting TLS/SSL.""" 269 270 _ssl_accepting = False 271 _ssl_closing = False 272 273 def secure_connection(self): 274 self.socket = ssl.wrap_socket(self.socket, suppress_ragged_eofs=False, 275 certfile=CERTFILE, server_side=True, 276 do_handshake_on_connect=False, 277 ssl_version=ssl.PROTOCOL_SSLv23) 278 self._ssl_accepting = True 279 280 def _do_ssl_handshake(self): 281 try: 282 self.socket.do_handshake() 283 except ssl.SSLError, err: 284 if err.args[0] in (ssl.SSL_ERROR_WANT_READ, 285 ssl.SSL_ERROR_WANT_WRITE): 286 return 287 elif err.args[0] == ssl.SSL_ERROR_EOF: 288 return self.handle_close() 289 raise 290 except socket.error, err: 291 if err.args[0] == errno.ECONNABORTED: 292 return self.handle_close() 293 else: 294 self._ssl_accepting = False 295 296 def _do_ssl_shutdown(self): 297 self._ssl_closing = True 298 try: 299 self.socket = self.socket.unwrap() 300 except ssl.SSLError, err: 301 if err.args[0] in (ssl.SSL_ERROR_WANT_READ, 302 ssl.SSL_ERROR_WANT_WRITE): 303 return 304 except socket.error, err: 305 # Any "socket error" corresponds to a SSL_ERROR_SYSCALL return 306 # from OpenSSL's SSL_shutdown(), corresponding to a 307 # closed socket condition. See also: 308 # http://www.mail-archive.com/openssl-users@openssl.org/msg60710.html 309 pass 310 self._ssl_closing = False 311 super(SSLConnection, self).close() 312 313 def handle_read_event(self): 314 if self._ssl_accepting: 315 self._do_ssl_handshake() 316 elif self._ssl_closing: 317 self._do_ssl_shutdown() 318 else: 319 super(SSLConnection, self).handle_read_event() 320 321 def handle_write_event(self): 322 if self._ssl_accepting: 323 self._do_ssl_handshake() 324 elif self._ssl_closing: 325 self._do_ssl_shutdown() 326 else: 327 super(SSLConnection, self).handle_write_event() 328 329 def send(self, data): 330 try: 331 return super(SSLConnection, self).send(data) 332 except ssl.SSLError, err: 333 if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN, 334 ssl.SSL_ERROR_WANT_READ, 335 ssl.SSL_ERROR_WANT_WRITE): 336 return 0 337 raise 338 339 def recv(self, buffer_size): 340 try: 341 return super(SSLConnection, self).recv(buffer_size) 342 except ssl.SSLError, err: 343 if err.args[0] in (ssl.SSL_ERROR_WANT_READ, 344 ssl.SSL_ERROR_WANT_WRITE): 345 return '' 346 if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN): 347 self.handle_close() 348 return '' 349 raise 350 351 def handle_error(self): 352 raise 353 354 def close(self): 355 if (isinstance(self.socket, ssl.SSLSocket) and 356 self.socket._sslobj is not None): 357 self._do_ssl_shutdown() 358 359 360 class DummyTLS_DTPHandler(SSLConnection, DummyDTPHandler): 361 """A DummyDTPHandler subclass supporting TLS/SSL.""" 362 363 def __init__(self, conn, baseclass): 364 DummyDTPHandler.__init__(self, conn, baseclass) 365 if self.baseclass.secure_data_channel: 366 self.secure_connection() 367 368 369 class DummyTLS_FTPHandler(SSLConnection, DummyFTPHandler): 370 """A DummyFTPHandler subclass supporting TLS/SSL.""" 371 372 dtp_handler = DummyTLS_DTPHandler 373 374 def __init__(self, conn): 375 DummyFTPHandler.__init__(self, conn) 376 self.secure_data_channel = False 377 378 def cmd_auth(self, line): 379 """Set up secure control channel.""" 380 self.push('234 AUTH TLS successful') 381 self.secure_connection() 382 383 def cmd_pbsz(self, line): 384 """Negotiate size of buffer for secure data transfer. 385 For TLS/SSL the only valid value for the parameter is '0'. 386 Any other value is accepted but ignored. 387 """ 388 self.push('200 PBSZ=0 successful.') 389 390 def cmd_prot(self, line): 391 """Setup un/secure data channel.""" 392 arg = line.upper() 393 if arg == 'C': 394 self.push('200 Protection set to Clear') 395 self.secure_data_channel = False 396 elif arg == 'P': 397 self.push('200 Protection set to Private') 398 self.secure_data_channel = True 399 else: 400 self.push("502 Unrecognized PROT type (use C or P).") 401 402 403 class DummyTLS_FTPServer(DummyFTPServer): 404 handler = DummyTLS_FTPHandler 405 406 228 407 class TestFTPClass(TestCase): 229 408 … … 231 410 self.server = DummyFTPServer((HOST, 0)) 232 411 self.server.start() 233 self.client = ftplib.FTP(timeout= 2)412 self.client = ftplib.FTP(timeout=10) 234 413 self.client.connect(self.server.host, self.server.port) 235 414 … … 302 481 self.client.rmd('foo') 303 482 483 def test_cwd(self): 484 dir = self.client.cwd('/foo') 485 self.assertEqual(dir, '250 cwd ok') 486 487 def test_mkd(self): 488 dir = self.client.mkd('/foo') 489 self.assertEqual(dir, '/foo') 490 304 491 def test_pwd(self): 305 492 dir = self.client.pwd() … … 315 502 self.client.retrbinary('retr', received.append) 316 503 self.assertEqual(''.join(received), RETR_DATA) 504 505 def test_retrbinary_rest(self): 506 for rest in (0, 10, 20): 507 received = [] 508 self.client.retrbinary('retr', received.append, rest=rest) 509 self.assertEqual(''.join(received), RETR_DATA[rest:], 510 msg='rest test case %d %d %d' % (rest, 511 len(''.join(received)), 512 len(RETR_DATA[rest:]))) 317 513 318 514 def test_retrlines(self): … … 331 527 self.assertTrue(flag) 332 528 529 def test_storbinary_rest(self): 530 f = StringIO.StringIO(RETR_DATA) 531 for r in (30, '30'): 532 f.seek(0) 533 self.client.storbinary('stor', f, rest=r) 534 self.assertEqual(self.server.handler.rest, str(r)) 535 333 536 def test_storlines(self): 334 537 f = StringIO.StringIO(RETR_DATA.replace('\r\n', '\n')) … … 357 560 def test_makepasv(self): 358 561 host, port = self.client.makepasv() 359 conn = socket.create_connection((host, port), 2)562 conn = socket.create_connection((host, port), 10) 360 563 conn.close() 361 564 # IPv4 is in use, just make sure send_epsv has not been used 362 565 self.assertEqual(self.server.handler.last_received_cmd, 'pasv') 363 566 567 def test_line_too_long(self): 568 self.assertRaises(ftplib.Error, self.client.sendcmd, 569 'x' * self.client.maxline * 2) 570 571 def test_retrlines_too_long(self): 572 self.client.sendcmd('SETLONGRETR %d' % (self.client.maxline * 2)) 573 received = [] 574 self.assertRaises(ftplib.Error, 575 self.client.retrlines, 'retr', received.append) 576 577 def test_storlines_too_long(self): 578 f = StringIO.StringIO('x' * self.client.maxline * 2) 579 self.assertRaises(ftplib.Error, self.client.storlines, 'stor', f) 580 364 581 365 582 class TestIPv6Environment(TestCase): 366 583 367 584 def setUp(self): 368 self.server = DummyFTPServer((HOST , 0), af=socket.AF_INET6)585 self.server = DummyFTPServer((HOSTv6, 0), af=socket.AF_INET6) 369 586 self.server.start() 370 587 self.client = ftplib.FTP() … … 384 601 def test_makepasv(self): 385 602 host, port = self.client.makepasv() 386 conn = socket.create_connection((host, port), 2)603 conn = socket.create_connection((host, port), 10) 387 604 conn.close() 388 605 self.assertEqual(self.server.handler.last_received_cmd, 'epsv') … … 399 616 400 617 618 class TestTLS_FTPClassMixin(TestFTPClass): 619 """Repeat TestFTPClass tests starting the TLS layer for both control 620 and data connections first. 621 """ 622 623 def setUp(self): 624 self.server = DummyTLS_FTPServer((HOST, 0)) 625 self.server.start() 626 self.client = ftplib.FTP_TLS(timeout=10) 627 self.client.connect(self.server.host, self.server.port) 628 # enable TLS 629 self.client.auth() 630 self.client.prot_p() 631 632 633 class TestTLS_FTPClass(TestCase): 634 """Specific TLS_FTP class tests.""" 635 636 def setUp(self): 637 self.server = DummyTLS_FTPServer((HOST, 0)) 638 self.server.start() 639 self.client = ftplib.FTP_TLS(timeout=10) 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_control_connection(self): 647 self.assertNotIsInstance(self.client.sock, ssl.SSLSocket) 648 self.client.auth() 649 self.assertIsInstance(self.client.sock, ssl.SSLSocket) 650 651 def test_data_connection(self): 652 # clear text 653 sock = self.client.transfercmd('list') 654 self.assertNotIsInstance(sock, ssl.SSLSocket) 655 sock.close() 656 self.assertEqual(self.client.voidresp(), "226 transfer complete") 657 658 # secured, after PROT P 659 self.client.prot_p() 660 sock = self.client.transfercmd('list') 661 self.assertIsInstance(sock, ssl.SSLSocket) 662 sock.close() 663 self.assertEqual(self.client.voidresp(), "226 transfer complete") 664 665 # PROT C is issued, the connection must be in cleartext again 666 self.client.prot_c() 667 sock = self.client.transfercmd('list') 668 self.assertNotIsInstance(sock, ssl.SSLSocket) 669 sock.close() 670 self.assertEqual(self.client.voidresp(), "226 transfer complete") 671 672 def test_login(self): 673 # login() is supposed to implicitly secure the control connection 674 self.assertNotIsInstance(self.client.sock, ssl.SSLSocket) 675 self.client.login() 676 self.assertIsInstance(self.client.sock, ssl.SSLSocket) 677 # make sure that AUTH TLS doesn't get issued again 678 self.client.login() 679 680 def test_auth_issued_twice(self): 681 self.client.auth() 682 self.assertRaises(ValueError, self.client.auth) 683 684 def test_auth_ssl(self): 685 try: 686 self.client.ssl_version = ssl.PROTOCOL_SSLv3 687 self.client.auth() 688 self.assertRaises(ValueError, self.client.auth) 689 finally: 690 self.client.ssl_version = ssl.PROTOCOL_TLSv1 691 692 401 693 class TestTimeouts(TestCase): 402 694 … … 404 696 self.evt = threading.Event() 405 697 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 406 self.sock.settimeout( 3)698 self.sock.settimeout(10) 407 699 self.port = test_support.bind_port(self.sock) 408 700 threading.Thread(target=self.server, args=(self.evt,self.sock)).start() … … 439 731 def testTimeoutDefault(self): 440 732 # default -- use global socket timeout 441 self.assert _(socket.getdefaulttimeout() is None)733 self.assertTrue(socket.getdefaulttimeout() is None) 442 734 socket.setdefaulttimeout(30) 443 735 try: 444 ftp = ftplib.FTP( "localhost")736 ftp = ftplib.FTP(HOST) 445 737 finally: 446 738 socket.setdefaulttimeout(None) … … 451 743 def testTimeoutNone(self): 452 744 # no timeout -- do not use global socket timeout 453 self.assert _(socket.getdefaulttimeout() is None)745 self.assertTrue(socket.getdefaulttimeout() is None) 454 746 socket.setdefaulttimeout(30) 455 747 try: 456 ftp = ftplib.FTP( "localhost", timeout=None)748 ftp = ftplib.FTP(HOST, timeout=None) 457 749 finally: 458 750 socket.setdefaulttimeout(None) … … 500 792 else: 501 793 tests.append(TestIPv6Environment) 794 795 if ssl is not None: 796 tests.extend([TestTLS_FTPClassMixin, TestTLS_FTPClass]) 797 502 798 thread_info = test_support.threading_setup() 503 799 try:
Note:
See TracChangeset
for help on using the changeset viewer.