mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 11:49:12 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			717 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			717 lines
		
	
	
	
		
			20 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
'''An FTP client class, and some helper functions.
 | 
						||
Based on RFC 959: File Transfer Protocol
 | 
						||
(FTP), by J. Postel and J. Reynolds
 | 
						||
 | 
						||
Changes and improvements suggested by Steve Majewski.
 | 
						||
Modified by Jack to work on the mac.
 | 
						||
Modified by Siebren to support docstrings and PASV.
 | 
						||
 | 
						||
 | 
						||
Example:
 | 
						||
 | 
						||
>>> from ftplib import FTP
 | 
						||
>>> ftp = FTP('ftp.python.org') # connect to host, default port
 | 
						||
>>> ftp.login() # default, i.e.: user anonymous, passwd user@hostname
 | 
						||
'230 Guest login ok, access restrictions apply.'
 | 
						||
>>> ftp.retrlines('LIST') # list directory contents
 | 
						||
total 9
 | 
						||
drwxr-xr-x   8 root     wheel        1024 Jan  3  1994 .
 | 
						||
drwxr-xr-x   8 root     wheel        1024 Jan  3  1994 ..
 | 
						||
drwxr-xr-x   2 root     wheel        1024 Jan  3  1994 bin
 | 
						||
drwxr-xr-x   2 root     wheel        1024 Jan  3  1994 etc
 | 
						||
d-wxrwxr-x   2 ftp      wheel        1024 Sep  5 13:43 incoming
 | 
						||
drwxr-xr-x   2 root     wheel        1024 Nov 17  1993 lib
 | 
						||
drwxr-xr-x   6 1094     wheel        1024 Sep 13 19:07 pub
 | 
						||
drwxr-xr-x   3 root     wheel        1024 Jan  3  1994 usr
 | 
						||
-rw-r--r--   1 root     root          312 Aug  1  1994 welcome.msg
 | 
						||
'226 Transfer complete.'
 | 
						||
>>> ftp.quit()
 | 
						||
'221 Goodbye.'
 | 
						||
>>> 
 | 
						||
 | 
						||
A nice test that reveals some of the network dialogue would be:
 | 
						||
python ftplib.py -d localhost -l -p -l
 | 
						||
'''
 | 
						||
 | 
						||
 | 
						||
import os
 | 
						||
import sys
 | 
						||
import string
 | 
						||
 | 
						||
# Import SOCKS module if it exists, else standard socket module socket
 | 
						||
try:
 | 
						||
	import SOCKS; socket = SOCKS
 | 
						||
except ImportError:
 | 
						||
	import socket
 | 
						||
 | 
						||
 | 
						||
# Magic number from <socket.h>
 | 
						||
MSG_OOB = 0x1				# Process data out of band
 | 
						||
 | 
						||
 | 
						||
# The standard FTP server control port
 | 
						||
FTP_PORT = 21
 | 
						||
 | 
						||
 | 
						||
# Exception raised when an error or invalid response is received
 | 
						||
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]
 | 
						||
 | 
						||
 | 
						||
# All exceptions (hopefully) that may be raised here and that aren't
 | 
						||
# (always) programming errors on our side
 | 
						||
all_errors = (error_reply, error_temp, error_perm, error_proto, \
 | 
						||
	      socket.error, IOError, EOFError)
 | 
						||
 | 
						||
 | 
						||
# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
 | 
						||
CRLF = '\r\n'
 | 
						||
 | 
						||
 | 
						||
# The class itself
 | 
						||
