Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-aioftp for openSUSE:Factory checked in at 2022-08-19 17:56:28 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-aioftp (Old) and /work/SRC/openSUSE:Factory/.python-aioftp.new.2083 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-aioftp" Fri Aug 19 17:56:28 2022 rev:6 rq:998097 version:0.21.3 Changes: -------- --- /work/SRC/openSUSE:Factory/python-aioftp/python-aioftp.changes 2021-11-08 17:25:00.220725594 +0100 +++ /work/SRC/openSUSE:Factory/.python-aioftp.new.2083/python-aioftp.changes 2022-08-19 17:59:10.536439527 +0200 @@ -1,0 +2,24 @@ +Thu Aug 18 10:42:42 UTC 2022 - Ben Greiner <c...@bnavigator.de> + +- Update to 0.21.3 + * server/LIST: prevent broken links are listed, but can't be used + with stat + * server: make User.get_permissions async +- Release 0.21.2 + + tests: remove exception representation check +- Release 0.21.1 + * tests: replace more specific ConnectionRefusedError with + OSError for compatibility with FreeBSD (#152) +- Release 0.21.0 + * server: support PASV response with custom address (#150) +- Release 0.20.1 + * server: fix real directory resolve for windows (#147) +- Release 0.20.0 + * add client argument to set priority of custom list parser + (parse_list_line_custom_first) (#145) + * do not ignore failed parsing of list response (#144) +- Enforce legacy pytest-asyncio mode + * gh#aio-libs/aioftp#155 + * works around gh#pytest-dev/pytest-asyncio#390 + +------------------------------------------------------------------- Old: ---- aioftp-0.19.0.tar.gz New: ---- aioftp-0.21.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-aioftp.spec ++++++ --- /var/tmp/diff_new_pack.c3UP6P/_old 2022-08-19 17:59:10.916440323 +0200 +++ /var/tmp/diff_new_pack.c3UP6P/_new 2022-08-19 17:59:10.920440332 +0200 @@ -1,7 +1,7 @@ # # spec file for package python-aioftp # -# Copyright (c) 2021 SUSE LLC +# Copyright (c) 2022 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -16,11 +16,10 @@ # -%{?!python_module:%define python_module() python-%{**} python3-%{**}} +%{?!python_module:%define python_module() python3-%{**}} %define skip_python2 1 -%define skip_python36 1 Name: python-aioftp -Version: 0.19.0 +Version: 0.21.3 Release: 0 Summary: FTP client/server for asyncio License: Apache-2.0 @@ -34,7 +33,6 @@ # SECTION test requirements BuildRequires: %{python_module async_timeout} BuildRequires: %{python_module pytest-asyncio} -BuildRequires: %{python_module pytest-cov} BuildRequires: %{python_module pytest} BuildRequires: %{python_module siosocks >= 0.2.0} BuildRequires: %{python_module trustme} @@ -55,12 +53,12 @@ %python_expand %fdupes %{buildroot}%{$python_sitelib} %check -%pytest -k 'not test_server_side_throttle' +%pytest --asyncio-mode=legacy %files %{python_files} %doc README.rst %license license.txt -%{python_sitelib}/aioftp*.egg-info +%{python_sitelib}/aioftp-%{version}*-info %{python_sitelib}/aioftp %changelog ++++++ aioftp-0.19.0.tar.gz -> aioftp-0.21.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.19.0/PKG-INFO new/aioftp-0.21.3/PKG-INFO --- old/aioftp-0.19.0/PKG-INFO 2021-10-26 21:52:54.011503000 +0200 +++ new/aioftp-0.21.3/PKG-INFO 2022-07-15 00:08:37.528395400 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: aioftp -Version: 0.19.0 +Version: 0.21.3 Summary: ftp client/server for asyncio Home-page: https://github.com/aio-libs/aioftp Author: pohmelie @@ -26,11 +26,7 @@ .. image:: https://github.com/aio-libs/aioftp/actions/workflows/ci.yml/badge.svg?branch=master :target: https://github.com/aio-libs/aioftp/actions/workflows/ci.yml - :alt: Github actions tests for master branch - -.. image:: https://github.com/aio-libs/aioftp/actions/workflows/cd.yml/badge.svg?branch=master - :target: https://github.com/aio-libs/aioftp/actions/workflows/cd.yml - :alt: Github actions deploy to pypi for master branch + :alt: Github actions ci for master branch .. image:: https://codecov.io/gh/aio-libs/aioftp/branch/master/graph/badge.svg :target: https://codecov.io/gh/aio-libs/aioftp diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.19.0/README.rst new/aioftp-0.21.3/README.rst --- old/aioftp-0.19.0/README.rst 2021-10-26 21:52:45.000000000 +0200 +++ new/aioftp-0.21.3/README.rst 2022-07-15 00:08:30.000000000 +0200 @@ -8,11 +8,7 @@ .. image:: https://github.com/aio-libs/aioftp/actions/workflows/ci.yml/badge.svg?branch=master :target: https://github.com/aio-libs/aioftp/actions/workflows/ci.yml - :alt: Github actions tests for master branch - -.. image:: https://github.com/aio-libs/aioftp/actions/workflows/cd.yml/badge.svg?branch=master - :target: https://github.com/aio-libs/aioftp/actions/workflows/cd.yml - :alt: Github actions deploy to pypi for master branch + :alt: Github actions ci for master branch .. image:: https://codecov.io/gh/aio-libs/aioftp/branch/master/graph/badge.svg :target: https://codecov.io/gh/aio-libs/aioftp diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.19.0/aioftp/__init__.py new/aioftp-0.21.3/aioftp/__init__.py --- old/aioftp-0.19.0/aioftp/__init__.py 2021-10-26 21:52:45.000000000 +0200 +++ new/aioftp-0.21.3/aioftp/__init__.py 2022-07-15 00:08:30.000000000 +0200 @@ -7,7 +7,7 @@ from .pathio import * from .server import * -__version__ = "0.19.0" +__version__ = "0.21.3" version = tuple(map(int, __version__.split("."))) __all__ = ( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.19.0/aioftp/client.py new/aioftp-0.21.3/aioftp/client.py --- old/aioftp-0.19.0/aioftp/client.py 2021-10-26 21:52:45.000000000 +0200 +++ new/aioftp-0.21.3/aioftp/client.py 2022-07-15 00:08:30.000000000 +0200 @@ -109,10 +109,16 @@ def __init__(self, *, socket_timeout=None, connection_timeout=None, - read_speed_limit=None, write_speed_limit=None, - path_timeout=None, path_io_factory=pathio.PathIO, - encoding="utf-8", ssl=None, parse_list_line_custom=None, - passive_commands=("epsv", "pasv"), **siosocks_asyncio_kwargs): + read_speed_limit=None, + write_speed_limit=None, + path_timeout=None, + path_io_factory=pathio.PathIO, + encoding="utf-8", + ssl=None, + parse_list_line_custom=None, + parse_list_line_custom_first=True, + passive_commands=("epsv", "pasv"), + **siosocks_asyncio_kwargs): self.socket_timeout = socket_timeout self.connection_timeout = connection_timeout self.throttle = StreamThrottle.from_limits( @@ -125,6 +131,7 @@ self.stream = None self.ssl = ssl self.parse_list_line_custom = parse_list_line_custom + self.parse_list_line_custom_first = parse_list_line_custom_first self._passive_commands = passive_commands self._open_connection = partial(open_connection, ssl=self.ssl, **siosocks_asyncio_kwargs) @@ -520,11 +527,14 @@ :rtype: (:py:class:`pathlib.PurePosixPath`, :py:class:`dict`) """ ex = [] - parsers = ( - self.parse_list_line_custom, + parsers = [ self.parse_list_line_unix, self.parse_list_line_windows, - ) + ] + if self.parse_list_line_custom_first: + parsers = [self.parse_list_line_custom] + parsers + else: + parsers = parsers + [self.parse_list_line_custom] for parser in parsers: if parser is None: continue @@ -597,6 +607,9 @@ dictionary with fields "modify", "type", "size". For more information see sources. :type parse_list_line_custom: callable + :param parse_list_line_custom_first: Should be custom parser tried first + or last + :type parse_list_line_custom_first: :py:class:`bool` :param **siosocks_asyncio_kwargs: siosocks key-word only arguments """ async def connect(self, host, port=DEFAULT_PORT): @@ -712,9 +725,10 @@ """ :py:func:`asyncio.coroutine` - List all files and directories in "path". + List all files and directories in "path". If "path" is a file, + then result will be empty - :param path: directory or file path + :param path: directory :type path: :py:class:`str` or :py:class:`pathlib.PurePosixPath` :param recursive: list recursively @@ -781,11 +795,7 @@ else: raise StopAsyncIteration - try: - name, info = cls.parse_line(line) - except Exception: - continue - + name, info = cls.parse_line(line) stat = cls.path / name, info if info["type"] == "dir" and recursive: cls.directories.append(stat) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.19.0/aioftp/server.py new/aioftp-0.21.3/aioftp/server.py --- old/aioftp-0.19.0/aioftp/server.py 2021-10-26 21:52:45.000000000 +0200 +++ new/aioftp-0.21.3/aioftp/server.py 2022-07-15 00:08:30.000000000 +0200 @@ -139,7 +139,7 @@ self.write_speed_limit_per_connection = \ write_speed_limit_per_connection - def get_permissions(self, path): + async def get_permissions(self, path): """ Return nearest parent permission for `path`. @@ -536,7 +536,9 @@ @functools.wraps(f) async def wrapper(cls, connection, rest, *args): real_path, virtual_path = cls.get_paths(connection, rest) - current_permission = connection.user.get_permissions(virtual_path) + current_permission = await connection.user.get_permissions( + virtual_path, + ) for permission in self.permissions: if not getattr(current_permission, permission): connection.response("550", "permission denied") @@ -619,6 +621,11 @@ connection in bytes per second :type write_speed_limit_per_connection: :py:class:`int` or :py:class:`None` + :param ipv4_pasv_forced_response_address: external IPv4 address for passive + connections + :type ipv4_pasv_forced_response_address: :py:class:`str` or + :py:class:`None` + :param data_ports: port numbers that are available for passive connections :type data_ports: :py:class:`collections.Iterable` or :py:class:`None` @@ -644,6 +651,7 @@ write_speed_limit=None, read_speed_limit_per_connection=None, write_speed_limit_per_connection=None, + ipv4_pasv_forced_response_address=None, data_ports=None, encoding="utf-8", ssl=None): @@ -653,6 +661,8 @@ self.wait_future_timeout = wait_future_timeout self.path_io_factory = pathio.PathIONursery(path_io_factory) self.path_timeout = path_timeout + self.ipv4_pasv_forced_response_address = \ + ipv4_pasv_forced_response_address if data_ports is not None: self.available_data_ports = asyncio.PriorityQueue() for data_port in data_ports: @@ -988,7 +998,8 @@ if tasks_to_wait: await asyncio.wait(tasks_to_wait) - def get_paths(self, connection, path): + @staticmethod + def get_paths(connection, path): """ Return *real* and *virtual* paths, resolves ".." with "up" action. *Real* path is path for path_io, when *virtual* deals with @@ -1014,6 +1025,12 @@ resolved_virtual_path /= part base_path = connection.user.base_path real_path = base_path / resolved_virtual_path.relative_to("/") + # replace with `is_relative_to` check after 3.9+ requirements lands + try: + real_path.relative_to(base_path) + except ValueError: + real_path = base_path + resolved_virtual_path = pathlib.PurePosixPath("/") return real_path, resolved_virtual_path async def greeting(self, connection, rest): @@ -1227,6 +1244,9 @@ del connection.data_connection async with stream: async for path in connection.path_io.list(real_path): + if not (await connection.path_io.exists(path)): + logger.warning("path %r does not exists", path) + continue s = await self.build_list_string(connection, path) b = (s + END_OF_LINE).encode(encoding=self.encoding) await stream.write(b) @@ -1441,6 +1461,11 @@ for sock in connection.passive_server.sockets: if sock.family == socket.AF_INET: host, port = sock.getsockname() + # If the FTP server is behind NAT, the server needs to report + # its external IP instead of the internal IP so that the client + # is able to connect to the server. + if self.ipv4_pasv_forced_response_address: + host = self.ipv4_pasv_forced_response_address break else: connection.response("503", ["this server started in ipv6 mode"]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.19.0/aioftp.egg-info/PKG-INFO new/aioftp-0.21.3/aioftp.egg-info/PKG-INFO --- old/aioftp-0.19.0/aioftp.egg-info/PKG-INFO 2021-10-26 21:52:53.000000000 +0200 +++ new/aioftp-0.21.3/aioftp.egg-info/PKG-INFO 2022-07-15 00:08:37.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: aioftp -Version: 0.19.0 +Version: 0.21.3 Summary: ftp client/server for asyncio Home-page: https://github.com/aio-libs/aioftp Author: pohmelie @@ -26,11 +26,7 @@ .. image:: https://github.com/aio-libs/aioftp/actions/workflows/ci.yml/badge.svg?branch=master :target: https://github.com/aio-libs/aioftp/actions/workflows/ci.yml - :alt: Github actions tests for master branch - -.. image:: https://github.com/aio-libs/aioftp/actions/workflows/cd.yml/badge.svg?branch=master - :target: https://github.com/aio-libs/aioftp/actions/workflows/cd.yml - :alt: Github actions deploy to pypi for master branch + :alt: Github actions ci for master branch .. image:: https://codecov.io/gh/aio-libs/aioftp/branch/master/graph/badge.svg :target: https://codecov.io/gh/aio-libs/aioftp diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.19.0/doc-requirements.txt new/aioftp-0.21.3/doc-requirements.txt --- old/aioftp-0.19.0/doc-requirements.txt 2021-10-26 21:52:45.000000000 +0200 +++ new/aioftp-0.21.3/doc-requirements.txt 2022-07-15 00:08:30.000000000 +0200 @@ -1,2 +1,3 @@ sphinx alabaster +docutils < 0.18.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.19.0/history.rst new/aioftp-0.21.3/history.rst --- old/aioftp-0.19.0/history.rst 2021-10-26 21:52:45.000000000 +0200 +++ new/aioftp-0.21.3/history.rst 2022-07-15 00:08:30.000000000 +0200 @@ -1,6 +1,37 @@ x.x.x (xx-xx-xxxx) ------------------ +0.21.3 (15-07-2022) +------------------- +- server/`LIST`: prevent broken links are listed, but can't be used with `stat` +- server: make `User.get_permissions` async + +0.21.2 (22-04-2022) +------------------- +- tests: remove exception representation check + +0.21.1 (20-04-2022) +------------------- +- tests: replace more specific `ConnectionRefusedError` with `OSError` for compatibility with FreeBSD (#152) +Thanks to `AMDmi3 https://github.com/AMDmi3`_ + +0.21.0 (18-03-2022) +------------------ +- server: support PASV response with custom address (#150) +Thanks to `janneronkko https://github.com/janneronkko`_ + +0.20.1 (15-02-2022) +------------------ +- server: fix real directory resolve for windows (#147) +Thanks to `ported-pw https://github.com/ported-pw`_ + +0.20.0 (27-12-2021) +------------------ + +- add client argument to set priority of custom list parser (`parse_list_line_custom_first`) (#145) +- do not ignore failed parsing of list response (#144) +Thanks to `spolloni https://github.com/spolloni`_ + 0.19.0 (08-10-2021) ------------------ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.19.0/tests/test_connection.py new/aioftp-0.21.3/tests/test_connection.py --- old/aioftp-0.19.0/tests/test_connection.py 2021-10-26 21:52:45.000000000 +0200 +++ new/aioftp-0.21.3/tests/test_connection.py 2022-07-15 00:08:30.000000000 +0200 @@ -1,4 +1,5 @@ import asyncio +import ipaddress import pytest @@ -119,6 +120,39 @@ assert pair.server.available_data_ports.qsize() == 0 +@pytest.mark.asyncio +async def test_pasv_connection_pasv_forced_response_address(pair_factory, + Server, + unused_tcp_port): + def ipv4_used(): + try: + ipaddress.IPv4Address(pair.host) + return True + except ValueError: + return False + + async with pair_factory( + server=Server(ipv4_pasv_forced_response_address='127.0.0.2'), + ) as pair: + assert pair.server.ipv4_pasv_forced_response_address == '127.0.0.2' + + if ipv4_used(): + # The connection fails here because the server starts to listen for + # the passive connections on the host (IPv4 address) that is used + # by the control channel. In reality, if the server is behind NAT, + # the server is reached with the defined external IPv4 address, + # i.e. we can check that the connection to + # pair.server.ipv4_pasv_forced_response_address failed to know that + # the server returned correct external IP + with pytest.raises(OSError): + await pair.client.get_passive_connection(commands=['pasv']) + + # With epsv the connection should open as that does not use the + # external IPv4 address but just tells the client the port to connect + # to + await pair.client.get_passive_connection(commands=['epsv']) + + @pytest.mark.parametrize("method", ["epsv", "pasv"]) @pytest.mark.asyncio async def test_pasv_connection_no_free_port(pair_factory, Server, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.19.0/tests/test_extra.py new/aioftp-0.21.3/tests/test_extra.py --- old/aioftp-0.19.0/tests/test_extra.py 2021-10-26 21:52:45.000000000 +0200 +++ new/aioftp-0.21.3/tests/test_extra.py 2022-07-15 00:08:30.000000000 +0200 @@ -31,7 +31,7 @@ @pytest.mark.asyncio async def test_no_server(unused_tcp_port): - with pytest.raises(ConnectionRefusedError): + with pytest.raises(OSError): async with aioftp.Client.context("127.0.0.1", unused_tcp_port): pass diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.19.0/tests/test_file.py new/aioftp-0.21.3/tests/test_file.py --- old/aioftp-0.19.0/tests/test_file.py 2021-10-26 21:52:45.000000000 +0200 +++ new/aioftp-0.21.3/tests/test_file.py 2022-07-15 00:08:30.000000000 +0200 @@ -26,6 +26,14 @@ @pytest.mark.asyncio +async def test_mlsd_file(pair_factory): + async with pair_factory() as pair: + await pair.make_server_files("foo/bar.txt") + result = await pair.client.list("foo/bar.txt") + assert len(result) == 0 + + +@pytest.mark.asyncio async def test_file_download(pair_factory): async with pair_factory() as pair: await pair.make_server_files("foo", size=1, atom=b"foobar") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.19.0/tests/test_list_fallback.py new/aioftp-0.21.3/tests/test_list_fallback.py --- old/aioftp-0.19.0/tests/test_list_fallback.py 2021-10-26 21:52:45.000000000 +0200 +++ new/aioftp-0.21.3/tests/test_list_fallback.py 2022-07-15 00:08:30.000000000 +0200 @@ -26,6 +26,8 @@ assert len(files) == 1 assert path == pathlib.PurePosixPath("bar/foo") assert stat["type"] == "file" + result = await pair.client.list("bar/foo") + assert len(result) == 0 async def implemented_badly(connection, rest): @@ -108,6 +110,33 @@ pair.server.commands_mapping["mlst"] = not_implemented pair.server.commands_mapping["mlsd"] = not_implemented pair.server.build_list_string = builder + await pair.client.make_directory("bar") + (path, stat), *_ = files = await pair.client.list() + assert len(files) == 1 + assert path == pathlib.PurePosixPath("bar") + assert stat == meta + + +@pytest.mark.asyncio +async def test_client_list_override_with_custom_last(pair_factory, Client): + meta = {"type": "file", "works": True} + + def parser(b): + import pickle + return pickle.loads(bytes.fromhex(b.decode().rstrip("\r\n"))) + + async def builder(_, path): + import pickle + return pickle.dumps((path, meta)).hex() + + client = Client( + parse_list_line_custom=parser, + parse_list_line_custom_first=False, + ) + async with pair_factory(client) as pair: + pair.server.commands_mapping["mlst"] = not_implemented + pair.server.commands_mapping["mlsd"] = not_implemented + pair.server.build_list_string = builder await pair.client.make_directory("bar") (path, stat), *_ = files = await pair.client.list() assert len(files) == 1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.19.0/tests/test_simple_functions.py new/aioftp-0.21.3/tests/test_simple_functions.py --- old/aioftp-0.19.0/tests/test_simple_functions.py 2021-10-26 21:52:45.000000000 +0200 +++ new/aioftp-0.21.3/tests/test_simple_functions.py 2022-07-15 00:08:30.000000000 +0200 @@ -230,3 +230,17 @@ b = aioftp.Server.build_list_mtime assert b(now, now) == "Jan 1 00:00" assert b(past, now) == "Jan 1 2001" + + +def test_get_paths_windows_traverse(): + base_path = pathlib.PureWindowsPath("C:\\ftp") + user = aioftp.User() + user.base_path = base_path + connection = aioftp.Connection(current_directory=base_path, user=user) + virtual_path = pathlib.PurePosixPath("/foo/C:\\windows") + real_path, resolved_virtual_path = aioftp.Server.get_paths( + connection, + virtual_path, + ) + assert real_path == base_path + assert resolved_virtual_path == pathlib.PurePosixPath("/")