summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuido van Rossum <guido@python.org>1992-11-04 15:51:30 (GMT)
committerGuido van Rossum <guido@python.org>1992-11-04 15:51:30 (GMT)
commit1115ab2a7469746859655cfa7f717c794a8a22ab (patch)
tree365fd7c70e288c29c692a323c386623754f94c31
parent0317a4719b6b08b8eb3210675b75ecc736a8f02d (diff)
downloadcpython-1115ab2a7469746859655cfa7f717c794a8a22ab.zip
cpython-1115ab2a7469746859655cfa7f717c794a8a22ab.tar.gz
cpython-1115ab2a7469746859655cfa7f717c794a8a22ab.tar.bz2
Initial revision
-rw-r--r--Lib/ftplib.py258
1 files changed, 258 insertions, 0 deletions
diff --git a/Lib/ftplib.py b/Lib/ftplib.py
new file mode 100644
index 0000000..b360942
--- /dev/null
+++ b/Lib/ftplib.py
@@ -0,0 +1,258 @@
+# An FTP client class. Based on RFC 959: File Transfer Protocol
+# (FTP), by J. Postel and J. Reynolds
+
+
+import os
+import sys
+import socket
+import string
+
+
+# Default port numbers used by the FTP protocol
+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]
+
+
+# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
+CRLF = '\r\n'
+
+
+# Next port to be used by makeport(), with PORT_OFFSET added
+nextport = 0
+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...
+
+
+# The class itself
+class FTP:
+
+ # Initialize an instance. Arguments:
+ # - host: hostname to connect to
+ # - port: port to connect to (default the standard FTP port)
+ def init(self, host, *args):
+ if len(args) > 1: raise TypeError, 'too many args'
+ if args: port = args[0]
+ else: port = FTP_PORT
+ self.host = host
+ self.port = port
+ self.debugging = 0
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock.connect(self.host, self.port)
+ self.file = self.sock.makefile('r')
+ self.welcome = self.getresp()
+ return self
+
+ # Get the welcome message from the server
+ # (this is read and squirreled away by init())
+ def getwelcome(self):
+ if self.debugging: print '*welcome*', `self.welcome`
+ return self.welcome
+
+ # Set the debugging level. Argument level means:
+ # 0: no debugging output (default)
+ # 1: print commands and responses but not body text etc.
+ # 2: also print raw lines read and sent before stripping CR/LF
+ def debug(self, level):
+ self.debugging = level
+
+ # Internal: send one line to the server, appending CRLF
+ def putline(self, line):
+ line = line + CRLF
+ if self.debugging > 1: print '*put*', `line`
+ self.sock.send(line)
+
+ # Internal: send one command to the server (through putline())
+ def putcmd(self, line):
+ if self.debugging: print '*cmd*', `line`
+ self.putline(line)
+
+ # Internal: return one line from the server, stripping CRLF.
+ # Raise EOFError if the connection is closed
+ def getline(self):
+ line = self.file.readline()
+ if self.debugging > 1:
+ print '*get*', `line`
+ if not line: raise EOFError
+ if line[-2:] == CRLF: line = line[:-2]
+ elif line[-1:] in CRLF: line = line[:-1]
+ return line
+
+ # Internal: get a response from the server, which may possibly
+ # consist of multiple lines. Return a single string with no
+ # trailing CRLF. If the response consists of multiple lines,
+ # these are separated by '\n' characters in the string
+ def getmultiline(self):
+ line = self.getline()
+ if line[3:4] == '-':
+ code = line[:3]
+ while 1:
+ nextline = self.getline()
+ line = line + ('\n' + nextline)
+ if nextline[:3] == code and \
+ nextline[3:4] <> '-':
+ break
+ return line
+
+ # Internal: get a response from the server.
+ # Raise various errors if the response indicates an error
+ def getresp(self):
+ resp = self.getmultiline()
+ if self.debugging: print '*resp*', `resp`
+ self.lastresp = resp[:3]
+ c = resp[:1]
+ if c == '4':
+ raise error_function, resp
+ if c == '5':
+ raise error_form, resp
+ if c not in '123':
+ raise error_protocol, resp
+ return resp
+
+ # Send a command and return the response
+ def sendcmd(self, cmd):
+ self.putcmd(cmd)
+ return self.getresp()
+
+ # Send a PORT command with the current host and the given port number
+ def sendport(self, port):
+ hostname = socket.gethostname()
+ hostaddr = socket.gethostbyname(hostname)
+ hbytes = string.splitfields(hostaddr, '.')
+ 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
+
+ # Create a new socket and send a PORT command for it
+ def makeport(self):
+ global nextport
+ port = nextport + PORT_OFFSET
+ nextport = (nextport + 1) % PORT_CYCLE
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.bind('', port)
+ sock.listen(0)
+ 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):
+ sock = self.makeport()
+ resp = self.sendcmd(cmd)
+ if resp[0] <> '1':
+ raise error_reply, resp
+ conn, host = sock.accept()
+ sock.close()
+ 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
+
+ # Retrieve data in line mode. (You must set the mode first.)
+ # 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()
+ fp = conn.makefile('r')
+ while 1:
+ line = fp.readline()
+ if not line:
+ break
+ if line[-2:] == CRLF:
+ line = line[:-2]
+ elif line[:-1] == '\n':
+ line = line[:-1]
+ callback(line)
+ fp.close()
+ conn.close()
+ resp = self.getresp()
+ if resp[0] <> '2':
+ raise error_reply, resp
+
+ # 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':
+ raise error_reply, resp
+
+ # Quit, and close the connection
+ def quit(self):
+ resp = self.sendcmd('QUIT')
+ if resp[0] <> '2':
+ raise error_reply, resp
+ self.file.close()
+ self.sock.close()
+
+
+# Test program.
+# Usage: ftp [-d] host [-l[dir]] [-d[dir]] [file] ...
+def test():
+ import marshal
+ global nextport
+ try:
+ nextport = marshal.load(open('.@nextport', 'r'))
+ except IOError:
+ pass
+ try:
+ debugging = 0
+ while sys.argv[1] == '-d':
+ debugging = debugging+1
+ del sys.argv[1]
+ host = sys.argv[1]
+ ftp = FTP().init(host)
+ ftp.debug(debugging)
+ ftp.anonymouslogin()
+ def writeln(line): print line
+ for file in sys.argv[2:]:
+ if file[:2] == '-l':
+ cmd = 'LIST'
+ if file[2:]: cmd = cmd + ' ' + file[2:]
+ ftp.retrlines(cmd, writeln)
+ elif file[:2] == '-d':
+ cmd = 'CWD'
+ if file[2:]: cmd = cmd + ' ' + file[2:]
+ resp = ftp.sendcmd(cmd)
+ else:
+ ftp.retrbinary('RETR ' + file, \
+ sys.stdout.write, 1024)
+ ftp.quit()
+ finally:
+ marshal.dump(nextport, open('.@nextport', 'w'))