~cbehrens/nova/lp844160-build-works-with-zones

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/test/test_ftp.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
 
2
# See LICENSE for details.
 
3
 
 
4
"""
 
5
FTP tests.
 
6
 
 
7
Maintainer: Andrew Bennetts
 
8
"""
 
9
 
 
10
import os
 
11
import errno
 
12
 
 
13
from zope.interface import implements
 
14
 
 
15
from twisted.trial import unittest, util
 
16
from twisted.protocols import basic
 
17
from twisted.internet import reactor, task, protocol, defer, error
 
18
from twisted.internet.interfaces import IConsumer
 
19
from twisted.cred import portal, checkers, credentials
 
20
from twisted.python import failure, filepath
 
21
from twisted.test import proto_helpers
 
22
 
 
23
from twisted.protocols import ftp, loopback
 
24
 
 
25
 
 
26
_changeDirectorySuppression = util.suppress(
 
27
    category=DeprecationWarning,
 
28
    message=(
 
29
        r"FTPClient\.changeDirectory is deprecated in Twisted 8\.2 and "
 
30
        r"newer\.  Use FTPClient\.cwd instead\."))
 
31
 
 
32
 
 
33
class Dummy(basic.LineReceiver):
 
34
    logname = None
 
35
    def __init__(self):
 
36
        self.lines = []
 
37
        self.rawData = []
 
38
    def connectionMade(self):
 
39
        self.f = self.factory   # to save typing in pdb :-)
 
40
    def lineReceived(self,line):
 
41
        self.lines.append(line)
 
42
    def rawDataReceived(self, data):
 
43
        self.rawData.append(data)
 
44
    def lineLengthExceeded(self, line):
 
45
        pass
 
46
 
 
47
 
 
48
class _BufferingProtocol(protocol.Protocol):
 
49
    def connectionMade(self):
 
50
        self.buffer = ''
 
51
        self.d = defer.Deferred()
 
52
    def dataReceived(self, data):
 
53
        self.buffer += data
 
54
    def connectionLost(self, reason):
 
55
        self.d.callback(self)
 
56
 
 
57
 
 
58
 
 
59
class FTPServerTestCase(unittest.TestCase):
 
60
    """
 
61
    Simple tests for an FTP server with the default settings.
 
62
 
 
63
    @ivar clientFactory: class used as ftp client.
 
64
    """
 
65
    clientFactory = ftp.FTPClientBasic
 
66
    userAnonymous = "anonymous"
 
67
 
 
68
    def setUp(self):
 
69
        # Create a directory
 
70
        self.directory = self.mktemp()
 
71
        os.mkdir(self.directory)
 
72
        self.dirPath = filepath.FilePath(self.directory)
 
73
 
 
74
        # Start the server
 
75
        p = portal.Portal(ftp.FTPRealm(self.directory))
 
76
        p.registerChecker(checkers.AllowAnonymousAccess(),
 
77
                          credentials.IAnonymous)
 
78
        self.factory = ftp.FTPFactory(portal=p,
 
79
                                      userAnonymous=self.userAnonymous)
 
80
        port = reactor.listenTCP(0, self.factory, interface="127.0.0.1")
 
81
        self.addCleanup(port.stopListening)
 
82
 
 
83
        # Hook the server's buildProtocol to make the protocol instance
 
84
        # accessible to tests.
 
85
        buildProtocol = self.factory.buildProtocol
 
86
        d1 = defer.Deferred()
 
87
        def _rememberProtocolInstance(addr):
 
88
            # Done hooking this.
 
89
            del self.factory.buildProtocol
 
90
 
 
91
            protocol = buildProtocol(addr)
 
92
            self.serverProtocol = protocol.wrappedProtocol
 
93
            def cleanupServer():
 
94
                if self.serverProtocol.transport is not None:
 
95
                    self.serverProtocol.transport.loseConnection()
 
96
            self.addCleanup(cleanupServer)
 
97
            d1.callback(None)
 
98
            return protocol
 
99
        self.factory.buildProtocol = _rememberProtocolInstance
 
100
 
 
101
        # Connect a client to it
 
102
        portNum = port.getHost().port
 
103
        clientCreator = protocol.ClientCreator(reactor, self.clientFactory)
 
104
        d2 = clientCreator.connectTCP("127.0.0.1", portNum)
 
105
        def gotClient(client):
 
106
            self.client = client
 
107
            self.addCleanup(self.client.transport.loseConnection)
 
108
        d2.addCallback(gotClient)
 
109
        return defer.gatherResults([d1, d2])
 
110
 
 
111
    def assertCommandResponse(self, command, expectedResponseLines,
 
112
                              chainDeferred=None):
 
113
        """Asserts that a sending an FTP command receives the expected
 
114
        response.
 
115
 
 
116
        Returns a Deferred.  Optionally accepts a deferred to chain its actions
 
117
        to.
 
118
        """
 
119
        if chainDeferred is None:
 
120
            chainDeferred = defer.succeed(None)
 
121
 
 
122
        def queueCommand(ignored):
 
123
            d = self.client.queueStringCommand(command)
 
124
            def gotResponse(responseLines):
 
125
                self.assertEquals(expectedResponseLines, responseLines)
 
126
            return d.addCallback(gotResponse)
 
127
        return chainDeferred.addCallback(queueCommand)
 
128
 
 
129
    def assertCommandFailed(self, command, expectedResponse=None,
 
130
                            chainDeferred=None):
 
131
        if chainDeferred is None:
 
132
            chainDeferred = defer.succeed(None)
 
133
 
 
134
        def queueCommand(ignored):
 
135
            return self.client.queueStringCommand(command)
 
136
        chainDeferred.addCallback(queueCommand)
 
137
        self.assertFailure(chainDeferred, ftp.CommandFailed)
 
138
        def failed(exception):
 
139
            if expectedResponse is not None:
 
140
                self.failUnlessEqual(
 
141
                    expectedResponse, exception.args[0])
 
142
        return chainDeferred.addCallback(failed)
 
143
 
 
144
    def _anonymousLogin(self):
 
145
        d = self.assertCommandResponse(
 
146
            'USER anonymous',
 
147
            ['331 Guest login ok, type your email address as password.'])
 
148
        return self.assertCommandResponse(
 
149
            'PASS test@twistedmatrix.com',
 
150
            ['230 Anonymous login ok, access restrictions apply.'],
 
151
            chainDeferred=d)
 
152
 
 
153
 
 
154
 
 
155
class FTPAnonymousTestCase(FTPServerTestCase):
 
156
    """
 
157
    Simple tests for an FTP server with different anonymous username.
 
158
    The new anonymous username used in this test case is "guest"
 
159
    """
 
160
    userAnonymous = "guest"
 
161
 
 
162
    def test_anonymousLogin(self):
 
163
        """
 
164
        Tests whether the changing of the anonymous username is working or not.
 
165
        The FTP server should not comply about the need of password for the
 
166
        username 'guest', letting it login as anonymous asking just an email
 
167
        address as password.
 
168
        """
 
169
        d = self.assertCommandResponse(
 
170
            'USER guest',
 
171
            ['331 Guest login ok, type your email address as password.'])
 
172
        return self.assertCommandResponse(
 
173
            'PASS test@twistedmatrix.com',
 
174
            ['230 Anonymous login ok, access restrictions apply.'],
 
175
            chainDeferred=d)
 
176
 
 
177
 
 
178
 
 
179
class BasicFTPServerTestCase(FTPServerTestCase):
 
180
    def testNotLoggedInReply(self):
 
181
        """When not logged in, all commands other than USER and PASS should
 
182
        get NOT_LOGGED_IN errors.
 
183
        """
 
184
        commandList = ['CDUP', 'CWD', 'LIST', 'MODE', 'PASV',
 
185
                       'PWD', 'RETR', 'STRU', 'SYST', 'TYPE']
 
186
 
 
187
        # Issue commands, check responses
 
188
        def checkResponse(exception):
 
189
            failureResponseLines = exception.args[0]
 
190
            self.failUnless(failureResponseLines[-1].startswith("530"),
 
191
                            "Response didn't start with 530: %r"
 
192
                            % (failureResponseLines[-1],))
 
193
        deferreds = []
 
194
        for command in commandList:
 
195
            deferred = self.client.queueStringCommand(command)
 
196
            self.assertFailure(deferred, ftp.CommandFailed)
 
197
            deferred.addCallback(checkResponse)
 
198
            deferreds.append(deferred)
 
199
        return defer.DeferredList(deferreds, fireOnOneErrback=True)
 
200
 
 
201
    def testPASSBeforeUSER(self):
 
202
        """Issuing PASS before USER should give an error."""
 
203
        return self.assertCommandFailed(
 
204
            'PASS foo',
 
205
            ["503 Incorrect sequence of commands: "
 
206
             "USER required before PASS"])
 
207
 
 
208
    def testNoParamsForUSER(self):
 
209
        """Issuing USER without a username is a syntax error."""
 
210
        return self.assertCommandFailed(
 
211
            'USER',
 
212
            ['500 Syntax error: USER requires an argument.'])
 
213
 
 
214
    def testNoParamsForPASS(self):
 
215
        """Issuing PASS without a password is a syntax error."""
 
216
        d = self.client.queueStringCommand('USER foo')
 
217
        return self.assertCommandFailed(
 
218
            'PASS',
 
219
            ['500 Syntax error: PASS requires an argument.'],
 
220
            chainDeferred=d)
 
221
 
 
222
    def testAnonymousLogin(self):
 
223
        return self._anonymousLogin()
 
224
 
 
225
    def testQuit(self):
 
226
        """Issuing QUIT should return a 221 message."""
 
227
        d = self._anonymousLogin()
 
228
        return self.assertCommandResponse(
 
229
            'QUIT',
 
230
            ['221 Goodbye.'],
 
231
            chainDeferred=d)
 
232
 
 
233
    def testAnonymousLoginDenied(self):
 
234
        # Reconfigure the server to disallow anonymous access, and to have an
 
235
        # IUsernamePassword checker that always rejects.
 
236
        self.factory.allowAnonymous = False
 
237
        denyAlwaysChecker = checkers.InMemoryUsernamePasswordDatabaseDontUse()
 
238
        self.factory.portal.registerChecker(denyAlwaysChecker,
 
239
                                            credentials.IUsernamePassword)
 
240
 
 
241
        # Same response code as allowAnonymous=True, but different text.
 
242
        d = self.assertCommandResponse(
 
243
            'USER anonymous',
 
244
            ['331 Password required for anonymous.'])
 
245
 
 
246
        # It will be denied.  No-one can login.
 
247
        d = self.assertCommandFailed(
 
248
            'PASS test@twistedmatrix.com',
 
249
            ['530 Sorry, Authentication failed.'],
 
250
            chainDeferred=d)
 
251
 
 
252
        # It's not just saying that.  You aren't logged in.
 
253
        d = self.assertCommandFailed(
 
254
            'PWD',
 
255
            ['530 Please login with USER and PASS.'],
 
256
            chainDeferred=d)
 
257
        return d
 
258
 
 
259
    def testUnknownCommand(self):
 
260
        d = self._anonymousLogin()
 
261
        return self.assertCommandFailed(
 
262
            'GIBBERISH',
 
263
            ["502 Command 'GIBBERISH' not implemented"],
 
264
            chainDeferred=d)
 
265
 
 
266
    def testRETRBeforePORT(self):
 
267
        d = self._anonymousLogin()
 
268
        return self.assertCommandFailed(
 
269
            'RETR foo',
 
270
            ["503 Incorrect sequence of commands: "
 
271
             "PORT or PASV required before RETR"],
 
272
            chainDeferred=d)
 
273
 
 
274
    def testSTORBeforePORT(self):
 
275
        d = self._anonymousLogin()
 
276
        return self.assertCommandFailed(
 
277
            'STOR foo',
 
278
            ["503 Incorrect sequence of commands: "
 
279
             "PORT or PASV required before STOR"],
 
280
            chainDeferred=d)
 
281
 
 
282
    def testBadCommandArgs(self):
 
283
        d = self._anonymousLogin()
 
284
        self.assertCommandFailed(
 
285
            'MODE z',
 
286
            ["504 Not implemented for parameter 'z'."],
 
287
            chainDeferred=d)
 
288
        self.assertCommandFailed(
 
289
            'STRU I',
 
290
            ["504 Not implemented for parameter 'I'."],
 
291
            chainDeferred=d)
 
292
        return d
 
293
 
 
294
    def testDecodeHostPort(self):
 
295
        self.assertEquals(ftp.decodeHostPort('25,234,129,22,100,23'),
 
296
                ('25.234.129.22', 25623))
 
297
        nums = range(6)
 
298
        for i in range(6):
 
299
            badValue = list(nums)
 
300
            badValue[i] = 256
 
301
            s = ','.join(map(str, badValue))
 
302
            self.assertRaises(ValueError, ftp.decodeHostPort, s)
 
303
 
 
304
    def testPASV(self):
 
305
        # Login
 
306
        wfd = defer.waitForDeferred(self._anonymousLogin())
 
307
        yield wfd
 
308
        wfd.getResult()
 
309
 
 
310
        # Issue a PASV command, and extract the host and port from the response
 
311
        pasvCmd = defer.waitForDeferred(self.client.queueStringCommand('PASV'))
 
312
        yield pasvCmd
 
313
        responseLines = pasvCmd.getResult()
 
314
        host, port = ftp.decodeHostPort(responseLines[-1][4:])
 
315
 
 
316
        # Make sure the server is listening on the port it claims to be
 
317
        self.assertEqual(port, self.serverProtocol.dtpPort.getHost().port)
 
318
 
 
319
        # Semi-reasonable way to force cleanup
 
320
        self.serverProtocol.transport.loseConnection()
 
321
    testPASV = defer.deferredGenerator(testPASV)
 
322
 
 
323
    def testSYST(self):
 
324
        d = self._anonymousLogin()
 
325
        self.assertCommandResponse('SYST', ["215 UNIX Type: L8"],
 
326
                                   chainDeferred=d)
 
327
        return d
 
328
 
 
329
 
 
330
    def test_portRangeForwardError(self):
 
331
        """
 
332
        Exceptions other than L{error.CannotListenError} which are raised by
 
333
        C{listenFactory} should be raised to the caller of L{FTP.getDTPPort}.
 
334
        """
 
335
        def listenFactory(portNumber, factory):
 
336
            raise RuntimeError()
 
337
        self.serverProtocol.listenFactory = listenFactory
 
338
 
 
339
        self.assertRaises(RuntimeError, self.serverProtocol.getDTPPort,
 
340
                          protocol.Factory())
 
341
 
 
342
 
 
343
    def test_portRange(self):
 
344
        """
 
345
        L{FTP.passivePortRange} should determine the ports which
 
346
        L{FTP.getDTPPort} attempts to bind. If no port from that iterator can
 
347
        be bound, L{error.CannotListenError} should be raised, otherwise the
 
348
        first successful result from L{FTP.listenFactory} should be returned.
 
349
        """
 
