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 2026-11-01 22:24:38 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-aioftp (Old) and /work/SRC/openSUSE:Factory/.python-aioftp.new.1255 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-aioftp" Sun Nov 1 22:24:38 2026 rev:16 rq:1394280 version:0.28.3 Changes: -------- --- /work/SRC/openSUSE:Factory/python-aioftp/python-aioftp.changes 2025-11-13 17:31:05.736599624 +0100 +++ /work/SRC/openSUSE:Factory/.python-aioftp.new.1255/python-aioftp.changes 2026-11-01 22:26:01.530491269 +0100 @@ -1,0 +2,12 @@ +Sun Oct 25 11:57:07 UTC 2026 - Dirk Müller <[email protected]> + +- update to 0.28.3: + * client is_dir should treat cdir and pdir as a directory too, + according to RFC3659 (fixes #211) + * apply connection_timeout to passive connections + * add trust_server_pasv_ipv4_address option with default to + False to avoid possible ipv4 spoofing in server response + * add system_type parameter to server + * add welcome_message parameter for a server + +------------------------------------------------------------------- Old: ---- aioftp-0.27.2.tar.gz New: ---- aioftp-0.28.3.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-aioftp.spec ++++++ --- /var/tmp/diff_new_pack.pTTb9M/_old 2026-11-01 22:26:02.135516351 +0100 +++ /var/tmp/diff_new_pack.pTTb9M/_new 2026-11-01 22:26:02.137516433 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-aioftp # -# Copyright (c) 2025 SUSE LLC +# Copyright (c) 2026 SUSE LLC and contributors # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-aioftp -Version: 0.27.2 +Version: 0.28.3 Release: 0 Summary: FTP client/server for asyncio License: Apache-2.0 ++++++ aioftp-0.27.2.tar.gz -> aioftp-0.28.3.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.27.2/PKG-INFO new/aioftp-0.28.3/PKG-INFO --- old/aioftp-0.27.2/PKG-INFO 2025-09-24 23:36:06.265699900 +0200 +++ new/aioftp-0.28.3/PKG-INFO 2026-09-19 16:24:16.943065000 +0200 @@ -1,8 +1,8 @@ Metadata-Version: 2.4 Name: aioftp -Version: 0.27.2 +Version: 0.28.3 Summary: ftp client/server for asyncio -Author: yieyu, rsichnyi, jw4js, oleksandr-kuzmenko, ndhansen, modelmat, greut, PonyPC, jacobtomlinson, bachya, CrafterKolyan, jkr78, sakosha +Author: yieyu, rsichnyi, jw4js, oleksandr-kuzmenko, ndhansen, modelmat, greut, PonyPC, jacobtomlinson, bachya, CrafterKolyan, jkr78, sakosha, rwakulszowa Author-email: pohmelie <[email protected]>, asvetlov <[email protected]>, decaz <[email protected]>, janneronkko <[email protected]>, thirtyseven <[email protected]>, ported-pw <[email protected]>, Olegt0rr <[email protected]>, michalc <[email protected]>, ch3pjw <[email protected]>, puddly <[email protected]>, AMDmi3 <[email protected]>, webknjaz <webknjaz+github/[email protected]>, rcfox <[email protected]>, bellini666 <[email protected]> License: Apache License Version 2.0, January 2004 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.27.2/history.rst new/aioftp-0.28.3/history.rst --- old/aioftp-0.27.2/history.rst 2025-09-24 23:36:02.000000000 +0200 +++ new/aioftp-0.28.3/history.rst 2026-09-19 16:24:13.827084000 +0200 @@ -1,4 +1,22 @@ -x.x.x (xxxx-xx-xx) +0.28.3 (2026-09-19) +------------------- +- client `is_dir` should treat `cdir` and `pdir` as a directory too, according to RFC3659 (fixes #211) + +0.28.2 (2026-09-19) +------------------- +- apply `connection_timeout` to passive connections (#212) +Thanks to `Cycloctane <https://github.com/Cycloctane>`_ + +0.28.1 (2026-09-19) +------------------- +- add `trust_server_pasv_ipv4_address` option with default to `False` to avoid possible ipv4 spoofing in server response +- add `system_type` parameter to server (#208) +Thanks to `rwakulszowa <https://github.com/rwakulszowa>`_ + +0.28.0 (2026-08-05) +------------------- +- add `welcome_message` parameter for a server (#207) +Thanks to `rwakulszowa <https://github.com/rwakulszowa>`_ 0.27.2 (2025-09-25) ------------------- diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.27.2/pyproject.toml new/aioftp-0.28.3/pyproject.toml --- old/aioftp-0.27.2/pyproject.toml 2025-09-24 23:36:02.000000000 +0200 +++ new/aioftp-0.28.3/pyproject.toml 2026-09-19 16:24:13.827084000 +0200 @@ -1,6 +1,6 @@ [project] name = "aioftp" -version = "0.27.2" +version = "0.28.3" description = "ftp client/server for asyncio" readme = "README.rst" requires-python = ">= 3.10" @@ -33,6 +33,7 @@ {name = "rcfox", email="[email protected]"}, {name = "bellini666", email="[email protected]"}, {name = "sakosha"}, + {name = "rwakulszowa"}, ] classifiers = [ "Programming Language :: Python", @@ -83,6 +84,7 @@ line-length = 120 target-version = "py310" lint.select = ["E", "W", "F", "Q", "UP", "I", "ASYNC"] +lint.ignore = ["ASYNC240"] src = ["src"] exclude = ["ftpbench.py"] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.27.2/src/aioftp/client.py new/aioftp-0.28.3/src/aioftp/client.py --- old/aioftp-0.27.2/src/aioftp/client.py 2025-09-24 23:36:02.000000000 +0200 +++ new/aioftp-0.28.3/src/aioftp/client.py 2026-09-19 16:24:13.828139800 +0200 @@ -201,6 +201,7 @@ parse_list_line_custom: ParseListLineCustomCallable | None = None, parse_list_line_custom_first: bool = True, passive_commands: tuple[str, ...] = ("epsv", "pasv"), + trust_server_pasv_ipv4_address: bool = False, **siosocks_asyncio_kwargs: Any, ): self.socket_timeout = socket_timeout @@ -217,6 +218,7 @@ 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.trust_server_pasv_ipv4_address = trust_server_pasv_ipv4_address self._siosocks_asyncio_kwargs = siosocks_asyncio_kwargs async def _open_connection(self, host: str, port: int) -> tuple[asyncio.StreamReader, asyncio.StreamWriter]: @@ -229,21 +231,21 @@ context=ssl_object.context, session=ssl_object.session, ) - connection: tuple[asyncio.StreamReader, asyncio.StreamWriter] = await open_connection( - host, - port, - ssl=ssl_resolved, - **self._siosocks_asyncio_kwargs, + connection: tuple[asyncio.StreamReader, asyncio.StreamWriter] = await asyncio.wait_for( + open_connection( + host, + port, + ssl=ssl_resolved, + **self._siosocks_asyncio_kwargs, + ), + self.connection_timeout, ) return connection async def connect(self, host: str, port: int = DEFAULT_PORT) -> None: self.server_host = host self.server_port = port - reader, writer = await asyncio.wait_for( - self._open_connection(host, port), - self.connection_timeout, - ) + reader, writer = await self._open_connection(host, port) self._stream = ThrottleStreamIO( reader, writer, @@ -1051,7 +1053,7 @@ :rtype: :py:class:`bool` """ info = await self.stat(path) - return info["type"] == "dir" + return info["type"] in ("dir", "cdir", "pdir") async def exists(self, path: PathLike) -> bool: """ @@ -1319,9 +1321,12 @@ ip, port = self.parse_epsv_response(info[-1]) return ip, port - async def _do_pasv(self) -> tuple[str, int]: + async def _do_pasv(self) -> tuple[str | None, int]: code, info = await self.command("PASV", "227") + ip: str | None ip, port = self.parse_pasv_response(info[-1]) + if not self.trust_server_pasv_ipv4_address: + ip = None return ip, port async def get_passive_connection( diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.27.2/src/aioftp/server.py new/aioftp-0.28.3/src/aioftp/server.py --- old/aioftp-0.27.2/src/aioftp/server.py 2025-09-24 23:36:02.000000000 +0200 +++ new/aioftp-0.28.3/src/aioftp/server.py 2026-09-19 16:24:13.828139800 +0200 @@ -676,6 +676,12 @@ to enable TLS over the accepted connections. Please look :py:meth:`asyncio.loop.create_server` docs. :type ssl: :py:class:`ssl.SSLContext` + + :param welcome_message: welcome message for new connections + :type welcome_message: :py:class:`str` + + :param system_type: identifier of the OS running the server + :type system_type: :py:class:`str` """ def __init__( @@ -697,6 +703,8 @@ data_ports: Iterable[int] | None = None, encoding: str = "utf-8", ssl: ssl.SSLContext | None = None, + welcome_message: str = "welcome", + system_type: str = "UNIX Type: L8", ) -> None: self.block_size = block_size self.socket_timeout = socket_timeout @@ -729,6 +737,8 @@ self.throttle_per_user: dict[User | None, StreamThrottle] = {} self.encoding = encoding self.ssl = ssl + self.welcome_message = welcome_message + self.system_type = system_type self.commands_mapping: dict[ str, Callable[[Connection, str], Awaitable[bool]] | Callable[[Connection, str | PurePosixPath], Awaitable[bool]], @@ -1098,7 +1108,7 @@ if self.available_connections.locked(): ok, code, info = False, "421", "Too many connections" else: - ok, code, info = True, "220", "welcome" + ok, code, info = True, "220", self.welcome_message connection.acquired = True self.available_connections.acquire() connection.response(code, info) @@ -1612,6 +1622,6 @@ return True async def syst(self, connection: Connection, rest: str) -> bool: - """Return system type (always returns UNIX type: L8).""" - connection.response("215", "UNIX Type: L8") + """Return system type.""" + connection.response("215", self.system_type) return True diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.27.2/src/aioftp.egg-info/PKG-INFO new/aioftp-0.28.3/src/aioftp.egg-info/PKG-INFO --- old/aioftp-0.27.2/src/aioftp.egg-info/PKG-INFO 2025-09-24 23:36:06.000000000 +0200 +++ new/aioftp-0.28.3/src/aioftp.egg-info/PKG-INFO 2026-09-19 16:24:16.924065000 +0200 @@ -1,8 +1,8 @@ Metadata-Version: 2.4 Name: aioftp -Version: 0.27.2 +Version: 0.28.3 Summary: ftp client/server for asyncio -Author: yieyu, rsichnyi, jw4js, oleksandr-kuzmenko, ndhansen, modelmat, greut, PonyPC, jacobtomlinson, bachya, CrafterKolyan, jkr78, sakosha +Author: yieyu, rsichnyi, jw4js, oleksandr-kuzmenko, ndhansen, modelmat, greut, PonyPC, jacobtomlinson, bachya, CrafterKolyan, jkr78, sakosha, rwakulszowa Author-email: pohmelie <[email protected]>, asvetlov <[email protected]>, decaz <[email protected]>, janneronkko <[email protected]>, thirtyseven <[email protected]>, ported-pw <[email protected]>, Olegt0rr <[email protected]>, michalc <[email protected]>, ch3pjw <[email protected]>, puddly <[email protected]>, AMDmi3 <[email protected]>, webknjaz <webknjaz+github/[email protected]>, rcfox <[email protected]>, bellini666 <[email protected]> License: Apache License Version 2.0, January 2004 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/aioftp-0.27.2/tests/test_connection.py new/aioftp-0.28.3/tests/test_connection.py --- old/aioftp-0.27.2/tests/test_connection.py 2025-09-24 23:36:02.000000000 +0200 +++ new/aioftp-0.28.3/tests/test_connection.py 2026-09-19 16:24:13.829216700 +0200 @@ -129,7 +129,7 @@ @pytest.mark.asyncio -async def test_pasv_connection_pasv_forced_response_address(pair_factory, Server): +async def test_pasv_connection_pasv_forced_response_address(pair_factory, Client, Server): def ipv4_used(): try: ipaddress.IPv4Address(pair.host) @@ -140,6 +140,7 @@ # using TEST-NET-1 address ipv4_address = "192.0.2.1" async with pair_factory( + client=Client(trust_server_pasv_ipv4_address=True), server=Server(ipv4_pasv_forced_response_address=ipv4_address), ) as pair: assert pair.server.ipv4_pasv_forced_response_address == ipv4_address @@ -294,3 +295,42 @@ pair.server.server_port, ) await pair.client.login() + + [email protected] +async def test_welcome_message(pair_factory, Server): + welcome_message = "Welcome to the FTP server!" + async with pair_factory( + server=Server(welcome_message=welcome_message), + connected=False, + ) as pair: + lines = await pair.client.connect( + pair.server.server_host, + pair.server.server_port, + ) + assert lines == [" " + welcome_message] + + [email protected] +async def test_system_type(pair_factory, Server): + system_type = "My custom OS" + async with pair_factory( + server=Server(system_type=system_type), + ) as pair: + resp = await pair.client.command("SYST", expected_codes=(215,)) + assert resp == ("215", [" " + system_type]) + + [email protected] +async def test_client_timeout_passive_connection(pair_factory, Client, monkeypatch): + open_connection = asyncio.open_connection + + async def slow_open_connection(host, port, **kwargs): + await asyncio.sleep(1) + return await open_connection(host, port, **kwargs) + + async with pair_factory(Client(connection_timeout=0.5), logged=True) as pair: + with monkeypatch.context() as m: + m.setattr("asyncio.open_connection", slow_open_connection) + with pytest.raises(asyncio.TimeoutError): + await pair.client.get_passive_connection()
