Validate required package names against wheel package names (#2516)

Closes https://github.com/astral-sh/uv/issues/2484.
This commit is contained in:
Charlie Marsh 2024-03-18 10:35:15 -07:00 committed by GitHub
parent 1911c966b5
commit 2b01d9f70b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 49 additions and 3 deletions

View file

@ -1,4 +1,5 @@
use url::Url; use url::Url;
use uv_normalize::PackageName;
#[derive(thiserror::Error, Debug)] #[derive(thiserror::Error, Debug)]
pub enum Error { pub enum Error {
@ -19,4 +20,7 @@ pub enum Error {
#[error("Unsupported scheme `{0}` on URL: {1} ({2})")] #[error("Unsupported scheme `{0}` on URL: {1} ({2})")]
UnsupportedScheme(String, String, String), UnsupportedScheme(String, String, String),
#[error("Requested package name `{0}` does not match `{1}` in the distribution filename: {2}")]
PackageNameMismatch(PackageName, PackageName, String),
} }

View file

@ -229,8 +229,18 @@ impl Dist {
.extension() .extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("whl")) .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 { Ok(Self::Built(BuiltDist::DirectUrl(DirectUrlBuiltDist {
filename: WheelFilename::from_str(&url.filename()?)?, filename,
url, url,
}))) })))
} else { } else {
@ -258,8 +268,18 @@ impl Dist {
.extension() .extension()
.is_some_and(|ext| ext.eq_ignore_ascii_case("whl")) .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 { Ok(Self::Built(BuiltDist::Path(PathBuiltDist {
filename: WheelFilename::from_str(&url.filename()?)?, filename,
url, url,
path, path,
}))) })))

View file

@ -5186,3 +5186,25 @@ fn compile_root_uri() -> Result<()> {
Ok(()) 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(())
}

View file

@ -1104,7 +1104,7 @@ fn mismatched_name() -> Result<()> {
let requirements_txt = context.temp_dir.child("requirements.txt"); let requirements_txt = context.temp_dir.child("requirements.txt");
requirements_txt.write_str(&format!( requirements_txt.write_str(&format!(
"tomli @ {}", "foo @ {}",
Url::from_file_path(archive.path()).unwrap() Url::from_file_path(archive.path()).unwrap()
))?; ))?;