mirror of https://github.com/astral-sh/uv
Check dist name to handle bogus redirect (#12917)
When an index performs a bogus redirect or otherwise returns a different distribution name than expected, uv currently hangs. In the example case, requesting the simple index page for any package returns the page for anyio. This mean querying the sniffio version map returns only anyio entries, and the version maps resolves to an anyio version. When the resolver makes a query for sniffio and waits for it to resolve, the main thread finds an anyio and resolves only that in the wait map, causing the hang. We fix this by checking the name of the returned distribution against the name of the requested distribution. For good measure, we add the same check in `Request::Dist` and `Request::Installed`. For performance and complexity reasons, we don't perform this check in the version map itself, but only after a candidate distribution has been selected. --------- Co-authored-by: Zanie Blue <contact@zanie.dev>
This commit is contained in:
parent
45910eb6d1
commit
473d7c75a4
|
|
@ -124,6 +124,12 @@ pub enum ResolveError {
|
|||
#[source]
|
||||
name_error: InvalidNameError,
|
||||
},
|
||||
#[error("The index returned metadata for the wrong package: expected {request} for {expected}, got {request} for {actual}")]
|
||||
MismatchedPackageName {
|
||||
request: &'static str,
|
||||
expected: PackageName,
|
||||
actual: PackageName,
|
||||
},
|
||||
}
|
||||
|
||||
impl<T> From<tokio::sync::mpsc::error::SendError<T>> for ResolveError {
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ use uv_distribution::DistributionDatabase;
|
|||
use uv_distribution_types::{
|
||||
BuiltDist, CompatibleDist, DerivationChain, Dist, DistErrorKind, DistributionMetadata,
|
||||
IncompatibleDist, IncompatibleSource, IncompatibleWheel, IndexCapabilities, IndexLocations,
|
||||
IndexMetadata, IndexUrl, InstalledDist, PythonRequirementKind, RemoteSource, Requirement,
|
||||
IndexMetadata, IndexUrl, InstalledDist, Name, PythonRequirementKind, RemoteSource, Requirement,
|
||||
ResolvedDist, ResolvedDistRef, SourceDist, VersionOrUrlRef,
|
||||
};
|
||||
use uv_git::GitResolver;
|
||||
|
|
@ -2261,12 +2261,32 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
.boxed_local()
|
||||
.await?;
|
||||
|
||||
if let MetadataResponse::Found(metadata) = &metadata {
|
||||
if &metadata.metadata.name != dist.name() {
|
||||
return Err(ResolveError::MismatchedPackageName {
|
||||
request: "distribution metadata",
|
||||
expected: dist.name().clone(),
|
||||
actual: metadata.metadata.name.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Response::Dist { dist, metadata }))
|
||||
}
|
||||
|
||||
Request::Installed(dist) => {
|
||||
let metadata = provider.get_installed_metadata(&dist).boxed_local().await?;
|
||||
|
||||
if let MetadataResponse::Found(metadata) = &metadata {
|
||||
if &metadata.metadata.name != dist.name() {
|
||||
return Err(ResolveError::MismatchedPackageName {
|
||||
request: "installed metadata",
|
||||
expected: dist.name().clone(),
|
||||
actual: metadata.metadata.name.clone(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Some(Response::Installed { dist, metadata }))
|
||||
}
|
||||
|
||||
|
|
@ -2369,6 +2389,13 @@ impl<InstalledPackages: InstalledPackagesProvider> ResolverState<InstalledPackag
|
|||
// Emit a request to fetch the metadata for this version.
|
||||
if self.index.distributions().register(candidate.version_id()) {
|
||||
let dist = dist.for_resolution().to_owned();
|
||||
if &package_name != dist.name() {
|
||||
return Err(ResolveError::MismatchedPackageName {
|
||||
request: "distribution",
|
||||
expected: package_name,
|
||||
actual: dist.name().clone(),
|
||||
});
|
||||
}
|
||||
|
||||
let response = match dist {
|
||||
ResolvedDist::Installable { dist, .. } => {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ use wiremock::{
|
|||
|
||||
#[cfg(feature = "git")]
|
||||
use crate::common::{self, decode_token};
|
||||
|
||||
use crate::common::{
|
||||
build_vendor_links_url, download_to_disk, get_bin, uv_snapshot, venv_bin_path,
|
||||
venv_to_interpreter, TestContext,
|
||||
|
|
@ -11108,3 +11107,37 @@ fn pep_751_multiple_sources() -> Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Test that uv doesn't hang if an index returns a distribution for the wrong package.
|
||||
#[tokio::test]
|
||||
async fn bogus_redirect() -> Result<()> {
|
||||
let context = TestContext::new("3.12");
|
||||
|
||||
let redirect_server = MockServer::start().await;
|
||||
|
||||
// Configure a bogus redirect where for all packages, anyio is returned.
|
||||
Mock::given(method("GET"))
|
||||
.respond_with(
|
||||
ResponseTemplate::new(302).insert_header("Location", "https://pypi.org/simple/anyio/"),
|
||||
)
|
||||
.mount(&redirect_server)
|
||||
.await;
|
||||
|
||||
uv_snapshot!(
|
||||
context
|
||||
.pip_install()
|
||||
.arg("--default-index")
|
||||
.arg(redirect_server.uri())
|
||||
.arg("sniffio"),
|
||||
@r"
|
||||
success: false
|
||||
exit_code: 2
|
||||
----- stdout -----
|
||||
|
||||
----- stderr -----
|
||||
error: The index returned metadata for the wrong package: expected distribution for sniffio, got distribution for anyio
|
||||
"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue