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