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