blob: 17c681d871d9c4f1c2178fc970c940478384c2c4 [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
Tim Peters88869f92001-01-14 23:36:06 +000094 # Initialization method (called by class instantiation).
95 # Initialize host to localhost, port to standard ftp port
96 # Optional arguments are host (for connect()),
97 # and user, passwd, acct (for login())
98 def __init__(self, host = '', user = '', passwd = '', acct = ''):
99 # Initialize the instance to something mostly harmless
100 self.debugging = 0
101 self.host = ''
102 self.port = FTP_PORT
103 self.sock = None
104 self.file = None
105 self.welcome = None
106 resp = None
107 if host:
108 resp = self.connect(host)
109 if user: resp = self.login(user, passwd, acct)
Guido van Rossum52fc1f61993-06-17 12:38:10 +0000110
Tim Peters88869f92001-01-14 23:36:06 +0000111 def connect(self, host = '', port = 0):
112 '''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
Guido van Rossume6ccf3a2001-01-15 16:32:49 +0000117 self.passiveserver = 1
Tim Peters88869f92001-01-14 23:36:06 +0000118 self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
119 self.sock.connect((self.host, self.port))
120 self.file = self.sock.makefile('rb')
121 self.welcome = self.getresp()
122 return self.welcome
Guido van Rossum1115ab21992-11-04 15:51:30 +0000123
Tim Peters88869f92001-01-14 23:36:06 +0000124 def getwelcome(self):
125 '''Get the welcome message from the server.
126 (this is read and squirreled away by connect())'''
127 if self.debugging:
128 print '*welcome*', self.sanitize(self.welcome)
129 return self.welcome
Guido van Rossum1115ab21992-11-04 15:51:30 +0000130
Tim Peters88869f92001-01-14 23:36:06 +0000131 def set_debuglevel(self, level):
132 '''Set the debugging level.
133 The required argument level means:
134 0: no debugging output (default)
135 1: print commands and responses but not body text etc.
136 2: also print raw lines read and sent before stripping CR/LF'''
137 self.debugging = level
138 debug = set_debuglevel
Guido van Rossum1115ab21992-11-04 15:51:30 +0000139
Tim Peters88869f92001-01-14 23:36:06 +0000140 def set_pasv(self, val):
141 '''Use passive or active mode for data transfers.
142 With a false argument, use the normal PORT mode,
143 With a true argument, use the PASV command.'''
144 self.passiveserver = val
Guido van Rossumd2560b01996-05-28 23:41:25 +0000145
Tim Peters88869f92001-01-14 23:36:06 +0000146 # Internal: "sanitize" a string for printing
147 def sanitize(self, s):
148 if s[:5] == 'pass ' or s[:5] == 'PASS ':
149 i = len(s)
150 while i > 5 and s[i-1] in '\r\n':
151 i = i-1
152 s = s[:5] + '*'*(i-5) + s[i:]
153 return `s`
Guido van Rossumebaf1041995-05-05 15:54:14 +0000154
Tim Peters88869f92001-01-14 23:36:06 +0000155 # Internal: send one line to the server, appending CRLF
156 def putline(self, line):
157 line = line + CRLF
158 if self.debugging > 1: print '*put*', self.sanitize(line)
159 self.sock.send(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000160
Tim Peters88869f92001-01-14 23:36:06 +0000161 # Internal: send one command to the server (through putline())
162 def putcmd(self, line):
163 if self.debugging: print '*cmd*', self.sanitize(line)
164 self.putline(line)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000165
Tim Peters88869f92001-01-14 23:36:06 +0000166 # Internal: return one line from the server, stripping CRLF.
167 # Raise EOFError if the connection is closed
168 def getline(self):
169 line = self.file.readline()
170 if self.debugging > 1:
171 print '*get*', self.sanitize(line)
172 if not line: raise EOFError
173 if line[-2:] == CRLF: line = line[:-2]
174 elif line[-1:] in CRLF: line = line[:-1]
175 return line
Guido van Rossum1115ab21992-11-04 15:51:30 +0000176
Tim Peters88869f92001-01-14 23:36:06 +0000177 # Internal: get a response from the server, which may possibly
178 # consist of multiple lines. Return a single string with no
179 # trailing CRLF. If the response consists of multiple lines,
180 # these are separated by '\n' characters in the string
181 def getmultiline(self):
182 line = self.getline()
183 if line[3:4] == '-':
184 code = line[:3]
185 while 1:
186 nextline = self.getline()
187 line = line + ('\n' + nextline)
188 if nextline[:3] == code and \
189 nextline[3:4] != '-':
190 break
191 return line
Guido van Rossum1115ab21992-11-04 15:51:30 +0000192
Tim Peters88869f92001-01-14 23:36:06 +0000193 # Internal: get a response from the server.
194 # Raise various errors if the response indicates an error
195 def getresp(self):
196 resp = self.getmultiline()
197 if self.debugging: print '*resp*', self.sanitize(resp)
198 self.lastresp = resp[:3]
199 c = resp[:1]
200 if c == '4':
201 raise error_temp, resp
202 if c == '5':
203 raise error_perm, resp
204 if c not in '123':
205 raise error_proto, resp
206 return resp
Guido van Rossum1115ab21992-11-04 15:51:30 +0000207
Tim Peters88869f92001-01-14 23:36:06 +0000208 def voidresp(self):
209 """Expect a response beginning with '2'."""
210 resp = self.getresp()
211 if resp[0] != '2':
212 raise error_reply, resp
213 return resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000214
Tim Peters88869f92001-01-14 23:36:06 +0000215 def abort(self):
216 '''Abort a file transfer. Uses out-of-band data.
217 This does not follow the procedure from the RFC to send Telnet
218 IP and Synch; that doesn't seem to work with the servers I've
219 tried. Instead, just send the ABOR command as OOB data.'''
220 line = 'ABOR' + CRLF
221 if self.debugging > 1: print '*put urgent*', self.sanitize(line)
222 self.sock.send(line, MSG_OOB)
223 resp = self.getmultiline()
224 if resp[:3] not in ('426', '226'):
225 raise error_proto, resp
Guido van Rossumd3166071993-05-24 14:16:22 +0000226
Tim Peters88869f92001-01-14 23:36:06 +0000227 def sendcmd(self, cmd):
228 '''Send a command and return the response.'''
229 self.putcmd(cmd)
230 return self.getresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000231
Tim Peters88869f92001-01-14 23:36:06 +0000232 def voidcmd(self, cmd):
233 """Send a command and expect a response beginning with '2'."""
234 self.putcmd(cmd)
235 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000236
Tim Peters88869f92001-01-14 23:36:06 +0000237 def sendport(self, host, port):
238 '''Send a PORT command with the current host and the given
239 port number.
240 '''
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000241 hbytes = host.split('.')
Tim Peters88869f92001-01-14 23:36:06 +0000242 pbytes = [`port/256`, `port%256`]
243 bytes = hbytes + pbytes
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000244 cmd = 'PORT ' + ','.join(bytes)
Tim Peters88869f92001-01-14 23:36:06 +0000245 return self.voidcmd(cmd)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000246
Tim Peters88869f92001-01-14 23:36:06 +0000247 def makeport(self):
248 '''Create a new socket and send a PORT command for it.'''
249 global nextport
250 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
251 sock.bind(('', 0))
252 sock.listen(1)
253 dummyhost, port = sock.getsockname() # Get proper port
254 host, dummyport = self.sock.getsockname() # Get proper host
255 resp = self.sendport(host, port)
256 return sock
Guido van Rossum1115ab21992-11-04 15:51:30 +0000257
Tim Peters88869f92001-01-14 23:36:06 +0000258 def ntransfercmd(self, cmd, rest=None):
259 """Initiate a transfer over the data connection.
Barry Warsaw100d81e2000-09-01 06:09:23 +0000260
Tim Peters88869f92001-01-14 23:36:06 +0000261 If the transfer is active, send a port command and the
262 transfer command, and accept the connection. If the server is
263 passive, send a pasv command, connect to it, and start the
264 transfer command. Either way, return the socket for the
265 connection and the expected size of the transfer. The
266 expected size may be None if it could not be determined.
Barry Warsaw100d81e2000-09-01 06:09:23 +0000267
Tim Peters88869f92001-01-14 23:36:06 +0000268 Optional `rest' argument can be a string that is sent as the
269 argument to a RESTART command. This is essentially a server
270 marker used to tell the server to skip over any data up to the
271 given marker.
272 """
273 size = None
274 if self.passiveserver:
275 host, port = parse227(self.sendcmd('PASV'))
276 conn=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
277 conn.connect((host, port))
278 if rest is not None:
279 self.sendcmd("REST %s" % rest)
280 resp = self.sendcmd(cmd)
281 if resp[0] != '1':
282 raise error_reply, resp
283 else:
284 sock = self.makeport()
285 if rest is not None:
286 self.sendcmd("REST %s" % rest)
287 resp = self.sendcmd(cmd)
288 if resp[0] != '1':
289 raise error_reply, resp
290 conn, sockaddr = sock.accept()
291 if resp[:3] == '150':
292 # this is conditional in case we received a 125
293 size = parse150(resp)
294 return conn, size
Fred Drake4de02d91997-01-10 18:26:09 +0000295
Tim Peters88869f92001-01-14 23:36:06 +0000296 def transfercmd(self, cmd, rest=None):
297 """Like nstransfercmd() but returns only the socket."""
298 return self.ntransfercmd(cmd, rest)[0]
Guido van Rossumc567c601992-11-05 22:22:37 +0000299
Tim Peters88869f92001-01-14 23:36:06 +0000300 def login(self, user = '', passwd = '', acct = ''):
301 '''Login, default anonymous.'''
302 if not user: user = 'anonymous'
303 if not passwd: passwd = ''
304 if not acct: acct = ''
305 if user == 'anonymous' and passwd in ('', '-'):
306 # get fully qualified domain name of local host
307 thishost = socket.getfqdn()
308 try:
309 if os.environ.has_key('LOGNAME'):
310 realuser = os.environ['LOGNAME']
311 elif os.environ.has_key('USER'):
312 realuser = os.environ['USER']
313 else:
314 realuser = 'anonymous'
315 except AttributeError:
316 # Not all systems have os.environ....
317 realuser = 'anonymous'
318 passwd = passwd + realuser + '@' + thishost
319 resp = self.sendcmd('USER ' + user)
320 if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
321 if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
322 if resp[0] != '2':
323 raise error_reply, resp
324 return resp
Guido van Rossumc567c601992-11-05 22:22:37 +0000325
Tim Peters88869f92001-01-14 23:36:06 +0000326 def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
327 """Retrieve data in binary mode.
Barry Warsaw100d81e2000-09-01 06:09:23 +0000328
Tim Peters88869f92001-01-14 23:36:06 +0000329 `cmd' is a RETR command. `callback' is a callback function is
330 called for each block. No more than `blocksize' number of
331 bytes will be read from the socket. Optional `rest' is passed
332 to transfercmd().
Guido van Rossum1115ab21992-11-04 15:51:30 +0000333
Tim Peters88869f92001-01-14 23:36:06 +0000334 A new port is created for you. Return the response code.
335 """
336 self.voidcmd('TYPE I')
337 conn = self.transfercmd(cmd, rest)
338 while 1:
339 data = conn.recv(blocksize)
340 if not data:
341 break
342 callback(data)
343 conn.close()
344 return self.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000345
Tim Peters88869f92001-01-14 23:36:06 +0000346 def retrlines(self, cmd, callback = None):
347 '''Retrieve data in line mode.
348 The argument is a RETR or LIST command.
349 The callback function (2nd argument) is called for each line,
350 with trailing CRLF stripped. This creates a new port for you.
351 print_line() is the default callback.'''
352 if not callback: callback = print_line
353 resp = self.sendcmd('TYPE A')
354 conn = self.transfercmd(cmd)
355 fp = conn.makefile('rb')
356 while 1:
357 line = fp.readline()
358 if self.debugging > 2: print '*retr*', `line`
359 if not line:
360 break
361 if line[-2:] == CRLF:
362 line = line[:-2]
363 elif line[-1:] == '\n':
364 line = line[:-1]
365 callback(line)
366 fp.close()
367 conn.close()
368 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000369
Tim Peters88869f92001-01-14 23:36:06 +0000370 def storbinary(self, cmd, fp, blocksize):
371 '''Store a file in binary mode.'''
372 self.voidcmd('TYPE I')
373 conn = self.transfercmd(cmd)
374 while 1:
375 buf = fp.read(blocksize)
376 if not buf: break
377 conn.send(buf)
378 conn.close()
379 return self.voidresp()
Guido van Rossumc567c601992-11-05 22:22:37 +0000380
Tim Peters88869f92001-01-14 23:36:06 +0000381 def storlines(self, cmd, fp):
382 '''Store a file in line mode.'''
383 self.voidcmd('TYPE A')
384 conn = self.transfercmd(cmd)
385 while 1:
386 buf = fp.readline()
387 if not buf: break
388 if buf[-2:] != CRLF:
389 if buf[-1] in CRLF: buf = buf[:-1]
390 buf = buf + CRLF
391 conn.send(buf)
392 conn.close()
393 return self.voidresp()
Guido van Rossum0eaa74b1996-01-25 18:37:21 +0000394
Tim Peters88869f92001-01-14 23:36:06 +0000395 def acct(self, password):
396 '''Send new account name.'''
397 cmd = 'ACCT ' + password
398 return self.voidcmd(cmd)
Guido van Rossumc567c601992-11-05 22:22:37 +0000399
Tim Peters88869f92001-01-14 23:36:06 +0000400 def nlst(self, *args):
401 '''Return a list of files in a given directory (default the current).'''
402 cmd = 'NLST'
403 for arg in args:
404 cmd = cmd + (' ' + arg)
405 files = []
406 self.retrlines(cmd, files.append)
407 return files
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000408
Tim Peters88869f92001-01-14 23:36:06 +0000409 def dir(self, *args):
410 '''List a directory in long form.
411 By default list current directory to stdout.
412 Optional last argument is callback function; all
413 non-empty arguments before it are concatenated to the
414 LIST command. (This *should* only be used for a pathname.)'''
415 cmd = 'LIST'
416 func = None
417 if args[-1:] and type(args[-1]) != type(''):
418 args, func = args[:-1], args[-1]
419 for arg in args:
420 if arg:
421 cmd = cmd + (' ' + arg)
422 self.retrlines(cmd, func)
Guido van Rossumc567c601992-11-05 22:22:37 +0000423
Tim Peters88869f92001-01-14 23:36:06 +0000424 def rename(self, fromname, toname):
425 '''Rename a file.'''
426 resp = self.sendcmd('RNFR ' + fromname)
427 if resp[0] != '3':
428 raise error_reply, resp
429 return self.voidcmd('RNTO ' + toname)
Guido van Rossuma61bdeb1995-10-11 17:36:31 +0000430
Tim Peters88869f92001-01-14 23:36:06 +0000431 def delete(self, filename):
432 '''Delete a file.'''
433 resp = self.sendcmd('DELE ' + filename)
434 if resp[:3] in ('250', '200'):
435 return resp
436 elif resp[:1] == '5':
437 raise error_perm, resp
438 else:
439 raise error_reply, resp
Guido van Rossum02cf5821993-05-17 08:00:02 +0000440
Tim Peters88869f92001-01-14 23:36:06 +0000441 def cwd(self, dirname):
442 '''Change to a directory.'''
443 if dirname == '..':
444 try:
445 return self.voidcmd('CDUP')
446 except error_perm, msg:
447 if msg[:3] != '500':
448 raise error_perm, msg
449 elif dirname == '':
450 dirname = '.' # does nothing, but could return error
451 cmd = 'CWD ' + dirname
452 return self.voidcmd(cmd)
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000453
Tim Peters88869f92001-01-14 23:36:06 +0000454 def size(self, filename):
455 '''Retrieve the size of a file.'''
456 # Note that the RFC doesn't say anything about 'SIZE'
457 resp = self.sendcmd('SIZE ' + filename)
458 if resp[:3] == '213':
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000459 return int(resp[3:].strip())
Guido van Rossumc567c601992-11-05 22:22:37 +0000460
Tim Peters88869f92001-01-14 23:36:06 +0000461 def mkd(self, dirname):
462 '''Make a directory, return its full pathname.'''
463 resp = self.sendcmd('MKD ' + dirname)
464 return parse257(resp)
Guido van Rossum98245091998-02-19 21:15:44 +0000465
Tim Peters88869f92001-01-14 23:36:06 +0000466 def rmd(self, dirname):
467 '''Remove a directory.'''
468 return self.voidcmd('RMD ' + dirname)
Guido van Rossum1115ab21992-11-04 15:51:30 +0000469
Tim Peters88869f92001-01-14 23:36:06 +0000470 def pwd(self):
471 '''Return current working directory.'''
472 resp = self.sendcmd('PWD')
473 return parse257(resp)
Guido van Rossum17ed1ae1993-06-01 13:21:04 +0000474
Tim Peters88869f92001-01-14 23:36:06 +0000475 def quit(self):
476 '''Quit, and close the connection.'''
477 resp = self.voidcmd('QUIT')
478 self.close()
479 return resp
480
481 def close(self):
482 '''Close the connection without assuming anything about it.'''
483 self.file.close()
484 self.sock.close()
485 del self.file, self.sock
Guido van Rossumc567c601992-11-05 22:22:37 +0000486
487
Guido van Rossumacfb82a1997-10-22 20:49:52 +0000488_150_re = None
Fred Drake4de02d91997-01-10 18:26:09 +0000489
490def parse150(resp):
Tim Peters88869f92001-01-14 23:36:06 +0000491 '''Parse the '150' response for a RETR request.
492 Returns the expected transfer size or None; size is not guaranteed to
493 be present in the 150 message.
494 '''
495 if resp[:3] != '150':
496 raise error_reply, resp
497 global _150_re
498 if _150_re is None:
499 import re
500 _150_re = re.compile("150 .* \((\d+) bytes\)", re.IGNORECASE)
501 m = _150_re.match(resp)
502 if m:
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000503 return int(m.group(1))
Tim Peters88869f92001-01-14 23:36:06 +0000504 return None
Fred Drake4de02d91997-01-10 18:26:09 +0000505
506
Guido van Rossumd2560b01996-05-28 23:41:25 +0000507def parse227(resp):
Tim Peters88869f92001-01-14 23:36:06 +0000508 '''Parse the '227' response for a PASV request.
509 Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
510 Return ('host.addr.as.numbers', port#) tuple.'''
Guido van Rossumd2560b01996-05-28 23:41:25 +0000511
Tim Peters88869f92001-01-14 23:36:06 +0000512 if resp[:3] != '227':
513 raise error_reply, resp
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000514 left = resp.find('(')
Tim Peters88869f92001-01-14 23:36:06 +0000515 if left < 0: raise error_proto, resp
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000516 right = resp.find(')', left + 1)
Tim Peters88869f92001-01-14 23:36:06 +0000517 if right < 0:
518 raise error_proto, resp # should contain '(h1,h2,h3,h4,p1,p2)'
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000519 numbers = resp[left+1:right].split(',')
Tim Peters88869f92001-01-14 23:36:06 +0000520 if len(numbers) != 6:
521 raise error_proto, resp
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000522 host = '.'.join(numbers[:4])
523 port = (int(numbers[4]) << 8) + int(numbers[5])
Tim Peters88869f92001-01-14 23:36:06 +0000524 return host, port
Guido van Rossumd2560b01996-05-28 23:41:25 +0000525
526
Guido van Rossumc567c601992-11-05 22:22:37 +0000527def parse257(resp):
Tim Peters88869f92001-01-14 23:36:06 +0000528 '''Parse the '257' response for a MKD or PWD request.
529 This is a response to a MKD or PWD request: a directory name.
530 Returns the directoryname in the 257 reply.'''
Guido van Rossumd2560b01996-05-28 23:41:25 +0000531
Tim Peters88869f92001-01-14 23:36:06 +0000532 if resp[:3] != '257':
533 raise error_reply, resp
534 if resp[3:5] != ' "':
535 return '' # Not compliant to RFC 959, but UNIX ftpd does this
536 dirname = ''
537 i = 5
538 n = len(resp)
539 while i < n:
540 c = resp[i]
541 i = i+1
542 if c == '"':
543 if i >= n or resp[i] != '"':
544 break
545 i = i+1
546 dirname = dirname + c
547 return dirname
Guido van Rossum1115ab21992-11-04 15:51:30 +0000548
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000549
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000550def print_line(line):
Tim Peters88869f92001-01-14 23:36:06 +0000551 '''Default retrlines callback to print a line.'''
552 print line
Guido van Rossumae3b3a31993-11-30 13:43:54 +0000553
Guido van Rossum2f3941d1997-10-07 14:49:56 +0000554
Guido van Rossumd2560b01996-05-28 23:41:25 +0000555def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
Tim Peters88869f92001-01-14 23:36:06 +0000556 '''Copy file from one FTP-instance to another.'''
557 if not targetname: targetname = sourcename
558 type = 'TYPE ' + type
559 source.voidcmd(type)
560 target.voidcmd(type)
561 sourcehost, sourceport = parse227(source.sendcmd('PASV'))
562 target.sendport(sourcehost, sourceport)
563 # RFC 959: the user must "listen" [...] BEFORE sending the
564 # transfer request.
565 # So: STOR before RETR, because here the target is a "user".
566 treply = target.sendcmd('STOR ' + targetname)
567 if treply[:3] not in ('125', '150'): raise error_proto # RFC 959
568 sreply = source.sendcmd('RETR ' + sourcename)
569 if sreply[:3] not in ('125', '150'): raise error_proto # RFC 959
570 source.voidresp()
571 target.voidresp()
Guido van Rossum1115ab21992-11-04 15:51:30 +0000572
Tim Peters88869f92001-01-14 23:36:06 +0000573
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000574class Netrc:
Tim Peters88869f92001-01-14 23:36:06 +0000575 """Class to parse & provide access to 'netrc' format files.
Fred Drake475d51d1997-06-24 22:02:54 +0000576
Tim Peters88869f92001-01-14 23:36:06 +0000577 See the netrc(4) man page for information on the file format.
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000578
Tim Peters88869f92001-01-14 23:36:06 +0000579 WARNING: This class is obsolete -- use module netrc instead.
Guido van Rossumc822a451998-12-22 16:49:16 +0000580
Tim Peters88869f92001-01-14 23:36:06 +0000581 """
582 __defuser = None
583 __defpasswd = None
584 __defacct = None
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000585
Tim Peters88869f92001-01-14 23:36:06 +0000586 def __init__(self, filename=None):
587 if not filename:
588 if os.environ.has_key("HOME"):
589 filename = os.path.join(os.environ["HOME"],
590 ".netrc")
591 else:
592 raise IOError, \
593 "specify file to load or set $HOME"
594 self.__hosts = {}
595 self.__macros = {}
596 fp = open(filename, "r")
597 in_macro = 0
598 while 1:
599 line = fp.readline()
600 if not line: break
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000601 if in_macro and line.strip():
Tim Peters88869f92001-01-14 23:36:06 +0000602 macro_lines.append(line)
603 continue
604 elif in_macro:
605 self.__macros[macro_name] = tuple(macro_lines)
606 in_macro = 0
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000607 words = line.split()
Tim Peters88869f92001-01-14 23:36:06 +0000608 host = user = passwd = acct = None
609 default = 0
610 i = 0
611 while i < len(words):
612 w1 = words[i]
613 if i+1 < len(words):
614 w2 = words[i + 1]
615 else:
616 w2 = None
617 if w1 == 'default':
618 default = 1
619 elif w1 == 'machine' and w2:
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000620 host = w2.lower()
Tim Peters88869f92001-01-14 23:36:06 +0000621 i = i + 1
622 elif w1 == 'login' and w2:
623 user = w2
624 i = i + 1
625 elif w1 == 'password' and w2:
626 passwd = w2
627 i = i + 1
628 elif w1 == 'account' and w2:
629 acct = w2
630 i = i + 1
631 elif w1 == 'macdef' and w2:
632 macro_name = w2
633 macro_lines = []
634 in_macro = 1
635 break
636 i = i + 1
637 if default:
638 self.__defuser = user or self.__defuser
639 self.__defpasswd = passwd or self.__defpasswd
640 self.__defacct = acct or self.__defacct
641 if host:
642 if self.__hosts.has_key(host):
643 ouser, opasswd, oacct = \
644 self.__hosts[host]
645 user = user or ouser
646 passwd = passwd or opasswd
647 acct = acct or oacct
648 self.__hosts[host] = user, passwd, acct
649 fp.close()
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000650
Tim Peters88869f92001-01-14 23:36:06 +0000651 def get_hosts(self):
652 """Return a list of hosts mentioned in the .netrc file."""
653 return self.__hosts.keys()
Guido van Rossum8ca84201998-03-26 20:56:10 +0000654
Tim Peters88869f92001-01-14 23:36:06 +0000655 def get_account(self, host):
656 """Returns login information for the named host.
Guido van Rossum8ca84201998-03-26 20:56:10 +0000657
Tim Peters88869f92001-01-14 23:36:06 +0000658 The return value is a triple containing userid,
659 password, and the accounting field.
Guido van Rossum8ca84201998-03-26 20:56:10 +0000660
Tim Peters88869f92001-01-14 23:36:06 +0000661 """
Eric S. Raymondc95bf692001-02-09 10:06:47 +0000662 host = host.lower()
Tim Peters88869f92001-01-14 23:36:06 +0000663 user = passwd = acct = None
664 if self.__hosts.has_key(host):
665 user, passwd, acct = self.__hosts[host]
666 user = user or self.__defuser
667 passwd = passwd or self.__defpasswd
668 acct = acct or self.__defacct
669 return user, passwd, acct
Guido van Rossum8ca84201998-03-26 20:56:10 +0000670
Tim Peters88869f92001-01-14 23:36:06 +0000671 def get_macros(self):
672 """Return a list of all defined macro names."""
673 return self.__macros.keys()
Guido van Rossum8ca84201998-03-26 20:56:10 +0000674
Tim Peters88869f92001-01-14 23:36:06 +0000675 def get_macro(self, macro):
676 """Return a sequence of lines which define a named macro."""
677 return self.__macros[macro]
Guido van Rossum56d1e3a1997-03-14 04:16:54 +0000678
Fred Drake475d51d1997-06-24 22:02:54 +0000679
Tim Peters88869f92001-01-14 23:36:06 +0000680
Guido van Rossum1115ab21992-11-04 15:51:30 +0000681def test():
Tim Peters88869f92001-01-14 23:36:06 +0000682 '''Test program.
683 Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...'''
Guido van Rossumd2560b01996-05-28 23:41:25 +0000684
Tim Peters88869f92001-01-14 23:36:06 +0000685 debugging = 0
686 rcfile = None
687 while sys.argv[1] == '-d':
688 debugging = debugging+1
689 del sys.argv[1]
690 if sys.argv[1][:2] == '-r':
691 # get name of alternate ~/.netrc file:
692 rcfile = sys.argv[1][2:]
693 del sys.argv[1]
694 host = sys.argv[1]
695 ftp = FTP(host)
696 ftp.set_debuglevel(debugging)
697 userid = passwd = acct = ''
698 try:
699 netrc = Netrc(rcfile)
700 except IOError:
701 if rcfile is not None:
702 sys.stderr.write("Could not open account file"
703 " -- using anonymous login.")
704 else:
705 try:
706 userid, passwd, acct = netrc.get_account(host)
707 except KeyError:
708 # no account for host
709 sys.stderr.write(
710 "No account -- using anonymous login.")
711 ftp.login(userid, passwd, acct)
712 for file in sys.argv[2:]:
713 if file[:2] == '-l':
714 ftp.dir(file[2:])
715 elif file[:2] == '-d':
716 cmd = 'CWD'
717 if file[2:]: cmd = cmd + ' ' + file[2:]
718 resp = ftp.sendcmd(cmd)
719 elif file == '-p':
720 ftp.set_pasv(not ftp.passiveserver)
721 else:
722 ftp.retrbinary('RETR ' + file, \
723 sys.stdout.write, 1024)
724 ftp.quit()
Guido van Rossum221ec0b1995-08-04 04:39:30 +0000725
726
727if __name__ == '__main__':
Tim Peters88869f92001-01-14 23:36:06 +0000728 test()