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

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

    diff --git a/twisted/protocols/ftp.py b/twisted/protocols/ftp.py
    index b035f04..38740d8 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): 
    884896        file.  A null argument implies the user's current working or
    885897        default directory.
    886898        """
    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'))
     899        self._checkDataTransportStarted('LIST')
    890900
    891901        # bug in konqueror
    892902        if path == "-a":
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    917927            segments,
    918928            ('size', 'directory', 'permissions', 'hardlinks',
    919929             'modified', 'owner', 'group'))
     930        d.addCallback(self._cbWaitDTPConnectionWithTimeout)
    920931        d.addCallback(gotListing)
    921932        return d
    922933
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    936947        @return: a L{Deferred} which will be fired when the listing request
    937948            is finished.
    938949        """
    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 
     950        self._checkDataTransportStarted('NLST')
    944951        try:
    945952            segments = toSegments(self.workingDirectory, path)
    946953        except InvalidPath:
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    9951002            '*' in segments[-1] or '?' in segments[-1] or
    9961003            ('[' in segments[-1] and ']' in segments[-1])):
    9971004            d = self.shell.list(segments[:-1])
     1005            d.addCallback(self._cbWaitDTPConnectionWithTimeout)
    9981006            d.addCallback(cbGlob)
    9991007        else:
    10001008            d = self.shell.list(segments)
     1009            d.addCallback(self._cbWaitDTPConnectionWithTimeout)
    10011010            d.addCallback(cbList)
    1002             # self.shell.list will generate an error if the path is invalid
    1003             d.addErrback(listErr)
     1011
     1012        # self.shell.list will generate an error if the path is invalid
     1013        d.addErrback(listErr)
    10041014        return d
    10051015
     1016    def _cbWaitDTPConnectionWithTimeout(self, result):
     1017        """Helper callback that waits for DTP instance to be connected.
     1018
     1019        It will raise a {PortConnectionError} if DTP instance is not
     1020        connected after the interval defined by self.factory.timeOut.
     1021        """
     1022        def cbTimeout(ignore):
     1023            self.reply(DTP_TIMEOUT)
     1024            return defer.fail(
     1025                     PortConnectionError(
     1026                         defer.TimeoutError("DTP connection timeout")))
     1027
     1028        def cbContinueCommand(ignore, timeoutDeferred):
     1029            if timeoutDeferred is not None and timeoutDeferred.active():
     1030                timeoutDeferred.cancel()
     1031            return result
     1032
     1033        deferred = self.factory._reactor.callLater(
     1034            self.factory.timeOut, cbTimeout, None)
     1035
     1036        self.dtpFactory.deferred.addCallback(
     1037            cbContinueCommand, deferred)
     1038        return self.dtpFactory.deferred
    10061039
    10071040    def ftp_CWD(self, path):
    10081041        try:
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    10381071        @rtype: L{Deferred}
    10391072        @return: a L{Deferred} which will be fired when the transfer is done.
    10401073        """
    1041         if self.dtpInstance is None:
    1042             raise BadCmdSequenceError('PORT or PASV required before RETR')
     1074        self._checkDataTransportStarted('RETR')
    10431075
    10441076        try:
    10451077            newsegs = toSegments(self.workingDirectory, path)
    class FTP(object, basic.LineReceiver, policies.TimeoutMixin): 
    11001132
    11011133
    11021134    def ftp_STOR(self, path):
    1103         if self.dtpInstance is None:
    1104             raise BadCmdSequenceError('PORT or PASV required before STOR')
     1135        """
     1136        This command causes the server-DTP to accept the data
     1137        transferred via the data connection and to store the data as
     1138        a file at the server site.  If the file specified in the
     1139        pathname exists at the server site, then its contents shall
     1140        be replaced by the data being transferred.  A new file is
     1141        created at the server site if the file specified in the
     1142        pathname does not already exist.
     1143
     1144        @type path: C{str}
     1145        @param path: The file path where the content should be stored.
     1146
     1147        @rtype: L{Deferred}
     1148        @return: a L{Deferred} which will be fired when the transfer
     1149            is finished.
     1150        """
     1151        self._checkDataTransportStarted('STOR')
    11051152
    11061153        try:
    11071154            newsegs = toSegments(self.workingDirectory, path)
    class FTPFactory(policies.LimitTotalConnectionsFactory): 
    13291376
    13301377    passivePortRange = xrange(0, 1)
    13311378
    1332     def __init__(self, portal=None, userAnonymous='anonymous'):
     1379    def __init__(self, portal=None, userAnonymous='anonymous', reactor=None):
    13331380        self.portal = portal
    13341381        self.userAnonymous = userAnonymous
    13351382        self.instances = []
     1383        if reactor is None:
     1384            from twisted.internet import reactor
     1385        self._reactor = reactor
     1386
    13361387
    13371388    def buildProtocol(self, addr):
    13381389        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..1883507 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        d = self._anonymousLogin()
     501        self._startDataConnection()
     502
     503        # Set timeout to a very small value to not slow down tests.
     504        self.serverProtocol.factory.timeOut = 0.1
     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
     532        def cbAdvanceClock(result, clock):
     533            clock.advance(6)
     534            return result
     535
     536        self.factory.timeOut = 5
     537        self.factory._reactor = task.Clock()
     538
     539        d = self._anonymousLogin()
     540        self._startDataConnection()
     541
     542        # Set timeout to a very small value to not slow down tests.
     543        self.assertCommandFailed(
     544            'NLST .',
     545            ["425 Data channel initialization timed out."],
     546            chainDeferred=d)
     547        d.addCallback(cbAdvanceClock, self.factory._reactor)
     548        return d
     549
     550
    466551
    467552class FTPServerTestCaseAdvancedClient(FTPServerTestCase):
    468553    """