350
        def listenFactory(portNumber, factory):
 
351
            if portNumber in (22032, 22033, 22034):
 
352
                raise error.CannotListenError('localhost', portNumber, 'error')
 
353
            return portNumber
 
354
        self.serverProtocol.listenFactory = listenFactory
 
355
 
 
356
        port = self.serverProtocol.getDTPPort(protocol.Factory())
 
357
        self.assertEquals(port, 0)
 
358
 
 
359
        self.serverProtocol.passivePortRange = xrange(22032, 65536)
 
360
        port = self.serverProtocol.getDTPPort(protocol.Factory())
 
361
        self.assertEquals(port, 22035)
 
362
 
 
363
        self.serverProtocol.passivePortRange = xrange(22032, 22035)
 
364
        self.assertRaises(error.CannotListenError,
 
365
                          self.serverProtocol.getDTPPort,
 
366
                          protocol.Factory())
 
367
 
 
368
 
 
369
    def test_portRangeInheritedFromFactory(self):
 
370
        """
 
371
        The L{FTP} instances created by L{ftp.FTPFactory.buildProtocol} have
 
372
        their C{passivePortRange} attribute set to the same object the
 
373
        factory's C{passivePortRange} attribute is set to.
 
374
        """
 
375
        portRange = xrange(2017, 2031)
 
376
        self.factory.passivePortRange = portRange
 
377
        protocol = self.factory.buildProtocol(None)
 
378
        self.assertEqual(portRange, protocol.wrappedProtocol.passivePortRange)
 
379
 
 
380
 
 
381
 
 
382
class FTPServerTestCaseAdvancedClient(FTPServerTestCase):
 
383
    """
 
384
    Test FTP server with the L{ftp.FTPClient} class.
 
385
    """
 
386
    clientFactory = ftp.FTPClient
 
387
 
 
388
    def test_anonymousSTOR(self):
 
389
        """
 
390
        Try to make an STOR as anonymous, and check that we got a permission
 
391
        denied error.
 
392
        """
 
393
        def eb(res):
 
394
            res.trap(ftp.CommandFailed)
 
395
            self.assertEquals(res.value.args[0][0],
 
396
                '550 foo: Permission denied.')
 
397
        d1, d2 = self.client.storeFile('foo')
 
398
        d2.addErrback(eb)
 
399
        return defer.gatherResults([d1, d2])
 
400
 
 
401
 
 
402
class FTPServerPasvDataConnectionTestCase(FTPServerTestCase):
 
403
    def _makeDataConnection(self, ignored=None):
 
404
        # Establish a passive data connection (i.e. client connecting to
 
405
        # server).
 
406
        d = self.client.queueStringCommand('PASV')
 
407
        def gotPASV(responseLines):
 
408
            host, port = ftp.decodeHostPort(responseLines[-1][4:])
 
409
            cc = protocol.ClientCreator(reactor, _BufferingProtocol)
 
410
            return cc.connectTCP('127.0.0.1', port)
 
411
        return d.addCallback(gotPASV)
 
412
 
 
413
    def _download(self, command, chainDeferred=None):
 
414
        if chainDeferred is None:
 
415
            chainDeferred = defer.succeed(None)
 
416
 
 
417
        chainDeferred.addCallback(self._makeDataConnection)
 
418
        def queueCommand(downloader):
 
419
            # wait for the command to return, and the download connection to be
 
420
            # closed.
 
421
            d1 = self.client.queueStringCommand(command)
 
422
            d2 = downloader.d
 
423
            return defer.gatherResults([d1, d2])
 
424
        chainDeferred.addCallback(queueCommand)
 
425
 
 
426
        def downloadDone((ignored, downloader)):
 
427
            return downloader.buffer
 
428
        return chainDeferred.addCallback(downloadDone)
 
429
 
 
430
    def testEmptyLIST(self):
 
431
        # Login
 
432
        d = self._anonymousLogin()
 
433
 
 
434
        # No files, so the file listing should be empty
 
435
        self._download('LIST', chainDeferred=d)
 
436
        def checkEmpty(result):
 
437
            self.assertEqual('', result)
 
438
        return d.addCallback(checkEmpty)
 
439
 
 
440
    def testTwoDirLIST(self):
 
441
        # Make some directories
 
442
        os.mkdir(os.path.join(self.directory, 'foo'))
 
443
        os.mkdir(os.path.join(self.directory, 'bar'))
 
444
 
 
445
        # Login
 
446
        d = self._anonymousLogin()
 
447
 
 
448
        # We expect 2 lines because there are two files.
 
449
        self._download('LIST', chainDeferred=d)
 
450
        def checkDownload(download):
 
451
            self.assertEqual(2, len(download[:-2].split('\r\n')))
 
452
        d.addCallback(checkDownload)
 
453
 
 
454
        # Download a names-only listing.
 
455
        self._download('NLST ', chainDeferred=d)
 
456
        def checkDownload(download):
 
457
            filenames = download[:-2].split('\r\n')
 
458
            filenames.sort()
 
459
            self.assertEqual(['bar', 'foo'], filenames)
 
460
        d.addCallback(checkDownload)
 
461
 
 
462
        # Download a listing of the 'foo' subdirectory.  'foo' has no files, so
 
463
        # the file listing should be empty.
 
464
        self._download('LIST foo', chainDeferred=d)
 
465
        def checkDownload(download):
 
466
            self.assertEqual('', download)
 
467
        d.addCallback(checkDownload)
 
468
 
 
469
        # Change the current working directory to 'foo'.
 
470
        def chdir(ignored):
 
471
            return self.client.queueStringCommand('CWD foo')
 
472
        d.addCallback(chdir)
 
473
 
 
474
        # Download a listing from within 'foo', and again it should be empty,
 
475
        # because LIST uses the working directory by default.
 
476
        self._download('LIST', chainDeferred=d)
 
477
        def checkDownload(download):
 
478
            self.assertEqual('', download)
 
479
        return d.addCallback(checkDownload)
 
480
 
 
481
    def testManyLargeDownloads(self):
 
482
        # Login
 
483
        d = self._anonymousLogin()
 
484
 
 
485
        # Download a range of different size files
 
486
        for size in range(100000, 110000, 500):
 
487
            fObj = file(os.path.join(self.directory, '%d.txt' % (size,)), 'wb')
 
488
            fObj.write('x' * size)
 
489
            fObj.close()
 
490
 
 
491
            self._download('RETR %d.txt' % (size,), chainDeferred=d)
 
492
            def checkDownload(download, size=size):
 
493
                self.assertEqual('x' * size, download)
 
494
            d.addCallback(checkDownload)
 
495
        return d
 
496
 
 
497
 
 
498
    def test_NLSTEmpty(self):
 
499
        """
 
500
        NLST with no argument returns the directory listing for the current
 
501
        working directory.
 
502
        """
 
503
        # Login
 
504
        d = self._anonymousLogin()
 
505
 
 
506
        # Touch a file in the current working directory
 
507
        self.dirPath.child('test.txt').touch()
 
508
        # Make a directory in the current working directory
 
509
        self.dirPath.child('foo').createDirectory()
 
510
 
 
511
        self._download('NLST ', chainDeferred=d)
 
512
        def checkDownload(download):
 
513
            filenames = download[:-2].split('\r\n')
 
514
            filenames.sort()
 
515
            self.assertEquals(['foo', 'test.txt'], filenames)
 
516
        return d.addCallback(checkDownload)
 
517
 
 
518
 
 
519
    def test_NLSTNonexistent(self):
 
520
        """
 
521
        NLST on a non-existent file/directory returns nothing.
 
522
        """
 
523
        # Login
 
524
        d = self._anonymousLogin()
 
525
 
 
526
        self._download('NLST nonexistent.txt', chainDeferred=d)
 
527
        def checkDownload(download):
 
528
            self.assertEquals('', download)
 
529
        return d.addCallback(checkDownload)
 
530
 
 
531
 
 
532
    def test_NLSTOnPathToFile(self):
 
533
        """
 
534
        NLST on an existent file returns only the path to that file.
 
535
        """
 
536
        # Login
 
537
        d = self._anonymousLogin()
 
538
 
 
539
        # Touch a file in the current working directory
 
540
        self.dirPath.child('test.txt').touch()
 
541
 
 
542
        self._download('NLST test.txt', chainDeferred=d)
 
543
        def checkDownload(download):
 
544
            filenames = download[:-2].split('\r\n')
 
545
            self.assertEquals(['test.txt'], filenames)
 
546
        return d.addCallback(checkDownload)
 
547
 
 
548
 
 
549
 
 
550
class FTPServerPortDataConnectionTestCase(FTPServerPasvDataConnectionTestCase):
 
551
    def setUp(self):
 
552
        self.dataPorts = []
 
553
        return FTPServerPasvDataConnectionTestCase.setUp(self)
 
554
 
 
555
    def _makeDataConnection(self, ignored=None):
 
556
        # Establish an active data connection (i.e. server connecting to
 
557
        # client).
 
558
        deferred = defer.Deferred()
 
559
        class DataFactory(protocol.ServerFactory):
 
560
            protocol = _BufferingProtocol
 
561
            def buildProtocol(self, addr):
 
562
                p = protocol.ServerFactory.buildProtocol(self, addr)
 
563
                reactor.callLater(0, deferred.callback, p)
 
564
                return p
 
565
        dataPort = reactor.listenTCP(0, DataFactory(), interface='127.0.0.1')
 
566
        self.dataPorts.append(dataPort)
 
567
        cmd = 'PORT ' + ftp.encodeHostPort('127.0.0.1', dataPort.getHost().port)
 
568
        self.client.queueStringCommand(cmd)
 
569
        return deferred
 
570
 
 
571
    def tearDown(self):
 
572
        l = [defer.maybeDeferred(port.stopListening) for port in self.dataPorts]
 
573
        d = defer.maybeDeferred(
 
574
            FTPServerPasvDataConnectionTestCase.tearDown, self)
 
575
        l.append(d)
 
576
        return defer.DeferredList(l, fireOnOneErrback=True)
 
577
 
 
578
    def testPORTCannotConnect(self):
 
579
        # Login
 
580
        d = self._anonymousLogin()
 
581
 
 
582
        # Listen on a port, and immediately stop listening as a way to find a
 
583
        # port number that is definitely closed.
 
584
        def loggedIn(ignored):
 
585
            port = reactor.listenTCP(0, protocol.Factory(),
 
586
                                     interface='127.0.0.1')
 
587
            portNum = port.getHost().port
 
588
            d = port.stopListening()
 
589
            d.addCallback(lambda _: portNum)
 
590
            return d
 
591
        d.addCallback(loggedIn)
 
592
 
 
593
        # Tell the server to connect to that port with a PORT command, and
 
594
        # verify that it fails with the right error.
 
595
        def gotPortNum(portNum):
 
596
            return self.assertCommandFailed(
 
597
                'PORT ' + ftp.encodeHostPort('127.0.0.1', portNum),
 
598
                ["425 Can't open data connection."])
 
599
        return d.addCallback(gotPortNum)
 
600
 
 
601
 
 
602
 
 
603
class DTPFactoryTests(unittest.TestCase):
 
604
    """
 
605
    Tests for L{ftp.DTPFactory}.
 
606
    """
 
607
    def setUp(self):
 
608
        """
 
609
        Create a fake protocol interpreter and a L{ftp.DTPFactory} instance to
 
610
        test.
 
611
        """
 
612
        self.reactor = task.Clock()
 
613
 
 
614
        class ProtocolInterpreter(object):
 
615
            dtpInstance = None
 
616
 
 
617
        self.protocolInterpreter = ProtocolInterpreter()
 
618
        self.factory = ftp.DTPFactory(
 
619
            self.protocolInterpreter, None, self.reactor)
 
620
 
 
621
 
 
622
    def test_setTimeout(self):
 
623
        """
 
624
        L{ftp.DTPFactory.setTimeout} uses the reactor passed to its initializer
 
625
        to set up a timed event to time out the DTP setup after the specified
 
626
        number of seconds.
 
627
        """
 
628
        # Make sure the factory's deferred fails with the right exception, and
 
629
        # make it so we can tell exactly when it fires.
 
630
        finished = []
 
631
        d = self.assertFailure(self.factory.deferred, ftp.PortConnectionError)
 
632
        d.addCallback(finished.append)
 
633
 
 
634
        self.factory.setTimeout(6)
 
635
 
 
636
        # Advance the clock almost to the timeout
 
637
        self.reactor.advance(5)
 
638
 
 
639
        # Nothing should have happened yet.
 
640
        self.assertFalse(finished)
 
641
 
 
642
        # Advance it to the configured timeout.
 
643
        self.reactor.advance(1)
 
644
 
 
645
        # Now the Deferred should have failed with TimeoutError.
 
646
        self.assertTrue(finished)
 
647
 
 
648
        # There should also be no calls left in the reactor.
 
649
        self.assertFalse(self.reactor.calls)
 
650
 
 
651
 
 
652
    def test_buildProtocolOnce(self):
 
653
        """
 
654
        A L{ftp.DTPFactory} instance's C{buildProtocol} method can be used once
 
655
        to create a L{ftp.DTP} instance.
 
656
        """
 
657
        protocol = self.factory.buildProtocol(None)
 
658
        self.assertIsInstance(protocol, ftp.DTP)
 
659
 
 
660
        # A subsequent call returns None.
 
661
        self.assertIdentical(self.factory.buildProtocol(None), None)
 
662
 
 
663
 
 
664
    def test_timeoutAfterConnection(self):
 
665
        """
 
666
        If a timeout has been set up using L{ftp.DTPFactory.setTimeout}, it is
 
667
        cancelled by L{ftp.DTPFactory.buildProtocol}.
 
668
        """
 
669
        self.factory.setTimeout(10)
 
670
        protocol = self.factory.buildProtocol(None)
 
671
        # Make sure the call is no longer active.
 
672
        self.assertFalse(self.reactor.calls)
 
673
 
 
674
 
 
675
    def test_connectionAfterTimeout(self):
 
676
        """
 
677
        If L{ftp.DTPFactory.buildProtocol} is called after the timeout
 
678
        specified by L{ftp.DTPFactory.setTimeout} has elapsed, C{None} is
 
679
        returned.
 
680
        """
 
681
        # Handle the error so it doesn't get logged.
 
682
        d = self.assertFailure(self.factory.deferred, ftp.PortConnectionError)
 
683
 
 
684
        # Set up the timeout and then cause it to elapse so the Deferred does
 
685
        # fail.
 
686
        self.factory.setTimeout(10)
 
687
        self.reactor.advance(10)
 
688
 
 
689
        # Try to get a protocol - we should not be able to.
 
690
        self.assertIdentical(self.factory.buildProtocol(None), None)
 
691
 
 
692
        # Make sure the Deferred is doing the right thing.
 
693
        return d
 
694
 
 
695
 
 
696
    def test_timeoutAfterConnectionFailed(self):
 
