(root)/
Python-3.12.0/
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 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  
     670  try:
     671      import ssl
     672  except ImportError:
     673      _SSLSocket = None
     674  else:
     675      _SSLSocket = ssl.SSLSocket
     676  
     677      class ESC[4;38;5;81mFTP_TLS(ESC[4;38;5;149mFTP):
     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  
     792  def 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  
     812  def 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  
     831  def 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  
     852  def 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  
     874  def print_line(line):
     875      '''Default retrlines callback to print a line.'''
     876      print(line)
     877  
     878  
     879  def 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  
     901  def test():
     902      '''Test program.
     903      Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...
     904  
     905      -d dir
     906      -l list
     907      -p password
     908      '''
     909  
     910      if len(sys.argv) < 2:
     911          print(test.__doc__)
     912          sys.exit(0)
     913  
     914      import netrc
     915  
     916      debugging = 0
     917      rcfile = None
     918      while sys.argv[1] == '-d':
     919          debugging = debugging+1
     920          del sys.argv[1]
     921      if sys.argv[1][:2] == '-r':
     922          # get name of alternate ~/.netrc file:
     923          rcfile = sys.argv[1][2:]
     924          del sys.argv[1]
     925      host = sys.argv[1]
     926      ftp = FTP(host)
     927      ftp.set_debuglevel(debugging)
     928      userid = passwd = acct = ''
     929      try:
     930          netrcobj = netrc.netrc(rcfile)
     931      except OSError:
     932          if rcfile is not None:
     933              sys.stderr.write("Could not open account file"
     934                               " -- using anonymous login.")
     935      else:
     936          try:
     937              userid, acct, passwd = netrcobj.authenticators(host)
     938          except KeyError:
     939              # no account for host
     940              sys.stderr.write(
     941                      "No account -- using anonymous login.")
     942      ftp.login(userid, passwd, acct)
     943      for file in sys.argv[2:]:
     944          if file[:2] == '-l':
     945              ftp.dir(file[2:])
     946          elif file[:2] == '-d':
     947              cmd = 'CWD'
     948              if file[2:]: cmd = cmd + ' ' + file[2:]
     949              resp = ftp.sendcmd(cmd)
     950          elif file == '-p':
     951              ftp.set_pasv(not ftp.passiveserver)
     952          else:
     953              ftp.retrbinary('RETR ' + file, \
     954                             sys.stdout.write, 1024)
     955      ftp.quit()
     956  
     957  
     958  if __name__ == '__main__':
     959      test()