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