Ticket #4180: 4180-list-checks-2.diff

File 4180-list-checks-2.diff, 11.9 KB (added by adiroiban, 2 years ago)
  • twisted/protocols/ftp.py

    diff --git a/twisted/protocols/ftp.py b/twisted/protocols/ftp.py
    index b035f04..05f1f7a 100644
    a b from zope.interface import Interface, implements 
    2525 
    2626# Twisted Imports 
    2727from twisted import copyright 
    28 from twisted.internet import reactor, interfaces, protocol, error, defer 
     28from twisted.internet import defer, error, interfaces, protocol, reactor, task 
    2929from twisted.protocols import basic, policies 
    3030 
    3131from twisted.python import log, failure, filepath 
    REQ_FILE_ACTN_PENDING_FURTHER_INFO = "350" 
    7272 
    7373SVC_NOT_AVAIL_CLOSING_CTRL_CNX          = "421.1" 
    7474TOO_MANY_CONNECTIONS                    = "421.2" 
    75 CANT_OPEN_DATA_CNX                      = "425" 
     75CANT_OPEN_DATA_CNX                      = "425.1" 
     76DTP_TIMEOUT                             = "425.2" 
    7677CNX_CLOSED_TXFR_ABORTED                 = "426" 
    7778REQ_ACTN_ABRTD_FILE_UNAVAIL             = "450" 
    7879REQ_ACTN_ABRTD_LOCAL_ERR                = "451" 
    RESPONSE = { 
    141142    SVC_NOT_AVAIL_CLOSING_CTRL_CNX:     '421 Service not available, closing control connection.', 
    142143    TOO_MANY_CONNECTIONS:               '421 Too many users right now, try again in a few minutes.', 
    143144    CANT_OPEN_DATA_CNX:                 "425 Can't open data connection.", 
     145    DTP_TIMEOUT:                        '425 Data channel initialization timed out.', 
    144146    CNX_CLOSED_TXFR_ABORTED:            '426 Transfer aborted.  Data connection closed.', 
    145147 
    146148    REQ_ACTN_ABRTD_FILE_UNAVAIL:        '450 Requested action aborted. File unavailable.', 
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    777779            (self.passivePortRange,)) 
    778780 
    779781 
     782    def _checkDataTransportStarted(self, command): 
     783        """Checks that data transport is ready. 
     784 
     785        If data transport was not requested using PORT, PASV etc it raises 
     786        L{BadCmdSequenceError}. 
     787        """ 
     788        if self.dtpInstance is None: 
     789            raise BadCmdSequenceError( 
     790                'PORT or PASV required before %s' % (command,)) 
     791 
    780792    def ftp_USER(self, username): 
    781793        """ 
    782794        First part of login.  Get the username the peer wants to 
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    829841 
    830842 
    831843    def ftp_PASV(self): 
    832         """Request for a passive connection 
     844        """ 
     845        Request for a passive connection 
    833846 
    834847        from the rfc:: 
    835848 
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    876889 
    877890 
    878891    def ftp_LIST(self, path=''): 
    879         """ This command causes a list to be sent from the server to the 
    880         passive DTP.  If the pathname specifies a directory or other 
     892        """ 
     893        This command causes a list to be sent from the server to the 
     894        passive DTP. 
     895 
     896        If the pathname specifies a directory or other 
    881897        group of files, the server should transfer a list of files 
    882         in the specified directory.  If the pathname specifies a 
    883         file then the server should send current information on the 
    884         file.  A null argument implies the user's current working or 
    885         default directory. 
     898        in the specified directory. 
     899        If the pathname specifies a file then the server should send current 
     900        information on the file. 
     901        A null argument implies the user's current working or default 
     902        directory. 
    886903        """ 
    887         # Uh, for now, do this retarded thing. 
    888         if self.dtpInstance is None or not self.dtpInstance.isConnected: 
    889             return defer.fail(BadCmdSequenceError('must send PORT or PASV before RETR')) 
     904        self._checkDataTransportStarted('LIST') 
    890905 
    891906        # bug in konqueror 
    892907        if path == "-a": 
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    917932            segments, 
    918933            ('size', 'directory', 'permissions', 'hardlinks', 
    919934             'modified', 'owner', 'group')) 
     935        d.addCallback(self._cbWaitDTPConnectionWithTimeout) 
    920936        d.addCallback(gotListing) 
    921937        return d 
    922938 
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    936952        @return: a L{Deferred} which will be fired when the listing request 
    937953            is finished. 
    938954        """ 
    939         # XXX: why is this check different from ftp_RETR/ftp_STOR? See #4180 
    940         if self.dtpInstance is None or not self.dtpInstance.isConnected: 
    941             return defer.fail( 
    942                 BadCmdSequenceError('must send PORT or PASV before RETR')) 
    943  
     955        self._checkDataTransportStarted('NLST') 
    944956        try: 
    945957            segments = toSegments(self.workingDirectory, path) 
    946958        except InvalidPath: 
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    9951007            '*' in segments[-1] or '?' in segments[-1] or 
    9961008            ('[' in segments[-1] and ']' in segments[-1])): 
    9971009            d = self.shell.list(segments[:-1]) 
     1010            d.addCallback(self._cbWaitDTPConnectionWithTimeout) 
    9981011            d.addCallback(cbGlob) 
    9991012        else: 
    10001013            d = self.shell.list(segments) 
     1014            d.addCallback(self._cbWaitDTPConnectionWithTimeout) 
    10011015            d.addCallback(cbList) 
    1002             # self.shell.list will generate an error if the path is invalid 
    1003             d.addErrback(listErr) 
     1016 
     1017        # self.shell.list will generate an error if the path is invalid 
     1018        d.addErrback(listErr) 
    10041019        return d 
    10051020 
     1021    def _cbWaitDTPConnectionWithTimeout(self, result): 
     1022        """ 
     1023        Helper callback that waits for DTP instance to be connected. 
     1024 
     1025        It will raise a C{PortConnectionError} if DTP instance is not 
     1026        connected after the interval defined by self.factory.timeOut. 
     1027        """ 
     1028        def ebDTPTimeout(failure): 
     1029            """ 
     1030            Called at data transport port timeout. 
     1031            """ 
     1032            failure.trap(defer.CancelledError) 
     1033            self.reply(DTP_TIMEOUT) 
     1034            return defer.fail( 
     1035                     PortConnectionError( 
     1036                         defer.TimeoutError("DTP connection timeout"))) 
     1037 
     1038        def cbContinueCommand(ignore, timeoutCall): 
     1039            if timeoutCall is not None and timeoutCall.active(): 
     1040                timeoutCall.cancel() 
     1041            return result 
     1042 
     1043        def cbCallTimeout(ignore): 
     1044            self.dtpFactory.deferred.cancel() 
     1045 
     1046        timeoutCall = self.factory._reactor.callLater( 
     1047            self.factory.timeOut, cbCallTimeout, None) 
     1048 
     1049        self.dtpFactory.deferred.addCallback(cbContinueCommand, timeoutCall) 
     1050        self.dtpFactory.deferred.addErrback(ebDTPTimeout) 
     1051 
     1052        return self.dtpFactory.deferred 
    10061053 
    10071054    def ftp_CWD(self, path): 
    10081055        try: 
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    10381085        @rtype: L{Deferred} 
    10391086        @return: a L{Deferred} which will be fired when the transfer is done. 
    10401087        """ 
    1041         if self.dtpInstance is None: 
    1042             raise BadCmdSequenceError('PORT or PASV required before RETR') 
     1088        self._checkDataTransportStarted('RETR') 
    10431089 
    10441090        try: 
    10451091            newsegs = toSegments(self.workingDirectory, path) 
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    11001146 
    11011147 
    11021148    def ftp_STOR(self, path): 
    1103         if self.dtpInstance is None: 
    1104             raise BadCmdSequenceError('PORT or PASV required before STOR') 
     1149        """ 
     1150        This command causes the server-DTP to accept the data 
     1151        transferred via the data connection and to store the data as 
     1152        a file at the server site.  If the file specified in the 
     1153        pathname exists at the server site, then its contents shall 
     1154        be replaced by the data being transferred.  A new file is 
     1155        created at the server site if the file specified in the 
     1156        pathname does not already exist. 
     1157 
     1158        @type path: C{str} 
     1159        @param path: The file path where the content should be stored. 
     1160 
     1161        @rtype: L{Deferred} 
     1162        @return: a L{Deferred} which will be fired when the transfer 
     1163            is finished. 
     1164        """ 
     1165        self._checkDataTransportStarted('STOR') 
    11051166 
    11061167        try: 
    11071168            newsegs = toSegments(self.workingDirectory, path) 
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    12891350 
    12901351 
    12911352    def cleanupDTP(self): 
    1292         """call when DTP connection exits 
     1353        """ 
     1354        Called when DTP connection exits. 
    12931355        """ 
    12941356        log.msg('cleanupDTP', debug=True) 
    12951357 
    class FTPFactory(policies.LimitTotalConnectionsFactory): 
    13291391 
    13301392    passivePortRange = xrange(0, 1) 
    13311393 
    1332     def __init__(self, portal=None, userAnonymous='anonymous'): 
     1394    def __init__(self, portal=None, userAnonymous='anonymous', reactor=None): 
    13331395        self.portal = portal 
    13341396        self.userAnonymous = userAnonymous 
    13351397        self.instances = [] 
     1398        if reactor is None: 
     1399            from twisted.internet import reactor 
     1400        self._reactor = reactor 
     1401 
    13361402 
    13371403    def buildProtocol(self, addr): 
    13381404        p = policies.LimitTotalConnectionsFactory.buildProtocol(self, addr) 
  • twisted/test/test_ftp.py

    diff --git a/twisted/test/test_ftp.py b/twisted/test/test_ftp.py
    index 23ffcba..06e38ee 100644
    a b class BasicFTPServerTestCase(FTPServerTestCase): 
    463463        self.assertEqual(portRange, protocol.wrappedProtocol.passivePortRange) 
    464464 
    465465 
     466    def _startDataConnection(self): 
     467        """ 
     468        Prepare data transport protocol to look like it was created by 
     469        a previous call to PASV or PORT 
     470        """ 
     471        self.serverProtocol.dtpFactory = ftp.DTPFactory(self.serverProtocol) 
     472        self.serverProtocol.dtpFactory.buildProtocol('ignore_address') 
     473 
     474        self.serverProtocol.dtpPort = self.serverProtocol.listenFactory( 
     475            6000, self.serverProtocol.dtpFactory) 
     476 
     477        dtpTransport = proto_helpers.StringTransportWithDisconnection() 
     478        dtpTransport.protocol = ftp.DTP() 
     479        self.serverProtocol.dtpInstance.transport = dtpTransport 
     480 
     481 
     482    def test_LISTWithoutDataChannel(self): 
     483        """ 
     484        Calling LIST without prior setup of data connection will result in a 
     485        incorrect sequence of commands error. 
     486        """ 
     487        d = self._anonymousLogin() 
     488        self.assertCommandFailed( 
     489            'LIST .', 
     490            ["503 Incorrect sequence of commands: " 
     491             "PORT or PASV required before LIST"], 
     492            chainDeferred=d) 
     493        return d 
     494 
     495 
     496    def test_LISTTimeout(self): 
     497        """ 
     498        LIST will timeout if setting up the DTP instance will take to long. 
     499        """ 
     500        # Set timeout to a very small value to not slow down tests. 
     501        self.factory.timeOut = 0.01 
     502 
     503        d = self._anonymousLogin() 
     504        self._startDataConnection() 
     505 
     506        self.assertCommandFailed( 
     507            'LIST .', 
     508            ["425 Data channel initialization timed out."], 
     509            chainDeferred=d) 
     510        return d 
     511 
     512 
     513    def test_NLSTWithoutDataChannel(self): 
     514        """ 
     515        Calling NLST without prior setup of data connection will result in a 
     516        incorrect sequence of commands error. 
     517        """ 
     518        d = self._anonymousLogin() 
     519        self.assertCommandFailed( 
     520            'NLST .', 
     521            ["503 Incorrect sequence of commands: " 
     522             "PORT or PASV required before NLST"], 
     523            chainDeferred=d) 
     524        return d 
     525 
     526 
     527    def test_NLSTTimeout(self): 
     528        """ 
     529        NLST will timeout if setting up the DTP instance will take to long. 
     530        """ 
     531        self.factory.timeOut = 0.01 
     532        #self.factory._reactor = task.Clock() 
     533 
     534        d = self._anonymousLogin() 
     535        self._startDataConnection() 
     536 
     537        # Set timeout to a very small value to not slow down tests. 
     538        self.assertCommandFailed( 
     539            'NLST .', 
     540            ["425 Data channel initialization timed out."], 
     541            chainDeferred=d) 
     542 
     543        def cbAdvanceClock(result, clock): 
     544            clock.advance(6) 
     545            return result 
     546        #d.addCallback(cbAdvanceClock, self.factory._reactor) 
     547 
     548        return d 
     549 
     550 
    466551 
    467552class FTPServerTestCaseAdvancedClient(FTPServerTestCase): 
    468553    """