mirror of
https://github.com/astral-sh/uv.git
synced 2025-08-04 10:58:28 +00:00
parent
a80d317e6b
commit
80aa03dcba
3 changed files with 139 additions and 16 deletions
|
@ -124,8 +124,7 @@ impl Default for Yanked {
|
|||
|
||||
/// A dictionary mapping a hash name to a hex encoded digest of the file.
|
||||
///
|
||||
/// PEP 691 says multiple hashes can be included and the interpretation is left to the client, we
|
||||
/// only support SHA 256 atm.
|
||||
/// PEP 691 says multiple hashes can be included and the interpretation is left to the client.
|
||||
#[derive(
|
||||
Debug,
|
||||
Clone,
|
||||
|
@ -146,20 +145,36 @@ impl Default for Yanked {
|
|||
pub struct Hashes {
|
||||
pub md5: Option<String>,
|
||||
pub sha256: Option<String>,
|
||||
pub sha384: Option<String>,
|
||||
pub sha512: Option<String>,
|
||||
}
|
||||
|
||||
impl Hashes {
|
||||
/// Format as `<algorithm>:<hash>`.
|
||||
pub fn to_string(&self) -> Option<String> {
|
||||
self.sha256
|
||||
self.sha512
|
||||
.as_ref()
|
||||
.map(|sha256| format!("sha256:{sha256}"))
|
||||
.map(|sha512| format!("sha512:{sha512}"))
|
||||
.or_else(|| {
|
||||
self.sha384
|
||||
.as_ref()
|
||||
.map(|sha384| format!("sha384:{sha384}"))
|
||||
})
|
||||
.or_else(|| {
|
||||
self.sha256
|
||||
.as_ref()
|
||||
.map(|sha256| format!("sha256:{sha256}"))
|
||||
})
|
||||
.or_else(|| self.md5.as_ref().map(|md5| format!("md5:{md5}")))
|
||||
}
|
||||
|
||||
/// Return the hash digest.
|
||||
pub fn as_str(&self) -> Option<&str> {
|
||||
self.sha256.as_deref().or(self.md5.as_deref())
|
||||
self.sha512
|
||||
.as_deref()
|
||||
.or(self.sha384.as_deref())
|
||||
.or(self.sha256.as_deref())
|
||||
.or(self.md5.as_deref())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,6 +203,8 @@ impl FromStr for Hashes {
|
|||
Ok(Hashes {
|
||||
md5: Some(md5),
|
||||
sha256: None,
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
})
|
||||
}
|
||||
"sha256" => {
|
||||
|
@ -195,6 +212,26 @@ impl FromStr for Hashes {
|
|||
Ok(Hashes {
|
||||
md5: None,
|
||||
sha256: Some(sha256),
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
})
|
||||
}
|
||||
"sha384" => {
|
||||
let sha384 = value.to_string();
|
||||
Ok(Hashes {
|
||||
md5: None,
|
||||
sha256: None,
|
||||
sha384: Some(sha384),
|
||||
sha512: None,
|
||||
})
|
||||
}
|
||||
"sha512" => {
|
||||
let sha512 = value.to_string();
|
||||
Ok(Hashes {
|
||||
md5: None,
|
||||
sha256: None,
|
||||
sha384: None,
|
||||
sha512: Some(sha512),
|
||||
})
|
||||
}
|
||||
_ => Err(HashError::UnsupportedHashAlgorithm(s.to_string())),
|
||||
|
@ -204,10 +241,12 @@ impl FromStr for Hashes {
|
|||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum HashError {
|
||||
#[error("Unexpected hash (expected `sha256:<hash>` or `md5:<hash>`) on: {0}")]
|
||||
#[error("Unexpected hash (expected `<algorithm>:<hash>`): {0}")]
|
||||
InvalidStructure(String),
|
||||
|
||||
#[error("Unsupported hash algorithm (expected `sha256` or `md5`) on: {0}")]
|
||||
#[error(
|
||||
"Unsupported hash algorithm (expected `md5`, `sha256`, `sha384`, or `sha512`) on: {0}"
|
||||
)]
|
||||
UnsupportedHashAlgorithm(String),
|
||||
}
|
||||
|
||||
|
@ -217,6 +256,34 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn parse_hashes() -> Result<(), HashError> {
|
||||
let hashes: Hashes =
|
||||
"sha512:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".parse()?;
|
||||
assert_eq!(
|
||||
hashes,
|
||||
Hashes {
|
||||
md5: None,
|
||||
sha256: None,
|
||||
sha384: None,
|
||||
sha512: Some(
|
||||
"40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".to_string()
|
||||
),
|
||||
}
|
||||
);
|
||||
|
||||
let hashes: Hashes =
|
||||
"sha384:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".parse()?;
|
||||
assert_eq!(
|
||||
hashes,
|
||||
Hashes {
|
||||
md5: None,
|
||||
sha256: None,
|
||||
sha384: Some(
|
||||
"40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".to_string()
|
||||
),
|
||||
sha512: None
|
||||
}
|
||||
);
|
||||
|
||||
let hashes: Hashes =
|
||||
"sha256:40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".parse()?;
|
||||
assert_eq!(
|
||||
|
@ -225,7 +292,9 @@ mod tests {
|
|||
md5: None,
|
||||
sha256: Some(
|
||||
"40627dcf047dadb22cd25ea7ecfe9cbf3bbbad0482ee5920b582f3809c97654f".to_string()
|
||||
)
|
||||
),
|
||||
sha384: None,
|
||||
sha512: None
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -237,7 +306,9 @@ mod tests {
|
|||
md5: Some(
|
||||
"090376d812fb6ac5f171e5938e82e7f2d7adc2b629101cec0db8b267815c85e2".to_string()
|
||||
),
|
||||
sha256: None
|
||||
sha256: None,
|
||||
sha384: None,
|
||||
sha512: None
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -245,7 +316,7 @@ mod tests {
|
|||
.parse::<Hashes>();
|
||||
assert!(result.is_err());
|
||||
|
||||
let result = "sha512:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"
|
||||
let result = "blake2:55f44b440d491028addb3b88f72207d71eeebfb7b5dbf0643f7c023ae1fba619"
|
||||
.parse::<Hashes>();
|
||||
assert!(result.is_err());
|
||||
|
||||
|
|
|
@ -537,7 +537,7 @@ impl CacheBucket {
|
|||
Self::FlatIndex => "flat-index-v0",
|
||||
Self::Git => "git-v0",
|
||||
Self::Interpreter => "interpreter-v0",
|
||||
Self::Simple => "simple-v4",
|
||||
Self::Simple => "simple-v5",
|
||||
Self::Wheels => "wheels-v0",
|
||||
Self::Archive => "archive-v0",
|
||||
}
|
||||
|
|
|
@ -93,6 +93,8 @@ impl SimpleHtml {
|
|||
Ok(Hashes {
|
||||
md5: Some(md5),
|
||||
sha256: None,
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
})
|
||||
}
|
||||
"sha256" => {
|
||||
|
@ -101,6 +103,28 @@ impl SimpleHtml {
|
|||
Ok(Hashes {
|
||||
md5: None,
|
||||
sha256: Some(sha256),
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
})
|
||||
}
|
||||
"sha384" => {
|
||||
let sha384 = std::str::from_utf8(value.as_bytes())?;
|
||||
let sha384 = sha384.to_string();
|
||||
Ok(Hashes {
|
||||
md5: None,
|
||||
sha256: None,
|
||||
sha384: Some(sha384),
|
||||
sha512: None,
|
||||
})
|
||||
}
|
||||
"sha512" => {
|
||||
let sha512 = std::str::from_utf8(value.as_bytes())?;
|
||||
let sha512 = sha512.to_string();
|
||||
Ok(Hashes {
|
||||
md5: None,
|
||||
sha256: None,
|
||||
sha384: None,
|
||||
sha512: Some(sha512),
|
||||
})
|
||||
}
|
||||
_ => Err(Error::UnsupportedHashAlgorithm(fragment.to_string())),
|
||||
|
@ -218,10 +242,12 @@ pub enum Error {
|
|||
#[error("Missing hash attribute on URL: {0}")]
|
||||
MissingHash(String),
|
||||
|
||||
#[error("Unexpected fragment (expected `#sha256=...`) on URL: {0}")]
|
||||
#[error("Unexpected fragment (expected `#sha256=...` or similar) on URL: {0}")]
|
||||
FragmentParse(String),
|
||||
|
||||
#[error("Unsupported hash algorithm (expected `sha256` or `md5`) on: {0}")]
|
||||
#[error(
|
||||
"Unsupported hash algorithm (expected `md5`, `sha256`, `sha384`, or `sha512`) on: {0}"
|
||||
)]
|
||||
UnsupportedHashAlgorithm(String),
|
||||
|
||||
#[error("Invalid `requires-python` specifier: {0}")]
|
||||
|
@ -274,6 +300,8 @@ mod tests {
|
|||
sha256: Some(
|
||||
"6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
||||
),
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
},
|
||||
requires_python: None,
|
||||
size: None,
|
||||
|
@ -328,6 +356,8 @@ mod tests {
|
|||
"6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
||||
),
|
||||
sha256: None,
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
},
|
||||
requires_python: None,
|
||||
size: None,
|
||||
|
@ -385,6 +415,8 @@ mod tests {
|
|||
sha256: Some(
|
||||
"6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
||||
),
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
},
|
||||
requires_python: None,
|
||||
size: None,
|
||||
|
@ -439,6 +471,8 @@ mod tests {
|
|||
sha256: Some(
|
||||
"6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
||||
),
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
},
|
||||
requires_python: None,
|
||||
size: None,
|
||||
|
@ -491,6 +525,8 @@ mod tests {
|
|||
hashes: Hashes {
|
||||
md5: None,
|
||||
sha256: None,
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
},
|
||||
requires_python: None,
|
||||
size: None,
|
||||
|
@ -543,6 +579,8 @@ mod tests {
|
|||
hashes: Hashes {
|
||||
md5: None,
|
||||
sha256: None,
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
},
|
||||
requires_python: None,
|
||||
size: None,
|
||||
|
@ -629,6 +667,8 @@ mod tests {
|
|||
hashes: Hashes {
|
||||
md5: None,
|
||||
sha256: None,
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
},
|
||||
requires_python: None,
|
||||
size: None,
|
||||
|
@ -655,7 +695,7 @@ mod tests {
|
|||
"#;
|
||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||
let result = SimpleHtml::parse(text, &base).unwrap_err();
|
||||
insta::assert_snapshot!(result, @"Unexpected fragment (expected `#sha256=...`) on URL: sha256");
|
||||
insta::assert_snapshot!(result, @"Unexpected fragment (expected `#sha256=...` or similar) on URL: sha256");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -665,14 +705,14 @@ mod tests {
|
|||
<html>
|
||||
<body>
|
||||
<h1>Links for jinja2</h1>
|
||||
<a href="/whl/Jinja2-3.1.2-py3-none-any.whl#sha512=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61">Jinja2-3.1.2-py3-none-any.whl</a><br/>
|
||||
<a href="/whl/Jinja2-3.1.2-py3-none-any.whl#blake2=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61">Jinja2-3.1.2-py3-none-any.whl</a><br/>
|
||||
</body>
|
||||
</html>
|
||||
<!--TIMESTAMP 1703347410-->
|
||||
"#;
|
||||
let base = Url::parse("https://download.pytorch.org/whl/jinja2/").unwrap();
|
||||
let result = SimpleHtml::parse(text, &base).unwrap_err();
|
||||
insta::assert_snapshot!(result, @"Unsupported hash algorithm (expected `sha256` or `md5`) on: sha512=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61");
|
||||
insta::assert_snapshot!(result, @"Unsupported hash algorithm (expected `md5`, `sha256`, `sha384`, or `sha512`) on: blake2=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -716,6 +756,8 @@ mod tests {
|
|||
hashes: Hashes {
|
||||
md5: None,
|
||||
sha256: None,
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
},
|
||||
requires_python: None,
|
||||
size: None,
|
||||
|
@ -729,6 +771,8 @@ mod tests {
|
|||
hashes: Hashes {
|
||||
md5: None,
|
||||
sha256: None,
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
},
|
||||
requires_python: None,
|
||||
size: None,
|
||||
|
@ -793,6 +837,8 @@ mod tests {
|
|||
sha256: Some(
|
||||
"9da884457e910bf0847d396cb4b778ad9f3c3d17db1c5997cb861937bd284237",
|
||||
),
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
},
|
||||
requires_python: None,
|
||||
size: None,
|
||||
|
@ -808,6 +854,8 @@ mod tests {
|
|||
sha256: Some(
|
||||
"4c83829ff83d408b5e1d4995472265411d2c414112298f2eb4b359d9e4563373",
|
||||
),
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
},
|
||||
requires_python: None,
|
||||
size: None,
|
||||
|
@ -823,6 +871,8 @@ mod tests {
|
|||
sha256: Some(
|
||||
"6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403",
|
||||
),
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
},
|
||||
requires_python: Some(
|
||||
Ok(
|
||||
|
@ -887,6 +937,8 @@ mod tests {
|
|||
sha256: Some(
|
||||
"6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
||||
),
|
||||
sha384: None,
|
||||
sha512: None,
|
||||
},
|
||||
requires_python: Some(
|
||||
Ok(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue