blob: fd9127bff152c7d335a47e90a4572403151b7536 [file] [log] [blame]
Guido van Rossum4b8c6ea2000-02-04 15:39:30 +00001"""An FTP client class and some helper functions.
2
Guido van Rossumd2560b01996-05-28 23:41:25 +00003Based on RFC 959: File Transfer Protocol
4(FTP), by J. Postel and J. Reynolds
Guido van Rossum1115ab21992-11-04 15:51:30 +00005
Guido van Rossumd2560b01996-05-28 23:41:25 +00006Example:
7
8>>> from ftplib import FTP
9>>> ftp = FTP('ftp.python.org') # connect to host, default port
10>>> ftp.login() # default, i.e.: user anonymous, passwd user@hostname
Guido van Rossum2f3941d1997-10-07 14:49:56 +000011'230 Guest login ok, access restrictions apply.'
Guido van Rossumd2560b01996-05-28 23:41:25 +000012>>> ftp.retrlines('LIST') # list directory contents
13total 9
14drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .
15drwxr-xr-x 8 root wheel 1024 Jan 3 1994 ..
16drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin
17drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc
18d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming
19drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib
20drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub
21drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr
22-rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg
Guido van Rossum2f3941d1997-10-07 14:49:56 +000023'226 Transfer complete.'
Guido van Rossumd2560b01996-05-28 23:41:25 +000024>>> ftp.quit()
Guido van Rossum2f3941d1997-10-07 14:49:56 +000025'221 Goodbye.'
Guido van Rossumd2560b01996-05-28 23:41:25 +000026>>>
27
28A nice test that reveals some of the network dialogue would be:
29python ftplib.py -d localhost -l -p -l
Guido van Rossum4b8c6ea2000-02-04 15:39:30 +000030"""
Guido van Rossumc567c601992-11-05 22:22:37 +000031
Guido van Rossum98d9fd32000-02-28 15:12:25 +000032#
33# Changes and improvements suggested by Steve Majewski.
34# Modified by Jack to work on the mac.
35# Modified by Siebren to support docstrings and PASV.
36#
Guido van Rossumc567c601992-11-05 22:22:37 +000037
Guido van Rossum1115ab21992-11-04 15:51:30 +000038import os
39import sys
Guido van Rossum1115ab21992-11-04 15:51:30 +000040import string
41
Guido van Rossumb6775db1994-08-01 11:34:53 +000042# Import SOCKS module if it exists, else standard socket module socket
43try:
Guido van Rossum8ca84201998-03-26 20:56:10 +000044 import SOCKS; socket = SOCKS
Guido van Rossumb6775db1994-08-01 11:34:53 +000045except ImportError:
Guido van Rossum8ca84201998-03-26 20:56:10 +000046 import socket
Guido van Rossumb6775db1994-08-01 11:34:53 +000047
Guido van Rossum1115ab21992-11-04 15:51:30 +000048
Guido van Rossumd3166071993-05-24 14:16:22 +000049# Magic number from <socket.h>
50MSG_OOB = 0x1 # Process data out of band
51
52
Guido van Rossumc567c601992-11-05 22:22:37 +000053# The standard FTP server control port
Guido van Rossum1115ab21992-11-04 15:51:30 +000054FTP_PORT = 21
Guido van Rossum1115ab21992-11-04 15:51:30 +000055
56
Guido van Rossum21974791992-11-06 13:34:17 +000057# Exception raised when an error or invalid response is received
Guido van Rossumc567c601992-11-05 22:22:37 +000058error_reply = 'ftplib.error_reply' # unexpected [123]xx reply
59error_temp = 'ftplib.error_temp' # 4xx errors
60error_perm = 'ftplib.error_perm' # 5xx errors
61error_proto = 'ftplib.error_proto' # response does not begin with [1-5]
Guido van Rossum1115ab21992-11-04 15:51:30 +000062
63
Guido van Rossum21974791992-11-06 13:34:17 +000064# All exceptions (hopefully) that may be raised here and that aren't
65# (always) programming errors on our side
66all_errors = (error_reply, error_temp, error_perm, error_proto, \
Guido van Rossumc0e68d11995-09-30 16:51:50 +000067 socket.error, IOError, EOFError)
Guido van Rossum21974791992-11-06 13:34:17 +000068
69
Guido van Rossum1115ab21992-11-04 15:51:30 +000070# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
71CRLF = '\r\n'
72
73
Guido van Rossum1115ab21992-11-04 15:51:30 +000074# The class itself
75class FTP:
76
Guido van Rossumd2560b01996-05-28 23:41:25 +000077 '''An FTP client class.
78
79 To create a connection, call the class using these argument:
80 host, user, passwd, acct
81 These are all strings, and have default value ''.
82 Then use self.connect() with optional host and port argument.
83
84 To download a file, use ftp.retrlines('RETR ' + filename),
85 or ftp.retrbinary() with slightly different arguments.
86 To upload a file, use ftp.storlines() or ftp.storbinary(),
87 which have an open file as argument (see their definitions
88 below for details).
89 The download/upload functions first issue appropriate TYPE
90 and PORT or PASV commands.
91'''
92
93 # Initialization method (called by class instantiation).
Guido van Rossum52fc1f61993-06-17 12:38:10 +000094 # Initialize host to localhost, port to standard ftp port
Guido van Rossumae3b3a31993-11-30 13:43:54 +000095 # Optional arguments are host (for connect()),
96 # and user, passwd, acct (for login())
Guido van Rossumb6775db1994-08-01 11:34:53 +000097 def __init__(self, host = '', user = '', passwd = '', acct = ''):
Guido van Rossum52fc1f61993-06-17 12:38:10 +000098 # Initialize the instance to something mostly harmless
Guido van Rossum1115ab21992-11-04 15:51:30 +000099 self.debugging = 0
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000100 self.host = ''
101 self.port = FTP_PORT
102 self.sock = None
103 self.file = None
104 self.welcome = None
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000105 resp = None
Guido van Rossumb6775db1994-08-01 11:34:53 +0000106 if host:
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000107 resp = self.connect(host)
108 if user: resp = self.login(user, passwd, acct)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000109
Guido van Rossumb6775db1994-08-01 11:34:53 +0000110 def connect(self, host = '', port = 0):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000111 '''Connect to host. Arguments are:
112 - host: hostname to connect to (string, default previous host)
113 - port: port to connect to (integer, default previous port)'''
Guido van Rossumb6775db1994-08-01 11:34:53 +0000114 if host: self.host = host
115 if port: self.port = port
Guido van Rossumd2560b01996-05-28 23:41:25 +0000116 self.passiveserver = 0
Guido van Rossum1115ab21992-11-04 15:51:30 +0000117 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossum93a7c0f2000-03-28 21:45:46 +0000118 self.sock.connect((self.host, self.port))
Guido van Rossum24611f81996-09-30 22:02:50 +0000119 self.file = self.sock.makefile('rb')
Guido van Rossum1115ab21992-11-04 15:51:30 +0000120 self.welcome = self.getresp()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000121 return self.welcome
Guido van Rossum1115ab21992-11-04 15:51:30 +0000122
Guido van Rossum1115ab21992-11-04 15:51:30 +0000123 def getwelcome(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000124 '''Get the welcome message from the server.
125 (this is read and squirreled away by connect())'''
Guido van Rossumebaf1041995-05-05 15:54:14 +0000126 if self.debugging:
127 print '*welcome*', self.sanitize(self.welcome)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000128 return self.welcome
129
Guido van Rossume65cce51993-11-08 15:05:21 +0000130 def set_debuglevel(self, level):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000131 '''Set the debugging level.
132 The required argument level means:
133 0: no debugging output (default)
134 1: print commands and responses but not body text etc.
135 2: also print raw lines read and sent before stripping CR/LF'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000136 self.debugging = level
Guido van Rossume65cce51993-11-08 15:05:21 +0000137 debug = set_debuglevel
Guido van Rossum1115ab21992-11-04 15:51:30 +0000138
Guido van Rossumd2560b01996-05-28 23:41:25 +0000139 def set_pasv(self, val):
140 '''Use passive or active mode for data transfers.
141 With a false argument, use the normal PORT mode,
142 With a true argument, use the PASV command.'''
143 self.passiveserver = val
144
Guido van Rossumebaf1041995-05-05 15:54:14 +0000145 # Internal: "sanitize" a string for printing
146 def sanitize(self, s):
147 if s[:5] == 'pass ' or s[:5] == 'PASS ':
148 i = len(s)
149 while i > 5 and s[i-1] in '\r\n':
150 i = i-1
151 s = s[:5] + '*'*(i-5) + s[i:]
152 return `s`
153
Guido van Rossum1115ab21992-11-04 15:51:30 +0000154 # Internal: send one line to the server, appending CRLF
155 def putline(self, line):
156 line = line + CRLF
Guido van Rossumebaf1041995-05-05 15:54:14 +0000157 if self.debugging > 1: print '*put*', self.sanitize(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000158 self.sock.send(line)
159
160 # Internal: send one command to the server (through putline())
161 def putcmd(self, line):
Guido van Rossumebaf1041995-05-05 15:54:14 +0000162 if self.debugging: print '*cmd*', self.sanitize(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000163 self.putline(line)
164
165 # Internal: return one line from the server, stripping CRLF.
166 # Raise EOFError if the connection is closed
167 def getline(self):
168 line = self.file.readline()
169 if self.debugging > 1:
Guido van Rossumebaf1041995-05-05 15:54:14 +0000170 print '*get*', self.sanitize(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000171 if not line: raise EOFError
172 if line[-2:] == CRLF: line = line[:-2]
173 elif line[-1:] in CRLF: line = line[:-1]
174 return line
175
176 # Internal: get a response from the server, which may possibly
177 # consist of multiple lines. Return a single string with no
178 # trailing CRLF. If the response consists of multiple lines,
179 # these are separated by '\n' characters in the string
180 def getmultiline(self):
181 line = self.getline()
182 if line[3:4] == '-':
183 code = line[:3]
184 while 1:
185 nextline = self.getline()
186 line = line + ('\n' + nextline)
187 if nextline[:3] == code and \
188 nextline[3:4] <> '-':
189 break
190 return line
191
192 # Internal: get a response from the server.
193 # Raise various errors if the response indicates an error
194 def getresp(self):
195 resp = self.getmultiline()
Guido van Rossumebaf1041995-05-05 15:54:14 +0000196 if self.debugging: print '*resp*', self.sanitize(resp)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000197 self.lastresp = resp[:3]
198 c = resp[:1]
199 if c == '4':
Guido van Rossumc567c601992-11-05 22:22:37 +0000200 raise error_temp, resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000201 if c == '5':
Guido van Rossumc567c601992-11-05 22:22:37 +0000202 raise error_perm, resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000203 if c not in '123':
Guido van Rossumc567c601992-11-05 22:22:37 +0000204 raise error_proto, resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000205 return resp
206
Guido van Rossumc567c601992-11-05 22:22:37 +0000207 def voidresp(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000208 """Expect a response beginning with '2'."""
Guido van Rossumc567c601992-11-05 22:22:37 +0000209 resp = self.getresp()
210 if resp[0] <> '2':
211 raise error_reply, resp
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000212 return resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000213
Guido van Rossumd3166071993-05-24 14:16:22 +0000214 def abort(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000215 '''Abort a file transfer. Uses out-of-band data.
216 This does not follow the procedure from the RFC to send Telnet
217 IP and Synch; that doesn't seem to work with the servers I've
218 tried. Instead, just send the ABOR command as OOB data.'''
Guido van Rossumd3166071993-05-24 14:16:22 +0000219 line = 'ABOR' + CRLF
Guido van Rossumebaf1041995-05-05 15:54:14 +0000220 if self.debugging > 1: print '*put urgent*', self.sanitize(line)
Guido van Rossumd3166071993-05-24 14:16:22 +0000221 self.sock.send(line, MSG_OOB)
222 resp = self.getmultiline()
223 if resp[:3] not in ('426', '226'):
224 raise error_proto, resp
225
Guido van Rossum1115ab21992-11-04 15:51:30 +0000226 def sendcmd(self, cmd):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000227 '''Send a command and return the response.'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000228 self.putcmd(cmd)
229 return self.getresp()
230
Guido van Rossumc567c601992-11-05 22:22:37 +0000231 def voidcmd(self, cmd):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000232 """Send a command and expect a response beginning with '2'."""
Guido van Rossumc68a4011992-11-05 23:01:42 +0000233 self.putcmd(cmd)
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000234 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000235
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000236 def sendport(self, host, port):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000237 '''Send a PORT command with the current host and the given port number.'''
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000238 hbytes = string.splitfields(host, '.')
Guido van Rossum1115ab21992-11-04 15:51:30 +0000239 pbytes = [`port/256`, `port%256`]
240 bytes = hbytes + pbytes
241 cmd = 'PORT ' + string.joinfields(bytes, ',')
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000242 return self.voidcmd(cmd)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000243
Guido van Rossum1115ab21992-11-04 15:51:30 +0000244 def makeport(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000245 '''Create a new socket and send a PORT command for it.'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000246 global nextport
Guido van Rossum1115ab21992-11-04 15:51:30 +0000247 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossum303c1791995-06-20 17:21:42 +0000248 sock.bind(('', 0))
Guido van Rossumb6775db1994-08-01 11:34:53 +0000249 sock.listen(1)
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000250 dummyhost, port = sock.getsockname() # Get proper port
251 host, dummyport = self.sock.getsockname() # Get proper host
252 resp = self.sendport(host, port)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000253 return sock
254
Fred Drake4de02d91997-01-10 18:26:09 +0000255 def ntransfercmd(self, cmd):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000256 '''Initiate a transfer over the data connection.
257 If the transfer is active, send a port command and
258 the transfer command, and accept the connection.
259 If the server is passive, send a pasv command, connect
260 to it, and start the transfer command.
Fred Drake4de02d91997-01-10 18:26:09 +0000261 Either way, return the socket for the connection and
262 the expected size of the transfer. The expected size
263 may be None if it could not be determined.'''
264 size = None
Guido van Rossumd2560b01996-05-28 23:41:25 +0000265 if self.passiveserver:
266 host, port = parse227(self.sendcmd('PASV'))
267 conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossum93a7c0f2000-03-28 21:45:46 +0000268 conn.connect((host, port))
Guido van Rossumd2560b01996-05-28 23:41:25 +0000269 resp = self.sendcmd(cmd)
270 if resp[0] <> '1':
271 raise error_reply, resp
272 else:
273 sock = self.makeport()
274 resp = self.sendcmd(cmd)
275 if resp[0] <> '1':
276 raise error_reply, resp
277 conn, sockaddr = sock.accept()
Fred Drake4de02d91997-01-10 18:26:09 +0000278 if resp[:3] == '150':
279 # this is conditional in case we received a 125
280 size = parse150(resp)
281 return conn, size
282
283 def transfercmd(self, cmd):
284 '''Initiate a transfer over the data connection. Returns
285 the socket for the connection. See also ntransfercmd().'''
286 return self.ntransfercmd(cmd)[0]
Guido van Rossumc567c601992-11-05 22:22:37 +0000287
Guido van Rossumb6775db1994-08-01 11:34:53 +0000288 def login(self, user = '', passwd = '', acct = ''):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000289 '''Login, default anonymous.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000290 if not user: user = 'anonymous'
Guido van Rossum98245091998-02-19 21:15:44 +0000291 if not passwd: passwd = ''
292 if not acct: acct = ''
Guido van Rossumc567c601992-11-05 22:22:37 +0000293 if user == 'anonymous' and passwd in ('', '-'):
294 thishost = socket.gethostname()
Jack Jansen2db6bfc1995-05-04 15:02:18 +0000295 # Make sure it is fully qualified
296 if not '.' in thishost:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000297 thisaddr = socket.gethostbyname(thishost)
298 firstname, names, unused = \
299 socket.gethostbyaddr(thisaddr)
300 names.insert(0, firstname)
301 for name in names:
302 if '.' in name:
303 thishost = name
304 break
Jack Jansen40b98351995-01-19 12:24:45 +0000305 try:
306 if os.environ.has_key('LOGNAME'):
307 realuser = os.environ['LOGNAME']
308 elif os.environ.has_key('USER'):
309 realuser = os.environ['USER']
310 else:
311 realuser = 'anonymous'
312 except AttributeError:
313 # Not all systems have os.environ....
Guido van Rossumc567c601992-11-05 22:22:37 +0000314 realuser = 'anonymous'
315 passwd = passwd + realuser + '@' + thishost
316 resp = self.sendcmd('USER ' + user)
317 if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
318 if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
319 if resp[0] <> '2':
320 raise error_reply, resp
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000321 return resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000322
Guido van Rossumab76af31997-12-03 19:34:14 +0000323 def retrbinary(self, cmd, callback, blocksize=8192):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000324 '''Retrieve data in binary mode.
325 The argument is a RETR command.
326 The callback function is called for each block.
327 This creates a new port for you'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000328 self.voidcmd('TYPE I')
329 conn = self.transfercmd(cmd)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000330 while 1:
331 data = conn.recv(blocksize)
332 if not data:
333 break
334 callback(data)
335 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000336 return self.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000337
Guido van Rossumb6775db1994-08-01 11:34:53 +0000338 def retrlines(self, cmd, callback = None):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000339 '''Retrieve data in line mode.
340 The argument is a RETR or LIST command.
341 The callback function (2nd argument) is called for each line,
342 with trailing CRLF stripped. This creates a new port for you.
Fred Draked5f173b1999-07-07 13:36:59 +0000343 print_line() is the default callback.'''
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000344 if not callback: callback = print_line
Guido van Rossumc567c601992-11-05 22:22:37 +0000345 resp = self.sendcmd('TYPE A')
346 conn = self.transfercmd(cmd)
Guido van Rossum24611f81996-09-30 22:02:50 +0000347 fp = conn.makefile('rb')
Guido van Rossum1115ab21992-11-04 15:51:30 +0000348 while 1:
349 line = fp.readline()
Guido van Rossumc0e68d11995-09-30 16:51:50 +0000350 if self.debugging > 2: print '*retr*', `line`
Guido van Rossum1115ab21992-11-04 15:51:30 +0000351 if not line:
352 break
353 if line[-2:] == CRLF:
354 line = line[:-2]
Guido van Rossumc6769c51998-12-21 16:26:31 +0000355 elif line[-1:] == '\n':
Guido van Rossum1115ab21992-11-04 15:51:30 +0000356 line = line[:-1]
357 callback(line)
358 fp.close()
359 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000360 return self.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000361
Guido van Rossumc567c601992-11-05 22:22:37 +0000362 def storbinary(self, cmd, fp, blocksize):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000363 '''Store a file in binary mode.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000364 self.voidcmd('TYPE I')
365 conn = self.transfercmd(cmd)
366 while 1:
367 buf = fp.read(blocksize)
368 if not buf: break
369 conn.send(buf)
370 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000371 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000372
Guido van Rossumc567c601992-11-05 22:22:37 +0000373 def storlines(self, cmd, fp):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000374 '''Store a file in line mode.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000375 self.voidcmd('TYPE A')
376 conn = self.transfercmd(cmd)
377 while 1:
378 buf = fp.readline()
379 if not buf: break
380 if buf[-2:] <> CRLF:
381 if buf[-1] in CRLF: buf = buf[:-1]
382 buf = buf + CRLF
383 conn.send(buf)
384 conn.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000385 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000386
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000387 def acct(self, password):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000388 '''Send new account name.'''
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000389 cmd = 'ACCT ' + password
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000390 return self.voidcmd(cmd)
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000391
Guido van Rossumc567c601992-11-05 22:22:37 +0000392 def nlst(self, *args):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000393 '''Return a list of files in a given directory (default the current).'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000394 cmd = 'NLST'
395 for arg in args:
396 cmd = cmd + (' ' + arg)
397 files = []
398 self.retrlines(cmd, files.append)
399 return files
400
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000401 def dir(self, *args):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000402 '''List a directory in long form.
403 By default list current directory to stdout.
404 Optional last argument is callback function; all
405 non-empty arguments before it are concatenated to the
406 LIST command. (This *should* only be used for a pathname.)'''
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000407 cmd = 'LIST'
408 func = None
409 if args[-1:] and type(args[-1]) != type(''):
410 args, func = args[:-1], args[-1]
411 for arg in args:
412 if arg:
413 cmd = cmd + (' ' + arg)
414 self.retrlines(cmd, func)
415
Guido van Rossumc567c601992-11-05 22:22:37 +0000416 def rename(self, fromname, toname):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000417 '''Rename a file.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000418 resp = self.sendcmd('RNFR ' + fromname)
419 if resp[0] <> '3':
Guido van Rossum1115ab21992-11-04 15:51:30 +0000420 raise error_reply, resp
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000421 return self.voidcmd('RNTO ' + toname)
Guido van Rossumc567c601992-11-05 22:22:37 +0000422
Guido van Rossum8ca84201998-03-26 20:56:10 +0000423 def delete(self, filename):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000424 '''Delete a file.'''
Guido van Rossum8ca84201998-03-26 20:56:10 +0000425 resp = self.sendcmd('DELE ' + filename)
Guido van Rossum6bbd1d01998-07-02 20:41:20 +0000426 if resp[:3] in ('250', '200'):
Guido van Rossum8ca84201998-03-26 20:56:10 +0000427 return resp
428 elif resp[:1] == '5':
429 raise error_perm, resp
430 else:
431 raise error_reply, resp
Guido van Rossuma61bdeb1995-10-11 17:36:31 +0000432
Guido van Rossum02cf5821993-05-17 08:00:02 +0000433 def cwd(self, dirname):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000434 '''Change to a directory.'''
Guido van Rossumdf563861993-07-06 15:19:36 +0000435 if dirname == '..':
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000436 try:
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000437 return self.voidcmd('CDUP')
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000438 except error_perm, msg:
439 if msg[:3] != '500':
440 raise error_perm, msg
Guido van Rossum1ebcf6a1999-08-18 21:51:10 +0000441 elif dirname == '':
442 dirname = '.' # does nothing, but could return error
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000443 cmd = 'CWD ' + dirname
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000444 return self.voidcmd(cmd)
Guido van Rossum02cf5821993-05-17 08:00:02 +0000445
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000446 def size(self, filename):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000447 '''Retrieve the size of a file.'''
448 # Note that the RFC doesn't say anything about 'SIZE'
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000449 resp = self.sendcmd('SIZE ' + filename)
450 if resp[:3] == '213':
451 return string.atoi(string.strip(resp[3:]))
452
Guido van Rossumc567c601992-11-05 22:22:37 +0000453 def mkd(self, dirname):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000454 '''Make a directory, return its full pathname.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000455 resp = self.sendcmd('MKD ' + dirname)
456 return parse257(resp)
457
Guido van Rossum98245091998-02-19 21:15:44 +0000458 def rmd(self, dirname):
459 '''Remove a directory.'''
460 return self.voidcmd('RMD ' + dirname)
461
Guido van Rossumc567c601992-11-05 22:22:37 +0000462 def pwd(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000463 '''Return current working directory.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000464 resp = self.sendcmd('PWD')
465 return parse257(resp)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000466
Guido van Rossum1115ab21992-11-04 15:51:30 +0000467 def quit(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000468 '''Quit, and close the connection.'''
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000469 resp = self.voidcmd('QUIT')
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000470 self.close()
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000471 return resp
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000472
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000473 def close(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000474 '''Close the connection without assuming anything about it.'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000475 self.file.close()
476 self.sock.close()
Guido van Rossumc567c601992-11-05 22:22:37 +0000477 del self.file, self.sock
478
479
Guido van Rossumacfb82a1997-10-22 20:49:52 +0000480_150_re = None
Fred Drake4de02d91997-01-10 18:26:09 +0000481
482def parse150(resp):
Guido van Rossum8ca84201998-03-26 20:56:10 +0000483 '''Parse the '150' response for a RETR request.
484 Returns the expected transfer size or None; size is not guaranteed to
485 be present in the 150 message.
486 '''
487 if resp[:3] != '150':
488 raise error_reply, resp
489 global _150_re
490 if _150_re is None:
491 import re
Fred Drake9291d271998-04-27 14:39:44 +0000492 _150_re = re.compile("150 .* \((\d+) bytes\)", re.IGNORECASE)
Guido van Rossum8ca84201998-03-26 20:56:10 +0000493 m = _150_re.match(resp)
494 if m:
495 return string.atoi(m.group(1))
496 return None
Fred Drake4de02d91997-01-10 18:26:09 +0000497
498
Guido van Rossumd2560b01996-05-28 23:41:25 +0000499def parse227(resp):
500 '''Parse the '227' response for a PASV request.
501 Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
502 Return ('host.addr.as.numbers', port#) tuple.'''
503
504 if resp[:3] <> '227':
505 raise error_reply, resp
506 left = string.find(resp, '(')
507 if left < 0: raise error_proto, resp
508 right = string.find(resp, ')', left + 1)
509 if right < 0:
510 raise error_proto, resp # should contain '(h1,h2,h3,h4,p1,p2)'
511 numbers = string.split(resp[left+1:right], ',')
512 if len(numbers) <> 6:
513 raise error_proto, resp
514 host = string.join(numbers[:4], '.')
515 port = (string.atoi(numbers[4]) << 8) + string.atoi(numbers[5])
516 return host, port
Guido van Rossumd2560b01996-05-28 23:41:25 +0000517
518
Guido van Rossumc567c601992-11-05 22:22:37 +0000519def parse257(resp):
Guido van Rossum98245091998-02-19 21:15:44 +0000520 '''Parse the '257' response for a MKD or PWD request.
521 This is a response to a MKD or PWD request: a directory name.
Guido van Rossumd2560b01996-05-28 23:41:25 +0000522 Returns the directoryname in the 257 reply.'''
523
Guido van Rossumc567c601992-11-05 22:22:37 +0000524 if resp[:3] <> '257':
525 raise error_reply, resp
526 if resp[3:5] <> ' "':
527 return '' # Not compliant to RFC 959, but UNIX ftpd does this
528 dirname = ''
529 i = 5
530 n = len(resp)
531 while i < n:
532 c = resp[i]
533 i = i+1
534 if c == '"':
535 if i >= n or resp[i] <> '"':
536 break
537 i = i+1
538 dirname = dirname + c
539 return dirname
Guido van Rossum1115ab21992-11-04 15:51:30 +0000540
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000541
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000542def print_line(line):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000543 '''Default retrlines callback to print a line.'''
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000544 print line
545
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000546
Guido van Rossumd2560b01996-05-28 23:41:25 +0000547def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
548 '''Copy file from one FTP-instance to another.'''
549 if not targetname: targetname = sourcename
550 type = 'TYPE ' + type
551 source.voidcmd(type)
552 target.voidcmd(type)
553 sourcehost, sourceport = parse227(source.sendcmd('PASV'))
554 target.sendport(sourcehost, sourceport)
555 # RFC 959: the user must "listen" [...] BEFORE sending the
556 # transfer request.
557 # So: STOR before RETR, because here the target is a "user".
558 treply = target.sendcmd('STOR ' + targetname)
559 if treply[:3] not in ('125', '150'): raise error_proto # RFC 959
560 sreply = source.sendcmd('RETR ' + sourcename)
561 if sreply[:3] not in ('125', '150'): raise error_proto # RFC 959
562 source.voidresp()
563 target.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000564
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000565
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000566class Netrc:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000567 """Class to parse & provide access to 'netrc' format files.
Fred Drake475d51d1997-06-24 22:02:54 +0000568
Guido van Rossum8ca84201998-03-26 20:56:10 +0000569 See the netrc(4) man page for information on the file format.
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000570
Guido van Rossumc822a451998-12-22 16:49:16 +0000571 WARNING: This class is obsolete -- use module netrc instead.
572
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000573 """
Guido van Rossum8ca84201998-03-26 20:56:10 +0000574 __defuser = None
575 __defpasswd = None
576 __defacct = None
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000577
Guido van Rossum8ca84201998-03-26 20:56:10 +0000578 def __init__(self, filename=None):
579 if not filename:
580 if os.environ.has_key("HOME"):
581 filename = os.path.join(os.environ["HOME"],
582 ".netrc")
583 else:
584 raise IOError, \
585 "specify file to load or set $HOME"
586 self.__hosts = {}
587 self.__macros = {}
588 fp = open(filename, "r")
589 in_macro = 0
590 while 1:
591 line = fp.readline()
592 if not line: break
593 if in_macro and string.strip(line):
594 macro_lines.append(line)
595 continue
596 elif in_macro:
597 self.__macros[macro_name] = tuple(macro_lines)
598 in_macro = 0
599 words = string.split(line)
600 host = user = passwd = acct = None
601 default = 0
602 i = 0
603 while i < len(words):
604 w1 = words[i]
605 if i+1 < len(words):
606 w2 = words[i + 1]
607 else:
608 w2 = None
609 if w1 == 'default':
610 default = 1
611 elif w1 == 'machine' and w2:
612 host = string.lower(w2)
613 i = i + 1
614 elif w1 == 'login' and w2:
615 user = w2
616 i = i + 1
617 elif w1 == 'password' and w2:
618 passwd = w2
619 i = i + 1
620 elif w1 == 'account' and w2:
621 acct = w2
622 i = i + 1
623 elif w1 == 'macdef' and w2:
624 macro_name = w2
625 macro_lines = []
626 in_macro = 1
627 break
628 i = i + 1
629 if default:
630 self.__defuser = user or self.__defuser
631 self.__defpasswd = passwd or self.__defpasswd
632 self.__defacct = acct or self.__defacct
633 if host:
634 if self.__hosts.has_key(host):
635 ouser, opasswd, oacct = \
636 self.__hosts[host]
637 user = user or ouser
638 passwd = passwd or opasswd
639 acct = acct or oacct
640 self.__hosts[host] = user, passwd, acct
641 fp.close()
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000642
Guido van Rossum8ca84201998-03-26 20:56:10 +0000643 def get_hosts(self):
644 """Return a list of hosts mentioned in the .netrc file."""
645 return self.__hosts.keys()
646
647 def get_account(self, host):
648 """Returns login information for the named host.
649
650 The return value is a triple containing userid,
651 password, and the accounting field.
652
653 """
654 host = string.lower(host)
655 user = passwd = acct = None
656 if self.__hosts.has_key(host):
657 user, passwd, acct = self.__hosts[host]
658 user = user or self.__defuser
659 passwd = passwd or self.__defpasswd
660 acct = acct or self.__defacct
661 return user, passwd, acct
662
663 def get_macros(self):
664 """Return a list of all defined macro names."""
665 return self.__macros.keys()
666
667 def get_macro(self, macro):
668 """Return a sequence of lines which define a named macro."""
669 return self.__macros[macro]
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000670
Fred Drake475d51d1997-06-24 22:02:54 +0000671
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000672
Guido van Rossum1115ab21992-11-04 15:51:30 +0000673def test():
Guido van Rossumd2560b01996-05-28 23:41:25 +0000674 '''Test program.
Fred Drake475d51d1997-06-24 22:02:54 +0000675 Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...'''
Guido van Rossumd2560b01996-05-28 23:41:25 +0000676
Guido van Rossumb6775db1994-08-01 11:34:53 +0000677 debugging = 0
Fred Drake475d51d1997-06-24 22:02:54 +0000678 rcfile = None
Guido van Rossumb6775db1994-08-01 11:34:53 +0000679 while sys.argv[1] == '-d':
680 debugging = debugging+1
681 del sys.argv[1]
Fred Drake475d51d1997-06-24 22:02:54 +0000682 if sys.argv[1][:2] == '-r':
683 # get name of alternate ~/.netrc file:
684 rcfile = sys.argv[1][2:]
685 del sys.argv[1]
Guido van Rossumb6775db1994-08-01 11:34:53 +0000686 host = sys.argv[1]
687 ftp = FTP(host)
688 ftp.set_debuglevel(debugging)
Fred Drake475d51d1997-06-24 22:02:54 +0000689 userid = passwd = acct = ''
690 try:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000691 netrc = Netrc(rcfile)
Fred Drake475d51d1997-06-24 22:02:54 +0000692 except IOError:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000693 if rcfile is not None:
694 sys.stderr.write("Could not open account file"
695 " -- using anonymous login.")
Fred Drake475d51d1997-06-24 22:02:54 +0000696 else:
Guido van Rossum8ca84201998-03-26 20:56:10 +0000697 try:
698 userid, passwd, acct = netrc.get_account(host)
699 except KeyError:
700 # no account for host
701 sys.stderr.write(
702 "No account -- using anonymous login.")
Fred Drake475d51d1997-06-24 22:02:54 +0000703 ftp.login(userid, passwd, acct)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000704 for file in sys.argv[2:]:
705 if file[:2] == '-l':
706 ftp.dir(file[2:])
707 elif file[:2] == '-d':
708 cmd = 'CWD'
709 if file[2:]: cmd = cmd + ' ' + file[2:]
710 resp = ftp.sendcmd(cmd)
Guido van Rossumd2560b01996-05-28 23:41:25 +0000711 elif file == '-p':
712 ftp.set_pasv(not ftp.passiveserver)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000713 else:
714 ftp.retrbinary('RETR ' + file, \
715 sys.stdout.write, 1024)
716 ftp.quit()
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000717
718
719if __name__ == '__main__':
720 test()