diff --git a/4356.patch b/4356.patch new file mode 100644 index 0000000000000000000000000000000000000000..3f980e303a795f4789d8a8d28e273b0c58258f71 --- /dev/null +++ b/4356.patch @@ -0,0 +1,40 @@ +From d53bf1509f40c8e84feb62ac13e91b76074a063a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= +Date: Tue, 14 May 2024 16:19:02 +0200 +Subject: [PATCH] Explicitly disallow resource paths starting with single + backslash + +Previously, such paths were disallowed implicitly +as they were treated as Windows absolute paths. + +Since Python 3.13, paths starting with a single backslash are not considered +Windows-absolute, so we treat them specially. + +This change makes the existing doctest pass with Python 3.13. + +Partially fixes https://github.com/pypa/setuptools/issues/4196 +--- + pkg_resources/__init__.py | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/pkg_resources/__init__.py b/pkg_resources/__init__.py +index 713d9bdfa3..faee7dec79 100644 +--- a/pkg_resources/__init__.py ++++ b/pkg_resources/__init__.py +@@ -1604,6 +1604,7 @@ def _validate_resource_path(path): + os.path.pardir in path.split(posixpath.sep) + or posixpath.isabs(path) + or ntpath.isabs(path) ++ or path.startswith("\\") + ) + if not invalid: + return +@@ -1611,7 +1612,7 @@ def _validate_resource_path(path): + msg = "Use of .. or absolute path in a resource path is not allowed." + + # Aggressively disallow Windows absolute paths +- if ntpath.isabs(path) and not posixpath.isabs(path): ++ if (path.startswith("\\") or ntpath.isabs(path)) and not posixpath.isabs(path): + raise ValueError(msg) + + # for compatibility, warn; in future diff --git a/4357.patch b/4357.patch new file mode 100644 index 0000000000000000000000000000000000000000..7222d3dba26638f4015a99da3f9fc5d3bce631ae --- /dev/null +++ b/4357.patch @@ -0,0 +1,30 @@ +From c6266e423fa26aafa01f1df71de7c6613273155e Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= +Date: Tue, 14 May 2024 16:24:07 +0200 +Subject: [PATCH] Make the validation test for entry-points work with Python + 3.13+ + +The exception in importlib.metadata has changed. +See https://github.com/python/importlib_metadata/issues/488 + +This makes an existing test pass with Python 3.13. + +Partially fixes https://github.com/pypa/setuptools/issues/4196 +--- + setuptools/_entry_points.py | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + +diff --git a/setuptools/_entry_points.py b/setuptools/_entry_points.py +index 747a69067e..b244e78387 100644 +--- a/setuptools/_entry_points.py ++++ b/setuptools/_entry_points.py +@@ -17,7 +17,8 @@ def ensure_valid(ep): + """ + try: + ep.extras +- except AttributeError as ex: ++ except (AttributeError, AssertionError) as ex: ++ # Why both? See https://github.com/python/importlib_metadata/issues/488 + msg = ( + f"Problems to parse {ep}.\nPlease ensure entry-point follows the spec: " + "https://packaging.python.org/en/latest/specifications/entry-points/" diff --git a/backport-CVE-2024-6345.patch b/backport-CVE-2024-6345.patch index ed179030d694d79b3e42459425c0af32ca078bae..d3f50741540067d04e94d17b0546a1f6fcff96e1 100644 --- a/backport-CVE-2024-6345.patch +++ b/backport-CVE-2024-6345.patch @@ -1,191 +1,33 @@ -From 88807c7062788254f654ea8c03427adc859321f0 Mon Sep 17 00:00:00 2001 -From: jaraco -Date: Tue, 30 Apr 2024 15:02:00 +0800 -Subject: [PATCH] Modernize package_index VCS handling -https://github.com/pypa/setuptools/commit/88807c7062788254f654ea8c03427adc859321f0 -https://github.com/pypa/setuptools/pull/4332 +From 472528deea4063f20c5d9525f0faf64ae0cd0a90 Mon Sep 17 00:00:00 2001 +From: Lumir Balhar +Date: Wed, 24 Jul 2024 14:26:09 +0200 +Subject: [PATCH] CVE-2024-6345 --- - changelog.d/4332.feature.rst | 1 + - setup.cfg | 1 + - setuptools/package_index.py | 146 ++++++++++++++------------ - setuptools/tests/test_packageindex.py | 56 +++++----- - 4 files changed, 108 insertions(+), 96 deletions(-) - create mode 100644 changelog.d/4332.feature.rst + setuptools/package_index.py | 21 +++++---------------- + setuptools/tests/test_packageindex.py | 20 ++++++++++---------- + 2 files changed, 15 insertions(+), 26 deletions(-) -diff --git a/changelog.d/4332.feature.rst b/changelog.d/4332.feature.rst -new file mode 100644 -index 0000000..1e612ec ---- /dev/null -+++ b/changelog.d/4332.feature.rst -@@ -0,0 +1 @@ -+Modernized and refactored VCS handling in package_index. -diff --git a/setup.cfg b/setup.cfg -index c1d8a69..6787594 100644 ---- a/setup.cfg -+++ b/setup.cfg -@@ -63,6 +63,7 @@ testing = - tomli-w>=1.0.0 - pytest-timeout - pytest-perf -+ pytest-subprocess - testing-integration = - pytest - pytest-xdist diff --git a/setuptools/package_index.py b/setuptools/package_index.py -index 3130ace..ae50db5 100644 +index 7095585..1368bde 100644 --- a/setuptools/package_index.py +++ b/setuptools/package_index.py -@@ -1,6 +1,7 @@ +@@ -1,5 +1,6 @@ """PyPI and direct package downloading.""" - import sys +import subprocess + import sys import os import re - import io -@@ -586,7 +587,7 @@ class PackageIndex(Environment): - scheme = URL_SCHEME(spec) - if scheme: - # It's a url, download it to tmpdir -- found = self._download_url(scheme.group(1), spec, tmpdir) -+ found = self._download_url(spec, tmpdir) - base, fragment = egg_info_for_url(spec) - if base.endswith('.py'): - found = self.gen_setup(found, fragment, tmpdir) -@@ -813,7 +814,7 @@ class PackageIndex(Environment): - else: - raise DistutilsError("Download error for %s: %s" % (url, v)) from v - -- def _download_url(self, scheme, url, tmpdir): -+ def _download_url(self, url, tmpdir): - # Determine download filename - # - name, fragment = egg_info_for_url(url) -@@ -828,19 +829,60 @@ class PackageIndex(Environment): - - filename = os.path.join(tmpdir, name) - -- # Download the file -- # -- if scheme == 'svn' or scheme.startswith('svn+'): -- return self._download_svn(url, filename) -- elif scheme == 'git' or scheme.startswith('git+'): -- return self._download_git(url, filename) -- elif scheme.startswith('hg+'): -- return self._download_hg(url, filename) -- elif scheme == 'file': -- return urllib.request.url2pathname(urllib.parse.urlparse(url)[2]) -- else: -- self.url_ok(url, True) # raises error if not allowed -- return self._attempt_download(url, filename) -+ return self._download_vcs(url, filename) or self._download_other(url, filename) -+ -+ -+ @staticmethod -+ def _resolve_vcs(url): -+ """ -+ >>> rvcs = PackageIndex._resolve_vcs -+ >>> rvcs('git+http://foo/bar') -+ 'git' -+ >>> rvcs('hg+https://foo/bar') -+ 'hg' -+ >>> rvcs('git:myhost') -+ 'git' -+ >>> rvcs('hg:myhost') -+ >>> rvcs('http://foo/bar') -+ """ -+ scheme = urllib.parse.urlsplit(url).scheme -+ pre, sep, post = scheme.partition('+') -+ # svn and git have their own protocol; hg does not -+ allowed = set(['svn', 'git'] + ['hg'] * bool(sep)) -+ return next(iter({pre} & allowed), None) -+ -+ def _download_vcs(self, url, spec_filename): -+ vcs = self._resolve_vcs(url) -+ if not vcs: -+ return -+ if vcs == 'svn': -+ raise DistutilsError( -+ f"Invalid config, SVN download is not supported: {url}" -+ ) -+ -+ filename, _, _ = spec_filename.partition('#') -+ url, rev = self._vcs_split_rev_from_url(url) -+ -+ self.info(f"Doing {vcs} clone from {url} to {filename}") -+ subprocess.check_call([vcs, 'clone', '--quiet', url, filename]) -+ -+ co_commands = dict( -+ git=[vcs, '-C', filename, 'checkout', '--quiet', rev], -+ hg=[vcs, '--cwd', filename, 'up', '-C', '-r', rev, '-q'], -+ ) -+ if rev is not None: -+ self.info(f"Checking out {rev}") -+ subprocess.check_call(co_commands[vcs]) -+ -+ return filename -+ -+ def _download_other(self, url, filename): -+ scheme = urllib.parse.urlsplit(url).scheme -+ if scheme == 'file': # pragma: no cover -+ return urllib.request.url2pathname(urllib.parse.urlparse(url).path) -+ # raise error if not allowed -+ self.url_ok(url, True) -+ return self._attempt_download(url, filename) - - def scan_url(self, url): - self.process_url(url, True) -@@ -856,64 +898,36 @@ class PackageIndex(Environment): - os.unlink(filename) - raise DistutilsError(f"Unexpected HTML page found at {url}") - -- def _download_svn(self, url, _filename): -- raise DistutilsError(f"Invalid config, SVN download is not supported: {url}") -- - @staticmethod -- def _vcs_split_rev_from_url(url, pop_prefix=False): -- scheme, netloc, path, query, frag = urllib.parse.urlsplit(url) -+ def _vcs_split_rev_from_url(url): -+ """ -+ Given a possible VCS URL, return a clean URL and resolved revision if any. -+ >>> vsrfu = PackageIndex._vcs_split_rev_from_url -+ >>> vsrfu('git+https://github.com/pypa/setuptools@v69.0.0#egg-info=setuptools') -+ ('https://github.com/pypa/setuptools', 'v69.0.0') -+ >>> vsrfu('git+https://github.com/pypa/setuptools#egg-info=setuptools') -+ ('https://github.com/pypa/setuptools', None) -+ >>> vsrfu('http://foo/bar') -+ ('http://foo/bar', None) -+ """ -+ parts = urllib.parse.urlsplit(url) +@@ -881,17 +882,11 @@ class PackageIndex(Environment): + url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) -- scheme = scheme.split('+', 1)[-1] -+ clean_scheme = parts.scheme.split('+', 1)[-1] - - # Some fragment identification fails -- path = path.split('#', 1)[0] -- -- rev = None -- if '@' in path: -- path, rev = path.rsplit('@', 1) -- -- # Also, discard fragment -- url = urllib.parse.urlunsplit((scheme, netloc, path, query, '')) -+ no_fragment_path, _, _ = parts.path.partition('#') - -- return url, rev -+ pre, sep, post = no_fragment_path.rpartition('@') -+ clean_path, rev = (pre, post) if sep else (post, None) - -- def _download_git(self, url, filename): -- filename = filename.split('#', 1)[0] -- url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) -- -- self.info("Doing git clone from %s to %s", url, filename) + self.info("Doing git clone from %s to %s", url, filename) - os.system("git clone --quiet %s %s" % (url, filename)) -- -- if rev is not None: -- self.info("Checking out %s", rev) ++ subprocess.check_call(["git", "clone", "--quiet", url, filename]) + + if rev is not None: + self.info("Checking out %s", rev) - os.system( - "git -C %s checkout --quiet %s" - % ( @@ -193,24 +35,19 @@ index 3130ace..ae50db5 100644 - rev, - ) - ) -+ resolved = parts._replace( -+ scheme=clean_scheme, -+ path=clean_path, -+ # discard the fragment -+ fragment='', -+ ).geturl() ++ subprocess.check_call(["git", "-C", filename, "checkout", "--quiet", rev]) + + return filename + +@@ -900,17 +895,11 @@ class PackageIndex(Environment): + url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) -- return filename -- -- def _download_hg(self, url, filename): -- filename = filename.split('#', 1)[0] -- url, rev = self._vcs_split_rev_from_url(url, pop_prefix=True) -- -- self.info("Doing hg clone from %s to %s", url, filename) + self.info("Doing hg clone from %s to %s", url, filename) - os.system("hg clone --quiet %s %s" % (url, filename)) -- -- if rev is not None: -- self.info("Updating to %s", rev) ++ subprocess.check_call(["hg", "clone", "--quiet", url, filename]) + + if rev is not None: + self.info("Updating to %s", rev) - os.system( - "hg --cwd %s up -C -r %s -q" - % ( @@ -218,100 +55,62 @@ index 3130ace..ae50db5 100644 - rev, - ) - ) -- -- return filename -+ return resolved, rev ++ subprocess.check_call(["hg", "--cwd", filename, "up", "-C", "-r", rev, "-q"]) + + return filename - def debug(self, msg, *args): - log.debug(msg, *args) diff --git a/setuptools/tests/test_packageindex.py b/setuptools/tests/test_packageindex.py -index f1fa745..a7d2b5d 100644 +index 0287063..c136e8d 100644 --- a/setuptools/tests/test_packageindex.py +++ b/setuptools/tests/test_packageindex.py -@@ -5,7 +5,6 @@ import platform - import urllib.request - import urllib.error - import http.client --from unittest import mock - - import pytest - -@@ -186,49 +185,46 @@ class TestPackageIndex: - assert dists[0].version == '' - assert dists[1].version == vc - -- def test_download_git_with_rev(self, tmpdir): -+ def test_download_git_with_rev(self, tmp_path, fp): +@@ -190,37 +190,37 @@ class TestPackageIndex: url = 'git+https://github.example/group/project@master#egg=foo' index = setuptools.package_index.PackageIndex() - with mock.patch("os.system") as os_system_mock: -- result = index.download(url, str(tmpdir)) -+ expected_dir = tmp_path / 'project@master' -+ fp.register([ -+ 'git', -+ 'clone', -+ '--quiet', -+ 'https://github.example/group/project', -+ expected_dir, -+ ]) -+ fp.register(['git', '-C', expected_dir, 'checkout', '--quiet', 'master']) ++ with mock.patch("subprocess.check_call") as subprocess_check_call_mock: + result = index.download(url, str(tmpdir)) - os_system_mock.assert_called() -+ result = index.download(url, tmp_path) ++ subprocess_check_call_mock.assert_called() -- expected_dir = str(tmpdir / 'project@master') -- expected = ( -- 'git clone --quiet ' 'https://github.example/group/project {expected_dir}' + expected_dir = str(tmpdir / 'project@master') + expected = ( + 'git clone --quiet ' 'https://github.example/group/project {expected_dir}' - ).format(**locals()) - first_call_args = os_system_mock.call_args_list[0][0] -- assert first_call_args == (expected,) -+ assert result == str(expected_dir) -+ assert len(fp.calls) == 2 ++ ).format(**locals()).split() ++ first_call_args = subprocess_check_call_mock.call_args_list[0][0] + assert first_call_args == (expected,) -- tmpl = 'git -C {expected_dir} checkout --quiet master' + tmpl = 'git -C {expected_dir} checkout --quiet master' - expected = tmpl.format(**locals()) - assert os_system_mock.call_args_list[1][0] == (expected,) -- assert result == expected_dir -- -- def test_download_git_no_rev(self, tmpdir): -+ def test_download_git_no_rev(self, tmp_path, fp): ++ expected = tmpl.format(**locals()).split() ++ assert subprocess_check_call_mock.call_args_list[1][0] == (expected,) + assert result == expected_dir + + def test_download_git_no_rev(self, tmpdir): url = 'git+https://github.example/group/project#egg=foo' index = setuptools.package_index.PackageIndex() - with mock.patch("os.system") as os_system_mock: -- result = index.download(url, str(tmpdir)) -- ++ with mock.patch("subprocess.check_call") as subprocess_check_call_mock: + result = index.download(url, str(tmpdir)) + - os_system_mock.assert_called() -- -- expected_dir = str(tmpdir / 'project') -- expected = ( -- 'git clone --quiet ' 'https://github.example/group/project {expected_dir}' ++ subprocess_check_call_mock.assert_called() + + expected_dir = str(tmpdir / 'project') + expected = ( + 'git clone --quiet ' 'https://github.example/group/project {expected_dir}' - ).format(**locals()) - os_system_mock.assert_called_once_with(expected) -- -- def test_download_svn(self, tmpdir): -+ expected_dir = tmp_path / 'project' -+ fp.register([ -+ 'git', -+ 'clone', -+ '--quiet', -+ 'https://github.example/group/project', -+ expected_dir, -+ ]) -+ index.download(url, tmp_path) -+ -+ def test_download_svn(self, tmp_path): - url = 'svn+https://svn.example/project#egg=foo' - index = setuptools.package_index.PackageIndex() ++ ).format(**locals()).split() ++ subprocess_check_call_mock.assert_called_once_with(expected) - msg = r".*SVN download is not supported.*" - with pytest.raises(distutils.errors.DistutilsError, match=msg): -- index.download(url, str(tmpdir)) -+ index.download(url, tmp_path) - - - class TestContentCheckers: + def test_download_svn(self, tmpdir): + url = 'svn+https://svn.example/project#egg=foo' -- -2.33.0 +2.45.2 diff --git a/python-setuptools.spec b/python-setuptools.spec index 0cfb03f9dace6b663f7156f2d503137377759835..33a7447c9545d69211b9873183458ca33a0e975e 100644 --- a/python-setuptools.spec +++ b/python-setuptools.spec @@ -7,16 +7,20 @@ %global python_whlname setuptools-%{version}-py3-none-any.whl Name: python-setuptools -Version: 68.0.0 -Release: 2 +Version: 69.2.0 +Release: 1 Summary: Easily build and distribute Python packages License: MIT and (BSD or ASL 2.0) URL: https://pypi.python.org/pypi/setuptools Source0: %{pypi_source setuptools %{version}} -Patch9000: bugfix-eliminate-random-order-in-metadata.patch Patch9001: backport-CVE-2024-6345.patch +# Python 3.13 compatibility patches, merged upstream +#https://github.com/pypa/setuptools/pull/4356.patch +#https://github.com/pypa/setuptools/pull/4357.patch +Patch9002: 4356.patch +Patch9003: 4357.patch BuildArch: noarch @@ -62,7 +66,6 @@ execute the software that requires pkg_resources.py. find setuptools pkg_resources -name \*.py | xargs sed -i -e '1 {/^#!\//d}' rm -f setuptools/*.exe rm setuptools/tests/test_integration.py -chmod -x README.rst %build %if %{without bootstrap} @@ -108,10 +111,13 @@ PYTHONDONTWRITEBYTECODE=1 PYTHONPATH=$(pwd) py.test-%{python3_version} --ignore= %files help %defattr(-,root,root) -%doc docs/* CHANGES.rst README.rst +%doc docs/* README.rst %changelog +* Mon Aug 26 2024 dillon chen - 69.2.0-1 +- back package to version 69.2.0 + * Mon Jul 15 2024 zhangxianting - 68.0.0-2 - Fix CVE-2024-6345 diff --git a/setuptools-68.0.0.tar.gz b/setuptools-68.0.0.tar.gz deleted file mode 100644 index b2d8822260df88c3b4013337986da29fe6875451..0000000000000000000000000000000000000000 Binary files a/setuptools-68.0.0.tar.gz and /dev/null differ diff --git a/setuptools-69.2.0.tar.gz b/setuptools-69.2.0.tar.gz new file mode 100644 index 0000000000000000000000000000000000000000..9252be2374d0ff49f15e0160dd4d7a736d14227e Binary files /dev/null and b/setuptools-69.2.0.tar.gz differ