PageRenderTime 933ms CodeModel.GetById 140ms app.highlight 277ms RepoModel.GetById 445ms app.codeStats 0ms

/Lib/ftplib.py

http://unladen-swallow.googlecode.com/
Python | 855 lines | 820 code | 7 blank | 28 comment | 11 complexity | 30d771e303f5376f5e518c466179db9e MD5 | raw file
  1"""An FTP client class and some helper functions.
  2
  3Based on RFC 959: File Transfer Protocol (FTP), by J. Postel and J. Reynolds
  4
  5Example:
  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 anonymous@
 10'230 Guest login ok, access restrictions apply.'
 11>>> 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
 22'226 Transfer complete.'
 23>>> ftp.quit()
 24'221 Goodbye.'
 25>>>
 26
 27A nice test that reveals some of the network dialogue would be:
 28python ftplib.py -d localhost -l -p -l
 29"""
 30
 31#
 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.
 35# Modified by Phil Schwartz to add storbinary and storlines callbacks.
 36#
 37
 38import os
 39import sys
 40
 41# Import SOCKS module if it exists, else standard socket module socket
 42try:
 43    import SOCKS; socket = SOCKS; del SOCKS # import SOCKS as socket
 44    from socket import getfqdn; socket.getfqdn = getfqdn; del getfqdn
 45except ImportError:
 46    import socket
 47from socket import _GLOBAL_DEFAULT_TIMEOUT
 48
 49__all__ = ["FTP","Netrc"]
 50
 51# Magic number from <socket.h>
 52MSG_OOB = 0x1                           # Process data out of band
 53
 54
 55# The standard FTP server control port
 56FTP_PORT = 21
 57
 58
 59# Exception raised when an error or invalid response is received
 60class Error(Exception): pass
 61class error_reply(Error): pass          # unexpected [123]xx reply
 62class error_temp(Error): pass           # 4xx errors
 63class error_perm(Error): pass           # 5xx errors
 64class error_proto(Error): pass          # response does not begin with [1-5]
 65
 66
 67# All exceptions (hopefully) that may be raised here and that aren't
 68# (always) programming errors on our side
 69all_errors = (Error, IOError, EOFError)
 70
 71
 72# Line terminators (we always output CRLF, but accept any of CRLF, CR, LF)
 73CRLF = '\r\n'
 74
 75# The class itself
 76class FTP:
 77
 78    '''An FTP client class.
 79
 80    To create a connection, call the class using these arguments:
 81            host, user, passwd, acct, timeout
 82
 83    The first four arguments are all strings, and have default value ''.
 84    timeout must be numeric and defaults to None if not passed,
 85    meaning that no timeout will be set on any ftp socket(s)
 86    If a timeout is passed, then this is now the default timeout for all ftp
 87    socket operations for this instance.
 88
 89    Then use self.connect() with optional host and port argument.
 90
 91    To download a file, use ftp.retrlines('RETR ' + filename),
 92    or ftp.retrbinary() with slightly different arguments.
 93    To upload a file, use ftp.storlines() or ftp.storbinary(),
 94    which have an open file as argument (see their definitions
 95    below for details).
 96    The download/upload functions first issue appropriate TYPE
 97    and PORT or PASV commands.
 98'''
 99
