Viewing file: test_filetransfer.py (32.36 KB) -rw-r--r-- Select action/file-type: (+) | (+) | (+) | Code (+) | Session (+) | (+) | SDB (+) | (+) | (+) | (+) | (+) | (+) |
# -*- test-case-name: twisted.conch.test.test_filetransfer -*- # Copyright (c) Twisted Matrix Laboratories. # See LICENSE file for details.
""" Tests for L{twisted.conch.ssh.filetransfer}. """
import os import re import struct from unittest import skipIf
from hamcrest import assert_that, equal_to
from twisted.internet import defer from twisted.internet.error import ConnectionLost from twisted.protocols import loopback from twisted.python import components from twisted.python.compat import _PY37PLUS from twisted.python.filepath import FilePath from twisted.test.proto_helpers import StringTransport from twisted.trial.unittest import TestCase
try: from twisted.conch import unix as _unix except ImportError: unix = None else: unix = _unix
try: from twisted.conch.unix import ( SFTPServerForUnixConchUser as _SFTPServerForUnixConchUser, ) except ImportError: SFTPServerForUnixConchUser = None else: SFTPServerForUnixConchUser = _SFTPServerForUnixConchUser
try: import cryptography as _cryptography except ImportError: cryptography = None else: cryptography = _cryptography
try: from twisted.conch.avatar import ConchUser as _ConchUser except ImportError: ConchUser = object else: ConchUser = _ConchUser # type: ignore[misc]
try: from twisted.conch.ssh import common, connection, filetransfer, session except ImportError: pass
class TestAvatar(ConchUser): def __init__(self): ConchUser.__init__(self) self.channelLookup[b"session"] = session.SSHSession self.subsystemLookup[b"sftp"] = filetransfer.FileTransferServer
def _runAsUser(self, f, *args, **kw): try: f = iter(f) except TypeError: f = [(f, args, kw)] for i in f: func = i[0] args = len(i) > 1 and i[1] or () kw = len(i) > 2 and i[2] or {} r = func(*args, **kw) return r
class FileTransferTestAvatar(TestAvatar): def __init__(self, homeDir): TestAvatar.__init__(self) self.homeDir = homeDir
def getHomeDir(self): return FilePath(os.getcwd()).preauthChild(self.homeDir.path)
class ConchSessionForTestAvatar: def __init__(self, avatar): self.avatar = avatar
if SFTPServerForUnixConchUser is None: # unix should either be a fully working module, or None. I'm not sure # how this happens, but on win32 it does. Try to cope. --spiv. import warnings
warnings.warn( ( "twisted.conch.unix imported %r, " "but doesn't define SFTPServerForUnixConchUser'" ) % (unix,) ) else:
class FileTransferForTestAvatar(SFTPServerForUnixConchUser): # type: ignore[misc,valid-type] def gotVersion(self, version, otherExt): return {b"conchTest": b"ext data"}
def extendedRequest(self, extName, extData): if extName == b"testExtendedRequest": return b"bar" raise NotImplementedError
components.registerAdapter( FileTransferForTestAvatar, TestAvatar, filetransfer.ISFTPServer )
class SFTPTestBase(TestCase): def setUp(self): self.testDir = FilePath(self.mktemp()) # Give the testDir another level so we can safely "cd .." from it in # tests. self.testDir = self.testDir.child("extra") self.testDir.child("testDirectory").makedirs(True)
with self.testDir.child("testfile1").open(mode="wb") as f: f.write(b"a" * 10 + b"b" * 10) with open("/dev/urandom", "rb") as f2: f.write(f2.read(1024 * 64)) # random data self.testDir.child("testfile1").chmod(0o644) with self.testDir.child("testRemoveFile").open(mode="wb") as f: f.write(b"a") with self.testDir.child("testRenameFile").open(mode="wb") as f: f.write(b"a") with self.testDir.child(".testHiddenFile").open(mode="wb") as f: f.write(b"a")
@skipIf(not unix, "can't run on non-posix computers") class OurServerOurClientTests(SFTPTestBase): def setUp(self): SFTPTestBase.setUp(self)
self.avatar = FileTransferTestAvatar(self.testDir) self.server = filetransfer.FileTransferServer(avatar=self.avatar) clientTransport = loopback.LoopbackRelay(self.server)
self.client = filetransfer.FileTransferClient() self._serverVersion = None self._extData = None
def _(serverVersion, extData): self._serverVersion = serverVersion self._extData = extData
self.client.gotServerVersion = _ serverTransport = loopback.LoopbackRelay(self.client) self.client.makeConnection(clientTransport) self.server.makeConnection(serverTransport)
self.clientTransport = clientTransport self.serverTransport = serverTransport
self._emptyBuffers()
def _emptyBuffers(self): while self.serverTransport.buffer or self.clientTransport.buffer: self.serverTransport.clearBuffer() self.clientTransport.clearBuffer()
def tearDown(self): self.serverTransport.loseConnection() self.clientTransport.loseConnection() self.serverTransport.clearBuffer() self.clientTransport.clearBuffer()
def test_serverVersion(self): self.assertEqual(self._serverVersion, 3) self.assertEqual(self._extData, {b"conchTest": b"ext data"})
def test_interface_implementation(self): """ It implements the ISFTPServer interface. """ self.assertTrue( filetransfer.ISFTPServer.providedBy(self.server.client), f"ISFTPServer not provided by {self.server.client!r}", )
def test_openedFileClosedWithConnection(self): """ A file opened with C{openFile} is closed when the connection is lost. """ d = self.client.openFile( b"testfile1", filetransfer.FXF_READ | filetransfer.FXF_WRITE, {} ) self._emptyBuffers()
oldClose = os.close closed = []
def close(fd): closed.append(fd) oldClose(fd)
self.patch(os, "close", close)
def _fileOpened(openFile): fd = self.server.openFiles[openFile.handle[4:]].fd self.serverTransport.loseConnection() self.clientTransport.loseConnection() self.serverTransport.clearBuffer() self.clientTransport.clearBuffer() self.assertEqual(self.server.openFiles, {}) self.assertIn(fd, closed)
d.addCallback(_fileOpened) return d
def test_openedDirectoryClosedWithConnection(self): """ A directory opened with C{openDirectory} is close when the connection is lost. """ d = self.client.openDirectory("") self._emptyBuffers()
def _getFiles(openDir): self.serverTransport.loseConnection() self.clientTransport.loseConnection() self.serverTransport.clearBuffer() self.clientTransport.clearBuffer() self.assertEqual(self.server.openDirs, {})
d.addCallback(_getFiles) return d
def test_openFileIO(self): d = self.client.openFile( b"testfile1", filetransfer.FXF_READ | filetransfer.FXF_WRITE, {} ) self._emptyBuffers()
def _fileOpened(openFile): self.assertEqual(openFile, filetransfer.ISFTPFile(openFile)) d = _readChunk(openFile) d.addCallback(_writeChunk, openFile) return d
def _readChunk(openFile): d = openFile.readChunk(0, 20) self._emptyBuffers() d.addCallback(self.assertEqual, b"a" * 10 + b"b" * 10) return d
def _writeChunk(_, openFile): d = openFile.writeChunk(20, b"c" * 10) self._emptyBuffers() d.addCallback(_readChunk2, openFile) return d
def _readChunk2(_, openFile): d = openFile.readChunk(0, 30) self._emptyBuffers() d.addCallback(self.assertEqual, b"a" * 10 + b"b" * 10 + b"c" * 10) return d
d.addCallback(_fileOpened) return d
def test_closedFileGetAttrs(self): d = self.client.openFile( b"testfile1", filetransfer.FXF_READ | filetransfer.FXF_WRITE, {} ) self._emptyBuffers()
def _getAttrs(_, openFile): d = openFile.getAttrs() self._emptyBuffers() return d
def _err(f): self.flushLoggedErrors() return f
def _close(openFile): d = openFile.close() self._emptyBuffers() d.addCallback(_getAttrs, openFile) d.addErrback(_err) return self.assertFailure(d, filetransfer.SFTPError)
d.addCallback(_close) return d
def test_openFileAttributes(self): d = self.client.openFile( b"testfile1", filetransfer.FXF_READ | filetransfer.FXF_WRITE, {} ) self._emptyBuffers()
def _getAttrs(openFile): d = openFile.getAttrs() self._emptyBuffers() d.addCallback(_getAttrs2) return d
def _getAttrs2(attrs1): d = self.client.getAttrs(b"testfile1") self._emptyBuffers() d.addCallback(self.assertEqual, attrs1) return d
return d.addCallback(_getAttrs)
def test_openFileSetAttrs(self): # XXX test setAttrs # Ok, how about this for a start? It caught a bug :) -- spiv. d = self.client.openFile( b"testfile1", filetransfer.FXF_READ | filetransfer.FXF_WRITE, {} ) self._emptyBuffers()
def _getAttrs(openFile): d = openFile.getAttrs() self._emptyBuffers() d.addCallback(_setAttrs) return d
def _setAttrs(attrs): attrs["atime"] = 0 d = self.client.setAttrs(b"testfile1", attrs) self._emptyBuffers() d.addCallback(_getAttrs2) d.addCallback(self.assertEqual, attrs) return d
def _getAttrs2(_): d = self.client.getAttrs(b"testfile1") self._emptyBuffers() return d
d.addCallback(_getAttrs) return d
def test_openFileExtendedAttributes(self): """ Check that L{filetransfer.FileTransferClient.openFile} can send extended attributes, that should be extracted server side. By default, they are ignored, so we just verify they are correctly parsed. """ savedAttributes = {} oldOpenFile = self.server.client.openFile
def openFile(filename, flags, attrs): savedAttributes.update(attrs) return oldOpenFile(filename, flags, attrs)
self.server.client.openFile = openFile
d = self.client.openFile( b"testfile1", filetransfer.FXF_READ | filetransfer.FXF_WRITE, {"ext_foo": b"bar"}, ) self._emptyBuffers()
def check(ign): self.assertEqual(savedAttributes, {"ext_foo": b"bar"})
return d.addCallback(check)
def test_removeFile(self): d = self.client.getAttrs(b"testRemoveFile") self._emptyBuffers()
def _removeFile(ignored): d = self.client.removeFile(b"testRemoveFile") self._emptyBuffers() return d
d.addCallback(_removeFile) d.addCallback(_removeFile) return self.assertFailure(d, filetransfer.SFTPError)
def test_renameFile(self): d = self.client.getAttrs(b"testRenameFile") self._emptyBuffers()
def _rename(attrs): d = self.client.renameFile(b"testRenameFile", b"testRenamedFile") self._emptyBuffers() d.addCallback(_testRenamed, attrs) return d
def _testRenamed(_, attrs): d = self.client.getAttrs(b"testRenamedFile") self._emptyBuffers() d.addCallback(self.assertEqual, attrs)
return d.addCallback(_rename)
def test_directoryBad(self): d = self.client.getAttrs(b"testMakeDirectory") self._emptyBuffers() return self.assertFailure(d, filetransfer.SFTPError)
def test_directoryCreation(self): d = self.client.makeDirectory(b"testMakeDirectory", {}) self._emptyBuffers()
def _getAttrs(_): d = self.client.getAttrs(b"testMakeDirectory") self._emptyBuffers() return d
# XXX not until version 4/5 # self.assertEqual(filetransfer.FILEXFER_TYPE_DIRECTORY&attrs['type'], # filetransfer.FILEXFER_TYPE_DIRECTORY)
def _removeDirectory(_): d = self.client.removeDirectory(b"testMakeDirectory") self._emptyBuffers() return d
d.addCallback(_getAttrs) d.addCallback(_removeDirectory) d.addCallback(_getAttrs) return self.assertFailure(d, filetransfer.SFTPError)
def test_openDirectory(self): d = self.client.openDirectory(b"") self._emptyBuffers() files = []
def _getFiles(openDir): def append(f): files.append(f) return openDir
d = defer.maybeDeferred(openDir.next) self._emptyBuffers() d.addCallback(append) d.addCallback(_getFiles) d.addErrback(_close, openDir) return d
def _checkFiles(ignored): fs = list(list(zip(*files))[0]) fs.sort() self.assertEqual( fs, [ b".testHiddenFile", b"testDirectory", b"testRemoveFile", b"testRenameFile", b"testfile1", ], )
def _close(_, openDir): d = openDir.close() self._emptyBuffers() return d
d.addCallback(_getFiles) d.addCallback(_checkFiles) return d
def test_linkDoesntExist(self): d = self.client.getAttrs(b"testLink") self._emptyBuffers() return self.assertFailure(d, filetransfer.SFTPError)
def test_linkSharesAttrs(self): d = self.client.makeLink(b"testLink", b"testfile1") self._emptyBuffers()
def _getFirstAttrs(_): d = self.client.getAttrs(b"testLink", 1) self._emptyBuffers() return d
def _getSecondAttrs(firstAttrs): d = self.client.getAttrs(b"testfile1") self._emptyBuffers() d.addCallback(self.assertEqual, firstAttrs) return d
d.addCallback(_getFirstAttrs) return d.addCallback(_getSecondAttrs)
def test_linkPath(self): d = self.client.makeLink(b"testLink", b"testfile1") self._emptyBuffers()
def _readLink(_): d = self.client.readLink(b"testLink") self._emptyBuffers() testFile = FilePath(os.getcwd()).preauthChild(self.testDir.path) testFile = testFile.child("testfile1") d.addCallback(self.assertEqual, testFile.path) return d
def _realPath(_): d = self.client.realPath(b"testLink") self._emptyBuffers() testLink = FilePath(os.getcwd()).preauthChild(self.testDir.path) testLink = testLink.child("testfile1") d.addCallback(self.assertEqual, testLink.path) return d
d.addCallback(_readLink) d.addCallback(_realPath) return d
def test_extendedRequest(self): d = self.client.extendedRequest(b"testExtendedRequest", b"foo") self._emptyBuffers() d.addCallback(self.assertEqual, b"bar") d.addCallback(self._cbTestExtendedRequest) return d
def _cbTestExtendedRequest(self, ignored): d = self.client.extendedRequest(b"testBadRequest", b"") self._emptyBuffers() return self.assertFailure(d, NotImplementedError)
@defer.inlineCallbacks @skipIf(_PY37PLUS, "Broken by PEP 479 and deprecated.") def test_openDirectoryIterator(self): """ Check that the object returned by L{filetransfer.FileTransferClient.openDirectory} can be used as an iterator. """
# This function is a little more complicated than it would be # normally, since we need to call _emptyBuffers() after # creating any SSH-related Deferreds, but before waiting on # them via yield.
d = self.client.openDirectory(b"") self._emptyBuffers() openDir = yield d
filenames = set() try: for f in openDir: self._emptyBuffers() (filename, _, fileattrs) = yield f filenames.add(filename) finally: d = openDir.close() self._emptyBuffers() yield d
self._emptyBuffers()
self.assertEqual( filenames, { b".testHiddenFile", b"testDirectory", b"testRemoveFile", b"testRenameFile", b"testfile1", }, )
@defer.inlineCallbacks def test_openDirectoryIteratorDeprecated(self): """ Using client.openDirectory as an iterator is deprecated. """ d = self.client.openDirectory(b"") self._emptyBuffers() openDir = yield d oneFile = openDir.next() self._emptyBuffers() yield oneFile
warnings = self.flushWarnings() message = ( "Using twisted.conch.ssh.filetransfer.ClientDirectory" " as an iterator was deprecated in Twisted 18.9.0." ) self.assertEqual(1, len(warnings)) self.assertEqual(DeprecationWarning, warnings[0]["category"]) self.assertEqual(message, warnings[0]["message"])
@defer.inlineCallbacks def test_closedConnectionCancelsRequests(self): """ If there are requests outstanding when the connection is closed for any reason, they should fail. """
d = self.client.openFile(b"testfile1", filetransfer.FXF_READ, {}) self._emptyBuffers() fh = yield d
# Intercept the handling of the read request on the server side gotReadRequest = []
def _slowRead(offset, length): self.assertEqual(gotReadRequest, []) d = defer.Deferred() gotReadRequest.append(offset) return d
[serverSideFh] = self.server.openFiles.values() serverSideFh.readChunk = _slowRead del serverSideFh
# Make a read request, dropping the connection before the reply # is sent d = fh.readChunk(100, 200) self._emptyBuffers() self.assertEqual(len(gotReadRequest), 1) self.assertNoResult(d)
# Lost connection should cause an errback self.serverTransport.loseConnection() self.serverTransport.clearBuffer() self.clientTransport.clearBuffer() self._emptyBuffers()
self.assertFalse(self.client.connected) self.failureResultOf(d, ConnectionLost)
# Further attempts to use the filetransfer session should fail # immediately d = fh.getAttrs() self.failureResultOf(d, ConnectionLost)
class FakeConn: def sendClose(self, channel): pass
@skipIf(not unix, "can't run on non-posix computers") class FileTransferCloseTests(TestCase): def setUp(self): self.avatar = TestAvatar()
def buildServerConnection(self): # make a server connection conn = connection.SSHConnection() # server connections have a 'self.transport.avatar'.
class DummyTransport: def __init__(self): self.transport = self
def sendPacket(self, kind, data): pass
def logPrefix(self): return "dummy transport"
conn.transport = DummyTransport() conn.transport.avatar = self.avatar return conn
def interceptConnectionLost(self, sftpServer): self.connectionLostFired = False origConnectionLost = sftpServer.connectionLost
def connectionLost(reason): self.connectionLostFired = True origConnectionLost(reason)
sftpServer.connectionLost = connectionLost
def assertSFTPConnectionLost(self): self.assertTrue( self.connectionLostFired, "sftpServer's connectionLost was not called" )
def test_sessionClose(self): """ Closing a session should notify an SFTP subsystem launched by that session. """ # make a session testSession = session.SSHSession(conn=FakeConn(), avatar=self.avatar)
# start an SFTP subsystem on the session testSession.request_subsystem(common.NS(b"sftp")) sftpServer = testSession.client.transport.proto
# intercept connectionLost so we can check that it's called self.interceptConnectionLost(sftpServer)
# close session testSession.closeReceived()
self.assertSFTPConnectionLost()
def test_clientClosesChannelOnConnnection(self): """ A client sending CHANNEL_CLOSE should trigger closeReceived on the associated channel instance. """ conn = self.buildServerConnection()
# somehow get a session packet = common.NS(b"session") + struct.pack(">L", 0) * 3 conn.ssh_CHANNEL_OPEN(packet) sessionChannel = conn.channels[0]
sessionChannel.request_subsystem(common.NS(b"sftp")) sftpServer = sessionChannel.client.transport.proto self.interceptConnectionLost(sftpServer)
# intercept closeReceived self.interceptConnectionLost(sftpServer)
# close the connection conn.ssh_CHANNEL_CLOSE(struct.pack(">L", 0))
self.assertSFTPConnectionLost()
def test_stopConnectionServiceClosesChannel(self): """ Closing an SSH connection should close all sessions within it. """ conn = self.buildServerConnection()
# somehow get a session packet = common.NS(b"session") + struct.pack(">L", 0) * 3 conn.ssh_CHANNEL_OPEN(packet) sessionChannel = conn.channels[0]
sessionChannel.request_subsystem(common.NS(b"sftp")) sftpServer = sessionChannel.client.transport.proto self.interceptConnectionLost(sftpServer)
# close the connection conn.serviceStopped()
self.assertSFTPConnectionLost()
@skipIf(not cryptography, "Cannot run without cryptography") class ConstantsTests(TestCase): """ Tests for the constants used by the SFTP protocol implementation.
@ivar filexferSpecExcerpts: Excerpts from the draft-ietf-secsh-filexfer-02.txt (draft) specification of the SFTP protocol. There are more recent drafts of the specification, but this one describes version 3, which is what conch (and OpenSSH) implements. """
filexferSpecExcerpts = [ """ The following values are defined for packet types.
#define SSH_FXP_INIT 1 #define SSH_FXP_VERSION 2 #define SSH_FXP_OPEN 3 #define SSH_FXP_CLOSE 4 #define SSH_FXP_READ 5 #define SSH_FXP_WRITE 6 #define SSH_FXP_LSTAT 7 #define SSH_FXP_FSTAT 8 #define SSH_FXP_SETSTAT 9 #define SSH_FXP_FSETSTAT 10 #define SSH_FXP_OPENDIR 11 #define SSH_FXP_READDIR 12 #define SSH_FXP_REMOVE 13 #define SSH_FXP_MKDIR 14 #define SSH_FXP_RMDIR 15 #define SSH_FXP_REALPATH 16 #define SSH_FXP_STAT 17 #define SSH_FXP_RENAME 18 #define SSH_FXP_READLINK 19 #define SSH_FXP_SYMLINK 20 #define SSH_FXP_STATUS 101 #define SSH_FXP_HANDLE 102 #define SSH_FXP_DATA 103 #define SSH_FXP_NAME 104 #define SSH_FXP_ATTRS 105 #define SSH_FXP_EXTENDED 200 #define SSH_FXP_EXTENDED_REPLY 201
Additional packet types should only be defined if the protocol version number (see Section ``Protocol Initialization'') is incremented, and their use MUST be negotiated using the version number. However, the SSH_FXP_EXTENDED and SSH_FXP_EXTENDED_REPLY packets can be used to implement vendor-specific extensions. See Section ``Vendor-Specific-Extensions'' for more details. """, """ The flags bits are defined to have the following values:
#define SSH_FILEXFER_ATTR_SIZE 0x00000001 #define SSH_FILEXFER_ATTR_UIDGID 0x00000002 #define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004 #define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008 #define SSH_FILEXFER_ATTR_EXTENDED 0x80000000
""", """ The `pflags' field is a bitmask. The following bits have been defined.
#define SSH_FXF_READ 0x00000001 #define SSH_FXF_WRITE 0x00000002 #define SSH_FXF_APPEND 0x00000004 #define SSH_FXF_CREAT 0x00000008 #define SSH_FXF_TRUNC 0x00000010 #define SSH_FXF_EXCL 0x00000020 """, """ Currently, the following values are defined (other values may be defined by future versions of this protocol):
#define SSH_FX_OK 0 #define SSH_FX_EOF 1 #define SSH_FX_NO_SUCH_FILE 2 #define SSH_FX_PERMISSION_DENIED 3 #define SSH_FX_FAILURE 4 #define SSH_FX_BAD_MESSAGE 5 #define SSH_FX_NO_CONNECTION 6 #define SSH_FX_CONNECTION_LOST 7 #define SSH_FX_OP_UNSUPPORTED 8 """, ]
def test_constantsAgainstSpec(self): """ The constants used by the SFTP protocol implementation match those found by searching through the spec. """ constants = {} for excerpt in self.filexferSpecExcerpts: for line in excerpt.splitlines(): m = re.match(r"^\s*#define SSH_([A-Z_]+)\s+([0-9x]*)\s*$", line) if m: constants[m.group(1)] = int(m.group(2), 0) self.assertTrue( len(constants) > 0, "No constants found (the test must be buggy)." ) for k, v in constants.items(): self.assertEqual(v, getattr(filetransfer, k))
@skipIf(not cryptography, "Cannot run without cryptography") class RawPacketDataServerTests(TestCase): """ Tests for L{filetransfer.FileTransferServer} which explicitly craft certain less common situations to exercise their handling. """
def setUp(self): self.fts = filetransfer.FileTransferServer(avatar=TestAvatar())
def test_closeInvalidHandle(self): """ A close request with an unknown handle receives an FX_NO_SUCH_FILE error response. """ transport = StringTransport() self.fts.makeConnection(transport)
# any four bytes requestId = b"1234" # The handle to close, arbitrary bytes. handle = b"invalid handle"
# Construct a message packet # https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-4 close = common.NS( # Packet type - SSH_FXP_CLOSE # https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-4.3 bytes([4]) + requestId + common.NS(handle) )
self.fts.dataReceived(close)
# An SSH_FXP_STATUS message # https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-9.1 expected = common.NS( # Packet type SSH_FXP_STATUS # https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-13#section-4.3 bytes([101]) + # The same request id requestId + # A four byte status code. SSH_FX_NO_SUCH_FILE in this case. bytes([0, 0, 0, 2]) + # Error message common.NS(b"No such file or directory") + # error message language tag - conch doesn't send one at all, # though maybe it should common.NS(b"") )
assert_that( transport.value(), equal_to(expected), )
@skipIf(not cryptography, "Cannot run without cryptography") class RawPacketDataTests(TestCase): """ Tests for L{filetransfer.FileTransferClient} which explicitly craft certain less common protocol messages to exercise their handling. """
def setUp(self): self.ftc = filetransfer.FileTransferClient()
def test_packetSTATUS(self): """ A STATUS packet containing a result code, a message, and a language is parsed to produce the result of an outstanding request L{Deferred}.
@see: U{section 9.1<http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.1>} of the SFTP Internet-Draft. """ d = defer.Deferred() d.addCallback(self._cbTestPacketSTATUS) self.ftc.openRequests[1] = d data = ( struct.pack("!LL", 1, filetransfer.FX_OK) + common.NS(b"msg") + common.NS(b"lang") ) self.ftc.packet_STATUS(data) return d
def _cbTestPacketSTATUS(self, result): """ Assert that the result is a two-tuple containing the message and language from the STATUS packet. """ self.assertEqual(result[0], b"msg") self.assertEqual(result[1], b"lang")
def test_packetSTATUSShort(self): """ A STATUS packet containing only a result code can also be parsed to produce the result of an outstanding request L{Deferred}. Such packets are sent by some SFTP implementations, though not strictly legal.
@see: U{section 9.1<http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.1>} of the SFTP Internet-Draft. """ d = defer.Deferred() d.addCallback(self._cbTestPacketSTATUSShort) self.ftc.openRequests[1] = d data = struct.pack("!LL", 1, filetransfer.FX_OK) self.ftc.packet_STATUS(data) return d
def _cbTestPacketSTATUSShort(self, result): """ Assert that the result is a two-tuple containing empty strings, since the STATUS packet had neither a message nor a language. """ self.assertEqual(result[0], b"") self.assertEqual(result[1], b"")
def test_packetSTATUSWithoutLang(self): """ A STATUS packet containing a result code and a message but no language can also be parsed to produce the result of an outstanding request L{Deferred}. Such packets are sent by some SFTP implementations, though not strictly legal.
@see: U{section 9.1<http://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#section-9.1>} of the SFTP Internet-Draft. """ d = defer.Deferred() d.addCallback(self._cbTestPacketSTATUSWithoutLang) self.ftc.openRequests[1] = d data = struct.pack("!LL", 1, filetransfer.FX_OK) + common.NS(b"msg") self.ftc.packet_STATUS(data) return d
def _cbTestPacketSTATUSWithoutLang(self, result): """ Assert that the result is a two-tuple containing the message from the STATUS packet and an empty string, since the language was missing. """ self.assertEqual(result[0], b"msg") self.assertEqual(result[1], b"")
|