]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
Added much functionality, changed some names (errors, login).
authorGuido van Rossum <guido@python.org>
Thu, 5 Nov 1992 22:22:37 +0000 (22:22 +0000)
committerGuido van Rossum <guido@python.org>
Thu, 5 Nov 1992 22:22:37 +0000 (22:22 +0000)
Lib/ftplib.py

index b360942ee50653f555608daffad5fe218c9ea196..4e740909ba5c3295b55e4f8f6fb4450bff808daa 100644 (file)
@@ -2,22 +2,45 @@
 # (FTP), by J. Postel and J. Reynolds
 
 
+# Example:
+#
+# >>> from ftplib import FTP
+# >>> ftp = FTP().init('ftp.cwi.nl') # connect to host, default port
+# >>> ftp.login() # default, i.e.: user anonymous, passwd user@hostname
+# >>> def handle_one_line(line): # callback for ftp.retrlines
+# ...     print line
+# ... 
+# >>> ftp.retrlines('LIST', handle_one_line) # list directory contents
+# total 43
+# d--x--x--x   2 root     root         512 Jul  1 16:50 bin
+# d--x--x--x   2 root     root         512 Sep 16  1991 etc
+# drwxr-xr-x   2 root     ftp        10752 Sep 16  1991 lost+found
+# drwxr-srwt  15 root     ftp        10240 Nov  5 20:43 pub
+# >>> ftp.quit()
+#
+# To download a file, use ftp.retrlines('RETR ' + filename, handle_one_line),
+# or ftp.retrbinary() with slightly different arguments.
+# To upload a file, use ftp.storlines() or ftp.storbinary(), which have
+# an open file as argument.
+# The download/upload functions first issue appropriate TYPE and PORT
+# commands.
+
+
 import os
 import sys
 import socket
 import string
 
 
-# Default port numbers used by the FTP protocol
+# The standard FTP server control port
 FTP_PORT = 21
-FTP_DATA_PORT = 20
 
 
 # Exception raiseds when an error or invalid response is received
-error_reply = 'nntp.error_reply'       # unexpected [123]xx reply
-error_function = 'nntp.error_function' # 4xx errors
-error_form = 'nntp.error_form'         # 5xx errors
-error_protocol = 'nntp.error_protocol' # response does not begin with [1-5]
+error_reply = 'ftplib.error_reply'     # unexpected [123]xx reply
+error_temp = 'ftplib.error_temp'       # 4xx errors
+error_perm = 'ftplib.error_perm'       # 5xx errors
+error_proto = 'ftplib.error_proto'     # response does not begin with [1-5]
 
 
 # Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
@@ -30,7 +53,7 @@ PORT_OFFSET = 40000
 PORT_CYCLE = 1000
 # XXX This is a nuisance: when using the program several times in a row,
 # reusing the port doesn't work and you have to edit the first port
-# assignment...
+# assignment...  We need getsockname()!
 
 
 # The class itself
@@ -111,18 +134,30 @@ class FTP:
                self.lastresp = resp[:3]
                c = resp[:1]
                if c == '4':
-                       raise error_function, resp
+                       raise error_temp, resp
                if c == '5':
-                       raise error_form, resp
+                       raise error_perm, resp
                if c not in '123':
-                       raise error_protocol, resp
+                       raise error_proto, resp
                return resp
 
+       # Expect a response beginning with '2'
+       def voidresp(self):
+               resp = self.getresp()
+               if resp[0] <> '2':
+                       raise error_reply, resp
+
        # Send a command and return the response
        def sendcmd(self, cmd):
                self.putcmd(cmd)
                return self.getresp()
 
+       # Send a command and ignore the response, which must begin with '2'
+       def voidcmd(self, cmd):
+               resp = self.sendcmd(cmd)
+               if resp[0] <> '2':
+                       raise error_reply, resp
+
        # Send a PORT command with the current host and the given port number
        def sendport(self, port):
                hostname = socket.gethostname()
@@ -131,9 +166,7 @@ class FTP:
                pbytes = [`port/256`, `port%256`]
                bytes = hbytes + pbytes
                cmd = 'PORT ' + string.joinfields(bytes, ',')
-               resp = self.sendcmd(cmd)
-               if resp[:3] <> '200':
-                       raise error_reply, resp
+               self.voidcmd(cmd)
 
        # Create a new socket and send a PORT command for it
        def makeport(self):
@@ -146,38 +179,62 @@ class FTP:
                resp = self.sendport(port)
                return sock
 
-       # Retrieve data in binary mode.  (You must set the mode first.)
-       # The argument is a RETR command.
-       # The callback function is called for each block.
-       # This creates a new port for you
-       def retrbinary(self, cmd, callback, blocksize):
+       # Send a port command and a transfer command, accept the connection
+       # and return the socket for the connection
+       def transfercmd(self, cmd):
                sock = self.makeport()
                resp = self.sendcmd(cmd)
                if resp[0] <> '1':
                        raise error_reply, resp