697
        """
 
698
        L{ftp.DTPFactory.deferred} fails with L{PortConnectionError} when
 
699
        L{ftp.DTPFactory.clientConnectionFailed} is called.  If the timeout
 
700
        specified with L{ftp.DTPFactory.setTimeout} expires after that, nothing
 
701
        additional happens.
 
702
        """
 
703
        finished = []
 
704
        d = self.assertFailure(self.factory.deferred, ftp.PortConnectionError)
 
705
        d.addCallback(finished.append)
 
706
 
 
707
        self.factory.setTimeout(10)
 
708
        self.assertFalse(finished)
 
709
        self.factory.clientConnectionFailed(None, None)
 
710
        self.assertTrue(finished)
 
711
        self.reactor.advance(10)
 
712
        return d
 
713
 
 
714
 
 
715
    def test_connectionFailedAfterTimeout(self):
 
716
        """
 
717
        If L{ftp.DTPFactory.clientConnectionFailed} is called after the timeout
 
718
        specified by L{ftp.DTPFactory.setTimeout} has elapsed, nothing beyond
 
719
        the normal timeout before happens.
 
720
        """
 
721
        # Handle the error so it doesn't get logged.
 
722
        d = self.assertFailure(self.factory.deferred, ftp.PortConnectionError)
 
723
 
 
724
        # Set up the timeout and then cause it to elapse so the Deferred does
 
725
        # fail.
 
726
        self.factory.setTimeout(10)
 
727
        self.reactor.advance(10)
 
728
 
 
729
        # Now fail the connection attempt.  This should do nothing.  In
 
730
        # particular, it should not raise an exception.
 
731
        self.factory.clientConnectionFailed(None, defer.TimeoutError("foo"))
 
732
 
 
733
        # Give the Deferred to trial so it can make sure it did what we
 
734
        # expected.
 
735
        return d
 
736
 
 
737
 
 
738
 
 
739
# -- Client Tests -----------------------------------------------------------
 
740
 
 
741
class PrintLines(protocol.Protocol):
 
742
    """Helper class used by FTPFileListingTests."""
 
743
 
 
744
    def __init__(self, lines):
 
745
        self._lines = lines
 
746
 
 
747
    def connectionMade(self):
 
748
        for line in self._lines:
 
749
            self.transport.write(line + "\r\n")
 
750
        self.transport.loseConnection()
 
751
 
 
752
 
 
753
class MyFTPFileListProtocol(ftp.FTPFileListProtocol):
 
754
    def __init__(self):
 
755
        self.other = []
 
756
        ftp.FTPFileListProtocol.__init__(self)
 
757
 
 
758
    def unknownLine(self, line):
 
759
        self.other.append(line)
 
760
 
 
761
 
 
762
class FTPFileListingTests(unittest.TestCase):
 
763
    def getFilesForLines(self, lines):
 
764
        fileList = MyFTPFileListProtocol()
 
765
        d = loopback.loopbackAsync(PrintLines(lines), fileList)
 
766
        d.addCallback(lambda _: (fileList.files, fileList.other))
 
767
        return d
 
768
 
 
769
    def testOneLine(self):
 
770
        # This example line taken from the docstring for FTPFileListProtocol
 
771
        line = '-rw-r--r--   1 root     other        531 Jan 29 03:26 README'
 
772
        def check(((file,), other)):
 
773
            self.failIf(other, 'unexpect unparsable lines: %s' % repr(other))
 
774
            self.failUnless(file['filetype'] == '-', 'misparsed fileitem')
 
775
            self.failUnless(file['perms'] == 'rw-r--r--', 'misparsed perms')
 
776
            self.failUnless(file['owner'] == 'root', 'misparsed fileitem')
 
777
            self.failUnless(file['group'] == 'other', 'misparsed fileitem')
 
778
            self.failUnless(file['size'] == 531, 'misparsed fileitem')
 
779
            self.failUnless(file['date'] == 'Jan 29 03:26', 'misparsed fileitem')
 
780
            self.failUnless(file['filename'] == 'README', 'misparsed fileitem')
 
781
            self.failUnless(file['nlinks'] == 1, 'misparsed nlinks')
 
782
            self.failIf(file['linktarget'], 'misparsed linktarget')
 
783
        return self.getFilesForLines([line]).addCallback(check)
 
784
 
 
785
    def testVariantLines(self):
 
786
        line1 = 'drw-r--r--   2 root     other        531 Jan  9  2003 A'
 
787
        line2 = 'lrw-r--r--   1 root     other          1 Jan 29 03:26 B -> A'
 
788
        line3 = 'woohoo! '
 
789
        def check(((file1, file2), (other,))):
 
790
            self.failUnless(other == 'woohoo! \r', 'incorrect other line')
 
791
            # file 1
 
792
            self.failUnless(file1['filetype'] == 'd', 'misparsed fileitem')
 
793
            self.failUnless(file1['perms'] == 'rw-r--r--', 'misparsed perms')
 
794
            self.failUnless(file1['owner'] == 'root', 'misparsed owner')
 
795
            self.failUnless(file1['group'] == 'other', 'misparsed group')
 
796
            self.failUnless(file1['size'] == 531, 'misparsed size')
 
797
            self.failUnless(file1['date'] == 'Jan  9  2003', 'misparsed date')
 
798
            self.failUnless(file1['filename'] == 'A', 'misparsed filename')
 
799
            self.failUnless(file1['nlinks'] == 2, 'misparsed nlinks')
 
800
            self.failIf(file1['linktarget'], 'misparsed linktarget')
 
801
            # file 2
 
802
            self.failUnless(file2['filetype'] == 'l', 'misparsed fileitem')
 
803
            self.failUnless(file2['perms'] == 'rw-r--r--', 'misparsed perms')
 
804
            self.failUnless(file2['owner'] == 'root', 'misparsed owner')
 
805
            self.failUnless(file2['group'] == 'other', 'misparsed group')
 
806
            self.failUnless(file2['size'] == 1, 'misparsed size')
 
807
            self.failUnless(file2['date'] == 'Jan 29 03:26', 'misparsed date')
 
808
            self.failUnless(file2['filename'] == 'B', 'misparsed filename')
 
809
            self.failUnless(file2['nlinks'] == 1, 'misparsed nlinks')
 
810
            self.failUnless(file2['linktarget'] == 'A', 'misparsed linktarget')
 
811
        return self.getFilesForLines([line1, line2, line3]).addCallback(check)
 
812
 
 
813
    def testUnknownLine(self):
 
814
        def check((files, others)):
 
815
            self.failIf(files, 'unexpected file entries')
 
816
            self.failUnless(others == ['ABC\r', 'not a file\r'],
 
817
                            'incorrect unparsable lines: %s' % repr(others))
 
818
        return self.getFilesForLines(['ABC', 'not a file']).addCallback(check)
 
819
 
 
820
    def testYear(self):
 
821
        # This example derived from bug description in issue 514.
 
822
        fileList = ftp.FTPFileListProtocol()
 
823
        exampleLine = (
 
824
            '-rw-r--r--   1 root     other        531 Jan 29 2003 README\n')
 
825
        class PrintLine(protocol.Protocol):
 
826
            def connectionMade(self):
 
827
                self.transport.write(exampleLine)
 
828
                self.transport.loseConnection()
 
829
 
 
830
        def check(ignored):
 
831
            file = fileList.files[0]
 
832
            self.failUnless(file['size'] == 531, 'misparsed fileitem')
 
833
            self.failUnless(file['date'] == 'Jan 29 2003', 'misparsed fileitem')
 
834
            self.failUnless(file['filename'] == 'README', 'misparsed fileitem')
 
835
 
 
836
        d = loopback.loopbackAsync(PrintLine(), fileList)
 
837
        return d.addCallback(check)
 
838
 
 
839
 
 
840
class FTPClientTests(unittest.TestCase):
 
841
 
 
842
    def testFailedRETR(self):
 
843
        f = protocol.Factory()
 
844
        f.noisy = 0
 
845
        port = reactor.listenTCP(0, f, interface="127.0.0.1")
 
846
        self.addCleanup(port.stopListening)
 
847
        portNum = port.getHost().port
 
848
        # This test data derived from a bug report by ranty on #twisted
 
849
        responses = ['220 ready, dude (vsFTPd 1.0.0: beat me, break me)',
 
850
                     # USER anonymous
 
851
                     '331 Please specify the password.',
 
852
                     # PASS twisted@twistedmatrix.com
 
853
                     '230 Login successful. Have fun.',
 
854
                     # TYPE I
 
855
                     '200 Binary it is, then.',
 
856
                     # PASV
 
857
                     '227 Entering Passive Mode (127,0,0,1,%d,%d)' %
 
858
                     (portNum >> 8, portNum & 0xff),
 
859
                     # RETR /file/that/doesnt/exist
 
860
                     '550 Failed to open file.']
 
861
        f.buildProtocol = lambda addr: PrintLines(responses)
 
862
 
 
863
        client = ftp.FTPClient(passive=1)
 
864
        cc = protocol.ClientCreator(reactor, ftp.FTPClient, passive=1)
 
865
        d = cc.connectTCP('127.0.0.1', portNum)
 
866
        def gotClient(client):
 
867
            p = protocol.Protocol()
 
868
            return client.retrieveFile('/file/that/doesnt/exist', p)
 
869
        d.addCallback(gotClient)
 
870
        return self.assertFailure(d, ftp.CommandFailed)
 
871
 
 
872
    def test_errbacksUponDisconnect(self):
 
873
        """
 
874
        Test the ftp command errbacks when a connection lost happens during
 
875
        the operation.
 
876
        """
 
877
        ftpClient = ftp.FTPClient()
 
878
        tr = proto_helpers.StringTransportWithDisconnection()
 
879
        ftpClient.makeConnection(tr)
 
880
        tr.protocol = ftpClient
 
881
        d = ftpClient.list('some path', Dummy())
 
882
        m = []
 
883
        def _eb(failure):
 
884
            m.append(failure)
 
885
            return None
 
886
        d.addErrback(_eb)
 
887
        from twisted.internet.main import CONNECTION_LOST
 
888
        ftpClient.connectionLost(failure.Failure(CONNECTION_LOST))
 
889
        self.failUnless(m, m)
 
890
        return d
 
891
 
 
892
 
 
893
 
 
894
class FTPClientTestCase(unittest.TestCase):
 
895
    """
 
896
    Test advanced FTP client commands.
 
897
    """
 
898
    def setUp(self):
 
899
        """
 
900
        Create a FTP client and connect it to fake transport.
 
901
        """
 
902
        self.client = ftp.FTPClient()
 
903
        self.transport = proto_helpers.StringTransportWithDisconnection()
 
904
        self.client.makeConnection(self.transport)
 
905
        self.transport.protocol = self.client
 
906
 
 
907
 
 
908
    def tearDown(self):
 
909
        """
 
910
        Deliver disconnection notification to the client so that it can
 
911
        perform any cleanup which may be required.
 
912
        """
 
913
        self.client.connectionLost(error.ConnectionLost())
 
914
 
 
915
 
 
916
    def _testLogin(self):
 
917
        """
 
918
        Test the login part.
 
919
        """
 
920
        self.assertEquals(self.transport.value(), '')
 
921
        self.client.lineReceived(
 
922
            '331 Guest login ok, type your email address as password.')
 
923
        self.assertEquals(self.transport.value(), 'USER anonymous\r\n')
 
924
        self.transport.clear()
 
925
        self.client.lineReceived(
 
926
            '230 Anonymous login ok, access restrictions apply.')
 
927
        self.assertEquals(self.transport.value(), 'TYPE I\r\n')
 
928
        self.transport.clear()
 
929
        self.client.lineReceived('200 Type set to I.')
 
930
 
 
931
 
 
932
    def test_CDUP(self):
 
933
        """
 
934
        Test the CDUP command.
 
935
 
 
936
        L{ftp.FTPClient.cdup} should return a Deferred which fires with a
 
937
        sequence of one element which is the string the server sent
 
938
        indicating that the command was executed successfully.
 
939
 
 
940
        (XXX - This is a bad API)
 
941
        """
 
942
        def cbCdup(res):
 
943
            self.assertEquals(res[0], '250 Requested File Action Completed OK')
 
944
 
 
945
        self._testLogin()
 
946
        d = self.client.cdup().addCallback(cbCdup)
 
947
        self.assertEquals(self.transport.value(), 'CDUP\r\n')
 
948
        self.transport.clear()
 
949
        self.client.lineReceived('250 Requested File Action Completed OK')
 
950
        return d
 
951
 
 
952
 
 
953
    def test_failedCDUP(self):
 
954
        """
 
955
        Test L{ftp.FTPClient.cdup}'s handling of a failed CDUP command.
 
956
 
 
957
        When the CDUP command fails, the returned Deferred should errback
 
958
        with L{ftp.CommandFailed}.
 
959
        """
 
960
        self._testLogin()
 
961
        d = self.client.cdup()
 
962
        self.assertFailure(d, ftp.CommandFailed)
 
963
        self.assertEquals(self.transport.value(), 'CDUP\r\n')
 
964
        self.transport.clear()
 
965
        self.client.lineReceived('550 ..: No such file or directory')
 
966
        return d
 
967
 
 
968
 
 
969
    def test_PWD(self):
 
970
        """
 
971
        Test the PWD command.
 
972
 
 
973
        L{ftp.FTPClient.pwd} should return a Deferred which fires with a
 
974
        sequence of one element which is a string representing the current
 
975
        working directory on the server.
 
976
 
 
977
        (XXX - This is a bad API)
 
978
        """
 
979
        def cbPwd(res):
 
980
            self.assertEquals(ftp.parsePWDResponse(res[0]), "/bar/baz")
 
981
 
 
982
        self._testLogin()
 
983
        d = self.client.pwd().addCallback(cbPwd)
 
984
        self.assertEquals(self.transport.value(), 'PWD\r\n')
 
985
        self.client.lineReceived('257 "/bar/baz"')
 
986
        return d
 
987
 
 
988
 
 
989
    def test_failedPWD(self):
 
990
        """
 
991
        Test a failure in PWD command.
 
992
 
 
993
        When the PWD command fails, the returned Deferred should errback
 
994
        with L{ftp.CommandFailed}.
 
995
        """
 
996
        self._testLogin()
 
997
        d = self.client.pwd()
 
998
        self.assertFailure(d, ftp.CommandFailed)
 
999
        self.assertEquals(self.transport.value(), 'PWD\r\n')
 
1000
        self.client.lineReceived('550 /bar/baz: No such file or directory')
 
1001
        return d
 
1002
 
 
1003
 
 
1004
    def test_CWD(self):
 
1005
        """
 
1006
        Test the CWD command.
 
1007
 
 
1008
        L{ftp.FTPClient.cwd} should return a Deferred which fires with a
 
1009
        sequence of one element which is the string the server sent
 
1010
        indicating that the command was executed successfully.
 
1011
 
 
1012
        (XXX - This is a bad API)
 
1013
        """
 
