blob: 3263281bc305e5a7dd162b62f106cc20ce6ad64b [file] [log] [blame]
Guido van Rossum4b8c6ea2000-02-04 15:39:30 +00001"""An FTP client class and some helper functions.
2
Barry Warsaw100d81e2000-09-01 06:09:23 +00003Based on RFC 959: File Transfer Protocol (FTP), by J. Postel and J. Reynolds
Guido van Rossum1115ab21992-11-04 15:51:30 +00004
Guido van Rossumd2560b01996-05-28 23:41:25 +00005Example:
6
7>>> from ftplib import FTP
8>>> ftp = FTP('ftp.python.org') # connect to host, default port
9>>> ftp.login() # default, i.e.: user anonymous, passwd user@hostname
Guido van Rossum2f3941d1997-10-07 14:49:56 +000010'230 Guest login ok, access restrictions apply.'
Guido van Rossumd2560b01996-05-28 23:41:25 +000011>>> ftp.retrlines('LIST') # list directory contents
12total 9
13drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .
14drwxr-xr-x 8 root wheel 1024 Jan 3 1994 ..
15drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin
16drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc
17d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming
18drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib
19drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub
20drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr
21-rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg
Guido van Rossum2f3941d1997-10-07 14:49:56 +000022'226 Transfer complete.'
Guido van Rossumd2560b01996-05-28 23:41:25 +000023>>> ftp.quit()
Guido van Rossum2f3941d1997-10-07 14:49:56 +000024'221 Goodbye.'
Tim Peters88869f92001-01-14 23:36:06 +000025>>>
Guido van Rossumd2560b01996-05-28 23:41:25 +000026
27A nice test that reveals some of the network dialogue would be:
28python ftplib.py -d localhost -l -p -l
Guido van Rossum4b8c6ea2000-02-04 15:39:30 +000029"""
Guido van Rossumc567c601992-11-05 22:22:37 +000030
Tim Peters88869f92001-01-14 23:36:06 +000031#
Guido van Rossum98d9fd32000-02-28 15:12:25 +000032# Changes and improvements suggested by Steve Majewski.
33# Modified by Jack to work on the mac.
34# Modified by Siebren to support docstrings and PASV.
35#
Guido van Rossumc567c601992-11-05 22:22:37 +000036
Guido van Rossum1115ab21992-11-04 15:51:30 +000037import os
38import sys
Guido van Rossum1115ab21992-11-04 15:51:30 +000039import string
40
Guido van Rossumb6775db1994-08-01 11:34:53 +000041# Import SOCKS module if it exists, else standard socket module socket
42try:
Tim Peters88869f92001-01-14 23:36:06 +000043 import SOCKS; socket = SOCKS; del SOCKS # import SOCKS as socket
44 from socket import getfqdn; socket.getfqdn = getfqdn; del getfqdn
Guido van Rossumb6775db1994-08-01 11:34:53 +000045except ImportError:
Tim Peters88869f92001-01-14 23:36:06 +000046 import socket
Guido van Rossumb6775db1994-08-01 11:34:53 +000047
Skip Montanaroeccd02a2001-01-20 23:34:12 +000048__all__ = ["FTP","Netrc"]
Guido van Rossum1115ab21992-11-04 15:51:30 +000049
Guido van Rossumd3166071993-05-24 14:16:22 +000050# Magic number from <socket.h>
Tim Peters88869f92001-01-14 23:36:06 +000051MSG_OOB = 0x1 # Process data out of band
Guido van Rossumd3166071993-05-24 14:16:22 +000052
53
Guido van Rossumc567c601992-11-05 22:22:37 +000054# The standard FTP server control port
Guido van Rossum1115ab21992-11-04 15:51:30 +000055FTP_PORT = 21
Guido van Rossum1115ab21992-11-04 15:51:30 +000056
57
Guido van Rossum21974791992-11-06 13:34:17 +000058# Exception raised when an error or invalid response is received
Fred Drake227b1202000-08-17 05:06:49 +000059class Error(Exception): pass
Tim Peters88869f92001-01-14 23:36:06 +000060class error_reply(Error): pass # unexpected [123]xx reply
61class error_temp(Error): pass # 4xx errors
62class error_perm(Error): pass # 5xx errors
63class error_proto(Error): pass # response does not begin with [1-5]
Guido van Rossum1115ab21992-11-04 15:51:30 +000064
65
Guido van Rossum21974791992-11-06 13:34:17 +000066# All exceptions (hopefully) that may be raised here and that aren't
67# (always) programming errors on our side
Fred Drake227b1202000-08-17 05:06:49 +000068all_errors = (Error, socket.error, IOError, EOFError)
Guido van Rossum21974791992-11-06 13:34:17 +000069
70
Guido van Rossum1115ab21992-11-04 15:51:30 +000071# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
72CRLF = '\r\n'
73
74
Guido van Rossum1115ab21992-11-04 15:51:30 +000075# The class itself
76class FTP:
77
Tim Peters88869f92001-01-14 23:36:06 +000078 '''An FTP client class.
Guido van Rossumd2560b01996-05-28 23:41:25 +000079
Tim Peters88869f92001-01-14 23:36:06 +000080 To create a connection, call the class using these argument:
81 host, user, passwd, acct
82 These are all strings, and have default value ''.
83 Then use self.connect() with optional host and port argument.
Guido van Rossumd2560b01996-05-28 23:41:25 +000084
Tim Peters88869f92001-01-14 23:36:06 +000085 To download a file, use ftp.retrlines('RETR ' + filename),
86 or ftp.retrbinary() with slightly different arguments.
87 To upload a file, use ftp.storlines() or ftp.storbinary(),
88 which have an open file as argument (see their definitions
89 below for details).
90 The download/upload functions first issue appropriate TYPE
91 and PORT or PASV commands.
Guido van Rossumd2560b01996-05-28 23:41:25 +000092'''
93
Fred Drake9c98a422001-02-28 21:46:37 +000094 debugging = 0
95 host = ''
96 port = FTP_PORT
97 sock = None
98 file = None
99 welcome = None
100 passiveserver = 1
101
Tim Peters88869f92001-01-14 23:36:06 +0000102 # Initialization method (called by class instantiation).
103 # Initialize host to localhost, port to standard ftp port
104 # Optional arguments are host (for connect()),
105 # and user, passwd, acct (for login())
Fred Drake9c98a422001-02-28 21:46:37 +0000106 def __init__(self, host='', user='', passwd='', acct=''):
Tim Peters88869f92001-01-14 23:36:06 +0000107 if host:
Fred Drake9c98a422001-02-28 21:46:37 +0000108 self.connect(host)
109 if user: self.login(user, passwd, acct)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000110
Fred Drake9c98a422001-02-28 21:46:37 +0000111 def connect(self, host='', port=0):
Tim Peters88869f92001-01-14 23:36:06 +0000112 '''Connect to host. Arguments are:
113 - host: hostname to connect to (string, default previous host)
114 - port: port to connect to (integer, default previous port)'''
115 if host: self.host = host
116 if port: self.port = port
Tim Peters88869f92001-01-14 23:36:06 +0000117 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
118 self.sock.connect((self.host, self.port))
119 self.file = self.sock.makefile('rb')
120 self.welcome = self.getresp()
121 return self.welcome
Guido van Rossum1115ab21992-11-04 15:51:30 +0000122
Tim Peters88869f92001-01-14 23:36:06 +0000123 def getwelcome(self):
124 '''Get the welcome message from the server.
125 (this is read and squirreled away by connect())'''
126 if self.debugging:
127 print '*welcome*', self.sanitize(self.welcome)
128 return self.welcome
Guido van Rossum1115ab21992-11-04 15:51:30 +0000129
Tim Peters88869f92001-01-14 23:36:06 +0000130 def set_debuglevel(self, level):
131 '''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'''
136 self.debugging = level
137 debug = set_debuglevel
Guido van Rossum1115ab21992-11-04 15:51:30 +0000138
Tim Peters88869f92001-01-14 23:36:06 +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
Guido van Rossumd2560b01996-05-28 23:41:25 +0000144
Tim Peters88869f92001-01-14 23:36:06 +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`
Guido van Rossumebaf1041995-05-05 15:54:14 +0000153
Tim Peters88869f92001-01-14 23:36:06 +0000154 # Internal: send one line to the server, appending CRLF
155 def putline(self, line):
156 line = line + CRLF
157 if self.debugging > 1: print '*put*', self.sanitize(line)
158 self.sock.send(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000159
Tim Peters88869f92001-01-14 23:36:06 +0000160 # Internal: send one command to the server (through putline())
161 def putcmd(self, line):
162 if self.debugging: print '*cmd*', self.sanitize(line)
163 self.putline(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000164
Tim Peters88869f92001-01-14 23:36:06 +0000165 # 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:
170 print '*get*', self.sanitize(line)
171 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
Guido van Rossum1115ab21992-11-04 15:51:30 +0000175
Tim Peters88869f92001-01-14 23:36:06 +0000176 # 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
Guido van Rossum1115ab21992-11-04 15:51:30 +0000191
Tim Peters88869f92001-01-14 23:36:06 +0000192 # 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()
196 if self.debugging: print '*resp*', self.sanitize(resp)
197 self.lastresp = resp[:3]
198 c = resp[:1]
199 if c == '4':
200 raise error_temp, resp
201 if c == '5':
202 raise error_perm, resp
203 if c not in '123':
204 raise error_proto, resp
205 return resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000206
Tim Peters88869f92001-01-14 23:36:06 +0000207 def voidresp(self):
208 """Expect a response beginning with '2'."""
209 resp = self.getresp()
210 if resp[0] != '2':
211 raise error_reply, resp
212 return resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000213
Tim Peters88869f92001-01-14 23:36:06 +0000214 def abort(self):
215 '''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.'''
219 line = 'ABOR' + CRLF
220 if self.debugging > 1: print '*put urgent*', self.sanitize(line)
221 self.sock.send(line, MSG_OOB)
222 resp = self.getmultiline()
223 if resp[:3] not in ('426', '226'):
224 raise error_proto, resp
Guido van Rossumd3166071993-05-24 14:16:22 +0000225
Tim Peters88869f92001-01-14 23:36:06 +0000226 def sendcmd(self, cmd):
227 '''Send a command and return the response.'''
228 self.putcmd(cmd)
229 return self.getresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000230
Tim Peters88869f92001-01-14 23:36:06 +0000231 def voidcmd(self, cmd):
232 """Send a command and expect a response beginning with '2'."""
233 self.putcmd(cmd)
234 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000235
Tim Peters88869f92001-01-14 23:36:06 +0000236 def sendport(self, host, port):
237 '''Send a PORT command with the current host and the given
238 port number.
239 '''
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000240 hbytes = host.split('.')
Tim Peters88869f92001-01-14 23:36:06 +0000241 pbytes = [`port/256`, `port%256`]
242 bytes = hbytes + pbytes
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000243 cmd = 'PORT ' + ','.join(bytes)
Tim Peters88869f92001-01-14 23:36:06 +0000244 return self.voidcmd(cmd)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000245
Tim Peters88869f92001-01-14 23:36:06 +0000246 def makeport(self):
247 '''Create a new socket and send a PORT command for it.'''
Tim Peters88869f92001-01-14 23:36:06 +0000248 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
249 sock.bind(('', 0))
250 sock.listen(1)
251 dummyhost, port = sock.getsockname() # Get proper port
252 host, dummyport = self.sock.getsockname() # Get proper host
253 resp = self.sendport(host, port)
254 return sock
Guido van Rossum1115ab21992-11-04 15:51:30 +0000255
Tim Peters88869f92001-01-14 23:36:06 +0000256 def ntransfercmd(self, cmd, rest=None):
257 """Initiate a transfer over the data connection.
Barry Warsaw100d81e2000-09-01 06:09:23 +0000258
Tim Peters88869f92001-01-14 23:36:06 +0000259 If the transfer is active, send a port command and the
260 transfer command, and accept the connection. If the server is
261 passive, send a pasv command, connect to it, and start the
262 transfer command. Either way, return the socket for the
263 connection and the expected size of the transfer. The
264 expected size may be None if it could not be determined.
Barry Warsaw100d81e2000-09-01 06:09:23 +0000265
Tim Peters88869f92001-01-14 23:36:06 +0000266 Optional `rest' argument can be a string that is sent as the
267 argument to a RESTART command. This is essentially a server
268 marker used to tell the server to skip over any data up to the
269 given marker.
270 """
271 size = None
272 if self.passiveserver:
273 host, port = parse227(self.sendcmd('PASV'))
274 conn=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
275 conn.connect((host, port))
276 if rest is not None:
277 self.sendcmd("REST %s" % rest)
278 resp = self.sendcmd(cmd)
279 if resp[0] != '1':
280 raise error_reply, resp
281 else:
282 sock = self.makeport()
283 if rest is not None:
284 self.sendcmd("REST %s" % rest)
285 resp = self.sendcmd(cmd)
286 if resp[0] != '1':
287 raise error_reply, resp
288 conn, sockaddr = sock.accept()
289 if resp[:3] == '150':
290 # this is conditional in case we received a 125
291 size = parse150(resp)
292 return conn, size
Fred Drake4de02d91997-01-10 18:26:09 +0000293
Tim Peters88869f92001-01-14 23:36:06 +0000294 def transfercmd(self, cmd, rest=None):
295 """Like nstransfercmd() but returns only the socket."""
296 return self.ntransfercmd(cmd, rest)[0]
Guido van Rossumc567c601992-11-05 22:22:37 +0000297
Tim Peters88869f92001-01-14 23:36:06 +0000298 def login(self, user = '', passwd = '', acct = ''):
299 '''Login, default anonymous.'''
300 if not user: user = 'anonymous'
301 if not passwd: passwd = ''
302 if not acct: acct = ''
303 if user == 'anonymous' and passwd in ('', '-'):
304 # get fully qualified domain name of local host
305 thishost = socket.getfqdn()
306 try:
307 if os.environ.has_key('LOGNAME'):
308 realuser = os.environ['LOGNAME']
309 elif os.environ.has_key('USER'):
310 realuser = os.environ['USER']
311 else:
312 realuser = 'anonymous'
313 except AttributeError:
314 # Not all systems have os.environ....
315 realuser = 'anonymous'
316 passwd = passwd + realuser + '@' + thishost
317 resp = self.sendcmd('USER ' + user)
318 if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
319 if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
320 if resp[0] != '2':
321 raise error_reply, resp
322 return resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000323
Tim Peters88869f92001-01-14 23:36:06 +0000324 def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
325 """Retrieve data in binary mode.
Barry Warsaw100d81e2000-09-01 06:09:23 +0000326
Tim Peters88869f92001-01-14 23:36:06 +0000327 `cmd' is a RETR command. `callback' is a callback function is
328 called for each block. No more than `blocksize' number of
329 bytes will be read from the socket. Optional `rest' is passed
330 to transfercmd().
Guido van Rossum1115ab21992-11-04 15:51:30 +0000331
Tim Peters88869f92001-01-14 23:36:06 +0000332 A new port is created for you. Return the response code.
333 """
334 self.voidcmd('TYPE I')
335 conn = self.transfercmd(cmd, rest)
336 while 1:
337 data = conn.recv(blocksize)
338 if not data:
339 break
340 callback(data)
341 conn.close()
342 return self.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000343
Tim Peters88869f92001-01-14 23:36:06 +0000344 def retrlines(self, cmd, callback = None):
345 '''Retrieve data in line mode.
346 The argument is a RETR or LIST command.
347 The callback function (2nd argument) is called for each line,
348 with trailing CRLF stripped. This creates a new port for you.
349 print_line() is the default callback.'''
350 if not callback: callback = print_line
351 resp = self.sendcmd('TYPE A')
352 conn = self.transfercmd(cmd)
353 fp = conn.makefile('rb')
354 while 1:
355 line = fp.readline()
356 if self.debugging > 2: print '*retr*', `line`
357 if not line:
358 break
359 if line[-2:] == CRLF:
360 line = line[:-2]
361 elif line[-1:] == '\n':
362 line = line[:-1]
363 callback(line)
364 fp.close()
365 conn.close()
366 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000367
Guido van Rossum4ac83472001-02-15 13:50:36 +0000368 def storbinary(self, cmd, fp, blocksize=8192):
Tim Peters88869f92001-01-14 23:36:06 +0000369 '''Store a file in binary mode.'''
370 self.voidcmd('TYPE I')
371 conn = self.transfercmd(cmd)
372 while 1:
373 buf = fp.read(blocksize)
374 if not buf: break
375 conn.send(buf)
376 conn.close()
377 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000378
Tim Peters88869f92001-01-14 23:36:06 +0000379 def storlines(self, cmd, fp):
380 '''Store a file in line mode.'''
381 self.voidcmd('TYPE A')
382 conn = self.transfercmd(cmd)
383 while 1:
384 buf = fp.readline()
385 if not buf: break
386 if buf[-2:] != CRLF:
387 if buf[-1] in CRLF: buf = buf[:-1]
388 buf = buf + CRLF
389 conn.send(buf)
390 conn.close()
391 return self.voidresp()
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000392
Tim Peters88869f92001-01-14 23:36:06 +0000393 def acct(self, password):
394 '''Send new account name.'''
395 cmd = 'ACCT ' + password
396 return self.voidcmd(cmd)
Guido van Rossumc567c601992-11-05 22:22:37 +0000397
Tim Peters88869f92001-01-14 23:36:06 +0000398 def nlst(self, *args):
399 '''Return a list of files in a given directory (default the current).'''
400 cmd = 'NLST'
401 for arg in args:
402 cmd = cmd + (' ' + arg)
403 files = []
404 self.retrlines(cmd, files.append)
405 return files
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000406
Tim Peters88869f92001-01-14 23:36:06 +0000407 def dir(self, *args):
408 '''List a directory in long form.
409 By default list current directory to stdout.
410 Optional last argument is callback function; all
411 non-empty arguments before it are concatenated to the
412 LIST command. (This *should* only be used for a pathname.)'''
413 cmd = 'LIST'
414 func = None
415 if args[-1:] and type(args[-1]) != type(''):
416 args, func = args[:-1], args[-1]
417 for arg in args:
418 if arg:
419 cmd = cmd + (' ' + arg)
420 self.retrlines(cmd, func)
Guido van Rossumc567c601992-11-05 22:22:37 +0000421
Tim Peters88869f92001-01-14 23:36:06 +0000422 def rename(self, fromname, toname):
423 '''Rename a file.'''
424 resp = self.sendcmd('RNFR ' + fromname)
425 if resp[0] != '3':
426 raise error_reply, resp
427 return self.voidcmd('RNTO ' + toname)
Guido van Rossuma61bdeb1995-10-11 17:36:31 +0000428
Tim Peters88869f92001-01-14 23:36:06 +0000429 def delete(self, filename):
430 '''Delete a file.'''
431 resp = self.sendcmd('DELE ' + filename)
432 if resp[:3] in ('250', '200'):
433 return resp
434 elif resp[:1] == '5':
435 raise error_perm, resp
436 else:
437 raise error_reply, resp
Guido van Rossum02cf5821993-05-17 08:00:02 +0000438
Tim Peters88869f92001-01-14 23:36:06 +0000439 def cwd(self, dirname):
440 '''Change to a directory.'''
441 if dirname == '..':
442 try:
443 return self.voidcmd('CDUP')
444 except error_perm, msg:
445 if msg[:3] != '500':
446 raise error_perm, msg
447 elif dirname == '':
448 dirname = '.' # does nothing, but could return error
449 cmd = 'CWD ' + dirname
450 return self.voidcmd(cmd)
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000451
Tim Peters88869f92001-01-14 23:36:06 +0000452 def size(self, filename):
453 '''Retrieve the size of a file.'''
454 # Note that the RFC doesn't say anything about 'SIZE'
455 resp = self.sendcmd('SIZE ' + filename)
456 if resp[:3] == '213':
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000457 return int(resp[3:].strip())
Guido van Rossumc567c601992-11-05 22:22:37 +0000458
Tim Peters88869f92001-01-14 23:36:06 +0000459 def mkd(self, dirname):
460 '''Make a directory, return its full pathname.'''
461 resp = self.sendcmd('MKD ' + dirname)
462 return parse257(resp)
Guido van Rossum98245091998-02-19 21:15:44 +0000463
Tim Peters88869f92001-01-14 23:36:06 +0000464 def rmd(self, dirname):
465 '''Remove a directory.'''
466 return self.voidcmd('RMD ' + dirname)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000467
Tim Peters88869f92001-01-14 23:36:06 +0000468 def pwd(self):
469 '''Return current working directory.'''
470 resp = self.sendcmd('PWD')
471 return parse257(resp)
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000472
Tim Peters88869f92001-01-14 23:36:06 +0000473 def quit(self):
474 '''Quit, and close the connection.'''
475 resp = self.voidcmd('QUIT')
476 self.close()
477 return resp
478
479 def close(self):
480 '''Close the connection without assuming anything about it.'''
Fred Drake9c98a422001-02-28 21:46:37 +0000481 if self.file:
482 self.file.close()
483 self.sock.close()
484 self.file = self.sock = None
Guido van Rossumc567c601992-11-05 22:22:37 +0000485
486
Guido van Rossumacfb82a1997-10-22 20:49:52 +0000487_150_re = None
Fred Drake4de02d91997-01-10 18:26:09 +0000488
489def parse150(resp):
Tim Peters88869f92001-01-14 23:36:06 +0000490 '''Parse the '150' response for a RETR request.
491 Returns the expected transfer size or None; size is not guaranteed to
492 be present in the 150 message.
493 '''
494 if resp[:3] != '150':
495 raise error_reply, resp
496 global _150_re
497 if _150_re is None:
498 import re
499 _150_re = re.compile("150 .* \((\d+) bytes\)", re.IGNORECASE)
500 m = _150_re.match(resp)
501 if m:
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000502 return int(m.group(1))
Tim Peters88869f92001-01-14 23:36:06 +0000503 return None
Fred Drake4de02d91997-01-10 18:26:09 +0000504
505
Guido van Rossumd2560b01996-05-28 23:41:25 +0000506def parse227(resp):
Tim Peters88869f92001-01-14 23:36:06 +0000507 '''Parse the '227' response for a PASV request.
508 Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
509 Return ('host.addr.as.numbers', port#) tuple.'''
Guido van Rossumd2560b01996-05-28 23:41:25 +0000510
Tim Peters88869f92001-01-14 23:36:06 +0000511 if resp[:3] != '227':
512 raise error_reply, resp
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000513 left = resp.find('(')
Tim Peters88869f92001-01-14 23:36:06 +0000514 if left < 0: raise error_proto, resp
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000515 right = resp.find(')', left + 1)
Tim Peters88869f92001-01-14 23:36:06 +0000516 if right < 0:
517 raise error_proto, resp # should contain '(h1,h2,h3,h4,p1,p2)'
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000518 numbers = resp[left+1:right].split(',')
Tim Peters88869f92001-01-14 23:36:06 +0000519 if len(numbers) != 6:
520 raise error_proto, resp
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000521 host = '.'.join(numbers[:4])
522 port = (int(numbers[4]) << 8) + int(numbers[5])
Tim Peters88869f92001-01-14 23:36:06 +0000523 return host, port
Guido van Rossumd2560b01996-05-28 23:41:25 +0000524
525
Guido van Rossumc567c601992-11-05 22:22:37 +0000526def parse257(resp):
Tim Peters88869f92001-01-14 23:36:06 +0000527 '''Parse the '257' response for a MKD or PWD request.
528 This is a response to a MKD or PWD request: a directory name.
529 Returns the directoryname in the 257 reply.'''
Guido van Rossumd2560b01996-05-28 23:41:25 +0000530
Tim Peters88869f92001-01-14 23:36:06 +0000531 if resp[:3] != '257':
532 raise error_reply, resp
533 if resp[3:5] != ' "':
534 return '' # Not compliant to RFC 959, but UNIX ftpd does this
535 dirname = ''
536 i = 5
537 n = len(resp)
538 while i < n:
539 c = resp[i]
540 i = i+1
541 if c == '"':
542 if i >= n or resp[i] != '"':
543 break
544 i = i+1
545 dirname = dirname + c
546 return dirname
Guido van Rossum1115ab21992-11-04 15:51:30 +0000547
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000548
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000549def print_line(line):
Tim Peters88869f92001-01-14 23:36:06 +0000550 '''Default retrlines callback to print a line.'''
551 print line
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000552
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000553
Guido van Rossumd2560b01996-05-28 23:41:25 +0000554def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
Tim Peters88869f92001-01-14 23:36:06 +0000555 '''Copy file from one FTP-instance to another.'''
556 if not targetname: targetname = sourcename
557 type = 'TYPE ' + type
558 source.voidcmd(type)
559 target.voidcmd(type)
560 sourcehost, sourceport = parse227(source.sendcmd('PASV'))
561 target.sendport(sourcehost, sourceport)
562 # RFC 959: the user must "listen" [...] BEFORE sending the
563 # transfer request.
564 # So: STOR before RETR, because here the target is a "user".
565 treply = target.sendcmd('STOR ' + targetname)
566 if treply[:3] not in ('125', '150'): raise error_proto # RFC 959
567 sreply = source.sendcmd('RETR ' + sourcename)
568 if sreply[:3] not in ('125', '150'): raise error_proto # RFC 959
569 source.voidresp()
570 target.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000571
Tim Peters88869f92001-01-14 23:36:06 +0000572
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000573class Netrc:
Tim Peters88869f92001-01-14 23:36:06 +0000574 """Class to parse & provide access to 'netrc' format files.
Fred Drake475d51d1997-06-24 22:02:54 +0000575
Tim Peters88869f92001-01-14 23:36:06 +0000576 See the netrc(4) man page for information on the file format.
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000577
Tim Peters88869f92001-01-14 23:36:06 +0000578 WARNING: This class is obsolete -- use module netrc instead.
Guido van Rossumc822a451998-12-22 16:49:16 +0000579
Tim Peters88869f92001-01-14 23:36:06 +0000580 """
581 __defuser = None
582 __defpasswd = None
583 __defacct = None
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000584
Tim Peters88869f92001-01-14 23:36:06 +0000585 def __init__(self, filename=None):
586 if not filename:
587 if os.environ.has_key("HOME"):
588 filename = os.path.join(os.environ["HOME"],
589 ".netrc")
590 else:
591 raise IOError, \
592 "specify file to load or set $HOME"
593 self.__hosts = {}
594 self.__macros = {}
595 fp = open(filename, "r")
596 in_macro = 0
597 while 1:
598 line = fp.readline()
599 if not line: break
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000600 if in_macro and line.strip():
Tim Peters88869f92001-01-14 23:36:06 +0000601 macro_lines.append(line)
602 continue
603 elif in_macro:
604 self.__macros[macro_name] = tuple(macro_lines)
605 in_macro = 0
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000606 words = line.split()
Tim Peters88869f92001-01-14 23:36:06 +0000607 host = user = passwd = acct = None
608 default = 0
609 i = 0
610 while i < len(words):
611 w1 = words[i]
612 if i+1 < len(words):
613 w2 = words[i + 1]
614 else:
615 w2 = None
616 if w1 == 'default':
617 default = 1
618 elif w1 == 'machine' and w2:
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000619 host = w2.lower()
Tim Peters88869f92001-01-14 23:36:06 +0000620 i = i + 1
621 elif w1 == 'login' and w2:
622 user = w2
623 i = i + 1
624 elif w1 == 'password' and w2:
625 passwd = w2
626 i = i + 1
627 elif w1 == 'account' and w2:
628 acct = w2
629 i = i + 1
630 elif w1 == 'macdef' and w2:
631 macro_name = w2
632 macro_lines = []
633 in_macro = 1
634 break
635 i = i + 1
636 if default:
637 self.__defuser = user or self.__defuser
638 self.__defpasswd = passwd or self.__defpasswd
639 self.__defacct = acct or self.__defacct
640 if host:
641 if self.__hosts.has_key(host):
642 ouser, opasswd, oacct = \
643 self.__hosts[host]
644 user = user or ouser
645 passwd = passwd or opasswd
646 acct = acct or oacct
647 self.__hosts[host] = user, passwd, acct
648 fp.close()
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000649
Tim Peters88869f92001-01-14 23:36:06 +0000650 def get_hosts(self):
651 """Return a list of hosts mentioned in the .netrc file."""
652 return self.__hosts.keys()
Guido van Rossum8ca84201998-03-26 20:56:10 +0000653
Tim Peters88869f92001-01-14 23:36:06 +0000654 def get_account(self, host):
655 """Returns login information for the named host.
Guido van Rossum8ca84201998-03-26 20:56:10 +0000656
Tim Peters88869f92001-01-14 23:36:06 +0000657 The return value is a triple containing userid,
658 password, and the accounting field.
Guido van Rossum8ca84201998-03-26 20:56:10 +0000659
Tim Peters88869f92001-01-14 23:36:06 +0000660 """
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000661 host = host.lower()
Tim Peters88869f92001-01-14 23:36:06 +0000662 user = passwd = acct = None
663 if self.__hosts.has_key(host):
664 user, passwd, acct = self.__hosts[host]
665 user = user or self.__defuser
666 passwd = passwd or self.__defpasswd
667 acct = acct or self.__defacct
668 return user, passwd, acct
Guido van Rossum8ca84201998-03-26 20:56:10 +0000669
Tim Peters88869f92001-01-14 23:36:06 +0000670 def get_macros(self):
671 """Return a list of all defined macro names."""
672 return self.__macros.keys()
Guido van Rossum8ca84201998-03-26 20:56:10 +0000673
Tim Peters88869f92001-01-14 23:36:06 +0000674 def get_macro(self, macro):
675 """Return a sequence of lines which define a named macro."""
676 return self.__macros[macro]
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000677
Fred Drake475d51d1997-06-24 22:02:54 +0000678
Tim Peters88869f92001-01-14 23:36:06 +0000679
Guido van Rossum1115ab21992-11-04 15:51:30 +0000680def test():
Tim Peters88869f92001-01-14 23:36:06 +0000681 '''Test program.
682 Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...'''
Guido van Rossumd2560b01996-05-28 23:41:25 +0000683
Tim Peters88869f92001-01-14 23:36:06 +0000684 debugging = 0
685 rcfile = None
686 while sys.argv[1] == '-d':
687 debugging = debugging+1
688 del sys.argv[1]
689 if sys.argv[1][:2] == '-r':
690 # get name of alternate ~/.netrc file:
691 rcfile = sys.argv[1][2:]
692 del sys.argv[1]
693 host = sys.argv[1]
694 ftp = FTP(host)
695 ftp.set_debuglevel(debugging)
696 userid = passwd = acct = ''
697 try:
698 netrc = Netrc(rcfile)
699 except IOError:
700 if rcfile is not None:
701 sys.stderr.write("Could not open account file"
702 " -- using anonymous login.")
703 else:
704 try:
705 userid, passwd, acct = netrc.get_account(host)
706 except KeyError:
707 # no account for host
708 sys.stderr.write(
709 "No account -- using anonymous login.")
710 ftp.login(userid, passwd, acct)
711 for file in sys.argv[2:]:
712 if file[:2] == '-l':
713 ftp.dir(file[2:])
714 elif file[:2] == '-d':
715 cmd = 'CWD'
716 if file[2:]: cmd = cmd + ' ' + file[2:]
717 resp = ftp.sendcmd(cmd)
718 elif file == '-p':
719 ftp.set_pasv(not ftp.passiveserver)
720 else:
721 ftp.retrbinary('RETR ' + file, \
722 sys.stdout.write, 1024)
723 ftp.quit()
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000724
725
726if __name__ == '__main__':
Tim Peters88869f92001-01-14 23:36:06 +0000727 test()