diff options
Diffstat (limited to 'Lib/ftplib.py')
| -rw-r--r-- | Lib/ftplib.py | 107 | 
1 files changed, 63 insertions, 44 deletions
| diff --git a/Lib/ftplib.py b/Lib/ftplib.py index 8e53023..18887a6 100644 --- a/Lib/ftplib.py +++ b/Lib/ftplib.py @@ -100,14 +100,15 @@ class FTP:      file = None      welcome = None      passiveserver = 1 -    encoding = "latin1" +    encoding = "latin-1"      # Initialization method (called by class instantiation).      # Initialize host to localhost, port to standard ftp port      # Optional arguments are host (for connect()),      # and user, passwd, acct (for login())      def __init__(self, host='', user='', passwd='', acct='', -                 timeout=_GLOBAL_DEFAULT_TIMEOUT): +                 timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None): +        self.source_address = source_address          self.timeout = timeout          if host:              self.connect(host) @@ -128,10 +129,12 @@ class FTP:                  if self.sock is not None:                      self.close() -    def connect(self, host='', port=0, timeout=-999): +    def connect(self, host='', port=0, timeout=-999, source_address=None):          '''Connect to host.  Arguments are:           - host: hostname to connect to (string, default previous host)           - port: port to connect to (integer, default previous port) +         - source_address: a 2-tuple (host, port) for the socket to bind +           to as its source address before connecting.          '''          if host != '':              self.host = host @@ -139,7 +142,10 @@ class FTP:              self.port = port          if timeout != -999:              self.timeout = timeout -        self.sock = socket.create_connection((self.host, self.port), self.timeout) +        if source_address is not None: +            self.source_address = source_address +        self.sock = socket.create_connection((self.host, self.port), self.timeout, +                                             source_address=self.source_address)          self.af = self.sock.family          self.file = self.sock.makefile('r', encoding=self.encoding)          self.welcome = self.getresp() @@ -169,10 +175,8 @@ class FTP:      # Internal: "sanitize" a string for printing      def sanitize(self, s): -        if s[:5] == 'pass ' or s[:5] == 'PASS ': -            i = len(s) -            while i > 5 and s[i-1] in {'\r', '\n'}: -                i = i-1 +        if s[:5] in {'pass ', 'PASS '}: +            i = len(s.rstrip('\r\n'))              s = s[:5] + '*'*(i-5) + s[i:]          return repr(s) @@ -335,7 +339,8 @@ class FTP:          size = None          if self.passiveserver:              host, port = self.makepasv() -            conn = socket.create_connection((host, port), self.timeout) +            conn = socket.create_connection((host, port), self.timeout, +                                            source_address=self.source_address)              try:                  if rest is not None:                      self.sendcmd("REST %s" % rest) @@ -354,8 +359,7 @@ class FTP:                  conn.close()                  raise          else: -            sock = self.makeport() -            try: +            with self.makeport() as sock:                  if rest is not None:                      self.sendcmd("REST %s" % rest)                  resp = self.sendcmd(cmd) @@ -367,8 +371,6 @@ class FTP:                  conn, sockaddr = sock.accept()                  if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT:                      conn.settimeout(self.timeout) -            finally: -                sock.close()          if resp[:3] == '150':              # this is conditional in case we received a 125              size = parse150(resp) @@ -426,7 +428,7 @@ class FTP:          """Retrieve data in line mode.  A new port is created for you.          Args: -          cmd: A RETR, LIST, NLST, or MLSD command. +          cmd: A RETR, LIST, or NLST command.            callback: An optional single parameter callable that is called                      for each line with the trailing CRLF stripped.                      [default: print_line()] @@ -527,6 +529,34 @@ class FTP:                  cmd = cmd + (' ' + arg)          self.retrlines(cmd, func) +    def mlsd(self, path="", facts=[]): +        '''List a directory in a standardized format by using MLSD +        command (RFC-3659). If path is omitted the current directory +        is assumed. "facts" is a list of strings representing the type +        of information desired (e.g. ["type", "size", "perm"]). + +        Return a generator object yielding a tuple of two elements +        for every file found in path. +        First element is the file name, the second one is a dictionary +        including a variable number of "facts" depending on the server +        and whether "facts" argument has been provided. +        ''' +        if facts: +            self.sendcmd("OPTS MLST " + ";".join(facts) + ";") +        if path: +            cmd = "MLSD %s" % path +        else: +            cmd = "MLSD" +        lines = [] +        self.retrlines(cmd, lines.append) +        for line in lines: +            facts_found, _, name = line.rstrip(CRLF).partition(' ') +            entry = {} +            for fact in facts_found[:-1].split(";"): +                key, _, value = fact.partition("=") +                entry[key.lower()] = value +            yield (name, entry) +      def rename(self, fromname, toname):          '''Rename a file.'''          resp = self.sendcmd('RNFR ' + fromname) @@ -561,10 +591,7 @@ class FTP:          resp = self.sendcmd('SIZE ' + filename)          if resp[:3] == '213':              s = resp[3:].strip() -            try: -                return int(s) -            except (OverflowError, ValueError): -                return int(s) +            return int(s)      def mkd(self, dirname):          '''Make a directory, return its full pathname.''' @@ -596,11 +623,11 @@ class FTP:      def close(self):          '''Close the connection without assuming anything about it.''' -        if self.file: +        if self.file is not None:              self.file.close() +        if self.sock is not None:              self.sock.close() -            self.file = self.sock = None - +        self.file = self.sock = None  try:      import ssl @@ -644,7 +671,7 @@ else:          def __init__(self, host='', user='', passwd='', acct='', keyfile=None,                       certfile=None, context=None, -                     timeout=_GLOBAL_DEFAULT_TIMEOUT): +                     timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None):              if context is not None and keyfile is not None:                  raise ValueError("context and keyfile arguments are mutually "                                   "exclusive") @@ -655,7 +682,7 @@ else:              self.certfile = certfile              self.context = context              self._prot_p = False -            FTP.__init__(self, host, user, passwd, acct, timeout) +            FTP.__init__(self, host, user, passwd, acct, timeout, source_address)          def login(self, user='', passwd='', acct='', secure=True):              if secure and not isinstance(self.sock, ssl.SSLSocket): @@ -679,6 +706,14 @@ else:              self.file = self.sock.makefile(mode='r', encoding=self.encoding)              return resp +        def ccc(self): +            '''Switch back to a clear-text control connection.''' +            if not isinstance(self.sock, ssl.SSLSocket): +                raise ValueError("not using TLS") +            resp = self.voidcmd('CCC') +            self.sock = self.sock.unwrap() +            return resp +          def prot_p(self):              '''Set up secure data connection.'''              # PROT defines whether or not the data channel is to be protected. @@ -715,8 +750,7 @@ else:          def retrbinary(self, cmd, callback, blocksize=8192, rest=None):              self.voidcmd('TYPE I') -            conn = self.transfercmd(cmd, rest) -            try: +            with self.transfercmd(cmd, rest) as conn:                  while 1:                      data = conn.recv(blocksize)                      if not data: @@ -725,8 +759,6 @@ else:                  # shutdown ssl layer                  if isinstance(conn, ssl.SSLSocket):                      conn.unwrap() -            finally: -                conn.close()              return self.voidresp()          def retrlines(self, cmd, callback = None): @@ -734,7 +766,7 @@ else:              resp = self.sendcmd('TYPE A')              conn = self.transfercmd(cmd)              fp = conn.makefile('r', encoding=self.encoding) -            try: +            with fp, conn:                  while 1:                      line = fp.readline()                      if self.debugging > 2: print('*retr*', repr(line)) @@ -748,15 +780,11 @@ else:                  # shutdown ssl layer                  if isinstance(conn, ssl.SSLSocket):                      conn.unwrap() -            finally: -                fp.close() -                conn.close()              return self.voidresp()          def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):              self.voidcmd('TYPE I') -            conn = self.transfercmd(cmd, rest) -            try: +            with self.transfercmd(cmd, rest) as conn:                  while 1:                      buf = fp.read(blocksize)                      if not buf: break @@ -765,14 +793,11 @@ else:                  # shutdown ssl layer                  if isinstance(conn, ssl.SSLSocket):                      conn.unwrap() -            finally: -                conn.close()              return self.voidresp()          def storlines(self, cmd, fp, callback=None):              self.voidcmd('TYPE A') -            conn = self.transfercmd(cmd) -            try: +            with self.transfercmd(cmd) as conn:                  while 1:                      buf = fp.readline()                      if not buf: break @@ -784,8 +809,6 @@ else:                  # shutdown ssl layer                  if isinstance(conn, ssl.SSLSocket):                      conn.unwrap() -            finally: -                conn.close()              return self.voidresp()          def abort(self): @@ -818,11 +841,7 @@ def parse150(resp):      m = _150_re.match(resp)      if not m:          return None -    s = m.group(1) -    try: -        return int(s) -    except (OverflowError, ValueError): -        return int(s) +    return int(m.group(1))  _227_re = None | 
