(root)/
Python-3.11.7/
Lib/
ftplib.py
       1  """An FTP client class and some helper functions.
       2  
       3  Based on RFC 959: File Transfer Protocol (FTP), by J. Postel and J. Reynolds
       4  
       5  Example:
       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
      12  total 9
      13  drwxr-xr-x   8 root     wheel        1024 Jan  3  1994 .
      14  drwxr-xr-x   8 root     wheel        1024 Jan  3  1994 ..
      15  drwxr-xr-x   2 root     wheel        1024 Jan  3  1994 bin
      16  drwxr-xr-x   2 root     wheel        1024 Jan  3  1994 etc
      17  d-wxrwxr-x   2 ftp      wheel        1024 Sep  5 13:43 incoming
      18  drwxr-xr-x   2 root     wheel        1024 Nov 17  1993 lib
      19  drwxr-xr-x   6 1094     wheel        1024 Sep 13 19:07 pub
      20  drwxr-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  
      27  A nice test that reveals some of the network dialogue would be:
      28  python 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  
      39  import sys
      40  import socket
      41  from 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>
      47  MSG_OOB = 0x1                           # Process data out of band
      48  
      49  
      50  # The standard FTP server control port
      51  FTP_PORT = 21
      52  # The sizehint parameter passed to readline() calls
      53  MAXLINE = 8192
      54  
      55  
      56  # Exception raised when an error or invalid response is received
      57  class ESC[4;38;5;81mError(ESC[4;38;5;149mException): pass
      58  class ESC[4;38;5;81merror_reply(ESC[4;38;5;149mError): pass          # unexpected [123]xx reply
      59  class ESC[4;38;5;81merror_temp(ESC[4;38;5;149mError): pass           # 4xx errors
      60  class ESC[4;38;5;81merror_perm(ESC[4;38;5;149mError): pass           # 5xx errors
      61  class ESC[4;38;5;81merror_proto(ESC[4;38;5;149mError): 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
      66  all_errors = (Error, OSError, EOFError)
      67  
      68  
      69  # Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
      70  CRLF = '\r\n'
      71  B_CRLF = b'\r\n'
      72  
      73  # The class itself
      74  class ESC[4;38;5;81mFTP:
      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 1:
     438                  data = conn.recv(blocksize)
     439                  if not data:
     440                      break
     441                  callback(data)
     442              # shutdown ssl layer
     443              if _SSLSocket is not None and isinstance(conn, _SSLSocket):
     444                  conn.unwrap()
     445          return self.voidresp()
     446  
     447      def retrlines(self, cmd, callback = None):
     448          """Retrieve data in line mode.  A new port is created for you.
     449  
     450          Args:
     451            cmd: A RETR, LIST, or NLST command.
     452            callback: An optional single parameter callable that is called
     453                      for each line with the trailing CRLF stripped.
     454                      [default: print_line()]
     455  
     456          Returns:
     457            The response code.
     458          """
     459          if callback is None:
     460              callback = print_line
     461          resp = self.sendcmd('TYPE A')
     462          with self.transfercmd(cmd) as conn, \
     463                   conn.makefile('r', encoding=self.encoding) as fp:
     464              while 1:
     465                  line = fp.readline(self.maxline + 1)
     466                  if len(line) > self.maxline:
     467                      raise Error("got more than %d bytes" % self.maxline)
     468                  if self.debugging > 2:
     469                      print('*retr*', repr(line))
     470                  if not line:
     471                      break
     472                  if line[-2:] == CRLF:
     473                      line = line[:-2]
     474                  elif line[-1:] == '\n':
     475                      line = line[:-1]
     476                  callback(line)
     477              # shutdown ssl layer
     478              if _SSLSocket is not None and isinstance(conn, _SSLSocket):
     479                  conn.unwrap()
     480          return self.voidresp()
     481  
     482      def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
     483          """Store a file in binary mode.  A new port is created for you.
     484  
     485          Args:
     486            cmd: A STOR command.
     487            fp: A file-like object with a read(num_bytes) method.
     488            blocksize: The maximum data size to read from fp and send over
     489                       the connection at once.  [default: 8192]
     490            callback: An optional single parameter callable that is called on
     491                      each block of data after it is sent.  [default: None]
     492            rest: Passed to transfercmd().  [default: None]
     493  
     494          Returns:
     495            The response code.
     496          """
     497          self.voidcmd('TYPE I')
     498          with self.transfercmd(cmd, rest) as conn:
     499              while 1:
     500                  buf = fp.read(blocksize)
     501                  if not buf:
     502                      break
     503                  conn.sendall(buf)
     504                  if callback:
     505                      callback(buf)
     506              # shutdown ssl layer
     507              if _SSLSocket is not None and isinstance(conn, _SSLSocket):
     508                  conn.unwrap()
     509          return self.voidresp()
     510  
     511      def storlines(self, cmd, fp, callback=None):
     512          """Store a file in line mode.  A new port is created for you.
     513  
     514          Args:
     515            cmd: A STOR command.
     516            fp: A file-like object with a readline() method.
     517            callback: An optional single parameter callable that is called on
     518                      each line after it is sent.  [default: None]
     519  
     520          Returns:
     521            The response code.
     522          """
     523          self.voidcmd('TYPE A')
     524          with self.transfercmd(cmd) as conn:
     525              while 1:
     526                  buf = fp.readline(self.maxline + 1)
     527                  if len(buf) > self.maxline:
     528                      raise Error("got more than %d bytes" % self.maxline)
     529                  if not buf:
     530                      break
     531                  if buf[-2:] != B_CRLF:
     532                      if buf[-1] in B_CRLF: buf = buf[:-1]
     533                      buf = buf + B_CRLF
     534                  conn.sendall(buf)
     535                  if callback:
     536                      callback(buf)
     537              # shutdown ssl layer
     538              if _SSLSocket is not None and isinstance(conn, _SSLSocket):
     539                  conn.unwrap()
     540          return self.voidresp()
     541  
     542      def acct(self, password):
     543          '''Send new account name.'''
     544          cmd = 'ACCT ' + password
     545          return self.voidcmd(cmd)
     546  
     547      def nlst(self, *args):
     548          '''Return a list of files in a given directory (default the current).'''
     549          cmd = 'NLST'
     550          for arg in args:
     551              cmd = cmd + (' ' + arg)
     552          files = []
     553          self.retrlines(cmd, files.append)
     554          return files
     555  
     556      def dir(self, *args):
     557          '''List a directory in long form.
     558          By default list current directory to stdout.
     559          Optional last argument is callback function; all
     560          non-empty arguments before it are concatenated to the
     561          LIST command.  (This *should* only be used for a pathname.)'''
     562          cmd = 'LIST'
     563          func = None
     564          if args[-1:] and type(args[-1]) != type(''):
     565              args, func = args[:-1], args[-1]
     566          for arg in args:
     567              if arg:
     568                  cmd = cmd + (' ' + arg)
     569          self.retrlines(cmd, func)
     570  
     571      def mlsd(self, path="", facts=[]):
     572          '''List a directory in a standardized format by using MLSD
     573          command (RFC-3659). If path is omitted the current directory
     574          is assumed. "facts" is a list of strings representing the type
     575          of information desired (e.g. ["type", "size", "perm"]).
     576  
     577          Return a generator object yielding a tuple of two elements
     578          for every file found in path.
     579          First element is the file name, the second one is a dictionary
     580          including a variable number of "facts" depending on the server
     581          and whether "facts" argument has been provided.
     582          '''
     583          if facts:
     584              self.sendcmd("OPTS MLST " + ";".join(facts) + ";")
     585          if path:
     586              cmd = "MLSD %s" % path
     587          else:
     588              cmd = "MLSD"
     589          lines = []
     590          self.retrlines(cmd, lines.append)
     591          for line in lines:
     592              facts_found, _, name = line.rstrip(CRLF).partition(' ')
     593              entry = {}
     594              for fact in facts_found[:-1].split(";"):
     595                  key, _, value = fact.partition("=")
     596                  entry[key.lower()] = value
     597              yield (name, entry)
     598  
     599      def rename(self, fromname, toname):
     600          '''Rename a file.'''
     601          resp = self.sendcmd('RNFR ' + fromname)
     602          if resp[0] != '3':
     603              raise error_reply(resp)
     604          return self.voidcmd('RNTO ' + toname)
     605  
     606      def delete(self, filename):
     607          '''Delete a file.'''
     608          resp = self.sendcmd('DELE ' + filename)
     609          if resp[:3] in {'250', '200'}:
     610              return resp
     611          else:
     612              raise error_reply(resp)
     613  
     614      def cwd(self, dirname):
     615          '''Change to a directory.'''
     616          if dirname == '..':
     617              try:
     618                  return self.voidcmd('CDUP')
     619              except error_perm as msg:
     620                  if msg.args[0][:3] != '500':
     621                      raise
     622          elif dirname == '':
     623              dirname = '.'  # does nothing, but could return error
     624          cmd = 'CWD ' + dirname
     625          return self.voidcmd(cmd)
     626  
     627      def size(self, filename):
     628          '''Retrieve the size of a file.'''
     629          # The SIZE command is defined in RFC-3659
     630          resp = self.sendcmd('SIZE ' + filename)
     631          if resp[:3] == '213':
     632              s = resp[3:].strip()
     633              return int(s)
     634  
     635      def mkd(self, dirname):
     636          '''Make a directory, return its full pathname.'''
     637          resp = self.voidcmd('MKD ' + dirname)
     638          # fix around non-compliant implementations such as IIS shipped
     639          # with Windows server 2003
     640          if not resp.startswith('257'):
     641              return ''
     642          return parse257(resp)
     643  
     644      def rmd(self, dirname):
     645          '''Remove a directory.'''
     646          return self.voidcmd('RMD ' + dirname)
     647  
     648      def pwd(self):
     649          '''Return current working directory.'''
     650          resp = self.voidcmd('PWD')
     651          # fix around non-compliant implementations such as IIS shipped
     652          # with Windows server 2003
     653          if not resp.startswith('257'):
     654              return ''
     655          return parse257(resp)
     656  
     657      def quit(self):
     658          '''Quit, and close the connection.'''
     659          resp = self.voidcmd('QUIT')
     660          self.close()
     661          return resp
     662  
     663      def close(self):
     664          '''Close the connection without assuming anything about it.'''
     665          try:
     666              file = self.file
     667              self.file = None
     668              if file is not None:
     669                  file.close()
     670          finally:
     671              sock = self.sock
     672              self.sock = None
     673              if sock is not None:
     674                  sock.close()
     675  
     676  try:
     677      import ssl
     678  except ImportError:
     679      _SSLSocket = None
     680  else:
     681      _SSLSocket = ssl.SSLSocket
     682  
     683      class ESC[4;38;5;81mFTP_TLS(ESC[4;38;5;149mFTP):
     684          '''A FTP subclass which adds TLS support to FTP as described
     685          in RFC-4217.
     686  
     687          Connect as usual to port 21 implicitly securing the FTP control
     688          connection before authenticating.
     689  
     690          Securing the data connection requires user to explicitly ask
     691          for it by calling prot_p() method.
     692  
     693          Usage example:
     694          >>> from ftplib import FTP_TLS
     695          >>> ftps = FTP_TLS('ftp.python.org')
     696          >>> ftps.login()  # login anonymously previously securing control channel
     697          '230 Guest login ok, access restrictions apply.'
     698          >>> ftps.prot_p()  # switch to secure data connection
     699          '200 Protection level set to P'
     700          >>> ftps.retrlines('LIST')  # list directory content securely
     701          total 9
     702          drwxr-xr-x   8 root     wheel        1024 Jan  3  1994 .
     703          drwxr-xr-x   8 root     wheel        1024 Jan  3  1994 ..
     704          drwxr-xr-x   2 root     wheel        1024 Jan  3  1994 bin
     705          drwxr-xr-x   2 root     wheel        1024 Jan  3  1994 etc
     706          d-wxrwxr-x   2 ftp      wheel        1024 Sep  5 13:43 incoming
     707          drwxr-xr-x   2 root     wheel        1024 Nov 17  1993 lib
     708          drwxr-xr-x   6 1094     wheel        1024 Sep 13 19:07 pub
     709          drwxr-xr-x   3 root     wheel        1024 Jan  3  1994 usr
     710          -rw-r--r--   1 root     root          312 Aug  1  1994 welcome.msg
     711          '226 Transfer complete.'
     712          >>> ftps.quit()
     713          '221 Goodbye.'
     714          >>>
     715          '''
     716          ssl_version = ssl.PROTOCOL_TLS_CLIENT
     717  
     718          def __init__(self, host='', user='', passwd='', acct='',
     719                       keyfile=None, certfile=None, context=None,
     720                       timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None, *,
     721                       encoding='utf-8'):
     722              if context is not None and keyfile is not None:
     723                  raise ValueError("context and keyfile arguments are mutually "
     724                                   "exclusive")
     725              if context is not None and certfile is not None:
     726                  raise ValueError("context and certfile arguments are mutually "
     727                                   "exclusive")
     728              if keyfile is not None or certfile is not None:
     729                  import warnings
     730                  warnings.warn("keyfile and certfile are deprecated, use a "
     731                                "custom context instead", DeprecationWarning, 2)
     732              self.keyfile = keyfile
     733              self.certfile = certfile
     734              if context is None:
     735                  context = ssl._create_stdlib_context(self.ssl_version,
     736                                                       certfile=certfile,
     737                                                       keyfile=keyfile)
     738              self.context = context
     739              self._prot_p = False
     740              super().__init__(host, user, passwd, acct,
     741                               timeout, source_address, encoding=encoding)
     742  
     743          def login(self, user='', passwd='', acct='', secure=True):
     744              if secure and not isinstance(self.sock, ssl.SSLSocket):
     745                  self.auth()
     746              return super().login(user, passwd, acct)
     747  
     748          def auth(self):
     749              '''Set up secure control connection by using TLS/SSL.'''
     750              if isinstance(self.sock, ssl.SSLSocket):
     751                  raise ValueError("Already using TLS")
     752              if self.ssl_version >= ssl.PROTOCOL_TLS:
     753                  resp = self.voidcmd('AUTH TLS')
     754              else:
     755                  resp = self.voidcmd('AUTH SSL')
     756              self.sock = self.context.wrap_socket(self.sock, server_hostname=self.host)
     757              self.file = self.sock.makefile(mode='r', encoding=self.encoding)
     758              return resp
     759  
     760          def ccc(self):
     761              '''Switch back to a clear-text control connection.'''
     762              if not isinstance(self.sock, ssl.SSLSocket):
     763                  raise ValueError("not using TLS")
     764              resp = self.voidcmd('CCC')
     765              self.sock = self.sock.unwrap()
     766              return resp
     767  
     768          def prot_p(self):
     769              '''Set up secure data connection.'''
     770              # PROT defines whether or not the data channel is to be protected.
     771              # Though RFC-2228 defines four possible protection levels,
     772              # RFC-4217 only recommends two, Clear and Private.
     773              # Clear (PROT C) means that no security is to be used on the
     774              # data-channel, Private (PROT P) means that the data-channel
     775              # should be protected by TLS.
     776              # PBSZ command MUST still be issued, but must have a parameter of
     777              # '0' to indicate that no buffering is taking place and the data
     778              # connection should not be encapsulated.
     779              self.voidcmd('PBSZ 0')
     780              resp = self.voidcmd('PROT P')
     781              self._prot_p = True
     782              return resp
     783  
     784          def prot_c(self):
     785              '''Set up clear text data connection.'''
     786              resp = self.voidcmd('PROT C')
     787              self._prot_p = False
     788              return resp
     789  
     790          # --- Overridden FTP methods
     791  
     792          def ntransfercmd(self, cmd, rest=None):
     793              conn, size = super().ntransfercmd(cmd, rest)
     794              if self._prot_p:
     795                  conn = self.context.wrap_socket(conn,
     796                                                  server_hostname=self.host)
     797              return conn, size
     798  
     799          def abort(self):
     800              # overridden as we can't pass MSG_OOB flag to sendall()
     801              line = b'ABOR' + B_CRLF
     802              self.sock.sendall(line)
     803              resp = self.getmultiline()
     804              if resp[:3] not in {'426', '225', '226'}:
     805                  raise error_proto(resp)
     806              return resp
     807  
     808      __all__.append('FTP_TLS')
     809      all_errors = (Error, OSError, EOFError, ssl.SSLError)
     810  
     811  
     812  _150_re = None
     813  
     814  def parse150(resp):
     815      '''Parse the '150' response for a RETR request.
     816      Returns the expected transfer size or None; size is not guaranteed to
     817      be present in the 150 message.
     818      '''
     819      if resp[:3] != '150':
     820          raise error_reply(resp)
     821      global _150_re
     822      if _150_re is None:
     823          import re
     824          _150_re = re.compile(
     825              r"150 .* \((\d+) bytes\)", re.IGNORECASE | re.ASCII)
     826      m = _150_re.match(resp)
     827      if not m:
     828          return None
     829      return int(m.group(1))
     830  
     831  
     832  _227_re = None
     833  
     834  def parse227(resp):
     835      '''Parse the '227' response for a PASV request.
     836      Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
     837      Return ('host.addr.as.numbers', port#) tuple.'''
     838      if resp[:3] != '227':
     839          raise error_reply(resp)
     840      global _227_re
     841      if _227_re is None:
     842          import re
     843          _227_re = re.compile(r'(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)', re.ASCII)
     844      m = _227_re.search(resp)
     845      if not m:
     846          raise error_proto(resp)
     847      numbers = m.groups()
     848      host = '.'.join(numbers[:4])
     849      port = (int(numbers[4]) << 8) + int(numbers[5])
     850      return host, port
     851  
     852  
     853  def parse229(resp, peer):
     854      '''Parse the '229' response for an EPSV request.
     855      Raises error_proto if it does not contain '(|||port|)'
     856      Return ('host.addr.as.numbers', port#) tuple.'''
     857      if resp[:3] != '229':
     858          raise error_reply(resp)
     859      left = resp.find('(')
     860      if left < 0: raise error_proto(resp)
     861      right = resp.find(')', left + 1)
     862      if right < 0:
     863          raise error_proto(resp) # should contain '(|||port|)'
     864      if resp[left + 1] != resp[right - 1]:
     865          raise error_proto(resp)
     866      parts = resp[left + 1:right].split(resp[left+1])
     867      if len(parts) != 5:
     868          raise error_proto(resp)
     869      host = peer[0]
     870      port = int(parts[3])
     871      return host, port
     872  
     873  
     874  def parse257(resp):
     875      '''Parse the '257' response for a MKD or PWD request.
     876      This is a response to a MKD or PWD request: a directory name.
     877      Returns the directoryname in the 257 reply.'''
     878      if resp[:3] != '257':
     879          raise error_reply(resp)
     880      if resp[3:5] != ' "':
     881          return '' # Not compliant to RFC 959, but UNIX ftpd does this
     882      dirname = ''
     883      i = 5
     884      n = len(resp)
     885      while i < n:
     886          c = resp[i]
     887          i = i+1
     888          if c == '"':
     889              if i >= n or resp[i] != '"':
     890                  break
     891              i = i+1
     892          dirname = dirname + c
     893      return dirname
     894  
     895  
     896  def print_line(line):
     897      '''Default retrlines callback to print a line.'''
     898      print(line)
     899  
     900  
     901  def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
     902      '''Copy file from one FTP-instance to another.'''
     903      if not targetname:
     904          targetname = sourcename
     905      type = 'TYPE ' + type
     906      source.voidcmd(type)
     907      target.voidcmd(type)
     908      sourcehost, sourceport = parse227(source.sendcmd('PASV'))
     909      target.sendport(sourcehost, sourceport)
     910      # RFC 959: the user must "listen" [...] BEFORE sending the
     911      # transfer request.
     912      # So: STOR before RETR, because here the target is a "user".
     913      treply = target.sendcmd('STOR ' + targetname)
     914      if treply[:3] not in {'125', '150'}:
     915          raise error_proto  # RFC 959
     916      sreply = source.sendcmd('RETR ' + sourcename)
     917      if sreply[:3] not in {'125', '150'}:
     918          raise error_proto  # RFC 959
     919      source.voidresp()
     920      target.voidresp()
     921  
     922  
     923  def test():
     924      '''Test program.
     925      Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...
     926  
     927      -d dir
     928      -l list
     929      -p password
     930      '''
     931  
     932      if len(sys.argv) < 2:
     933          print(test.__doc__)
     934          sys.exit(0)
     935  
     936      import netrc
     937  
     938      debugging = 0
     939      rcfile = None
     940      while sys.argv[1] == '-d':
     941          debugging = debugging+1
     942          del sys.argv[1]
     943      if sys.argv[1][:2] == '-r':
     944          # get name of alternate ~/.netrc file:
     945          rcfile = sys.argv[1][2:]
     946          del sys.argv[1]
     947      host = sys.argv[1]
     948      ftp = FTP(host)
     949      ftp.set_debuglevel(debugging)
     950      userid = passwd = acct = ''
     951      try:
     952          netrcobj = netrc.netrc(rcfile)
     953      except OSError:
     954          if rcfile is not None:
     955              sys.stderr.write("Could not open account file"
     956                               " -- using anonymous login.")
     957      else:
     958          try:
     959              userid, acct, passwd = netrcobj.authenticators(host)
     960          except KeyError:
     961              # no account for host
     962              sys.stderr.write(
     963                      "No account -- using anonymous login.")
     964      ftp.login(userid, passwd, acct)
     965      for file in sys.argv[2:]:
     966          if file[:2] == '-l':
     967              ftp.dir(file[2:])
     968          elif file[:2] == '-d':
     969              cmd = 'CWD'
     970              if file[2:]: cmd = cmd + ' ' + file[2:]
     971              resp = ftp.sendcmd(cmd)
     972          elif file == '-p':
     973              ftp.set_pasv(not ftp.passiveserver)
     974          else:
     975              ftp.retrbinary('RETR ' + file, \
     976                             sys.stdout.write, 1024)
     977      ftp.quit()
     978  
     979  
     980  if __name__ == '__main__':
     981      test()