an ftp client class and some helper functions based on rfc 959 file transfer protocol ftp by j postel and j reynolds example from ftplib import ftp ftp ftp ftp python org connect to host default port ftp login default i e user anonymous passwd anonymous 230 guest login ok access restrictions apply ftp retrlines list list directory contents total 9 drwxrxrx 8 root wheel 1024 jan 3 1994 drwxrxrx 8 root wheel 1024 jan 3 1994 drwxrxrx 2 root wheel 1024 jan 3 1994 bin drwxrxrx 2 root wheel 1024 jan 3 1994 etc dwxrwxrx 2 ftp wheel 1024 sep 5 13 43 incoming drwxrxrx 2 root wheel 1024 nov 17 1993 lib drwxrxrx 6 1094 wheel 1024 sep 13 19 07 pub drwxrxrx 3 root wheel 1024 jan 3 1994 usr rwrr 1 root root 312 aug 1 1994 welcome msg 226 transfer complete ftp quit 221 goodbye a nice test that reveals some of the network dialogue would be python ftplib py d localhost l p l changes and improvements suggested by steve majewski modified by jack to work on the mac modified by siebren to support docstrings and pasv modified by phil schwartz to add storbinary and storlines callbacks modified by giampaolo rodola to add tls support magic number from socket h the standard ftp server control port the sizehint parameter passed to readline calls exception raised when an error or invalid response is received all exceptions hopefully that may be raised here and that aren t always programming errors on our side line terminators we always output crlf but accept any of crlf cr lf the class itself an ftp client class to create a connection call the class using these arguments host user passwd acct timeout sourceaddress encoding the first four arguments are all strings and have default value the parameter timeout must be numeric and defaults to none if not passed meaning that no timeout will be set on any ftp sockets if a timeout is passed then this is now the default timeout for all ftp socket operations for this instance the last parameter is the encoding of filenames which defaults to utf8 then use self connect with optional host and port argument to download a file use ftp retrlines retr filename or ftp retrbinary with slightly different arguments to upload a file use ftp storlines or ftp storbinary which have an open file as argument see their definitions below for details the downloadupload functions first issue appropriate type and port or pasv commands disables https bugs python orgissue43285 security if set to true initialization method called by class instantiation initialize host to localhost port to standard ftp port optional arguments are host for connect and user passwd acct for login context management protocol try to quit if active connect to host arguments are host hostname to connect to string default previous host port port to connect to integer default previous port timeout the timeout to set against the ftp sockets sourceaddress a 2tuple host port for the socket to bind to as its source address before connecting get the welcome message from the server this is read and squirreled away by connect if self debugging print welcome self sanitizeself welcome return self welcome def setdebuglevelself level use passive or active mode for data transfers with a false argument use the normal port mode with a true argument use the pasv command self passiveserver val internal sanitize a string for printing def sanitizeself s if s 5 in pass pass i lens rstrip rn s s 5 i5 si return reprs internal send one line to the server appending crlf def putlineself line if r in line or n in line raise valueerror an illegal newline character should not be contained sys auditftplib sendcmd self line line line crlf if self debugging 1 print put self sanitizeline self sock sendallline encodeself encoding internal send one command to the server through putline def putcmdself line if self debugging print cmd self sanitizeline self putlineline internal return one line from the server stripping crlf raise eoferror if the connection is closed def getlineself line self file readlineself maxline 1 if lenline self maxline raise errorgot more than d bytes self maxline if self debugging 1 print get self sanitizeline if not line raise eoferror if line2 crlf line line 2 elif line1 in crlf line line 1 return line internal get a response from the server which may possibly consist of multiple lines return a single string with no trailing crlf if the response consists of multiple lines these are separated by n characters in the string def getmultilineself line self getline if line3 4 code line 3 while 1 nextline self getline line line n nextline if nextline 3 code and nextline3 4 break return line internal get a response from the server raise various errors if the response indicates an error def getrespself resp self getmultiline if self debugging print resp self sanitizeresp self lastresp resp 3 c resp 1 if c in 1 2 3 return resp if c 4 raise errortempresp if c 5 raise errorpermresp raise errorprotoresp def voidrespself abort a file transfer uses outofband data this does not follow the procedure from the rfc to send telnet ip and synch that doesn t seem to work with the servers i ve tried instead just send the abor command as oob data line b abor bcrlf if self debugging 1 print put urgent self sanitizeline self sock sendallline msgoob resp self getmultiline if resp 3 not in 426 225 226 raise errorprotoresp return resp def sendcmdself cmd send a command and expect a response beginning with 2 self putcmdcmd return self voidresp def sendportself host port hbytes host split pbytes reprport256 reprport256 bytes hbytes pbytes cmd port joinbytes return self voidcmdcmd def sendeprtself host port create a new socket and send a port command for it sock socket createserver 0 familyself af backlog1 port sock getsockname1 get proper port host self sock getsockname0 get proper host if self af socket afinet resp self sendporthost port else resp self sendeprthost port if self timeout is not globaldefaulttimeout sock settimeoutself timeout return sock def makepasvself initiate a transfer over the data connection if the transfer is active send a port command and the transfer command and accept the connection if the server is passive send a pasv command connect to it and start the transfer command either way return the socket for the connection and the expected size of the transfer the expected size may be none if it could not be determined optional rest argument can be a string that is sent as the argument to a rest command this is essentially a server marker used to tell the server to skip over any data up to the given marker some servers apparently send a 200 reply to a list or stor command before the 150 reply and way before the 226 reply this seems to be in violation of the protocol which only allows 1xx or error messages for list so we just discard this response see above this is conditional in case we received a 125 like ntransfercmd but returns only the socket return self ntransfercmdcmd rest0 def loginself user passwd acct if there is no anonymous ftp password specified then we ll just use anonymous we don t send any other thing because we want to remain anonymous we want to stop spam we don t want to let ftp sites to discriminate by the user host or country retrieve data in binary mode a new port is created for you args cmd a retr command callback a single parameter callable to be called on each block of data read blocksize the maximum number of bytes to read from the socket at one time default 8192 rest passed to transfercmd default none returns the response code shutdown ssl layer retrieve data in line mode a new port is created for you args cmd a retr list or nlst command callback an optional single parameter callable that is called for each line with the trailing crlf stripped default printline returns the response code shutdown ssl layer store a file in binary mode a new port is created for you args cmd a stor command fp a filelike object with a readnumbytes method blocksize the maximum data size to read from fp and send over the connection at once default 8192 callback an optional single parameter callable that is called on each block of data after it is sent default none rest passed to transfercmd default none returns the response code shutdown ssl layer store a file in line mode a new port is created for you args cmd a stor command fp a filelike object with a readline method callback an optional single parameter callable that is called on each line after it is sent default none returns the response code shutdown ssl layer send new account name cmd acct password return self voidcmdcmd def nlstself args list a directory in long form by default list current directory to stdout optional last argument is callback function all nonempty arguments before it are concatenated to the list command this should only be used for a pathname cmd list func none if args1 and not isinstanceargs1 str args func args 1 args1 for arg in args if arg cmd cmd arg self retrlinescmd func def mlsdself path facts if facts self sendcmdopts mlst joinfacts if path cmd mlsd s path else cmd mlsd lines self retrlinescmd lines append for line in lines factsfound name line rstripcrlf partition entry for fact in factsfound 1 split key value fact partition entrykey lower value yield name entry def renameself fromname toname delete a file resp self sendcmd dele filename if resp 3 in 250 200 return resp else raise errorreplyresp def cwdself dirname retrieve the size of a file the size command is defined in rfc3659 resp self sendcmd size filename if resp 3 213 s resp3 strip return ints def mkdself dirname fix around noncompliant implementations such as iis shipped with windows server 2003 remove a directory return self voidcmd rmd dirname def pwdself fix around noncompliant implementations such as iis shipped with windows server 2003 quit and close the connection resp self voidcmd quit self close return resp def closeself a ftp subclass which adds tls support to ftp as described in rfc4217 connect as usual to port 21 implicitly securing the ftp control connection before authenticating securing the data connection requires user to explicitly ask for it by calling protp method usage example from ftplib import ftptls ftps ftptls ftp python org ftps login login anonymously previously securing control channel 230 guest login ok access restrictions apply ftps protp switch to secure data connection 200 protection level set to p ftps retrlines list list directory content securely total 9 drwxrxrx 8 root wheel 1024 jan 3 1994 drwxrxrx 8 root wheel 1024 jan 3 1994 drwxrxrx 2 root wheel 1024 jan 3 1994 bin drwxrxrx 2 root wheel 1024 jan 3 1994 etc dwxrwxrx 2 ftp wheel 1024 sep 5 13 43 incoming drwxrxrx 2 root wheel 1024 nov 17 1993 lib drwxrxrx 6 1094 wheel 1024 sep 13 19 07 pub drwxrxrx 3 root wheel 1024 jan 3 1994 usr rwrr 1 root root 312 aug 1 1994 welcome msg 226 transfer complete ftps quit 221 goodbye set up secure control connection by using tlsssl if isinstanceself sock ssl sslsocket raise valueerroralready using tls if self context protocol ssl protocoltls resp self voidcmd auth tls else resp self voidcmd auth ssl self sock self context wrapsocketself sock serverhostnameself host self file self sock makefilemode r encodingself encoding return resp def cccself set up secure data connection prot defines whether or not the data channel is to be protected though rfc2228 defines four possible protection levels rfc4217 only recommends two clear and private clear prot c means that no security is to be used on the datachannel private prot p means that the datachannel should be protected by tls pbsz command must still be issued but must have a parameter of 0 to indicate that no buffering is taking place and the data connection should not be encapsulated self voidcmd pbsz 0 resp self voidcmd prot p self protp true return resp def protcself overridden ftp methods overridden as we can t pass msgoob flag to sendall parse the 150 response for a retr request returns the expected transfer size or none size is not guaranteed to be present in the 150 message parse the 227 response for a pasv request raises errorproto if it does not contain h1 h2 h3 h4 p1 p2 return host addr as numbers port tuple if resp 3 227 raise errorreplyresp global 227re if 227re is none import re 227re re compiler d d d d d d re ascii m 227re searchresp if not m raise errorprotoresp numbers m groups host joinnumbers 4 port intnumbers4 8 intnumbers5 return host port def parse229resp peer parse the 257 response for a mkd or pwd request this is a response to a mkd or pwd request a directory name returns the directoryname in the 257 reply if resp 3 257 raise errorreplyresp if resp3 5 return not compliant to rfc 959 but unix ftpd does this dirname i 5 n lenresp while i n c respi i i1 if c if i n or respi break i i1 dirname dirname c return dirname def printlineline copy file from one ftpinstance to another if not targetname targetname sourcename type type type source voidcmdtype target voidcmdtype sourcehost sourceport parse227source sendcmd pasv target sendportsourcehost sourceport rfc 959 the user must listen before sending the transfer request so stor before retr because here the target is a user treply target sendcmd stor targetname if treply 3 not in 125 150 raise errorproto rfc 959 sreply source sendcmd retr sourcename if sreply 3 not in 125 150 raise errorproto rfc 959 source voidresp target voidresp def test if lensys argv 2 printtest doc sys exit0 import netrc debugging 0 rcfile none while sys argv1 d debugging debugging1 del sys argv1 if sys argv1 2 r get name of alternate netrc file rcfile sys argv12 del sys argv1 host sys argv1 ftp ftphost ftp setdebugleveldebugging userid passwd acct try netrcobj netrc netrcrcfile except oserror if rcfile is not none sys stderr writecould not open account file using anonymous login else try userid acct passwd netrcobj authenticatorshost except keyerror no account for host sys stderr write no account using anonymous login ftp loginuserid passwd acct for file in sys argv2 if file 2 l ftp dirfile2 elif file 2 d cmd cwd if file2 cmd cmd file2 resp ftp sendcmdcmd elif file p ftp setpasvnot ftp passiveserver else ftp retrbinary retr file sys stdout write 1024 ftp quit if name main test changes and improvements suggested by steve majewski modified by jack to work on the mac modified by siebren to support docstrings and pasv modified by phil schwartz to add storbinary and storlines callbacks modified by giampaolo rodola to add tls support magic number from socket h process data out of band the standard ftp server control port the sizehint parameter passed to readline calls exception raised when an error or invalid response is received unexpected 123 xx reply 4xx errors 5xx errors response does not begin with 1 5 all exceptions hopefully that may be raised here and that aren t always programming errors on our side line terminators we always output crlf but accept any of crlf cr lf the class itself an ftp client class to create a connection call the class using these arguments host user passwd acct timeout source_address encoding the first four arguments are all strings and have default value the parameter timeout must be numeric and defaults to none if not passed meaning that no timeout will be set on any ftp socket s if a timeout is passed then this is now the default timeout for all ftp socket operations for this instance the last parameter is the encoding of filenames which defaults to utf 8 then use self connect with optional host and port argument to download a file use ftp retrlines retr filename or ftp retrbinary with slightly different arguments to upload a file use ftp storlines or ftp storbinary which have an open file as argument see their definitions below for details the download upload functions first issue appropriate type and port or pasv commands disables https bugs python org issue43285 security if set to true initialization method called by class instantiation initialize host to localhost port to standard ftp port optional arguments are host for connect and user passwd acct for login context management protocol try to quit if active connect to host arguments are host hostname to connect to string default previous host port port to connect to integer default previous port timeout the timeout to set against the ftp socket s source_address a 2 tuple host port for the socket to bind to as its source address before connecting get the welcome message from the server this is read and squirreled away by connect set the debugging level the required argument level means 0 no debugging output default 1 print commands and responses but not body text etc 2 also print raw lines read and sent before stripping cr lf use passive or active mode for data transfers with a false argument use the normal port mode with a true argument use the pasv command internal sanitize a string for printing internal send one line to the server appending crlf internal send one command to the server through putline internal return one line from the server stripping crlf raise eoferror if the connection is closed internal get a response from the server which may possibly consist of multiple lines return a single string with no trailing crlf if the response consists of multiple lines these are separated by n characters in the string internal get a response from the server raise various errors if the response indicates an error expect a response beginning with 2 abort a file transfer uses out of band data this does not follow the procedure from the rfc to send telnet ip and synch that doesn t seem to work with the servers i ve tried instead just send the abor command as oob data send a command and return the response send a command and expect a response beginning with 2 send a port command with the current host and the given port number send an eprt command with the current host and the given port number create a new socket and send a port command for it get proper port get proper host internal does the pasv or epsv handshake address port initiate a transfer over the data connection if the transfer is active send a port command and the transfer command and accept the connection if the server is passive send a pasv command connect to it and start the transfer command either way return the socket for the connection and the expected size of the transfer the expected size may be none if it could not be determined optional rest argument can be a string that is sent as the argument to a rest command this is essentially a server marker used to tell the server to skip over any data up to the given marker some servers apparently send a 200 reply to a list or stor command before the 150 reply and way before the 226 reply this seems to be in violation of the protocol which only allows 1xx or error messages for list so we just discard this response see above this is conditional in case we received a 125 like ntransfercmd but returns only the socket login default anonymous if there is no anonymous ftp password specified then we ll just use anonymous we don t send any other thing because we want to remain anonymous we want to stop spam we don t want to let ftp sites to discriminate by the user host or country retrieve data in binary mode a new port is created for you args cmd a retr command callback a single parameter callable to be called on each block of data read blocksize the maximum number of bytes to read from the socket at one time default 8192 rest passed to transfercmd default none returns the response code shutdown ssl layer retrieve data in line mode a new port is created for you args cmd a retr list or nlst command callback an optional single parameter callable that is called for each line with the trailing crlf stripped default print_line returns the response code shutdown ssl layer store a file in binary mode a new port is created for you args cmd a stor command fp a file like object with a read num_bytes method blocksize the maximum data size to read from fp and send over the connection at once default 8192 callback an optional single parameter callable that is called on each block of data after it is sent default none rest passed to transfercmd default none returns the response code shutdown ssl layer store a file in line mode a new port is created for you args cmd a stor command fp a file like object with a readline method callback an optional single parameter callable that is called on each line after it is sent default none returns the response code shutdown ssl layer send new account name return a list of files in a given directory default the current list a directory in long form by default list current directory to stdout optional last argument is callback function all non empty arguments before it are concatenated to the list command this should only be used for a pathname list a directory in a standardized format by using mlsd command rfc 3659 if path is omitted the current directory is assumed facts is a list of strings representing the type of information desired e g type size perm return a generator object yielding a tuple of two elements for every file found in path first element is the file name the second one is a dictionary including a variable number of facts depending on the server and whether facts argument has been provided rename a file delete a file change to a directory does nothing but could return error retrieve the size of a file the size command is defined in rfc 3659 make a directory return its full pathname fix around non compliant implementations such as iis shipped with windows server 2003 remove a directory return current working directory fix around non compliant implementations such as iis shipped with windows server 2003 quit and close the connection close the connection without assuming anything about it a ftp subclass which adds tls support to ftp as described in rfc 4217 connect as usual to port 21 implicitly securing the ftp control connection before authenticating securing the data connection requires user to explicitly ask for it by calling prot_p method usage example from ftplib import ftp_tls ftps ftp_tls ftp python org ftps login login anonymously previously securing control channel 230 guest login ok access restrictions apply ftps prot_p switch to secure data connection 200 protection level set to p ftps retrlines list list directory content securely total 9 drwxr xr x 8 root wheel 1024 jan 3 1994 drwxr xr x 8 root wheel 1024 jan 3 1994 drwxr xr x 2 root wheel 1024 jan 3 1994 bin drwxr xr x 2 root wheel 1024 jan 3 1994 etc d wxrwxr x 2 ftp wheel 1024 sep 5 13 43 incoming drwxr xr x 2 root wheel 1024 nov 17 1993 lib drwxr xr x 6 1094 wheel 1024 sep 13 19 07 pub drwxr xr x 3 root wheel 1024 jan 3 1994 usr rw r r 1 root root 312 aug 1 1994 welcome msg 226 transfer complete ftps quit 221 goodbye set up secure control connection by using tls ssl switch back to a clear text control connection set up secure data connection prot defines whether or not the data channel is to be protected though rfc 2228 defines four possible protection levels rfc 4217 only recommends two clear and private clear prot c means that no security is to be used on the data channel private prot p means that the data channel should be protected by tls pbsz command must still be issued but must have a parameter of 0 to indicate that no buffering is taking place and the data connection should not be encapsulated set up clear text data connection overridden ftp methods overridden as we can t pass msg_oob flag to sendall parse the 150 response for a retr request returns the expected transfer size or none size is not guaranteed to be present in the 150 message parse the 227 response for a pasv request raises error_proto if it does not contain h1 h2 h3 h4 p1 p2 return host addr as numbers port tuple parse the 229 response for an epsv request raises error_proto if it does not contain port return host addr as numbers port tuple should contain port parse the 257 response for a mkd or pwd request this is a response to a mkd or pwd request a directory name returns the directoryname in the 257 reply not compliant to rfc 959 but unix ftpd does this default retrlines callback to print a line copy file from one ftp instance to another rfc 959 the user must listen before sending the transfer request so stor before retr because here the target is a user rfc 959 rfc 959 test program usage ftp d r file host l dir d dir p file d dir l list p password get name of alternate netrc file no account for host
import sys
import socket
from socket import _GLOBAL_DEFAULT_TIMEOUT

