• 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 1:
438                data = conn.recv(blocksize)
439                if not data:
440                    break
441                callback(data)
442            # shutdown ssl layer
443            if _SSLSocket is not None and isinstance(conn, _SSLSocket):
444                conn.unwrap()
445        return self.voidresp()
446
447    def retrlines(self, cmd, callback = None):
448        """Retrieve data in line mode.  A new port is created for you.
449
450        Args:
451          cmd: A RETR, LIST, or NLST command.
452          callback: An optional single parameter callable that is called
453                    for each line with the trailing CRLF stripped.
454                    [default: print_line()]
455
456        Returns:
457          The response code.
458        """
459        if callback is None:
460            callback = print_line
461        resp = self.sendcmd('TYPE A')
462        with self.transfercmd(cmd) as conn, \
463                 conn.makefile('r', encoding=self.encoding) as fp:
464            while 1:
465                line = fp.readline(self.maxline + 1)
466                if len(line) > self.maxline:
467                    raise Error("got more than %d bytes" % self.maxline)
468                if self.debugging > 2:
469                    print('*retr*', repr(line))
470                if not line:
471                    break
472                if line[-2:] == CRLF:
473                    line = line[:-2]
474                elif line[-1:] == '\n':
475                    line = line[:-1]
476                callback(line)
477            # shutdown ssl layer
478            if _SSLSocket is not None and isinstance(conn, _SSLSocket):
479                conn.unwrap()
480        return self.voidresp()
481
482    def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
483        """Store a file in binary mode.  A new port is created for you.
484
485        Args:
486          cmd: A STOR command.
487          fp: A file-like object with a read(num_bytes) method.
488          blocksize: The maximum data size to read from fp and send over
489                     the connection at once.  [default: 8192]
490          callback: An optional single parameter callable that is called on
491                    each block of data after it is sent.  [default: None]
492          rest: Passed to transfercmd().  [default: None]
493
494        Returns:
495          The response code.
496        """
497        self.voidcmd('TYPE I')
498        with self.transfercmd(cmd, rest) as conn:
499            while 1:
500                buf = fp.read(blocksize)
501                if not buf:
502                    break
503                conn.sendall(buf)
504                if callback:
505                    callback(buf)
506            # shutdown ssl layer
507            if _SSLSocket is not None and isinstance(conn, _SSLSocket):
508                conn.unwrap()
509        return self.voidresp()
510
511    def storlines(self, cmd, fp, callback=None):
512        """Store a file in line mode.  A new port is created for you.
513
514        Args:
515          cmd: A STOR command.
516          fp: A file-like object with a readline() method.
517          callback: An optional single parameter callable that is called on
518                    each line after it is sent.  [default: None]
519
520        Returns:
521          The response code.
522        """
523        self.voidcmd('TYPE A')
524        with self.transfercmd(cmd) as conn:
525            while 1:
526                buf = fp.readline(self.maxline + 1)
527                if len(buf) > self.maxline:
528                    raise Error("got more than %d bytes" % self.maxline)
529                if not buf:
530                    break
531                if buf[-2:] != B_CRLF:
532                    if buf[-1] in B_CRLF: buf = buf[:-1]
533                    buf = buf + B_CRLF
534                conn.sendall(buf)
535                if callback:
536                    callback(buf)
537            # shutdown ssl layer
538            if _SSLSocket is not None and isinstance(conn, _SSLSocket):
539                conn.unwrap()
540        return self.voidresp()
541
542    def acct(self, password):
543        '''Send new account name.'''
544        cmd = 'ACCT ' + password
545        return self.voidcmd(cmd)
546
547    def nlst(self, *args):
548        '''Return a list of files in a given directory (default the current).'''
549        cmd = 'NLST'
550        for arg in args:
551            cmd = cmd + (' ' + arg)
552        files = []
553        self.retrlines(cmd, files.append)
554        return files
555
556    def dir(self, *args):
557        '''List a directory in long form.
558        By default list current directory to stdout.
559        Optional last argument is callback function; all
560        non-empty arguments before it are concatenated to the
561        LIST command.  (This *should* only be used for a pathname.)'''
562        cmd = 'LIST'
563        func = None
564        if args[-1:] and type(args[-1]) != type(''):
565            args, func = args[:-1], args[-1]
566        for arg in args:
567            if arg:
568                cmd = cmd + (' ' + arg)
569        self.retrlines(cmd, func)
570
571    def mlsd(self, path="", facts=[]):
572        '''List a directory in a standardized format by using MLSD
573        command (RFC-3659). If path is omitted the current directory
574        is assumed. "facts" is a list of strings representing the type
575        of information desired (e.g. ["type", "size", "perm"]).
576
577        Return a generator object yielding a tuple of two elements
578        for every file found in path.
579        First element is the file name, the second one is a dictionary
580        including a variable number of "facts" depending on the server
581        and whether "facts" argument has been provided.
582        '''
583        if facts:
584            self.sendcmd("OPTS MLST " + ";".join(facts) + ";")
585        if path:
586            cmd = "MLSD %s" % path
587        else:
588            cmd = "MLSD"
589        lines = []
590        self.retrlines(cmd, lines.append)
591        for line in lines:
592            facts_found, _, name = line.rstrip(CRLF).partition(' ')
593            entry = {}
594            for fact in facts_found[:-1].split(";"):
595                key, _, value = fact.partition("=")
596                entry[key.lower()] = value
597            yield (name, entry)
598
599    def rename(self, fromname, toname):
600        '''Rename a file.'''
601        resp = self.sendcmd('RNFR ' + fromname)
602        if resp[0] != '3':
603            raise error_reply(resp)
604        return self.voidcmd('RNTO ' + toname)
605
606    def delete(self, filename):
607        '''Delete a file.'''
608        resp = self.sendcmd('DELE ' + filename)
609        if resp[:3] in {'250', '200'}:
610            return resp
611        else:
612            raise error_reply(resp)
613
614    def cwd(self, dirname):
615        '''Change to a directory.'''
616        if dirname == '..':
617            try:
618                return self.voidcmd('CDUP')
619            except error_perm as msg:
620                if msg.args[0][:3] != '500':
621                    raise
622        elif dirname == '':
623            dirname = '.'  # does nothing, but could return error
624        cmd = 'CWD ' + dirname
625        return self.voidcmd(cmd)
626
627    def size(self, filename):
628        '''Retrieve the size of a file.'''
629        # The SIZE command is defined in RFC-3659
630        resp = self.sendcmd('SIZE ' + filename)
631        if resp[:3] == '213':
632            s = resp[3:].strip()
633            return int(s)
634
635    def mkd(self, dirname):
636        '''Make a directory, return its full pathname.'''
637        resp = self.voidcmd('MKD ' + dirname)
638        # fix around non-compliant implementations such as IIS shipped
639        # with Windows server 2003
640        if not resp.startswith('257'):
641            return ''
642        return parse257(resp)
643
644    def rmd(self, dirname):
645        '''Remove a directory.'''
646        return self.voidcmd('RMD ' + dirname)
647
648    def pwd(self):
649        '''Return current working directory.'''
650        resp = self.voidcmd('PWD')
651        # fix around non-compliant implementations such as IIS shipped
652        # with Windows server 2003
653        if not resp.startswith('257'):
654            return ''
655        return parse257(resp)
656
657    def quit(self):
658        '''Quit, and close the connection.'''
659        resp = self.voidcmd('QUIT')
660        self.close()
661        return resp
662
663    def close(self):
664        '''Close the connection without assuming anything about it.'''
665        try:
666            file = self.file
667            self.file = None
668            if file is not None:
669                file.close()
670        finally:
671            sock = self.sock
672            self.sock = None
673            if sock is not None:
674                sock.close()
675
676try:
677    import ssl
678except ImportError:
679    _SSLSocket = None
680else:
681    _SSLSocket = ssl.SSLSocket
682
683    class FTP_TLS(FTP):
684        '''A FTP subclass which adds TLS support to FTP as described
685        in RFC-4217.
686
687        Connect as usual to port 21 implicitly securing the FTP control
688        connection before authenticating.
689
690        Securing the data connection requires user to explicitly ask
691        for it by calling prot_p() method.
692
693        Usage example:
694        >>> from ftplib import FTP_TLS
695        >>> ftps = FTP_TLS('ftp.python.org')
696        >>> ftps.login()  # login anonymously previously securing control channel
697        '230 Guest login ok, access restrictions apply.'
698        >>> ftps.prot_p()  # switch to secure data connection
699        '200 Protection level set to P'
700        >>> ftps.retrlines('LIST')  # list directory content securely
701        total 9
702        drwxr-xr-x   8 root     wheel        1024 Jan  3  1994 .
703        drwxr-xr-x   8 root     wheel        1024 Jan  3  1994 ..
704        drwxr-xr-x   2 root     wheel        1024 Jan  3  1994 bin
705        drwxr-xr-x   2 root     wheel        1024 Jan  3  1994 etc
706        d-wxrwxr-x   2 ftp      wheel        1024 Sep  5 13:43 incoming
707        drwxr-xr-x   2 root     wheel        1024 Nov 17  1993 lib
708        drwxr-xr-x   6 1094     wheel        1024 Sep 13 19:07 pub
709        drwxr-xr-x   3 root     wheel        1024 Jan  3  1994 usr
710        -rw-r--r--   1 root     root          312 Aug  1  1994 welcome.msg
711        '226 Transfer complete.'
712        >>> ftps.quit()
713        '221 Goodbye.'
714        >>>
715        '''
716        ssl_version = ssl.PROTOCOL_TLS_CLIENT
717
718        def __init__(self, host='', user='', passwd='', acct='',
719                     keyfile=None, certfile=None, context=None,
720                     timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None, *,
721                     encoding='utf-8'):
722            if context is not None and keyfile is not None:
723                raise ValueError("context and keyfile arguments are mutually "
724                                 "exclusive")
725            if context is not None and certfile is not None:
726                raise ValueError("context and certfile arguments are mutually "
727                                 "exclusive")
728            if keyfile is not None or certfile is not None:
729                import warnings
730                warnings.warn("keyfile and certfile are deprecated, use a "
731                              "custom context instead", DeprecationWarning, 2)
732            self.keyfile = keyfile
733            self.certfile = certfile
734            if context is None:
735                context = ssl._create_stdlib_context(self.ssl_version,
736                                                     certfile=certfile,
737                                                     keyfile=keyfile)
738            self.context = context
739            self._prot_p = False
740            super().__init__(host, user, passwd, acct,
741                             timeout, source_address, encoding=encoding)
742
743        def login(self, user='', passwd='', acct='', secure=True):
744            if secure and not isinstance(self.sock, ssl.SSLSocket):
745                self.auth()
746            return super().login(user, passwd, acct)
747
748        def auth(self):
749            '''Set up secure control connection by using TLS/SSL.'''
750            if isinstance(self.sock, ssl.SSLSocket):
751                raise ValueError("Already using TLS")
752            if self.ssl_version >= ssl.PROTOCOL_TLS:
753                resp = self.voidcmd('AUTH TLS')
754            else:
755                resp = self.voidcmd('AUTH SSL')
756            self.sock = self.context.wrap_socket(self.sock, server_hostname=self.host)
757            self.file = self.sock.makefile(mode='r', encoding=self.encoding)
758            return resp
759
760        def ccc(self):
761            '''Switch back to a clear-text control connection.'''
762            if not isinstance(self.sock, ssl.SSLSocket):
763                raise ValueError("not using TLS")
764            resp = self.voidcmd('CCC')
765            self.sock = self.sock.unwrap()
766            return resp
767
768        def prot_p(self):
769            '''Set up secure data connection.'''
770            # PROT defines whether or not the data channel is to be protected.
771            # Though RFC-2228 defines four possible protection levels,
772            # RFC-4217 only recommends two, Clear and Private.
773            # Clear (PROT C) means that no security is to be used on the
774            # data-channel, Private (PROT P) means that the data-channel
775            # should be protected by TLS.
776            # PBSZ command MUST still be issued, but must have a parameter of
777            # '0' to indicate that no buffering is taking place and the data
778            # connection should not be encapsulated.
779            self.voidcmd('PBSZ 0')
780            resp = self.voidcmd('PROT P')
781            self._prot_p = True
782            return resp
783
784        def prot_c(self):
785            '''Set up clear text data connection.'''
786            resp = self.voidcmd('PROT C')
787            self._prot_p = False
788            return resp
789
790        # --- Overridden FTP methods
791
792        def ntransfercmd(self, cmd, rest=None):
793            conn, size = super().ntransfercmd(cmd, rest)
794            if self._prot_p:
795                conn = self.context.wrap_socket(conn,
796                                                server_hostname=self.host)
797            return conn, size
798
799        def abort(self):
800            # overridden as we can't pass MSG_OOB flag to sendall()
801            line = b'ABOR' + B_CRLF
802            self.sock.sendall(line)
803            resp = self.getmultiline()
804            if resp[:3] not in {'426', '225', '226'}:
805                raise error_proto(resp)
806            return resp
807
808    __all__.append('FTP_TLS')
809    all_errors = (Error, OSError, EOFError, ssl.SSLError)
810
811
812_150_re = None
813
814def parse150(resp):
815    '''Parse the '150' response for a RETR request.
816    Returns the expected transfer size or None; size is not guaranteed to
817    be present in the 150 message.
818    '''
819    if resp[:3] != '150':
820        raise error_reply(resp)
821    global _150_re
822    if _150_re is None:
823        import re
824        _150_re = re.compile(
825            r"150 .* \((\d+) bytes\)", re.IGNORECASE | re.ASCII)
826    m = _150_re.match(resp)
827    if not m:
828        return None
829    return int(m.group(1))
830
831
832_227_re = None
833
834def parse227(resp):
835    '''Parse the '227' response for a PASV request.
836    Raises error_proto if it does not contain '(h1,h2,h3,h4,p1,p2)'
837    Return ('host.addr.as.numbers', port#) tuple.'''
838    if resp[:3] != '227':
839        raise error_reply(resp)
840    global _227_re
841    if _227_re is None:
842        import re
843        _227_re = re.compile(r'(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)', re.ASCII)
844    m = _227_re.search(resp)
845    if not m:
846        raise error_proto(resp)
847    numbers = m.groups()
848    host = '.'.join(numbers[:4])
849    port = (int(numbers[4]) << 8) + int(numbers[5])
850    return host, port
851
852
853def parse229(resp, peer):
854    '''Parse the '229' response for an EPSV request.
855    Raises error_proto if it does not contain '(|||port|)'
856    Return ('host.addr.as.numbers', port#) tuple.'''
857    if resp[:3] != '229':
858        raise error_reply(resp)
859    left = resp.find('(')
860    if left < 0: raise error_proto(resp)
861    right = resp.find(')', left + 1)
862    if right < 0:
863        raise error_proto(resp) # should contain '(|||port|)'
864    if resp[left + 1] != resp[right - 1]:
865        raise error_proto(resp)
866    parts = resp[left + 1:right].split(resp[left+1])
867    if len(parts) != 5:
868        raise error_proto(resp)
869    host = peer[0]
870    port = int(parts[3])
871    return host, port
872
873
874def parse257(resp):
875    '''Parse the '257' response for a MKD or PWD request.
876    This is a response to a MKD or PWD request: a directory name.
877    Returns the directoryname in the 257 reply.'''
878    if resp[:3] != '257':
879        raise error_reply(resp)
880    if resp[3:5] != ' "':
881        return '' # Not compliant to RFC 959, but UNIX ftpd does this
882    dirname = ''
883    i = 5
884    n = len(resp)
885    while i < n:
886        c = resp[i]
887        i = i+1
888        if c == '"':
889            if i >= n or resp[i] != '"':
890                break
891            i = i+1
892        dirname = dirname + c
893    return dirname
894
895
896def print_line(line):
897    '''Default retrlines callback to print a line.'''
898    print(line)
899
900
901def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
902    '''Copy file from one FTP-instance to another.'''
903    if not targetname:
904        targetname = sourcename
905    type = 'TYPE ' + type
906    source.voidcmd(type)
907    target.voidcmd(type)
908    sourcehost, sourceport = parse227(source.sendcmd('PASV'))
909    target.sendport(sourcehost, sourceport)
910    # RFC 959: the user must "listen" [...] BEFORE sending the
911    # transfer request.
912    # So: STOR before RETR, because here the target is a "user".
913    treply = target.sendcmd('STOR ' + targetname)
914    if treply[:3] not in {'125', '150'}:
915        raise error_proto  # RFC 959
916    sreply = source.sendcmd('RETR ' + sourcename)
917    if sreply[:3] not in {'125', '150'}:
918        raise error_proto  # RFC 959
919    source.voidresp()
920    target.voidresp()
921
922
923def test():
924    '''Test program.
925    Usage: ftp [-d] [-r[file]] host [-l[dir]] [-d[dir]] [-p] [file] ...
926
927    -d dir
928    -l list
929    -p password
930    '''
931
932    if len(sys.argv) < 2:
933        print(test.__doc__)
934        sys.exit(0)
935
936    import netrc
937
938    debugging = 0
939    rcfile = None
940    while sys.argv[1] == '-d':
941        debugging = debugging+1
942        del sys.argv[1]
943    if sys.argv[1][:2] == '-r':
944        # get name of alternate ~/.netrc file:
945        rcfile = sys.argv[1][2:]
946        del sys.argv[1]
947    host = sys.argv[1]
948    ftp = FTP(host)
949    ftp.set_debuglevel(debugging)
950    userid = passwd = acct = ''
951    try:
952        netrcobj = netrc.netrc(rcfile)
953    except OSError:
954        if rcfile is not None:
955            sys.stderr.write("Could not open account file"
956                             " -- using anonymous login.")
957    else:
958        try:
959            userid, acct, passwd = netrcobj.authenticators(host)
960        except KeyError:
961            # no account for host
962            sys.stderr.write(
963                    "No account -- using anonymous login.")
964    ftp.login(userid, passwd, acct)
965    for file in sys.argv[2:]:
966        if file[:2] == '-l':
967            ftp.dir(file[2:])
968        elif file[:2] == '-d':
969            cmd = 'CWD'
970            if file[2:]: cmd = cmd + ' ' + file[2:]
971            resp = ftp.sendcmd(cmd)
972        elif file == '-p':
973            ftp.set_pasv(not ftp.passiveserver)
974        else:
975            ftp.retrbinary('RETR ' + file, \
976                           sys.stdout.write, 1024)
977    ftp.quit()
978
979
980if __name__ == '__main__':
981    test()
982