1"""Test script for ftplib module.""" 2 3# Modified by Giampaolo Rodola' to test FTP class, IPv6 and TLS 4# environment 5 6import ftplib 7import asyncore 8import asynchat 9import socket 10import io 11import errno 12import os 13import threading 14import time 15try: 16 import ssl 17except ImportError: 18 ssl = None 19 20from unittest import TestCase, skipUnless 21from test import support 22from test.support import HOST, HOSTv6 23 24TIMEOUT = 3 25# the dummy data returned by server over the data channel when 26# RETR, LIST, NLST, MLSD commands are issued 27RETR_DATA = 'abcde12345\r\n' * 1000 28LIST_DATA = 'foo\r\nbar\r\n' 29NLST_DATA = 'foo\r\nbar\r\n' 30MLSD_DATA = ("type=cdir;perm=el;unique==keVO1+ZF4; test\r\n" 31 "type=pdir;perm=e;unique==keVO1+d?3; ..\r\n" 32 "type=OS.unix=slink:/foobar;perm=;unique==keVO1+4G4; foobar\r\n" 33 "type=OS.unix=chr-13/29;perm=;unique==keVO1+5G4; device\r\n" 34 "type=OS.unix=blk-11/108;perm=;unique==keVO1+6G4; block\r\n" 35 "type=file;perm=awr;unique==keVO1+8G4; writable\r\n" 36 "type=dir;perm=cpmel;unique==keVO1+7G4; promiscuous\r\n" 37 "type=dir;perm=;unique==keVO1+1t2; no-exec\r\n" 38 "type=file;perm=r;unique==keVO1+EG4; two words\r\n" 39 "type=file;perm=r;unique==keVO1+IH4; leading space\r\n" 40 "type=file;perm=r;unique==keVO1+1G4; file1\r\n" 41 "type=dir;perm=cpmel;unique==keVO1+7G4; incoming\r\n" 42 "type=file;perm=r;unique==keVO1+1G4; file2\r\n" 43 "type=file;perm=r;unique==keVO1+1G4; file3\r\n" 44 "type=file;perm=r;unique==keVO1+1G4; file4\r\n") 45 46 47class DummyDTPHandler(asynchat.async_chat): 48 dtp_conn_closed = False 49 50 def __init__(self, conn, baseclass): 51 asynchat.async_chat.__init__(self, conn) 52 self.baseclass = baseclass 53 self.baseclass.last_received_data = '' 54 55 def handle_read(self): 56 self.baseclass.last_received_data += self.recv(1024).decode('ascii') 57 58 def handle_close(self): 59 # XXX: this method can be called many times in a row for a single 60 # connection, including in clear-text (non-TLS) mode. 61 # (behaviour witnessed with test_data_connection) 62 if not self.dtp_conn_closed: 63 self.baseclass.push('226 transfer complete') 64 self.close() 65 self.dtp_conn_closed = True 66 67 def push(self, what): 68 if self.baseclass.next_data is not None: 69 what = self.baseclass.next_data 70 self.baseclass.next_data = None 71 if not what: 72 return self.close_when_done() 73 super(DummyDTPHandler, self).push(what.encode('ascii')) 74 75 def handle_error(self): 76 raise Exception 77 78 79class DummyFTPHandler(asynchat.async_chat): 80 81 dtp_handler = DummyDTPHandler 82 83 def __init__(self, conn): 84 asynchat.async_chat.__init__(self, conn) 85 # tells the socket to handle urgent data inline (ABOR command) 86 self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_OOBINLINE, 1) 87 self.set_terminator(b"\r\n") 88 self.in_buffer = [] 89 self.dtp = None 90 self.last_received_cmd = None 91 self.last_received_data = '' 92 self.next_response = '' 93 self.next_data = None 94 self.rest = None 95 self.next_retr_data = RETR_DATA 96 self.push('220 welcome') 97 98 def collect_incoming_data(self, data): 99 self.in_buffer.append(data) 100 101 def found_terminator(self): 102 line = b''.join(self.in_buffer).decode('ascii') 103 self.in_buffer = [] 104 if self.next_response: 105 self.push(self.next_response) 106 self.next_response = '' 107 cmd = line.split(' ')[0].lower() 108 self.last_received_cmd = cmd 109 space = line.find(' ') 110 if space != -1: 111 arg = line[space + 1:] 112 else: 113 arg = "" 114 if hasattr(self, 'cmd_' + cmd): 115 method = getattr(self, 'cmd_' + cmd) 116 method(arg) 117 else: 118 self.push('550 command "%s" not understood.' %cmd) 119 120 def handle_error(self): 121 raise Exception 122 123 def push(self, data): 124 asynchat.async_chat.push(self, data.encode('ascii') + b'\r\n') 125 126 def cmd_port(self, arg): 127 addr = list(map(int, arg.split(','))) 128 ip = '%d.%d.%d.%d' %tuple(addr[:4]) 129 port = (addr[4] * 256) + addr[5] 130 s = socket.create_connection((ip, port), timeout=TIMEOUT) 131 self.dtp = self.dtp_handler(s, baseclass=self) 132 self.push('200 active data connection established') 133 134 def cmd_pasv(self, arg): 135 with socket.create_server((self.socket.getsockname()[0], 0)) as sock: 136 sock.settimeout(TIMEOUT) 137 ip, port = sock.getsockname()[:2] 138 ip = ip.replace('.', ','); p1 = port / 256; p2 = port % 256 139 self.push('227 entering passive mode (%s,%d,%d)' %(ip, p1, p2)) 140 conn, addr = sock.accept() 141 self.dtp = self.dtp_handler(conn, baseclass=self) 142 143 def cmd_eprt(self, arg): 144 af, ip, port = arg.split(arg[0])[1:-1] 145 port = int(port) 146 s = socket.create_connection((ip, port), timeout=TIMEOUT) 147 self.dtp = self.dtp_handler(s, baseclass=self) 148 self.push('200 active data connection established') 149 150 def cmd_epsv(self, arg): 151 with socket.create_server((self.socket.getsockname()[0], 0), 152 family=socket.AF_INET6) as sock: 153 sock.settimeout(TIMEOUT) 154 port = sock.getsockname()[1] 155 self.push('229 entering extended passive mode (|||%d|)' %port) 156 conn, addr = sock.accept() 157 self.dtp = self.dtp_handler(conn, baseclass=self) 158 159 def cmd_echo(self, arg): 160 # sends back the received string (used by the test suite) 161 self.push(arg) 162 163 def cmd_noop(self, arg): 164 self.push('200 noop ok') 165 166 def cmd_user(self, arg): 167 self.push('331 username ok') 168 169 def cmd_pass(self, arg): 170 self.push('230 password ok') 171 172 def cmd_acct(self, arg): 173 self.push('230 acct ok') 174 175 def cmd_rnfr(self, arg): 176 self.push('350 rnfr ok') 177 178 def cmd_rnto(self, arg): 179 self.push('250 rnto ok') 180 181 def cmd_dele(self, arg): 182 self.push('250 dele ok') 183 184 def cmd_cwd(self, arg): 185 self.push('250 cwd ok') 186 187 def cmd_size(self, arg): 188 self.push('250 1000') 189 190 def cmd_mkd(self, arg): 191 self.push('257 "%s"' %arg) 192 193 def cmd_rmd(self, arg): 194 self.push('250 rmd ok') 195 196 def cmd_pwd(self, arg): 197 self.push('257 "pwd ok"') 198 199 def cmd_type(self, arg): 200 self.push('200 type ok') 201 202 def cmd_quit(self, arg): 203 self.push('221 quit ok') 204 self.close() 205 206 def cmd_abor(self, arg): 207 self.push('226 abor ok') 208 209 def cmd_stor(self, arg): 210 self.push('125 stor ok') 211 212 def cmd_rest(self, arg): 213 self.rest = arg 214 self.push('350 rest ok') 215 216 def cmd_retr(self, arg): 217 self.push('125 retr ok') 218 if self.rest is not None: 219 offset = int(self.rest) 220 else: 221 offset = 0 222 self.dtp.push(self.next_retr_data[offset:]) 223 self.dtp.close_when_done() 224 self.rest = None 225 226 def cmd_list(self, arg): 227 self.push('125 list ok') 228 self.dtp.push(LIST_DATA) 229 self.dtp.close_when_done() 230 231 def cmd_nlst(self, arg): 232 self.push('125 nlst ok') 233 self.dtp.push(NLST_DATA) 234 self.dtp.close_when_done() 235 236 def cmd_opts(self, arg): 237 self.push('200 opts ok') 238 239 def cmd_mlsd(self, arg): 240 self.push('125 mlsd ok') 241 self.dtp.push(MLSD_DATA) 242 self.dtp.close_when_done() 243 244 def cmd_setlongretr(self, arg): 245 # For testing. Next RETR will return long line. 246 self.next_retr_data = 'x' * int(arg) 247 self.push('125 setlongretr ok') 248 249 250class DummyFTPServer(asyncore.dispatcher, threading.Thread): 251 252 handler = DummyFTPHandler 253 254 def __init__(self, address, af=socket.AF_INET): 255 threading.Thread.__init__(self) 256 asyncore.dispatcher.__init__(self) 257 self.daemon = True 258 self.create_socket(af, socket.SOCK_STREAM) 259 self.bind(address) 260 self.listen(5) 261 self.active = False 262 self.active_lock = threading.Lock() 263 self.host, self.port = self.socket.getsockname()[:2] 264 self.handler_instance = None 265 266 def start(self): 267 assert not self.active 268 self.__flag = threading.Event() 269 threading.Thread.start(self) 270 self.__flag.wait() 271 272 def run(self): 273 self.active = True 274 self.__flag.set() 275 while self.active and asyncore.socket_map: 276 self.active_lock.acquire() 277 asyncore.loop(timeout=0.1, count=1) 278 self.active_lock.release() 279 asyncore.close_all(ignore_all=True) 280 281 def stop(self): 282 assert self.active 283 self.active = False 284 self.join() 285 286 def handle_accepted(self, conn, addr): 287 self.handler_instance = self.handler(conn) 288 289 def handle_connect(self): 290 self.close() 291 handle_read = handle_connect 292 293 def writable(self): 294 return 0 295 296 def handle_error(self): 297 raise Exception 298 299 300if ssl is not None: 301 302 CERTFILE = os.path.join(os.path.dirname(__file__), "keycert3.pem") 303 CAFILE = os.path.join(os.path.dirname(__file__), "pycacert.pem") 304 305 class SSLConnection(asyncore.dispatcher): 306 """An asyncore.dispatcher subclass supporting TLS/SSL.""" 307 308 _ssl_accepting = False 309 _ssl_closing = False 310 311 def secure_connection(self): 312 context = ssl.SSLContext() 313 context.load_cert_chain(CERTFILE) 314 socket = context.wrap_socket(self.socket, 315 suppress_ragged_eofs=False, 316 server_side=True, 317 do_handshake_on_connect=False) 318 self.del_channel() 319 self.set_socket(socket) 320 self._ssl_accepting = True 321 322 def _do_ssl_handshake(self): 323 try: 324 self.socket.do_handshake() 325 except ssl.SSLError as err: 326 if err.args[0] in (ssl.SSL_ERROR_WANT_READ, 327 ssl.SSL_ERROR_WANT_WRITE): 328 return 329 elif err.args[0] == ssl.SSL_ERROR_EOF: 330 return self.handle_close() 331 # TODO: SSLError does not expose alert information 332 elif "SSLV3_ALERT_BAD_CERTIFICATE" in err.args[1]: 333 return self.handle_close() 334 raise 335 except OSError as err: 336 if err.args[0] == errno.ECONNABORTED: 337 return self.handle_close() 338 else: 339 self._ssl_accepting = False 340 341 def _do_ssl_shutdown(self): 342 self._ssl_closing = True 343 try: 344 self.socket = self.socket.unwrap() 345 except ssl.SSLError as err: 346 if err.args[0] in (ssl.SSL_ERROR_WANT_READ, 347 ssl.SSL_ERROR_WANT_WRITE): 348 return 349 except OSError as err: 350 # Any "socket error" corresponds to a SSL_ERROR_SYSCALL return 351 # from OpenSSL's SSL_shutdown(), corresponding to a 352 # closed socket condition. See also: 353 # http://www.mail-archive.com/openssl-users@openssl.org/msg60710.html 354 pass 355 self._ssl_closing = False 356 if getattr(self, '_ccc', False) is False: 357 super(SSLConnection, self).close() 358 else: 359 pass 360 361 def handle_read_event(self): 362 if self._ssl_accepting: 363 self._do_ssl_handshake() 364 elif self._ssl_closing: 365 self._do_ssl_shutdown() 366 else: 367 super(SSLConnection, self).handle_read_event() 368 369 def handle_write_event(self): 370 if self._ssl_accepting: 371 self._do_ssl_handshake() 372 elif self._ssl_closing: 373 self._do_ssl_shutdown() 374 else: 375 super(SSLConnection, self).handle_write_event() 376 377 def send(self, data): 378 try: 379 return super(SSLConnection, self).send(data) 380 except ssl.SSLError as err: 381 if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN, 382 ssl.SSL_ERROR_WANT_READ, 383 ssl.SSL_ERROR_WANT_WRITE): 384 return 0 385 raise 386 387 def recv(self, buffer_size): 388 try: 389 return super(SSLConnection, self).recv(buffer_size) 390 except ssl.SSLError as err: 391 if err.args[0] in (ssl.SSL_ERROR_WANT_READ, 392 ssl.SSL_ERROR_WANT_WRITE): 393 return b'' 394 if err.args[0] in (ssl.SSL_ERROR_EOF, ssl.SSL_ERROR_ZERO_RETURN): 395 self.handle_close() 396 return b'' 397 raise 398 399 def handle_error(self): 400 raise Exception 401 402 def close(self): 403 if (isinstance(self.socket, ssl.SSLSocket) and 404 self.socket._sslobj is not None): 405 self._do_ssl_shutdown() 406 else: 407 super(SSLConnection, self).close() 408 409 410 class DummyTLS_DTPHandler(SSLConnection, DummyDTPHandler): 411 """A DummyDTPHandler subclass supporting TLS/SSL.""" 412 413 def __init__(self, conn, baseclass): 414 DummyDTPHandler.__init__(self, conn, baseclass) 415 if self.baseclass.secure_data_channel: 416 self.secure_connection() 417 418 419 class DummyTLS_FTPHandler(SSLConnection, DummyFTPHandler): 420 """A DummyFTPHandler subclass supporting TLS/SSL.""" 421 422 dtp_handler = DummyTLS_DTPHandler 423 424 def __init__(self, conn): 425 DummyFTPHandler.__init__(self, conn) 426 self.secure_data_channel = False 427 self._ccc = False 428 429 def cmd_auth(self, line): 430 """Set up secure control channel.""" 431 self.push('234 AUTH TLS successful') 432 self.secure_connection() 433 434 def cmd_ccc(self, line): 435 self.push('220 Reverting back to clear-text') 436 self._ccc = True 437 self._do_ssl_shutdown() 438 439 def cmd_pbsz(self, line): 440 """Negotiate size of buffer for secure data transfer. 441 For TLS/SSL the only valid value for the parameter is '0'. 442 Any other value is accepted but ignored. 443 """ 444 self.push('200 PBSZ=0 successful.') 445 446 def cmd_prot(self, line): 447 """Setup un/secure data channel.""" 448 arg = line.upper() 449 if arg == 'C': 450 self.push('200 Protection set to Clear') 451 self.secure_data_channel = False 452 elif arg == 'P': 453 self.push('200 Protection set to Private') 454 self.secure_data_channel = True 455 else: 456 self.push("502 Unrecognized PROT type (use C or P).") 457 458 459 class DummyTLS_FTPServer(DummyFTPServer): 460 handler = DummyTLS_FTPHandler 461 462 463class TestFTPClass(TestCase): 464 465 def setUp(self): 466 self.server = DummyFTPServer((HOST, 0)) 467 self.server.start() 468 self.client = ftplib.FTP(timeout=TIMEOUT) 469 self.client.connect(self.server.host, self.server.port) 470 471 def tearDown(self): 472 self.client.close() 473 self.server.stop() 474 # Explicitly clear the attribute to prevent dangling thread 475 self.server = None 476 asyncore.close_all(ignore_all=True) 477 478 def check_data(self, received, expected): 479 self.assertEqual(len(received), len(expected)) 480 self.assertEqual(received, expected) 481 482 def test_getwelcome(self): 483 self.assertEqual(self.client.getwelcome(), '220 welcome') 484 485 def test_sanitize(self): 486 self.assertEqual(self.client.sanitize('foo'), repr('foo')) 487 self.assertEqual(self.client.sanitize('pass 12345'), repr('pass *****')) 488 self.assertEqual(self.client.sanitize('PASS 12345'), repr('PASS *****')) 489 490 def test_exceptions(self): 491 self.assertRaises(ValueError, self.client.sendcmd, 'echo 40\r\n0') 492 self.assertRaises(ValueError, self.client.sendcmd, 'echo 40\n0') 493 self.assertRaises(ValueError, self.client.sendcmd, 'echo 40\r0') 494 self.assertRaises(ftplib.error_temp, self.client.sendcmd, 'echo 400') 495 self.assertRaises(ftplib.error_temp, self.client.sendcmd, 'echo 499') 496 self.assertRaises(ftplib.error_perm, self.client.sendcmd, 'echo 500') 497 self.assertRaises(ftplib.error_perm, self.client.sendcmd, 'echo 599') 498 self.assertRaises(ftplib.error_proto, self.client.sendcmd, 'echo 999') 499 500 def test_all_errors(self): 501 exceptions = (ftplib.error_reply, ftplib.error_temp, ftplib.error_perm, 502 ftplib.error_proto, ftplib.Error, OSError, 503 EOFError) 504 for x in exceptions: 505 try: 506 raise x('exception not included in all_errors set') 507 except ftplib.all_errors: 508 pass 509 510 def test_set_pasv(self): 511 # passive mode is supposed to be enabled by default 512 self.assertTrue(self.client.passiveserver) 513 self.client.set_pasv(True) 514 self.assertTrue(self.client.passiveserver) 515 self.client.set_pasv(False) 516 self.assertFalse(self.client.passiveserver) 517 518 def test_voidcmd(self): 519 self.client.voidcmd('echo 200') 520 self.client.voidcmd('echo 299') 521 self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 199') 522 self.assertRaises(ftplib.error_reply, self.client.voidcmd, 'echo 300') 523 524 def test_login(self): 525 self.client.login() 526 527 def test_acct(self): 528 self.client.acct('passwd') 529 530 def test_rename(self): 531 self.client.rename('a', 'b') 532 self.server.handler_instance.next_response = '200' 533 self.assertRaises(ftplib.error_reply, self.client.rename, 'a', 'b') 534 535 def test_delete(self): 536 self.client.delete('foo') 537 self.server.handler_instance.next_response = '199' 538 self.assertRaises(ftplib.error_reply, self.client.delete, 'foo') 539 540 def test_size(self): 541 self.client.size('foo') 542 543 def test_mkd(self): 544 dir = self.client.mkd('/foo') 545 self.assertEqual(dir, '/foo') 546 547 def test_rmd(self): 548 self.client.rmd('foo') 549 550 def test_cwd(self): 551 dir = self.client.cwd('/foo') 552 self.assertEqual(dir, '250 cwd ok') 553 554 def test_pwd(self): 555 dir = self.client.pwd() 556 self.assertEqual(dir, 'pwd ok') 557 558 def test_quit(self): 559 self.assertEqual(self.client.quit(), '221 quit ok') 560 # Ensure the connection gets closed; sock attribute should be None 561 self.assertEqual(self.client.sock, None) 562 563 def test_abort(self): 564 self.client.abort() 565 566 def test_retrbinary(self): 567 def callback(data): 568 received.append(data.decode('ascii')) 569 received = [] 570 self.client.retrbinary('retr', callback) 571 self.check_data(''.join(received), RETR_DATA) 572 573 def test_retrbinary_rest(self): 574 def callback(data): 575 received.append(data.decode('ascii')) 576 for rest in (0, 10, 20): 577 received = [] 578 self.client.retrbinary('retr', callback, rest=rest) 579 self.check_data(''.join(received), RETR_DATA[rest:]) 580 581 def test_retrlines(self): 582 received = [] 583 self.client.retrlines('retr', received.append) 584 self.check_data(''.join(received), RETR_DATA.replace('\r\n', '')) 585 586 def test_storbinary(self): 587 f = io.BytesIO(RETR_DATA.encode('ascii')) 588 self.client.storbinary('stor', f) 589 self.check_data(self.server.handler_instance.last_received_data, RETR_DATA) 590 # test new callback arg 591 flag = [] 592 f.seek(0) 593 self.client.storbinary('stor', f, callback=lambda x: flag.append(None)) 594 self.assertTrue(flag) 595 596 def test_storbinary_rest(self): 597 f = io.BytesIO(RETR_DATA.replace('\r\n', '\n').encode('ascii')) 598 for r in (30, '30'): 599 f.seek(0) 600 self.client.storbinary('stor', f, rest=r) 601 self.assertEqual(self.server.handler_instance.rest, str(r)) 602 603 def test_storlines(self): 604 f = io.BytesIO(RETR_DATA.replace('\r\n', '\n').encode('ascii')) 605 self.client.storlines('stor', f) 606 self.check_data(self.server.handler_instance.last_received_data, RETR_DATA) 607 # test new callback arg 608 flag = [] 609 f.seek(0) 610 self.client.storlines('stor foo', f, callback=lambda x: flag.append(None)) 611 self.assertTrue(flag) 612 613 f = io.StringIO(RETR_DATA.replace('\r\n', '\n')) 614 # storlines() expects a binary file, not a text file 615 with support.check_warnings(('', BytesWarning), quiet=True): 616 self.assertRaises(TypeError, self.client.storlines, 'stor foo', f) 617 618 def test_nlst(self): 619 self.client.nlst() 620 self.assertEqual(self.client.nlst(), NLST_DATA.split('\r\n')[:-1]) 621 622 def test_dir(self): 623 l = [] 624 self.client.dir(lambda x: l.append(x)) 625 self.assertEqual(''.join(l), LIST_DATA.replace('\r\n', '')) 626 627 def test_mlsd(self): 628 list(self.client.mlsd()) 629 list(self.client.mlsd(path='/')) 630 list(self.client.mlsd(path='/', facts=['size', 'type'])) 631 632 ls = list(self.client.mlsd()) 633 for name, facts in ls: 634 self.assertIsInstance(name, str) 635 self.assertIsInstance(facts, dict) 636 self.assertTrue(name) 637 self.assertIn('type', facts) 638 self.assertIn('perm', facts) 639 self.assertIn('unique', facts) 640 641 def set_data(data): 642 self.server.handler_instance.next_data = data 643 644 def test_entry(line, type=None, perm=None, unique=None, name=None): 645 type = 'type' if type is None else type 646 perm = 'perm' if perm is None else perm 647 unique = 'unique' if unique is None else unique 648 name = 'name' if name is None else name 649 set_data(line) 650 _name, facts = next(self.client.mlsd()) 651 self.assertEqual(_name, name) 652 self.assertEqual(facts['type'], type) 653 self.assertEqual(facts['perm'], perm) 654 self.assertEqual(facts['unique'], unique) 655 656 # plain 657 test_entry('type=type;perm=perm;unique=unique; name\r\n') 658 # "=" in fact value 659 test_entry('type=ty=pe;perm=perm;unique=unique; name\r\n', type="ty=pe") 660 test_entry('type==type;perm=perm;unique=unique; name\r\n', type="=type") 661 test_entry('type=t=y=pe;perm=perm;unique=unique; name\r\n', type="t=y=pe") 662 test_entry('type=====;perm=perm;unique=unique; name\r\n', type="====") 663 # spaces in name 664 test_entry('type=type;perm=perm;unique=unique; na me\r\n', name="na me") 665 test_entry('type=type;perm=perm;unique=unique; name \r\n', name="name ") 666 test_entry('type=type;perm=perm;unique=unique; name\r\n', name=" name") 667 test_entry('type=type;perm=perm;unique=unique; n am e\r\n', name="n am e") 668 # ";" in name 669 test_entry('type=type;perm=perm;unique=unique; na;me\r\n', name="na;me") 670 test_entry('type=type;perm=perm;unique=unique; ;name\r\n', name=";name") 671 test_entry('type=type;perm=perm;unique=unique; ;name;\r\n', name=";name;") 672 test_entry('type=type;perm=perm;unique=unique; ;;;;\r\n', name=";;;;") 673 # case sensitiveness 674 set_data('Type=type;TyPe=perm;UNIQUE=unique; name\r\n') 675 _name, facts = next(self.client.mlsd()) 676 for x in facts: 677 self.assertTrue(x.islower()) 678 # no data (directory empty) 679 set_data('') 680 self.assertRaises(StopIteration, next, self.client.mlsd()) 681 set_data('') 682 for x in self.client.mlsd(): 683 self.fail("unexpected data %s" % x) 684 685 def test_makeport(self): 686 with self.client.makeport(): 687 # IPv4 is in use, just make sure send_eprt has not been used 688 self.assertEqual(self.server.handler_instance.last_received_cmd, 689 'port') 690 691 def test_makepasv(self): 692 host, port = self.client.makepasv() 693 conn = socket.create_connection((host, port), timeout=TIMEOUT) 694 conn.close() 695 # IPv4 is in use, just make sure send_epsv has not been used 696 self.assertEqual(self.server.handler_instance.last_received_cmd, 'pasv') 697 698 def test_with_statement(self): 699 self.client.quit() 700 701 def is_client_connected(): 702 if self.client.sock is None: 703 return False 704 try: 705 self.client.sendcmd('noop') 706 except (OSError, EOFError): 707 return False 708 return True 709 710 # base test 711 with ftplib.FTP(timeout=TIMEOUT) as self.client: 712 self.client.connect(self.server.host, self.server.port) 713 self.client.sendcmd('noop') 714 self.assertTrue(is_client_connected()) 715 self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit') 716 self.assertFalse(is_client_connected()) 717 718 # QUIT sent inside the with block 719 with ftplib.FTP(timeout=TIMEOUT) as self.client: 720 self.client.connect(self.server.host, self.server.port) 721 self.client.sendcmd('noop') 722 self.client.quit() 723 self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit') 724 self.assertFalse(is_client_connected()) 725 726 # force a wrong response code to be sent on QUIT: error_perm 727 # is expected and the connection is supposed to be closed 728 try: 729 with ftplib.FTP(timeout=TIMEOUT) as self.client: 730 self.client.connect(self.server.host, self.server.port) 731 self.client.sendcmd('noop') 732 self.server.handler_instance.next_response = '550 error on quit' 733 except ftplib.error_perm as err: 734 self.assertEqual(str(err), '550 error on quit') 735 else: 736 self.fail('Exception not raised') 737 # needed to give the threaded server some time to set the attribute 738 # which otherwise would still be == 'noop' 739 time.sleep(0.1) 740 self.assertEqual(self.server.handler_instance.last_received_cmd, 'quit') 741 self.assertFalse(is_client_connected()) 742 743 def test_source_address(self): 744 self.client.quit() 745 port = support.find_unused_port() 746 try: 747 self.client.connect(self.server.host, self.server.port, 748 source_address=(HOST, port)) 749 self.assertEqual(self.client.sock.getsockname()[1], port) 750 self.client.quit() 751 except OSError as e: 752 if e.errno == errno.EADDRINUSE: 753 self.skipTest("couldn't bind to port %d" % port) 754 raise 755 756 def test_source_address_passive_connection(self): 757 port = support.find_unused_port() 758 self.client.source_address = (HOST, port) 759 try: 760 with self.client.transfercmd('list') as sock: 761 self.assertEqual(sock.getsockname()[1], port) 762 except OSError as e: 763 if e.errno == errno.EADDRINUSE: 764 self.skipTest("couldn't bind to port %d" % port) 765 raise 766 767 def test_parse257(self): 768 self.assertEqual(ftplib.parse257('257 "/foo/bar"'), '/foo/bar') 769 self.assertEqual(ftplib.parse257('257 "/foo/bar" created'), '/foo/bar') 770 self.assertEqual(ftplib.parse257('257 ""'), '') 771 self.assertEqual(ftplib.parse257('257 "" created'), '') 772 self.assertRaises(ftplib.error_reply, ftplib.parse257, '250 "/foo/bar"') 773 # The 257 response is supposed to include the directory 774 # name and in case it contains embedded double-quotes 775 # they must be doubled (see RFC-959, chapter 7, appendix 2). 776 self.assertEqual(ftplib.parse257('257 "/foo/b""ar"'), '/foo/b"ar') 777 self.assertEqual(ftplib.parse257('257 "/foo/b""ar" created'), '/foo/b"ar') 778 779 def test_line_too_long(self): 780 self.assertRaises(ftplib.Error, self.client.sendcmd, 781 'x' * self.client.maxline * 2) 782 783 def test_retrlines_too_long(self): 784 self.client.sendcmd('SETLONGRETR %d' % (self.client.maxline * 2)) 785 received = [] 786 self.assertRaises(ftplib.Error, 787 self.client.retrlines, 'retr', received.append) 788 789 def test_storlines_too_long(self): 790 f = io.BytesIO(b'x' * self.client.maxline * 2) 791 self.assertRaises(ftplib.Error, self.client.storlines, 'stor', f) 792 793 794@skipUnless(support.IPV6_ENABLED, "IPv6 not enabled") 795class TestIPv6Environment(TestCase): 796 797 def setUp(self): 798 self.server = DummyFTPServer((HOSTv6, 0), af=socket.AF_INET6) 799 self.server.start() 800 self.client = ftplib.FTP(timeout=TIMEOUT) 801 self.client.connect(self.server.host, self.server.port) 802 803 def tearDown(self): 804 self.client.close() 805 self.server.stop() 806 # Explicitly clear the attribute to prevent dangling thread 807 self.server = None 808 asyncore.close_all(ignore_all=True) 809 810 def test_af(self): 811 self.assertEqual(self.client.af, socket.AF_INET6) 812 813 def test_makeport(self): 814 with self.client.makeport(): 815 self.assertEqual(self.server.handler_instance.last_received_cmd, 816 'eprt') 817 818 def test_makepasv(self): 819 host, port = self.client.makepasv() 820 conn = socket.create_connection((host, port), timeout=TIMEOUT) 821 conn.close() 822 self.assertEqual(self.server.handler_instance.last_received_cmd, 'epsv') 823 824 def test_transfer(self): 825 def retr(): 826 def callback(data): 827 received.append(data.decode('ascii')) 828 received = [] 829 self.client.retrbinary('retr', callback) 830 self.assertEqual(len(''.join(received)), len(RETR_DATA)) 831 self.assertEqual(''.join(received), RETR_DATA) 832 self.client.set_pasv(True) 833 retr() 834 self.client.set_pasv(False) 835 retr() 836 837 838@skipUnless(ssl, "SSL not available") 839class TestTLS_FTPClassMixin(TestFTPClass): 840 """Repeat TestFTPClass tests starting the TLS layer for both control 841 and data connections first. 842 """ 843 844 def setUp(self): 845 self.server = DummyTLS_FTPServer((HOST, 0)) 846 self.server.start() 847 self.client = ftplib.FTP_TLS(timeout=TIMEOUT) 848 self.client.connect(self.server.host, self.server.port) 849 # enable TLS 850 self.client.auth() 851 self.client.prot_p() 852 853 854@skipUnless(ssl, "SSL not available") 855class TestTLS_FTPClass(TestCase): 856 """Specific TLS_FTP class tests.""" 857 858 def setUp(self): 859 self.server = DummyTLS_FTPServer((HOST, 0)) 860 self.server.start() 861 self.client = ftplib.FTP_TLS(timeout=TIMEOUT) 862 self.client.connect(self.server.host, self.server.port) 863 864 def tearDown(self): 865 self.client.close() 866 self.server.stop() 867 # Explicitly clear the attribute to prevent dangling thread 868 self.server = None 869 asyncore.close_all(ignore_all=True) 870 871 def test_control_connection(self): 872 self.assertNotIsInstance(self.client.sock, ssl.SSLSocket) 873 self.client.auth() 874 self.assertIsInstance(self.client.sock, ssl.SSLSocket) 875 876 def test_data_connection(self): 877 # clear text 878 with self.client.transfercmd('list') as sock: 879 self.assertNotIsInstance(sock, ssl.SSLSocket) 880 self.assertEqual(sock.recv(1024), LIST_DATA.encode('ascii')) 881 self.assertEqual(self.client.voidresp(), "226 transfer complete") 882 883 # secured, after PROT P 884 self.client.prot_p() 885 with self.client.transfercmd('list') as sock: 886 self.assertIsInstance(sock, ssl.SSLSocket) 887 # consume from SSL socket to finalize handshake and avoid 888 # "SSLError [SSL] shutdown while in init" 889 self.assertEqual(sock.recv(1024), LIST_DATA.encode('ascii')) 890 self.assertEqual(self.client.voidresp(), "226 transfer complete") 891 892 # PROT C is issued, the connection must be in cleartext again 893 self.client.prot_c() 894 with self.client.transfercmd('list') as sock: 895 self.assertNotIsInstance(sock, ssl.SSLSocket) 896 self.assertEqual(sock.recv(1024), LIST_DATA.encode('ascii')) 897 self.assertEqual(self.client.voidresp(), "226 transfer complete") 898 899 def test_login(self): 900 # login() is supposed to implicitly secure the control connection 901 self.assertNotIsInstance(self.client.sock, ssl.SSLSocket) 902 self.client.login() 903 self.assertIsInstance(self.client.sock, ssl.SSLSocket) 904 # make sure that AUTH TLS doesn't get issued again 905 self.client.login() 906 907 def test_auth_issued_twice(self): 908 self.client.auth() 909 self.assertRaises(ValueError, self.client.auth) 910 911 def test_context(self): 912 self.client.quit() 913 ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) 914 ctx.check_hostname = False 915 ctx.verify_mode = ssl.CERT_NONE 916 self.assertRaises(ValueError, ftplib.FTP_TLS, keyfile=CERTFILE, 917 context=ctx) 918 self.assertRaises(ValueError, ftplib.FTP_TLS, certfile=CERTFILE, 919 context=ctx) 920 self.assertRaises(ValueError, ftplib.FTP_TLS, certfile=CERTFILE, 921 keyfile=CERTFILE, context=ctx) 922 923 self.client = ftplib.FTP_TLS(context=ctx, timeout=TIMEOUT) 924 self.client.connect(self.server.host, self.server.port) 925 self.assertNotIsInstance(self.client.sock, ssl.SSLSocket) 926 self.client.auth() 927 self.assertIs(self.client.sock.context, ctx) 928 self.assertIsInstance(self.client.sock, ssl.SSLSocket) 929 930 self.client.prot_p() 931 with self.client.transfercmd('list') as sock: 932 self.assertIs(sock.context, ctx) 933 self.assertIsInstance(sock, ssl.SSLSocket) 934 935 def test_ccc(self): 936 self.assertRaises(ValueError, self.client.ccc) 937 self.client.login(secure=True) 938 self.assertIsInstance(self.client.sock, ssl.SSLSocket) 939 self.client.ccc() 940 self.assertRaises(ValueError, self.client.sock.unwrap) 941 942 @skipUnless(False, "FIXME: bpo-32706") 943 def test_check_hostname(self): 944 self.client.quit() 945 ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) 946 self.assertEqual(ctx.verify_mode, ssl.CERT_REQUIRED) 947 self.assertEqual(ctx.check_hostname, True) 948 ctx.load_verify_locations(CAFILE) 949 self.client = ftplib.FTP_TLS(context=ctx, timeout=TIMEOUT) 950 951 # 127.0.0.1 doesn't match SAN 952 self.client.connect(self.server.host, self.server.port) 953 with self.assertRaises(ssl.CertificateError): 954 self.client.auth() 955 # exception quits connection 956 957 self.client.connect(self.server.host, self.server.port) 958 self.client.prot_p() 959 with self.assertRaises(ssl.CertificateError): 960 with self.client.transfercmd("list") as sock: 961 pass 962 self.client.quit() 963 964 self.client.connect("localhost", self.server.port) 965 self.client.auth() 966 self.client.quit() 967 968 self.client.connect("localhost", self.server.port) 969 self.client.prot_p() 970 with self.client.transfercmd("list") as sock: 971 pass 972 973 974class TestTimeouts(TestCase): 975 976 def setUp(self): 977 self.evt = threading.Event() 978 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 979 self.sock.settimeout(20) 980 self.port = support.bind_port(self.sock) 981 self.server_thread = threading.Thread(target=self.server) 982 self.server_thread.daemon = True 983 self.server_thread.start() 984 # Wait for the server to be ready. 985 self.evt.wait() 986 self.evt.clear() 987 self.old_port = ftplib.FTP.port 988 ftplib.FTP.port = self.port 989 990 def tearDown(self): 991 ftplib.FTP.port = self.old_port 992 self.server_thread.join() 993 # Explicitly clear the attribute to prevent dangling thread 994 self.server_thread = None 995 996 def server(self): 997 # This method sets the evt 3 times: 998 # 1) when the connection is ready to be accepted. 999 # 2) when it is safe for the caller to close the connection 1000 # 3) when we have closed the socket 1001 self.sock.listen() 1002 # (1) Signal the caller that we are ready to accept the connection. 1003 self.evt.set() 1004 try: 1005 conn, addr = self.sock.accept() 1006 except socket.timeout: 1007 pass 1008 else: 1009 conn.sendall(b"1 Hola mundo\n") 1010 conn.shutdown(socket.SHUT_WR) 1011 # (2) Signal the caller that it is safe to close the socket. 1012 self.evt.set() 1013 conn.close() 1014 finally: 1015 self.sock.close() 1016 1017 def testTimeoutDefault(self): 1018 # default -- use global socket timeout 1019 self.assertIsNone(socket.getdefaulttimeout()) 1020 socket.setdefaulttimeout(30) 1021 try: 1022 ftp = ftplib.FTP(HOST) 1023 finally: 1024 socket.setdefaulttimeout(None) 1025 self.assertEqual(ftp.sock.gettimeout(), 30) 1026 self.evt.wait() 1027 ftp.close() 1028 1029 def testTimeoutNone(self): 1030 # no timeout -- do not use global socket timeout 1031 self.assertIsNone(socket.getdefaulttimeout()) 1032 socket.setdefaulttimeout(30) 1033 try: 1034 ftp = ftplib.FTP(HOST, timeout=None) 1035 finally: 1036 socket.setdefaulttimeout(None) 1037 self.assertIsNone(ftp.sock.gettimeout()) 1038 self.evt.wait() 1039 ftp.close() 1040 1041 def testTimeoutValue(self): 1042 # a value 1043 ftp = ftplib.FTP(HOST, timeout=30) 1044 self.assertEqual(ftp.sock.gettimeout(), 30) 1045 self.evt.wait() 1046 ftp.close() 1047 1048 def testTimeoutConnect(self): 1049 ftp = ftplib.FTP() 1050 ftp.connect(HOST, timeout=30) 1051 self.assertEqual(ftp.sock.gettimeout(), 30) 1052 self.evt.wait() 1053 ftp.close() 1054 1055 def testTimeoutDifferentOrder(self): 1056 ftp = ftplib.FTP(timeout=30) 1057 ftp.connect(HOST) 1058 self.assertEqual(ftp.sock.gettimeout(), 30) 1059 self.evt.wait() 1060 ftp.close() 1061 1062 def testTimeoutDirectAccess(self): 1063 ftp = ftplib.FTP() 1064 ftp.timeout = 30 1065 ftp.connect(HOST) 1066 self.assertEqual(ftp.sock.gettimeout(), 30) 1067 self.evt.wait() 1068 ftp.close() 1069 1070 1071class MiscTestCase(TestCase): 1072 def test__all__(self): 1073 blacklist = {'MSG_OOB', 'FTP_PORT', 'MAXLINE', 'CRLF', 'B_CRLF', 1074 'Error', 'parse150', 'parse227', 'parse229', 'parse257', 1075 'print_line', 'ftpcp', 'test'} 1076 support.check__all__(self, ftplib, blacklist=blacklist) 1077 1078 1079def test_main(): 1080 tests = [TestFTPClass, TestTimeouts, 1081 TestIPv6Environment, 1082 TestTLS_FTPClassMixin, TestTLS_FTPClass, 1083 MiscTestCase] 1084 1085 thread_info = support.threading_setup() 1086 try: 1087 support.run_unittest(*tests) 1088 finally: 1089 support.threading_cleanup(*thread_info) 1090 1091 1092if __name__ == '__main__': 1093 test_main() 1094