class FTP:
 | 
						||
 | 
						||
	'''An FTP client class.
 | 
						||
 | 
						||
	To create a connection, call the class using these argument:
 | 
						||
		host, user, passwd, acct
 | 
						||
	These are all strings, and have default value ''.
 | 
						||
	Then use self.connect() with optional host and port argument.
 | 
						||
 | 
						||
	To download a file, use ftp.retrlines('RETR ' + filename),
 | 
						||
	or ftp.retrbinary() with slightly different arguments.
 | 
						||
	To upload a file, use ftp.storlines() or ftp.storbinary(),
 | 
						||
	which have an open file as argument (see their definitions
 | 
						||
	below for details).
 | 
						||
	The download/upload functions first issue appropriate TYPE
 | 
						||
	and PORT or PASV commands.
 | 
						||
'''
 | 
						||
 | 
						||
	# 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 = ''):
 | 
						||
		# Initialize the instance to something mostly harmless
 | 
						||
		self.debugging = 0
 | 
						||
		self.host = ''
 | 
						||
		self.port = FTP_PORT
 | 
						||
		self.sock = None
 | 
						||
		self.file = None
 | 
						||
		self.welcome = None
 | 
						||
		resp = None
 | 
						||
		if host:
 | 
						||
			resp = self.connect(host)
 | 
						||
			if user: resp = self.login(user, passwd, acct)
 | 
						||
 | 
						||
	def connect(self, host = '', port = 0):
 | 
						||
		'''Connect to host.  Arguments are:
 | 
						||
		- host: hostname to connect to (string, default previous host)
 | 
						||
		- port: port to connect to (integer, default previous port)'''
 | 
						||
		if host: self.host = host
 | 
						||
		if port: self.port = port
 | 
						||
		self.passiveserver = 0
 | 
						||
		self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
						||
		self.sock.connect(self.host, self.port)
 | 
						||
		self.file = self.sock.makefile('rb')
 | 
						||
		self.welcome = self.getresp()
 | 
						||
		return self.welcome
 | 
						||
 | 
						||
	def getwelcome(self):
 | 
						||
		'''Get the welcome message from the server.
 | 
						||
		(this is read and squirreled away by connect())'''
 | 
						||
		if self.debugging:
 | 
						||
			print '*welcome*', self.sanitize(self.welcome)
 | 
						||
		return self.welcome
 | 
						||
 | 
						||
	def set_debuglevel(self, level):
 | 
						||
		'''Set the debugging level.
 | 
						||
		The required 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'''
 | 
						||
		self.debugging = level
 | 
						||
	debug = set_debuglevel
 | 
						||
 | 
						||
	def set_pasv(self, val):
 | 
						||
		'''Use passive or active mode for data transfers.
 | 
						||
		With a false argument, use the normal PORT mode,
 | 
						||
		With a true argument, use the PASV command.'''
 | 
						||
		self.passiveserver = val
 | 
						||
 | 
						||
	# 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
 | 
						||
			s = s[:5] + '*'*(i-5) + s[i:]
 | 
						||
		return `s`
 | 
						||
 | 
						||
	# Internal: send one line to the server, appending CRLF
 | 
						||
	def putline(self, line):
 | 
						||
		line = line + CRLF
 | 
						||
		if self.debugging > 1: print '*put*', self.sanitize(line)
 | 
						||
		self.sock.send(line)
 | 
						||
 | 
						||
	# Internal: send one command to the server (through putline())
 | 
						||
	def putcmd(self, line):
 | 
						||
		if self.debugging: print '*cmd*', self.sanitize(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*', self.sanitize(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*', self.sanitize(resp)
 | 
						||
		self.lastresp = resp[:3]
 | 
						||
		c = resp[:1]
 | 
						||
		if c == '4':
 | 
						||
			raise error_temp, resp
 | 
						||
		if c == '5':
 | 
						||
			raise error_perm, resp
 | 
						||
		if c not in '123':
 | 
						||
			raise error_proto, resp
 | 
						||
		return resp
 | 
						||
 | 
						||
	def voidresp(self):
 | 
						||
		"""Expect a response beginning with '2'."""
 | 
						||
		resp = self.getresp()
 | 
						||
		if resp[0] <> '2':
 | 
						||
			raise error_reply, resp
 | 
						||
		return resp
 | 
						||
 | 
						||
	def abort(self):
 | 
						||
		'''Abort a file transfer.  Uses out-of-band data.
 | 
						||
		This does not follow the procedure from the RFC to send Telnet
 | 
						||
		IP and Synch; that doesn't seem to work with the servers I've
 | 
						||
		tried.  Instead, just send the ABOR command as OOB data.'''
 | 
						||
		line = 'ABOR' + CRLF
 | 
						||
		if self.debugging > 1: print '*put urgent*', self.sanitize(line)
 | 
						||
		self.sock.send(line, MSG_OOB)
 | 
						||
		resp = self.getmultiline()
 | 
						||
		if resp[:3] not in ('426', '226'):
 | 
						||
			raise error_proto, resp
 | 
						||
 | 
						||
	def sendcmd(self, cmd):
 | 
						||
		'''Send a command and return the response.'''
 | 
						||
		self.putcmd(cmd)
 | 
						||
		return self.getresp()
 | 
						||
 | 
						||
	def voidcmd(self, cmd):
 | 
						||
		"""Send a command and expect a response beginning with '2'."""
 | 
						||
		self.putcmd(cmd)
 | 
						||
		return self.voidresp()
 | 
						||
 | 
						||
	def sendport(self, host, port):
 | 
						||
		'''Send a PORT command with the current host and the given port number.'''
 | 
						||
		hbytes = string.splitfields(host, '.')
 | 
						||
		pbytes = [`port/256`, `port%256`]
 | 
						||
		bytes = hbytes + pbytes
 | 
						||
		cmd = 'PORT ' + string.joinfields(bytes, ',')
 | 
						||
		return self.voidcmd(cmd)
 | 
						||
 | 
						||
	def makeport(self):
 | 
						||
		'''Create a new socket and send a PORT command for it.'''
 | 
						||
		global nextport
 | 
						||
		sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
						||
		sock.bind(('', 0))
 | 
						||
		sock.listen(1)
 | 
						||
		dummyhost, port = sock.getsockname() # Get proper port
 | 
						||
		host, dummyport = self.sock.getsockname() # Get proper host
 | 
						||
		resp = self.sendport(host, port)
 | 
						||
		return sock
 | 
						||
 | 
						||
	def ntransfercmd(self, cmd):
 | 
						||
		'''Initiate a transfer over the data connection.
 | 
						||
		If the transfer is active, send a port command and
 | 
						||
		the transfer command, and accept the connection.
 | 
						||
		If the server is passive, send a pasv command, connect
 | 
						||
		to it, and start the transfer command.
 | 
						||
		Either way, return the socket for the connection and
 | 
						||
		the expected size of the transfer.  The expected size
 | 
						||
		may be None if it could not be determined.'''
 | 
						||
		size = None
 | 
						||
		if self.passiveserver:
 | 
						||
			host, port = parse227(self.sendcmd('PASV'))
 | 
						||
			conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 | 
						||
			conn.connect(host, port)
 | 
						||
			resp = self.sendcmd(cmd)
 | 
						||
			if resp[0] <> '1':
 | 
						||
				raise error_reply, resp
 | 
						||
		else:
 | 
						||
			sock = self.makeport()
 | 
						||
			resp = self.sendcmd(cmd)
 | 
						||
			if resp[0] <> '1':
 | 
						||
				raise error_reply, resp
 | 
						||
			conn, sockaddr = sock.accept()
 | 
						||
		if resp[:3] == '150':
 | 
						||
			# this is conditional in case we received a 125
 | 
						||
			size = parse150(resp)
 | 
						||
		return conn, size
 | 
						||
 | 
						||
	def transfercmd(self, cmd):
 | 
						||
		'''Initiate a transfer over the data connection.  Returns
 | 
						||
		the socket for the connection.  See also ntransfercmd().'''
 | 
						||
		return self.ntransfercmd(cmd)[0]
 | 
						||
 | 
						||
	def login(self, user = '', passwd = '', acct = ''):
 | 
						||
		'''Login, default anonymous.'''
 | 
						||
		if not user: user = 'anonymous'
 | 
						||
		if not passwd: passwd = ''
 | 
						||
		if not acct: acct = ''
 | 
						||
		if user == 'anonymous' and passwd in ('', '-'):
 | 
						||
			thishost = socket.gethostname()
 | 
						||
			# Make sure it is fully qualified
 | 
						||
			if not '.' in thishost:
 | 
						||
				thisaddr = socket.gethostbyname(thishost)
 | 
						||
				firstname, names, unused = \
 | 
						||
					   socket.gethostbyaddr(thisaddr)
 | 
						||
				names.insert(0, firstname)
 | 
						||
				for name in names:
 | 
						||
					if '.' in name:
 | 
						||
						thishost = name
 | 
						||
						break
 | 
						||
			try:
 | 
						||
				if os.environ.has_key('LOGNAME'):
 | 
						||
					realuser = os.environ['LOGNAME']
 | 
						||
				elif os.environ.has_key('USER'):
 | 
						||
					realuser = os.environ['USER']
 | 
						||
				else:
 | 
						||
					realuser = 'anonymous'
 | 
						||
			except AttributeError:
 | 
						||
				# Not all systems have os.environ....
 | 
						||
				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
 | 
						||
		return resp
 | 
						||
 | 
						||
	def retrbinary(self, cmd, callback, blocksize=8192):
 | 
						||
		'''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'''
 | 
						||
		self.voidcmd('TYPE I')
 | 
						||
		conn = self.transfercmd(cmd)
 | 
						||
		while 1:
 | 
						||
			data = conn.recv(blocksize)
 | 
						||
			if not data:
 | 
						||
				break
 | 
						||
			callback(data)
 | 
						||
		conn.close()
 | 
						||
		return self.voidresp()
 | 
						||
 | 
						||
	def retrlines(self, cmd, callback = None):
 | 
						||
		'''Retrieve data in line mode.
 | 
						||
		The argument is a RETR or LIST command.
 | 
						||
		The callback function (2nd argument) is called for each line,
 | 
						||
		with trailing CRLF stripped.  This creates a new port for you.
 | 
						||
		print_lines is the default callback.'''
 | 
						||
		if not callback: callback = print_line
 | 
						||
		resp = self.sendcmd('TYPE A')
 | 
						||
		conn = self.transfercmd(cmd)
 | 
						||
		fp = conn.makefile('rb')
 | 
						||
		while 1:
 | 
						||
			line = fp.readline()
 | 
						||
			if self.debugging > 2: print '*retr*', `line`
 | 
						||
			if not line:
 | 
						||
				break
 | 
						||
			if line[-2:] == CRLF:
 | 
						||
				line = line[:-2]
 | 
						||
			elif line[-1:] == '\n':
 | 
						||
				line = line[:-1]
 | 
						||
			callback(line)
 | 
						||
		fp.close()
 | 
						||
		conn.close()
 | 
						||
		return self.voidresp()
 | 
						||
 | 
						||
	def storbinary(self, cmd, fp, blocksize):
 | 
						||
		'''Store a file in binary mode.'''
 | 
						||
		self.voidcmd('TYPE I')
 | 
						||
		conn = self.transfercmd(cmd)
 | 
						||
		while 1:
 | 
						||
			buf = fp.read(blocksize)
 | 
						||
			if not buf: break
 | 
						||
			conn.send(buf)
 | 
						||
		conn.close()
 | 
						||
		return self.voidresp()
 | 
						||
 | 
						||
	def storlines(self, cmd, fp):
 | 
						||
		'''Store a file in line mode.'''
 | 
						||
		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()
 | 
						||
		return self.voidresp()
 | 
						||
 | 
						||
	def acct(self, password):
 | 
						||
		'''Send new account name.'''
 | 
						||
		cmd = 'ACCT ' + password
 | 
						||
		return self.voidcmd(cmd)
 | 
						||
 | 
						||
	def nlst(self, *args):
 | 
						||
		'''Return a list of files in a given directory (default the current).'''
 | 
						||
		cmd = 'NLST'
 | 
						||
		for arg in args:
 | 
						||
			cmd = cmd + (' ' + arg)
 | 
						||
		files = []
 | 
						||
		self.retrlines(cmd, files.append)
 | 
						||
		return files
 | 
						||
 | 
						||
	def dir(self, *args):
 | 
						||
		'''List a directory in long form.
 | 
						||
		By default list current directory to stdout.
 | 
						||
		Optional last argument is callback function; all
 | 
						||
		non-empty arguments before it are concatenated to the
 | 
						||
		LIST command.  (This *should* only be used for a pathname.)'''
 | 
						||
		cmd = 'LIST' 
 | 
						||
		func = None
 | 
						||
		if args[-1:] and type(args[-1]) != type(''):
 | 
						||
			args, func = args[:-1], args[-1]
 | 
						||
		for arg in args:
 | 
						||
			if arg:
 | 
						||
				cmd = cmd + (' ' + arg) 
 | 
						||
		self.retrlines(cmd, func)
 | 
						||
 | 
						||
	def rename(self, fromname, toname):
 | 
						||
		'''Rename a file.'''
 | 
						||
		resp = self.sendcmd('RNFR ' + fromname)
 | 
						||
		if resp[0] <> '3':
 | 
						||
			raise error_reply, resp
 | 
						||
		return self.voidcmd('RNTO ' + toname)
 | 
						||
 | 
						||
	def delete(self, filename):
 | 
						||
		'''Delete a file.'''
 | 
						||
		resp = self.sendcmd('DELE ' + filename)
 | 
						||
		if resp[:3] in ('250', '200'):
 | 
						||
			return resp
 | 
						||
		elif resp[:1] == '5':
 | 
						||
			raise error_perm, resp
 | 
						||
		else:
 | 
						||
			raise error_reply, resp
 | 
						||
 | 
						||
	def cwd(self, dirname):
 | 
						||
		'''Change to a directory.'''
 | 
						||
		if dirname == '..':
 | 
						||
			try:
 | 
						||
				return self.voidcmd('CDUP')
 | 
						||
			except error_perm, msg:
 | 
						||
				if msg[:3] != '500':
 | 
						||
					raise error_perm, msg
 | 
						||
		cmd = 'CWD ' + dirname
 | 
						||
		return self.voidcmd(cmd)
 | 
						||
 | 
						||
	def size(self, filename):
 | 
						||
		'''Retrieve the size of a file.'''
 | 
						||
		# Note that the RFC doesn't say anything about 'SIZE'
 | 
						||
		resp = self.sendcmd('SIZE ' + filename)
 | 
						||
		if resp[:3] == '213':
 | 
						||
			return string.atoi(string.strip(resp[3:]))
 | 
						||
 | 
						||
	def mkd(self, dirname):
 | 
						||
		'''Make a directory, return its full pathname.'''
 | 
						||
		resp = self.sendcmd('MKD ' + dirname)
 | 
						||
		return parse257(resp)
 | 
						||
 | 
						||
	def rmd(self, dirname):
 | 
						||
		'''Remove a directory.'''
 | 
						||
		return self.voidcmd('RMD ' + dirname)
 | 
						||
 | 
						||
	def pwd(self):
 | 
						||
		'''Return current working directory.'''
 | 
						||
		resp = self.sendcmd('PWD')
 | 
						||
		return parse257(resp)
 | 
						||
 | 
						||
	def quit(self):
 | 
						||
		'''Quit, and close the connection.'''
 | 
						||
		resp = self.voidcmd('QUIT')
 | 
						||
		self.close()
 | 
						||
		return resp
 | 
						||
 | 
						||
	def close(self):
 | 
						||
		'''Close the connection without assuming anything about it.'''
 | 
						||
		self.file.close()
 | 
						||
		self.sock.close()
 | 
						||
		del self.file, self.sock
 | 
						||
 | 
						||
 | 
						||
_150_re = None
 | 
						||
 | 
						||
def parse150(resp):
 | 
						||
	'''Parse the '150' response for a RETR request.
 | 
						||
	Returns the expected transfer size or None; size is not guaranteed to
 | 
						||
	be present in the 150 message.
 | 
						||
	'''
 | 
						||
	if resp[:3] != '150':
 | 
						||
		raise error_reply, resp
 | 
						||
	global _150_re
 | 
						||
	if _150_re is None:
 | 
						||
		import re
 | 
						||
		_150_re = re.compile("150 .* \((\d+) bytes\)", re.IGNORECASE)
 | 
						||
	m = _150_re.match(resp)
 | 
						||
	if m:
 | 
						||
		return string.atoi(m.group(1))
 | 
						||
	return None
 | 
						||
 | 
						||
 | 
						||
def parse227(resp):
 | 
						||
	'''Parse the '227' response for a PASV request.
 | 
						||
	Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
 | 
						||
	Return ('host.addr.as.numbers', port#) tuple.'''
 | 
						||
 | 
						||
	if resp[:3] <> '227':
 | 
						||
		raise error_reply, resp
 | 
						||
	left = string.find(resp, '(')
 | 
						||
	if left < 0: raise error_proto, resp
 | 
						||
	right = string.find(resp, ')', left + 1)
 | 
						||
	if right < 0:
 | 
						||
		raise error_proto, resp	# should contain '(h1,h2,h3,h4,p1,p2)'
 | 
						||
	numbers = string.split(resp[left+1:right], ',')
 | 
						||
	if len(numbers) <> 6:
 | 
						||
		raise error_proto, resp
 | 
						||
	host = string.join(numbers[:4], '.')
 | 
						||
	port = (string.atoi(numbers[4]) << 8) + string.atoi(numbers[5])
 | 
						||
	return host, port
 | 
						||
 | 
						||
 | 
						||
def parse257(resp):
 | 
						||
	'''Parse the '257' response for a MKD or PWD request.
 | 
						||
	This is a response to a MKD or PWD request: a directory name.
 | 
						||
	Returns the directoryname in the 257 reply.'''
 | 
						||
 | 
						||
	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
 | 
						||
 | 
						||
 | 
						||
def print_line(line):
 | 
						||
	'''Default retrlines callback to print a line.'''
 | 
						||
	print line
 | 
						||
 | 
						||
 | 
						||
def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
 | 
						||
	'''Copy file from one FTP-instance to another.'''
 | 
						||
	if not targetname: targetname = sourcename
 | 
						||
	type = 'TYPE ' + type
 | 
						||
	source.voidcmd(type)
 | 
						||
	target.voidcmd(type)
 | 
						||
	sourcehost, sourceport = parse227(source.sendcmd('PASV'))
 | 
						||
	target.sendport(sourcehost, sourceport)
 | 
						||
	# RFC 959: the user must "listen" [...] BEFORE sending the
 | 
						||
	# transfer request.
 | 
						||
	# So: STOR before RETR, because here the target is a "user".
 | 
						||
	treply = target.sendcmd('STOR ' + targetname)
 | 
						||
	if treply[:3] not in ('125', '150'): raise error_proto	# RFC 959
 | 
						||
	sreply = source.sendcmd('RETR ' + sourcename)
 | 
						||
	if sreply[:3] not in ('125', '150'): raise error_proto	# RFC 959
 | 
						||
	source.voidresp()
 | 
						||
	target.voidresp()
 | 
						||
 | 
						||
 | 
						||
class Netrc:
 | 
						||
	"""Class to parse & provide access to 'netrc' format files.
 | 
						||
 | 
						||
	See the netrc(4) man page for information on the file format.
 | 
						||
 | 
						||
	WARNING: This class is obsolete -- use module netrc instead.
 | 
						||
 | 
						||
	"""
 | 
						||
	__defuser = None
 | 
						||
	__defpasswd = None
 | 
						||
	__defacct = None
 | 
						||
 | 
						||
	def __init__(self, filename=None):
 | 
						||
		if not filename:
 | 
						||
			if os.environ.has_key("HOME"):
 | 
						||
				filename = os.path.join(os.environ["HOME"],
 | 
						||
							".netrc")
 | 
						||
			else:
 | 
						||
				raise IOError, \
 | 
						||
				      "specify file to load or set $HOME"
 | 
						||
		self.__hosts = {}
 | 
						||
		self.__macros = {}
 | 
						||
		fp = open(filename, "r")
 | 
						||
		in_macro = 0
 | 
						||
		while 1:
 | 
						||
			line = fp.readline()
 | 
						||
			if not line: break
 | 
						||
			if in_macro and string.strip(line):
 | 
						||
				macro_lines.append(line)
 | 
						||
				continue
 | 
						||
			elif in_macro:
 | 
						||
				self.__macros[macro_name] = tuple(macro_lines)
 | 
						||
				in_macro = 0
 | 
						||
			words = string.split(line)
 | 
						||
			host = user = passwd = acct = None
 | 
						||
			default = 0
 | 
						||
			i = 0
 | 
						||
			while i < len(words):
 | 
						||
				w1 = words[i]
 | 
						||
				if i+1 < len(words):
 | 
						||
					w2 = words[i + 1]
 | 
						||
				else:
 | 
						||
					w2 = None
 | 
						||
				if w1 == 'default':
 | 
						||
					default = 1
 | 
						||
				elif w1 == 'machine' and w2:
 | 
						||
					host = string.lower(w2)
 | 
						||
					i = i + 1
 | 
						||
				elif w1 == 'login' and w2:
 | 
						||
					user = w2
 | 
						||
					i = i + 1
 | 
						||
				elif w1 == 'password' and w2:
 | 
						||
					passwd = w2
 | 
						||
					i = i + 1
 | 
						||
				elif w1 == 'account' and w2:
 | 
						||
					acct = w2
 | 
						||
					i = i + 1
 | 
						||
				elif w1 == 'macdef' and w2:
 | 
						||
					macro_name = w2
 | 
						||
					macro_lines = []
 | 
						||
					in_macro = 1
 | 
						||
					break
 | 
						||
				i = i + 1
 | 
						||
			if default:
 | 
						||
				self.__defuser = user or self.__defuser
 | 
						||
				self.__defpasswd = passwd or self.__defpasswd
 | 
						||
				self.__defacct = acct or self.__defacct
 | 
						||
			if host:
 | 
						||
				if self.__hosts.has_key(host):
 | 
						||
					ouser, opasswd, oacct = \
 | 
						||
					       self.__hosts[host]
 | 
						||
					user = user or ouser
 | 
						||
					passwd = passwd or opasswd
 | 
						||
					acct = acct or oacct
 | 
						||
				self.__hosts[host] = user, passwd, acct
 | 
						||
		fp.close()
 | 
						||
 | 
						||
	def get_hosts(self):
 | 
						||
		"""Return a list of hosts mentioned in the .netrc file."""
 | 
						||
		return self.__hosts.keys()
 | 
						||
 | 
						||
	def get_account(self, host):
 | 
						||
		"""Returns login information for the named host.
 | 
						||
 | 
						||
		The return value is a triple containing userid,
 | 
						||
		password, and the accounting field.
 | 
						||
 | 
						||
		"""
 | 
						||
		host = string.lower(host)
 | 
						||
		user = passwd = acct = None
 | 
						||
		if self.__hosts.has_key(host):
 | 
						||
			user, passwd, acct = self.__hosts[host]
 | 
						||
		user = user or self.__defuser
 | 
						||
		passwd = passwd or self.__defpasswd
 | 
						||
		acct = acct or self.__defacct
 | 
						||
		return user, passwd, acct
 | 
						||
 | 
						||
	def get_macros(self):
 | 
						||
		"""Return a list of all defined macro names."""
 | 
						||
		return self.__macros.keys()
 | 
						||
 | 
						||
	def get_macro(self, macro):
 | 
						||
		"""Return a sequence of lines which define a named macro."""
 | 
						||
		return self.__macros[macro]
 | 
						||
 | 
						||
 | 
						||
 | 
						||
def test():
 | 
						||
	'''Test program.
 | 
						||
	Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...'''
 | 
						||
 | 
						||
	debugging = 0
 | 
						||
	rcfile = None
 | 
						||
	while sys.argv[1] == '-d':
 | 
						||
		debugging = debugging+1
 | 
						||
		del sys.argv[1]
 | 
						||
	if sys.argv[1][:2] == '-r':
 | 
						||
		# get name of alternate ~/.netrc file:
 | 
						||
		rcfile = sys.argv[1][2:]
 | 
						||
		del sys.argv[1]
 | 
						||
	host = sys.argv[1]
 | 
						||
	ftp = FTP(host)
 | 
						||
	ftp.set_debuglevel(debugging)
 | 
						||
	userid = passwd = acct = ''
 | 
						||
	try:
 | 
						||
		netrc = Netrc(rcfile)
 | 
						||
	except IOError:
 | 
						||
		if rcfile is not None:
 | 
						||
			sys.stderr.write("Could not open account file"
 | 
						||
					 " -- using anonymous login.")
 | 
						||
	else:
 | 
						||
		try:
 | 
						||
			userid, passwd, acct = netrc.get_account(host)
 | 
						||
		except KeyError:
 | 
						||
			# no account for host
 | 
						||
			sys.stderr.write(
 | 
						||
				"No account -- using anonymous login.")
 | 
						||
	ftp.login(userid, passwd, acct)
 | 
						||
	for file in sys.argv[2:]:
 | 
						||
		if file[:2] == '-l':
 | 
						||
			ftp.dir(file[2:])
 | 
						||
		elif file[:2] == '-d':
 | 
						||
			cmd = 'CWD'
 | 
						||
			if file[2:]: cmd = cmd + ' ' + file[2:]
 | 
						||
			resp = ftp.sendcmd(cmd)
 | 
						||
		elif file == '-p':
 | 
						||
			ftp.set_pasv(not ftp.passiveserver)
 | 
						||
		else:
 | 
						||
			ftp.retrbinary('RETR ' + file, \
 | 
						||
				       sys.stdout.write, 1024)
 | 
						||
	ftp.quit()
 | 
						||
 | 
						||
 | 
						||
if __name__ == '__main__':
 | 
						||
	test()
 |