diff --git a/crates/uv-client/src/html.rs b/crates/uv-client/src/html.rs index 7d73595f6..cda16dfca 100644 --- a/crates/uv-client/src/html.rs +++ b/crates/uv-client/src/html.rs @@ -2,12 +2,12 @@ use std::str::FromStr; use jiff::Timestamp; use tl::HTMLTag; -use tracing::{instrument, warn}; +use tracing::{debug, instrument, warn}; use url::Url; use uv_pep440::VersionSpecifiers; -use uv_pypi_types::LenientVersionSpecifiers; use uv_pypi_types::{BaseUrl, CoreMetadata, File, Hashes, Yanked}; +use uv_pypi_types::{HashError, LenientVersionSpecifiers}; /// A parsed structure from PyPI "HTML" index format for a single package. #[derive(Debug, Clone)] @@ -99,7 +99,24 @@ impl SimpleHtml { if fragment.trim().is_empty() { Hashes::default() } else { - Hashes::parse_fragment(&fragment)? + match Hashes::parse_fragment(&fragment) { + Ok(hashes) => hashes, + Err( + err + @ (HashError::InvalidFragment(..) | HashError::InvalidStructure(..)), + ) => { + // If the URL includes an irrelevant hash (e.g., `#main`), ignore it. + debug!("{err}"); + Hashes::default() + } + Err( + err + @ (HashError::UnsupportedHashAlgorithm(..) | HashError::NonUtf8(..)), + ) => { + // If the URL references a hash, but it's unsupported, error. + return Err(err.into()); + } + } }, ) } else { @@ -836,20 +853,61 @@ mod tests { } #[test] - fn parse_missing_hash_value() { + fn parse_unknown_fragment() { let text = r#"