1014
        def cbCwd(res):
 
1015
            self.assertEquals(res[0], '250 Requested File Action Completed OK')
 
1016
 
 
1017
        self._testLogin()
 
1018
        d = self.client.cwd("bar/foo").addCallback(cbCwd)
 
1019
        self.assertEquals(self.transport.value(), 'CWD bar/foo\r\n')
 
1020
        self.client.lineReceived('250 Requested File Action Completed OK')
 
1021
        return d
 
1022
 
 
1023
 
 
1024
    def test_failedCWD(self):
 
1025
        """
 
1026
        Test a failure in CWD command.
 
1027
 
 
1028
        When the PWD command fails, the returned Deferred should errback
 
1029
        with L{ftp.CommandFailed}.
 
1030
        """
 
1031
        self._testLogin()
 
1032
        d = self.client.cwd("bar/foo")
 
1033
        self.assertFailure(d, ftp.CommandFailed)
 
1034
        self.assertEquals(self.transport.value(), 'CWD bar/foo\r\n')
 
1035
        self.client.lineReceived('550 bar/foo: No such file or directory')
 
1036
        return d
 
1037
 
 
1038
 
 
1039
    def test_passiveRETR(self):
 
1040
        """
 
1041
        Test the RETR command in passive mode: get a file and verify its
 
1042
        content.
 
1043
 
 
1044
        L{ftp.FTPClient.retrieveFile} should return a Deferred which fires
 
1045
        with the protocol instance passed to it after the download has
 
1046
        completed.
 
1047
 
 
1048
        (XXX - This API should be based on producers and consumers)
 
1049
        """
 
1050
        def cbRetr(res, proto):
 
1051
            self.assertEquals(proto.buffer, 'x' * 1000)
 
1052
 
 
1053
        def cbConnect(host, port, factory):
 
1054
            self.assertEquals(host, '127.0.0.1')
 
1055
            self.assertEquals(port, 12345)
 
1056
            proto = factory.buildProtocol((host, port))
 
1057
            proto.makeConnection(proto_helpers.StringTransport())
 
1058
            self.client.lineReceived(
 
1059
                '150 File status okay; about to open data connection.')
 
1060
            proto.dataReceived("x" * 1000)
 
1061
            proto.connectionLost(failure.Failure(error.ConnectionDone("")))
 
1062
 
 
1063
        self.client.connectFactory = cbConnect
 
1064
        self._testLogin()
 
1065
        proto = _BufferingProtocol()
 
1066
        d = self.client.retrieveFile("spam", proto)
 
1067
        d.addCallback(cbRetr, proto)
 
1068
        self.assertEquals(self.transport.value(), 'PASV\r\n')
 
1069
        self.transport.clear()
 
1070
        self.client.lineReceived('227 Entering Passive Mode (%s).' %
 
1071
            (ftp.encodeHostPort('127.0.0.1', 12345),))
 
1072
        self.assertEquals(self.transport.value(), 'RETR spam\r\n')
 
1073
        self.transport.clear()
 
1074
        self.client.lineReceived('226 Transfer Complete.')
 
1075
        return d
 
1076
 
 
1077
 
 
1078
    def test_RETR(self):
 
1079
        """
 
1080
        Test the RETR command in non-passive mode.
 
1081
 
 
1082
        Like L{test_passiveRETR} but in the configuration where the server
 
1083
        establishes the data connection to the client, rather than the other
 
1084
        way around.
 
1085
        """
 
1086
        self.client.passive = False
 
1087
 
 
1088
        def generatePort(portCmd):
 
1089
            portCmd.text = 'PORT %s' % (ftp.encodeHostPort('127.0.0.1', 9876),)
 
1090
            portCmd.protocol.makeConnection(proto_helpers.StringTransport())
 
1091
            portCmd.protocol.dataReceived("x" * 1000)
 
1092
            portCmd.protocol.connectionLost(
 
1093
                failure.Failure(error.ConnectionDone("")))
 
1094
 
 
1095
        def cbRetr(res, proto):
 
1096
            self.assertEquals(proto.buffer, 'x' * 1000)
 
1097
 
 
1098
        self.client.generatePortCommand = generatePort
 
1099
        self._testLogin()
 
1100
        proto = _BufferingProtocol()
 
1101
        d = self.client.retrieveFile("spam", proto)
 
1102
        d.addCallback(cbRetr, proto)
 
1103
        self.assertEquals(self.transport.value(), 'PORT %s\r\n' %
 
1104
            (ftp.encodeHostPort('127.0.0.1', 9876),))
 
1105
        self.transport.clear()
 
1106
        self.client.lineReceived('200 PORT OK')
 
1107
        self.assertEquals(self.transport.value(), 'RETR spam\r\n')
 
1108
        self.transport.clear()
 
1109
        self.client.lineReceived('226 Transfer Complete.')
 
1110
        return d
 
1111
 
 
1112
 
 
1113
    def test_failedRETR(self):
 
1114
        """
 
1115
        Try to RETR an unexisting file.
 
1116
 
 
1117
        L{ftp.FTPClient.retrieveFile} should return a Deferred which
 
1118
        errbacks with L{ftp.CommandFailed} if the server indicates the file
 
1119
        cannot be transferred for some reason.
 
1120
        """
 
1121
        def cbConnect(host, port, factory):
 
1122
            self.assertEquals(host, '127.0.0.1')
 
1123
            self.assertEquals(port, 12345)
 
1124
            proto = factory.buildProtocol((host, port))
 
1125
            proto.makeConnection(proto_helpers.StringTransport())
 
1126
            self.client.lineReceived(
 
1127
                '150 File status okay; about to open data connection.')
 
1128
            proto.connectionLost(failure.Failure(error.ConnectionDone("")))
 
1129
 
 
1130
        self.client.connectFactory = cbConnect
 
1131
        self._testLogin()
 
1132
        proto = _BufferingProtocol()
 
1133
        d = self.client.retrieveFile("spam", proto)
 
1134
        self.assertFailure(d, ftp.CommandFailed)
 
1135
        self.assertEquals(self.transport.value(), 'PASV\r\n')
 
1136
        self.transport.clear()
 
1137
        self.client.lineReceived('227 Entering Passive Mode (%s).' %
 
1138
            (ftp.encodeHostPort('127.0.0.1', 12345),))
 
1139
        self.assertEquals(self.transport.value(), 'RETR spam\r\n')
 
1140
        self.transport.clear()
 
1141
        self.client.lineReceived('550 spam: No such file or directory')
 
1142
        return d
 
1143
 
 
1144
 
 
1145
    def test_lostRETR(self):
 
1146
        """
 
1147
        Try a RETR, but disconnect during the transfer.
 
1148
        L{ftp.FTPClient.retrieveFile} should return a Deferred which
 
1149
        errbacks with L{ftp.ConnectionLost)
 
1150
        """
 
1151
        self.client.passive = False
 
1152
 
 
1153
        l = []
 
1154
        def generatePort(portCmd):
 
1155
            portCmd.text = 'PORT %s' % (ftp.encodeHostPort('127.0.0.1', 9876),)
 
1156
            tr = proto_helpers.StringTransportWithDisconnection()
 
1157
            portCmd.protocol.makeConnection(tr)
 
1158
            tr.protocol = portCmd.protocol
 
1159
            portCmd.protocol.dataReceived("x" * 500)
 
1160
            l.append(tr)
 
1161
 
 
1162
        self.client.generatePortCommand = generatePort
 
1163
        self._testLogin()
 
1164
        proto = _BufferingProtocol()
 
1165
        d = self.client.retrieveFile("spam", proto)
 
1166
        self.assertEquals(self.transport.value(), 'PORT %s\r\n' %
 
1167
            (ftp.encodeHostPort('127.0.0.1', 9876),))
 
1168
        self.transport.clear()
 
1169
        self.client.lineReceived('200 PORT OK')
 
1170
        self.assertEquals(self.transport.value(), 'RETR spam\r\n')
 
1171
 
 
1172
        self.assert_(l)
 
1173
        l[0].loseConnection()
 
1174
        self.transport.loseConnection()
 
1175
        self.assertFailure(d, ftp.ConnectionLost)
 
1176
        return d
 
1177
 
 
1178
 
 
1179
    def test_passiveSTOR(self):
 
1180
        """
 
1181
        Test the STOR command: send a file and verify its content.
 
1182
 
 
1183
        L{ftp.FTPClient.storeFile} should return a two-tuple of Deferreds.
 
1184
        The first of which should fire with a protocol instance when the
 
1185
        data connection has been established and is responsible for sending
 
1186
        the contents of the file.  The second of which should fire when the
 
1187
        upload has completed, the data connection has been closed, and the
 
1188
        server has acknowledged receipt of the file.
 
1189
 
 
1190
        (XXX - storeFile should take a producer as an argument, instead, and
 
1191
        only return a Deferred which fires when the upload has succeeded or
 
1192
        failed).
 
1193
        """
 
1194
        tr = proto_helpers.StringTransport()
 
1195
        def cbStore(sender):
 
1196
            self.client.lineReceived(
 
1197
                '150 File status okay; about to open data connection.')
 
1198
            sender.transport.write("x" * 1000)
 
1199
            sender.finish()
 
1200
            sender.connectionLost(failure.Failure(error.ConnectionDone("")))
 
1201
 
 
1202
        def cbFinish(ign):
 
1203
            self.assertEquals(tr.value(), "x" * 1000)
 
1204
 
 
1205
        def cbConnect(host, port, factory):
 
1206
            self.assertEquals(host, '127.0.0.1')
 
1207
            self.assertEquals(port, 12345)
 
1208
            proto = factory.buildProtocol((host, port))
 
1209
            proto.makeConnection(tr)
 
1210
 
 
1211
        self.client.connectFactory = cbConnect
 
1212
        self._testLogin()
 
1213
        d1, d2 = self.client.storeFile("spam")
 
1214
        d1.addCallback(cbStore)
 
1215
        d2.addCallback(cbFinish)
 
1216
        self.assertEquals(self.transport.value(), 'PASV\r\n')
 
1217
        self.transport.clear()
 
1218
        self.client.lineReceived('227 Entering Passive Mode (%s).' %
 
1219
            (ftp.encodeHostPort('127.0.0.1', 12345),))
 
1220
        self.assertEquals(self.transport.value(), 'STOR spam\r\n')
 
1221
        self.transport.clear()
 
1222
        self.client.lineReceived('226 Transfer Complete.')
 
1223
        return defer.gatherResults([d1, d2])
 
1224
 
 
1225
 
 
1226
    def test_failedSTOR(self):
 
1227
        """
 
1228
        Test a failure in the STOR command.
 
1229
 
 
1230
        If the server does not acknowledge successful receipt of the
 
1231
        uploaded file, the second Deferred returned by
 
1232
        L{ftp.FTPClient.storeFile} should errback with L{ftp.CommandFailed}.
 
1233
        """
 
1234
        tr = proto_helpers.StringTransport()
 
1235
        def cbStore(sender):
 
1236
            self.client.lineReceived(
 
1237
                '150 File status okay; about to open data connection.')
 
1238
            sender.transport.write("x" * 1000)
 
1239
            sender.finish()
 
1240
            sender.connectionLost(failure.Failure(error.ConnectionDone("")))
 
1241
 
 
1242
        def cbConnect(host, port, factory):
 
1243
            self.assertEquals(host, '127.0.0.1')
 
1244
            self.assertEquals(port, 12345)
 
1245
            proto = factory.buildProtocol((host, port))
 
1246
            proto.makeConnection(tr)
 
1247
 
 
1248
        self.client.connectFactory = cbConnect
 
1249
        self._testLogin()
 
1250
        d1, d2 = self.client.storeFile("spam")
 
1251
        d1.addCallback(cbStore)
 
1252
        self.assertFailure(d2, ftp.CommandFailed)
 
1253
        self.assertEquals(self.transport.value(), 'PASV\r\n')
 
1254
        self.transport.clear()
 
1255
        self.client.lineReceived('227 Entering Passive Mode (%s).' %
 
1256
            (ftp.encodeHostPort('127.0.0.1', 12345),))
 
1257
        self.assertEquals(self.transport.value(), 'STOR spam\r\n')
 
1258
        self.transport.clear()
 
1259
        self.client.lineReceived(
 
1260
            '426 Transfer aborted.  Data connection closed.')
 
1261
        return defer.gatherResults([d1, d2])
 
1262
 
 
1263
 
 
1264
    def test_STOR(self):
 
1265
        """
 
1266
        Test the STOR command in non-passive mode.
 
1267
 
 
1268
        Like L{test_passiveSTOR} but in the configuration where the server
 
1269
        establishes the data connection to the client, rather than the other
 
1270
        way around.
 
1271
        """
 
1272
        tr = proto_helpers.StringTransport()
 
1273
        self.client.passive = False
 
1274
        def generatePort(portCmd):
 
1275
            portCmd.text = 'PORT %s' % ftp.encodeHostPort('127.0.0.1', 9876)
 
1276
            portCmd.protocol.makeConnection(tr)
 
1277
 
 
1278
        def cbStore(sender):
 
1279
            self.assertEquals(self.transport.value(), 'PORT %s\r\n' %
 
1280
                (ftp.encodeHostPort('127.0.0.1', 9876),))
 
1281
            self.transport.clear()
 
1282
            self.client.lineReceived('200 PORT OK')
 
1283
            self.assertEquals(self.transport.value(), 'STOR spam\r\n')
 
1284
            self.transport.clear()
 
1285
            self.client.lineReceived(
 
1286
                '150 File status okay; about to open data connection.')
 
1287
            sender.transport.write("x" * 1000)
 
1288
            sender.finish()
 
1289
            sender.connectionLost(failure.Failure(error.ConnectionDone("")))
 
1290
            self.client.lineReceived('226 Transfer Complete.')
 
1291
 
 
1292
        def cbFinish(ign):
 
1293
            self.assertEquals(tr.value(), "x" * 1000)
 
1294
 
 
1295
        self.client.generatePortCommand = generatePort
 
1296
        self._testLogin()
 
1297
        d1, d2 = self.client.storeFile("spam")
 
1298
        d1.addCallback(cbStore)
 
1299
        d2.addCallback(cbFinish)
 
1300
        return defer.gatherResults([d1, d2])
 
1301
 
 
1302
 
 
1303
    def test_passiveLIST(self):
 
1304
        """
 
1305
        Test the LIST command.
 
1306
 
 
1307
        L{ftp.FTPClient.list} should return a Deferred which fires with a
 
1308
        protocol instance which was passed to list after the command has
 
1309
        succeeded.
 
1310
 
 
1311
        (XXX - This is a very unfortunate API; if my understanding is
 
1312
        correct, the results are always at least line-oriented, so allowing
 
1313
        a per-line parser function to be specified would make this simpler,
 
1314
        but a default implementation should really be provided which knows
 
1315
        how to deal with all the formats used in real servers, so
 
1316
        application developers never have to care about this insanity.  It
 
1317
        would also be nice to either get back a Deferred of a list of
 
1318
        filenames or to be able to consume the files as they are received
 
1319
        (which the current API does allow, but in a somewhat inconvenient
 
1320
        fashion) -exarkun)
 
1321
        """
 
1322
        def cbList(res, fileList):
 
1323
            fls = [f["filename"] for f in fileList.files]
 
1324
            expected = ["foo", "bar", "baz"]
 
1325
            expected.sort()
 
1326
            fls.sort()
 
1327
            self.assertEquals(fls, expected)
 
1328
 
 
1329
        def cbConnect(host, port, factory):
 
1330
            self.assertEquals(host, '127.0.0.1')
 
1331
            self.assertEquals(port, 12345)
 
1332
            proto = factory.buildProtocol((host, port))
 
1333
            proto.makeConnection(proto_helpers.StringTransport())
 
1334
            self.client.lineReceived(
 
1335
                '150 File status okay; about to open data connection.')
 
1336
            sending = [
 
1337
                '-rw-r--r--    0 spam      egg      100 Oct 10 2006 foo\r\n',
 
1338
                '-rw-r--r--    3 spam      egg      100 Oct 10 2006 bar\r\n',
 
1339
                '-rw-r--r--    4 spam      egg      100 Oct 10 2006 baz\r\n',
 
1340
            ]
 
1341
            for i in sending:
 
1342
                proto.dataReceived(i)
 
1343
            proto.connectionLost(failure.Failure(error.ConnectionDone("")))
 
1344
 
 
1345
        self.client.connectFactory = cbConnect
 
1346
        self._testLogin()
 
1347
        fileList = ftp.FTPFileListProtocol()
 
1348
        d = self.client.list('foo/bar', fileList).addCallback(cbList, fileList)
 
1349
        self.assertEquals(self.transport.value(), 'PASV\r\n')
 
1350
        self.transport.clear()
 
1351
        self.client.lineReceived('227 Entering Passive Mode (%s).' %
 
1352
            (ftp.encodeHostPort('127.0.0.1', 12345),))
 
1353
        self.assertEquals(self.transport.value(), 'LIST foo/bar\r\n')
 
1354
        self.client.lineReceived('226 Transfer Complete.')
 
1355
        return d
 
1356
 
 
1357
 
 
1358
    def test_LIST(self):
 
1359
        """
 
1360
        Test the LIST command in non-passive mode.
 
1361
 
 
1362
        Like L{test_passiveLIST} but in the configuration where the server
 
1363
        establishes the data connection to the client, rather than the other
 
1364
        way around.
 
1365
        """
 
1366
        self.client.passive = False
 
1367
        def generatePort(portCmd):
 
1368
            portCmd.text = 'PORT %s' % (ftp.encodeHostPort('127.0.0.1', 9876),)
 
1369
            portCmd.protocol.makeConnection(proto_helpers.StringTransport())
 
1370
            self.client.lineReceived(
 
1371
                '150 File status okay; about to open data connection.')
 
1372
            sending = [
 
1373
                '-rw-r--r--    0 spam      egg      100 Oct 10 2006 foo\r\n',
 
1374
                '-rw-r--r--    3 spam      egg      100 Oct 10 2006 bar\r\n',
 
1375
                '-rw-r--r--    4 spam      egg      100 Oct 10 2006 baz\r\n',
 
1376
            ]
 
1377
            for i in sending:
 
1378
                portCmd.protocol.dataReceived(i)
 
1379
            portCmd.protocol.connectionLost(
 
1380
                failure.Failure(error.ConnectionDone("")))
 
1381
 
 
1382
        def cbList(res, fileList):
 
1383
            fls = [f["filename"] for f in fileList.files]
 
1384
            expected = ["foo", "bar", "baz"]
 
1385
            expected.sort()
 
1386
            fls.sort()
 
1387
            self.assertEquals(fls, expected)
 
1388
 
 
1389
        self.client.generatePortCommand = generatePort
 
1390
        self._testLogin()
 
1391
        fileList = ftp.FTPFileListProtocol()
 
1392
        d = self.client.list('foo/bar', fileList).addCallback(cbList, fileList)
 
1393
        self.assertEquals(self.transport.value(), 'PORT %s\r\n' %
 
1394
            (ftp.encodeHostPort('127.0.0.1', 9876),))
 
1395
        self.transport.clear()
 
1396
        self.client.lineReceived('200 PORT OK')
 
1397
        self.assertEquals(self.transport.value(), 'LIST foo/bar\r\n')
 
1398
        self.transport.clear()
 
1399
        self.client.lineReceived('226 Transfer Complete.')
 
1400
        return d
 
1401
 
 
1402
 
 
1403
    def test_failedLIST(self):
 
1404
        """
 
1405
        Test a failure in LIST command.
 
1406
 
 
1407
        L{ftp.FTPClient.list} should return a Deferred which fails with
 
1408
        L{ftp.CommandFailed} if the server indicates the indicated path is
 
1409
        invalid for some reason.
 
1410
        """
 
1411
        def cbConnect(host, port, factory):
 
1412
            self.assertEquals(host, '127.0.0.1')
 
1413
            self.assertEquals(port, 12345)
 
1414
            proto = factory.buildProtocol((host, port))
 
1415
            proto.makeConnection(proto_helpers.StringTransport())
 
1416
            self.client.lineReceived(
 
1417
                '150 File status okay; about to open data connection.')
 
1418
            proto.connectionLost(failure.Failure(error.ConnectionDone("")))
 
1419
 
 
1420
        self.client.connectFactory = cbConnect
 
1421
        self._testLogin()
 
1422
        fileList = ftp.FTPFileListProtocol()
 
1423
        d = self.client.list('foo/bar', fileList)
 
1424
        self.assertFailure(d, ftp.CommandFailed)
 
1425
        self.assertEquals(self.transport.value(), 'PASV\r\n')
 
1426
        self.transport.clear()
 
1427
        self.client.lineReceived('227 Entering Passive Mode (%s).' %
 
1428
            (ftp.encodeHostPort('127.0.0.1', 12345),))
 
1429
        self.assertEquals(self.transport.value(), 'LIST foo/bar\r\n')
 
1430
        self.client.lineReceived('550 foo/bar: No such file or directory')
 
1431
        return d
 
1432
 
 
1433
 
 
1434
    def test_NLST(self):
 
1435
        """
 
1436
        Test the NLST command in non-passive mode.
 
1437
 
 
1438
        L{ftp.FTPClient.nlst} should return a Deferred which fires with a
 
1439
        list of filenames when the list command has completed.
 
1440
        """
 
1441
        self.client.passive = False
 
1442
        def generatePort(portCmd):
 
1443
            portCmd.text = 'PORT %s' % (ftp.encodeHostPort('127.0.0.1', 9876),)
 
1444
            portCmd.protocol.makeConnection(proto_helpers.StringTransport())
 
1445
            self.client.lineReceived(
 
1446
                '150 File status okay; about to open data connection.')
 
1447
            portCmd.protocol.dataReceived('foo\r\n')
 
1448
            portCmd.protocol.dataReceived('bar\r\n')
 
1449
            portCmd.protocol.dataReceived('baz\r\n')
 
1450
            portCmd.protocol.connectionLost(
 
1451
                failure.Failure(error.ConnectionDone("")))
 
1452
 
 
1453
        def cbList(res, proto):
 
1454
            fls = proto.buffer.splitlines()
 
1455
            expected = ["foo", "bar", "baz"]
 
1456
            expected.sort()
 
1457
            fls.sort()
 
1458
            self.assertEquals(fls, expected)
 
1459
 
 
1460
        self.client.generatePortCommand = generatePort
 
1461
        self._testLogin()
 
1462
        lstproto = _BufferingProtocol()
 
1463
        d = self.client.nlst('foo/bar', lstproto).addCallback(cbList, lstproto)
 
1464
        self.assertEquals(self.transport.value(), 'PORT %s\r\n' %
 
1465
            (ftp.encodeHostPort('127.0.0.1', 9876),))
 
1466
        self.transport.clear()
 
1467
        self.client.lineReceived('200 PORT OK')
 
1468
        self.assertEquals(self.transport.value(), 'NLST foo/bar\r\n')
 
1469
        self.client.lineReceived('226 Transfer Complete.')
 
1470
        return d
 
1471
 
 
1472
 
 
1473
    def test_passiveNLST(self):
 
1474
        """
 
1475
        Test the NLST command.
 
1476
 
 
1477
        Like L{test_passiveNLST} but in the configuration where the server
 
1478
        establishes the data connection to the client, rather than the other
 
1479
        way around.
 
1480
        """
 
1481
        def cbList(res, proto):
 
1482
            fls = proto.buffer.splitlines()
 
1483
            expected = ["foo", "bar", "baz"]
 
1484
            expected.sort()
 
1485
            fls.sort()
 
1486
            self.assertEquals(fls, expected)
 
1487
 
 
1488
        def cbConnect(host, port, factory):
 
1489
            self.assertEquals(host, '127.0.0.1')
 
1490
            self.assertEquals(port, 12345)
 
1491
            proto = factory.buildProtocol((host, port))
 
1492
            proto.makeConnection(proto_helpers.StringTransport())
 
1493
            self.client.lineReceived(
 
1494
                '150 File status okay; about to open data connection.')
 
1495
            proto.dataReceived('foo\r\n')
 
1496
            proto.dataReceived('bar\r\n')
 
1497
            proto.dataReceived('baz\r\n')
 
1498
            proto.connectionLost(failure.Failure(error.ConnectionDone("")))
 
1499
 
 
1500
        self.client.connectFactory = cbConnect
 
1501
        self._testLogin()
 
1502
        lstproto = _BufferingProtocol()
 
1503
        d = self.client.nlst('foo/bar', lstproto).addCallback(cbList, lstproto)
 
1504
        self.assertEquals(self.transport.value(), 'PASV\r\n')
 
1505
        self.transport.clear()
 
1506
        self.client.lineReceived('227 Entering Passive Mode (%s).' %
 
1507
            (ftp.encodeHostPort('127.0.0.1', 12345),))
 
1508
        self.assertEquals(self.transport.value(), 'NLST foo/bar\r\n')
 
1509
        self.client.lineReceived('226 Transfer Complete.')
 
1510
        return d
 
1511
 
 
1512
 
 
1513
    def test_failedNLST(self):
 
1514
        """
 
1515
        Test a failure in NLST command.
 
1516
 
 
1517
        L{ftp.FTPClient.nlst} should return a Deferred which fails with
 
1518
        L{ftp.CommandFailed} if the server indicates the indicated path is
 
1519
        invalid for some reason.
 
1520
        """
 
1521
        tr = proto_helpers.StringTransport()
 
1522
        def cbConnect(host, port, factory):
 
1523
            self.assertEquals(host, '127.0.0.1')
 
1524
            self.assertEquals(port, 12345)
 
1525
            proto = factory.buildProtocol((host, port))
 
1526
            proto.makeConnection(tr)
 
1527
            self.client.lineReceived(
 
1528
                '150 File status okay; about to open data connection.')
 
1529
            proto.connectionLost(failure.Failure(error.ConnectionDone("")))
 
1530
 
 
1531
        self.client.connectFactory = cbConnect
 
1532
        self._testLogin()
 
1533
        lstproto = _BufferingProtocol()
 
1534
        d = self.client.nlst('foo/bar', lstproto)
 
1535
        self.assertFailure(d, ftp.CommandFailed)
 
1536
        self.assertEquals(self.transport.value(), 'PASV\r\n')
 
1537
        self.transport.clear()
 
1538
        self.client.lineReceived('227 Entering Passive Mode (%s).' %
 
1539
            (ftp.encodeHostPort('127.0.0.1', 12345),))
 
1540
        self.assertEquals(self.transport.value(), 'NLST foo/bar\r\n')
 
1541
        self.client.lineReceived('550 foo/bar: No such file or directory')
 
1542
        return d
 
1543
 
 
1544
 
 
1545
    def test_changeDirectoryDeprecated(self):
 
1546
        """
 
1547
        L{ftp.FTPClient.changeDirectory} is deprecated and the direct caller of
 
1548
        it is warned of this.
 
1549
        """
 
1550
        self._testLogin()
 
1551
        d = self.assertWarns(
 
1552
            DeprecationWarning,
 
1553
            "FTPClient.changeDirectory is deprecated in Twisted 8.2 and "
 
1554
            "newer.  Use FTPClient.cwd instead.",
 
1555
            __file__,
 
1556
            lambda: self.client.changeDirectory('.'))
 
1557
        # This is necessary to make the Deferred fire.  The Deferred needs
 
1558
        # to fire so that tearDown doesn't cause it to errback and fail this
 
1559
        # or (more likely) a later test.
 
1560
        self.client.lineReceived('250 success')
 
1561
        return d
 
1562
 
 
1563
 
 
1564
    def test_changeDirectory(self):
 
1565
        """
 
1566
        Test the changeDirectory method.
 
1567
 
 
1568
        L{ftp.FTPClient.changeDirectory} should return a Deferred which fires
 
1569
        with True if succeeded.
 
1570
        """
 
1571
        def cbCd(res):
 
1572
            self.assertEquals(res, True)
 
1573
 
 
1574
        self._testLogin()
 
1575
        d = self.client.changeDirectory("bar/foo").addCallback(cbCd)
 
1576
        self.assertEquals(self.transport.value(), 'CWD bar/foo\r\n')
 
1577
        self.client.lineReceived('250 Requested File Action Completed OK')
 
1578
        return d
 
1579
    test_changeDirectory.suppress = [_changeDirectorySuppression]
 
1580
 
 
1581
 
 
1582
    def test_failedChangeDirectory(self):
 
1583
        """
 
1584
        Test a failure in the changeDirectory method.
 
1585
 
 
1586
        The behaviour here is the same as a failed CWD.
 
1587
        """
 
1588
        self._testLogin()
 
1589
        d = self.client.changeDirectory("bar/foo")
 
1590
        self.assertFailure(d, ftp.CommandFailed)
 
1591
        self.assertEquals(self.transport.value(), 'CWD bar/foo\r\n')
 
1592
        self.client.lineReceived('550 bar/foo: No such file or directory')
 
1593
        return d
 
1594
    test_failedChangeDirectory.suppress = [_changeDirectorySuppression]
 
1595
 
 
1596
 
 
1597
    def test_strangeFailedChangeDirectory(self):
 
1598
        """
 
1599
        Test a strange failure in changeDirectory method.
 
1600
 
 
1601
        L{ftp.FTPClient.changeDirectory} is stricter than CWD as it checks
 
1602
        code 250 for success.
 
1603
        """
 
1604
        self._testLogin()
 
1605
        d = self.client.changeDirectory("bar/foo")
 
1606
        self.assertFailure(d, ftp.CommandFailed)
 
1607
        self.assertEquals(self.transport.value(), 'CWD bar/foo\r\n')
 
1608
        self.client.lineReceived('252 I do what I want !')
 
1609
        return d
 
1610
    test_strangeFailedChangeDirectory.suppress = [_changeDirectorySuppression]
 
1611
 
 
1612
 
 
1613
    def test_renameFromTo(self):
 
1614
        """
 
1615
        L{ftp.FTPClient.rename} issues I{RNTO} and I{RNFR} commands and returns
 
1616
        a L{Deferred} which fires when a file has successfully been renamed.
 
1617
        """
 
1618
        self._testLogin()
 
1619
 
 
1620
        d = self.client.rename("/spam", "/ham")
 
1621
        self.assertEqual(self.transport.value(), 'RNFR /spam\r\n')
 
1622
        self.transport.clear()
 
1623
 
 
1624
        fromResponse = (
 
1625
            '350 Requested file action pending further information.\r\n')
 
1626
        self.client.lineReceived(fromResponse)
 
1627
        self.assertEqual(self.transport.value(), 'RNTO /ham\r\n')
 
1628
        toResponse = (
 
1629
            '250 Requested File Action Completed OK')
 
1630
        self.client.lineReceived(toResponse)
 
1631
 
 
1632
        d.addCallback(self.assertEqual, ([fromResponse], [toResponse]))
 
1633
        return d
 
1634
 
 
1635
 
 
1636
    def test_renameFromToEscapesPaths(self):
 
1637
        """
 
1638
        L{ftp.FTPClient.rename} issues I{RNTO} and I{RNFR} commands with paths
 
1639
        escaped according to U{http://cr.yp.to/ftp/filesystem.html}.
 
1640
        """
 
1641
        self._testLogin()
 
1642
 
 
1643
        fromFile = "/foo/ba\nr/baz"
 
1644
        toFile = "/qu\nux"
 
1645
        self.client.rename(fromFile, toFile)
 
1646
        self.client.lineReceived("350 ")
 
1647
        self.client.lineReceived("250 ")
 
1648
        self.assertEqual(
 
1649
            self.transport.value(),
 
1650
            "RNFR /foo/ba\x00r/baz\r\n"
 
1651
            "RNTO /qu\x00ux\r\n")
 
1652
 
 
1653
 
 
1654
    def test_renameFromToFailingOnFirstError(self):
 
1655
        """
 
1656
        The L{Deferred} returned by L{ftp.FTPClient.rename} is errbacked with
 
1657
        L{CommandFailed} if the I{RNFR} command receives an error response code
 
1658
        (for example, because the file does not exist).
 
1659
        """
 
1660
        self._testLogin()
 
1661
 
 
1662
        d = self.client.rename("/spam", "/ham")
 
1663
        self.assertEqual(self.transport.value(), 'RNFR /spam\r\n')
 
1664
        self.transport.clear()
 
1665
 
 
1666
        self.client.lineReceived('550 Requested file unavailable.\r\n')
 
1667
        # The RNTO should not execute since the RNFR failed.
 
1668
        self.assertEqual(self.transport.value(), '')
 
1669
 
 
1670
        return self.assertFailure(d, ftp.CommandFailed)
 
1671
 
 
1672
 
 
1673
    def test_renameFromToFailingOnRenameTo(self):
 
1674
        """
 
1675
        The L{Deferred} returned by L{ftp.FTPClient.rename} is errbacked with
 
1676
        L{CommandFailed} if the I{RNTO} command receives an error response code
 
1677
        (for example, because the destination directory does not exist).
 
1678
        """
 
1679
        self._testLogin()
 
1680
 
 
1681
        d = self.client.rename("/spam", "/ham")
 
1682
        self.assertEqual(self.transport.value(), 'RNFR /spam\r\n')
 
1683
        self.transport.clear()
 
1684
 
 
1685
        self.client.lineReceived('350 Requested file action pending further information.\r\n')
 
1686
        self.assertEqual(self.transport.value(), 'RNTO /ham\r\n')
 
1687
        self.client.lineReceived('550 Requested file unavailable.\r\n')
 
1688
        return self.assertFailure(d, ftp.CommandFailed)
 
1689
 
 
1690
 
 
1691
    def test_makeDirectory(self):
 
1692
        """
 
1693
        L{ftp.FTPClient.makeDirectory} issues a I{MKD} command and returns a
 
1694
        L{Deferred} which is called back with the server's response if the
 
1695
        directory is created.
 
1696
        """
 
1697
        self._testLogin()
 
1698
 
 
1699
        d = self.client.makeDirectory("/spam")
 
1700
        self.assertEquals(self.transport.value(), 'MKD /spam\r\n')
 
1701
        self.client.lineReceived('257 "/spam" created.')
 
1702
        return d.addCallback(self.assertEqual, ['257 "/spam" created.'])
 
1703
 
 
1704
 
 
1705
    def test_makeDirectoryPathEscape(self):
 
1706
        """
 
1707
        L{ftp.FTPClient.makeDirectory} escapes the path name it sends according
 
1708
        to U{http://cr.yp.to/ftp/filesystem.html}.
 
1709
        """
 
1710
        self._testLogin()
 
1711
        d = self.client.makeDirectory("/sp\nam")
 
1712
        self.assertEqual(self.transport.value(), 'MKD /sp\x00am\r\n')
 
1713
        # This is necessary to make the Deferred fire.  The Deferred needs
 
1714
        # to fire so that tearDown doesn't cause it to errback and fail this
 
1715
        # or (more likely) a later test.
 
1716
        self.client.lineReceived('257 win')
 
1717
        return d
 
1718
 
 
1719
 
 
1720
    def test_failedMakeDirectory(self):
 
1721
        """
 
1722
        L{ftp.FTPClient.makeDirectory} returns a L{Deferred} which is errbacked
 
1723
        with L{CommandFailed} if the server returns an error response code.
 
1724
        """
 
1725
        self._testLogin()
 
1726
 
 
1727
        d = self.client.makeDirectory("/spam")
 
1728
        self.assertEquals(self.transport.value(), 'MKD /spam\r\n')
 
1729
        self.client.lineReceived('550 PERMISSION DENIED')
 
1730
        return self.assertFailure(d, ftp.CommandFailed)
 
1731
 
 
1732
 
 
1733
    def test_getDirectory(self):
 
1734
        """
 
1735
        Test the getDirectory method.
 
1736
 
 
1737
        L{ftp.FTPClient.getDirectory} should return a Deferred which fires with
 
1738
        the current directory on the server. It wraps PWD command.
 
1739
        """
 
1740
        def cbGet(res):
 
1741
            self.assertEquals(res, "/bar/baz")
 
1742
 
 
1743
        self._testLogin()
 
1744
        d = self.client.getDirectory().addCallback(cbGet)
 
1745
        self.assertEquals(self.transport.value(), 'PWD\r\n')
 
1746
        self.client.lineReceived('257 "/bar/baz"')
 
1747
        return d
 
1748
 
 
1749
 
 
1750
    def test_failedGetDirectory(self):
 
1751
        """
 
1752
        Test a failure in getDirectory method.
 
1753
 
 
1754
        The behaviour should be the same as PWD.
 
1755
        """
 
1756
        self._testLogin()
 
1757
        d = self.client.getDirectory()
 
1758
        self.assertFailure(d, ftp.CommandFailed)
 
1759
        self.assertEquals(self.transport.value(), 'PWD\r\n')
 
1760
        self.client.lineReceived('550 /bar/baz: No such file or directory')
 
1761
        return d
 
1762
 
 
1763
 
 
1764
    def test_anotherFailedGetDirectory(self):
 
1765
        """
 
1766
        Test a different failure in getDirectory method.
 
1767
 
 
1768
        The response should be quoted to be parsed, so it returns an error
 
1769
        otherwise.
 
1770
        """
 
1771
        self._testLogin()
 
1772
        d = self.client.getDirectory()
 
1773
        self.assertFailure(d, ftp.CommandFailed)
 
1774
        self.assertEquals(self.transport.value(), 'PWD\r\n')
 
1775
        self.client.lineReceived('257 /bar/baz')
 
1776
        return d
 
1777
 
 
1778
 
 
1779
    def test_removeFile(self):
 
1780
        """
 
1781
        L{ftp.FTPClient.removeFile} sends a I{DELE} command to the server for
 
1782
        the indicated file and returns a Deferred which fires after the server
 
1783
        sends a 250 response code.
 
1784
        """
 
1785
        self._testLogin()
 
1786
        d = self.client.removeFile("/tmp/test")
 
1787
        self.assertEquals(self.transport.value(), 'DELE /tmp/test\r\n')
 
1788
        response = '250 Requested file action okay, completed.'
 
1789
        self.client.lineReceived(response)
 
1790
        return d.addCallback(self.assertEqual, [response])
 
1791
 
 
1792
 
 
1793
    def test_failedRemoveFile(self):
 
1794
        """
 
1795
        If the server returns a response code other than 250 in response to a
 
1796
        I{DELE} sent by L{ftp.FTPClient.removeFile}, the L{Deferred} returned
 
1797
        by C{removeFile} is errbacked with a L{Failure} wrapping a
 
1798
        L{CommandFailed}.
 
1799
        """
 
1800
        self._testLogin()
 
1801
        d = self.client.removeFile("/tmp/test")
 
1802
        self.assertEquals(self.transport.value(), 'DELE /tmp/test\r\n')
 
1803
        response = '501 Syntax error in parameters or arguments.'
 
1804
        self.client.lineReceived(response)
 
1805
        d = self.assertFailure(d, ftp.CommandFailed)
 
1806
        d.addCallback(lambda exc: self.assertEqual(exc.args, ([response],)))
 
1807
        return d
 
1808
 
 
1809
 
 
1810
    def test_unparsableRemoveFileResponse(self):
 
1811
        """
 
1812
        If the server returns a response line which cannot be parsed, the
 
1813
        L{Deferred} returned by L{ftp.FTPClient.removeFile} is errbacked with a
 
1814
        L{BadResponse} containing the response.
 
1815
        """
 
1816
        self._testLogin()
 
1817
        d = self.client.removeFile("/tmp/test")
 
1818
        response = '765 blah blah blah'
 
1819
        self.client.lineReceived(response)
 
1820
        d = self.assertFailure(d, ftp.BadResponse)
 
1821
        d.addCallback(lambda exc: self.assertEqual(exc.args, ([response],)))
 
1822
        return d
 
1823
 
 
1824
 
 
1825
    def test_multilineRemoveFileResponse(self):
 
1826
        """
 
1827
        If the server returns multiple response lines, the L{Deferred} returned
 
1828
        by L{ftp.FTPClient.removeFile} is still fired with a true value if the
 
1829
        ultimate response code is 250.
 
1830
        """
 
1831
        self._testLogin()
 
1832
        d = self.client.removeFile("/tmp/test")
 
1833
        response = ['250-perhaps a progress report',
 
1834
                    '250 okay']
 
1835
        map(self.client.lineReceived, response)
 
1836
        return d.addCallback(self.assertTrue)
 
1837
 
 
1838
 
 
1839
 
 
1840
class FTPClientBasicTests(unittest.TestCase):
 
1841
 
 
1842
    def testGreeting(self):
 
1843
        # The first response is captured as a greeting.
 
1844
        ftpClient = ftp.FTPClientBasic()
 
1845
        ftpClient.lineReceived('220 Imaginary FTP.')
 
1846
        self.failUnlessEqual(['220 Imaginary FTP.'], ftpClient.greeting)
 
1847
 
 
1848
    def testResponseWithNoMessage(self):
 
1849
        # Responses with no message are still valid, i.e. three digits followed
 
1850
        # by a space is complete response.
 
1851
        ftpClient = ftp.FTPClientBasic()
 
1852
        ftpClient.lineReceived('220 ')
 
1853
        self.failUnlessEqual(['220 '], ftpClient.greeting)
 
1854
 
 
1855
    def testMultilineResponse(self):
 
1856
        ftpClient = ftp.FTPClientBasic()
 
1857
        ftpClient.transport = proto_helpers.StringTransport()
 
1858
        ftpClient.lineReceived('220 Imaginary FTP.')
 
1859
 
 
1860
        # Queue (and send) a dummy command, and set up a callback to capture the
 
1861
        # result
 
1862
        deferred = ftpClient.queueStringCommand('BLAH')
 
1863
        result = []
 
1864
        deferred.addCallback(result.append)
 
1865
        deferred.addErrback(self.fail)
 
1866
 
 
1867
        # Send the first line of a multiline response.
 
1868
        ftpClient.lineReceived('210-First line.')
 
1869
        self.failUnlessEqual([], result)
 
1870
 
 
1871
        # Send a second line, again prefixed with "nnn-".
 
1872
        ftpClient.lineReceived('123-Second line.')
 
1873
        self.failUnlessEqual([], result)
 
1874
 
 
1875
        # Send a plain line of text, no prefix.
 
1876
        ftpClient.lineReceived('Just some text.')
 
1877
        self.failUnlessEqual([], result)
 
1878
 
 
1879
        # Now send a short (less than 4 chars) line.
 
1880
        ftpClient.lineReceived('Hi')
 
1881
        self.failUnlessEqual([], result)
 
1882
 
 
1883
        # Now send an empty line.
 
1884
        ftpClient.lineReceived('')
 
1885
        self.failUnlessEqual([], result)
 
1886
 
 
1887
        # And a line with 3 digits in it, and nothing else.
 
1888
        ftpClient.lineReceived('321')
 
1889
        self.failUnlessEqual([], result)
 
1890
 
 
1891
        # Now finish it.
 
1892
        ftpClient.lineReceived('210 Done.')
 
1893
        self.failUnlessEqual(
 
1894
            ['210-First line.',
 
1895
             '123-Second line.',
 
1896
             'Just some text.',
 
1897
             'Hi',
 
1898
             '',
 
1899
             '321',
 
1900
             '210 Done.'], result[0])
 
1901
 
 
1902
 
 
1903
    def test_noPasswordGiven(self):
 
1904
        """
 
1905
        Passing None as the password avoids sending the PASS command.
 
1906
        """
 
1907
        # Create a client, and give it a greeting.
 
1908
        ftpClient = ftp.FTPClientBasic()
 
1909
        ftpClient.transport = proto_helpers.StringTransport()
 
1910
        ftpClient.lineReceived('220 Welcome to Imaginary FTP.')
 
1911
 
 
1912
        # Queue a login with no password
 
1913
        ftpClient.queueLogin('bob', None)
 
1914
        self.assertEquals('USER bob\r\n', ftpClient.transport.value())
 
1915
 
 
1916
        # Clear the test buffer, acknowledge the USER command.
 
1917
        ftpClient.transport.clear()
 
1918
        ftpClient.lineReceived('200 Hello bob.')
 
1919
 
 
1920
        # The client shouldn't have sent anything more (i.e. it shouldn't have
 
1921
        # sent a PASS command).
 
1922
        self.assertEquals('', ftpClient.transport.value())
 
1923
 
 
1924
 
 
1925
    def test_noPasswordNeeded(self):
 
