[Python-modules-commits] [python-asyncssh] 02/08: Import python-asyncssh_1.5.6.orig.tar.gz

Vincent Bernat bernat at moszumanska.debian.org
Sat Jul 2 12:15:54 UTC 2016


This is an automated email from the git hooks/post-receive script.

bernat pushed a commit to branch master
in repository python-asyncssh.

commit 1ed6ea46c8c27b3a572bb0638e3ab153c8d55720
Author: Vincent Bernat <bernat at debian.org>
Date:   Sat Jul 2 13:53:39 2016 +0200

    Import python-asyncssh_1.5.6.orig.tar.gz
---
 .coveragerc                           |    2 +
 .travis.yml                           |   70 ++
 .travis/build-libsodium.sh            |   11 +
 MANIFEST.in                           |    3 +-
 README.rst                            |   29 +-
 asyncssh/connection.py                |  133 ++-
 asyncssh/misc.py                      |   60 +-
 asyncssh/public_key.py                |    6 +-
 asyncssh/sftp.py                      |  365 +++---
 asyncssh/version.py                   |    2 +-
 docs/api.rst                          |    7 +
 docs/changes.rst                      |   47 +
 docs/index.rst                        |   20 +-
 examples/check_exit_status.py         |   15 +-
 examples/chroot_sftp_server.py        |   13 +-
 examples/direct_client.py             |   17 +-
 examples/direct_server.py             |   13 +-
 examples/listening_client.py          |   17 +-
 examples/local_forwarding_client.py   |   15 +-
 examples/local_forwarding_client2.py  |   15 +-
 examples/local_forwarding_server.py   |   13 +-
 examples/math_client.py               |   15 +-
 examples/math_server.py               |   13 +-
 examples/remote_forwarding_client.py  |   15 +-
 examples/remote_forwarding_client2.py |   15 +-
 examples/remote_forwarding_server.py  |   13 +-
 examples/sample_client.py             |   17 +-
 examples/set_environment.py           |   19 +-
 examples/set_terminal.py              |   21 +-
 examples/sftp_client.py               |   15 +-
 examples/show_environment.py          |   16 +-
 examples/show_terminal.py             |   18 +-
 examples/simple_cert_server.py        |   13 +-
 examples/simple_client.py             |   15 +-
 examples/simple_keyed_server.py       |   11 +-
 examples/simple_server.py             |   11 +-
 examples/simple_sftp_server.py        |   13 +-
 examples/stderr_client.py             |   15 +-
 examples/stream_direct_client.py      |   15 +-
 examples/stream_direct_server.py      |   18 +-
 examples/stream_listening_client.py   |   22 +-
 examples/stream_math_client.py        |   15 +-
 examples/stream_math_server.py        |   18 +-
 setup.py                              |    4 +-
 tests/__init__.py                     |    5 +-
 tests/server.py                       |   19 +-
 tests/test_auth_keys.py               |    2 +-
 tests/test_channel.py                 |    2 +-
 tests/test_connection.py              |  146 +++
 tests/test_forward.py                 |   10 +-
 tests/test_sftp.py                    | 2069 ++++++++++++++++++++++++++++++++-
 tests/util.py                         |   15 +-
 tests_py35/test_async.py              |  118 ++
 tox.ini                               |   13 +
 54 files changed, 3008 insertions(+), 611 deletions(-)

diff --git a/.coveragerc b/.coveragerc
index f2c3d0b..688d20e 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -5,3 +5,5 @@ branch = True
 exclude_lines =
     pragma: no cover
     raise NotImplementedError