-               conn, host = sock.accept()
-               sock.close()
+               conn, sockaddr = sock.accept()
+               return conn
+
+       # Login, default anonymous
+       def login(self, *args):
+               user = passwd = acct = ''
+               n = len(args)
+               if n > 3: raise TypeError, 'too many arguments'
+               if n > 0: user = args[0]
+               if n > 1: passwd = args[1]
+               if n > 2: acct = args[2]
+               if not user: user = 'anonymous'
+               if user == 'anonymous' and passwd in ('', '-'):
+                       thishost = socket.gethostname()
+                       if os.environ.has_key('LOGNAME'):
+                               realuser = os.environ['LOGNAME']
+                       elif os.environ.has_key('USER'):
+                               realuser = os.environ['USER']
+                       else:
+                               realuser = 'anonymous'
+                       passwd = passwd + realuser + '@' + thishost
+               resp = self.sendcmd('USER ' + user)
+               if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
+               if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
+               if resp[0] <> '2':
+                       raise error_reply, resp
+
+       # Retrieve data in binary mode.
+       # The argument is a RETR command.
+       # The callback function is called for each block.
+       # This creates a new port for you
+       def retrbinary(self, cmd, callback, blocksize):
+               self.voidcmd('TYPE I')
+               conn = self.transfercmd(cmd)
                while 1:
                        data = conn.recv(blocksize)
                        if not data:
                                break
                        callback(data)
                conn.close()
-               resp = self.getresp()
-               if resp[0] <> '2':
-                       raise error_reply, resp
+               self.voidresp()
 
-       # Retrieve data in line mode.  (You must set the mode first.)
+       # Retrieve data in line mode.
        # The argument is a RETR or LIST command.
        # The callback function is called for each line, with trailing
        # CRLF stripped.  This creates a new port for you
        def retrlines(self, cmd, callback):
-               sock = self.makeport()
-               resp = self.sendcmd(cmd)
-               if resp[0] <> '1':
-                       raise error_reply, resp
-               conn, host = sock.accept()
-               sock.close()
+               resp = self.sendcmd('TYPE A')
+               conn = self.transfercmd(cmd)
                fp = conn.makefile('r')
                while 1:
                        line = fp.readline()
@@ -190,36 +247,85 @@ class FTP:
                        callback(line)
                fp.close()
                conn.close()
-               resp = self.getresp()
-               if resp[0] <> '2':
-                       raise error_reply, resp
+               self.voidresp()
 
-       # Login as user anonymous with given passwd (default user@thishost)
-       def anonymouslogin(self, *args):
-               resp = self.sendcmd('USER anonymous')
-               if resp[0] == '3':
-                       if args:
-                               passwd = args[0]
-                       else:
-                               thishost = socket.gethostname()
-                               if os.environ.has_key('LOGNAME'):
-                                       user = os.environ['LOGNAME']
-                               elif os.environ.has_key('USER'):
-                                       user = os.environ['USER']
-                               else:
-                                       user = 'anonymous'
-                               passwd = user + '@' + thishost
-                       resp = self.sendcmd('PASS ' + passwd)
-               if resp[0] <> '2':
+       # Store a file in binary mode
+       def storbinary(self, cmd, fp, blocksize):
+               self.voidcmd('TYPE I')
+               conn = self.transfercmd(cmd)
+               while 1:
+                       buf = fp.read(blocksize)
+                       if not buf: break
+                       conn.send(buf)
+               conn.close()
+               self.voidresp()
+
+       # Store a file in line mode
+       def storlines(self, cmd, fp):
+               self.voidcmd('TYPE A')
+               conn = self.transfercmd(cmd)
+               while 1:
+                       buf = fp.readline()
+                       if not buf: break
+                       if buf[-2:] <> CRLF:
+                               if buf[-1] in CRLF: buf = buf[:-1]
+                               buf = buf + CRLF
+                       conn.send(buf)
+               conn.close()
+               self.voidresp()
+
+       # Return a list of files in a given directory (default the current)
+       def nlst(self, *args):
+               cmd = 'NLST'
+               for arg in args:
+                       cmd = cmd + (' ' + arg)
+               files = []
+               self.retrlines(cmd, files.append)
+               return files
+
+       # Rename a file
+       def rename(self, fromname, toname):
+               resp = self.sendcmd('RNFR ' + fromname)
+               if resp[0] <> '3':
                        raise error_reply, resp
+               self.voidcmd('RNTO ' + toname)
+
+       # Make a directory, return its full pathname
+       def mkd(self, dirname):
+               resp = self.sendcmd('MKD ' + dirname)
+               return parse257(resp)
+
+       # Return current wording directory
+       def pwd(self):
+               resp = self.sendcmd('PWD')
+               return parse257(resp)
 
        # Quit, and close the connection
        def quit(self):
-               resp = self.sendcmd('QUIT')
-               if resp[0] <> '2':
-                       raise error_reply, resp
+               self.voidcmd('QUIT')
                self.file.close()
                self.sock.close()
+               del self.file, self.sock
+
+
+# Parse a response type 257
+def parse257(resp):
+       if resp[:3] <> '257':
+               raise error_reply, resp
+       if resp[3:5] <> ' "':
+               return '' # Not compliant to RFC 959, but UNIX ftpd does this
+       dirname = ''
+       i = 5
+       n = len(resp)
+       while i < n:
+               c = resp[i]
+               i = i+1
+               if c == '"':
+                       if i >= n or resp[i] <> '"':
+                               break
+                       i = i+1
+               dirname = dirname + c
+       return dirname
 
 
 # Test program.
@@ -239,7 +345,7 @@ def test():
                host = sys.argv[1]
                ftp = FTP().init(host)
                ftp.debug(debugging)
-               ftp.anonymouslogin()
+               ftp.login()
                def writeln(line): print line
                for file in sys.argv[2:]:
                        if file[:2] == '-l':