commit python-asyncssh for openSUSE:Factory
Hello community, here is the log from the commit of package python-asyncssh for openSUSE:Factory checked in at 2019-08-09 16:53:50 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-asyncssh (Old) and /work/SRC/openSUSE:Factory/.python-asyncssh.new.9556 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-asyncssh" Fri Aug 9 16:53:50 2019 rev:8 rq:721769 version:1.17.1 Changes: -------- --- /work/SRC/openSUSE:Factory/python-asyncssh/python-asyncssh.changes 2019-07-04 15:44:46.154255491 +0200 +++ /work/SRC/openSUSE:Factory/.python-asyncssh.new.9556/python-asyncssh.changes 2019-08-09 16:53:55.785460852 +0200 @@ -1,0 +2,24 @@ +Thu Aug 8 12:49:50 UTC 2019 - Ondřej Súkup <mimi.vx@gmail.com> + +- update to 1.17.1 + * Improved construction of file paths in SFTP to better handle native Windows + source paths containing backslashes or drive letters. + * Improved SFTP parallel I/O for large reads and file copies to better handle + the case where a read returns less data than what was requested when not + at the end of the file, allowing AsyncSSH to get back the right result even + if the requested block size is larger than the SFTP server can handle. + * Fixed an issue where the requested SFTP block_size wasn’t used in the get, + copy, mget, and mcopy functions if it was larger than the default size of 16 KB. + * Fixed a problem where the list of client keys provided in + an SSHClientConnectionOptions object wasn’t always preserved properly across + the opening of multiple SSH connections. + * Made AsyncSSH tolerant of unexpected authentication success/failure messages + sent after authentication completes. AsyncSSH previously treated this as + a protocol error and dropped the connection, while most other SSH implementations + ignored these messages and allowed the connection to continue. + * Made AsyncSSH tolerant of SFTP status responses which are missing error message + and language tag fields, improving interoperability with servers that omit + these fields. When missing, AsyncSSH treats these fields as if they were + set to empty strings. + +------------------------------------------------------------------- Old: ---- asyncssh-1.17.0.tar.gz New: ---- asyncssh-1.17.1.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-asyncssh.spec ++++++ --- /var/tmp/diff_new_pack.FplzXE/_old 2019-08-09 16:53:56.221460747 +0200 +++ /var/tmp/diff_new_pack.FplzXE/_new 2019-08-09 16:53:56.221460747 +0200 @@ -19,7 +19,7 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} %define skip_python2 1 Name: python-asyncssh -Version: 1.17.0 +Version: 1.17.1 Release: 0 Summary: Asynchronous SSHv2 client and server library License: EPL-2.0 OR GPL-2.0-or-later ++++++ asyncssh-1.17.0.tar.gz -> asyncssh-1.17.1.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-1.17.0/PKG-INFO new/asyncssh-1.17.1/PKG-INFO --- old/asyncssh-1.17.0/PKG-INFO 2019-06-01 06:59:51.000000000 +0200 +++ new/asyncssh-1.17.1/PKG-INFO 2019-07-24 08:26:22.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: asyncssh -Version: 1.17.0 +Version: 1.17.1 Summary: AsyncSSH: Asynchronous SSHv2 client and server library Home-page: http://asyncssh.timeheart.net Author: Ron Frederick @@ -219,8 +219,8 @@ Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Networking -Provides-Extra: gssapi Provides-Extra: libnacl +Provides-Extra: gssapi Provides-Extra: bcrypt Provides-Extra: pyOpenSSL Provides-Extra: pypiwin32 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-1.17.0/asyncssh/agent.py new/asyncssh-1.17.1/asyncssh/agent.py --- old/asyncssh-1.17.0/asyncssh/agent.py 2019-06-01 06:34:50.000000000 +0200 +++ new/asyncssh-1.17.1/asyncssh/agent.py 2019-07-24 07:54:36.000000000 +0200 @@ -204,8 +204,16 @@ self._reader, self._writer = \ yield from self._agent_path.open_agent_connection() else: - self._reader, self._writer = \ - yield from open_agent(self._loop, self._agent_path) + agent_path = self._agent_path + + try: + self._reader, self._writer = \ + yield from open_agent(self._loop, agent_path) + except OSError as exc: + if agent_path: + logger.warning('Unable to contact agent: %s', exc) + + raise @asyncio.coroutine def _make_request(self, msgtype, *args): @@ -571,6 +579,9 @@ """ + if not agent_path: + agent_path = os.environ.get('SSH_AUTH_SOCK', None) + agent = SSHAgentClient(loop, agent_path) try: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-1.17.0/asyncssh/agent_unix.py new/asyncssh-1.17.1/asyncssh/agent_unix.py --- old/asyncssh-1.17.0/asyncssh/agent_unix.py 2019-03-31 04:50:15.000000000 +0200 +++ new/asyncssh-1.17.1/asyncssh/agent_unix.py 2019-07-24 07:54:36.000000000 +0200 @@ -22,7 +22,6 @@ import asyncio import errno -import os @asyncio.coroutine @@ -33,9 +32,6 @@ loop = asyncio.get_event_loop() if not agent_path: - agent_path = os.environ.get('SSH_AUTH_SOCK', None) - - if not agent_path: - raise OSError(errno.ENOENT, 'Agent not found') + raise OSError(errno.ENOENT, 'Agent not found') return (yield from asyncio.open_unix_connection(agent_path, loop=loop)) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-1.17.0/asyncssh/agent_win32.py new/asyncssh-1.17.1/asyncssh/agent_win32.py --- old/asyncssh-1.17.0/asyncssh/agent_win32.py 2019-05-29 04:54:23.000000000 +0200 +++ new/asyncssh-1.17.1/asyncssh/agent_win32.py 2019-07-24 07:54:36.000000000 +0200 @@ -27,7 +27,6 @@ import ctypes import ctypes.wintypes import errno -import os try: import mmapfile @@ -162,14 +161,11 @@ transport = None if not agent_path: - agent_path = os.environ.get('SSH_AUTH_SOCK', None) - - if not agent_path: - try: - _find_agent_window() - transport = _PageantTransport() - except OSError: - agent_path = _DEFAULT_OPENSSH_PATH + try: + _find_agent_window() + transport = _PageantTransport() + except OSError: + agent_path = _DEFAULT_OPENSSH_PATH if not transport: transport = _W10OpenSSHTransport(agent_path) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-1.17.0/asyncssh/connection.py new/asyncssh-1.17.1/asyncssh/connection.py --- old/asyncssh-1.17.0/asyncssh/connection.py 2019-06-01 06:34:50.000000000 +0200 +++ new/asyncssh-1.17.1/asyncssh/connection.py 2019-07-24 07:54:36.000000000 +0200 @@ -1698,7 +1698,8 @@ # pylint: disable=no-member self.try_next_auth() else: - raise ProtocolError('Unexpected userauth response') + self.logger.debug2('Unexpected userauth failure response') + self.send_packet(MSG_UNIMPLEMENTED, UInt32(pktid)) def _process_userauth_success(self, pkttype, pktid, packet): """Process a user authentication success response""" @@ -1738,7 +1739,8 @@ self._waiter.set_result(None) self._wait = None else: - raise ProtocolError('Unexpected userauth response') + self.logger.debug2('Unexpected userauth success response') + self.send_packet(MSG_UNIMPLEMENTED, UInt32(pktid)) def _process_userauth_banner(self, pkttype, pktid, packet): """Process a user authentication banner message""" @@ -2387,10 +2389,12 @@ self._password = options.password self._client_host_keysign = options.client_host_keysign - self._client_host_keys = options.client_host_keys + self._client_host_keys = None if options.client_host_keys is None else \ + list(options.client_host_keys) self._client_host = options.client_host self._client_username = options.client_username - self._client_keys = options.client_keys + self._client_keys = None if options.client_keys is None else \ + list(options.client_keys) if options.agent_path is not None: self._agent = SSHAgentClient(self._loop, options.agent_path) @@ -2602,8 +2606,8 @@ try: agent_keys = yield from self._agent.get_keys() self._client_keys = agent_keys + (self._client_keys or []) - except ValueError as exc: - logger.warning('Unable to read keys from agent: %s', exc) + except ValueError: + pass self._get_agent_keys = False @@ -5195,15 +5199,15 @@ :param client_keys: (optional) A list of keys which will be used to authenticate this client via public key authentication. If no client keys are specified, - an attempt will be made to get them from an ssh-agent process. - If that is not available, an attempt will be made to load them - from the files :file:`.ssh/id_ed25519`, :file:`.ssh/id_ecdsa`, - :file:`.ssh/id_rsa`, and :file:`.ssh/id_dsa` in the user's home - directory, with optional certificates loaded from the files - :file:`.ssh/id_ed25519-cert.pub`, :file:`.ssh/id_ecdsa-cert.pub`, - :file:`.ssh/id_rsa-cert.pub`, and :file:`.ssh/id_dsa-cert.pub`. - If this argument is explicitly set to `None`, client public - key authentication will not be performed. + an attempt will be made to get them from an ssh-agent process + and/or load them from the files :file:`.ssh/id_ed25519`, + :file:`.ssh/id_ecdsa`, :file:`.ssh/id_rsa`, and :file:`.ssh/id_dsa` + in the user's home directory, with optional certificates loaded + from the files :file:`.ssh/id_ed25519-cert.pub`, + :file:`.ssh/id_ecdsa-cert.pub`, :file:`.ssh/id_rsa-cert.pub`, + and :file:`.ssh/id_dsa-cert.pub`. If this argument is explicitly + set to `None`, client public key authentication will not be + performed. :param passphrase: (optional) The passphrase to use to decrypt client keys when loading them, if they are encrypted. If this is not specified, only unencrypted @@ -5365,16 +5369,18 @@ self.gss_delegate_creds = gss_delegate_creds if agent_path == (): - agent_path = os.environ.get('SSH_AUTH_SOCK', ()) + agent_path = os.environ.get('SSH_AUTH_SOCK', None) self.agent_path = None if client_keys: client_keys = load_keypairs(client_keys, passphrase) - elif client_keys == (): - self.agent_path = agent_path + else: + if client_keys is not None: + self.agent_path = agent_path - client_keys = load_default_keypairs(passphrase) + if client_keys == (): + client_keys = load_default_keypairs(passphrase) self.agent_forward_path = agent_path if agent_forwarding else None self.client_keys = client_keys diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-1.17.0/asyncssh/sftp.py new/asyncssh-1.17.1/asyncssh/sftp.py --- old/asyncssh-1.17.0/asyncssh/sftp.py 2019-06-01 06:34:50.000000000 +0200 +++ new/asyncssh-1.17.1/asyncssh/sftp.py 2019-07-24 07:54:36.000000000 +0200 @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2018 by Ron Frederick <ronf@timeheart.net> and others. +# Copyright (c) 2015-2019 by Ron Frederick <ronf@timeheart.net> and others. # # This program and the accompanying materials are made available under # the terms of the Eclipse Public License v2.0 which accompanies this @@ -254,6 +254,12 @@ self._file = f @classmethod + def basename(cls, path): + """Return the final component of a local file path""" + + return os.path.basename(path) + + @classmethod def encode(cls, path): """Encode path name using filesystem native encoding @@ -288,9 +294,11 @@ @classmethod @asyncio.coroutine - def open(cls, path, *args): + def open(cls, path, *args, block_size=None): """Open a local file""" + # pylint: disable=unused-argument + return cls(open(_to_local_path(path), *args)) @classmethod @@ -484,14 +492,20 @@ def run_task(self, offset, size): """Read a block of the file""" - data = yield from self._handler.read(self._handle, offset, size) - pos = offset - self._start - pad = pos - len(self._data) + while size: + data = yield from self._handler.read(self._handle, offset, size) - if pad > 0: - self._data += pad * b'\0' + pos = offset - self._start + pad = pos - len(self._data) - self._data[pos:pos+size] = data + if pad > 0: + self._data += pad * b'\0' + + datalen = len(data) + self._data[pos:pos+datalen] = data + + offset += datalen + size -= datalen @asyncio.coroutine def finish(self): @@ -550,20 +564,38 @@ def start(self): """Start parallel copy""" - self._src = yield from self._srcfs.open(self._srcpath, 'rb') - self._dst = yield from self._dstfs.open(self._dstpath, 'wb') + self._src = yield from self._srcfs.open(self._srcpath, 'rb', + block_size=None) + self._dst = yield from self._dstfs.open(self._dstpath, 'wb', + block_size=None) @asyncio.coroutine def run_task(self, offset, size): """Copy the next block of the file""" - data = yield from self._src.read(size, offset) - yield from self._dst.write(data, offset) + while size: + data = yield from self._src.read(size, offset) + + if not data: + exc = SFTPError(FX_FAILURE, 'Unexpected EOF during file copy') + + # pylint: disable=attribute-defined-outside-init + exc.filename = self._srcpath + exc.offset = offset + + raise exc + + yield from self._dst.write(data, offset) - if self._progress_handler: - self._bytes_copied += size - self._progress_handler(self._srcpath, self._dstpath, - self._bytes_copied, self._total_bytes) + datalen = len(data) + + if self._progress_handler: + self._bytes_copied += datalen + self._progress_handler(self._srcpath, self._dstpath, + self._bytes_copied, self._total_bytes) + + offset += datalen + size -= datalen @asyncio.coroutine def cleanup(self): @@ -1039,11 +1071,20 @@ code = packet.get_uint32() - try: - reason = packet.get_string().decode('utf-8') - lang = packet.get_string().decode('ascii') - except UnicodeDecodeError: - raise SFTPError(FX_BAD_MESSAGE, 'Invalid status message') from None + if packet: + try: + reason = packet.get_string().decode('utf-8') + lang = packet.get_string().decode('ascii') + except UnicodeDecodeError: + raise SFTPError(FX_BAD_MESSAGE, + 'Invalid status message') from None + else: + # Some servers may not always send reason and lang (usually + # when responding with FX_OK). Tolerate this, automatically + # filling in empty strings for them if they're not present. + + reason = '' + lang = '' packet.check_end() @@ -1887,6 +1928,13 @@ return self._handler.logger + def basename(self, path): + """Return the final component of a POSIX-style path""" + + # pylint: disable=no-self-use + + return posixpath.basename(path) + def encode(self, path): """Encode path name using configured path encoding @@ -2070,7 +2118,7 @@ for srcfile in srcpaths: srcfile = srcfs.encode(srcfile) - filename = posixpath.basename(srcfile) + filename = srcfs.basename(srcfile) if dstpath is None: dstfile = filename diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-1.17.0/asyncssh/version.py new/asyncssh-1.17.1/asyncssh/version.py --- old/asyncssh-1.17.0/asyncssh/version.py 2019-06-01 06:57:05.000000000 +0200 +++ new/asyncssh-1.17.1/asyncssh/version.py 2019-07-24 07:55:53.000000000 +0200 @@ -1,4 +1,4 @@ -# Copyright (c) 2013-2018 by Ron Frederick <ronf@timeheart.net> and others. +# Copyright (c) 2013-2019 by Ron Frederick <ronf@timeheart.net> and others. # # This program and the accompanying materials are made available under # the terms of the Eclipse Public License v2.0 which accompanies this @@ -26,4 +26,4 @@ __url__ = 'http://asyncssh.timeheart.net' -__version__ = '1.17.0' +__version__ = '1.17.1' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-1.17.0/asyncssh.egg-info/PKG-INFO new/asyncssh-1.17.1/asyncssh.egg-info/PKG-INFO --- old/asyncssh-1.17.0/asyncssh.egg-info/PKG-INFO 2019-06-01 06:59:51.000000000 +0200 +++ new/asyncssh-1.17.1/asyncssh.egg-info/PKG-INFO 2019-07-24 08:26:22.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: asyncssh -Version: 1.17.0 +Version: 1.17.1 Summary: AsyncSSH: Asynchronous SSHv2 client and server library Home-page: http://asyncssh.timeheart.net Author: Ron Frederick @@ -219,8 +219,8 @@ Classifier: Topic :: Security :: Cryptography Classifier: Topic :: Software Development :: Libraries :: Python Modules Classifier: Topic :: System :: Networking -Provides-Extra: gssapi Provides-Extra: libnacl +Provides-Extra: gssapi Provides-Extra: bcrypt Provides-Extra: pyOpenSSL Provides-Extra: pypiwin32 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-1.17.0/tests/test_agent.py new/asyncssh-1.17.1/tests/test_agent.py --- old/asyncssh-1.17.0/tests/test_agent.py 2019-03-31 04:50:15.000000000 +0200 +++ new/asyncssh-1.17.1/tests/test_agent.py 2019-07-24 07:54:36.000000000 +0200 @@ -89,7 +89,7 @@ os.remove(self._path) -class _TestAPI(AsyncTestCase): +class _TestAgent(AsyncTestCase): """Unit tests for AsyncSSH API""" _agent_pid = None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-1.17.0/tests/test_connection.py new/asyncssh-1.17.1/tests/test_connection.py --- old/asyncssh-1.17.0/tests/test_connection.py 2019-06-01 06:34:50.000000000 +0200 +++ new/asyncssh-1.17.1/tests/test_connection.py 2019-07-23 04:31:59.000000000 +0200 @@ -28,7 +28,7 @@ from unittest.mock import patch import asyncssh -from asyncssh.constants import MSG_DEBUG +from asyncssh.constants import MSG_UNIMPLEMENTED, MSG_DEBUG from asyncssh.constants import MSG_SERVICE_REQUEST, MSG_SERVICE_ACCEPT from asyncssh.constants import MSG_KEXINIT, MSG_NEWKEYS from asyncssh.constants import MSG_USERAUTH_REQUEST, MSG_USERAUTH_SUCCESS @@ -313,6 +313,14 @@ return False +def disconnect_on_unimplemented(self, pkttype, pktid, packet): + """Process an unimplemented message response""" + + # pylint: disable=unused-argument + + self.disconnect(asyncssh.DISC_BY_APPLICATION, 'Unexpected response') + + @patch_gss class _TestConnection(ServerTestCase): """Unit tests for AsyncSSH connection API""" @@ -1098,21 +1106,25 @@ def test_unexpected_userauth_success(self): """Test unexpected userauth success response""" - conn = yield from self.connect() + with patch.dict('asyncssh.connection.SSHConnection._packet_handlers', + {MSG_UNIMPLEMENTED: disconnect_on_unimplemented}): + conn = yield from self.connect() - conn.send_packet(MSG_USERAUTH_SUCCESS) + conn.send_packet(MSG_USERAUTH_SUCCESS) - yield from conn.wait_closed() + yield from conn.wait_closed() @asynctest def test_unexpected_userauth_failure(self): """Test unexpected userauth failure response""" - conn = yield from self.connect() + with patch.dict('asyncssh.connection.SSHConnection._packet_handlers', + {MSG_UNIMPLEMENTED: disconnect_on_unimplemented}): + conn = yield from self.connect() - conn.send_packet(MSG_USERAUTH_FAILURE, NameList([]), Boolean(False)) + conn.send_packet(MSG_USERAUTH_FAILURE, NameList([]), Boolean(False)) - yield from conn.wait_closed() + yield from conn.wait_closed() @asynctest def test_unexpected_userauth_banner(self): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-1.17.0/tests/test_connection_auth.py new/asyncssh-1.17.1/tests/test_connection_auth.py --- old/asyncssh-1.17.0/tests/test_connection_auth.py 2019-05-11 17:06:42.000000000 +0200 +++ new/asyncssh-1.17.1/tests/test_connection_auth.py 2019-07-23 04:31:59.000000000 +0200 @@ -1227,6 +1227,26 @@ yield from conn.wait_closed() + @asynctest + def test_auth_options_reuse(self): + """Test public key auth via SSHClientConnectionOptions""" + + def connect(): + """Connect to the server using options""" + + with (yield from asyncssh.connect(self._server_addr, + self._server_port, + loop=self.loop, + options=options)) as conn: + pass + + yield from conn.wait_closed() + + options = asyncssh.SSHClientConnectionOptions( + username='ckey', client_keys=[('ckey', None)], gss_host=None) + + yield from asyncio.gather(connect(), connect()) + class _TestPublicKeyAsyncServerAuth(_TestPublicKeyAuth): """Unit tests for public key authentication with async server callbacks""" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/asyncssh-1.17.0/tests/test_sftp.py new/asyncssh-1.17.1/tests/test_sftp.py --- old/asyncssh-1.17.0/tests/test_sftp.py 2019-06-01 06:34:50.000000000 +0200 +++ new/asyncssh-1.17.1/tests/test_sftp.py 2019-07-23 04:31:59.000000000 +0200 @@ -1,4 +1,4 @@ -# Copyright (c) 2015-2018 by Ron Frederick <ronf@timeheart.net> and others. +# Copyright (c) 2015-2019 by Ron Frederick <ronf@timeheart.net> and others. # # This program and the accompanying materials are made available under # the terms of the Eclipse Public License v2.0 which accompanies this @@ -190,6 +190,28 @@ super().write(file_obj, offset, data) +class _SmallBlockSizeSFTPServer(SFTPServer): + """Limit reads to a small block size""" + + @asyncio.coroutine + def read(self, file_obj, offset, size): + """Limit reads to return no more than 4 KB at a time""" + + return super().read(file_obj, offset, min(size, 4096)) + + +class _TruncateSFTPServer(SFTPServer): + """Truncate a file when it is accessed, simulating a simultaneous writer""" + + @asyncio.coroutine + def read(self, file_obj, offset, size): + """Truncate a file to 32 KB when a read is done""" + + os.truncate('src', 32768) + + return super().read(file_obj, offset, size) + + class _NotImplSFTPServer(SFTPServer): """Return an error that a request is not implemented""" @@ -475,7 +497,9 @@ if data == (): data = str(id(self)) - with open(name, 'w') as f: + binary = 'b' if isinstance(data, bytes) else '' + + with open(name, 'w' + binary) as f: f.write(data) if mode is not None: @@ -506,8 +530,8 @@ if preserve: self._check_attr(name1, name2, follow_symlinks, check_atime) - with open(name1) as file1: - with open(name2) as file2: + with open(name1, 'rb') as file1: + with open(name2, 'rb') as file2: self.assertEqual(file1.read(), file2.read()) def _check_stat(self, sftp_stat, local_stat): @@ -2173,6 +2197,22 @@ yield from sftp.open('file') @sftp_test + def test_short_ok_response(self, sftp): + """Test sending an FX_OK response without a reason and lang""" + + @asyncio.coroutine + def _short_ok_response(self, pkttype, pktid, packet): + """Send an FX_OK response missing reason and lang""" + + # pylint: disable=unused-argument + + self.send_packet(FXP_STATUS, pktid, UInt32(pktid), UInt32(FX_OK)) + + with patch('asyncssh.sftp.SFTPServerHandler._process_packet', + _short_ok_response): + self.assertIsNone((yield from sftp.mkdir('dir'))) + + @sftp_test def test_malformed_realpath_response(self, sftp): """Test receiving malformed realpath response""" @@ -2490,6 +2530,68 @@ remove('file') +class _TestSFTPSmallBlockSize(_CheckSFTP): + """Unit test for SFTP server returning file I/O error""" + + @classmethod + @asyncio.coroutine + def start_server(cls): + """Start an SFTP server which returns file I/O errors""" + + return (yield from cls.create_server( + sftp_factory=_SmallBlockSizeSFTPServer)) + + @sftp_test + def test_read(self, sftp): + """Test a large read on a server with a small block size""" + + try: + data = os.urandom(65536) + self._create_file('file', data) + + with (yield from sftp.open('file', 'rb')) as f: + result = yield from f.read(32768, 16384) + + self.assertEqual(result, data[16384:49152]) + finally: + remove('file') + + @sftp_test + def test_get(self, sftp): + """Test getting a file from an SFTP server with a small block size""" + + try: + data = os.urandom(65536) + self._create_file('src', data) + yield from sftp.get('src', 'dst') + self._check_file('src', 'dst') + finally: + remove('src dst') + + +class _TestSFTPEOFDuringCopy(_CheckSFTP): + """Unit test for SFTP server returning EOF during a file copy""" + + @classmethod + @asyncio.coroutine + def start_server(cls): + """Start an SFTP server which truncates files when accessed""" + + return (yield from cls.create_server(sftp_factory=_TruncateSFTPServer)) + + @sftp_test + def test_get(self, sftp): + """Test getting a file from an SFTP server truncated during the copy""" + + try: + self._create_file('src', 65536*'\0') + + with self.assertRaises(SFTPError): + yield from sftp.get('src', 'dst') + finally: + remove('src dst') + + class _TestSFTPNotImplemented(_CheckSFTP): """Unit test for SFTP server returning not-implemented error"""
participants (1)
-
root