mirror of
https://github.com/astral-sh/uv.git
synced 2025-09-04 09:40:39 +00:00
Support MD5 hashes (#1556)
## Summary We can add other hashes if necessary, but I don't know that they're really used in practice. Closes https://github.com/astral-sh/uv/issues/1547.
This commit is contained in:
parent
9e0336c28a
commit
c1eb6130e1
4 changed files with 95 additions and 15 deletions
|
@ -678,7 +678,7 @@ impl Identifier for Url {
|
||||||
|
|
||||||
impl Identifier for File {
|
impl Identifier for File {
|
||||||
fn distribution_id(&self) -> DistributionId {
|
fn distribution_id(&self) -> DistributionId {
|
||||||
if let Some(hash) = &self.hashes.sha256 {
|
if let Some(hash) = self.hashes.as_str() {
|
||||||
DistributionId::new(hash)
|
DistributionId::new(hash)
|
||||||
} else {
|
} else {
|
||||||
self.url.distribution_id()
|
self.url.distribution_id()
|
||||||
|
@ -686,7 +686,7 @@ impl Identifier for File {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resource_id(&self) -> ResourceId {
|
fn resource_id(&self) -> ResourceId {
|
||||||
if let Some(hash) = &self.hashes.sha256 {
|
if let Some(hash) = self.hashes.as_str() {
|
||||||
ResourceId::new(hash)
|
ResourceId::new(hash)
|
||||||
} else {
|
} else {
|
||||||
self.url.resource_id()
|
self.url.resource_id()
|
||||||
|
|
|
@ -136,16 +136,21 @@ impl Default for Yanked {
|
||||||
#[archive(check_bytes)]
|
#[archive(check_bytes)]
|
||||||
#[archive_attr(derive(Debug))]
|
#[archive_attr(derive(Debug))]
|
||||||
pub struct Hashes {
|
pub struct Hashes {
|
||||||
|
pub md5: Option<String>,
|
||||||
pub sha256: Option<String>,
|
pub sha256: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hashes {
|
impl Hashes {
|
||||||
/// Format as `<algorithm>:<hash>`.
|
/// Format as `<algorithm>:<hash>`.
|
||||||
///
|
|
||||||
/// Currently limited to SHA256.
|
|
||||||
pub fn to_string(&self) -> Option<String> {
|
pub fn to_string(&self) -> Option<String> {
|
||||||
self.sha256
|
self.sha256
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|sha256| format!("sha256:{sha256}"))
|
.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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -228,7 +228,7 @@ impl<'a> FlatIndexClient<'a> {
|
||||||
let file = File {
|
let file = File {
|
||||||
dist_info_metadata: None,
|
dist_info_metadata: None,
|
||||||
filename: filename.to_string(),
|
filename: filename.to_string(),
|
||||||
hashes: Hashes { sha256: None },
|
hashes: Hashes::default(),
|
||||||
requires_python: None,
|
requires_python: None,
|
||||||
size: None,
|
size: None,
|
||||||
upload_time_utc_ms: None,
|
upload_time_utc_ms: None,
|
||||||
|
|
|
@ -86,17 +86,26 @@ impl SimpleHtml {
|
||||||
return Err(Error::FragmentParse(fragment.to_string()));
|
return Err(Error::FragmentParse(fragment.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(charlie): Support all hash algorithms.
|
match name {
|
||||||
if name != "sha256" {
|
"md5" => {
|
||||||
return Err(Error::UnsupportedHashAlgorithm(fragment.to_string()));
|
let md5 = std::str::from_utf8(value.as_bytes())?;
|
||||||
|
let md5 = md5.to_string();
|
||||||
|
Ok(Hashes {
|
||||||
|
md5: Some(md5),
|
||||||
|
sha256: None,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
"sha256" => {
|
||||||
let sha256 = std::str::from_utf8(value.as_bytes())?;
|
let sha256 = std::str::from_utf8(value.as_bytes())?;
|
||||||
let sha256 = sha256.to_string();
|
let sha256 = sha256.to_string();
|
||||||
Ok(Hashes {
|
Ok(Hashes {
|
||||||
|
md5: None,
|
||||||
sha256: Some(sha256),
|
sha256: Some(sha256),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
_ => Err(Error::UnsupportedHashAlgorithm(fragment.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Parse a [`File`] from an `<a>` tag.
|
/// Parse a [`File`] from an `<a>` tag.
|
||||||
fn parse_anchor(link: &HTMLTag) -> Result<File, Error> {
|
fn parse_anchor(link: &HTMLTag) -> Result<File, Error> {
|
||||||
|
@ -224,7 +233,7 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_file() {
|
fn parse_sha256() {
|
||||||
let text = r#"
|
let text = r#"
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
@ -261,6 +270,7 @@ mod tests {
|
||||||
dist_info_metadata: None,
|
dist_info_metadata: None,
|
||||||
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||||
hashes: Hashes {
|
hashes: Hashes {
|
||||||
|
md5: None,
|
||||||
sha256: Some(
|
sha256: Some(
|
||||||
"6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
"6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
||||||
),
|
),
|
||||||
|
@ -276,6 +286,60 @@ mod tests {
|
||||||
"###);
|
"###);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_md5() {
|
||||||
|
let text = r#"
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<h1>Links for jinja2</h1>
|
||||||
|
<a href="/whl/Jinja2-3.1.2-py3-none-any.whl#md5=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();
|
||||||
|
insta::assert_debug_snapshot!(result, @r###"
|
||||||
|
SimpleHtml {
|
||||||
|
base: BaseUrl(
|
||||||
|
Url {
|
||||||
|
scheme: "https",
|
||||||
|
cannot_be_a_base: false,
|
||||||
|
username: "",
|
||||||
|
password: None,
|
||||||
|
host: Some(
|
||||||
|
Domain(
|
||||||
|
"download.pytorch.org",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
port: None,
|
||||||
|
path: "/whl/jinja2/",
|
||||||
|
query: None,
|
||||||
|
fragment: None,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
files: [
|
||||||
|
File {
|
||||||
|
dist_info_metadata: None,
|
||||||
|
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||||
|
hashes: Hashes {
|
||||||
|
md5: Some(
|
||||||
|
"6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
||||||
|
),
|
||||||
|
sha256: None,
|
||||||
|
},
|
||||||
|
requires_python: None,
|
||||||
|
size: None,
|
||||||
|
upload_time: None,
|
||||||
|
url: "/whl/Jinja2-3.1.2-py3-none-any.whl#md5=6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
||||||
|
yanked: None,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
"###);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_base() {
|
fn parse_base() {
|
||||||
let text = r#"
|
let text = r#"
|
||||||
|
@ -317,6 +381,7 @@ mod tests {
|
||||||
dist_info_metadata: None,
|
dist_info_metadata: None,
|
||||||
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||||
hashes: Hashes {
|
hashes: Hashes {
|
||||||
|
md5: None,
|
||||||
sha256: Some(
|
sha256: Some(
|
||||||
"6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
"6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
||||||
),
|
),
|
||||||
|
@ -370,6 +435,7 @@ mod tests {
|
||||||
dist_info_metadata: None,
|
dist_info_metadata: None,
|
||||||
filename: "Jinja2-3.1.2+233fca715f49-py3-none-any.whl",
|
filename: "Jinja2-3.1.2+233fca715f49-py3-none-any.whl",
|
||||||
hashes: Hashes {
|
hashes: Hashes {
|
||||||
|
md5: None,
|
||||||
sha256: Some(
|
sha256: Some(
|
||||||
"6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
"6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
||||||
),
|
),
|
||||||
|
@ -423,6 +489,7 @@ mod tests {
|
||||||
dist_info_metadata: None,
|
dist_info_metadata: None,
|
||||||
filename: "torchtext-0.17.0+cpu-cp39-cp39-win_amd64.whl",
|
filename: "torchtext-0.17.0+cpu-cp39-cp39-win_amd64.whl",
|
||||||
hashes: Hashes {
|
hashes: Hashes {
|
||||||
|
md5: None,
|
||||||
sha256: None,
|
sha256: None,
|
||||||
},
|
},
|
||||||
requires_python: None,
|
requires_python: None,
|
||||||
|
@ -474,6 +541,7 @@ mod tests {
|
||||||
dist_info_metadata: None,
|
dist_info_metadata: None,
|
||||||
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||||
hashes: Hashes {
|
hashes: Hashes {
|
||||||
|
md5: None,
|
||||||
sha256: None,
|
sha256: None,
|
||||||
},
|
},
|
||||||
requires_python: None,
|
requires_python: None,
|
||||||
|
@ -559,6 +627,7 @@ mod tests {
|
||||||
dist_info_metadata: None,
|
dist_info_metadata: None,
|
||||||
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||||
hashes: Hashes {
|
hashes: Hashes {
|
||||||
|
md5: None,
|
||||||
sha256: None,
|
sha256: None,
|
||||||
},
|
},
|
||||||
requires_python: None,
|
requires_python: None,
|
||||||
|
@ -645,6 +714,7 @@ mod tests {
|
||||||
dist_info_metadata: None,
|
dist_info_metadata: None,
|
||||||
filename: "jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl",
|
filename: "jaxlib-0.1.52+cuda100-cp36-none-manylinux2010_x86_64.whl",
|
||||||
hashes: Hashes {
|
hashes: Hashes {
|
||||||
|
md5: None,
|
||||||
sha256: None,
|
sha256: None,
|
||||||
},
|
},
|
||||||
requires_python: None,
|
requires_python: None,
|
||||||
|
@ -657,6 +727,7 @@ mod tests {
|
||||||
dist_info_metadata: None,
|
dist_info_metadata: None,
|
||||||
filename: "jaxlib-0.1.52+cuda100-cp37-none-manylinux2010_x86_64.whl",
|
filename: "jaxlib-0.1.52+cuda100-cp37-none-manylinux2010_x86_64.whl",
|
||||||
hashes: Hashes {
|
hashes: Hashes {
|
||||||
|
md5: None,
|
||||||
sha256: None,
|
sha256: None,
|
||||||
},
|
},
|
||||||
requires_python: None,
|
requires_python: None,
|
||||||
|
@ -718,6 +789,7 @@ mod tests {
|
||||||
dist_info_metadata: None,
|
dist_info_metadata: None,
|
||||||
filename: "Flask-0.1.tar.gz",
|
filename: "Flask-0.1.tar.gz",
|
||||||
hashes: Hashes {
|
hashes: Hashes {
|
||||||
|
md5: None,
|
||||||
sha256: Some(
|
sha256: Some(
|
||||||
"9da884457e910bf0847d396cb4b778ad9f3c3d17db1c5997cb861937bd284237",
|
"9da884457e910bf0847d396cb4b778ad9f3c3d17db1c5997cb861937bd284237",
|
||||||
),
|
),
|
||||||
|
@ -732,6 +804,7 @@ mod tests {
|
||||||
dist_info_metadata: None,
|
dist_info_metadata: None,
|
||||||
filename: "Flask-0.10.1.tar.gz",
|
filename: "Flask-0.10.1.tar.gz",
|
||||||
hashes: Hashes {
|
hashes: Hashes {
|
||||||
|
md5: None,
|
||||||
sha256: Some(
|
sha256: Some(
|
||||||
"4c83829ff83d408b5e1d4995472265411d2c414112298f2eb4b359d9e4563373",
|
"4c83829ff83d408b5e1d4995472265411d2c414112298f2eb4b359d9e4563373",
|
||||||
),
|
),
|
||||||
|
@ -746,6 +819,7 @@ mod tests {
|
||||||
dist_info_metadata: None,
|
dist_info_metadata: None,
|
||||||
filename: "flask-3.0.1.tar.gz",
|
filename: "flask-3.0.1.tar.gz",
|
||||||
hashes: Hashes {
|
hashes: Hashes {
|
||||||
|
md5: None,
|
||||||
sha256: Some(
|
sha256: Some(
|
||||||
"6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403",
|
"6489f51bb3666def6f314e15f19d50a1869a19ae0e8c9a3641ffe66c77d42403",
|
||||||
),
|
),
|
||||||
|
@ -809,6 +883,7 @@ mod tests {
|
||||||
dist_info_metadata: None,
|
dist_info_metadata: None,
|
||||||
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
filename: "Jinja2-3.1.2-py3-none-any.whl",
|
||||||
hashes: Hashes {
|
hashes: Hashes {
|
||||||
|
md5: None,
|
||||||
sha256: Some(
|
sha256: Some(
|
||||||
"6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
"6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61",
|
||||||
),
|
),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue