1"""An FTP client class and some helper functions. 2 3Based on RFC 959: File Transfer Protocol (FTP), by J. Postel and J. Reynolds 4 5Example: 6 7>>> from ftplib import FTP 8>>> ftp = FTP('ftp.python.org') # connect to host, default port 9>>> ftp.login() # default, i.e.: user anonymous, passwd anonymous@ 10'230 Guest login ok, access restrictions apply.' 11>>> ftp.retrlines('LIST') # list directory contents 12total 9 13drwxr-xr-x 8 root wheel 1024 Jan 3 1994 . 14drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .. 15drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin 16drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc 17d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming 18drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib 19drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub 20drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr 21-rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg 22'226 Transfer complete.' 23>>> ftp.quit() 24'221 Goodbye.' 25>>> 26 27A nice test that reveals some of the network dialogue would be: 28python ftplib.py -d localhost -l -p -l 29""" 30 31# 32# Changes and improvements suggested by Steve Majewski. 33# Modified by Jack to work on the mac. 34# Modified by Siebren to support docstrings and PASV. 35# Modified by Phil Schwartz to add storbinary and storlines callbacks. 36# Modified by Giampaolo Rodola' to add TLS support. 37# 38 39import sys 40import socket 41from socket import _GLOBAL_DEFAULT_TIMEOUT 42 43__all__ = ["FTP", "error_reply", "error_temp", "error_perm", "error_proto", 44 "all_errors"] 45 46# Magic number from <socket.h> 47MSG_OOB = 0x1 # Process data out of band 48 49 50# The standard FTP server control port 51FTP_PORT = 21 52# The sizehint parameter passed to readline() calls 53MAXLINE = 8192 54 55 56# Exception raised when an error or invalid response is received 57class Error(Exception): pass 58class error_reply(Error): pass # unexpected [123]xx reply 59class error_temp(Error): pass # 4xx errors 60class error_perm(Error): pass # 5xx errors 61class error_proto(Error): pass # response does not begin with [1-5] 62 63 64# All exceptions (hopefully) that may be raised here and that aren't 65# (always) programming errors on our side 66all_errors = (Error, OSError, EOFError) 67 68 69# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF) 70CRLF = '\r\n' 71B_CRLF = b'\r\n' 72 73# The class itself 74class FTP: 75 '''An FTP client class. 76 77 To create a connection, call the class using these arguments: 78 host, user, passwd, acct, timeout, source_address, encoding 79 80 The first four arguments are all strings, and have default value ''. 81 The parameter ´timeout´ must be numeric and defaults to None if not 82 passed, meaning that no timeout will be set on any ftp socket(s). 83 If a timeout is passed, then this is now the default timeout for all ftp 84 socket operations for this instance. 85 The last parameter is the encoding of filenames, which defaults to utf-8. 86 87 Then use self.connect() with optional host and port argument. 88 89 To download a file, use ftp.retrlines('RETR ' + filename), 90 or ftp.retrbinary() with slightly different arguments. 91 To upload a file, use ftp.storlines() or ftp.storbinary(), 92 which have an open file as argument (see their definitions 93 below for details). 94 The download/upload functions first issue appropriate TYPE 95 and PORT or PASV commands. 96 ''' 97 98 debugging = 0 99 host = '' 100 port = FTP_PORT 101 maxline = MAXLINE 102 sock = None 103 file = None 104 welcome = None 105 passiveserver = True 106 # Disables https://bugs.python.org/issue43285 security if set to True. 107 trust_server_pasv_ipv4_address = False 108 109 def __init__(self, host='', user='', passwd='', acct='', 110 timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None, *, 111 encoding='utf-8'): 112 """Initialization method (called by class instantiation). 113 Initialize host to localhost, port to standard ftp port. 114 Optional arguments are host (for connect()), 115 and user, passwd, acct (for login()). 116 """ 117 self.encoding = encoding 118 self.source_address = source_address 119 self.timeout = timeout 120 if host: 121 self.connect(host) 122 if user: 123 self.login(user, passwd, acct) 124 125 def __enter__(self): 126 return self 127 128 # Context management protocol: try to quit() if active 129 def __exit__(self, *args): 130 if self.sock is not None: 131 try: 132 self.quit() 133 except (OSError, EOFError): 134 pass 135 finally: 136 if self.sock is not None: 137 self.close() 138 139 def connect(self, host='', port=0, timeout=-999, source_address=None): 140 '''Connect to host. Arguments are: 141 - host: hostname to connect to (string, default previous host) 142 - port: port to connect to (integer, default previous port) 143 - timeout: the timeout to set against the ftp socket(s) 144 - source_address: a 2-tuple (host, port) for the socket to bind 145 to as its source address before connecting. 146 ''' 147 if host != '': 148 self.host = host 149 if port > 0: 150 self.port = port 151 if timeout != -999: 152 self.timeout = timeout 153 if self.timeout is not None and not self.timeout: 154 raise ValueError('Non-blocking socket (timeout=0) is not supported') 155 if source_address is not None: 156 self.source_address = source_address 157 sys.audit("ftplib.connect", self, self.host, self.port) 158 self.sock = socket.create_connection((self.host, self.port), self.timeout, 159 source_address=self.source_address) 160 self.af = self.sock.family 161 self.file = self.sock.makefile('r', encoding=self.encoding) 162 self.welcome = self.getresp() 163 return self.welcome 164 165 def getwelcome(self): 166 '''Get the welcome message from the server. 167 (this is read and squirreled away by connect())''' 168 if self.debugging: 169 print('*welcome*', self.sanitize(self.welcome)) 170 return self.welcome 171 172 def set_debuglevel(self, level): 173 '''Set the debugging level. 174 The required argument level means: 175 0: no debugging output (default) 176 1: print commands and responses but not body text etc. 177 2: also print raw lines read and sent before stripping CR/LF''' 178 self.debugging = level 179 debug = set_debuglevel 180 181 def set_pasv(self, val): 182 '''Use passive or active mode for data transfers. 183 With a false argument, use the normal PORT mode, 184 With a true argument, use the PASV command.''' 185 self.passiveserver = val 186 187 # Internal: "sanitize" a string for printing 188 def sanitize(self, s): 189 if s[:5] in {'pass ', 'PASS '}: 190 i = len(s.rstrip('\r\n')) 191 s = s[:5] + '*'*(i-5) + s[i:] 192 return repr(s) 193 194 # Internal: send one line to the server, appending CRLF 195 def putline(self, line): 196 if '\r' in line or '\n' in line: 197 raise ValueError('an illegal newline character should not be contained') 198 sys.audit("ftplib.sendcmd", self, line) 199 line = line + CRLF 200 if self.debugging > 1: 201 print('*put*', self.sanitize(line)) 202 self.sock.sendall(line.encode(self.encoding)) 203 204 # Internal: send one command to the server (through putline()) 205 def putcmd(self, line): 206 if self.debugging: print('*cmd*', self.sanitize(line)) 207 self.putline(line) 208 209 # Internal: return one line from the server, stripping CRLF. 210 # Raise EOFError if the connection is closed 211 def getline(self): 212 line = self.file.readline(self.maxline + 1) 213 if len(line) > self.maxline: 214 raise Error("got more than %d bytes" % self.maxline) 215 if self.debugging > 1: 216 print('*get*', self.sanitize(line)) 217 if not line: 218 raise EOFError 219 if line[-2:] == CRLF: 220 line = line[:-2] 221 elif line[-1:] in CRLF: 222 line = line[:-1] 223 return line 224 225 # Internal: get a response from the server, which may possibly 226 # consist of multiple lines. Return a single string with no 227 # trailing CRLF. If the response consists of multiple lines, 228 # these are separated by '\n' characters in the string 229 def getmultiline(self): 230 line = self.getline() 231 if line[3:4] == '-': 232 code = line[:3] 233 while 1: 234 nextline = self.getline() 235 line = line + ('\n' + nextline) 236 if nextline[:3] == code and \ 237 nextline[3:4] != '-': 238 break 239 return line 240 241 # Internal: get a response from the server. 242 # Raise various errors if the response indicates an error 243 def getresp(self): 244 resp = self.getmultiline() 245 if self.debugging: 246 print('*resp*', self.sanitize(resp)) 247 self.lastresp = resp[:3] 248 c = resp[:1] 249 if c in {'1', '2', '3'}: 250 return resp 251 if c == '4': 252 raise error_temp(resp) 253 if c == '5': 254 raise error_perm(resp) 255 raise error_proto(resp) 256 257 def voidresp(self): 258 """Expect a response beginning with '2'.""" 259 resp = self.getresp() 260 if resp[:1] != '2': 261 raise error_reply(resp) 262 return resp 263 264 def abort(self): 265 '''Abort a file transfer. Uses out-of-band data. 266 This does not follow the procedure from the RFC to send Telnet 267 IP and Synch; that doesn't seem to work with the servers I've 268 tried. Instead, just send the ABOR command as OOB data.''' 269 line = b'ABOR' + B_CRLF 270 if self.debugging > 1: 271 print('*put urgent*', self.sanitize(line)) 272 self.sock.sendall(line, MSG_OOB) 273 resp = self.getmultiline() 274 if resp[:3] not in {'426', '225', '226'}: 275 raise error_proto(resp) 276 return resp 277 278 def sendcmd(self, cmd): 279 '''Send a command and return the response.''' 280 self.putcmd(cmd) 281 return self.getresp() 282 283 def voidcmd(self, cmd): 284 """Send a command and expect a response beginning with '2'.""" 285 self.putcmd(cmd) 286 return self.voidresp() 287 288 def sendport(self, host, port): 289 '''Send a PORT command with the current host and the given 290 port number. 291 ''' 292 hbytes = host.split('.') 293 pbytes = [repr(port//256), repr(port%256)] 294 bytes = hbytes + pbytes 295 cmd = 'PORT ' + ','.join(bytes) 296 return self.voidcmd(cmd) 297 298 def sendeprt(self, host, port): 299 '''Send an EPRT command with the current host and the given port number.''' 300 af = 0 301 if self.af == socket.AF_INET: 302 af = 1 303 if self.af == socket.AF_INET6: 304 af = 2 305 if af == 0: 306 raise error_proto('unsupported address family') 307 fields = ['', repr(af), host, repr(port), ''] 308 cmd = 'EPRT ' + '|'.join(fields) 309 return self.voidcmd(cmd) 310 311 def makeport(self): 312 '''Create a new socket and send a PORT command for it.''' 313 sock = socket.create_server(("", 0), family=self.af, backlog=1) 314 port = sock.getsockname()[1] # Get proper port 315 host = self.sock.getsockname()[0] # Get proper host 316 if self.af == socket.AF_INET: 317 resp = self.sendport(host, port) 318 else: 319 resp = self.sendeprt(host, port) 320 if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT: 321 sock.settimeout(self.timeout) 322 return sock 323 324 def makepasv(self): 325 """Internal: Does the PASV or EPSV handshake -> (address, port)""" 326 if self.af == socket.AF_INET: 327 untrusted_host, port = parse227(self.sendcmd('PASV')) 328 if self.trust_server_pasv_ipv4_address: 329 host = untrusted_host 330 else: 331 host = self.sock.getpeername()[0] 332 else: 333 host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername()) 334 return host, port 335 336 def ntransfercmd(self, cmd, rest=None): 337 """Initiate a transfer over the data connection. 338 339 If the transfer is active, send a port command and the 340 transfer command, and accept the connection. If the server is 341 passive, send a pasv command, connect to it, and start the 342 transfer command. Either way, return the socket for the 343 connection and the expected size of the transfer. The 344 expected size may be None if it could not be determined. 345 346 Optional `rest' argument can be a string that is sent as the 347 argument to a REST command. This is essentially a server 348 marker used to tell the server to skip over any data up to the 349 given marker. 350 """ 351 size = None 352 if self.passiveserver: 353 host, port = self.makepasv() 354 conn = socket.create_connection((host, port), self.timeout, 355 source_address=self.source_address) 356 try: 357 if rest is not None: 358 self.sendcmd("REST %s" % rest) 359 resp = self.sendcmd(cmd) 360 # Some servers apparently send a 200 reply to 361 # a LIST or STOR command, before the 150 reply 362 # (and way before the 226 reply). This seems to 363 # be in violation of the protocol (which only allows 364 # 1xx or error messages for LIST), so we just discard 365 # this response. 366 if resp[0] == '2': 367 resp = self.getresp() 368 if resp[0] != '1': 369 raise error_reply(resp) 370 except: 371 conn.close() 372 raise 373 else: 374 with self.makeport() as sock: 375 if rest is not None: 376 self.sendcmd("REST %s" % rest) 377 resp = self.sendcmd(cmd) 378 # See above. 379 if resp[0] == '2': 380 resp = self.getresp() 381 if resp[0] != '1': 382 raise error_reply(resp) 383 conn, sockaddr = sock.accept() 384 if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT: 385 conn.settimeout(self.timeout) 386 if resp[:3] == '150': 387 # this is conditional in case we received a 125 388 size = parse150(resp) 389 return conn, size 390 391 def transfercmd(self, cmd, rest=None): 392 """Like ntransfercmd() but returns only the socket.""" 393 return self.ntransfercmd(cmd, rest)[0] 394 395 def login(self, user = '', passwd = '', acct = ''): 396 '''Login, default anonymous.''' 397 if not user: 398 user = 'anonymous' 399 if not passwd: 400 passwd = '' 401 if not acct: 402 acct = '' 403 if user == 'anonymous' and passwd in {'', '-'}: 404 # If there is no anonymous ftp password specified 405 # then we'll just use anonymous@ 406 # We don't send any other thing because: 407 # - We want to remain anonymous 408 # - We want to stop SPAM 409 # - We don't want to let ftp sites to discriminate by the user, 410 # host or country. 411 passwd = passwd + 'anonymous@' 412 resp = self.sendcmd('USER ' + user) 413 if resp[0] == '3': 414 resp = self.sendcmd('PASS ' + passwd) 415 if resp[0] == '3': 416 resp = self.sendcmd('ACCT ' + acct) 417 if resp[0] != '2': 418 raise error_reply(resp) 419 return resp 420 421 def retrbinary(self, cmd, callback, blocksize=8192, rest=None): 422 """Retrieve data in binary mode. A new port is created for you. 423 424 Args: 425 cmd: A RETR command. 426 callback: A single parameter callable to be called on each 427 block of data read. 428 blocksize: The maximum number of bytes to read from the 429 socket at one time. [default: 8192] 430 rest: Passed to transfercmd(). [default: None] 431 432 Returns: 433 The response code. 434 """ 435 self.voidcmd('TYPE I') 436 with self.transfercmd(cmd, rest) as conn: 437 while data := conn.recv(blocksize): 438 callback(data) 439 # shutdown ssl layer 440 if _SSLSocket is not None and isinstance(conn, _SSLSocket): 441 conn.unwrap() 442 return self.voidresp() 443 444 def retrlines(self, cmd, callback = None): 445 """Retrieve data in line mode. A new port is created for you. 446 447 Args: 448 cmd: A RETR, LIST, or NLST command. 449 callback: An optional single parameter callable that is called 450 for each line with the trailing CRLF stripped. 451 [default: print_line()] 452 453 Returns: 454 The response code. 455 """ 456 if callback is None: 457 callback = print_line 458 resp = self.sendcmd('TYPE A') 459 with self.transfercmd(cmd) as conn, \ 460 conn.makefile('r', encoding=self.encoding) as fp: 461 while 1: 462 line = fp.readline(self.maxline + 1) 463 if len(line) > self.maxline: 464 raise Error("got more than %d bytes" % self.maxline) 465 if self.debugging > 2: 466 print('*retr*', repr(line)) 467 if not line: 468 break 469 if line[-2:] == CRLF: 470 line = line[:-2] 471 elif line[-1:] == '\n': 472 line = line[:-1] 473 callback(line) 474 # shutdown ssl layer 475 if _SSLSocket is not None and isinstance(conn, _SSLSocket): 476 conn.unwrap() 477 return self.voidresp() 478 479 def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None): 480 """Store a file in binary mode. A new port is created for you. 481 482 Args: 483 cmd: A STOR command. 484 fp: A file-like object with a read(num_bytes) method. 485 blocksize: The maximum data size to read from fp and send over 486 the connection at once. [default: 8192] 487 callback: An optional single parameter callable that is called on 488 each block of data after it is sent. [default: None] 489 rest: Passed to transfercmd(). [default: None] 490 491 Returns: 492 The response code. 493 """ 494 self.voidcmd('TYPE I') 495 with self.transfercmd(cmd, rest) as conn: 496 while buf := fp.read(blocksize): 497 conn.sendall(buf) 498 if callback: 499 callback(buf) 500 # shutdown ssl layer 501 if _SSLSocket is not None and isinstance(conn, _SSLSocket): 502 conn.unwrap() 503 return self.voidresp() 504 505 def storlines(self, cmd, fp, callback=None): 506 """Store a file in line mode. A new port is created for you. 507 508 Args: 509 cmd: A STOR command. 510 fp: A file-like object with a readline() method. 511 callback: An optional single parameter callable that is called on 512 each line after it is sent. [default: None] 513 514 Returns: 515 The response code. 516 """ 517 self.voidcmd('TYPE A') 518 with self.transfercmd(cmd) as conn: 519 while 1: 520 buf = fp.readline(self.maxline + 1) 521 if len(buf) > self.maxline: 522 raise Error("got more than %d bytes" % self.maxline) 523 if not buf: 524 break 525 if buf[-2:] != B_CRLF: 526 if buf[-1] in B_CRLF: buf = buf[:-1] 527 buf = buf + B_CRLF 528 conn.sendall(buf) 529 if callback: 530 callback(buf) 531 # shutdown ssl layer 532 if _SSLSocket is not None and isinstance(conn, _SSLSocket): 533 conn.unwrap() 534 return self.voidresp() 535 536 def acct(self, password): 537 '''Send new account name.''' 538 cmd = 'ACCT ' + password 539 return self.voidcmd(cmd) 540 541 def nlst(self, *args): 542 '''Return a list of files in a given directory (default the current).''' 543 cmd = 'NLST' 544 for arg in args: 545 cmd = cmd + (' ' + arg) 546 files = [] 547 self.retrlines(cmd, files.append) 548 return files 549 550 def dir(self, *args): 551 '''List a directory in long form. 552 By default list current directory to stdout. 553 Optional last argument is callback function; all 554 non-empty arguments before it are concatenated to the 555 LIST command. (This *should* only be used for a pathname.)''' 556 cmd = 'LIST' 557 func = None 558 if args[-1:] and not isinstance(args[-1], str): 559 args, func = args[:-1], args[-1] 560 for arg in args: 561 if arg: 562 cmd = cmd + (' ' + arg) 563 self.retrlines(cmd, func) 564 565 def mlsd(self, path="", facts=[]): 566 '''List a directory in a standardized format by using MLSD 567 command (RFC-3659). If path is omitted the current directory 568 is assumed. "facts" is a list of strings representing the type 569 of information desired (e.g. ["type", "size", "perm"]). 570 571 Return a generator object yielding a tuple of two elements 572 for every file found in path. 573 First element is the file name, the second one is a dictionary 574 including a variable number of "facts" depending on the server 575 and whether "facts" argument has been provided. 576 ''' 577 if facts: 578 self.sendcmd("OPTS MLST " + ";".join(facts) + ";") 579 if path: 580 cmd = "MLSD %s" % path 581 else: 582 cmd = "MLSD" 583 lines = [] 584 self.retrlines(cmd, lines.append) 585 for line in lines: 586 facts_found, _, name = line.rstrip(CRLF).partition(' ') 587 entry = {} 588 for fact in facts_found[:-1].split(";"): 589 key, _, value = fact.partition("=") 590 entry[key.lower()] = value 591 yield (name, entry) 592 593 def rename(self, fromname, toname): 594 '''Rename a file.''' 595 resp = self.sendcmd('RNFR ' + fromname) 596 if resp[0] != '3': 597 raise error_reply(resp) 598 return self.voidcmd('RNTO ' + toname) 599 600 def delete(self, filename): 601 '''Delete a file.''' 602 resp = self.sendcmd('DELE ' + filename) 603 if resp[:3] in {'250', '200'}: 604 return resp 605 else: 606 raise error_reply(resp) 607 608 def cwd(self, dirname): 609 '''Change to a directory.''' 610 if dirname == '..': 611 try: 612 return self.voidcmd('CDUP') 613 except error_perm as msg: 614 if msg.args[0][:3] != '500': 615 raise 616 elif dirname == '': 617 dirname = '.' # does nothing, but could return error 618 cmd = 'CWD ' + dirname 619 return self.voidcmd(cmd) 620 621 def size(self, filename): 622 '''Retrieve the size of a file.''' 623 # The SIZE command is defined in RFC-3659 624 resp = self.sendcmd('SIZE ' + filename) 625 if resp[:3] == '213': 626 s = resp[3:].strip() 627 return int(s) 628 629 def mkd(self, dirname): 630 '''Make a directory, return its full pathname.''' 631 resp = self.voidcmd('MKD ' + dirname) 632 # fix around non-compliant implementations such as IIS shipped 633 # with Windows server 2003 634 if not resp.startswith('257'): 635 return '' 636 return parse257(resp) 637 638 def rmd(self, dirname): 639 '''Remove a directory.''' 640 return self.voidcmd('RMD ' + dirname) 641 642 def pwd(self): 643 '''Return current working directory.''' 644 resp = self.voidcmd('PWD') 645 # fix around non-compliant implementations such as IIS shipped 646 # with Windows server 2003 647 if not resp.startswith('257'): 648 return '' 649 return parse257(resp) 650 651 def quit(self): 652 '''Quit, and close the connection.''' 653 resp = self.voidcmd('QUIT') 654 self.close() 655 return resp 656 657 def close(self): 658 '''Close the connection without assuming anything about it.''' 659 try: 660 file = self.file 661 self.file = None 662 if file is not None: 663 file.close() 664 finally: 665 sock = self.sock 666 self.sock = None 667 if sock is not None: 668 sock.close() 669 670try: 671 import ssl 672except ImportError: 673 _SSLSocket = None 674else: 675 _SSLSocket = ssl.SSLSocket 676 677 class FTP_TLS(FTP): 678 '''A FTP subclass which adds TLS support to FTP as described 679 in RFC-4217. 680 681 Connect as usual to port 21 implicitly securing the FTP control 682 connection before authenticating. 683 684 Securing the data connection requires user to explicitly ask 685 for it by calling prot_p() method. 686 687 Usage example: 688 >>> from ftplib import FTP_TLS 689 >>> ftps = FTP_TLS('ftp.python.org') 690 >>> ftps.login() # login anonymously previously securing control channel 691 '230 Guest login ok, access restrictions apply.' 692 >>> ftps.prot_p() # switch to secure data connection 693 '200 Protection level set to P' 694 >>> ftps.retrlines('LIST') # list directory content securely 695 total 9 696 drwxr-xr-x 8 root wheel 1024 Jan 3 1994 . 697 drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .. 698 drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin 699 drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc 700 d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming 701 drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib 702 drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub 703 drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr 704 -rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg 705 '226 Transfer complete.' 706 >>> ftps.quit() 707 '221 Goodbye.' 708 >>> 709 ''' 710 711 def __init__(self, host='', user='', passwd='', acct='', 712 *, context=None, timeout=_GLOBAL_DEFAULT_TIMEOUT, 713 source_address=None, encoding='utf-8'): 714 if context is None: 715 context = ssl._create_stdlib_context() 716 self.context = context 717 self._prot_p = False 718 super().__init__(host, user, passwd, acct, 719 timeout, source_address, encoding=encoding) 720 721 def login(self, user='', passwd='', acct='', secure=True): 722 if secure and not isinstance(self.sock, ssl.SSLSocket): 723 self.auth() 724 return super().login(user, passwd, acct) 725 726 def auth(self): 727 '''Set up secure control connection by using TLS/SSL.''' 728 if isinstance(self.sock, ssl.SSLSocket): 729 raise ValueError("Already using TLS") 730 if self.context.protocol >= ssl.PROTOCOL_TLS: 731 resp = self.voidcmd('AUTH TLS') 732 else: 733 resp = self.voidcmd('AUTH SSL') 734 self.sock = self.context.wrap_socket(self.sock, server_hostname=self.host) 735 self.file = self.sock.makefile(mode='r', encoding=self.encoding) 736 return resp 737 738 def ccc(self): 739 '''Switch back to a clear-text control connection.''' 740 if not isinstance(self.sock, ssl.SSLSocket): 741 raise ValueError("not using TLS") 742 resp = self.voidcmd('CCC') 743 self.sock = self.sock.unwrap() 744 return resp 745 746 def prot_p(self): 747 '''Set up secure data connection.''' 748 # PROT defines whether or not the data channel is to be protected. 749 # Though RFC-2228 defines four possible protection levels, 750 # RFC-4217 only recommends two, Clear and Private. 751 # Clear (PROT C) means that no security is to be used on the 752 # data-channel, Private (PROT P) means that the data-channel 753 # should be protected by TLS. 754 # PBSZ command MUST still be issued, but must have a parameter of 755 # '0' to indicate that no buffering is taking place and the data 756 # connection should not be encapsulated. 757 self.voidcmd('PBSZ 0') 758 resp = self.voidcmd('PROT P') 759 self._prot_p = True 760 return resp 761 762 def prot_c(self): 763 '''Set up clear text data connection.''' 764 resp = self.voidcmd('PROT C') 765 self._prot_p = False 766 return resp 767 768 # --- Overridden FTP methods 769 770 def ntransfercmd(self, cmd, rest=None): 771 conn, size = super().ntransfercmd(cmd, rest) 772 if self._prot_p: 773 conn = self.context.wrap_socket(conn, 774 server_hostname=self.host) 775 return conn, size 776 777 def abort(self): 778 # overridden as we can't pass MSG_OOB flag to sendall() 779 line = b'ABOR' + B_CRLF 780 self.sock.sendall(line) 781 resp = self.getmultiline() 782 if resp[:3] not in {'426', '225', '226'}: 783 raise error_proto(resp) 784 return resp 785 786 __all__.append('FTP_TLS') 787 all_errors = (Error, OSError, EOFError, ssl.SSLError) 788 789 790_150_re = None 791 792def parse150(resp): 793 '''Parse the '150' response for a RETR request. 794 Returns the expected transfer size or None; size is not guaranteed to 795 be present in the 150 message. 796 ''' 797 if resp[:3] != '150': 798 raise error_reply(resp) 799 global _150_re 800 if _150_re is None: 801 import re 802 _150_re = re.compile( 803 r"150 .* \((\d+) bytes\)", re.IGNORECASE | re.ASCII) 804 m = _150_re.match(resp) 805 if not m: 806 return None 807 return int(m.group(1)) 808 809 810_227_re = None 811 812def parse227(resp): 813 '''Parse the '227' response for a PASV request. 814 Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)' 815 Return ('host.addr.as.numbers', port#) tuple.''' 816 if resp[:3] != '227': 817 raise error_reply(resp) 818 global _227_re 819 if _227_re is None: 820 import re 821 _227_re = re.compile(r'(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)', re.ASCII) 822 m = _227_re.search(resp) 823 if not m: 824 raise error_proto(resp) 825 numbers = m.groups() 826 host = '.'.join(numbers[:4]) 827 port = (int(numbers[4]) << 8) + int(numbers[5]) 828 return host, port 829 830 831def parse229(resp, peer): 832 '''Parse the '229' response for an EPSV request. 833 Raises error_proto if it does not contain '(|||port|)' 834 Return ('host.addr.as.numbers', port#) tuple.''' 835 if resp[:3] != '229': 836 raise error_reply(resp) 837 left = resp.find('(') 838 if left < 0: raise error_proto(resp) 839 right = resp.find(')', left + 1) 840 if right < 0: 841 raise error_proto(resp) # should contain '(|||port|)' 842 if resp[left + 1] != resp[right - 1]: 843 raise error_proto(resp) 844 parts = resp[left + 1:right].split(resp[left+1]) 845 if len(parts) != 5: 846 raise error_proto(resp) 847 host = peer[0] 848 port = int(parts[3]) 849 return host, port 850 851 852def parse257(resp): 853 '''Parse the '257' response for a MKD or PWD request. 854 This is a response to a MKD or PWD request: a directory name. 855 Returns the directoryname in the 257 reply.''' 856 if resp[:3] != '257': 857 raise error_reply(resp) 858 if resp[3:5] != ' "': 859 return '' # Not compliant to RFC 959, but UNIX ftpd does this 860 dirname = '' 861 i = 5 862 n = len(resp) 863 while i < n: 864 c = resp[i] 865 i = i+1 866 if c == '"': 867 if i >= n or resp[i] != '"': 868 break 869 i = i+1 870 dirname = dirname + c 871 return dirname 872 873 874def print_line(line): 875 '''Default retrlines callback to print a line.''' 876 print(line) 877 878 879def ftpcp(source, sourcename, target, targetname = '', type = 'I'): 880 '''Copy file from one FTP-instance to another.''' 881 if not targetname: 882 targetname = sourcename 883 type = 'TYPE ' + type 884 source.voidcmd(type) 885 target.voidcmd(type) 886 sourcehost, sourceport = parse227(source.sendcmd('PASV')) 887 target.sendport(sourcehost, sourceport) 888 # RFC 959: the user must "listen" [...] BEFORE sending the 889 # transfer request. 890 # So: STOR before RETR, because here the target is a "user". 891 treply = target.sendcmd('STOR ' + targetname) 892 if treply[:3] not in {'125', '150'}: 893 raise error_proto # RFC 959 894 sreply = source.sendcmd('RETR ' + sourcename) 895 if sreply[:3] not in {'125', '150'}: 896 raise error_proto # RFC 959 897 source.voidresp() 898 target.voidresp() 899 900 901def test(): 902 '''Test program. 903 Usage: ftplib [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ... 904 905 Options: 906 -d increase debugging level 907 -r[file] set alternate ~/.netrc file 908 909 Commands: 910 -l[dir] list directory 911 -d[dir] change the current directory 912 -p toggle passive and active mode 913 file retrieve the file and write it to stdout 914 ''' 915 916 if len(sys.argv) < 2: 917 print(test.__doc__) 918 sys.exit(0) 919 920 import netrc 921 922 debugging = 0 923 rcfile = None 924 while sys.argv[1] == '-d': 925 debugging = debugging+1 926 del sys.argv[1] 927 if sys.argv[1][:2] == '-r': 928 # get name of alternate ~/.netrc file: 929 rcfile = sys.argv[1][2:] 930 del sys.argv[1] 931 host = sys.argv[1] 932 ftp = FTP(host) 933 ftp.set_debuglevel(debugging) 934 userid = passwd = acct = '' 935 try: 936 netrcobj = netrc.netrc(rcfile) 937 except OSError: 938 if rcfile is not None: 939 print("Could not open account file -- using anonymous login.", 940 file=sys.stderr) 941 else: 942 try: 943 userid, acct, passwd = netrcobj.authenticators(host) 944 except (KeyError, TypeError): 945 # no account for host 946 print("No account -- using anonymous login.", file=sys.stderr) 947 ftp.login(userid, passwd, acct) 948 for file in sys.argv[2:]: 949 if file[:2] == '-l': 950 ftp.dir(file[2:]) 951 elif file[:2] == '-d': 952 cmd = 'CWD' 953 if file[2:]: cmd = cmd + ' ' + file[2:] 954 resp = ftp.sendcmd(cmd) 955 elif file == '-p': 956 ftp.set_pasv(not ftp.passiveserver) 957 else: 958 ftp.retrbinary('RETR ' + file, \ 959 sys.stdout.buffer.write, 1024) 960 sys.stdout.buffer.flush() 961 sys.stdout.flush() 962 ftp.quit() 963 964 965if __name__ == '__main__': 966 test() 967