During lockfile validation, check for URL match regardless of trailing slash

This commit is contained in:
John Mumm 2025-07-08 14:56:19 +02:00
parent c5cd30d2bb
commit 08025b0967
No known key found for this signature in database
GPG Key ID: 73D2271AFDC26EA8
2 changed files with 7 additions and 62 deletions

View File

@ -169,26 +169,6 @@ impl UrlString {
.map(|(path, _)| Cow::Owned(UrlString(SmallString::from(path))))
.unwrap_or(Cow::Borrowed(self))
}
/// Return the [`UrlString`] (as a [`Cow`]) with trailing slash removed.
///
/// This matches the semantics of [`Url::pop_if_empty`], which will not trim a trailing slash if
/// it's the only path segment, e.g., `https://example.com/` would be unchanged.
#[must_use]
pub fn without_trailing_slash(&self) -> Cow<'_, Self> {
self.as_ref()
.strip_suffix('/')
.filter(|path| {
// Only strip the trailing slash if there's _another_ trailing slash that isn't a
// part of the scheme.
path.split_once("://")
.map(|(_scheme, rest)| rest)
.unwrap_or(path)
.contains('/')
})
.map(|path| Cow::Owned(UrlString(SmallString::from(path))))
.unwrap_or(Cow::Borrowed(self))
}
}
impl AsRef<str> for UrlString {
@ -283,38 +263,4 @@ mod tests {
);
assert!(matches!(url.without_fragment(), Cow::Owned(_)));
}
#[test]
fn without_trailing_slash() {
// Borrows a URL without a slash
let url = UrlString("https://example.com/path".into());
assert_eq!(&*url.without_trailing_slash(), &url);
assert!(matches!(url.without_trailing_slash(), Cow::Borrowed(_)));
// Removes the trailing slash if present on the URL
let url = UrlString("https://example.com/path/".into());
assert_eq!(
&*url.without_trailing_slash(),
&UrlString("https://example.com/path".into())
);
assert!(matches!(url.without_trailing_slash(), Cow::Owned(_)));
// Does not remove a trailing slash if it's the only path segment
let url = UrlString("https://example.com/".into());
assert_eq!(&*url.without_trailing_slash(), &url);
assert!(matches!(url.without_trailing_slash(), Cow::Borrowed(_)));
// Does not remove a trailing slash if it's the only path segment with a missing scheme
let url = UrlString("example.com/".into());
assert_eq!(&*url.without_trailing_slash(), &url);
assert!(matches!(url.without_trailing_slash(), Cow::Borrowed(_)));
// Removes the trailing slash when the scheme is missing
let url = UrlString("example.com/path/".into());
assert_eq!(
&*url.without_trailing_slash(),
&UrlString("example.com/path".into())
);
assert!(matches!(url.without_trailing_slash(), Cow::Owned(_)));
}
}

View File

@ -1455,7 +1455,7 @@ impl Lock {
Some(path)
}
})
.collect::<BTreeSet<_>>()
.collect::<Vec<_>>()
});
// Add the workspace packages to the queue.
@ -1478,12 +1478,11 @@ impl Lock {
if let Source::Registry(index) = &package.id.source {
match index {
RegistrySource::Url(url) => {
// Normalize URL before validating.
let url = url.without_trailing_slash();
if remotes
.as_ref()
.is_some_and(|remotes| !remotes.contains(&url))
{
if remotes.as_ref().is_some_and(|remotes| {
!remotes.iter().any(|remote| {
matches!(url.as_ref().strip_prefix(remote.as_ref()), Some("" | "/"))
})
}) {
let name = &package.id.name;
let version = &package
.id
@ -1493,7 +1492,7 @@ impl Lock {
return Ok(SatisfiesResult::MissingRemoteIndex(
name,
version,
url.into_owned(),
url.clone(),
));
}
}