From 2b01d9f70b393a57104e34f0973b3824ed5bebdd Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Mon, 18 Mar 2024 10:35:15 -0700 Subject: [PATCH] Validate required package names against wheel package names (#2516) Closes https://github.com/astral-sh/uv/issues/2484. --- crates/distribution-types/src/error.rs | 4 ++++ crates/distribution-types/src/lib.rs | 24 ++++++++++++++++++++++-- crates/uv/tests/pip_compile.rs | 22 ++++++++++++++++++++++ crates/uv/tests/pip_sync.rs | 2 +- 4 files changed, 49 insertions(+), 3 deletions(-) diff --git a/crates/distribution-types/src/error.rs b/crates/distribution-types/src/error.rs index 1a5078460..b7bf23782 100644 --- a/crates/distribution-types/src/error.rs +++ b/crates/distribution-types/src/error.rs @@ -1,4 +1,5 @@ use url::Url; +use uv_normalize::PackageName; #[derive(thiserror::Error, Debug)] pub enum Error { @@ -19,4 +20,7 @@ pub enum Error { #[error("Unsupported scheme `{0}` on URL: {1} ({2})")] UnsupportedScheme(String, String, String), + + #[error("Requested package name `{0}` does not match `{1}` in the distribution filename: {2}")] + PackageNameMismatch(PackageName, PackageName, String), } diff --git a/crates/distribution-types/src/lib.rs b/crates/distribution-types/src/lib.rs index 06c4e9abd..92c5fb2f7 100644 --- a/crates/distribution-types/src/lib.rs +++ b/crates/distribution-types/src/lib.rs @@ -229,8 +229,18 @@ impl Dist { .extension() .is_some_and(|ext| ext.eq_ignore_ascii_case("whl")) { + // Validate that the name in the wheel matches that of the requirement. + let filename = WheelFilename::from_str(&url.filename()?)?; + if filename.name != name { + return Err(Error::PackageNameMismatch( + name, + filename.name, + url.verbatim().to_string(), + )); + } + Ok(Self::Built(BuiltDist::DirectUrl(DirectUrlBuiltDist { - filename: WheelFilename::from_str(&url.filename()?)?, + filename, url, }))) } else { @@ -258,8 +268,18 @@ impl Dist { .extension() .is_some_and(|ext| ext.eq_ignore_ascii_case("whl")) { + // Validate that the name in the wheel matches that of the requirement. + let filename = WheelFilename::from_str(&url.filename()?)?; + if filename.name != name { + return Err(Error::PackageNameMismatch( + name, + filename.name, + url.verbatim().to_string(), + )); + } + Ok(Self::Built(BuiltDist::Path(PathBuiltDist { - filename: WheelFilename::from_str(&url.filename()?)?, + filename, url, path, }))) diff --git a/crates/uv/tests/pip_compile.rs b/crates/uv/tests/pip_compile.rs index 28b40f219..464cac9c3 100644 --- a/crates/uv/tests/pip_compile.rs +++ b/crates/uv/tests/pip_compile.rs @@ -5186,3 +5186,25 @@ fn compile_root_uri() -> Result<()> { Ok(()) } + +/// Request a local wheel with a mismatched package name. +#[test] +fn requirement_wheel_name_mismatch() -> Result<()> { + let context = TestContext::new("3.12"); + + let requirements_in = context.temp_dir.child("requirements.in"); + requirements_in.write_str("dateutil @ https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl")?; + + uv_snapshot!(context.compile() + .arg("requirements.in"), @r###" + success: false + exit_code: 2 + ----- stdout ----- + + ----- stderr ----- + error: Requested package name `dateutil` does not match `python-dateutil` in the distribution filename: https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl + "### + ); + + Ok(()) +} diff --git a/crates/uv/tests/pip_sync.rs b/crates/uv/tests/pip_sync.rs index 539ebf479..b5de8f21c 100644 --- a/crates/uv/tests/pip_sync.rs +++ b/crates/uv/tests/pip_sync.rs @@ -1104,7 +1104,7 @@ fn mismatched_name() -> Result<()> { let requirements_txt = context.temp_dir.child("requirements.txt"); requirements_txt.write_str(&format!( - "tomli @ {}", + "foo @ {}", Url::from_file_path(archive.path()).unwrap() ))?;