+omit =
+    .tox/*
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..2e472ce
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,70 @@
+language: python
+
+install:
+  - pip install tox py-bcrypt libnacl
+
+matrix:
+  allow_failures:
+    - python: nightly
+  fast_finish: true
+  include:
+    - os: linux
+      sudo: required
+      dist: trusty
+      python: 3.4
+      before_install:
+        - .travis/build-libsodium.sh
+      env:
+        - TOXENV=py34
+    - os: linux
+      sudo: required
+      dist: trusty
+      python: 3.5
+      before_install:
+        - .travis/build-libsodium.sh
+      env:
+        - TOXENV=py35
+    - os: linux
+      sudo: required
+      dist: trusty
+      python: nightly
+      before_install:
+        - .travis/build-libsodium.sh
+      env:
+        - TOXENV=py36
+    - os: osx
+      osx_image: xcode7.3
+      language: generic
+      env:
+        - CPPFLAGS=-I/usr/local/opt/openssl/include
+        - LDFLAGS=-L/usr/local/opt/openssl/lib -L/usr/local/opt/libffi/lib
+        - PATH=$HOME/.pyenv/bin:$PATH
+        - TOXENV=py34
+      before_install:
+        - brew update
+        - brew install libffi libsodium openssl pyenv pyenv-virtualenv
+        - eval "$(pyenv init -)"
+        - eval "$(pyenv virtualenv-init -)"
+        - pyenv install 3.4.4
+        - pyenv rehash
+        - pyenv virtualenv 3.4.4 venv
+        - pyenv activate venv
+    - os: osx
+      osx_image: xcode7.3
+      language: generic
+      env:
+        - CPPFLAGS=-I/usr/local/opt/openssl/include
+        - LDFLAGS=-L/usr/local/opt/openssl/lib -L/usr/local/opt/libffi/lib
+        - PATH=$HOME/.pyenv/bin:$PATH
+        - TOXENV=py35
+      before_install:
+        - brew update
+        - brew install libffi libsodium openssl pyenv pyenv-virtualenv
+        - eval "$(pyenv init -)"
+        - eval "$(pyenv virtualenv-init -)"
+        - pyenv install 3.5.1
+        - pyenv rehash
+        - pyenv virtualenv 3.5.1 venv
+        - pyenv activate venv
+
+script: tox
diff --git a/.travis/build-libsodium.sh b/.travis/build-libsodium.sh
new file mode 100755
index 0000000..4b72471
--- /dev/null
+++ b/.travis/build-libsodium.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+#
+# Build and install libsodum 1.0.10
+#
+git clone git://github.com/jedisct1/libsodium.git
+cd libsodium
+git checkout tags/1.0.10
+./autogen.sh
+./configure
+make && sudo make install
+sudo ldconfig
diff --git a/MANIFEST.in b/MANIFEST.in
index e375cd5..9cd1216 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1 +1,2 @@
-include COPYRIGHT LICENSE README.rst examples/*.py tests/*.py
+include CONTRIBUTING.rst COPYRIGHT LICENSE README.rst pylintrc tox.ini
+include examples/*.py tests/*.py tests_py35/*.py
diff --git a/README.rst b/README.rst
index b5696da..30462ba 100644
--- a/README.rst
+++ b/README.rst
@@ -9,15 +9,14 @@ asyncio framework.
 
   import asyncio, asyncssh, sys
 
-  @asyncio.coroutine
-  def run_client():
-      with (yield from asyncssh.connect('localhost')) as conn:
-          stdin, stdout, stderr = yield from conn.open_session('echo "Hello!"')
+  async def run_client():
+      async with asyncssh.connect('localhost') as conn:
+          stdin, stdout, stderr = await conn.open_session('echo "Hello!"')
 
-          output = yield from stdout.read()
+          output = await stdout.read()
           print(output, end='')
 
-          yield from stdout.channel.wait_closed()
+          await stdout.channel.wait_closed()
 
           status = stdout.channel.get_exit_status()
           if status:
@@ -29,7 +28,7 @@ asyncio framework.
 
 Check out the `examples`__ to get started!
 
-__ http://asyncssh.readthedocs.org/en/stable/#client-examples
+__ http://asyncssh.readthedocs.io/en/stable/#client-examples
 
 Features
 --------
@@ -63,14 +62,14 @@ Features
 * Designed to be easy to extend to support new forms of key exchange,
   authentication, encryption, and compression algorithms
 
-__ http://asyncssh.readthedocs.org/en/stable/api.html#key-exchange-algorithms
-__ http://asyncssh.readthedocs.org/en/stable/api.html#encryption-algorithms
-__ http://asyncssh.readthedocs.org/en/stable/api.html#mac-algorithms
-__ http://asyncssh.readthedocs.org/en/stable/api.html#compression-algorithms
-__ http://asyncssh.readthedocs.org/en/stable/api.html#public-key-support
-__ http://asyncssh.readthedocs.org/en/stable/api.html#ssh-agent-support
-__ http://asyncssh.readthedocs.org/en/stable/api.html#known-hosts
-__ http://asyncssh.readthedocs.org/en/stable/api.html#authorized-keys
+__ http://asyncssh.readthedocs.io/en/stable/api.html#key-exchange-algorithms
+__ http://asyncssh.readthedocs.io/en/stable/api.html#encryption-algorithms
+__ http://asyncssh.readthedocs.io/en/stable/api.html#mac-algorithms
+__ http://asyncssh.readthedocs.io/en/stable/api.html#compression-algorithms
+__ http://asyncssh.readthedocs.io/en/stable/api.html#public-key-support
+__ http://asyncssh.readthedocs.io/en/stable/api.html#ssh-agent-support
+__ http://asyncssh.readthedocs.io/en/stable/api.html#known-hosts
+__ http://asyncssh.readthedocs.io/en/stable/api.html#authorized-keys
 
 License
 -------
diff --git a/asyncssh/connection.py b/asyncssh/connection.py
index a724dff..079080d 100644
--- a/asyncssh/connection.py
+++ b/asyncssh/connection.py
@@ -75,7 +75,7 @@ from .logging import logger
 from .mac import get_mac_algs, get_mac_params, get_mac
 
 from .misc import ChannelOpenError, DisconnectError, PasswordChangeRequired
-from .misc import ip_address, map_handler_name
+from .misc import async_context_manager, ip_address, map_handler_name
 
 from .packet import Boolean, Byte, NameList, String, UInt32, UInt64
 from .packet import PacketDecodeError, SSHPacket, SSHPacketHandler
@@ -236,6 +236,7 @@ def _load_private_keypair_list(keylist, passphrase=None):
 
         return result
 
+
 def _load_public_key(key):
     """Load a public key
 
@@ -271,6 +272,7 @@ def _load_public_key_list(keylist):
     else:
         return [_load_public_key(key) for key in keylist]
 
+
 def _load_authorized_keys(authorized_keys):
     """Load authorized keys list
 
@@ -286,6 +288,29 @@ def _load_authorized_keys(authorized_keys):
     else:
         return authorized_keys
 
+
+def _validate_version(version):
+    """Validate requested SSH version"""
+
+    if version is ():
+        from .version import __version__
+
+        version = b'AsyncSSH_' + __version__.encode('ascii')
+    else:
+        if isinstance(version, str):
+            version = version.encode('ascii')
+
+        # Version including 'SSH-2.0-' and CRLF must be 255 chars or less
+        if len(version) > 245:
+            raise ValueError('Version string is too long')
+
+        for b in version:
+            if b < 0x20 or b > 0x7e:
+                raise ValueError('Version string must be printable ASCII')
+
+    return version
+
+
 def _select_algs(alg_type, algs, possible_algs, none_value=None):
     """Select a set of allowed algorithms"""
 
@@ -324,9 +349,9 @@ def _validate_algs(kex_algs, enc_algs, mac_algs, cmp_algs):
 class SSHConnection(SSHPacketHandler):
     """Parent class for SSH connections"""
 
-    def __init__(self, protocol_factory, loop, kex_algs, encryption_algs,
-                 mac_algs, compression_algs, rekey_bytes, rekey_seconds,
-                 server):
+    def __init__(self, protocol_factory, loop, version, kex_algs,
+                 encryption_algs, mac_algs, compression_algs, rekey_bytes,
+                 rekey_seconds, server):
         self._protocol_factory = protocol_factory
         self._loop = loop
         self._tasks = set()
@@ -339,6 +364,7 @@ class SSHConnection(SSHPacketHandler):
         self._packet = b''
         self._pktlen = 0
 
+        self._version = version
         self._client_version = b''
         self._server_version = b''
         self._client_kexinit = b''
@@ -438,6 +464,19 @@ class SSHConnection(SSHPacketHandler):
             else:
                 raise
 
+    @asyncio.coroutine
+    def __aenter__(self):
+        """Allow SSHConnection to be used as an async context manager"""
+
+        return self
+
+    @asyncio.coroutine
+    def __aexit__(self, *exc_info):
+        """Wait for connection close when used as an async context manager"""
+
+        self.__exit__()
+        yield from self.wait_closed()
+
     def _cleanup(self, exc):
         """Clean up this connection"""
 
@@ -665,9 +704,7 @@ class SSHConnection(SSHPacketHandler):
     def _send_version(self):
         """Start the SSH handshake"""
 
-        from .version import __version__
-
-        version = b'SSH-2.0-AsyncSSH_' + __version__.encode('ascii')
+        version = b'SSH-2.0-' + self._version
 
         if self.is_client():
             self._client_version = version
@@ -1806,6 +1843,9 @@ class SSHConnection(SSHPacketHandler):
         """
 
         try:
+            if dest_host == '':
+                dest_host = None
+
             _, peer = yield from self._loop.create_connection(SSHForwarder,
                                                               dest_host,
                                                               dest_port)
@@ -1938,13 +1978,13 @@ class SSHClientConnection(SSHConnection):
 
     """
 
-    def __init__(self, client_factory, loop, kex_algs, encryption_algs,
-                 mac_algs, compression_algs, rekey_bytes, rekey_seconds,
-                 host, port, known_hosts, username, password, client_keys,
-                 agent, agent_path, auth_waiter):
-        super().__init__(client_factory, loop, kex_algs, encryption_algs,
-                         mac_algs, compression_algs, rekey_bytes,
-                         rekey_seconds, server=False)
+    def __init__(self, client_factory, loop, client_version, kex_algs,
+                 encryption_algs, mac_algs, compression_algs, rekey_bytes,
+                 rekey_seconds, host, port, known_hosts, username, password,
+                 client_keys, agent, agent_path, auth_waiter):
+        super().__init__(client_factory, loop, client_version, kex_algs,
+                         encryption_algs, mac_algs, compression_algs,
+                         rekey_bytes, rekey_seconds, server=False)
 
         self._host = host
         self._port = port if port != _DEFAULT_PORT else None
@@ -2876,7 +2916,7 @@ class SSHClientConnection(SSHConnection):
         return (yield from self.create_unix_server(session_factory,
                                                    listen_path))
 
-    @asyncio.coroutine
+    @async_context_manager
     def start_sftp_client(self, path_encoding='utf-8', path_errors='strict'):
         """Start an SFTP client
 
@@ -2949,14 +2989,15 @@ class SSHServerConnection(SSHConnection):
 
     """
 
-    def __init__(self, server_factory, loop, kex_algs, encryption_algs,
-                 mac_algs, compression_algs, rekey_bytes, rekey_seconds,
-                 server_host_keys, authorized_client_keys, allow_pty,
-                 agent_forwarding, session_factory, session_encoding,
-                 sftp_factory, window, max_pktsize, login_timeout):
-        super().__init__(server_factory, loop, kex_algs, encryption_algs,
-                         mac_algs, compression_algs, rekey_bytes,
-                         rekey_seconds, server=True)
+    def __init__(self, server_factory, loop, server_version, kex_algs,
+                 encryption_algs, mac_algs, compression_algs, rekey_bytes,
+                 rekey_seconds, server_host_keys, authorized_client_keys,
+                 allow_pty, agent_forwarding, session_factory,
+                 session_encoding, sftp_factory, window, max_pktsize,
+                 login_timeout):
+        super().__init__(server_factory, loop, server_version, kex_algs,
+                         encryption_algs, mac_algs, compression_algs,
+                         rekey_bytes, rekey_seconds, server=True)
 
         self._server_host_keys = server_host_keys
         self._server_host_key_algs = server_host_keys.keys()
@@ -3860,8 +3901,9 @@ def create_connection(client_factory, host, port=_DEFAULT_PORT, *,
                       loop=None, tunnel=None, family=0, flags=0,
                       local_addr=None, known_hosts=(), username=None,
                       password=None, client_keys=(), passphrase=None,
-                      agent_path=(), agent_forwarding=False, kex_algs=(),
-                      encryption_algs=(), mac_algs=(), compression_algs=(),
+                      agent_path=(), agent_forwarding=False,
+                      client_version=(), kex_algs=(), encryption_algs=(),
+                      mac_algs=(), compression_algs=(),
                       rekey_bytes=_DEFAULT_REKEY_BYTES,
                       rekey_seconds=_DEFAULT_REKEY_SECONDS):
     """Create an SSH client connection
@@ -3963,6 +4005,9 @@ def create_connection(client_factory, host, port=_DEFAULT_PORT, *,
            Whether or not to allow forwarding of ssh-agent requests from
            processes running on the server. By default, ssh-agent forwarding
            requests from the server are not allowed.
+       :param str client_version: (optional)
+           An ASCII string to advertise to the SSH server as the version of
+           this client, defaulting to ``AsyncSSH`` and its version number.
        :param kex_algs: (optional)
            A list of allowed key exchange algorithms in the SSH handshake,
            taken from :ref:`key exchange algorithms <KexAlgs>`
@@ -4002,11 +4047,12 @@ def create_connection(client_factory, host, port=_DEFAULT_PORT, *,
     def conn_factory():
         """Return an SSH client connection handler"""
 
-        return SSHClientConnection(client_factory, loop, kex_algs,
-                                   encryption_algs, mac_algs, compression_algs,
-                                   rekey_bytes, rekey_seconds, host, port,
-                                   known_hosts, username, password,
-                                   client_keys, agent, agent_path, auth_waiter)
+        return SSHClientConnection(client_factory, loop, client_version,
+                                   kex_algs, encryption_algs, mac_algs,
+                                   compression_algs, rekey_bytes,
+                                   rekey_seconds, host, port, known_hosts,
+                                   username, password, client_keys, agent,
+                                   agent_path, auth_waiter)
 
     if not client_factory:
         client_factory = SSHClient
@@ -4014,6 +4060,8 @@ def create_connection(client_factory, host, port=_DEFAULT_PORT, *,
     if not loop:
         loop = asyncio.get_event_loop()
 
+    client_version = _validate_version(client_version)
+
     kex_algs, encryption_algs, mac_algs, compression_algs = \
         _validate_algs(kex_algs, encryption_algs, mac_algs, compression_algs)
 
@@ -4077,7 +4125,7 @@ def create_connection(client_factory, host, port=_DEFAULT_PORT, *,
 def create_server(server_factory, host=None, port=_DEFAULT_PORT, *,
                   loop=None, family=0, flags=socket.AI_PASSIVE, backlog=100,
                   reuse_address=None, server_host_keys, passphrase=None,
-                  authorized_client_keys=None, kex_algs=(),
+                  authorized_client_keys=None, server_version=(), kex_algs=(),
                   encryption_algs=(), mac_algs=(), compression_algs=(),
                   allow_pty=True, agent_forwarding=True, session_factory=None,
                   session_encoding='utf-8', sftp_factory=None,
@@ -4128,6 +4176,9 @@ def create_server(server_factory, host=None, port=_DEFAULT_PORT, *,
        :param authorized_client_keys: (optional)
            A list of authorized user and CA public keys which should be
            trusted for certifcate-based client public key authentication.
+       :param str server_version: (optional)
+           An ASCII string to advertise to SSH clients as the version of
+           this server, defaulting to ``AsyncSSH`` and its version number.
        :param kex_algs: (optional)
            A list of allowed key exchange algorithms in the SSH handshake,
            taken from :ref:`key exchange algorithms <KexAlgs>`
@@ -4196,14 +4247,14 @@ def create_server(server_factory, host=None, port=_DEFAULT_PORT, *,
     def conn_factory():
         """Return an SSH server connection handler"""
 
-        return SSHServerConnection(server_factory, loop, kex_algs,
-                                   encryption_algs, mac_algs, compression_algs,
-                                   rekey_bytes, rekey_seconds,
-                                   server_host_keys, authorized_client_keys,
-                                   allow_pty, agent_forwarding,
-                                   session_factory, session_encoding,
-                                   sftp_factory, window, max_pktsize,
-                                   login_timeout)
+        return SSHServerConnection(server_factory, loop, server_version,
+                                   kex_algs, encryption_algs, mac_algs,
+                                   compression_algs, rekey_bytes,
+                                   rekey_seconds, server_host_keys,
+                                   authorized_client_keys, allow_pty,
+                                   agent_forwarding, session_factory,
+                                   session_encoding, sftp_factory, window,
+                                   max_pktsize, login_timeout)
 
     if not server_factory:
         server_factory = SSHServer
@@ -4214,6 +4265,8 @@ def create_server(server_factory, host=None, port=_DEFAULT_PORT, *,
     if not loop:
         loop = asyncio.get_event_loop()
 
+    server_version = _validate_version(server_version)
+
     kex_algs, encryption_algs, mac_algs, compression_algs = \
         _validate_algs(kex_algs, encryption_algs, mac_algs, compression_algs)
 
@@ -4239,7 +4292,7 @@ def create_server(server_factory, host=None, port=_DEFAULT_PORT, *,
                                           reuse_address=reuse_address))
 
 
- at asyncio.coroutine
+ at async_context_manager
 def connect(host, port=_DEFAULT_PORT, **kwargs):
     """Make an SSH client connection
 
diff --git a/asyncssh/misc.py b/asyncssh/misc.py
index f41ca81..c918255 100644
--- a/asyncssh/misc.py
+++ b/asyncssh/misc.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2013-2015 by Ron Frederick <ronf at timeheart.net>.
+# Copyright (c) 2013-2016 by Ron Frederick <ronf at timeheart.net>.
 # All rights reserved.
 #
 # This program and the accompanying materials are made available under
@@ -12,7 +12,10 @@
 
 """Miscellaneous utility classes and functions"""
 
+import asyncio
+import functools
 import ipaddress
+import platform
 import socket
 
 from random import SystemRandom
@@ -20,6 +23,10 @@ from random import SystemRandom
 from .constants import DEFAULT_LANG
 
 
+# Provide a global to test if we're on Python 3.5 or later
+python35 = platform.python_version_tuple() >= ('3', '5', '0')
+
+
 # Define a version of randrange which is based on SystemRandom(), so that
 # we get back numbers suitable for cryptographic use.
 _random = SystemRandom()
@@ -81,6 +88,57 @@ def ip_network(addr):
     return ipaddress.ip_network(_normalize_scoped_ip(addr) + mask)
 
 
+def async_context_manager(coro):
+    """Decorator for methods returning asynchronous context managers
+
+       This function can be used as a decorator for coroutines which
+       return objects intended to be used as Python 3.5 asynchronous
+       context managers. The object returned should implement __aenter__
+       and __aexit__ methods to run when the async context is entered
+       and exited.
+
+       This wrapper also allows non-async context managers to be defined
+       on the returned object, as well as the use of "await" or "yield
+       from" on the function being decorated for backward compatibility
+       with the API defined by older versions of AsyncSSH.
+
+    """
+
+    class AsyncContextManager:
+        """Async context manager wrapper for Python 3.5 and later"""
+
+        def __init__(self, coro):
+            self._coro = coro
+            self._result = None
+
+        def __iter__(self):
+            return (yield from self._coro)
+
+        def __await__(self):
+            return (yield from self._coro)
+
+        @asyncio.coroutine
+        def __aenter__(self):
+            self._result = yield from self._coro
+            return (yield from self._result.__aenter__())
+
+        @asyncio.coroutine
+        def __aexit__(self, *exc_info):
+            yield from self._result.__aexit__(*exc_info)
+            self._result = None
+
+    @functools.wraps(coro)
+    def coro_wrapper(*args, **kwargs):
+        """Return an async context manager wrapper for this coroutine"""
+
+        return AsyncContextManager(asyncio.coroutine(coro)(*args, **kwargs))
+
+    if python35:
+        return coro_wrapper
+    else:
+        return coro
+
+
 class Error(Exception):
     """General SSH error"""
 
diff --git a/asyncssh/public_key.py b/asyncssh/public_key.py
index e25d614..c6a97a2 100644
--- a/asyncssh/public_key.py
+++ b/asyncssh/public_key.py
@@ -18,7 +18,7 @@ import time
 
 try:
     import bcrypt
-    _bcrypt_available = True
+    _bcrypt_available = hasattr(bcrypt, 'kdf')
 except ImportError: # pragma: no cover
     _bcrypt_available = False
 
@@ -241,7 +241,7 @@ class SSHKey:
 
                 if not _bcrypt_available: # pragma: no cover
                     raise KeyExportError('OpenSSH private key encryption '
-                                         'requires bcrypt')
+                                         'requires bcrypt with KDF support')
 
                 kdf = b'bcrypt'
                 salt = os.urandom(_OPENSSH_SALT_LEN)
@@ -699,7 +699,7 @@ def _decode_openssh_private(data, passphrase):
 
             if not _bcrypt_available: # pragma: no cover
                 raise KeyEncryptionError('OpenSSH private key encryption '
-                                         'requires bcrypt')
+                                         'requires bcrypt with KDF support')
 
             packet = SSHPacket(kdf_data)
             salt = packet.get_string()
diff --git a/asyncssh/sftp.py b/asyncssh/sftp.py
index ad431b6..c8c05fd 100644
--- a/asyncssh/sftp.py
+++ b/asyncssh/sftp.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2015 by Ron Frederick <ronf at timeheart.net>.
+# Copyright (c) 2015-2016 by Ron Frederick <ronf at timeheart.net>.
 # All rights reserved.
 #
 # This program and the accompanying materials are made available under
@@ -15,17 +15,15 @@
 """SFTP handlers"""
 
 import asyncio
+from collections import OrderedDict
+import errno
+from fnmatch import fnmatch
 import os
+from os import SEEK_SET, SEEK_CUR, SEEK_END
 import posixpath
 import stat
 import time
 
-# pylint: disable=ungrouped-imports
-from asyncio import FIRST_COMPLETED
-from collections import OrderedDict
-from fnmatch import fnmatch
-from os import SEEK_SET, SEEK_CUR, SEEK_END
-
 from .constants import DEFAULT_LANG
 
 from .constants import FXP_INIT, FXP_VERSION, FXP_OPEN, FXP_CLOSE, FXP_READ
@@ -47,10 +45,9 @@ from .constants import FX_OK, FX_EOF, FX_NO_SUCH_FILE, FX_PERMISSION_DENIED
 from .constants import FX_FAILURE, FX_BAD_MESSAGE, FX_NO_CONNECTION
 from .constants import FX_CONNECTION_LOST, FX_OP_UNSUPPORTED
 
-from .misc import Error
-from .packet import Byte, String, UInt32, UInt64, PacketDecodeError, SSHPacket
+from .misc import Error, async_context_manager
 
-# pylint: enable=ungrouped-imports
+from .packet import Byte, String, UInt32, UInt64, PacketDecodeError, SSHPacket
 
 _SFTP_VERSION = 3
 _SFTP_BLOCK_SIZE = 16384
@@ -133,7 +130,7 @@ class _LocalFile:
     def __enter__(self):
         return self
 
-    def __exit__(self, *excinfo):
+    def __exit__(self, *exc_info):
         self._file.close()
 
     @classmethod
@@ -144,10 +141,7 @@ class _LocalFile:
 
         """
 
-        if isinstance(path, str):
-            path = os.fsencode(path)
-
-        return path
+        return os.fsencode(path)
 
     @classmethod
     def decode(cls, path, want_string=True):
@@ -204,69 +198,6 @@ class _LocalFile:
 
     @classmethod
     @asyncio.coroutine
-    def truncate(cls, path):
-        """Truncate a local file to the specified size"""
-
-        os.truncate(path)
-
-    @classmethod
-    @asyncio.coroutine
-    def chown(cls, path, uid, gid):
-        """Change the owner user and group id of a local file or directory"""
-
-        os.chown(path, uid, gid)
-
-    @classmethod
-    @asyncio.coroutine
-    def chmod(cls, path, mode):
-        """Change the file permissions of a local file or directory"""
-
-        os.chmod(path, mode)
-
-    @classmethod
-    @asyncio.coroutine
-    def utime(cls, path, times=None):
-        """Change the access and modify times of a local file or directory"""
-
-        os.utime(path, times)
-
-    @classmethod
-    @asyncio.coroutine
-    def exists(cls, path):
-        """Return if the local path exists and isn't a broken symbolic link"""
-
-        return os.path.exists(path)
-
-    @classmethod
-    @asyncio.coroutine
-    def lexists(cls, path):
-        """Return if the local path exists, without following symbolic links"""
-
-        return os.path.lexists(path)
-
-    @classmethod
-    @asyncio.coroutine
-    def getatime(cls, path):
-        """Return the last access time of a local file or directory"""
-
-        return os.path.getatime(path)
-
-    @classmethod
-    @asyncio.coroutine
-    def getmtime(cls, path):
-        """Return the last modification time of a local file or directory"""
-
-        return os.path.getmtime(path)
-
-    @classmethod
-    @asyncio.coroutine
-    def getsize(cls, path):
-        """Return the size of a local file or directory"""
-
-        return os.path.getsize(path)
-
-    @classmethod
-    @asyncio.coroutine
     def isdir(cls, path):
         """Return if the local path refers to a directory"""
 
@@ -274,53 +205,6 @@ class _LocalFile:
 
     @classmethod
     @asyncio.coroutine
-    def isfile(cls, path):
-        """Return if the local path refers to a regular file"""
-
-        return os.path.isfile(path)
-
-    @classmethod
-    @asyncio.coroutine
-    def islink(cls, path):
-        """Return if the local path refers to a symbolic link"""
-
-        return os.path.islink(path)
-
-    @classmethod
-    @asyncio.coroutine
-    def remove(cls, path):
-        """Remove a local file"""
-
-        os.remove(path)
-
-    @classmethod
-    @asyncio.coroutine
-    def unlink(cls, path):
-        """Remove a local file (see :meth:`remove`)"""
-
-        os.unlink(path)
-
-    @classmethod
-    @asyncio.coroutine
-    def rename(cls, oldpath, newpath):
-        """Rename a local file, directory, or link"""
-
-        os.rename(oldpath, newpath)
-
-    @classmethod
-    @asyncio.coroutine
-    def readdir(cls, path):
-        """Read the contents of a local directory"""
-
-        names = os.listdir(path)
-
-        # This pylint error appears to be a false positive
-        # pylint: disable=not-an-iterable
-        return [SFTPName(filename=name, attrs=(yield from cls.stat(name)))
-                for name in names]
-
-    @classmethod
-    @asyncio.coroutine
     def listdir(cls, path):
         """Read the names of the files in a local directory"""
 
@@ -335,34 +219,6 @@ class _LocalFile:
 
     @classmethod
     @asyncio.coroutine
-    def rmdir(cls, path):
-        """Remove a local directory"""
-
-        os.rmdir(path)
-
-    @classmethod
-    @asyncio.coroutine
-    def realpath(cls, path):
-        """Return the canonical version of a local path"""
-
-        return os.path.realpath(path)
-
-    @classmethod
-    @asyncio.coroutine
-    def getcwd(cls):
-        """Return the local current working directory"""
-
-        return os.getcwd()
-
-    @classmethod
-    @asyncio.coroutine
-    def chdir(cls, path):
-        """Change the local current working directory"""
-
-        os.chdir(path)
-
-    @classmethod
-    @asyncio.coroutine
     def readlink(cls, path):
         """Return the target of a local symbolic link"""
 
@@ -375,49 +231,20 @@ class _LocalFile:
 
         os.symlink(oldpath, newpath)
 
-    @classmethod
     @asyncio.coroutine
-    def link(cls, oldpath, newpath):
-        """Create a local hard link"""
-
-        os.link(oldpath, newpath)
-
-    @asyncio.coroutine
-    def read(self, size=-1, offset=None):
+    def read(self, size, offset):
         """Read data from the local file"""
 
-        if offset is not None:
-            self._file.seek(offset)
-
+        self._file.seek(offset)
         return self._file.read(size)
 
     @asyncio.coroutine
-    def write(self, data, offset=None):
+    def write(self, data, offset):
         """Write data to the local file"""
 
-        if offset is not None:
-            self._file.seek(offset)
-
+        self._file.seek(offset)
         return self._file.write(data)
 
-    @asyncio.coroutine
-    def seek(self, offset, from_what=SEEK_SET):
-        """Seek to a new position in the local file"""
-
-        return self._file.seek(offset, from_what)
-
-    @asyncio.coroutine
-    def tell(self):
-        """Return the current position in the local file"""
-
-        return self._file.tell()
-
-    @asyncio.coroutine
-    def close(self):
-        """Close the local file"""
-
-        self._file.close()
-
 
 class _SFTPFileCopier:
     """SFTP file copier
@@ -438,11 +265,8 @@ class _SFTPFileCopier:
     def _copy_block(self, offset, size):
         """Copy the next block of the file"""
 
-        data = yield from self._src.read(size, offset=offset)
-        if not data:
-            return
-
-        yield from self._dst.write(data, offset=offset)
+        data = yield from self._src.read(size, offset)
+        yield from self._dst.write(data, offset)
 
     def _copy_blocks(self):
         """Create parallel requests to copy blocks from one file to another"""
@@ -465,15 +289,20 @@ class _SFTPFileCopier:
                 self._bytes_left = size
                 self._copy_blocks()
 
-                try:
-                    while self._pending:
-                        _, self._pending = yield from asyncio.wait(
-                            self._pending, return_when=FIRST_COMPLETED)
+                while self._pending:
+                    done, self._pending = yield from asyncio.wait(
+                        self._pending, return_when=asyncio.FIRST_COMPLETED)
 
-                        self._copy_blocks()
-                finally:
-                    for task in self._pending:
-                        task.cancel()
+                    exceptions = [task.exception() for task in done
+                                  if task.exception()]
+
+                    if exceptions:
+                        for task in self._pending:
+                            task.cancel()
+
+                        raise exceptions[0]
+
+                    self._copy_blocks()
 
 
 class SFTPError(Error):
@@ -748,12 +577,13 @@ class SFTPHandler:
         self._reader = reader
         self._writer = writer
 
+    @asyncio.coroutine
     def _cleanup(self, exc):
         """Clean up this SFTP session"""
 
         # pylint: disable=unused-argument
 
-        if self._writer:
+        if self._writer: # pragma: no branch
             self._writer.close()
             self._reader = None
             self._writer = None
@@ -777,7 +607,7 @@ class SFTPHandler:
         try:
             self._writer.write(UInt32(len(payload)) + payload)
         except ConnectionError as exc:
-            raise SFTPError(FX_CONNECTION_LOST, str(exc))
+            raise SFTPError(FX_CONNECTION_LOST, str(exc)) from None
 
     @asyncio.coroutine
     def recv_packet(self):
@@ -789,8 +619,8 @@ class SFTPHandler:
 
             packet = yield from self._reader.readexactly(pktlen)
             packet = SSHPacket(packet)
-        except (ConnectionError, EOFError) as exc:
-            raise SFTPError(FX_CONNECTION_LOST, str(exc))
+        except EOFError:
+            raise SFTPError(FX_CONNECTION_LOST, 'Channel closed') from None
 
         return packet
 
@@ -799,7 +629,7 @@ class SFTPHandler:
         """Receive and process SFTP packets"""
 
         try:
-            while self._reader:
+            while self._reader: # pragma: no branch
                 packet = yield from self.recv_packet()
 
... 4072 lines suppressed ...

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/python-asyncssh.git



More information about the Python-modules-commits mailing list