Bump version to 0.9.1.
[python/dscho.git] / Lib / ftplib.py
blobfd9127bff152c7d335a47e90a4572403151b7536
1 """An FTP client class and some helper functions.
3 Based on RFC 959: File Transfer Protocol
4 (FTP), by J. Postel and J. Reynolds
6 Example:
8 >>> from ftplib import FTP
9 >>> ftp = FTP('ftp.python.org') # connect to host, default port
10 >>> ftp.login() # default, i.e.: user anonymous, passwd user@hostname
11 '230 Guest login ok, access restrictions apply.'
12 >>> ftp.retrlines('LIST') # list directory contents
13 total 9
14 drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .
15 drwxr-xr-x 8 root wheel 1024 Jan 3 1994 ..
16 drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin
17 drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc
18 d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming
19 drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib
20 drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub
21 drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr
22 -rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg
23 '226 Transfer complete.'
24 >>> ftp.quit()
25 '221 Goodbye.'
26 >>>
28 A nice test that reveals some of the network dialogue would be:
29 python ftplib.py -d localhost -l -p -l
30 """
33 # Changes and improvements suggested by Steve Majewski.
34 # Modified by Jack to work on the mac.
35 # Modified by Siebren to support docstrings and PASV.
38 import os
39 import sys
40 import string
42 # Import SOCKS module if it exists, else standard socket module socket
43 try:
44 import SOCKS; socket = SOCKS
45 except ImportError:
46 import socket
49 # Magic number from <socket.h>
50 MSG_OOB = 0x1 # Process data out of band
53 # The standard FTP server control port
54 FTP_PORT = 21
57 # Exception raised when an error or invalid response is received
58 error_reply = 'ftplib.error_reply' # unexpected [123]xx reply
59 error_temp = 'ftplib.error_temp' # 4xx errors
60 error_perm = 'ftplib.error_perm' # 5xx errors
61 error_proto = 'ftplib.error_proto' # response does not begin with [1-5]
64 # All exceptions (hopefully) that may be raised here and that aren't
65 # (always) programming errors on our side
66 all_errors = (error_reply, error_temp, error_perm, error_proto, \
67 socket.error, IOError, EOFError)
70 # Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
71 CRLF = '\r\n'
74 # The class itself
75 class FTP:
77 '''An FTP client class.
79 To create a connection, call the class using these argument:
80 host, user, passwd, acct
81 These are all strings, and have default value ''.
82 Then use self.connect() with optional host and port argument.
84 To download a file, use ftp.retrlines('RETR ' + filename),
85 or ftp.retrbinary() with slightly different arguments.
86 To upload a file, use ftp.storlines() or ftp.storbinary(),
87 which have an open file as argument (see their definitions
88 below for details).
89 The download/upload functions first issue appropriate TYPE
90 and PORT or PASV commands.
91 '''
93 # Initialization method (called by class instantiation).
94 # Initialize host to localhost, port to standard ftp port
95 # Optional arguments are host (for connect()),
96 # and user, passwd, acct (for login())
97 def __init__(self, host = '', user = '', passwd = '', acct = ''):
98 # Initialize the instance to something mostly harmless
99 self.debugging = 0
100 self.host = ''
101 self.port = FTP_PORT
102 self.sock = None
103 self.file = None
104 self.welcome = None
105 resp = None
106 if host:
107 resp = self.connect(host)
108 if user: resp = self.login(user, passwd, acct)
110 def connect(self, host = '', port = 0):
111 '''Connect to host. Arguments are:
112 - host: hostname to connect to (string, default previous host)
113 - port: port to connect to (integer, default previous port)'''
114 if host: self.host = host
115 if port: self.port = port
116 self.passiveserver = 0
117 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
123 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
130 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
139 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
145 # 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`
154 # 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)
160 # 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)
165 # Internal: return one line from the server, stripping CRLF.
166 # Raise EOFError if the connection is closed
167 def getline(self):
168 line = self.file.readline()
169 if self.debugging > 1:
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
176 # Internal: get a response from the server, which may possibly
177 # consist of multiple lines. Return a single string with no
178 # trailing CRLF. If the response consists of multiple lines,
179 # these are separated by '\n' characters in the string
180 def getmultiline(self):
181 line = self.getline()
182 if line[3:4] == '-':
183 code = line[:3]
184 while 1:
185 nextline = self.getline()
186 line = line + ('\n' + nextline)
187 if nextline[:3] == code and \
188 nextline[3:4] <> '-':
189 break
190 return line
192 # Internal: get a response from the server.
193 # Raise various errors if the response indicates an error
194 def getresp(self):
195 resp = self.getmultiline()
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
207 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
214 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
226 def sendcmd(self, cmd):
227 '''Send a command and return the response.'''
228 self.putcmd(cmd)
229 return self.getresp()
231 def voidcmd(self, cmd):
232 """Send a command and expect a response beginning with '2'."""
233 self.putcmd(cmd)
234 return self.voidresp()
236 def sendport(self, host, port):
237 '''Send a PORT command with the current host and the given port number.'''
238 hbytes = string.splitfields(host, '.')
239 pbytes = [`port/256`, `port%256`]
240 bytes = hbytes + pbytes
241 cmd = 'PORT ' + string.joinfields(bytes, ',')
242 return self.voidcmd(cmd)
244 def makeport(self):
245 '''Create a new socket and send a PORT command for it.'''
246 global nextport
247 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
248 sock.bind(('', 0))
249 sock.listen(1)
250 dummyhost, port = sock.getsockname() # Get proper port
251 host, dummyport = self.sock.getsockname() # Get proper host
252 resp = self.sendport(host, port)
253 return sock
255 def ntransfercmd(self, cmd):
256 '''Initiate a transfer over the data connection.
257 If the transfer is active, send a port command and
258 the transfer command, and accept the connection.
259 If the server is passive, send a pasv command, connect
260 to it, and start the transfer command.
261 Either way, return the socket for the connection and
262 the expected size of the transfer. The expected size
263 may be None if it could not be determined.'''
264 size = None
265 if self.passiveserver:
266 host, port = parse227(self.sendcmd('PASV'))
267 conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
268 conn.connect((host, port))
269 resp = self.sendcmd(cmd)
270 if resp[0] <> '1':
271 raise error_reply, resp
272 else:
273 sock = self.makeport()
274 resp = self.sendcmd(cmd)
275 if resp[0] <> '1':
276 raise error_reply, resp
277 conn, sockaddr = sock.accept()
278 if resp[:3] == '150':
279 # this is conditional in case we received a 125
280 size = parse150(resp)
281 return conn, size
283 def transfercmd(self, cmd):
284 '''Initiate a transfer over the data connection. Returns
285 the socket for the connection. See also ntransfercmd().'''
286 return self.ntransfercmd(cmd)[0]
288 def login(self, user = '', passwd = '', acct = ''):
289 '''Login, default anonymous.'''
290 if not user: user = 'anonymous'
291 if not passwd: passwd = ''
292 if not acct: acct = ''
293 if user == 'anonymous' and passwd in ('', '-'):
294 thishost = socket.gethostname()
295 # Make sure it is fully qualified
296 if not '.' in thishost:
297 thisaddr = socket.gethostbyname(thishost)
298 firstname, names, unused = \
299 socket.gethostbyaddr(thisaddr)
300 names.insert(0, firstname)
301 for name in names:
302 if '.' in name:
303 thishost = name
304 break
305 try:
306 if os.environ.has_key('LOGNAME'):
307 realuser = os.environ['LOGNAME']
308 elif os.environ.has_key('USER'):
309 realuser = os.environ['USER']
310 else:
311 realuser = 'anonymous'
312 except AttributeError:
313 # Not all systems have os.environ....
314 realuser = 'anonymous'
315 passwd = passwd + realuser + '@' + thishost
316 resp = self.sendcmd('USER ' + user)
317 if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
318 if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
319 if resp[0] <> '2':
320 raise error_reply, resp
321 return resp
323 def retrbinary(self, cmd, callback, blocksize=8192):
324 '''Retrieve data in binary mode.
325 The argument is a RETR command.
326 The callback function is called for each block.
327 This creates a new port for you'''
328 self.voidcmd('TYPE I')
329 conn = self.transfercmd(cmd)
330 while 1:
331 data = conn.recv(blocksize)
332 if not data:
333 break
334 callback(data)
335 conn.close()
336 return self.voidresp()
338 def retrlines(self, cmd, callback = None):
339 '''Retrieve data in line mode.
340 The argument is a RETR or LIST command.
341 The callback function (2nd argument) is called for each line,
342 with trailing CRLF stripped. This creates a new port for you.
343 print_line() is the default callback.'''
344 if not callback: callback = print_line
345 resp = self.sendcmd('TYPE A')
346 conn = self.transfercmd(cmd)
347 fp = conn.makefile('rb')
348 while 1:
349 line = fp.readline()
350 if self.debugging > 2: print '*retr*', `line`
351 if not line:
352 break
353 if line[-2:] == CRLF:
354 line = line[:-2]
355 elif line[-1:] == '\n':
356 line = line[:-1]
357 callback(line)
358 fp.close()
359 conn.close()
360 return self.voidresp()
362 def storbinary(self, cmd, fp, blocksize):
363 '''Store a file in binary mode.'''
364 self.voidcmd('TYPE I')
365 conn = self.transfercmd(cmd)
366 while 1:
367 buf = fp.read(blocksize)
368 if not buf: break
369 conn.send(buf)
370 conn.close()
371 return self.voidresp()
373 def storlines(self, cmd, fp):
374 '''Store a file in line mode.'''
375 self.voidcmd('TYPE A')
376 conn = self.transfercmd(cmd)
377 while 1:
378 buf = fp.readline()
379 if not buf: break
380 if buf[-2:] <> CRLF:
381 if buf[-1] in CRLF: buf = buf[:-1]
382 buf = buf + CRLF
383 conn.send(buf)
384 conn.close()
385 return self.voidresp()
387 def acct(self, password):
388 '''Send new account name.'''
389 cmd = 'ACCT ' + password
390 return self.voidcmd(cmd)
392 def nlst(self, *args):
393 '''Return a list of files in a given directory (default the current).'''
394 cmd = 'NLST'
395 for arg in args:
396 cmd = cmd + (' ' + arg)
397 files = []
398 self.retrlines(cmd, files.append)
399 return files
401 def dir(self, *args):
402 '''List a directory in long form.
403 By default list current directory to stdout.
404 Optional last argument is callback function; all
405 non-empty arguments before it are concatenated to the
406 LIST command. (This *should* only be used for a pathname.)'''
407 cmd = 'LIST'
408 func = None
409 if args[-1:] and type(args[-1]) != type(''):
410 args, func = args[:-1], args[-1]
411 for arg in args:
412 if arg:
413 cmd = cmd + (' ' + arg)
414 self.retrlines(cmd, func)
416 def rename(self, fromname, toname):
417 '''Rename a file.'''
418 resp = self.sendcmd('RNFR ' + fromname)
419 if resp[0] <> '3':
420 raise error_reply, resp
421 return self.voidcmd('RNTO ' + toname)
423 def delete(self, filename):
424 '''Delete a file.'''
425 resp = self.sendcmd('DELE ' + filename)
426 if resp[:3] in ('250', '200'):
427 return resp
428 elif resp[:1] == '5':
429 raise error_perm, resp
430 else:
431 raise error_reply, resp
433 def cwd(self, dirname):
434 '''Change to a directory.'''
435 if dirname == '..':
436 try:
437 return self.voidcmd('CDUP')
438 except error_perm, msg:
439 if msg[:3] != '500':
440 raise error_perm, msg
441 elif dirname == '':
442 dirname = '.' # does nothing, but could return error
443 cmd = 'CWD ' + dirname
444 return self.voidcmd(cmd)
446 def size(self, filename):
447 '''Retrieve the size of a file.'''
448 # Note that the RFC doesn't say anything about 'SIZE'
449 resp = self.sendcmd('SIZE ' + filename)
450 if resp[:3] == '213':
451 return string.atoi(string.strip(resp[3:]))
453 def mkd(self, dirname):
454 '''Make a directory, return its full pathname.'''
455 resp = self.sendcmd('MKD ' + dirname)
456 return parse257(resp)
458 def rmd(self, dirname):
459 '''Remove a directory.'''
460 return self.voidcmd('RMD ' + dirname)
462 def pwd(self):
463 '''Return current working directory.'''
464 resp = self.sendcmd('PWD')
465 return parse257(resp)
467 def quit(self):
468 '''Quit, and close the connection.'''
469 resp = self.voidcmd('QUIT')
470 self.close()
471 return resp
473 def close(self):
474 '''Close the connection without assuming anything about it.'''
475 self.file.close()
476 self.sock.close()
477 del self.file, self.sock
480 _150_re = None
482 def parse150(resp):
483 '''Parse the '150' response for a RETR request.
484 Returns the expected transfer size or None; size is not guaranteed to
485 be present in the 150 message.
487 if resp[:3] != '150':
488 raise error_reply, resp
489 global _150_re
490 if _150_re is None:
491 import re
492 _150_re = re.compile("150 .* \((\d+) bytes\)", re.IGNORECASE)
493 m = _150_re.match(resp)
494 if m:
495 return string.atoi(m.group(1))
496 return None
499 def parse227(resp):
500 '''Parse the '227' response for a PASV request.
501 Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
502 Return ('host.addr.as.numbers', port#) tuple.'''
504 if resp[:3] <> '227':
505 raise error_reply, resp
506 left = string.find(resp, '(')
507 if left < 0: raise error_proto, resp
508 right = string.find(resp, ')', left + 1)
509 if right < 0:
510 raise error_proto, resp # should contain '(h1,h2,h3,h4,p1,p2)'
511 numbers = string.split(resp[left+1:right], ',')
512 if len(numbers) <> 6:
513 raise error_proto, resp
514 host = string.join(numbers[:4], '.')
515 port = (string.atoi(numbers[4]) << 8) + string.atoi(numbers[5])
516 return host, port
519 def parse257(resp):
520 '''Parse the '257' response for a MKD or PWD request.
521 This is a response to a MKD or PWD request: a directory name.
522 Returns the directoryname in the 257 reply.'''
524 if resp[:3] <> '257':
525 raise error_reply, resp
526 if resp[3:5] <> ' "':
527 return '' # Not compliant to RFC 959, but UNIX ftpd does this
528 dirname = ''
529 i = 5
530 n = len(resp)
531 while i < n:
532 c = resp[i]
533 i = i+1
534 if c == '"':
535 if i >= n or resp[i] <> '"':
536 break
537 i = i+1
538 dirname = dirname + c
539 return dirname
542 def print_line(line):
543 '''Default retrlines callback to print a line.'''
544 print line
547 def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
548 '''Copy file from one FTP-instance to another.'''
549 if not targetname: targetname = sourcename
550 type = 'TYPE ' + type
551 source.voidcmd(type)
552 target.voidcmd(type)
553 sourcehost, sourceport = parse227(source.sendcmd('PASV'))
554 target.sendport(sourcehost, sourceport)
555 # RFC 959: the user must "listen" [...] BEFORE sending the
556 # transfer request.
557 # So: STOR before RETR, because here the target is a "user".
558 treply = target.sendcmd('STOR ' + targetname)
559 if treply[:3] not in ('125', '150'): raise error_proto # RFC 959
560 sreply = source.sendcmd('RETR ' + sourcename)
561 if sreply[:3] not in ('125', '150'): raise error_proto # RFC 959
562 source.voidresp()
563 target.voidresp()
566 class Netrc:
567 """Class to parse & provide access to 'netrc' format files.
569 See the netrc(4) man page for information on the file format.
571 WARNING: This class is obsolete -- use module netrc instead.
574 __defuser = None
575 __defpasswd = None
576 __defacct = None
578 def __init__(self, filename=None):
579 if not filename:
580 if os.environ.has_key("HOME"):
581 filename = os.path.join(os.environ["HOME"],
582 ".netrc")
583 else:
584 raise IOError, \
585 "specify file to load or set $HOME"
586 self.__hosts = {}
587 self.__macros = {}
588 fp = open(filename, "r")
589 in_macro = 0
590 while 1:
591 line = fp.readline()
592 if not line: break
593 if in_macro and string.strip(line):
594 macro_lines.append(line)
595 continue
596 elif in_macro:
597 self.__macros[macro_name] = tuple(macro_lines)
598 in_macro = 0
599 words = string.split(line)
600 host = user = passwd = acct = None
601 default = 0
602 i = 0
603 while i < len(words):
604 w1 = words[i]
605 if i+1 < len(words):
606 w2 = words[i + 1]
607 else:
608 w2 = None
609 if w1 == 'default':
610 default = 1
611 elif w1 == 'machine' and w2:
612 host = string.lower(w2)
613 i = i + 1
614 elif w1 == 'login' and w2:
615 user = w2
616 i = i + 1
617 elif w1 == 'password' and w2:
618 passwd = w2
619 i = i + 1
620 elif w1 == 'account' and w2:
621 acct = w2
622 i = i + 1
623 elif w1 == 'macdef' and w2:
624 macro_name = w2
625 macro_lines = []
626 in_macro = 1
627 break
628 i = i + 1
629 if default:
630 self.__defuser = user or self.__defuser
631 self.__defpasswd = passwd or self.__defpasswd
632 self.__defacct = acct or self.__defacct
633 if host:
634 if self.__hosts.has_key(host):
635 ouser, opasswd, oacct = \
636 self.__hosts[host]
637 user = user or ouser
638 passwd = passwd or opasswd
639 acct = acct or oacct
640 self.__hosts[host] = user, passwd, acct
641 fp.close()
643 def get_hosts(self):
644 """Return a list of hosts mentioned in the .netrc file."""
645 return self.__hosts.keys()
647 def get_account(self, host):
648 """Returns login information for the named host.
650 The return value is a triple containing userid,
651 password, and the accounting field.
654 host = string.lower(host)
655 user = passwd = acct = None
656 if self.__hosts.has_key(host):
657 user, passwd, acct = self.__hosts[host]
658 user = user or self.__defuser
659 passwd = passwd or self.__defpasswd
660 acct = acct or self.__defacct
661 return user, passwd, acct
663 def get_macros(self):
664 """Return a list of all defined macro names."""
665 return self.__macros.keys()
667 def get_macro(self, macro):
668 """Return a sequence of lines which define a named macro."""
669 return self.__macros[macro]
673 def test():
674 '''Test program.
675 Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...'''
677 debugging = 0
678 rcfile = None
679 while sys.argv[1] == '-d':
680 debugging = debugging+1
681 del sys.argv[1]
682 if sys.argv[1][:2] == '-r':
683 # get name of alternate ~/.netrc file:
684 rcfile = sys.argv[1][2:]
685 del sys.argv[1]
686 host = sys.argv[1]
687 ftp = FTP(host)
688 ftp.set_debuglevel(debugging)
689 userid = passwd = acct = ''
690 try:
691 netrc = Netrc(rcfile)
692 except IOError:
693 if rcfile is not None:
694 sys.stderr.write("Could not open account file"
695 " -- using anonymous login.")
696 else:
697 try:
698 userid, passwd, acct = netrc.get_account(host)
699 except KeyError:
700 # no account for host
701 sys.stderr.write(
702 "No account -- using anonymous login.")
703 ftp.login(userid, passwd, acct)
704 for file in sys.argv[2:]:
705 if file[:2] == '-l':
706 ftp.dir(file[2:])
707 elif file[:2] == '-d':
708 cmd = 'CWD'
709 if file[2:]: cmd = cmd + ' ' + file[2:]
710 resp = ftp.sendcmd(cmd)
711 elif file == '-p':
712 ftp.set_pasv(not ftp.passiveserver)
713 else:
714 ftp.retrbinary('RETR ' + file, \
715 sys.stdout.write, 1024)
716 ftp.quit()
719 if __name__ == '__main__':
720 test()