From 786093a8fb95f2d91a8c27392576e4d6bd0e71e9 Mon Sep 17 00:00:00 2001 From: Azure Linux Security Servicing Account Date: Wed, 29 Apr 2026 18:50:57 +0000 Subject: [PATCH 1/2] Patch poetry for CVE-2026-41140 --- SPECS/poetry/CVE-2026-41140.patch | 374 ++++++++++++++++++++++++++++++ SPECS/poetry/poetry.spec | 6 +- 2 files changed, 379 insertions(+), 1 deletion(-) create mode 100644 SPECS/poetry/CVE-2026-41140.patch diff --git a/SPECS/poetry/CVE-2026-41140.patch b/SPECS/poetry/CVE-2026-41140.patch new file mode 100644 index 00000000000..8a120908ac9 --- /dev/null +++ b/SPECS/poetry/CVE-2026-41140.patch @@ -0,0 +1,374 @@ +From a63b68b2f8e952a7d72c39a06817b5fbddd22c4c Mon Sep 17 00:00:00 2001 +From: AllSpark +Date: Wed, 29 Apr 2026 18:44:11 +0000 +Subject: [PATCH] fix(utils.extractall): refuse to write files outside target + directory during sdist and wheel extraction; centralize traversal fixtures + and add tests for tar and zip extraction safety; update broken tarfile filter + versions + +Signed-off-by: Azure Linux Security Servicing Account +Upstream-reference: AI Backport of https://github.com/python-poetry/poetry/commit/47e97340cae50d3698aac858732788861ba8dd1f.patch +--- + src/poetry/utils/helpers.py | 56 +++++++++++- + tests/conftest.py | 80 +++++++++++++++++ + tests/utils/test_helpers.py | 169 ++++++++++++++++++++++++++++++++++++ + 3 files changed, 302 insertions(+), 3 deletions(-) + +diff --git a/src/poetry/utils/helpers.py b/src/poetry/utils/helpers.py +index fbc29e5..0578cbf 100644 +--- a/src/poetry/utils/helpers.py ++++ b/src/poetry/utils/helpers.py +@@ -358,11 +358,27 @@ def extractall(source: Path, dest: Path, zip: bool) -> None: + """Extract all members from either a zip or tar archive.""" + if zip: + with zipfile.ZipFile(source) as archive: +- archive.extractall(dest) ++ # Validate all member paths before extraction ++ # ++ # Attention: Path.absolute() is not sufficient because it does not ++ # normalize, i.e. does not remove "..". ++ # ++ # We want to avoid Path.resolve() because it is significantly slower ++ # than os.path.abspath()! ++ dest = Path(os.path.abspath(dest)) ++ safe_members: list[zipfile.ZipInfo] = [] ++ for member in archive.infolist(): ++ member_path = Path(os.path.abspath(dest / member.filename)) ++ if not member_path.is_relative_to(dest): ++ # skip members that would extract outside dest ++ continue ++ safe_members.append(member) ++ for member in safe_members: ++ archive.extract(member, path=dest) + else: + # These versions of python shipped with a broken tarfile data_filter, per + # https://github.com/python/cpython/issues/107845. +- broken_tarfile_filter = {(3, 8, 17), (3, 9, 17), (3, 10, 12), (3, 11, 4)} ++ broken_tarfile_filter = {(3, 10, 12), (3, 11, 4)} + with tarfile.open(source) as archive: + if ( + hasattr(tarfile, "data_filter") +@@ -370,4 +386,38 @@ def extractall(source: Path, dest: Path, zip: bool) -> None: + ): + archive.extractall(dest, filter="data") + else: +- archive.extractall(dest) ++ # Validate all member paths before extraction ++ # ++ # Attention: Path.absolute() is not sufficient because it does not ++ # normalize, i.e. does not remove "..". ++ # ++ # We want to avoid Path.resolve() because it is significantly slower ++ # than os.path.abspath()! ++ dest = Path(os.path.abspath(dest)) ++ safe_members: list[tarfile.TarInfo] = [] ++ for member in archive.getmembers(): ++ member_path = Path(os.path.abspath(dest / member.name)) ++ if not member_path.is_relative_to(dest): ++ raise ValueError( ++ f"Refusing to extract {member.name}: " ++ f"would write outside {dest}" ++ ) ++ if member.issym(): ++ link_target = Path( ++ os.path.abspath(member_path.parent / member.linkname) ++ ) ++ if not link_target.is_relative_to(dest): ++ raise ValueError( ++ f"Refusing symlink {member.name}: " ++ f"target {member.linkname} outside {dest}" ++ ) ++ elif member.islnk(): ++ link_target = Path(os.path.abspath(dest / member.linkname)) ++ if not link_target.is_relative_to(dest): ++ raise ValueError( ++ f"Refusing hardlink {member.name}: " ++ f"target {member.linkname} outside {dest}" ++ ) ++ safe_members.append(member) ++ archive.extractall(dest, members=safe_members) ++ +diff --git a/tests/conftest.py b/tests/conftest.py +index 5955c81..1c3d42f 100644 +--- a/tests/conftest.py ++++ b/tests/conftest.py +@@ -525,3 +525,83 @@ def venv_flags_default() -> dict[str, bool]: + def httpretty_windows_mock_urllib3_wait_for_socket(mocker: MockerFixture) -> None: + # this is a workaround for https://github.com/gabrielfalcao/HTTPretty/issues/442 + mocker.patch("urllib3.util.wait.select_wait_for_socket", returns=True) ++ ++ ++ ++@pytest.fixture(params=[False, True]) # relative path ++def wheel_with_path_traversal(tmp_path: Path, request: pytest.FixtureRequest) -> Path: ++ import zipfile ++ ++ traversal_path = ( ++ "../../traversal.txt" ++ if request.param ++ else (tmp_path / "traversal.txt").as_posix() ++ ) ++ ++ wheel = tmp_path / "traversal-0.1-py3-none-any.whl" ++ files = { ++ "traversal/__init__.py": b"", ++ traversal_path: b"path traversal", ++ "traversal-0.1.dist-info/WHEEL": ( ++ b"Wheel-Version: 1.0\nRoot-Is-Purelib: true\nTag: py3-none-any\n" ++ ), ++ "traversal-0.1.dist-info/METADATA": ( ++ b"Metadata-Version: 2.1\nName: traversal\nVersion: 0.1\n" ++ ), ++ } ++ files["traversal-0.1.dist-info/RECORD"] = ( ++ "\n".join([f"{k},," for k in files] + ["traversal-0.1.dist-info/RECORD,,"]) + "\n" ++ ).encode() ++ ++ with zipfile.ZipFile(wheel, "w") as z: ++ for k, v in files.items(): ++ z.writestr(k, v) ++ ++ return wheel ++ ++ ++@pytest.fixture(params=[False, True]) # relative path ++def wheel_with_path_traversal_via_symlink( ++ tmp_path: Path, request: pytest.FixtureRequest ++) -> Path: ++ import stat ++ import zipfile ++ ++ wheel = tmp_path / "symlink-0.1-py3-none-any.whl" ++ files = { ++ "symlink/__init__.py": b"", ++ "symlink-0.1.dist-info/WHEEL": ( ++ b"Wheel-Version: 1.0\nRoot-Is-Purelib: true\nTag: py3-none-any\n" ++ ), ++ "symlink-0.1.dist-info/METADATA": ( ++ b"Metadata-Version: 2.1\nName: symlink-pkg\nVersion: 0.1\n" ++ ), ++ } ++ ++ symlink_entry = "symlink/traversal_link" ++ symlink_target = ( ++ b"../../target" if request.param else (tmp_path / "target").as_posix().encode("utf-8") ++ ) ++ traversal_file = "symlink/traversal_link/traversal.txt" ++ ++ record_lines = [f"{k},," for k in files] ++ record_lines.append(f"{symlink_entry},,") ++ record_lines.append(f"{traversal_file},,") ++ record_lines.append("symlink-0.1.dist-info/RECORD,,") ++ files["symlink-0.1.dist-info/RECORD"] = ("\n".join(record_lines) + "\n").encode() ++ ++ with zipfile.ZipFile(wheel, "w") as z: ++ for k, v in files.items(): ++ z.writestr(k, v) ++ ++ # Add a ZIP entry whose external attributes mark it as a symlink. ++ # The entry's data is the symlink target, pointing outside the ++ # installation directory. ++ info = zipfile.ZipInfo(symlink_entry) ++ info.create_system = 3 # unix ++ info.external_attr = (stat.S_IFLNK | 0o777) << 16 ++ z.writestr(info, symlink_target) ++ ++ z.writestr(traversal_file, b"path traversal") ++ ++ return wheel +diff --git a/tests/utils/test_helpers.py b/tests/utils/test_helpers.py +index 2399e29..1e5d112 100644 +--- a/tests/utils/test_helpers.py ++++ b/tests/utils/test_helpers.py +@@ -3,12 +3,18 @@ from __future__ import annotations + from typing import TYPE_CHECKING + from typing import Any + ++import contextlib ++import sys ++import tarfile + import pytest + + from poetry.core.utils.helpers import parse_requires + ++from poetry.utils._compat import WINDOWS + from poetry.utils.helpers import HTTPRangeRequestSupported + from poetry.utils.helpers import download_file ++from poetry.utils.helpers import ensure_path ++from poetry.utils.helpers import extractall + from poetry.utils.helpers import get_file_hash + from poetry.utils.helpers import get_highest_priority_hash_type + +@@ -183,6 +189,169 @@ def test_download_file_raise_accepts_ranges( + + if accepts_ranges and raise_accepts_ranges: + with pytest.raises(HTTPRangeRequestSupported): ++ ++ ++@pytest.mark.parametrize("relative", [False, True]) ++@pytest.mark.parametrize("existing", [False, True]) ++def test_extractall_sdist_no_path_traversal( ++ tmp_path: Path, relative: bool, existing: bool ++) -> None: ++ import io ++ import tarfile ++ ++ archive = tmp_path / "traversal.tar.gz" ++ dest = tmp_path / "dest" ++ dest.mkdir() ++ ++ target = tmp_path / "traversal.txt" ++ if existing: ++ target.write_text("original", encoding="utf-8") ++ ++ with tarfile.open(archive, "w:gz") as tar: ++ b = b"path traversal" ++ t = tarfile.TarInfo("../traversal.txt" if relative else target.as_posix()) ++ t.size = len(b) ++ tar.addfile(t, io.BytesIO(b)) ++ ++ has_data_filter = hasattr(tarfile, "data_filter") ++ # The stdlib implementation just strips the leading "/" from absolute paths ++ # and extracts them relative to the target directory (except for Windows). ++ # We do not care and raise an error. ++ raises = ( ++ relative ++ or WINDOWS ++ or not has_data_filter ++ or sys.version_info[:3] in {(3, 10, 12), (3, 11, 4)} ++ ) ++ exceptions: tuple[type[Exception], ...] ++ if has_data_filter: ++ if relative: ++ exceptions = (tarfile.OutsideDestinationError, ValueError) ++ else: ++ exceptions = (tarfile.AbsolutePathError, ValueError) ++ else: ++ # tarfile.OutsideDestinationError does not exist ++ exceptions = (ValueError,) ++ ++ with pytest.raises(exceptions) if raises else contextlib.nullcontext(): ++ extractall(source=archive, dest=dest, zip=False) ++ ++ if existing: ++ assert target.exists() ++ assert target.read_text(encoding="utf-8") == "original" ++ else: ++ assert not target.exists() ++ if not raises: ++ # check that expected location exists, otherwise we have to check ++ # that there is no traversal in an unexpected location ++ assert (dest / target.as_posix().lstrip("/")).exists() ++ ++ ++@pytest.mark.parametrize("link_type", [tarfile.SYMTYPE, tarfile.LNKTYPE]) ++@pytest.mark.parametrize("relative", [False, True]) ++@pytest.mark.parametrize("existing", [False, True]) ++def test_extractall_sdist_no_symlink_path_traversal( ++ tmp_path: Path, link_type: bytes, relative: bool, existing: bool ++) -> None: ++ import io ++ import tarfile ++ ++ archive = tmp_path / "traversal.tar.gz" ++ dest = tmp_path / "dest" ++ dest.mkdir() ++ ++ target = tmp_path / "traversal.txt" ++ if existing: ++ target.write_text("original", encoding="utf-8") ++ ++ with tarfile.open(archive, "w:gz") as tar: ++ # We use a link in a subdirectory to test the difference ++ # between symlinks and hardlinks: ++ # symlinks are relative to the directory of the symlink, ++ # while hardlinks are relative to the root of the archive ++ s = tarfile.TarInfo("sub/link") ++ s.type = link_type ++ if relative: ++ s.linkname = ( ++ "../../traversal.txt" ++ if link_type == tarfile.SYMTYPE ++ else "../traversal.txt" ++ ) ++ else: ++ s.linkname = target.as_posix() ++ tar.addfile(s) ++ p = b"path traversal" ++ f = tarfile.TarInfo("sub/link") ++ f.size = len(p) ++ tar.addfile(f, io.BytesIO(p)) ++ ++ exceptions: tuple[type[Exception], ...] ++ if hasattr(tarfile, "data_filter"): ++ exceptions = ( ++ tarfile.AbsoluteLinkError, ++ tarfile.LinkOutsideDestinationError, ++ ValueError, ++ ) ++ else: ++ # tarfile.OutsideDestinationError does not exist ++ exceptions = (ValueError,) ++ ++ with pytest.raises(exceptions): ++ extractall(source=archive, dest=dest, zip=False) ++ ++ if existing: ++ assert target.exists() ++ assert target.read_text(encoding="utf-8") == "original" ++ else: ++ assert not target.exists() ++ ++ ++@pytest.mark.parametrize("existing", [False, True]) ++def test_extractall_wheel_no_path_traversal( ++ tmp_path: Path, wheel_with_path_traversal: Path, existing: bool ++) -> None: ++ """see also test_no_path_traversal in test_wheel_installer.py""" ++ dest = tmp_path / "dest" / "dir" ++ dest.mkdir(parents=True) ++ target = tmp_path / "traversal.txt" ++ if existing: ++ target.write_text("original", encoding="utf-8") ++ ++ extractall(source=wheel_with_path_traversal, dest=dest, zip=True) ++ ++ if existing: ++ assert target.exists() ++ assert target.read_text(encoding="utf-8") == "original" ++ else: ++ assert not target.exists() ++ ++ # target is "../.." but also check ".." just to be sure ++ assert not (dest.parent / "traversal.txt").exists() ++ ++ ++@pytest.mark.parametrize("existing", [False, True]) ++def test_extractall_wheel_no_path_traversal_via_symlink( ++ tmp_path: Path, wheel_with_path_traversal_via_symlink: Path, existing: bool ++) -> None: ++ """see also test_no_path_traversal_via_symlink in test_wheel_installer.py""" ++ dest = tmp_path / "dest" / "dir" ++ dest.mkdir(parents=True) ++ target_dir = tmp_path / "target" ++ target_dir.mkdir() ++ target = target_dir / "traversal.txt" ++ if existing: ++ target.write_text("original", encoding="utf-8") ++ ++ with pytest.raises(FileNotFoundError if WINDOWS else NotADirectoryError): ++ extractall(source=wheel_with_path_traversal_via_symlink, dest=dest, zip=True) ++ ++ assert target_dir.exists() ++ if existing: ++ assert target.exists() ++ assert target.read_text(encoding="utf-8") == "original" ++ else: ++ assert not target.exists() ++ + download_file(url, dest, raise_accepts_ranges=raise_accepts_ranges) + assert not dest.exists() + else: +-- +2.45.4 + diff --git a/SPECS/poetry/poetry.spec b/SPECS/poetry/poetry.spec index bb2c467f8a5..b6ec73df567 100644 --- a/SPECS/poetry/poetry.spec +++ b/SPECS/poetry/poetry.spec @@ -5,13 +5,14 @@ projects, ensuring you have the right stack everywhere.} Summary: Python dependency management and packaging made easy Name: %{pypi_name} Version: 1.8.5 -Release: 1%{?dist} +Release: 2%{?dist} License: MIT Vendor: Microsoft Corporation Distribution: Azure Linux URL: https://poetry.eustace.io/ Source0: https://github.com/python-poetry/poetry/archive/refs/tags/%{version}.tar.gz#/poetry-%{version}.tar.gz Patch0: CVE-2026-34591.patch +Patch1: CVE-2026-41140.patch # relax some too-strict dependencies that are specified in setup.py: # - importlib-metadata (either removed or too old in fedora) # - keyring (too new in fedora, but should be compatible) @@ -109,6 +110,9 @@ pip3 install --ignore-installed \ %{python3_sitelib}/%{pypi_name}-%{version}.dist-info/ %changelog +* Wed Apr 29 2026 Azure Linux Security Servicing Account - 1.8.5-2 +- Patch for CVE-2026-41140 + * Tue Apr 07 2026 Azure Linux Security Servicing Account - 1.8.5-1 - Upgrade to version 1.8.5 - Patch for CVE-2026-34591 From 3c3df01b0c388b2646c913d558977539a33a48ce Mon Sep 17 00:00:00 2001 From: SumitJenaHCL Date: Thu, 30 Apr 2026 18:07:05 +0530 Subject: [PATCH 2/2] Updated patch for CVE-2026-41140 --- SPECS/poetry/CVE-2026-41140.patch | 105 +++++++++++++++++------------- 1 file changed, 59 insertions(+), 46 deletions(-) diff --git a/SPECS/poetry/CVE-2026-41140.patch b/SPECS/poetry/CVE-2026-41140.patch index 8a120908ac9..f11ebeaf347 100644 --- a/SPECS/poetry/CVE-2026-41140.patch +++ b/SPECS/poetry/CVE-2026-41140.patch @@ -9,37 +9,17 @@ Subject: [PATCH] fix(utils.extractall): refuse to write files outside target Signed-off-by: Azure Linux Security Servicing Account Upstream-reference: AI Backport of https://github.com/python-poetry/poetry/commit/47e97340cae50d3698aac858732788861ba8dd1f.patch --- - src/poetry/utils/helpers.py | 56 +++++++++++- - tests/conftest.py | 80 +++++++++++++++++ - tests/utils/test_helpers.py | 169 ++++++++++++++++++++++++++++++++++++ - 3 files changed, 302 insertions(+), 3 deletions(-) + src/poetry/utils/helpers.py | 37 ++++- + tests/conftest.py | 82 ++++++++++ + tests/installation/test_wheel_installer.py | 26 ---- + tests/utils/test_helpers.py | 167 +++++++++++++++++++++ + 4 files changed, 284 insertions(+), 28 deletions(-) diff --git a/src/poetry/utils/helpers.py b/src/poetry/utils/helpers.py -index fbc29e5..0578cbf 100644 +index fbc29e5..6797c19 100644 --- a/src/poetry/utils/helpers.py +++ b/src/poetry/utils/helpers.py -@@ -358,11 +358,27 @@ def extractall(source: Path, dest: Path, zip: bool) -> None: - """Extract all members from either a zip or tar archive.""" - if zip: - with zipfile.ZipFile(source) as archive: -- archive.extractall(dest) -+ # Validate all member paths before extraction -+ # -+ # Attention: Path.absolute() is not sufficient because it does not -+ # normalize, i.e. does not remove "..". -+ # -+ # We want to avoid Path.resolve() because it is significantly slower -+ # than os.path.abspath()! -+ dest = Path(os.path.abspath(dest)) -+ safe_members: list[zipfile.ZipInfo] = [] -+ for member in archive.infolist(): -+ member_path = Path(os.path.abspath(dest / member.filename)) -+ if not member_path.is_relative_to(dest): -+ # skip members that would extract outside dest -+ continue -+ safe_members.append(member) -+ for member in safe_members: -+ archive.extract(member, path=dest) +@@ -362,7 +362,7 @@ def extractall(source: Path, dest: Path, zip: bool) -> None: else: # These versions of python shipped with a broken tarfile data_filter, per # https://github.com/python/cpython/issues/107845. @@ -48,7 +28,7 @@ index fbc29e5..0578cbf 100644 with tarfile.open(source) as archive: if ( hasattr(tarfile, "data_filter") -@@ -370,4 +386,38 @@ def extractall(source: Path, dest: Path, zip: bool) -> None: +@@ -370,4 +370,37 @@ def extractall(source: Path, dest: Path, zip: bool) -> None: ): archive.extractall(dest, filter="data") else: @@ -61,7 +41,7 @@ index fbc29e5..0578cbf 100644 + # We want to avoid Path.resolve() because it is significantly slower + # than os.path.abspath()! + dest = Path(os.path.abspath(dest)) -+ safe_members: list[tarfile.TarInfo] = [] ++ safe_members = [] + for member in archive.getmembers(): + member_path = Path(os.path.abspath(dest / member.name)) + if not member_path.is_relative_to(dest): @@ -87,18 +67,16 @@ index fbc29e5..0578cbf 100644 + ) + safe_members.append(member) + archive.extractall(dest, members=safe_members) -+ diff --git a/tests/conftest.py b/tests/conftest.py -index 5955c81..1c3d42f 100644 +index 5955c81..0c72188 100644 --- a/tests/conftest.py +++ b/tests/conftest.py -@@ -525,3 +525,83 @@ def venv_flags_default() -> dict[str, bool]: +@@ -525,3 +525,85 @@ def venv_flags_default() -> dict[str, bool]: def httpretty_windows_mock_urllib3_wait_for_socket(mocker: MockerFixture) -> None: # this is a workaround for https://github.com/gabrielfalcao/HTTPretty/issues/442 mocker.patch("urllib3.util.wait.select_wait_for_socket", returns=True) + + -+ +@pytest.fixture(params=[False, True]) # relative path +def wheel_with_path_traversal(tmp_path: Path, request: pytest.FixtureRequest) -> Path: + import zipfile @@ -121,7 +99,8 @@ index 5955c81..1c3d42f 100644 + ), + } + files["traversal-0.1.dist-info/RECORD"] = ( -+ "\n".join([f"{k},," for k in files] + ["traversal-0.1.dist-info/RECORD,,"]) + "\n" ++ "\n".join([f"{k},," for k in files] + ["traversal-0.1.dist-info/RECORD,,"]) ++ + "\n" + ).encode() + + with zipfile.ZipFile(wheel, "w") as z: @@ -151,7 +130,9 @@ index 5955c81..1c3d42f 100644 + + symlink_entry = "symlink/traversal_link" + symlink_target = ( -+ b"../../target" if request.param else (tmp_path / "target").as_posix().encode("utf-8") ++ b"../../target" ++ if request.param ++ else (tmp_path / "target").as_posix().encode("utf-8") + ) + traversal_file = "symlink/traversal_link/traversal.txt" + @@ -176,11 +157,48 @@ index 5955c81..1c3d42f 100644 + z.writestr(traversal_file, b"path traversal") + + return wheel +diff --git a/tests/installation/test_wheel_installer.py b/tests/installation/test_wheel_installer.py +index f891b3c..7ac9a82 100644 +--- a/tests/installation/test_wheel_installer.py ++++ b/tests/installation/test_wheel_installer.py +@@ -97,32 +97,6 @@ def test_install_dir_is_symlink(tmp_path: Path, demo_wheel: Path) -> None: + assert (Path(env.paths["purelib"]) / "demo").exists() + + +-@pytest.fixture +-def wheel_with_path_traversal(tmp_path: Path) -> Path: +- import zipfile +- +- wheel = tmp_path / "traversal-0.1-py3-none-any.whl" +- files = { +- "traversal/__init__.py": b"", +- "../../traversal.txt": b"", +- "traversal-0.1.dist-info/WHEEL": ( +- b"Wheel-Version: 1.0\nRoot-Is-Purelib: true\nTag: py3-none-any\n" +- ), +- "traversal-0.1.dist-info/METADATA": ( +- b"Metadata-Version: 2.1\nName: traversal\nVersion: 0.1\n" +- ), +- } +- files["traversal-0.1.dist-info/RECORD"] = ( +- "\n".join([f"{k},," for k in files] + ["traversal-0.1.dist-info/RECORD,,"]) +- + "\n" +- ).encode() +- +- with zipfile.ZipFile(wheel, "w") as z: +- for k, v in files.items(): +- z.writestr(k, v) +- +- return wheel +- + + def test_path_traversal(env: MockEnv, wheel_with_path_traversal: Path) -> None: + installer = WheelInstaller(env) diff --git a/tests/utils/test_helpers.py b/tests/utils/test_helpers.py -index 2399e29..1e5d112 100644 +index 2399e29..669588b 100644 --- a/tests/utils/test_helpers.py +++ b/tests/utils/test_helpers.py -@@ -3,12 +3,18 @@ from __future__ import annotations +@@ -3,12 +3,17 @@ from __future__ import annotations from typing import TYPE_CHECKING from typing import Any @@ -194,15 +212,14 @@ index 2399e29..1e5d112 100644 +from poetry.utils._compat import WINDOWS from poetry.utils.helpers import HTTPRangeRequestSupported from poetry.utils.helpers import download_file -+from poetry.utils.helpers import ensure_path +from poetry.utils.helpers import extractall from poetry.utils.helpers import get_file_hash from poetry.utils.helpers import get_highest_priority_hash_type -@@ -183,6 +189,169 @@ def test_download_file_raise_accepts_ranges( - - if accepts_ranges and raise_accepts_ranges: - with pytest.raises(HTTPRangeRequestSupported): +@@ -188,3 +193,165 @@ def test_download_file_raise_accepts_ranges( + else: + download_file(url, dest, raise_accepts_ranges=raise_accepts_ranges) + assert dest.is_file() + + +@pytest.mark.parametrize("relative", [False, True]) @@ -365,10 +382,6 @@ index 2399e29..1e5d112 100644 + assert target.read_text(encoding="utf-8") == "original" + else: + assert not target.exists() -+ - download_file(url, dest, raise_accepts_ranges=raise_accepts_ranges) - assert not dest.exists() - else: -- 2.45.4