blob: 851e03c050acd659ffebdc37300c01f4f6fe2879 [file] [log] [blame]
Guido van Rossumd2560b01996-05-28 23:41:25 +00001'''An FTP client class, and some helper functions.
2Based on RFC 959: File Transfer Protocol
3(FTP), by J. Postel and J. Reynolds
Guido van Rossum1115ab21992-11-04 15:51:30 +00004
Guido van Rossumd2560b01996-05-28 23:41:25 +00005Changes and improvements suggested by Steve Majewski.
6Modified by Jack to work on the mac.
7Modified by Siebren to support docstrings and PASV.
Guido van Rossumae3b3a31993-11-30 13:43:54 +00008
Guido van Rossum1115ab21992-11-04 15:51:30 +00009
Guido van Rossumd2560b01996-05-28 23:41:25 +000010Example:
11
12>>> from ftplib import FTP
13>>> ftp = FTP('ftp.python.org') # connect to host, default port
14>>> ftp.login() # default, i.e.: user anonymous, passwd user@hostname
15>>> ftp.retrlines('LIST') # list directory contents
16total 9
17drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .
18drwxr-xr-x 8 root wheel 1024 Jan 3 1994 ..
19drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin
20drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc
21d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming
22drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib
23drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub
24drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr
25-rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg
26>>> ftp.quit()
27>>>
28
29A nice test that reveals some of the network dialogue would be:
30python ftplib.py -d localhost -l -p -l
31'''
Guido van Rossumc567c601992-11-05 22:22:37 +000032
33
Guido van Rossum1115ab21992-11-04 15:51:30 +000034import os
35import sys
Guido van Rossum1115ab21992-11-04 15:51:30 +000036import string
37
Guido van Rossumb6775db1994-08-01 11:34:53 +000038# Import SOCKS module if it exists, else standard socket module socket
39try:
40 import SOCKS; socket = SOCKS
41except ImportError:
42 import socket
43
Guido van Rossum1115ab21992-11-04 15:51:30 +000044
Guido van Rossumd3166071993-05-24 14:16:22 +000045# Magic number from <socket.h>
46MSG_OOB = 0x1 # Process data out of band
47
48
Guido van Rossumc567c601992-11-05 22:22:37 +000049# The standard FTP server control port
Guido van Rossum1115ab21992-11-04 15:51:30 +000050FTP_PORT = 21
Guido van Rossum1115ab21992-11-04 15:51:30 +000051
52
Guido van Rossum21974791992-11-06 13:34:17 +000053# Exception raised when an error or invalid response is received
Guido van Rossumc567c601992-11-05 22:22:37 +000054error_reply = 'ftplib.error_reply' # unexpected [123]xx reply
55error_temp = 'ftplib.error_temp' # 4xx errors
56error_perm = 'ftplib.error_perm' # 5xx errors
57error_proto = 'ftplib.error_proto' # response does not begin with [1-5]
Guido van Rossum1115ab21992-11-04 15:51:30 +000058
59
Guido van Rossum21974791992-11-06 13:34:17 +000060# All exceptions (hopefully) that may be raised here and that aren't
61# (always) programming errors on our side
62all_errors = (error_reply, error_temp, error_perm, error_proto, \
Guido van Rossumc0e68d11995-09-30 16:51:50 +000063 socket.error, IOError, EOFError)
Guido van Rossum21974791992-11-06 13:34:17 +000064
65
Guido van Rossum1115ab21992-11-04 15:51:30 +000066# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
67CRLF = '\r\n'
68
69
Guido van Rossum1115ab21992-11-04 15:51:30 +000070# The class itself
71class FTP:
72
Guido van Rossumd2560b01996-05-28 23:41:25 +000073 '''An FTP client class.
74
75 To create a connection, call the class using these argument:
76 host, user, passwd, acct
77 These are all strings, and have default value ''.
78 Then use self.connect() with optional host and port argument.
79
80 To download a file, use ftp.retrlines('RETR ' + filename),
81 or ftp.retrbinary() with slightly different arguments.
82 To upload a file, use ftp.storlines() or ftp.storbinary(),
83 which have an open file as argument (see their definitions
84 below for details).
85 The download/upload functions first issue appropriate TYPE
86 and PORT or PASV commands.
87'''
88
89 # Initialization method (called by class instantiation).
Guido van Rossum52fc1f61993-06-17 12:38:10 +000090 # Initialize host to localhost, port to standard ftp port
Guido van Rossumae3b3a31993-11-30 13:43:54 +000091 # Optional arguments are host (for connect()),
92 # and user, passwd, acct (for login())
Guido van Rossumb6775db1994-08-01 11:34:53 +000093 def __init__(self, host = '', user = '', passwd = '', acct = ''):
Guido van Rossum52fc1f61993-06-17 12:38:10 +000094 # Initialize the instance to something mostly harmless
Guido van Rossum1115ab21992-11-04 15:51:30 +000095 self.debugging = 0
Guido van Rossum52fc1f61993-06-17 12:38:10 +000096 self.host = ''
97 self.port = FTP_PORT
98 self.sock = None
99 self.file = None
100 self.welcome = None
Guido van Rossumb6775db1994-08-01 11:34:53 +0000101 if host:
102 self.connect(host)
103 if user: self.login(user, passwd, acct)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000104
Guido van Rossumb6775db1994-08-01 11:34:53 +0000105 def connect(self, host = '', port = 0):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000106 '''Connect to host. Arguments are:
107 - host: hostname to connect to (string, default previous host)
108 - port: port to connect to (integer, default previous port)'''
Guido van Rossumb6775db1994-08-01 11:34:53 +0000109 if host: self.host = host
110 if port: self.port = port
Guido van Rossumd2560b01996-05-28 23:41:25 +0000111 self.passiveserver = 0
Guido van Rossum1115ab21992-11-04 15:51:30 +0000112 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
113 self.sock.connect(self.host, self.port)
Guido van Rossum24611f81996-09-30 22:02:50 +0000114 self.file = self.sock.makefile('rb')
Guido van Rossum1115ab21992-11-04 15:51:30 +0000115 self.welcome = self.getresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000116
Guido van Rossum1115ab21992-11-04 15:51:30 +0000117 def getwelcome(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000118 '''Get the welcome message from the server.
119 (this is read and squirreled away by connect())'''
Guido van Rossumebaf1041995-05-05 15:54:14 +0000120 if self.debugging:
121 print '*welcome*', self.sanitize(self.welcome)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000122 return self.welcome
123
Guido van Rossume65cce51993-11-08 15:05:21 +0000124 def set_debuglevel(self, level):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000125 '''Set the debugging level.
126 The required argument level means:
127 0: no debugging output (default)
128 1: print commands and responses but not body text etc.
129 2: also print raw lines read and sent before stripping CR/LF'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000130 self.debugging = level
Guido van Rossume65cce51993-11-08 15:05:21 +0000131 debug = set_debuglevel
Guido van Rossum1115ab21992-11-04 15:51:30 +0000132
Guido van Rossumd2560b01996-05-28 23:41:25 +0000133 def set_pasv(self, val):
134 '''Use passive or active mode for data transfers.
135 With a false argument, use the normal PORT mode,
136 With a true argument, use the PASV command.'''
137 self.passiveserver = val
138
Guido van Rossumebaf1041995-05-05 15:54:14 +0000139 # Internal: "sanitize" a string for printing
140 def sanitize(self, s):
141 if s[:5] == 'pass ' or s[:5] == 'PASS ':
142 i = len(s)
143 while i > 5 and s[i-1] in '\r\n':
144 i = i-1
145 s = s[:5] + '*'*(i-5) + s[i:]
146 return `s`
147
Guido van Rossum1115ab21992-11-04 15:51:30 +0000148 # Internal: send one line to the server, appending CRLF
149 def putline(self, line):
150 line = line + CRLF
Guido van Rossumebaf1041995-05-05 15:54:14 +0000151 if self.debugging > 1: print '*put*', self.sanitize(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000152 self.sock.send(line)
153
154 # Internal: send one command to the server (through putline())
155 def putcmd(self, line):
Guido van Rossumebaf1041995-05-05 15:54:14 +0000156 if self.debugging: print '*cmd*', self.sanitize(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000157 self.putline(line)
158
159 # Internal: return one line from the server, stripping CRLF.
160 # Raise EOFError if the connection is closed
161 def getline(self):
162 line = self.file.readline()
163 if self.debugging > 1:
Guido van Rossumebaf1041995-05-05 15:54:14 +0000164 print '*get*', self.sanitize(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000165 if not line: raise EOFError
166 if line[-2:] == CRLF: line = line[:-2]
167 elif line[-1:] in CRLF: line = line[:-1]
168 return line
169
170 # Internal: get a response from the server, which may possibly
171 # consist of multiple lines. Return a single string with no
172 # trailing CRLF. If the response consists of multiple lines,
173 # these are separated by '\n' characters in the string
174 def getmultiline(self):
175 line = self.getline()
176 if line[3:4] == '-':
177 code = line[:3]
178 while 1:
179 nextline = self.getline()
180 line = line + ('\n' + nextline)
181 if nextline[:3] == code and \
182 nextline[3:4] <> '-':
183 break
184 return line
185
186 # Internal: get a response from the server.
187 # Raise various errors if the response indicates an error
188 def getresp(self):
189 resp = self.getmultiline()
Guido van Rossumebaf1041995-05-05 15:54:14 +0000190 if self.debugging: print '*resp*', self.sanitize(resp)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000191 self.lastresp = resp[:3]
192 c = resp[:1]
193 if c == '4':
Guido van Rossumc567c601992-11-05 22:22:37 +0000194 raise error_temp, resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000195 if c == '5':
Guido van Rossumc567c601992-11-05 22:22:37 +0000196 raise error_perm, resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000197 if c not in '123':
Guido van Rossumc567c601992-11-05 22:22:37 +0000198 raise error_proto, resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000199 return resp
200
Guido van Rossumc567c601992-11-05 22:22:37 +0000201 def voidresp(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000202 """Expect a response beginning with '2'."""
Guido van Rossumc567c601992-11-05 22:22:37 +0000203 resp = self.getresp()
204 if resp[0] <> '2':
205 raise error_reply, resp
206
Guido van Rossumd3166071993-05-24 14:16:22 +0000207 def abort(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000208 '''Abort a file transfer. Uses out-of-band data.
209 This does not follow the procedure from the RFC to send Telnet
210 IP and Synch; that doesn't seem to work with the servers I've
211 tried. Instead, just send the ABOR command as OOB data.'''
Guido van Rossumd3166071993-05-24 14:16:22 +0000212 line = 'ABOR' + CRLF
Guido van Rossumebaf1041995-05-05 15:54:14 +0000213 if self.debugging > 1: print '*put urgent*', self.sanitize(line)
Guido van Rossumd3166071993-05-24 14:16:22 +0000214 self.sock.send(line, MSG_OOB)
215 resp = self.getmultiline()
216 if resp[:3] not in ('426', '226'):
217 raise error_proto, resp
218
Guido van Rossum1115ab21992-11-04 15:51:30 +0000219 def sendcmd(self, cmd):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000220 '''Send a command and return the response.'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000221 self.putcmd(cmd)
222 return self.getresp()
223
Guido van Rossumc567c601992-11-05 22:22:37 +0000224 def voidcmd(self, cmd):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000225 """Send a command and expect a response beginning with '2'."""
Guido van Rossumc68a4011992-11-05 23:01:42 +0000226 self.putcmd(cmd)
227 self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000228
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000229 def sendport(self, host, port):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000230 '''Send a PORT command with the current host and the given port number.'''
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000231 hbytes = string.splitfields(host, '.')
Guido van Rossum1115ab21992-11-04 15:51:30 +0000232 pbytes = [`port/256`, `port%256`]
233 bytes = hbytes + pbytes
234 cmd = 'PORT ' + string.joinfields(bytes, ',')
Guido van Rossumc567c601992-11-05 22:22:37 +0000235 self.voidcmd(cmd)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000236
Guido van Rossum1115ab21992-11-04 15:51:30 +0000237 def makeport(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000238 '''Create a new socket and send a PORT command for it.'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000239 global nextport
Guido van Rossum1115ab21992-11-04 15:51:30 +0000240 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Guido van Rossum303c1791995-06-20 17:21:42 +0000241 sock.bind(('', 0))
Guido van Rossumb6775db1994-08-01 11:34:53 +0000242 sock.listen(1)
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000243 dummyhost, port = sock.getsockname() # Get proper port
244 host, dummyport = self.sock.getsockname() # Get proper host
245 resp = self.sendport(host, port)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000246 return sock
247
Fred Drake4de02d91997-01-10 18:26:09 +0000248 def ntransfercmd(self, cmd):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000249 '''Initiate a transfer over the data connection.
250 If the transfer is active, send a port command and
251 the transfer command, and accept the connection.
252 If the server is passive, send a pasv command, connect
253 to it, and start the transfer command.
Fred Drake4de02d91997-01-10 18:26:09 +0000254 Either way, return the socket for the connection and
255 the expected size of the transfer. The expected size
256 may be None if it could not be determined.'''
257 size = None
Guido van Rossumd2560b01996-05-28 23:41:25 +0000258 if self.passiveserver:
259 host, port = parse227(self.sendcmd('PASV'))
260 conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
261 conn.connect(host, port)
262 resp = self.sendcmd(cmd)
263 if resp[0] <> '1':
264 raise error_reply, resp
265 else:
266 sock = self.makeport()
267 resp = self.sendcmd(cmd)
268 if resp[0] <> '1':
269 raise error_reply, resp
270 conn, sockaddr = sock.accept()
Fred Drake4de02d91997-01-10 18:26:09 +0000271 if resp[:3] == '150':
272 # this is conditional in case we received a 125
273 size = parse150(resp)
274 return conn, size
275
276 def transfercmd(self, cmd):
277 '''Initiate a transfer over the data connection. Returns
278 the socket for the connection. See also ntransfercmd().'''
279 return self.ntransfercmd(cmd)[0]
Guido van Rossumc567c601992-11-05 22:22:37 +0000280
Guido van Rossumb6775db1994-08-01 11:34:53 +0000281 def login(self, user = '', passwd = '', acct = ''):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000282 '''Login, default anonymous.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000283 if not user: user = 'anonymous'
284 if user == 'anonymous' and passwd in ('', '-'):
285 thishost = socket.gethostname()
Jack Jansen2db6bfc1995-05-04 15:02:18 +0000286 # Make sure it is fully qualified
287 if not '.' in thishost:
288 thisaddr = socket.gethostbyname(thishost)
Guido van Rossum303c1791995-06-20 17:21:42 +0000289 firstname, names, unused = \
290 socket.gethostbyaddr(thisaddr)
291 names.insert(0, firstname)
292 for name in names:
293 if '.' in name:
294 thishost = name
295 break
Jack Jansen40b98351995-01-19 12:24:45 +0000296 try:
297 if os.environ.has_key('LOGNAME'):
298 realuser = os.environ['LOGNAME']
299 elif os.environ.has_key('USER'):
300 realuser = os.environ['USER']
301 else:
302 realuser = 'anonymous'
303 except AttributeError:
304 # Not all systems have os.environ....
Guido van Rossumc567c601992-11-05 22:22:37 +0000305 realuser = 'anonymous'
306 passwd = passwd + realuser + '@' + thishost
307 resp = self.sendcmd('USER ' + user)
308 if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
309 if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
310 if resp[0] <> '2':
311 raise error_reply, resp
312
Guido van Rossumc567c601992-11-05 22:22:37 +0000313 def retrbinary(self, cmd, callback, blocksize):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000314 '''Retrieve data in binary mode.
315 The argument is a RETR command.
316 The callback function is called for each block.
317 This creates a new port for you'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000318 self.voidcmd('TYPE I')
319 conn = self.transfercmd(cmd)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000320 while 1:
321 data = conn.recv(blocksize)
322 if not data:
323 break
324 callback(data)
325 conn.close()
Guido van Rossumc567c601992-11-05 22:22:37 +0000326 self.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000327
Guido van Rossumb6775db1994-08-01 11:34:53 +0000328 def retrlines(self, cmd, callback = None):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000329 '''Retrieve data in line mode.
330 The argument is a RETR or LIST command.
331 The callback function (2nd argument) is called for each line,
332 with trailing CRLF stripped. This creates a new port for you.
333 print_lines is the default callback.'''
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000334 if not callback: callback = print_line
Guido van Rossumc567c601992-11-05 22:22:37 +0000335 resp = self.sendcmd('TYPE A')
336 conn = self.transfercmd(cmd)
Guido van Rossum24611f81996-09-30 22:02:50 +0000337 fp = conn.makefile('rb')
Guido van Rossum1115ab21992-11-04 15:51:30 +0000338 while 1:
339 line = fp.readline()
Guido van Rossumc0e68d11995-09-30 16:51:50 +0000340 if self.debugging > 2: print '*retr*', `line`
Guido van Rossum1115ab21992-11-04 15:51:30 +0000341 if not line:
342 break
343 if line[-2:] == CRLF:
344 line = line[:-2]
345 elif line[:-1] == '\n':
346 line = line[:-1]
347 callback(line)
348 fp.close()
349 conn.close()
Guido van Rossumc567c601992-11-05 22:22:37 +0000350 self.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000351
Guido van Rossumc567c601992-11-05 22:22:37 +0000352 def storbinary(self, cmd, fp, blocksize):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000353 '''Store a file in binary mode.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000354 self.voidcmd('TYPE I')
355 conn = self.transfercmd(cmd)
356 while 1:
357 buf = fp.read(blocksize)
358 if not buf: break
359 conn.send(buf)
360 conn.close()
361 self.voidresp()
362
Guido van Rossumc567c601992-11-05 22:22:37 +0000363 def storlines(self, cmd, fp):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000364 '''Store a file in line mode.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000365 self.voidcmd('TYPE A')
366 conn = self.transfercmd(cmd)
367 while 1:
368 buf = fp.readline()
369 if not buf: break
370 if buf[-2:] <> CRLF:
371 if buf[-1] in CRLF: buf = buf[:-1]
372 buf = buf + CRLF
373 conn.send(buf)
374 conn.close()
375 self.voidresp()
376
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000377 def acct(self, password):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000378 '''Send new account name.'''
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000379 cmd = 'ACCT ' + password
380 self.voidcmd(cmd)
381
Guido van Rossumc567c601992-11-05 22:22:37 +0000382 def nlst(self, *args):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000383 '''Return a list of files in a given directory (default the current).'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000384 cmd = 'NLST'
385 for arg in args:
386 cmd = cmd + (' ' + arg)
387 files = []
388 self.retrlines(cmd, files.append)
389 return files
390
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000391 def dir(self, *args):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000392 '''List a directory in long form.
393 By default list current directory to stdout.
394 Optional last argument is callback function; all
395 non-empty arguments before it are concatenated to the
396 LIST command. (This *should* only be used for a pathname.)'''
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000397 cmd = 'LIST'
398 func = None
399 if args[-1:] and type(args[-1]) != type(''):
400 args, func = args[:-1], args[-1]
401 for arg in args:
402 if arg:
403 cmd = cmd + (' ' + arg)
404 self.retrlines(cmd, func)
405
Guido van Rossumc567c601992-11-05 22:22:37 +0000406 def rename(self, fromname, toname):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000407 '''Rename a file.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000408 resp = self.sendcmd('RNFR ' + fromname)
409 if resp[0] <> '3':
Guido van Rossum1115ab21992-11-04 15:51:30 +0000410 raise error_reply, resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000411 self.voidcmd('RNTO ' + toname)
412
Guido van Rossuma61bdeb1995-10-11 17:36:31 +0000413 def delete(self, filename):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000414 '''Delete a file.'''
Guido van Rossuma61bdeb1995-10-11 17:36:31 +0000415 resp = self.sendcmd('DELE ' + filename)
416 if resp[:3] == '250':
417 return
418 elif resp[:1] == '5':
419 raise error_perm, resp
420 else:
421 raise error_reply, resp
422
Guido van Rossum02cf5821993-05-17 08:00:02 +0000423 def cwd(self, dirname):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000424 '''Change to a directory.'''
Guido van Rossumdf563861993-07-06 15:19:36 +0000425 if dirname == '..':
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000426 try:
427 self.voidcmd('CDUP')
428 return
429 except error_perm, msg:
430 if msg[:3] != '500':
431 raise error_perm, msg
432 cmd = 'CWD ' + dirname
Guido van Rossumdf563861993-07-06 15:19:36 +0000433 self.voidcmd(cmd)
Guido van Rossum02cf5821993-05-17 08:00:02 +0000434
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000435 def size(self, filename):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000436 '''Retrieve the size of a file.'''
437 # Note that the RFC doesn't say anything about 'SIZE'
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000438 resp = self.sendcmd('SIZE ' + filename)
439 if resp[:3] == '213':
440 return string.atoi(string.strip(resp[3:]))
441
Guido van Rossumc567c601992-11-05 22:22:37 +0000442 def mkd(self, dirname):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000443 '''Make a directory, return its full pathname.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000444 resp = self.sendcmd('MKD ' + dirname)
445 return parse257(resp)
446
Guido van Rossumc567c601992-11-05 22:22:37 +0000447 def pwd(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000448 '''Return current working directory.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000449 resp = self.sendcmd('PWD')
450 return parse257(resp)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000451
Guido van Rossum1115ab21992-11-04 15:51:30 +0000452 def quit(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000453 '''Quit, and close the connection.'''
Guido van Rossumc567c601992-11-05 22:22:37 +0000454 self.voidcmd('QUIT')
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000455 self.close()
456
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000457 def close(self):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000458 '''Close the connection without assuming anything about it.'''
Guido van Rossum1115ab21992-11-04 15:51:30 +0000459 self.file.close()
460 self.sock.close()
Guido van Rossumc567c601992-11-05 22:22:37 +0000461 del self.file, self.sock
462
463
Fred Drake4de02d91997-01-10 18:26:09 +0000464import regex
465_150_re = regex.compile("150 .* (\([0-9][0-9]*\) bytes)", regex.casefold)
466
467def parse150(resp):
468 '''Parse the '150' response for a RETR request.
469 Returns the expected transfer size or None; size is not guaranteed to
470 be present in the 150 message.
471 '''
472 if resp[:3] != '150':
473 raise error_reply, resp
474 length = _150_re.match(resp)
475 if length >= 0:
476 return string.atoi(_150_re.group(1))
477 return None
478
479
Guido van Rossumd2560b01996-05-28 23:41:25 +0000480def parse227(resp):
481 '''Parse the '227' response for a PASV request.
482 Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
483 Return ('host.addr.as.numbers', port#) tuple.'''
484
485 if resp[:3] <> '227':
486 raise error_reply, resp
487 left = string.find(resp, '(')
488 if left < 0: raise error_proto, resp
489 right = string.find(resp, ')', left + 1)
490 if right < 0:
491 raise error_proto, resp # should contain '(h1,h2,h3,h4,p1,p2)'
492 numbers = string.split(resp[left+1:right], ',')
493 if len(numbers) <> 6:
494 raise error_proto, resp
495 host = string.join(numbers[:4], '.')
496 port = (string.atoi(numbers[4]) << 8) + string.atoi(numbers[5])
497 return host, port
498# end parse227
499
500
Guido van Rossumc567c601992-11-05 22:22:37 +0000501def parse257(resp):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000502 '''Parse the '257' response for a MKD or RMD request.
503 This is a response to a MKD or RMD request: a directory name.
504 Returns the directoryname in the 257 reply.'''
505
Guido van Rossumc567c601992-11-05 22:22:37 +0000506 if resp[:3] <> '257':
507 raise error_reply, resp
508 if resp[3:5] <> ' "':
509 return '' # Not compliant to RFC 959, but UNIX ftpd does this
510 dirname = ''
511 i = 5
512 n = len(resp)
513 while i < n:
514 c = resp[i]
515 i = i+1
516 if c == '"':
517 if i >= n or resp[i] <> '"':
518 break
519 i = i+1
520 dirname = dirname + c
521 return dirname
Guido van Rossum1115ab21992-11-04 15:51:30 +0000522
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000523def print_line(line):
Guido van Rossumd2560b01996-05-28 23:41:25 +0000524 '''Default retrlines callback to print a line.'''
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000525 print line
526
Guido van Rossumd2560b01996-05-28 23:41:25 +0000527def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
528 '''Copy file from one FTP-instance to another.'''
529 if not targetname: targetname = sourcename
530 type = 'TYPE ' + type
531 source.voidcmd(type)
532 target.voidcmd(type)
533 sourcehost, sourceport = parse227(source.sendcmd('PASV'))
534 target.sendport(sourcehost, sourceport)
535 # RFC 959: the user must "listen" [...] BEFORE sending the
536 # transfer request.
537 # So: STOR before RETR, because here the target is a "user".
538 treply = target.sendcmd('STOR ' + targetname)
539 if treply[:3] not in ('125', '150'): raise error_proto # RFC 959
540 sreply = source.sendcmd('RETR ' + sourcename)
541 if sreply[:3] not in ('125', '150'): raise error_proto # RFC 959
542 source.voidresp()
543 target.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000544
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000545
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000546class Netrc:
Fred Drake475d51d1997-06-24 22:02:54 +0000547 """Class to parse & provide access to 'netrc' format files.
548
549 See the netrc(4) man page for information on the file format.
550
551 """
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000552 __defuser = None
553 __defpasswd = None
554 __defacct = None
555
556 def __init__(self, filename=None):
557 if not filename:
558 if os.environ.has_key("HOME"):
559 filename = os.path.join(os.environ["HOME"], ".netrc")
560 else:
Fred Drake475d51d1997-06-24 22:02:54 +0000561 raise IOError, "specify file to load or set $HOME"
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000562 self.__hosts = {}
563 self.__macros = {}
564 fp = open(filename, "r")
565 in_macro = 0
566 while 1:
567 line = fp.readline()
568 if not line: break
569 if in_macro and string.strip(line):
570 macro_lines.append(line)
571 continue
572 elif in_macro:
573 self.__macros[macro_name] = tuple(macro_lines)
574 in_macro = 0
575 words = string.split(line)
576 host = user = passwd = acct = None
577 default = 0
578 i = 0
579 while i < len(words):
580 w1 = words[i]
581 if i+1 < len(words):
582 w2 = words[i + 1]
583 else:
584 w2 = None
585 if w1 == 'default':
586 default = 1
587 elif w1 == 'machine' and w2:
588 host = string.lower(w2)
589 i = i + 1
590 elif w1 == 'login' and w2:
591 user = w2
592 i = i + 1
593 elif w1 == 'password' and w2:
594 passwd = w2
595 i = i + 1
596 elif w1 == 'account' and w2:
597 acct = w2
598 i = i + 1
599 elif w1 == 'macdef' and w2:
600 macro_name = w2
601 macro_lines = []
602 in_macro = 1
603 break
604 i = i + 1
605 if default:
606 self.__defuser = user or self.__defuser
607 self.__defpasswd = passwd or self.__defpasswd
608 self.__defacct = acct or self.__defacct
609 if host:
610 if self.__hosts.has_key(host):
611 ouser, opasswd, oacct = self.__hosts[host]
612 user = user or ouser
613 passwd = passwd or opasswd
614 acct = acct or oacct
615 self.__hosts[host] = user, passwd, acct
616 fp.close()
617
618 def get_hosts(self):
619 """Return a list of hosts mentioned in the .netrc file."""
620 return self.__hosts.keys()
621
622 def get_account(self, host):
623 """Returns login information for the named host.
624
625 The return value is a triple containing userid, password, and the
626 accounting field.
627
628 """
629 host = string.lower(host)
Fred Drake475d51d1997-06-24 22:02:54 +0000630 user = passwd = acct = None
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000631 if self.__hosts.has_key(host):
632 user, passwd, acct = self.__hosts[host]
633 user = user or self.__defuser
634 passwd = passwd or self.__defpasswd
635 acct = acct or self.__defacct
636 return user, passwd, acct
637
638 def get_macros(self):
639 """Return a list of all defined macro names."""
640 return self.__macros.keys()
641
642 def get_macro(self, macro):
643 """Return a sequence of lines which define a named macro."""
644 return self.__macros[macro]
645
Fred Drake475d51d1997-06-24 22:02:54 +0000646
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000647
Guido van Rossum1115ab21992-11-04 15:51:30 +0000648def test():
Guido van Rossumd2560b01996-05-28 23:41:25 +0000649 '''Test program.
Fred Drake475d51d1997-06-24 22:02:54 +0000650 Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...'''
Guido van Rossumd2560b01996-05-28 23:41:25 +0000651
Guido van Rossumb6775db1994-08-01 11:34:53 +0000652 debugging = 0
Fred Drake475d51d1997-06-24 22:02:54 +0000653 rcfile = None
Guido van Rossumb6775db1994-08-01 11:34:53 +0000654 while sys.argv[1] == '-d':
655 debugging = debugging+1
656 del sys.argv[1]
Fred Drake475d51d1997-06-24 22:02:54 +0000657 if sys.argv[1][:2] == '-r':
658 # get name of alternate ~/.netrc file:
659 rcfile = sys.argv[1][2:]
660 del sys.argv[1]
Guido van Rossumb6775db1994-08-01 11:34:53 +0000661 host = sys.argv[1]
662 ftp = FTP(host)
663 ftp.set_debuglevel(debugging)
Fred Drake475d51d1997-06-24 22:02:54 +0000664 userid = passwd = acct = ''
665 try:
666 netrc = Netrc(rcfile)
667 except IOError:
668 if rcfile is not None:
669 sys.stderr.write("Could not open account file"
670 " -- using anonymous login.")
671 else:
672 try:
673 userid, passwd, acct = netrc.get_account(host)
674 except KeyError:
675 # no account for host
676 sys.stderr.write("No account -- using anonymous login.")
677 ftp.login(userid, passwd, acct)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000678 for file in sys.argv[2:]:
679 if file[:2] == '-l':
680 ftp.dir(file[2:])
681 elif file[:2] == '-d':
682 cmd = 'CWD'
683 if file[2:]: cmd = cmd + ' ' + file[2:]
684 resp = ftp.sendcmd(cmd)
Guido van Rossumd2560b01996-05-28 23:41:25 +0000685 elif file == '-p':
686 ftp.set_pasv(not ftp.passiveserver)
Guido van Rossumb6775db1994-08-01 11:34:53 +0000687 else:
688 ftp.retrbinary('RETR ' + file, \
689 sys.stdout.write, 1024)
690 ftp.quit()
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000691
692
693if __name__ == '__main__':
694 test()