1926
        """
 
1927
        Receiving a 230 response to USER prevents PASS from being sent.
 
1928
        """
 
1929
        # Create a client, and give it a greeting.
 
1930
        ftpClient = ftp.FTPClientBasic()
 
1931
        ftpClient.transport = proto_helpers.StringTransport()
 
1932
        ftpClient.lineReceived('220 Welcome to Imaginary FTP.')
 
1933
 
 
1934
        # Queue a login with no password
 
1935
        ftpClient.queueLogin('bob', 'secret')
 
1936
        self.assertEquals('USER bob\r\n', ftpClient.transport.value())
 
1937
 
 
1938
        # Clear the test buffer, acknowledge the USER command with a 230
 
1939
        # response code.
 
1940
        ftpClient.transport.clear()
 
1941
        ftpClient.lineReceived('230 Hello bob.  No password needed.')
 
1942
 
 
1943
        # The client shouldn't have sent anything more (i.e. it shouldn't have
 
1944
        # sent a PASS command).
 
1945
        self.assertEquals('', ftpClient.transport.value())
 
1946
 
 
1947
 
 
1948
 
 
1949
class PathHandling(unittest.TestCase):
 
1950
    def testNormalizer(self):
 
1951
        for inp, outp in [('a', ['a']),
 
1952
                          ('/a', ['a']),
 
1953
                          ('/', []),
 
1954
                          ('a/b/c', ['a', 'b', 'c']),
 
1955
                          ('/a/b/c', ['a', 'b', 'c']),
 
1956
                          ('/a/', ['a']),
 
1957
                          ('a/', ['a'])]:
 
1958
            self.assertEquals(ftp.toSegments([], inp), outp)
 
1959
 
 
1960
        for inp, outp in [('b', ['a', 'b']),
 
1961
                          ('b/', ['a', 'b']),
 
1962
                          ('/b', ['b']),
 
1963
                          ('/b/', ['b']),
 
1964
                          ('b/c', ['a', 'b', 'c']),
 
1965
                          ('b/c/', ['a', 'b', 'c']),
 
1966
                          ('/b/c', ['b', 'c']),
 
1967
                          ('/b/c/', ['b', 'c'])]:
 
1968
            self.assertEquals(ftp.toSegments(['a'], inp), outp)
 
1969
 
 
1970
        for inp, outp in [('//', []),
 
1971
                          ('//a', ['a']),
 
1972
                          ('a//', ['a']),
 
1973
                          ('a//b', ['a', 'b'])]:
 
1974
            self.assertEquals(ftp.toSegments([], inp), outp)
 
1975
 
 
1976
        for inp, outp in [('//', []),
 
1977
                          ('//b', ['b']),
 
1978
                          ('b//c', ['a', 'b', 'c'])]:
 
1979
            self.assertEquals(ftp.toSegments(['a'], inp), outp)
 
1980
 
 
1981
        for inp, outp in [('..', []),
 
1982
                          ('../', []),
 
1983
                          ('a/..', ['x']),
 
1984
                          ('/a/..', []),
 
1985
                          ('/a/b/..', ['a']),
 
1986
                          ('/a/b/../', ['a']),
 
1987
                          ('/a/b/../c', ['a', 'c']),
 
1988
                          ('/a/b/../c/', ['a', 'c']),
 
1989
                          ('/a/b/../../c', ['c']),
 
1990
                          ('/a/b/../../c/', ['c']),
 
1991
                          ('/a/b/../../c/..', []),
 
1992
                          ('/a/b/../../c/../', [])]:
 
1993
            self.assertEquals(ftp.toSegments(['x'], inp), outp)
 
1994
 
 
1995
        for inp in ['..', '../', 'a/../..', 'a/../../',
 
1996
                    '/..', '/../', '/a/../..', '/a/../../',
 
1997
                    '/a/b/../../..']:
 
1998
            self.assertRaises(ftp.InvalidPath, ftp.toSegments, [], inp)
 
1999
 
 
2000
        for inp in ['../..', '../../', '../a/../..']:
 
2001
            self.assertRaises(ftp.InvalidPath, ftp.toSegments, ['x'], inp)
 
2002
 
 
2003
 
 
2004
 
 
2005
class ErrnoToFailureTestCase(unittest.TestCase):
 
2006
    """
 
2007
    Tests for L{ftp.errnoToFailure} errno checking.
 
2008
    """
 
2009
 
 
2010
    def test_notFound(self):
 
2011
        """
 
2012
        C{errno.ENOENT} should be translated to L{ftp.FileNotFoundError}.
 
2013
        """
 
2014
        d = ftp.errnoToFailure(errno.ENOENT, "foo")
 
2015
        return self.assertFailure(d, ftp.FileNotFoundError)
 
2016
 
 
2017
 
 
2018
    def test_permissionDenied(self):
 
2019
        """
 
2020
        C{errno.EPERM} should be translated to L{ftp.PermissionDeniedError}.
 
2021
        """
 
2022
        d = ftp.errnoToFailure(errno.EPERM, "foo")
 
2023
        return self.assertFailure(d, ftp.PermissionDeniedError)
 
2024
 
 
2025
 
 
2026
    def test_accessDenied(self):
 
2027
        """
 
2028
        C{errno.EACCES} should be translated to L{ftp.PermissionDeniedError}.
 
2029
        """
 
2030
        d = ftp.errnoToFailure(errno.EACCES, "foo")
 
2031
        return self.assertFailure(d, ftp.PermissionDeniedError)
 
2032
 
 
2033
 
 
2034
    def test_notDirectory(self):
 
2035
        """
 
2036
        C{errno.ENOTDIR} should be translated to L{ftp.IsNotADirectoryError}.
 
2037
        """
 
2038
        d = ftp.errnoToFailure(errno.ENOTDIR, "foo")
 
2039
        return self.assertFailure(d, ftp.IsNotADirectoryError)
 
2040
 
 
2041
 
 
2042
    def test_fileExists(self):
 
2043
        """
 
2044
        C{errno.EEXIST} should be translated to L{ftp.FileExistsError}.
 
2045
        """
 
2046
        d = ftp.errnoToFailure(errno.EEXIST, "foo")
 
2047
        return self.assertFailure(d, ftp.FileExistsError)
 
2048
 
 
2049
 
 
2050
    def test_isDirectory(self):
 
2051
        """
 
2052
        C{errno.EISDIR} should be translated to L{ftp.IsADirectoryError}.
 
2053
        """
 
2054
        d = ftp.errnoToFailure(errno.EISDIR, "foo")
 
2055
        return self.assertFailure(d, ftp.IsADirectoryError)
 
2056
 
 
2057
 
 
2058
    def test_passThrough(self):
 
2059
        """
 
2060
        If an unknown errno is passed to L{ftp.errnoToFailure}, it should let
 
2061
        the originating exception pass through.
 
2062
        """
 
2063
        try:
 
2064
            raise RuntimeError("bar")
 
2065
        except:
 
2066
            d = ftp.errnoToFailure(-1, "foo")
 
2067
            return self.assertFailure(d, RuntimeError)
 
2068
 
 
2069
 
 
2070
 
 
2071
class AnonymousFTPShellTestCase(unittest.TestCase):
 
2072
    """
 
2073
    Test anynomous shell properties.
 
2074
    """
 
2075
 
 
2076
    def test_anonymousWrite(self):
 
2077
        """
 
2078
        Check that L{ftp.FTPAnonymousShell} returns an error when trying to
 
2079
        open it in write mode.
 
2080
        """
 
2081
        shell = ftp.FTPAnonymousShell('')
 
2082
        d = shell.openForWriting(('foo',))
 
2083
        self.assertFailure(d, ftp.PermissionDeniedError)
 
2084
        return d
 
2085
 
 
2086
 
 
2087
 
 
2088
class IFTPShellTestsMixin:
 
2089
    """
 
2090
    Generic tests for the C{IFTPShell} interface.
 
2091
    """
 
2092
 
 
2093
    def directoryExists(self, path):
 
2094
        """
 
2095
        Test if the directory exists at C{path}.
 
2096
 
 
2097
        @param path: the relative path to check.
 
2098
        @type path: C{str}.
 
2099
 
 
2100
        @return: C{True} if C{path} exists and is a directory, C{False} if
 
2101
            it's not the case
 
2102
        @rtype: C{bool}
 
2103
        """
 
2104
        raise NotImplementedError()
 
2105
 
 
2106
 
 
2107
    def createDirectory(self, path):
 
2108
        """
 
2109
        Create a directory in C{path}.
 
2110
 
 
2111
        @param path: the relative path of the directory to create, with one
 
2112
            segment.
 
2113
        @type path: C{str}
 
2114
        """
 
2115
        raise NotImplementedError()
 
2116
 
 
2117
 
 
2118
    def fileExists(self, path):
 
2119
        """
 
2120
        Test if the file exists at C{path}.
 
2121
 
 
2122
        @param path: the relative path to check.
 
2123
        @type path: C{str}.
 
2124
 
 
2125
        @return: C{True} if C{path} exists and is a file, C{False} if it's not
 
2126
            the case.
 
2127
        @rtype: C{bool}
 
2128
        """
 
2129
        raise NotImplementedError()
 
2130
 
 
2131
 
 
2132
    def createFile(self, path, fileContent=''):
 
2133
        """
 
2134
        Create a file named C{path} with some content.
 
2135
 
 
2136
        @param path: the relative path of the file to create, without
 
2137
            directory.
 
2138
        @type path: C{str}
 
2139
 
 
2140
        @param fileContent: the content of the file.
 
2141
        @type fileContent: C{str}
 
2142
        """
 
2143
        raise NotImplementedError()
 
2144
 
 
2145
 
 
2146
    def test_createDirectory(self):
 
2147
        """
 
2148
        C{directoryExists} should report correctly about directory existence,
 
2149
        and C{createDirectory} should create a directory detectable by
 
2150
        C{directoryExists}.
 
2151
        """
 
2152
        self.assertFalse(self.directoryExists('bar'))
 
2153
        self.createDirectory('bar')
 
2154
        self.assertTrue(self.directoryExists('bar'))
 
2155
 
 
2156
 
 
2157
    def test_createFile(self):
 
2158
        """
 
2159
        C{fileExists} should report correctly about file existence, and
 
2160
        C{createFile} should create a file detectable by C{fileExists}.
 
2161
        """
 
2162
        self.assertFalse(self.fileExists('file.txt'))
 
2163
        self.createFile('file.txt')
 
2164
        self.assertTrue(self.fileExists('file.txt'))
 
2165
 
 
2166
 
 
2167
    def test_makeDirectory(self):
 
2168
        """
 
2169
        Create a directory and check it ends in the filesystem.
 
2170
        """
 
2171
        d = self.shell.makeDirectory(('foo',))
 
2172
        def cb(result):
 
2173
            self.assertTrue(self.directoryExists('foo'))
 
2174
        return d.addCallback(cb)
 
2175
 
 
2176
 
 
2177
    def test_makeDirectoryError(self):
 
2178
        """
 
2179
        Creating a directory that already exists should fail with a
 
2180
        C{ftp.FileExistsError}.
 
2181
        """
 
2182
        self.createDirectory('foo')
 
2183
        d = self.shell.makeDirectory(('foo',))
 
2184
        return self.assertFailure(d, ftp.FileExistsError)
 
2185
 
 
2186
 
 
2187
    def test_removeDirectory(self):
 
2188
        """
 
2189
        Try to remove a directory and check it's removed from the filesystem.
 
2190
        """
 
2191
        self.createDirectory('bar')
 
2192
        d = self.shell.removeDirectory(('bar',))
 
2193
        def cb(result):
 
2194
            self.assertFalse(self.directoryExists('bar'))
 
2195
        return d.addCallback(cb)
 
2196
 
 
2197
 
 
2198
    def test_removeDirectoryOnFile(self):
 
2199
        """
 
2200
        removeDirectory should not work in file and fail with a
 
2201
        C{ftp.IsNotADirectoryError}.
 
2202
        """
 
2203
        self.createFile('file.txt')
 
2204
        d = self.shell.removeDirectory(('file.txt',))
 
2205
        return self.assertFailure(d, ftp.IsNotADirectoryError)
 
2206
 
 
2207
 
 
2208
    def test_removeNotExistingDirectory(self):
 
2209
        """
 
2210
        Removing directory that doesn't exist should fail with a
 
2211
        C{ftp.FileNotFoundError}.
 
2212
        """
 
2213
        d = self.shell.removeDirectory(('bar',))
 
2214
        return self.assertFailure(d, ftp.FileNotFoundError)
 
2215
 
 
2216
 
 
2217
    def test_removeFile(self):
 
2218
        """
 
2219
        Try to remove a file and check it's removed from the filesystem.
 
2220
        """
 
2221
        self.createFile('file.txt')
 
2222
        d = self.shell.removeFile(('file.txt',))
 
2223
        def cb(res):
 
2224
            self.assertFalse(self.fileExists('file.txt'))
 
2225
        d.addCallback(cb)
 
2226
        return d
 
2227
 
 
2228
 
 
2229
    def test_removeFileOnDirectory(self):
 
2230
        """
 
2231
        removeFile should not work on directory.
 
2232
        """
 
2233
        self.createDirectory('ned')
 
2234
        d = self.shell.removeFile(('ned',))
 
2235
        return self.assertFailure(d, ftp.IsADirectoryError)
 
2236
 
 
2237
 
 
2238
    def test_removeNotExistingFile(self):
 
2239
        """
 
2240
        Try to remove a non existent file, and check it raises a
 
2241
        L{ivfs.NotFoundError}.
 
2242
        """
 
2243
        d = self.shell.removeFile(('foo',))
 
2244
        return self.assertFailure(d, ftp.FileNotFoundError)
 
2245
 
 
2246
 
 
2247
    def test_list(self):
 
2248
        """
 
2249
        Check the output of the list method.
 
2250
        """
 
2251
        self.createDirectory('ned')
 
2252
        self.createFile('file.txt')
 
2253
        d = self.shell.list(('.',))
 
2254
        def cb(l):
 
2255
            l.sort()
 
2256
            self.assertEquals(l,
 
2257
                [('file.txt', []), ('ned', [])])
 
2258
        return d.addCallback(cb)
 
2259
 
 
2260
 
 
2261
    def test_listWithStat(self):
 
2262
        """
 
2263
        Check the output of list with asked stats.
 
2264
        """
 
2265
        self.createDirectory('ned')
 
2266
        self.createFile('file.txt')
 
2267
        d = self.shell.list(('.',), ('size', 'permissions',))
 
2268
        def cb(l):
 
2269
            l.sort()
 
2270
            self.assertEquals(len(l), 2)
 
2271
            self.assertEquals(l[0][0], 'file.txt')
 
2272
            self.assertEquals(l[1][0], 'ned')
 
2273
            # Size and permissions are reported differently between platforms
 
2274
            # so just check they are present
 
2275
            self.assertEquals(len(l[0][1]), 2)
 
