Files for 2.1b1 distribution.
[python/dscho.git] / Lib / ftplib.py
blob1688d9ab88c11cf18d0a6c4da53f02382d346954
1 """An FTP client class and some helper functions.
3 Based on RFC 959: File Transfer Protocol (FTP), by J. Postel and J. Reynolds
5 Example:
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
10 '230 Guest login ok, access restrictions apply.'
11 >>> ftp.retrlines('LIST') # list directory contents
12 total 9
13 drwxr-xr-x 8 root wheel 1024 Jan 3 1994 .
14 drwxr-xr-x 8 root wheel 1024 Jan 3 1994 ..
15 drwxr-xr-x 2 root wheel 1024 Jan 3 1994 bin
16 drwxr-xr-x 2 root wheel 1024 Jan 3 1994 etc
17 d-wxrwxr-x 2 ftp wheel 1024 Sep 5 13:43 incoming
18 drwxr-xr-x 2 root wheel 1024 Nov 17 1993 lib
19 drwxr-xr-x 6 1094 wheel 1024 Sep 13 19:07 pub
20 drwxr-xr-x 3 root wheel 1024 Jan 3 1994 usr
21 -rw-r--r-- 1 root root 312 Aug 1 1994 welcome.msg
22 '226 Transfer complete.'
23 >>> ftp.quit()
24 '221 Goodbye.'
25 >>>
27 A nice test that reveals some of the network dialogue would be:
28 python ftplib.py -d localhost -l -p -l
29 """
32 # 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.
37 import os
38 import sys
39 import string
41 # Import SOCKS module if it exists, else standard socket module socket
42 try:
43 import SOCKS; socket = SOCKS; del SOCKS # import SOCKS as socket
44 from socket import getfqdn; socket.getfqdn = getfqdn; del getfqdn
45 except ImportError:
46 import socket
48 __all__ = ["FTP","Netrc"]
50 # Magic number from <socket.h>
51 MSG_OOB = 0x1 # Process data out of band
54 # The standard FTP server control port
55 FTP_PORT = 21
58 # Exception raised when an error or invalid response is received
59 class Error(Exception): pass
60 class error_reply(Error): pass # unexpected [123]xx reply
61 class error_temp(Error): pass # 4xx errors
62 class error_perm(Error): pass # 5xx errors
63 class error_proto(Error): pass # response does not begin with [1-5]
66 # All exceptions (hopefully) that may be raised here and that aren't
67 # (always) programming errors on our side
68 all_errors = (Error, socket.error, IOError, EOFError)
71 # Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
72 CRLF = '\r\n'
75 # The class itself
76 class FTP:
78 '''An FTP client class.
80 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.
85 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.
92 '''
94 debugging = 0
95 host = ''
96 port = FTP_PORT
97 sock = None
98 file = None
99 welcome = None
100 passiveserver = 1
102 # 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())
106 def __init__(self, host='', user='', passwd='', acct=''):
107 if host:
108 self.connect(host)
109 if user: self.login(user, passwd, acct)
111 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
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
238 port number.
240 hbytes = host.split('.')
241 pbytes = [`port/256`, `port%256`]
242 bytes = hbytes + pbytes
243 cmd = 'PORT ' + ','.join(bytes)
244 return self.voidcmd(cmd)
246 def makeport(self):
247 '''Create a new socket and send a PORT command for it.'''
248 global nextport
249 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
250 sock.bind(('', 0))
251 sock.listen(1)
252 dummyhost, port = sock.getsockname() # Get proper port
253 host, dummyport = self.sock.getsockname() # Get proper host
254 resp = self.sendport(host, port)
255 return sock
257 def ntransfercmd(self, cmd, rest=None):
258 """Initiate a transfer over the data connection.
260 If the transfer is active, send a port command and the
261 transfer command, and accept the connection. If the server is
262 passive, send a pasv command, connect to it, and start the
263 transfer command. Either way, return the socket for the
264 connection and the expected size of the transfer. The
265 expected size may be None if it could not be determined.
267 Optional `rest' argument can be a string that is sent as the
268 argument to a RESTART command. This is essentially a server
269 marker used to tell the server to skip over any data up to the
270 given marker.
272 size = None
273 if self.passiveserver:
274 host, port = parse227(self.sendcmd('PASV'))
275 conn=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
276 conn.connect((host, port))
277 if rest is not None:
278 self.sendcmd("REST %s" % rest)
279 resp = self.sendcmd(cmd)
280 if resp[0] != '1':
281 raise error_reply, resp
282 else:
283 sock = self.makeport()
284 if rest is not None:
285 self.sendcmd("REST %s" % rest)
286 resp = self.sendcmd(cmd)
287 if resp[0] != '1':
288 raise error_reply, resp
289 conn, sockaddr = sock.accept()
290 if resp[:3] == '150':
291 # this is conditional in case we received a 125
292 size = parse150(resp)
293 return conn, size
295 def transfercmd(self, cmd, rest=None):
296 """Like nstransfercmd() but returns only the socket."""
297 return self.ntransfercmd(cmd, rest)[0]
299 def login(self, user = '', passwd = '', acct = ''):
300 '''Login, default anonymous.'''
301 if not user: user = 'anonymous'
302 if not passwd: passwd = ''
303 if not acct: acct = ''
304 if user == 'anonymous' and passwd in ('', '-'):
305 # get fully qualified domain name of local host
306 thishost = socket.getfqdn()
307 try:
308 if os.environ.has_key('LOGNAME'):
309 realuser = os.environ['LOGNAME']
310 elif os.environ.has_key('USER'):
311 realuser = os.environ['USER']
312 else:
313 realuser = 'anonymous'
314 except AttributeError:
315 # Not all systems have os.environ....
316 realuser = 'anonymous'
317 passwd = passwd + realuser + '@' + thishost
318 resp = self.sendcmd('USER ' + user)
319 if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
320 if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
321 if resp[0] != '2':
322 raise error_reply, resp
323 return resp
325 def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
326 """Retrieve data in binary mode.
328 `cmd' is a RETR command. `callback' is a callback function is
329 called for each block. No more than `blocksize' number of
330 bytes will be read from the socket. Optional `rest' is passed
331 to transfercmd().
333 A new port is created for you. Return the response code.
335 self.voidcmd('TYPE I')
336 conn = self.transfercmd(cmd, rest)
337 while 1:
338 data = conn.recv(blocksize)
339 if not data:
340 break
341 callback(data)
342 conn.close()
343 return self.voidresp()
345 def retrlines(self, cmd, callback = None):
346 '''Retrieve data in line mode.
347 The argument is a RETR or LIST command.
348 The callback function (2nd argument) is called for each line,
349 with trailing CRLF stripped. This creates a new port for you.
350 print_line() is the default callback.'''
351 if not callback: callback = print_line
352 resp = self.sendcmd('TYPE A')
353 conn = self.transfercmd(cmd)
354 fp = conn.makefile('rb')
355 while 1:
356 line = fp.readline()
357 if self.debugging > 2: print '*retr*', `line`
358 if not line:
359 break
360 if line[-2:] == CRLF:
361 line = line[:-2]
362 elif line[-1:] == '\n':
363 line = line[:-1]
364 callback(line)
365 fp.close()
366 conn.close()
367 return self.voidresp()
369 def storbinary(self, cmd, fp, blocksize=8192):
370 '''Store a file in binary mode.'''
371 self.voidcmd('TYPE I')
372 conn = self.transfercmd(cmd)
373 while 1:
374 buf = fp.read(blocksize)
375 if not buf: break
376 conn.send(buf)
377 conn.close()
378 return self.voidresp()
380 def storlines(self, cmd, fp):
381 '''Store a file in line mode.'''
382 self.voidcmd('TYPE A')
383 conn = self.transfercmd(cmd)
384 while 1:
385 buf = fp.readline()
386 if not buf: break
387 if buf[-2:] != CRLF:
388 if buf[-1] in CRLF: buf = buf[:-1]
389 buf = buf + CRLF
390 conn.send(buf)
391 conn.close()
392 return self.voidresp()
394 def acct(self, password):
395 '''Send new account name.'''
396 cmd = 'ACCT ' + password
397 return self.voidcmd(cmd)
399 def nlst(self, *args):
400 '''Return a list of files in a given directory (default the current).'''
401 cmd = 'NLST'
402 for arg in args:
403 cmd = cmd + (' ' + arg)
404 files = []
405 self.retrlines(cmd, files.append)
406 return files
408 def dir(self, *args):
409 '''List a directory in long form.
410 By default list current directory to stdout.
411 Optional last argument is callback function; all
412 non-empty arguments before it are concatenated to the
413 LIST command. (This *should* only be used for a pathname.)'''
414 cmd = 'LIST'
415 func = None
416 if args[-1:] and type(args[-1]) != type(''):
417 args, func = args[:-1], args[-1]
418 for arg in args:
419 if arg:
420 cmd = cmd + (' ' + arg)
421 self.retrlines(cmd, func)
423 def rename(self, fromname, toname):
424 '''Rename a file.'''
425 resp = self.sendcmd('RNFR ' + fromname)
426 if resp[0] != '3':
427 raise error_reply, resp
428 return self.voidcmd('RNTO ' + toname)
430 def delete(self, filename):
431 '''Delete a file.'''
432 resp = self.sendcmd('DELE ' + filename)
433 if resp[:3] in ('250', '200'):
434 return resp
435 elif resp[:1] == '5':
436 raise error_perm, resp
437 else:
438 raise error_reply, resp
440 def cwd(self, dirname):
441 '''Change to a directory.'''
442 if dirname == '..':
443 try:
444 return self.voidcmd('CDUP')
445 except error_perm, msg:
446 if msg[:3] != '500':
447 raise error_perm, msg
448 elif dirname == '':
449 dirname = '.' # does nothing, but could return error
450 cmd = 'CWD ' + dirname
451 return self.voidcmd(cmd)
453 def size(self, filename):
454 '''Retrieve the size of a file.'''
455 # Note that the RFC doesn't say anything about 'SIZE'
456 resp = self.sendcmd('SIZE ' + filename)
457 if resp[:3] == '213':
458 return int(resp[3:].strip())
460 def mkd(self, dirname):
461 '''Make a directory, return its full pathname.'''
462 resp = self.sendcmd('MKD ' + dirname)
463 return parse257(resp)
465 def rmd(self, dirname):
466 '''Remove a directory.'''
467 return self.voidcmd('RMD ' + dirname)
469 def pwd(self):
470 '''Return current working directory.'''
471 resp = self.sendcmd('PWD')
472 return parse257(resp)
474 def quit(self):
475 '''Quit, and close the connection.'''
476 resp = self.voidcmd('QUIT')
477 self.close()
478 return resp
480 def close(self):
481 '''Close the connection without assuming anything about it.'''
482 if self.file:
483 self.file.close()
484 self.sock.close()
485 self.file = self.sock = None
488 _150_re = None
490 def parse150(resp):
491 '''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.
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:
503 return int(m.group(1))
504 return None
507 def parse227(resp):
508 '''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.'''
512 if resp[:3] != '227':
513 raise error_reply, resp
514 left = resp.find('(')
515 if left < 0: raise error_proto, resp
516 right = resp.find(')', left + 1)
517 if right < 0:
518 raise error_proto, resp # should contain '(h1,h2,h3,h4,p1,p2)'
519 numbers = resp[left+1:right].split(',')
520 if len(numbers) != 6:
521 raise error_proto, resp
522 host = '.'.join(numbers[:4])
523 port = (int(numbers[4]) << 8) + int(numbers[5])
524 return host, port
527 def parse257(resp):
528 '''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.'''
532 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
550 def print_line(line):
551 '''Default retrlines callback to print a line.'''
552 print line
555 def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
556 '''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()
574 class Netrc:
575 """Class to parse & provide access to 'netrc' format files.
577 See the netrc(4) man page for information on the file format.
579 WARNING: This class is obsolete -- use module netrc instead.
582 __defuser = None
583 __defpasswd = None
584 __defacct = None
586 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
601 if in_macro and line.strip():
602 macro_lines.append(line)
603 continue
604 elif in_macro:
605 self.__macros[macro_name] = tuple(macro_lines)
606 in_macro = 0
607 words = line.split()
608 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:
620 host = w2.lower()
621 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()
651 def get_hosts(self):
652 """Return a list of hosts mentioned in the .netrc file."""
653 return self.__hosts.keys()
655 def get_account(self, host):
656 """Returns login information for the named host.
658 The return value is a triple containing userid,
659 password, and the accounting field.
662 host = host.lower()
663 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
671 def get_macros(self):
672 """Return a list of all defined macro names."""
673 return self.__macros.keys()
675 def get_macro(self, macro):
676 """Return a sequence of lines which define a named macro."""
677 return self.__macros[macro]
681 def test():
682 '''Test program.
683 Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...'''
685 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()
727 if __name__ == '__main__':
728 test()