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