__all__ = ["FTP", "error_reply", "error_temp", "error_perm", "error_proto",
           "all_errors"]


MSG_OOB = 0x1                           



FTP_PORT = 21

MAXLINE = 8192



class Error(Exception): pass
class error_reply(Error): pass          
class error_temp(Error): pass           
class error_perm(Error): pass           
class error_proto(Error): pass          




all_errors = (Error, OSError, EOFError)



CRLF = '\r\n'
B_CRLF = b'\r\n'


class FTP:
    

    debugging = 0
    host = ''
    port = FTP_PORT
    maxline = MAXLINE
    sock = None
    file = None
    welcome = None
    passiveserver = True
    
    trust_server_pasv_ipv4_address = False

    def __init__(self, host='', user='', passwd='', acct='',
                 timeout=_GLOBAL_DEFAULT_TIMEOUT, source_address=None, *,
                 encoding='utf-8'):
        
        self.encoding = encoding
        self.source_address = source_address
        self.timeout = timeout
        if host:
            self.connect(host)
            if user:
                self.login(user, passwd, acct)

    def __enter__(self):
        return self

    
    def __exit__(self, *args):
        if self.sock is not None:
            try:
                self.quit()
            except (OSError, EOFError):
                pass
            finally:
                if self.sock is not None:
                    self.close()

    def connect(self, host='', port=0, timeout=-999, source_address=None):
        
        if host != '':
            self.host = host
        if port > 0:
            self.port = port
        if timeout != -999:
            self.timeout = timeout
        if self.timeout is not None and not self.timeout:
            raise ValueError('Non-blocking socket (timeout=0) is not supported')
        if source_address is not None:
            self.source_address = source_address
        sys.audit("ftplib.connect", self, self.host, self.port)
        self.sock = socket.create_connection((self.host, self.port), self.timeout,
                                             source_address=self.source_address)
        self.af = self.sock.family
        self.file = self.sock.makefile('r', encoding=self.encoding)
        self.welcome = self.getresp()
        return self.welcome

    def getwelcome(self):
        
        if self.debugging:
            print('*welcome*', self.sanitize(self.welcome))
        return self.welcome

    def set_debuglevel(self, level):
        
        self.debugging = level
    debug = set_debuglevel

    def set_pasv(self, val):
        
        self.passiveserver = val

    
    def sanitize(self, s):
        if s[:5] in {'pass ', 'PASS '}:
            i = len(s.rstrip('\r\n'))
            s = s[:5] + '*'*(i-5) + s[i:]
        return repr(s)

    
    def putline(self, line):
        if '\r' in line or '\n' in line:
            raise ValueError('an illegal newline character should not be contained')
        sys.audit("ftplib.sendcmd", self, line)
        line = line + CRLF
        if self.debugging > 1:
            print('*put*', self.sanitize(line))
        self.sock.sendall(line.encode(self.encoding))

    
    def putcmd(self, line):
        if self.debugging: print('*cmd*', self.sanitize(line))
        self.putline(line)

    
    
    def getline(self):
        line = self.file.readline(self.maxline + 1)
        if len(line) > self.maxline:
            raise Error("got more than %d bytes" % self.maxline)
        if self.debugging > 1:
            print('*get*', self.sanitize(line))
        if not line:
            raise EOFError
        if line[-2:] == CRLF:
            line = line[:-2]
        elif line[-1:] in CRLF:
            line = line[:-1]
        return line

    
    
    
    
    def getmultiline(self):
        line = self.getline()
        if line[3:4] == '-':
            code = line[:3]
            while 1:
                nextline = self.getline()
                line = line + ('\n' + nextline)
                if nextline[:3] == code and \
                        nextline[3:4] != '-':
                    break
        return line

    
    
    def getresp(self):
        resp = self.getmultiline()
        if self.debugging:
            print('*resp*', self.sanitize(resp))
        self.lastresp = resp[:3]
        c = resp[:1]
        if c in {'1', '2', '3'}:
            return resp
        if c == '4':
            raise error_temp(resp)
        if c == '5':
            raise error_perm(resp)
        raise error_proto(resp)

    def voidresp(self):
        
        resp = self.getresp()
        if resp[:1] != '2':
            raise error_reply(resp)
        return resp

    def abort(self):
        
        line = b'ABOR' + B_CRLF
        if self.debugging > 1:
            print('*put urgent*', self.sanitize(line))
        self.sock.sendall(line, MSG_OOB)
        resp = self.getmultiline()
        if resp[:3] not in {'426', '225', '226'}:
            raise error_proto(resp)
        return resp

    def sendcmd(self, cmd):
        
        self.putcmd(cmd)
        return self.getresp()

    def voidcmd(self, cmd):
        
        self.putcmd(cmd)
        return self.voidresp()

    def sendport(self, host, port):
        
        hbytes = host.split('.')
        pbytes = [repr(port//256), repr(port%256)]
        bytes = hbytes + pbytes
        cmd = 'PORT ' + ','.join(bytes)
        return self.voidcmd(cmd)

    def sendeprt(self, host, port):
        
        af = 0
        if self.af == socket.AF_INET:
            af = 1
        if self.af == socket.AF_INET6:
            af = 2
        if af == 0:
            raise error_proto('unsupported address family')
        fields = ['', repr(af), host, repr(port), '']
        cmd = 'EPRT ' + '|'.join(fields)
        return self.voidcmd(cmd)

    def makeport(self):
        
        sock = socket.create_server(("", 0), family=self.af, backlog=1)
        port = sock.getsockname()[1] 
        host = self.sock.getsockname()[0] 
        if self.af == socket.AF_INET:
            resp = self.sendport(host, port)
        else:
            resp = self.sendeprt(host, port)
        if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT:
            sock.settimeout(self.timeout)
        return sock

    def makepasv(self):
        
        if self.af == socket.AF_INET:
            untrusted_host, port = parse227(self.sendcmd('PASV'))
            if self.trust_server_pasv_ipv4_address:
                host = untrusted_host
            else:
                host = self.sock.getpeername()[0]
        else:
            host, port = parse229(self.sendcmd('EPSV'), self.sock.getpeername())
        return host, port

    def ntransfercmd(self, cmd, rest=None):
        
        size = None
        if self.passiveserver:
            host, port = self.makepasv()
            conn = socket.create_connection((host, port), self.timeout,
                                            source_address=self.source_address)
            try:
                if rest is not None:
                    self.sendcmd("REST %s" % rest)
                resp = self.sendcmd(cmd)
                
                
                
                
                
                
                if resp[0] == '2':
                    resp = self.getresp()
                if resp[0] != '1':
                    raise error_reply(resp)
            except:
                conn.close()
                raise
        else:
            with self.makeport() as sock:
                if rest is not None:
                    self.sendcmd("REST %s" % rest)
                resp = self.sendcmd(cmd)
                
                if resp[0] == '2':
                    resp = self.getresp()
                if resp[0] != '1':
                    raise error_reply(resp)
                conn, sockaddr = sock.accept()
                if self.timeout is not _GLOBAL_DEFAULT_TIMEOUT:
                    conn.settimeout(self.timeout)
        if resp[:3] == '150':
            
            size = parse150(resp)
        return conn, size

    def transfercmd(self, cmd, rest=None):
        
        return self.ntransfercmd(cmd, rest)[0]

    def login(self, user = '', passwd = '', acct = ''):
        
        if not user:
            user = 'anonymous'
        if not passwd:
            passwd = ''
        if not acct:
            acct = ''
        if user == 'anonymous' and passwd in {'', '-'}:
            
            
            
            
            
            
            
            passwd = passwd + 'anonymous@'
        resp = self.sendcmd('USER ' + user)
        if resp[0] == '3':
            resp = self.sendcmd('PASS ' + passwd)
        if resp[0] == '3':
            resp = self.sendcmd('ACCT ' + acct)
        if resp[0] != '2':
            raise error_reply(resp)
        return resp

    def retrbinary(self, cmd, callback, blocksize=8192, rest=None):
        
        self.voidcmd('TYPE I')
        with self.transfercmd(cmd, rest) as conn:
            while data := conn.recv(blocksize):
                callback(data)
            
            if _SSLSocket is not None and isinstance(conn, _SSLSocket):
                conn.unwrap()
        return self.voidresp()

    def retrlines(self, cmd, callback = None):
        
        if callback is None:
            callback = print_line
        resp = self.sendcmd('TYPE A')
        with self.transfercmd(cmd) as conn, \
                 conn.makefile('r', encoding=self.encoding) as fp:
            while 1:
                line = fp.readline(self.maxline + 1)
                if len(line) > self.maxline:
                    raise Error("got more than %d bytes" % self.maxline)
                if self.debugging > 2:
                    print('*retr*', repr(line))
                if not line:
                    break
                if line[-2:] == CRLF:
                    line = line[:-2]
                elif line[-1:] == '\n':
                    line = line[:-1]
                callback(line)
            
            if _SSLSocket is not None and isinstance(conn, _SSLSocket):
                conn.unwrap()
        return self.voidresp()

    def storbinary(self, cmd, fp, blocksize=8192, callback=None, rest=None):
        
        self.voidcmd('TYPE I')
        with self.transfercmd(cmd, rest) as conn:
            while buf := fp.read(blocksize):
                conn.sendall(buf)
                if callback:
                    callback(buf)
            
            if _SSLSocket is not None and isinstance(conn, _SSLSocket):
                conn.unwrap()
        return self.voidresp()

    def storlines(self, cmd, fp, callback=None):
        
        self.voidcmd('TYPE A')
        with self.transfercmd(cmd) as conn:
            while 1:
                buf = fp.readline(self.maxline + 1)
                if len(buf) > self.maxline:
                    raise Error("got more than %d bytes" % self.maxline)
                if not buf:
                    break
                if buf[-2:] != B_CRLF:
                    if buf[-1] in B_CRLF: buf = buf[:-1]
                    buf = buf + B_CRLF
                conn.sendall(buf)
                if callback:
                    callback(buf)
            
            if _SSLSocket is not None and isinstance(conn, _SSLSocket):
                conn.unwrap()
        return self.voidresp()

    def acct(self, password):
        
        cmd = 'ACCT ' + password
        return self.voidcmd(cmd)

    def nlst(self, *args):
        
        cmd = 'NLST'
        for arg in args:
            cmd = cmd + (' ' + arg)
        files = []
        self.retrlines(cmd, files.append)
        return files

    def dir(self, *args):
        
        cmd = 'LIST'
        func = None
        if args[-1:] and not isinstance(args[-1], str):
            args, func = args[:-1], args[-1]
        for arg in args:
            if arg:
                cmd = cmd + (' ' + arg)
        self.retrlines(cmd, func)

    def mlsd(self, path="", facts=[]):
        
        if facts:
            self.sendcmd("OPTS MLST " + ";".join(facts) + ";")
        if path:
            cmd = "MLSD %s" % path
        else:
            cmd = "MLSD"
        lines = []
        self.retrlines(cmd, lines.append)
        for line in lines:
            facts_found, _, name = line.rstrip(CRLF).partition(' ')
            entry = {}
            for fact in facts_found[:-1].split(";"):
                key, _, value = fact.partition("=")
                entry[key.lower()] = value
            yield (name, entry)

    def rename(self, fromname, toname):
        
        resp = self.sendcmd('RNFR ' + fromname)
        if resp[0] != '3':
            raise error_reply(resp)
        return self.voidcmd('RNTO ' + toname)

    def delete(self, filename):
        
        resp = self.sendcmd('DELE ' + filename)
        if resp[:3] in {'250', '200'}:
            return resp
        else:
            raise error_reply(resp)

    def cwd(self, dirname):
        
        if dirname == '..':
            try:
                return self.voidcmd('CDUP')
            except error_perm as msg:
                if msg.args[0][:3] != '500':
                    raise
        elif dirname == '':
            dirname = '.'  
        cmd = 'CWD ' + dirname
        return self.voidcmd(cmd)

    def size(self, filename):
        
        
        resp = self.sendcmd('SIZE ' + filename)
        if resp[:3] == '213':
            s = resp[3:].strip()
            return int(s)

    def mkd(self, dirname):
        
        resp = self.voidcmd('MKD ' + dirname)
        
        
        if not resp.startswith('257'):
            return ''
        return parse257(resp)

    def rmd(self, dirname):
        
        return self.voidcmd('RMD ' + dirname)

    def pwd(self):
        
        resp = self.voidcmd('PWD')
        
        
        if not resp.startswith('257'):
            return ''
        return parse257(resp)

    def quit(self):
        
        resp = self.voidcmd('QUIT')
        self.close()
        return resp

    def close(self):
        
        try:
            file = self.file
            self.file = None
            if file is not None:
                file.close()
        finally:
            sock = self.sock
            self.sock = None
            if sock is not None:
                sock.close()

try:
    import ssl
except ImportError:
    _SSLSocket = None
else:
    _SSLSocket = ssl.SSLSocket

    class FTP_TLS(FTP):
        

        def __init__(self, host='', user='', passwd='', acct='',
                     *, context=None, timeout=_GLOBAL_DEFAULT_TIMEOUT,
                     source_address=None, encoding='utf-8'):
            if context is None:
                context = ssl._create_stdlib_context()
            self.context = context
            self._prot_p = False
            super().__init__(host, user, passwd, acct,
                             timeout, source_address, encoding=encoding)

        def login(self, user='', passwd='', acct='', secure=True):
            if secure and not isinstance(self.sock, ssl.SSLSocket):
                self.auth()
            return super().login(user, passwd, acct)

        def auth(self):
            
            if isinstance(self.sock, ssl.SSLSocket):
                raise ValueError("Already using TLS")
            if self.context.protocol >= ssl.PROTOCOL_TLS:
                resp = self.voidcmd('AUTH TLS')
            else:
                resp = self.voidcmd('AUTH SSL')
            self.sock = self.context.wrap_socket(self.sock, server_hostname=self.host)
            self.file = self.sock.makefile(mode='r', encoding=self.encoding)
            return resp

        def ccc(self):
            
            if not isinstance(self.sock, ssl.SSLSocket):
                raise ValueError("not using TLS")
            resp = self.voidcmd('CCC')
            self.sock = self.sock.unwrap()
            return resp

        def prot_p(self):
            
            
            
            
            
            
            
            
            
            
            self.voidcmd('PBSZ 0')
            resp = self.voidcmd('PROT P')
            self._prot_p = True
            return resp

        def prot_c(self):
            
            resp = self.voidcmd('PROT C')
            self._prot_p = False
            return resp

        

        def ntransfercmd(self, cmd, rest=None):
            conn, size = super().ntransfercmd(cmd, rest)
            if self._prot_p:
                conn = self.context.wrap_socket(conn,
                                                server_hostname=self.host)
            return conn, size

        def abort(self):
            
            line = b'ABOR' + B_CRLF
            self.sock.sendall(line)
            resp = self.getmultiline()
            if resp[:3] not in {'426', '225', '226'}:
                raise error_proto(resp)
            return resp

    __all__.append('FTP_TLS')
    all_errors = (Error, OSError, EOFError, ssl.SSLError)


_150_re = None

def parse150(resp):
    
    if resp[:3] != '150':
        raise error_reply(resp)
    global _150_re
    if _150_re is None:
        import re
        _150_re = re.compile(
            r"150 .* \((\d+) bytes\)", re.IGNORECASE | re.ASCII)
    m = _150_re.match(resp)
    if not m:
        return None
    return int(m.group(1))


_227_re = None

def parse227(resp):
    
    if resp[:3] != '227':
        raise error_reply(resp)
    global _227_re
    if _227_re is None:
        import re
        _227_re = re.compile(r'(\d+),(\d+),(\d+),(\d+),(\d+),(\d+)', re.ASCII)
    m = _227_re.search(resp)
    if not m:
        raise error_proto(resp)
    numbers = m.groups()
    host = '.'.join(numbers[:4])
    port = (int(numbers[4]) << 8) + int(numbers[5])
    return host, port


def parse229(resp, peer):
    
    if resp[:3] != '229':
        raise error_reply(resp)
    left = resp.find('(')
    if left < 0: raise error_proto(resp)
    right = resp.find(')', left + 1)
    if right < 0:
        raise error_proto(resp) 
    if resp[left + 1] != resp[right - 1]:
        raise error_proto(resp)
    parts = resp[left + 1:right].split(resp[left+1])
    if len(parts) != 5:
        raise error_proto(resp)
    host = peer[0]
    port = int(parts[3])
    return host, port


def parse257(resp):
    
    if resp[:3] != '257':
        raise error_reply(resp)
    if resp[3:5] != ' "':
        return '' 
    dirname = ''
    i = 5
    n = len(resp)
    while i < n:
        c = resp[i]
        i = i+1
        if c == '"':
            if i >= n or resp[i] != '"':
                break
            i = i+1
        dirname = dirname + c
    return dirname


def print_line(line):
    
    print(line)


def ftpcp(source, sourcename, target, targetname = '', type = 'I'):
    
    if not targetname:
        targetname = sourcename
    type = 'TYPE ' + type
    source.voidcmd(type)
    target.voidcmd(type)
    sourcehost, sourceport = parse227(source.sendcmd('PASV'))
    target.sendport(sourcehost, sourceport)
    
    
    
    treply = target.sendcmd('STOR ' + targetname)
    if treply[:3] not in {'125', '150'}:
        raise error_proto  
    sreply = source.sendcmd('RETR ' + sourcename)
    if sreply[:3] not in {'125', '150'}:
        raise error_proto  
    source.voidresp()
    target.voidresp()


def test():
    

    if len(sys.argv) < 2:
        print(test.__doc__)
        sys.exit(0)

    import netrc

    debugging = 0
    rcfile = None
    while sys.argv[1] == '-d':
        debugging = debugging+1
        del sys.argv[1]
    if sys.argv[1][:2] == '-r':
        
        rcfile = sys.argv[1][2:]
        del sys.argv[1]
    host = sys.argv[1]
    ftp = FTP(host)
    ftp.set_debuglevel(debugging)
    userid = passwd = acct = ''
    try:
        netrcobj = netrc.netrc(rcfile)
    except OSError:
        if rcfile is not None:
            sys.stderr.write("Could not open account file"
                             " -- using anonymous login.")
    else:
        try:
            userid, acct, passwd = netrcobj.authenticators(host)
        except KeyError:
            
            sys.stderr.write(
                    "No account -- using anonymous login.")
    ftp.login(userid, passwd, acct)
    for file in sys.argv[2:]:
        if file[:2] == '-l':
            ftp.dir(file[2:])
        elif file[:2] == '-d':
            cmd = 'CWD'
            if file[2:]: cmd = cmd + ' ' + file[2:]
            resp = ftp.sendcmd(cmd)
        elif file == '-p':
            ftp.set_pasv(not ftp.passiveserver)
        else:
            ftp.retrbinary('RETR ' + file, \
                           sys.stdout.write, 1024)
    ftp.quit()


if __name__ == '__main__':
    test()