Hello community, here is the log from the commit of package python-scp for openSUSE:Factory checked in at 2019-03-14 15:00:29 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-scp (Old) and /work/SRC/openSUSE:Factory/.python-scp.new.28833 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-scp" Thu Mar 14 15:00:29 2019 rev:3 rq:684703 version:0.13.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-scp/python-scp.changes 2018-12-24 11:43:40.897330329 +0100 +++ /work/SRC/openSUSE:Factory/.python-scp.new.28833/python-scp.changes 2019-03-14 15:01:14.331696844 +0100 @@ -1,0 +2,16 @@ +Wed Mar 13 13:52:24 UTC 2019 - Tomáš Chvátal <[email protected]> + +- Update to 0.13.1: + * Guard against some malformed messages from the server + * Remove all introspection logic for progress callback introduced in 0.12 + * progress callback only accept 3 arguments again + * Introduce progress4 parameter which accepts the peername as 4th argument + * Fix progress callback failing when it is an instance or class method + * Fix README.rst for PyPI + * Add possibility of getting the peer IP and port from the progress callback + * Make putfo() work with file-like objects that don't provide getvalue() + * Add putfo() method, allowing one to upload a file-like object + * Add top-level get() and put() functions for convenience + * Increase default socket time from 5 to 10 seconds + +------------------------------------------------------------------- Old: ---- scp-0.10.2.tar.gz New: ---- scp-0.13.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-scp.spec ++++++ --- /var/tmp/diff_new_pack.dRCgyd/_old 2019-03-14 15:01:14.991696715 +0100 +++ /var/tmp/diff_new_pack.dRCgyd/_new 2019-03-14 15:01:14.991696715 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-scp # -# Copyright (c) 2018 SUSE LINUX GmbH, Nuernberg, Germany. +# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany. # Copyright (c) 2017, Martin Hauke <[email protected]> # # All modifications and additions to the file contributed by third parties @@ -18,22 +18,20 @@ %{?!python_module:%define python_module() python-%{**} python3-%{**}} -%bcond_without test Name: python-scp -Version: 0.10.2 +Version: 0.13.0 Release: 0 Summary: SSH scp module for paramiko License: LGPL-2.1-or-later Group: Development/Languages/Python URL: https://github.com/jbardin/scp.py Source: https://files.pythonhosted.org/packages/source/s/scp/scp-%{version}.tar.gz +BuildRequires: %{python_module paramiko} BuildRequires: %{python_module setuptools} +BuildRequires: fdupes BuildRequires: python-rpm-macros Requires: python-paramiko BuildArch: noarch -%if %{with test} -BuildRequires: %{python_module paramiko} -%endif %python_subpackages %description @@ -49,6 +47,11 @@ %install %python_install +%python_expand %fdupes %{buildroot}%{$python_sitelib} + +%check +# tests require running ssh server +#%%python_exec -m unittest discover %files %{python_files} %license LICENSE.txt ++++++ scp-0.10.2.tar.gz -> scp-0.13.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/scp-0.10.2/CHANGELOG.md new/scp-0.13.0/CHANGELOG.md --- old/scp-0.10.2/CHANGELOG.md 1970-01-01 01:00:00.000000000 +0100 +++ new/scp-0.13.0/CHANGELOG.md 2018-11-12 21:53:11.000000000 +0100 @@ -0,0 +1,40 @@ +# Changelog + +## 0.13.0 (2018-11-12) + +- Remove all introspection logic for `progress` callback introduced in 0.12 +- `progress` callback only accept 3 arguments again +- Introduce `progress4` parameter which accepts the peername as 4th argument + +## 0.12.1 (2018-10-12) + +- Fix `progress` callback failing when it is an instance or class method + +## 0.12.0 (2018-10-09) + +- Fix README.rst for PyPI +- Add possibility of getting the peer IP and port from the `progress` callback +- Make `putfo()` work with file-like objects that don't provide `getvalue()` + +## 0.11.0 (2018-05-05) + +- Add `putfo()` method, allowing one to upload a file-like object +- Add top-level `get()` and `put()` functions for convenience +- Increase default socket time from 5 to 10 seconds + +## 0.10.2 (2015-05-15) + +- Fixes using the SCPClient multiple times + +## 0.10.0 (2015-05-07) + +- SCPClient can be used as a context manager +- Added `close()` + +## 0.9.0 (2015-02-04) + +- Add changelog +- Finish up py3k and unicode support +- Unicode should work on OSX, Windows and Linux +- Some tests have been added + diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/scp-0.10.2/MANIFEST.in new/scp-0.13.0/MANIFEST.in --- old/scp-0.10.2/MANIFEST.in 2015-05-15 15:30:51.000000000 +0200 +++ new/scp-0.13.0/MANIFEST.in 2018-05-05 19:52:53.000000000 +0200 @@ -1,2 +1,4 @@ include LICENSE.txt include README.rst +include CHANGELOG.md +include test.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/scp-0.10.2/PKG-INFO new/scp-0.13.0/PKG-INFO --- old/scp-0.10.2/PKG-INFO 2015-05-15 16:24:53.000000000 +0200 +++ new/scp-0.13.0/PKG-INFO 2018-11-12 21:55:19.000000000 +0100 @@ -1,6 +1,6 @@ -Metadata-Version: 1.0 +Metadata-Version: 1.1 Name: scp -Version: 0.10.2 +Version: 0.13.0 Summary: scp module for paramiko Home-page: https://github.com/jbardin/scp.py Author: James Bardin @@ -26,12 +26,16 @@ ssh.load_system_host_keys() ssh.connect('example.com') - # SCPCLient takes a paramiko transport as its only argument + # SCPCLient takes a paramiko transport as an argument scp = SCPClient(ssh.get_transport()) scp.put('test.txt', 'test2.txt') scp.get('test2.txt') + # Uploading the 'test' directory with its content in the + # '/home/user/dump' remote directory + scp.put('test', recursive=True, remote_path='/home/user/dump') + scp.close() @@ -42,7 +46,7 @@ fc264c65fb17b7db5237cf7ce1780769 test2.txt Using 'with' keyword - ------------------ + -------------------- .. code-block:: python @@ -64,4 +68,84 @@ fc264c65fb17b7db5237cf7ce1780769 test.txt fc264c65fb17b7db5237cf7ce1780769 test2.txt + + Uploading file-like objects + --------------------------- + + The ``putfo`` method can be used to upload file-like objects: + + .. code-block:: python + + import io + from paramiko import SSHClient + from scp import SCPClient + + ssh = SSHClient() + ssh.load_system_host_keys() + ssh.connect('example.com') + + # SCPCLient takes a paramiko transport as an argument + scp = SCPClient(ssh.get_transport()) + + # generate in-memory file-like object + fl = io.BytesIO() + fl.write(b'test') + fl.seek(0) + # upload it directly from memory + scp.putfo(fl, '/tmp/test.txt') + # close connection + scp.close() + # close file handler + fl.close() + + + Tracking progress of your file uploads/downloads + ------------------------------------------------ + + A ``progress`` function can be given as a callback to the SCPClient to handle + how the current SCP operation handles the progress of the transfers. In the + example below we print the percentage complete of the file transfer. + + .. code-block:: python + + from paramiko import SSHClient + from scp import SCPClient + import sys + + ssh = SSHClient() + ssh.load_system_host_keys() + ssh.connect('example.com') + + # Define progress callback that prints the current percentage completed for the file + def progress(filename, size, sent): + sys.stdout.write("%s\'s progress: %.2f%% \r" % (filename, float(sent)/float(size)*100) ) + + # SCPCLient takes a paramiko transport and progress callback as its arguments. + scp = SCPClient(ssh.get_transport(), progress=progress) + + # you can also use progress4, which adds a 4th parameter to track IP and port + # useful with multiple threads to track source + def progress4(filename, size, sent, peername): + sys.stdout.write("(%s:%s) %s\'s progress: %.2f%% \r" % (peername[0], peername[1], filename, float(sent)/float(size)*100) ) + scp = SCPClient(ssh.get_transport(), progress4=progress4) + + scp.put('test.txt', '~/test.txt') + # Should now be printing the current progress of your put function. + + scp.close() + +Keywords: paramiko,ssh,scp,transfer Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Topic :: Internet diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/scp-0.10.2/README.rst new/scp-0.13.0/README.rst --- old/scp-0.10.2/README.rst 2015-05-15 15:36:01.000000000 +0200 +++ new/scp-0.13.0/README.rst 2018-11-12 21:51:26.000000000 +0100 @@ -18,12 +18,16 @@ ssh.load_system_host_keys() ssh.connect('example.com') - # SCPCLient takes a paramiko transport as its only argument + # SCPCLient takes a paramiko transport as an argument scp = SCPClient(ssh.get_transport()) scp.put('test.txt', 'test2.txt') scp.get('test2.txt') + # Uploading the 'test' directory with its content in the + # '/home/user/dump' remote directory + scp.put('test', recursive=True, remote_path='/home/user/dump') + scp.close() @@ -34,7 +38,7 @@ fc264c65fb17b7db5237cf7ce1780769 test2.txt Using 'with' keyword ------------------- +-------------------- .. code-block:: python @@ -55,3 +59,69 @@ $ md5sum test.txt test2.txt fc264c65fb17b7db5237cf7ce1780769 test.txt fc264c65fb17b7db5237cf7ce1780769 test2.txt + + +Uploading file-like objects +--------------------------- + +The ``putfo`` method can be used to upload file-like objects: + +.. code-block:: python + + import io + from paramiko import SSHClient + from scp import SCPClient + + ssh = SSHClient() + ssh.load_system_host_keys() + ssh.connect('example.com') + + # SCPCLient takes a paramiko transport as an argument + scp = SCPClient(ssh.get_transport()) + + # generate in-memory file-like object + fl = io.BytesIO() + fl.write(b'test') + fl.seek(0) + # upload it directly from memory + scp.putfo(fl, '/tmp/test.txt') + # close connection + scp.close() + # close file handler + fl.close() + + +Tracking progress of your file uploads/downloads +------------------------------------------------ + +A ``progress`` function can be given as a callback to the SCPClient to handle +how the current SCP operation handles the progress of the transfers. In the +example below we print the percentage complete of the file transfer. + +.. code-block:: python + + from paramiko import SSHClient + from scp import SCPClient + import sys + + ssh = SSHClient() + ssh.load_system_host_keys() + ssh.connect('example.com') + + # Define progress callback that prints the current percentage completed for the file + def progress(filename, size, sent): + sys.stdout.write("%s\'s progress: %.2f%% \r" % (filename, float(sent)/float(size)*100) ) + + # SCPCLient takes a paramiko transport and progress callback as its arguments. + scp = SCPClient(ssh.get_transport(), progress=progress) + + # you can also use progress4, which adds a 4th parameter to track IP and port + # useful with multiple threads to track source + def progress4(filename, size, sent, peername): + sys.stdout.write("(%s:%s) %s\'s progress: %.2f%% \r" % (peername[0], peername[1], filename, float(sent)/float(size)*100) ) + scp = SCPClient(ssh.get_transport(), progress4=progress4) + + scp.put('test.txt', '~/test.txt') + # Should now be printing the current progress of your put function. + + scp.close() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/scp-0.10.2/scp.egg-info/PKG-INFO new/scp-0.13.0/scp.egg-info/PKG-INFO --- old/scp-0.10.2/scp.egg-info/PKG-INFO 2015-05-15 16:24:53.000000000 +0200 +++ new/scp-0.13.0/scp.egg-info/PKG-INFO 2018-11-12 21:55:19.000000000 +0100 @@ -1,6 +1,6 @@ -Metadata-Version: 1.0 +Metadata-Version: 1.1 Name: scp -Version: 0.10.2 +Version: 0.13.0 Summary: scp module for paramiko Home-page: https://github.com/jbardin/scp.py Author: James Bardin @@ -26,12 +26,16 @@ ssh.load_system_host_keys() ssh.connect('example.com') - # SCPCLient takes a paramiko transport as its only argument + # SCPCLient takes a paramiko transport as an argument scp = SCPClient(ssh.get_transport()) scp.put('test.txt', 'test2.txt') scp.get('test2.txt') + # Uploading the 'test' directory with its content in the + # '/home/user/dump' remote directory + scp.put('test', recursive=True, remote_path='/home/user/dump') + scp.close() @@ -42,7 +46,7 @@ fc264c65fb17b7db5237cf7ce1780769 test2.txt Using 'with' keyword - ------------------ + -------------------- .. code-block:: python @@ -64,4 +68,84 @@ fc264c65fb17b7db5237cf7ce1780769 test.txt fc264c65fb17b7db5237cf7ce1780769 test2.txt + + Uploading file-like objects + --------------------------- + + The ``putfo`` method can be used to upload file-like objects: + + .. code-block:: python + + import io + from paramiko import SSHClient + from scp import SCPClient + + ssh = SSHClient() + ssh.load_system_host_keys() + ssh.connect('example.com') + + # SCPCLient takes a paramiko transport as an argument + scp = SCPClient(ssh.get_transport()) + + # generate in-memory file-like object + fl = io.BytesIO() + fl.write(b'test') + fl.seek(0) + # upload it directly from memory + scp.putfo(fl, '/tmp/test.txt') + # close connection + scp.close() + # close file handler + fl.close() + + + Tracking progress of your file uploads/downloads + ------------------------------------------------ + + A ``progress`` function can be given as a callback to the SCPClient to handle + how the current SCP operation handles the progress of the transfers. In the + example below we print the percentage complete of the file transfer. + + .. code-block:: python + + from paramiko import SSHClient + from scp import SCPClient + import sys + + ssh = SSHClient() + ssh.load_system_host_keys() + ssh.connect('example.com') + + # Define progress callback that prints the current percentage completed for the file + def progress(filename, size, sent): + sys.stdout.write("%s\'s progress: %.2f%% \r" % (filename, float(sent)/float(size)*100) ) + + # SCPCLient takes a paramiko transport and progress callback as its arguments. + scp = SCPClient(ssh.get_transport(), progress=progress) + + # you can also use progress4, which adds a 4th parameter to track IP and port + # useful with multiple threads to track source + def progress4(filename, size, sent, peername): + sys.stdout.write("(%s:%s) %s\'s progress: %.2f%% \r" % (peername[0], peername[1], filename, float(sent)/float(size)*100) ) + scp = SCPClient(ssh.get_transport(), progress4=progress4) + + scp.put('test.txt', '~/test.txt') + # Should now be printing the current progress of your put function. + + scp.close() + +Keywords: paramiko,ssh,scp,transfer Platform: UNKNOWN +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.6 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Topic :: Internet diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/scp-0.10.2/scp.egg-info/SOURCES.txt new/scp-0.13.0/scp.egg-info/SOURCES.txt --- old/scp-0.10.2/scp.egg-info/SOURCES.txt 2015-05-15 16:24:53.000000000 +0200 +++ new/scp-0.13.0/scp.egg-info/SOURCES.txt 2018-11-12 21:55:19.000000000 +0100 @@ -1,8 +1,11 @@ +CHANGELOG.md LICENSE.txt MANIFEST.in README.rst scp.py +setup.cfg setup.py +test.py scp.egg-info/PKG-INFO scp.egg-info/SOURCES.txt scp.egg-info/dependency_links.txt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/scp-0.10.2/scp.py new/scp-0.13.0/scp.py --- old/scp-0.10.2/scp.py 2015-05-15 16:24:03.000000000 +0200 +++ new/scp-0.13.0/scp.py 2018-11-12 21:53:50.000000000 +0100 @@ -5,12 +5,13 @@ Utilities for sending files over ssh using the scp1 protocol. """ -__version__ = '0.10.2' +__version__ = '0.13.0' import locale import os import re from socket import timeout as SocketTimeout +import types # this is quote from the shlex module, added in py3.3 @@ -83,8 +84,8 @@ Since scp doesn't support symlinks, we send file symlinks as the file (matching scp behaviour), but we make no attempt at symlinked directories. """ - def __init__(self, transport, buff_size=16384, socket_timeout=5.0, - progress=None, sanitize=_sh_quote): + def __init__(self, transport, buff_size=16384, socket_timeout=10.0, + progress=None, progress4=None, sanitize=_sh_quote): """ Create an scp1 client. @@ -96,21 +97,31 @@ @type socket_timeout: float @param progress: callback - called with (filename, size, sent) during transfers + @param progress4: callback - called with (filename, size, sent, peername) + during transfers. peername is a tuple contains (IP, PORT) @param sanitize: function - called with filename, should return safe or escaped string. Uses _sh_quote by default. - @type progress: function(string, int, int) + @type progress: function(string, int, int, tuple) """ self.transport = transport self.buff_size = buff_size self.socket_timeout = socket_timeout self.channel = None self.preserve_times = False - self._progress = progress + if progress is not None and progress4 is not None: + raise TypeError("You may only set one of progress, progress4") + elif progress4 is not None: + self._progress = progress4 + elif progress is not None: + self._progress = lambda *a: progress(*a[:3]) + else: + self._progress = None self._recv_dir = b'' self._rename = False self._utime = None self.sanitize = sanitize self._dirtimes = {} + self.peername = self.transport.getpeername() def __enter__(self): self.channel = self._open() @@ -122,9 +133,9 @@ def put(self, files, remote_path=b'.', recursive=False, preserve_times=False): """ - Transfer files to remote host. + Transfer files and directories to remote host. - @param files: A single path, or a list of paths to be transfered. + @param files: A single path, or a list of paths to be transferred. recursive must be True to transfer directories. @type files: string OR list of strings @param remote_path: path in which to receive the files on the remote @@ -132,7 +143,7 @@ @type remote_path: str @param recursive: transfer files and directories recursively @type recursive: bool - @param preserve_times: preserve mtime and atime of transfered files + @param preserve_times: preserve mtime and atime of transferred files and directories. @type preserve_times: bool """ @@ -155,12 +166,39 @@ self.close() + def putfo(self, fl, remote_path, mode='0644', size=None): + """ + Transfer file-like object to remote host. + + @param fl: opened file or file-like object to copy + @type fl: file-like object + @param remote_path: full destination path + @type remote_path: str + @param mode: permissions (posix-style) for the uploaded file + @type mode: str + @param size: size of the file in bytes. If ``None``, the size will be + computed using `seek()` and `tell()`. + """ + if size is None: + pos = fl.tell() + fl.seek(0, os.SEEK_END) # Seek to end + size = fl.tell() - pos + fl.seek(pos, os.SEEK_SET) # Seek back + + self.channel = self._open() + self.channel.settimeout(self.socket_timeout) + self.channel.exec_command(b'scp -t ' + + self.sanitize(asbytes(remote_path))) + self._recv_confirm() + self._send_file(fl, remote_path, mode, size=size) + self.close() + def get(self, remote_path, local_path='', recursive=False, preserve_times=False): """ - Transfer files from remote host to localhost + Transfer files and directories from remote host to localhost. - @param remote_path: path to retreive from remote host. since this is + @param remote_path: path to retrieve from remote host. since this is evaluated by scp on the remote host, shell wildcards and environment variables may be used. @type remote_path: str @@ -168,7 +206,7 @@ @type local_path: str @param recursive: transfer files and directories recursively @type recursive: bool - @param preserve_times: preserve mtime and atime of transfered files + @param preserve_times: preserve mtime and atime of transferred files and directories. @type preserve_times: bool """ @@ -200,7 +238,7 @@ def _open(self): """open a scp channel""" - if self.channel is None: + if self.channel is None or self.channel.closed: self.channel = self.transport.open_session() return self.channel @@ -224,35 +262,37 @@ def _send_files(self, files): for name in files: - basename = asbytes(os.path.basename(name)) (mode, size, mtime, atime) = self._read_stats(name) if self.preserve_times: self._send_time(mtime, atime) - file_hdl = open(name, 'rb') - - # The protocol can't handle \n in the filename. - # Quote them as the control sequence \^J for now, - # which is how openssh handles it. - self.channel.sendall(("C%s %d " % (mode, size)).encode('ascii') + - basename.replace(b'\n', b'\\^J') + b"\n") - self._recv_confirm() - file_pos = 0 + fl = open(name, 'rb') + self._send_file(fl, name, mode, size) + fl.close() + + def _send_file(self, fl, name, mode, size): + basename = asbytes(os.path.basename(name)) + # The protocol can't handle \n in the filename. + # Quote them as the control sequence \^J for now, + # which is how openssh handles it. + self.channel.sendall(("C%s %d " % (mode, size)).encode('ascii') + + basename.replace(b'\n', b'\\^J') + b"\n") + self._recv_confirm() + file_pos = 0 + if self._progress: + if size == 0: + # avoid divide-by-zero + self._progress(basename, 1, 1, self.peername) + else: + self._progress(basename, size, 0, self.peername) + buff_size = self.buff_size + chan = self.channel + while file_pos < size: + chan.sendall(fl.read(buff_size)) + file_pos = fl.tell() if self._progress: - if size == 0: - # avoid divide-by-zero - self._progress(basename, 1, 1) - else: - self._progress(basename, size, 0) - buff_size = self.buff_size - chan = self.channel - while file_pos < size: - chan.sendall(file_hdl.read(buff_size)) - file_pos = file_hdl.tell() - if self._progress: - self._progress(basename, size, file_pos) - chan.sendall('\x00') - file_hdl.close() - self._recv_confirm() + self._progress(basename, size, file_pos, self.peername) + chan.sendall('\x00') + self._recv_confirm() def _chdir(self, from_dir, to_dir): # Pop until we're one level up from our next push. @@ -264,7 +304,7 @@ common = os.path.commonprefix([from_dir + bytes_sep, to_dir + bytes_sep]) # now take the dirname, since commonprefix is character based, - # and we either have a seperator, or a partial name + # and we either have a separator, or a partial name common = os.path.dirname(common) cur_dir = from_dir.rstrip(bytes_sep) while cur_dir != common: @@ -313,7 +353,7 @@ try: msg = self.channel.recv(512) except SocketTimeout: - raise SCPException('Timout waiting for scp response') + raise SCPException('Timeout waiting for scp response') # slice off the first byte, so this compare will work in py2 and py3 if msg and msg[0:1] == b'\x00': return @@ -342,10 +382,9 @@ assert msg[-1:] == b'\n' msg = msg[:-1] code = msg[0:1] - try: - command[code](msg[1:]) - except KeyError: + if code not in command: raise SCPException(asunicode(msg[1:])) + command[code](msg[1:]) # directory times can't be set until we're done writing files self._set_dirtimes() @@ -391,9 +430,9 @@ if self._progress: if size == 0: # avoid divide-by-zero - self._progress(path, 1, 1) + self._progress(path, 1, 1, self.peername) else: - self._progress(path, size, 0) + self._progress(path, size, 0, self.peername) buff_size = self.buff_size pos = 0 chan.send(b'\x00') @@ -405,8 +444,7 @@ file_hdl.write(chan.recv(buff_size)) pos = file_hdl.tell() if self._progress: - self._progress(path, size, pos) - + self._progress(path, size, pos, self.peername) msg = chan.recv(512) if msg and msg[0:1] != b'\x00': raise SCPException(asunicode(msg[1:])) @@ -468,3 +506,53 @@ class SCPException(Exception): """SCP exception class""" pass + + +def put(transport, files, remote_path=b'.', + recursive=False, preserve_times=False): + """ + Transfer files and directories to remote host. + + This is a convenience function that creates a SCPClient from the given + transport and closes it at the end, useful for one-off transfers. + + @param files: A single path, or a list of paths to be transferred. + recursive must be True to transfer directories. + @type files: string OR list of strings + @param remote_path: path in which to receive the files on the remote host. + defaults to '.' + @type remote_path: str + @param recursive: transfer files and directories recursively + @type recursive: bool + @param preserve_times: preserve mtime and atime of transferred files and + directories. + @type preserve_times: bool + """ + with SCPClient(transport) as client: + client.put(files, remote_path, recursive, preserve_times) + + +def get(transport, remote_path, local_path='', + recursive=False, preserve_times=False): + """ + Transfer files and directories from remote host to localhost. + + This is a convenience function that creates a SCPClient from the given + transport and closes it at the end, useful for one-off transfers. + + @param transport: an paramiko L{Transport} + @type transport: L{Transport} + @param remote_path: path to retrieve from remote host. since this is + evaluated by scp on the remote host, shell wildcards and environment + variables may be used. + @type remote_path: str + @param local_path: path in which to receive files locally + @type local_path: str + @param recursive: transfer files and directories recursively + @type recursive: bool + @param preserve_times: preserve mtime and atime of transferred files + and directories. + @type preserve_times: bool + """ + with SCPClient(transport) as client: + client.get(remote_path, local_path, recursive, preserve_times) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/scp-0.10.2/setup.cfg new/scp-0.13.0/setup.cfg --- old/scp-0.10.2/setup.cfg 2015-05-15 16:24:53.000000000 +0200 +++ new/scp-0.13.0/setup.cfg 2018-11-12 21:55:19.000000000 +0100 @@ -1,5 +1,7 @@ +[bdist_wheel] +universal = 1 + [egg_info] tag_build = tag_date = 0 -tag_svn_revision = 0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/scp-0.10.2/setup.py new/scp-0.13.0/setup.py --- old/scp-0.10.2/setup.py 2015-05-15 16:22:38.000000000 +0200 +++ new/scp-0.13.0/setup.py 2018-11-12 21:53:43.000000000 +0100 @@ -10,7 +10,7 @@ description = fp.read() setup( name = 'scp', - version = '0.10.2', + version = '0.13.0', author = 'James Bardin', author_email = '[email protected]', license = 'LGPL', @@ -19,4 +19,20 @@ long_description=description, py_modules = ['scp'], install_requires = ['paramiko'], + keywords=['paramiko', 'ssh', 'scp', 'transfer'], + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', + 'Operating System :: OS Independent', + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Topic :: Internet', + ], ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/scp-0.10.2/test.py new/scp-0.13.0/test.py --- old/scp-0.10.2/test.py 1970-01-01 01:00:00.000000000 +0100 +++ new/scp-0.13.0/test.py 2018-11-12 21:51:26.000000000 +0100 @@ -0,0 +1,318 @@ +from __future__ import print_function + +from io import BytesIO +import os +import paramiko +import random +import shutil +import sys +from scp import SCPClient, SCPException, put, get +import tempfile +try: + import unittest2 as unittest + sys.modules['unittest'] = unittest +except ImportError: + import unittest + + +ssh_info = { + 'hostname': os.environ.get('SCPPY_HOSTNAME', '127.0.0.1'), + 'port': int(os.environ.get('SCPPY_PORT', 22)), + 'username': os.environ.get('SCPPY_USERNAME', None), +} + + +# Environment info +PY3 = sys.version_info >= (3,) +WINDOWS = os.name == 'nt' +MACOS = sys.platform == 'darwin' + + +if MACOS: + import unicodedata + + def normalize_paths(names): + """Ensures the test names are normalized (NFC). + + HFS (on Mac OS X) will normalize filenames if necessary. + """ + normed = set() + for n in names: + if isinstance(n, bytes): + n = n.decode('utf-8') + + normed.add(unicodedata.normalize('NFC', n).encode('utf-8')) + return normed +else: + normalize_paths = set + + +def unique_names(): + """Generates unique sequences of bytes. + """ + characters = (b"abcdefghijklmnopqrstuvwxyz" + b"0123456789") + characters = [characters[i:i + 1] for i in range(len(characters))] + rng = random.Random() + while True: + letters = [rng.choice(characters) for i in range(10)] + yield b''.join(letters) +unique_names = unique_names() + + +class TestDownload(unittest.TestCase): + @classmethod + def setUpClass(cls): + # Server connection + cls.ssh = paramiko.SSHClient() + cls.ssh.load_system_host_keys() + cls.ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) + cls.ssh.connect(**ssh_info) + + # Makes some files on the server + chan = cls.ssh.get_transport().open_session() + chan.exec_command( + b'if ! echo -ne "/tmp/r\\xC3\\xA9mi" | xargs test -d; then ' + # Directory + b'echo -ne "/tmp/bien rang\\xC3\\xA9" | xargs -0 mkdir; ' + # Files + b'echo -ne "' + b'/tmp/r\\xC3\\xA9mi\\x00' + b'/tmp/bien rang\\xC3\\xA9/file\\x00' + b'/tmp/bien rang\\xC3\\xA9/b\\xC3\\xA8te\\x00' + b'/tmp/p\\xE9t\\xE9' # invalid UTF-8 here + b'" | xargs -0 touch; ' + b'fi') + assert chan.recv_exit_status() == 0 + + print("Running tests on %s with %s" % ( + "Windows" if WINDOWS else + "Mac OS X" if MACOS else + "POSIX", + "Python 3" if PY3 else "Python 2")) + + def download_test(self, filename, recursive, destination=None, + expected_win=[], expected_posix=[]): + # Make a temporary directory + temp = tempfile.mkdtemp(prefix='scp-py_test_') + # Add some unicode in the path + if WINDOWS: + if isinstance(temp, bytes): + temp = temp.decode(sys.getfilesystemencoding()) + temp_in = os.path.join(temp, u'cl\xE9') + else: + if not isinstance(temp, bytes): + temp = temp.encode('utf-8') + temp_in = os.path.join(temp, b'cl\xC3\xA9') + previous = os.getcwd() + os.mkdir(temp_in) + os.chdir(temp_in) + cb3 = lambda filename, size, sent: None + try: + with SCPClient(self.ssh.get_transport(), progress=cb3) as scp: + scp.get(filename, + destination if destination is not None else u'.', + preserve_times=True, recursive=recursive) + actual = [] + + def listdir(path, fpath): + for name in os.listdir(fpath): + fname = os.path.join(fpath, name) + actual.append(os.path.join(path, name)) + if os.path.isdir(fname): + listdir(name, fname) + listdir(u'' if WINDOWS else b'', + u'.' if WINDOWS else b'.') + self.assertEqual(normalize_paths(actual), + set(expected_win if WINDOWS else expected_posix)) + finally: + os.chdir(previous) + shutil.rmtree(temp) + + def test_get_bytes(self): + self.download_test(b'/tmp/r\xC3\xA9mi', False, b'target', + [u'target'], [b'target']) + self.download_test(b'/tmp/r\xC3\xA9mi', False, u'target', + [u'target'], [b'target']) + self.download_test(b'/tmp/r\xC3\xA9mi', False, None, + [u'r\xE9mi'], [b'r\xC3\xA9mi']) + self.download_test([b'/tmp/bien rang\xC3\xA9/file', + b'/tmp/bien rang\xC3\xA9/b\xC3\xA8te'], + False, None, + [u'file', u'b\xE8te'], [b'file', b'b\xC3\xA8te']) + + def test_get_unicode(self): + self.download_test(u'/tmp/r\xE9mi', False, b'target', + [u'target'], [b'target']) + self.download_test(u'/tmp/r\xE9mi', False, u'target', + [u'target'], [b'target']) + self.download_test(u'/tmp/r\xE9mi', False, None, + [u'r\xE9mi'], [b'r\xC3\xA9mi']) + self.download_test([u'/tmp/bien rang\xE9/file', + u'/tmp/bien rang\xE9/b\xE8te'], + False, None, + [u'file', u'b\xE8te'], [b'file', b'b\xC3\xA8te']) + + def test_get_folder(self): + self.download_test(b'/tmp/bien rang\xC3\xA9', True, None, + [u'bien rang\xE9', u'bien rang\xE9\\file', + u'bien rang\xE9\\b\xE8te'], + [b'bien rang\xC3\xA9', b'bien rang\xC3\xA9/file', + b'bien rang\xC3\xA9/b\xC3\xA8te']) + + def test_get_invalid_unicode(self): + self.download_test(b'/tmp/p\xE9t\xE9', False, u'target', + [u'target'], [b'target']) + if WINDOWS: + with self.assertRaises(SCPException): + self.download_test(b'/tmp/p\xE9t\xE9', False, None, + [], []) + elif MACOS: + self.download_test(b'/tmp/p\xE9t\xE9', False, None, + [u'not windows'], [b'p%E9t%E9']) + else: + self.download_test(b'/tmp/p\xE9t\xE9', False, None, + [u'not windows'], [b'p\xE9t\xE9']) + + +class TestUpload(unittest.TestCase): + @classmethod + def setUpClass(cls): + # Server connection + cls.ssh = paramiko.SSHClient() + cls.ssh.load_system_host_keys() + cls.ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) + cls.ssh.connect(**ssh_info) + + # Makes some files locally + cls._temp = tempfile.mkdtemp(prefix='scp_py_test_') + if isinstance(cls._temp, bytes): + cls._temp = cls._temp.decode(sys.getfilesystemencoding()) + inner = os.path.join(cls._temp, u'cl\xE9') + os.mkdir(inner) + os.mkdir(os.path.join(inner, u'dossi\xE9')) + os.mkdir(os.path.join(inner, u'dossi\xE9', u'bien rang\xE9')) + open(os.path.join(inner, u'dossi\xE9', u'bien rang\xE9', u'test'), + 'w').close() + open(os.path.join(inner, u'r\xE9mi'), 'w').close() + + @classmethod + def tearDownClass(cls): + shutil.rmtree(cls._temp) + + def upload_test(self, filenames, recursive, expected=[], fl=None): + destination = b'/tmp/upp\xC3\xA9' + next(unique_names) + chan = self.ssh.get_transport().open_session() + chan.exec_command(b'mkdir ' + destination) + assert chan.recv_exit_status() == 0 + previous = os.getcwd() + cb4 = lambda filename, size, sent, peername: None + try: + os.chdir(self._temp) + with SCPClient(self.ssh.get_transport(), progress4=cb4) as scp: + if not fl: + scp.put(filenames, destination, recursive) + else: + prefix = destination.decode(sys.getfilesystemencoding()) + remote_path = '%s/%s' % (prefix, filenames) + scp.putfo(fl, remote_path) + fl.close() + + chan = self.ssh.get_transport().open_session() + chan.exec_command( + b'echo -ne "' + + destination.decode('iso-8859-1') + .encode('ascii', 'backslashreplace') + + b'" | xargs find') + out_list = b'' + while True: + data = chan.recv(1024) + if not data: + break + out_list += data + prefix = len(destination) + 1 + out_list = [l[prefix:] for l in out_list.splitlines() + if len(l) > prefix] + self.assertEqual(normalize_paths(out_list), set(expected)) + finally: + os.chdir(previous) + chan = self.ssh.get_transport().open_session() + chan.exec_command(b'rm -Rf ' + destination) + assert chan.recv_exit_status() == 0 + + @unittest.skipIf(WINDOWS, "Use unicode paths on Windows") + def test_put_bytes(self): + self.upload_test(b'cl\xC3\xA9/r\xC3\xA9mi', False, [b'r\xC3\xA9mi']) + self.upload_test(b'cl\xC3\xA9/dossi\xC3\xA9/bien rang\xC3\xA9/test', + False, + [b'test']) + self.upload_test(b'cl\xC3\xA9/dossi\xC3\xA9', True, + [b'dossi\xC3\xA9', + b'dossi\xC3\xA9/bien rang\xC3\xA9', + b'dossi\xC3\xA9/bien rang\xC3\xA9/test']) + + def test_put_unicode(self): + self.upload_test(u'cl\xE9/r\xE9mi', False, [b'r\xC3\xA9mi']) + self.upload_test(u'cl\xE9/dossi\xE9/bien rang\xE9/test', False, + [b'test']) + self.upload_test(u'cl\xE9/dossi\xE9', True, + [b'dossi\xC3\xA9', + b'dossi\xC3\xA9/bien rang\xC3\xA9', + b'dossi\xC3\xA9/bien rang\xC3\xA9/test']) + self.upload_test([u'cl\xE9/dossi\xE9/bien rang\xE9', + u'cl\xE9/r\xE9mi'], True, + [b'bien rang\xC3\xA9', + b'bien rang\xC3\xA9/test', + b'r\xC3\xA9mi']) + self.upload_test([u'cl\xE9/dossi\xE9', + u'cl\xE9/r\xE9mi'], True, + [b'dossi\xC3\xA9', + b'dossi\xC3\xA9/bien rang\xC3\xA9', + b'dossi\xC3\xA9/bien rang\xC3\xA9/test', + b'r\xC3\xA9mi']) + + def test_putfo(self): + fl = BytesIO() + fl.write(b'r\xC3\xA9mi') + fl.seek(0) + self.upload_test(u'putfo-test', False, [b'putfo-test'], fl) + + +class TestUpAndDown(unittest.TestCase): + @classmethod + def setUpClass(cls): + # Server connection + cls.ssh = paramiko.SSHClient() + cls.ssh.load_system_host_keys() + cls.ssh.set_missing_host_key_policy(paramiko.WarningPolicy()) + cls.ssh.connect(**ssh_info) + + # Makes some files locally + cls._temp = tempfile.mkdtemp(prefix='scp_py_test_') + if isinstance(cls._temp, bytes): + cls._temp = cls._temp.decode(sys.getfilesystemencoding()) + + @classmethod + def tearDownClass(cls): + shutil.rmtree(cls._temp) + + def test_up_and_down(self): + '''send and receive files with the same client''' + previous = os.getcwd() + testfile = os.path.join(self._temp, 'testfile') + testfile_sent = os.path.join(self._temp, 'testfile_sent') + testfile_rcvd = os.path.join(self._temp, 'testfile_rcvd') + try: + os.chdir(self._temp) + with open(testfile, 'w') as f: + f.write("TESTING\n") + put(self.ssh.get_transport(), testfile, testfile_sent) + get(self.ssh.get_transport(), testfile_sent, testfile_rcvd) + + assert open(testfile_rcvd).read() == 'TESTING\n' + finally: + os.chdir(previous) + + +if __name__ == '__main__': + unittest.main()