2276
            self.assertEquals(len(l[1][1]), 2)
 
2277
        return d.addCallback(cb)
 
2278
 
 
2279
 
 
2280
    def test_listWithInvalidStat(self):
 
2281
        """
 
2282
        Querying an invalid stat should result to a C{AttributeError}.
 
2283
        """
 
2284
        self.createDirectory('ned')
 
2285
        d = self.shell.list(('.',), ('size', 'whateverstat',))
 
2286
        return self.assertFailure(d, AttributeError)
 
2287
 
 
2288
 
 
2289
    def test_listFile(self):
 
2290
        """
 
2291
        Check the output of the list method on a file.
 
2292
        """
 
2293
        self.createFile('file.txt')
 
2294
        d = self.shell.list(('file.txt',))
 
2295
        def cb(l):
 
2296
            l.sort()
 
2297
            self.assertEquals(l,
 
2298
                [('file.txt', [])])
 
2299
        return d.addCallback(cb)
 
2300
 
 
2301
 
 
2302
    def test_listNotExistingDirectory(self):
 
2303
        """
 
2304
        list on a directory that doesn't exist should fail with a
 
2305
        L{ftp.FileNotFoundError}.
 
2306
        """
 
2307
        d = self.shell.list(('foo',))
 
2308
        return self.assertFailure(d, ftp.FileNotFoundError)
 
2309
 
 
2310
 
 
2311
    def test_access(self):
 
2312
        """
 
2313
        Try to access a resource.
 
2314
        """
 
2315
        self.createDirectory('ned')
 
2316
        d = self.shell.access(('ned',))
 
2317
        return d
 
2318
 
 
2319
 
 
2320
    def test_accessNotFound(self):
 
2321
        """
 
2322
        access should fail on a resource that doesn't exist.
 
2323
        """
 
2324
        d = self.shell.access(('foo',))
 
2325
        return self.assertFailure(d, ftp.FileNotFoundError)
 
2326
 
 
2327
 
 
2328
    def test_openForReading(self):
 
2329
        """
 
2330
        Check that openForReading returns an object providing C{ftp.IReadFile}.
 
2331
        """
 
2332
        self.createFile('file.txt')
 
2333
        d = self.shell.openForReading(('file.txt',))
 
2334
        def cb(res):
 
2335
            self.assertTrue(ftp.IReadFile.providedBy(res))
 
2336
        d.addCallback(cb)
 
2337
        return d
 
2338
 
 
2339
 
 
2340
    def test_openForReadingNotFound(self):
 
2341
        """
 
2342
        openForReading should fail with a C{ftp.FileNotFoundError} on a file
 
2343
        that doesn't exist.
 
2344
        """
 
2345
        d = self.shell.openForReading(('ned',))
 
2346
        return self.assertFailure(d, ftp.FileNotFoundError)
 
2347
 
 
2348
 
 
2349
    def test_openForReadingOnDirectory(self):
 
2350
        """
 
2351
        openForReading should not work on directory.
 
2352
        """
 
2353
        self.createDirectory('ned')
 
2354
        d = self.shell.openForReading(('ned',))
 
2355
        return self.assertFailure(d, ftp.IsADirectoryError)
 
2356
 
 
2357
 
 
2358
    def test_openForWriting(self):
 
2359
        """
 
2360
        Check that openForWriting returns an object providing C{ftp.IWriteFile}.
 
2361
        """
 
2362
        d = self.shell.openForWriting(('foo',))
 
2363
        def cb1(res):
 
2364
            self.assertTrue(ftp.IWriteFile.providedBy(res))
 
2365
            return res.receive().addCallback(cb2)
 
2366
        def cb2(res):
 
2367
            self.assertTrue(IConsumer.providedBy(res))
 
2368
        d.addCallback(cb1)
 
2369
        return d
 
2370
 
 
2371
 
 
2372
    def test_openForWritingExistingDirectory(self):
 
2373
        """
 
2374
        openForWriting should not be able to open a directory that already
 
2375
        exists.
 
2376
        """
 
2377
        self.createDirectory('ned')
 
2378
        d = self.shell.openForWriting(('ned',))
 
2379
        return self.assertFailure(d, ftp.IsADirectoryError)
 
2380
 
 
2381
 
 
2382
    def test_openForWritingInNotExistingDirectory(self):
 
2383
        """
 
2384
        openForWring should fail with a L{ftp.FileNotFoundError} if you specify
 
2385
        a file in a directory that doesn't exist.
 
2386
        """
 
2387
        self.createDirectory('ned')
 
2388
        d = self.shell.openForWriting(('ned', 'idonotexist', 'foo'))
 
2389
        return self.assertFailure(d, ftp.FileNotFoundError)
 
2390
 
 
2391
 
 
2392
    def test_statFile(self):
 
2393
        """
 
2394
        Check the output of the stat method on a file.
 
2395
        """
 
2396
        fileContent = 'wobble\n'
 
2397
        self.createFile('file.txt', fileContent)
 
2398
        d = self.shell.stat(('file.txt',), ('size', 'directory'))
 
2399
        def cb(res):
 
2400
            self.assertEquals(res[0], len(fileContent))
 
2401
            self.assertFalse(res[1])
 
2402
        d.addCallback(cb)
 
2403
        return d
 
2404
 
 
2405
 
 
2406
    def test_statDirectory(self):
 
2407
        """
 
2408
        Check the output of the stat method on a directory.
 
2409
        """
 
2410
        self.createDirectory('ned')
 
2411
        d = self.shell.stat(('ned',), ('size', 'directory'))
 
2412
        def cb(res):
 
2413
            self.assertTrue(res[1])
 
2414
        d.addCallback(cb)
 
2415
        return d
 
2416
 
 
2417
 
 
2418
    def test_statOwnerGroup(self):
 
2419
        """
 
2420
        Check the owner and groups stats.
 
2421
        """
 
2422
        self.createDirectory('ned')
 
2423
        d = self.shell.stat(('ned',), ('owner', 'group'))
 
2424
        def cb(res):
 
2425
            self.assertEquals(len(res), 2)
 
2426
        d.addCallback(cb)
 
2427
        return d
 
2428
 
 
2429
 
 
2430
    def test_statNotExisting(self):
 
2431
        """
 
2432
        stat should fail with L{ftp.FileNotFoundError} on a file that doesn't
 
2433
        exist.
 
2434
        """
 
2435
        d = self.shell.stat(('foo',), ('size', 'directory'))
 
2436
        return self.assertFailure(d, ftp.FileNotFoundError)
 
2437
 
 
2438
 
 
2439
    def test_invalidStat(self):
 
2440
        """
 
2441
        Querying an invalid stat should result to a C{AttributeError}.
 
2442
        """
 
2443
        self.createDirectory('ned')
 
2444
        d = self.shell.stat(('ned',), ('size', 'whateverstat'))
 
2445
        return self.assertFailure(d, AttributeError)
 
2446
 
 
2447
 
 
2448
    def test_rename(self):
 
2449
        """
 
2450
        Try to rename a directory.
 
2451
        """
 
2452
        self.createDirectory('ned')
 
2453
        d = self.shell.rename(('ned',), ('foo',))
 
2454
        def cb(res):
 
2455
            self.assertTrue(self.directoryExists('foo'))
 
2456
            self.assertFalse(self.directoryExists('ned'))
 
2457
        return d.addCallback(cb)
 
2458
 
 
2459
 
 
2460
    def test_renameNotExisting(self):
 
2461
        """
 
2462
        Renaming a directory that doesn't exist should fail with
 
2463
        L{ftp.FileNotFoundError}.
 
2464
        """
 
2465
        d = self.shell.rename(('foo',), ('bar',))
 
2466
        return self.assertFailure(d, ftp.FileNotFoundError)
 
2467
 
 
2468
 
 
2469
 
 
2470
class FTPShellTestCase(unittest.TestCase, IFTPShellTestsMixin):
 
2471
    """
 
2472
    Tests for the C{ftp.FTPShell} object.
 
2473
    """
 
2474
 
 
2475
    def setUp(self):
 
2476
        """
 
2477
        Create a root directory and instantiate a shell.
 
2478
        """
 
2479
        self.root = filepath.FilePath(self.mktemp())
 
2480
        self.root.createDirectory()
 
2481
        self.shell = ftp.FTPShell(self.root)
 
2482
 
 
2483
 
 
2484
    def directoryExists(self, path):
 
2485
        """
 
2486
        Test if the directory exists at C{path}.
 
2487
        """
 
2488
        return self.root.child(path).isdir()
 
2489
 
 
2490
 
 
2491
    def createDirectory(self, path):
 
2492
        """
 
2493
        Create a directory in C{path}.
 
2494
        """
 
2495
        return self.root.child(path).createDirectory()
 
2496
 
 
2497
 
 
2498
    def fileExists(self, path):
 
2499
        """
 
2500
        Test if the file exists at C{path}.
 
2501
        """
 
2502
        return self.root.child(path).isfile()
 
2503
 
 
2504
 
 
2505
    def createFile(self, path, fileContent=''):
 
2506
        """
 
2507
        Create a file named C{path} with some content.
 
2508
        """
 
2509
        return self.root.child(path).setContent(fileContent)
 
2510
 
 
2511
 
 
2512
 
 
2513
class TestConsumer(object):
 
2514
    """
 
2515
    A simple consumer for tests. It only works with non-streaming producers.
 
2516
 
 
2517
    @ivar producer: an object providing
 
2518
        L{twisted.internet.interfaces.IPullProducer}.
 
2519
    """
 
2520
 
 
2521
    implements(IConsumer)
 
2522
    producer = None
 
2523
 
 
2524
    def registerProducer(self, producer, streaming):
 
2525
        """
 
2526
        Simple register of producer, checks that no register has happened
 
2527
        before.
 
2528
        """
 
2529
        assert self.producer is None
 
2530
        self.buffer = []
 
2531
        self.producer = producer
 
2532
        self.producer.resumeProducing()
 
2533
 
 
2534
 
 
2535
    def unregisterProducer(self):
 
2536
        """
 
2537
        Unregister the producer, it should be done after a register.
 
2538
        """
 
2539
        assert self.producer is not None
 
2540
        self.producer = None
 
2541
 
 
2542
 
 
2543
    def write(self, data):
 
2544
        """
 
2545
        Save the data received.
 
2546
        """
 
2547
        self.buffer.append(data)
 
2548
        self.producer.resumeProducing()
 
2549
 
 
2550
 
 
2551
 
 
2552
class TestProducer(object):
 
2553
    """
 
2554
    A dumb producer.
 
2555
    """
 
2556
 
 
2557
    def __init__(self, toProduce, consumer):
 
2558
        """
 
2559
        @param toProduce: data to write
 
2560
        @type toProduce: C{str}
 
2561
        @param consumer: the consumer of data.
 
2562
        @type consumer: C{IConsumer}
 
2563
        """
 
2564
        self.toProduce = toProduce
 
2565
        self.consumer = consumer
 
2566
 
 
2567
 
 
2568
    def start(self):
 
2569
        """
 
2570
        Send the data to consume.
 
2571
        """
 
2572
        self.consumer.write(self.toProduce)
 
2573
 
 
2574
 
 
2575
 
 
2576
class IReadWriteTestsMixin:
 
2577
    """
 
2578
    Generic tests for the C{IReadFile} and C{IWriteFile} interfaces.
 
2579
    """
 
2580
 
 
2581
    def getFileReader(self, content):
 
2582
        """
 
2583
        Return an object providing C{IReadFile}, ready to send data C{content}.
 
2584
        """
 
2585
        raise NotImplementedError()
 
2586
 
 
2587
 
 
2588
    def getFileWriter(self):
 
2589
        """
 
2590
        Return an object providing C{IWriteFile}, ready to receive data.
 
2591
        """
 
2592
        raise NotImplementedError()
 
2593
 
 
2594
 
 
2595
    def getFileContent(self):
 
2596
        """
 
2597
        Return the content of the file used.
 
2598
        """
 
2599
        raise NotImplementedError()
 
2600
 
 
2601
 
 
2602
    def test_read(self):
 
2603
        """
 
2604
        Test L{ftp.IReadFile}: the implementation should have a send method
 
2605
        returning a C{Deferred} which fires when all the data has been sent
 
2606
        to the consumer, and the data should be correctly send to the consumer.
 
2607
        """
 
2608
        content = 'wobble\n'
 
2609
        consumer = TestConsumer()
 
2610
        def cbGet(reader):
 
2611
            return reader.send(consumer).addCallback(cbSend)
 
2612
        def cbSend(res):
 
2613
            self.assertEquals("".join(consumer.buffer), content)
 
2614
        return self.getFileReader(content).addCallback(cbGet)
 
2615
 
 
2616
 
 
2617
    def test_write(self):
 
2618
        """
 
2619
        Test L{ftp.IWriteFile}: the implementation should have a receive method
 
2620
        returning a C{Deferred} with fires with a consumer ready to receive
 
2621
        data to be written.
 
2622
        """
 
2623
        content = 'elbbow\n'
 
2624
        def cbGet(writer):
 
2625
            return writer.receive().addCallback(cbReceive)
 
2626
        def cbReceive(consumer):
 
2627
            producer = TestProducer(content, consumer)
 
2628
            consumer.registerProducer(None, True)
 
2629
            producer.start()
 
2630
            consumer.unregisterProducer()
 
2631
            self.assertEquals(self.getFileContent(), content)
 
2632
        return self.getFileWriter().addCallback(cbGet)
 
2633
 
 
2634
 
 
2635
 
 
2636
class FTPReadWriteTestCase(unittest.TestCase, IReadWriteTestsMixin):
 
2637
    """
 
2638
    Tests for C{ftp._FileReader} and C{ftp._FileWriter}, the objects returned
 
2639
    by the shell in C{openForReading}/C{openForWriting}.
 
2640
    """
 
2641
 
 
2642
    def setUp(self):
 
2643
        """
 
2644
        Create a temporary file used later.
 
2645
        """
 
2646
        self.root = filepath.FilePath(self.mktemp())
 
2647
        self.root.createDirectory()
 
2648
        self.shell = ftp.FTPShell(self.root)
 
2649
        self.filename = "file.txt"
 
2650
 
 
2651
 
 
2652
    def getFileReader(self, content):
 
2653
        """
 
2654
        Return a C{ftp._FileReader} instance with a file opened for reading.
 
2655
        """
 
2656
        self.root.child(self.filename).setContent(content)
 
2657
        return self.shell.openForReading((self.filename,))
 
2658
 
 
2659
 
 
2660
    def getFileWriter(self):
 
2661
        """
 
2662
        Return a C{ftp._FileWriter} instance with a file opened for writing.
 
2663
        """
 
2664
        return self.shell.openForWriting((self.filename,))
 
2665
 
 
2666
 
 
2667
    def getFileContent(self):
 
2668
        """
 
2669
        Return the content of the temporary file.
 
2670
        """
 
2671
        return self.root.child(self.filename).getContent()