diff --git a/relenv/build/darwin.py b/relenv/build/darwin.py index ab6c8a4b..c35fb80f 100644 --- a/relenv/build/darwin.py +++ b/relenv/build/darwin.py @@ -197,7 +197,7 @@ def build_python(env: MutableMapping[str, str], dirs: Dirs, logfp: IO[str]) -> N xz_checksum = xz_info["sha256"] else: xz_version = "5.8.1" - xz_url = "http://tukaani.org/xz/xz-{version}.tar.gz" + xz_url = "https://github.com/tukaani-project/xz/releases/download/v{version}/xz-{version}.tar.gz" xz_checksum = "ed4d5589c4cfe84e1697bd02a9954b76af336931" build.add( diff --git a/relenv/build/linux.py b/relenv/build/linux.py index 35f2206f..62b10c9d 100644 --- a/relenv/build/linux.py +++ b/relenv/build/linux.py @@ -689,7 +689,7 @@ def build_python(env: EnvMapping, dirs: Dirs, logfp: IO[str]) -> None: else: # Fallback to hardcoded values xz_version = "5.8.1" - xz_url = "http://tukaani.org/xz/xz-{version}.tar.gz" + xz_url = "https://github.com/tukaani-project/xz/releases/download/v{version}/xz-{version}.tar.gz" xz_checksum = "ed4d5589c4cfe84e1697bd02a9954b76af336931" build.add( diff --git a/relenv/python-versions.json b/relenv/python-versions.json index 1af410f4..d84959b1 100644 --- a/relenv/python-versions.json +++ b/relenv/python-versions.json @@ -367,7 +367,7 @@ }, "xz": { "5.8.2": { - "url": "http://tukaani.org/xz/xz-{version}.tar.gz", + "url": "https://github.com/tukaani-project/xz/releases/download/v{version}/xz-{version}.tar.gz", "sha256": "ce09c50a5962786b83e5da389c90dd2c15ecd0980a258dd01f70f9e7ce58a8f1", "platforms": [ "linux", @@ -376,7 +376,7 @@ ] }, "5.8.3": { - "url": "http://tukaani.org/xz/xz-{version}.tar.gz", + "url": "https://github.com/tukaani-project/xz/releases/download/v{version}/xz-{version}.tar.gz", "sha256": "3d3a1b973af218114f4f889bbaa2f4c037deaae0c8e815eec381c3d546b974a0", "platforms": [ "linux", diff --git a/relenv/pyversions.py b/relenv/pyversions.py index c9f210ab..a9844cc0 100644 --- a/relenv/pyversions.py +++ b/relenv/pyversions.py @@ -331,16 +331,36 @@ def detect_sqlite_versions() -> list[tuple[str, str]]: def detect_xz_versions() -> list[str]: """ - Detect available XZ versions from tukaani.org. + Detect available XZ versions from the tukaani-project GitHub releases API. + + Sourcing xz version metadata from tukaani.org is discouraged after + CVE-2024-3094 (the March 2024 xz-utils supply-chain backdoor). Both + release tarball hosting and the authoritative release listing moved + to https://github.com/tukaani-project/xz, so we consult that project's + Releases API and skip drafts / prereleases. """ - url = "https://tukaani.org/xz/" + url = "https://api.github.com/repos/tukaani-project/xz/releases" content = fetch_url_content(url) - # Find xz-X.Y.Z.tar.gz - pattern = r"xz-(\d+\.\d+\.\d+)\.tar\.gz" - matches = re.findall(pattern, content) - # Deduplicate and sort - versions = sorted(set(matches), key=lambda v: [int(x) for x in v.split(".")], reverse=True) - return versions + try: + releases = json.loads(content) + except json.JSONDecodeError: + return [] + versions: list[str] = [] + for release in releases: + if not isinstance(release, dict): + continue + if release.get("draft") or release.get("prerelease"): + continue + tag = release.get("tag_name", "") + if not isinstance(tag, str): + continue + # Tags are like "v5.8.4"; accept plain "5.8.4" too for robustness. + if tag.startswith("v"): + tag = tag[1:] + if not re.fullmatch(r"\d+\.\d+\.\d+", tag): + continue + versions.append(tag) + return sorted(set(versions), key=lambda v: [int(x) for x in v.split(".")], reverse=True) def detect_libffi_versions() -> list[str]: @@ -691,13 +711,13 @@ def update_dependency_versions(path: pathlib.Path, deps_to_update: list[str] | N if "xz" not in dependencies: dependencies["xz"] = {} if latest not in dependencies["xz"]: - url = f"http://tukaani.org/xz/xz-{latest}.tar.gz" + url = f"https://github.com/tukaani-project/xz/releases/download/v{latest}/xz-{latest}.tar.gz" print(f"Downloading {url}...") download_path = download_url(url, cwd) checksum = sha256_digest(download_path) print(f"SHA-256: {checksum}") dependencies["xz"][latest] = { - "url": "http://tukaani.org/xz/xz-{version}.tar.gz", + "url": "https://github.com/tukaani-project/xz/releases/download/v{version}/xz-{version}.tar.gz", "sha256": checksum, "platforms": ["linux", "darwin", "win32"], } diff --git a/tests/test_pyversions_runtime.py b/tests/test_pyversions_runtime.py index 52e924e7..50eebae8 100644 --- a/tests/test_pyversions_runtime.py +++ b/tests/test_pyversions_runtime.py @@ -170,24 +170,35 @@ def fake_fetch(url: str) -> str: def test_detect_xz_versions(monkeypatch: pytest.MonkeyPatch) -> None: - """Test XZ version detection from tukaani.org.""" - mock_html = """ - - xz-5.8.1.tar.gz - xz-5.8.0.tar.gz - xz-5.6.3.tar.gz - - """ + """Test XZ version detection from the tukaani-project GitHub releases API.""" + import json + + mock_releases = json.dumps( + [ + {"tag_name": "v5.8.1", "draft": False, "prerelease": False}, + {"tag_name": "v5.7.2beta", "draft": False, "prerelease": True}, + {"tag_name": "v5.8.0", "draft": False, "prerelease": False}, + {"tag_name": "v5.6.3", "draft": False, "prerelease": False}, + {"tag_name": "v5.9.0-draft", "draft": True, "prerelease": False}, + ] + ) + + captured_url: dict[str, str] = {} def fake_fetch(url: str) -> str: - return mock_html + captured_url["url"] = url + return mock_releases monkeypatch.setattr(pyversions, "fetch_url_content", fake_fetch) versions = pyversions.detect_xz_versions() + assert captured_url["url"] == "https://api.github.com/repos/tukaani-project/xz/releases" assert isinstance(versions, list) assert "5.8.1" in versions assert "5.8.0" in versions assert "5.6.3" in versions + # Draft and prerelease tags must be skipped + assert "5.7.2beta" not in versions + assert not any(v.startswith("5.9") for v in versions) # Verify sorting (latest first) assert versions[0] == "5.8.1"