100    debugging = 0
101    host = ''
102    port = FTP_PORT
103    sock = None
104    file = None
105    welcome = None
106    passiveserver = 1
107
108    # Initialization method (called by class instantiation).
109    # Initialize host to localhost, port to standard ftp port
110    # Optional arguments are host (for connect()),
111    # and user, passwd, acct (for login())
112    def __init__(self, host='', user='', passwd='', acct='',
113                 timeout=_GLOBAL_DEFAULT_TIMEOUT):
114        self.timeout = timeout
115        if host:
116            self.connect(host)
117            if user:
118                self.login(user, passwd, acct)
119
120    def connect(self, host='', port=0, timeout=-999):
121        '''Connect to host.  Arguments are:
122         - host: hostname to connect to (string, default previous host)
123         - port: port to connect to (integer, default previous port)
124        '''
125        if host != '':
126            self.host = host
127        if port > 0:
128            self.port = port
129        if timeout != -999:
130            self.timeout = timeout
131        self.sock = socket.create_connection((self.host, self.port), self.timeout)
132        self.af = self.sock.family
133        self.file = self.sock.makefile('rb')
134        self.welcome = self.getresp()
135        return self.welcome
136
137    def getwelcome(self):
138        '''Get the welcome message from the server.
139        (this is read and squirreled away by connect())'''
140        if self.debugging:
141            print '*welcome*', self.sanitize(self.welcome)
142        return self.welcome
143
144    def set_debuglevel(self, level):
145        '''Set the debugging level.
146        The required argument level means:
147        0: no debugging output (default)
148        1: print commands and responses but not body text etc.
149        2: also print raw lines read and sent before stripping CR/LF'''
150        self.debugging = level
151    debug = set_debuglevel
152
153    def set_pasv(self, val):
154        '''Use passive or active mode for data transfers.
155        With a false argument, use the normal PORT mode,
156        With a true argument, use the PASV command.'''
157        self.passiveserver = val
158
159    # Internal: "sanitize" a string for printing
160    def sanitize(self, s):
161        if s[:5] == 'pass ' or s[:5] == 'PASS ':
162            i = len(s)
163            while i > 5 and s[i-1] in '\r\n':
164                i = i-1
165            s = s[:5] + '*'*(i-5) + s[i:]
166        return repr(s)
167
168    # Internal: send one line to the server, appending CRLF
169    def putline(self, line):
170        line = line + CRLF
171        if self.debugging > 1: print '*put*', self.sanitize(line)
172        self.sock.sendall(line)
173
174    # Internal: send one command to the server (through putline())
175    def putcmd(self, line):
176        if self.debugging: print '*cmd*', self.sanitize(line)
177        self.putline(line)
178
179    # Internal: return one line from the server, stripping CRLF.
180    # Raise EOFError if the connection is closed
181    def getline(self):
182        line = self.file.readline()
183        if self.debugging > 1:
184            print '*get*', self.sanitize(line)
185        if not line: raise EOFError
186        if line[-2:] == CRLF: line = line[:-2]
187        elif line[-1:] in CRLF: line = line[:-1]
188        return line
189
190    # Internal: get a response from the server, which may possibly
191    # consist of multiple lines.  Return a single string with no
192    # trailing CRLF.  If the response consists of multiple lines,
193    # these are separated by '\n' characters in the string
194    def getmultiline(self):
195        line = self.getline()
196        if line[3:4] == '-':
197            code = line[:3]
198            while 1:
199                nextline = self.getline()
200                line = line + ('\n' + nextline)
201                if nextline[:3] == code and \
202                        nextline[3:4] != '-':
203                    break
204        return line
205
206    # Internal: get a response from the server.
207    # Raise various errors if the response indicates an error
208    def getresp(self):
209        resp = self.getmultiline()
210        if self.debugging: print '*resp*', self.sanitize(resp)
211        self.lastresp = resp[:3]
212        c = resp[:1]
213        if c in ('1', '2', '3'):
214            return resp
215        if c == '4':
216            raise error_temp, resp
217        if c == '5':
218            raise error_perm, resp
219        raise error_proto, resp
220
221    def voidresp(self):
222        """Expect a response beginning with '2'."""
223        resp = self.getresp()
224        if resp[:1] != '2':
225            raise error_reply, resp
226        return resp
227
228    def abort(self):
229        '''Abort a file transfer.  Uses out-of-band data.
230        This does not follow the procedure from the RFC to send Telnet
231        IP and Synch; that doesn't seem to work with the servers I've
232        tried.  Instead, just send the ABOR command as OOB data.'''
233        line = 'ABOR' + CRLF
234        if self.debugging > 1: print '*put urgent*', self.sanitize(line)
235        self.sock.sendall(line, MSG_OOB)
236        resp = self.getmultiline()
237        if resp[:3] not in ('426', '226'):
238            raise error_proto, resp
239
240    def sendcmd(self, cmd):
241        '''Send a command and return the response.'''
242        self.putcmd(cmd)
243        return self.getresp()
244
245    def voidcmd(self, cmd):
246        """Send a command and expect a response beginning with '2'."""
247        self.putcmd(cmd)
248        return self.voidresp()
249
250    def sendport(self, host, port):
251        '''Send a PORT command with the current host and the given
252        port number.
253        '''
254        hbytes = host.split('.')
255        pbytes = [repr(port//256), repr(port%256)]
256        bytes = hbytes + pbytes
257        cmd = 'PORT ' + ','.join(bytes)
258        return self.voidcmd(cmd)
259
260    def sendeprt(self, host, port):
261        '''Send a EPRT command with the current host and the given port number.'''
262        af = 0
263        if self.af == socket.AF_INET:
264            af = 1
265        if self.af == socket.AF_INET6:
266            af = 2
267        if af == 0:
268            raise error_proto, 'unsupported address family'
269        fields = ['', repr(af), host, repr(port), '']
270        cmd = 'EPRT ' + '|'.join(fields)
271        return self.voidcmd(cmd)
272
273    def makeport(self):
274        '''Create a new socket and send a PORT command for it.'''
275        msg = "getaddrinfo returns an empty list"
276        sock = None
277        for res in socket.getaddrinfo(None, 0, self.af, socket.SOCK_STREAM, 0, socket.AI_PASSIVE):
278            af, socktype, proto, canonname, sa = res
279            try:
280                sock = socket.socket(af, socktype, proto)
281                sock.bind(sa)
282            except socket.error, msg:
283                if sock:
284                    sock.close()
285                sock = None
286                continue
287            break
288        if not sock:
289            raise socket.error, msg
290        sock.listen(1)
291        port = sock.getsockname()[1] # Get proper port
292        host = self.sock.getsockname()[0] # Get proper host
293        if self.af == socket.AF_INET:
294            resp = self.sendport(host, port)
295        else:
296            resp = self.sendeprt(host, port)
297        return sock
298
299    def makepasv(self):
300        if self.af == socket.AF_INET:
301            host, port = parse227(self.sendcmd('PASV'))
302        else:
303            host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername())
304        return host, port
305
306    def ntransfercmd(self, cmd, rest=None):
307        """Initiate a transfer over the data connection.
308
309        If the transfer is active, send a port command and the
310        transfer command, and accept the connection.  If the server is
311        passive, send a pasv command, connect to it, and start the
312        transfer command.  Either way, return the socket for the
313        connection and the expected size of the transfer.  The
314        expected size may be None if it could not be determined.
315
316        Optional `rest' argument can be a string that is sent as the
317        argument to a REST command.  This is essentially a server
318        marker used to tell the server to skip over any data up to the
319        given marker.
320        """
321        size = None
322        if self.passiveserver:
323            host, port = self.makepasv()
324            conn = socket.create_connection((host, port), self.timeout)
325            if rest is not None:
326                self.sendcmd("REST %s" % rest)
327            resp = self.sendcmd(cmd)
328            # Some servers apparently send a 200 reply to
329            # a LIST or STOR command, before the 150 reply
330            # (and way before the 226 reply). This seems to
331            # be in violation of the protocol (which only allows
332            # 1xx or error messages for LIST), so we just discard
333            # this response.
334            if resp[0] == '2':
335                resp = self.getresp()
336            if resp[0] != '1':
337                raise error_reply, resp
338        else:
339            sock = self.makeport()
340            if rest is not None:
341                self.sendcmd("REST %s" % rest)
342            resp = self.sendcmd(cmd)
343            # See above.
344            if resp[0] == '2':
345                resp = self.getresp()
346            if resp[0] != '1':
347                raise error_reply, resp
348            conn, sockaddr = sock.accept()
349        if resp[:3] == '150':
350            # this is conditional in case we received a 125
351            size = parse150(resp)
352        return conn, size
353
354    def transfercmd(self, cmd, rest=None):
355        """Like ntransfercmd() but returns only the socket."""
356        return self.ntransfercmd(cmd, rest)[0]
357
358    def login(self, user = '', passwd = '', acct = ''):
359        '''Login, default anonymous.'''
360        if not user: user = 'anonymous'
361        if not passwd: passwd = ''
362        if not acct: acct = ''
363        if user == 'anonymous' and passwd in ('', '-'):
364            # If there is no anonymous ftp password specified
365            # then we'll just use anonymous@
366            # We don't send any other thing because:
367            # - We want to remain anonymous
368            # - We want to stop SPAM
369            # - We don't want to let ftp sites to discriminate by the user,
370            #   host or country.
371            passwd = passwd + 'anonymous@'
372        resp = self.sendcmd('USER ' + user)
373        if resp[0] == '3': resp = self.sendcmd('PASS ' + passwd)
374        if resp[0] == '3': resp = self.sendcmd('ACCT ' + acct)
375        if resp[0] != '2':
376            raise error_reply, resp
377        return resp
378
379    def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
380        """Retrieve data in binary mode.  A new port is created for you.
381
382        Args:
383          cmd: A RETR command.
384          callback: A single parameter callable to be called on each
385                    block of data read.
386          blocksize: The maximum number of bytes to read from the
387                     socket at one time.  [default: 8192]
388          rest: Passed to transfercmd().  [default: None]
389
390        Returns:
391          The response code.
392        """
393        self.voidcmd('TYPE I')
394        conn = self.transfercmd(cmd, rest)
395        while 1:
396            data = conn.recv(blocksize)
397            if not data:
398                break
399            callback(data)
400        conn.close()
401        return self.voidresp()
402
403    def retrlines(self, cmd, callback = None):
404        """Retrieve data in line mode.  A new port is created for you.
405
406        Args:
407          cmd: A RETR, LIST, NLST, or MLSD command.
408          callback: An optional single parameter callable that is called
409                    for each line with the trailing CRLF stripped.
410                    [default: print_line()]
411
412        Returns:
413          The response code.
414        """
415        if callback is None: callback = print_line
416        resp = self.sendcmd('TYPE A')
417        conn = self.transfercmd(cmd)
418        fp = conn.makefile('rb')
419        while 1:
420            line = fp.readline()
421            if self.debugging > 2: print '*retr*', repr(line)
422            if not line:
423                break
424            if line[-2:] == CRLF:
425                line = line[:-2]
426            elif line[-1:] == '\n':
427                line = line[:-1]
428            callback(line)
429        fp.close()
430        conn.close()
431        return self.voidresp()
432
433    def storbinary(self, cmd, fp, blocksize=8192, callback=None):
434        """Store a file in binary mode.  A new port is created for you.
435
436        Args:
437          cmd: A STOR command.
438          fp: A file-like object with a read(num_bytes) method.
439          blocksize: The maximum data size to read from fp and send over
440                     the connection at once.  [default: 8192]
441          callback: An optional single parameter callable that is called on
442                    on each block of data after it is sent.  [default: None]
443
444        Returns:
445          The response code.
446        """
447        self.voidcmd('TYPE I')
448        conn = self.transfercmd(cmd)
449        while 1:
450            buf = fp.read(blocksize)
451            if not buf: break
452            conn.sendall(buf)
453            if callback: callback(buf)
454        conn.close()
455        return self.voidresp()
456
457    def storlines(self, cmd, fp, callback=None):
458        """Store a file in line mode.  A new port is created for you.
459
460        Args:
461          cmd: A STOR command.
462          fp: A file-like object with a readline() method.
463          callback: An optional single parameter callable that is called on
464                    on each line after it is sent.  [default: None]
465
466        Returns:
467          The response code.
468        """
469        self.voidcmd('TYPE A')
470        conn = self.transfercmd(cmd)
471        while 1:
472            buf = fp.readline()
473            if not buf: break
474            if buf[-2:] != CRLF:
475                if buf[-1] in CRLF: buf = buf[:-1]
476                buf = buf + CRLF
477            conn.sendall(buf)
478            if callback: callback(buf)
479        conn.close()
480        return self.voidresp()
481
482    def acct(self, password):
483        '''Send new account name.'''
484        cmd = 'ACCT ' + password
485        return self.voidcmd(cmd)
486
487    def nlst(self, *args):
488        '''Return a list of files in a given directory (default the current).'''
489        cmd = 'NLST'
490        for arg in args:
491            cmd = cmd + (' ' + arg)
492        files = []
493        self.retrlines(cmd, files.append)
494        return files
495
496    def dir(self, *args):
497        '''List a directory in long form.
498        By default list current directory to stdout.
499        Optional last argument is callback function; all
500        non-empty arguments before it are concatenated to the
501        LIST command.  (This *should* only be used for a pathname.)'''
502        cmd = 'LIST'
503        func = None
504        if args[-1:] and type(args[-1]) != type(''):
505            args, func = args[:-1], args[-1]
506        for arg in args:
507            if arg:
508                cmd = cmd + (' ' + arg)
509        self.retrlines(cmd, func)
510
511    def rename(self, fromname, toname):
512        '''Rename a file.'''
513        resp = self.sendcmd('RNFR ' + fromname)
514        if resp[0] != '3':
515            raise error_reply, resp
516        return self.voidcmd('RNTO ' + toname)
517
518    def delete(self, filename):
519        '''Delete a file.'''
520        resp = self.sendcmd('DELE ' + filename)
521        if resp[:3] in ('250', '200'):
522            return resp
523        else:
524            raise error_reply, resp
525
526    def cwd(self, dirname):
527        '''Change to a directory.'''
528        if dirname == '..':
529            try:
530                return self.voidcmd('CDUP')
531            except error_perm, msg:
532                if msg.args[0][:3] != '500':
533                    raise
534        elif dirname == '':
535            dirname = '.'  # does nothing, but could return error
536        cmd = 'CWD ' + dirname
537        return self.voidcmd(cmd)
538
539    def size(self, filename):
540        '''Retrieve the size of a file.'''
541        # The SIZE command is defined in RFC-3659
542        resp = self.sendcmd('SIZE ' + filename)
543        if resp[:3] == '213':
544            s = resp[3:].strip()
545            try:
546                return int(s)
547            except (OverflowError, ValueError):
548                return long(s)
549
550    def mkd(self, dirname):
551        '''Make a directory, return its full pathname.'''
552        resp = self.sendcmd('MKD ' + dirname)
553        return parse257(resp)
554
555    def rmd(self, dirname):
556        '''Remove a directory.'''
557        return self.voidcmd('RMD ' + dirname)
558
559    def pwd(self):
560        '''Return current working directory.'''
561        resp = self.sendcmd('PWD')
562        return parse257(resp)
563
564    def quit(self):
565        '''Quit, and close the connection.'''
566        resp = self.voidcmd('QUIT')
567        self.close()
568        return resp
569
570    def close(self):
571        '''Close the connection without assuming anything about it.'''
572        if self.file:
573            self.file.close()
574            self.sock.close()
575            self.file = self.sock = None
576
577
578_150_re = None
579
580def parse150(resp):
581    '''Parse the '150' response for a RETR request.
582    Returns the expected transfer size or None; size is not guaranteed to
583    be present in the 150 message.
584    '''
585    if resp[:3] != '150':
586        raise error_reply, resp
587    global _150_re
588    if _150_re is None:
589        import re
590        _150_re = re.compile("150 .* \((\d+) bytes\)", re.IGNORECASE)
591    m = _150_re.match(resp)
592    if not m:
593        return None
594    s = m.group(1)
595    try:
596        return int(s)
597    except (OverflowError, ValueError):
598        return long(s)
599
600
601_227_re = None
602
603def parse227(resp):
604    '''Parse the '227' response for a PASV request.
605    Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
606    Return ('host.addr.as.numbers', port#) tuple.'''
607
608    if resp[:3] != '227':
609        raise error_reply, resp
610    global _227_re
611    if _227_re is None:
612        import re
613        _227_re = re.compile(r'(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)')
614    m = _227_re.search(resp)
615    if not m:
616        raise error_proto, resp
617    numbers = m.groups()
618    host = '.'.join(numbers[:4])
619    port = (int(numbers[4]) << 8) + int(numbers[5])
620    return host, port
621
622
623def parse229(resp, peer):
624    '''Parse the '229' response for a EPSV request.
625    Raises error_proto if it does not contain '(|||port|)'
626    Return ('host.addr.as.numbers', port#) tuple.'''
627
628    if resp[:3] != '229':
629        raise error_reply, resp
630    left = resp.find('(')
631    if left < 0: raise error_proto, resp
632    right = resp.find(')', left + 1)
633    if right < 0:
634        raise error_proto, resp # should contain '(|||port|)'
635    if resp[left + 1] != resp[right - 1]:
636        raise error_proto, resp
637    parts = resp[left + 1:right].split(resp[left+1])
638    if len(parts) != 5:
639        raise error_proto, resp
640    host = peer[0]
641    port = int(parts[3])
642    return host, port
643
644
645def parse257(resp):
646    '''Parse the '257' response for a MKD or PWD request.
647    This is a response to a MKD or PWD request: a directory name.
648    Returns the directoryname in the 257 reply.'''
649
650    if resp[:3] != '257':
651        raise error_reply, resp
652    if resp[3:5] != ' "':
653        return '' # Not compliant to RFC 959, but UNIX ftpd does this
654    dirname = ''
655    i = 5
656    n = len(resp)
657    while i < n:
658        c = resp[i]
659        i = i+1
660        if c == '"':
661            if i >= n or resp[i] != '"':
662                break
663            i = i+1
664        dirname = dirname + c
665    return dirname
666
667
668def print_line(line):
669    '''Default retrlines callback to print a line.'''
670    print line
671
672
673def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
674    '''Copy file from one FTP-instance to another.'''
675    if not targetname: targetname = sourcename
676    type = 'TYPE ' + type
677    source.voidcmd(type)
678    target.voidcmd(type)
679    sourcehost, sourceport = parse227(source.sendcmd('PASV'))
680    target.sendport(sourcehost, sourceport)
681    # RFC 959: the user must "listen" [...] BEFORE sending the
682    # transfer request.
683    # So: STOR before RETR, because here the target is a "user".
684    treply = target.sendcmd('STOR ' + targetname)
685    if treply[:3] not in ('125', '150'): raise error_proto  # RFC 959
686    sreply = source.sendcmd('RETR ' + sourcename)
687    if sreply[:3] not in ('125', '150'): raise error_proto  # RFC 959
688    source.voidresp()
689    target.voidresp()
690
691
692class Netrc:
693    """Class to parse & provide access to 'netrc' format files.
694
695    See the netrc(4) man page for information on the file format.
696
697    WARNING: This class is obsolete -- use module netrc instead.
698
699    """
700    __defuser = None
701    __defpasswd = None
702    __defacct = None
703
704    def __init__(self, filename=None):
705        if filename is None:
706            if "HOME" in os.environ:
707                filename = os.path.join(os.environ["HOME"],
708                                        ".netrc")
709            else:
710                raise IOError, \
711                      "specify file to load or set $HOME"
712        self.__hosts = {}
713        self.__macros = {}
714        fp = open(filename, "r")
715        in_macro = 0
716        while 1:
717            line = fp.readline()
718            if not line: break
719            if in_macro and line.strip():
720                macro_lines.append(line)
721                continue
722            elif in_macro:
723                self.__macros[macro_name] = tuple(macro_lines)
724                in_macro = 0
725            words = line.split()
726            host = user = passwd = acct = None
727            default = 0
728            i = 0
729            while i < len(words):
730                w1 = words[i]
731                if i+1 < len(words):
732                    w2 = words[i + 1]
733                else:
734                    w2 = None
735                if w1 == 'default':
736                    default = 1
737                elif w1 == 'machine' and w2:
738                    host = w2.lower()
739                    i = i + 1
740                elif w1 == 'login' and w2:
741                    user = w2
742                    i = i + 1
743                elif w1 == 'password' and w2:
744                    passwd = w2
745                    i = i + 1
746                elif w1 == 'account' and w2:
747                    acct = w2
748                    i = i + 1
749                elif w1 == 'macdef' and w2:
750                    macro_name = w2
751                    macro_lines = []
752                    in_macro = 1
753                    break
754                i = i + 1
755            if default:
756                self.__defuser = user or self.__defuser
757                self.__defpasswd = passwd or self.__defpasswd
758                self.__defacct = acct or self.__defacct
759            if host:
760                if host in self.__hosts:
761                    ouser, opasswd, oacct = \
762                           self.__hosts[host]
763                    user = user or ouser
764                    passwd = passwd or opasswd
765                    acct = acct or oacct
766                self.__hosts[host] = user, passwd, acct
767        fp.close()
768
769    def get_hosts(self):
770        """Return a list of hosts mentioned in the .netrc file."""
771        return self.__hosts.keys()
772
773    def get_account(self, host):
774        """Returns login information for the named host.
775
776        The return value is a triple containing userid,
777        password, and the accounting field.
778
779        """
780        host = host.lower()
781        user = passwd = acct = None
782        if host in self.__hosts:
783            user, passwd, acct = self.__hosts[host]
784        user = user or self.__defuser
785        passwd = passwd or self.__defpasswd
786        acct = acct or self.__defacct
787        return user, passwd, acct
788
789    def get_macros(self):
790        """Return a list of all defined macro names."""
791        return self.__macros.keys()
792
793    def get_macro(self, macro):
794        """Return a sequence of lines which define a named macro."""
795        return self.__macros[macro]
796
797
798
799def test():
800    '''Test program.
801    Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...
802
803    -d dir
804    -l list
805    -p password
806    '''
807
808    if len(sys.argv) < 2:
809        print test.__doc__
810        sys.exit(0)
811
812    debugging = 0
813    rcfile = None
814    while sys.argv[1] == '-d':
815        debugging = debugging+1
816        del sys.argv[1]
817    if sys.argv[1][:2] == '-r':
818        # get name of alternate ~/.netrc file:
819        rcfile = sys.argv[1][2:]
820        del sys.argv[1]
821    host = sys.argv[1]
822    ftp = FTP(host)
823    ftp.set_debuglevel(debugging)
824    userid = passwd = acct = ''
825    try:
826        netrc = Netrc(rcfile)
827    except IOError:
828        if rcfile is not None:
829            sys.stderr.write("Could not open account file"
830                             " -- using anonymous login.")
831    else:
832        try:
833            userid, passwd, acct = netrc.get_account(host)
834        except KeyError:
835            # no account for host
836            sys.stderr.write(
837                    "No account -- using anonymous login.")
838    ftp.login(userid, passwd, acct)
839    for file in sys.argv[2:]:
840        if file[:2] == '-l':
841            ftp.dir(file[2:])
842        elif file[:2] == '-d':
843            cmd = 'CWD'
844            if file[2:]: cmd = cmd + ' ' + file[2:]
845            resp = ftp.sendcmd(cmd)
846        elif file == '-p':
847            ftp.set_pasv(not ftp.passiveserver)
848        else:
849            ftp.retrbinary('RETR ' + file, \
850                           sys.stdout.write, 1024)
851    ftp.quit()
852
853
854if __name__ == '__main__':
855    test()