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 <[email protected]>
+
+- 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 <[email protected]> and others.
+# Copyright (c) 2015-2019 by Ron Frederick <[email protected]> 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 <[email protected]> and others.
+# Copyright (c) 2013-2019 by Ron Frederick <[email protected]> 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 <[email protected]> and others.
+# Copyright (c) 2015-2019 by Ron Frederick <[email protected]> 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"""
 


Reply via email to