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