feat(publish): include blake2b hash in upload form (#15794)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / lint (push) Waiting to run
CI / cargo clippy | ubuntu (push) Blocked by required conditions
CI / cargo clippy | windows (push) Blocked by required conditions
CI / cargo dev generate-all (push) Blocked by required conditions
CI / cargo shear (push) Waiting to run
CI / cargo test | ubuntu (push) Blocked by required conditions
CI / cargo test | macos (push) Blocked by required conditions
CI / cargo test | windows (push) Blocked by required conditions
CI / check windows trampoline | aarch64 (push) Blocked by required conditions
CI / check windows trampoline | i686 (push) Blocked by required conditions
CI / check windows trampoline | x86_64 (push) Blocked by required conditions
CI / smoke test | macos (push) Blocked by required conditions
CI / test windows trampoline | aarch64 (push) Blocked by required conditions
CI / test windows trampoline | i686 (push) Blocked by required conditions
CI / test windows trampoline | x86_64 (push) Blocked by required conditions
CI / typos (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / build binary | linux libc (push) Blocked by required conditions
CI / build binary | linux aarch64 (push) Blocked by required conditions
CI / build binary | linux musl (push) Blocked by required conditions
CI / build binary | macos aarch64 (push) Blocked by required conditions
CI / build binary | macos x86_64 (push) Blocked by required conditions
CI / build binary | freebsd (push) Blocked by required conditions
CI / ecosystem test | pydantic/pydantic-core (push) Blocked by required conditions
CI / ecosystem test | prefecthq/prefect (push) Blocked by required conditions
CI / ecosystem test | pallets/flask (push) Blocked by required conditions
CI / smoke test | linux (push) Blocked by required conditions
CI / smoke test | linux aarch64 (push) Blocked by required conditions
CI / check system | alpine (push) Blocked by required conditions
CI / smoke test | windows x86_64 (push) Blocked by required conditions
CI / integration test | activate nushell venv (push) Blocked by required conditions
CI / integration test | conda on ubuntu (push) Blocked by required conditions
CI / integration test | free-threaded on windows (push) Blocked by required conditions
CI / integration test | aarch64 windows implicit (push) Blocked by required conditions
CI / integration test | aarch64 windows explicit (push) Blocked by required conditions
CI / integration test | pypy on ubuntu (push) Blocked by required conditions
CI / integration test | pypy on windows (push) Blocked by required conditions
CI / integration test | graalpy on ubuntu (push) Blocked by required conditions
CI / integration test | graalpy on windows (push) Blocked by required conditions
CI / integration test | pyodide on ubuntu (push) Blocked by required conditions
CI / integration test | github actions (push) Blocked by required conditions
CI / integration test | free-threaded python on github actions (push) Blocked by required conditions
CI / integration test | pyenv on wsl x86-64 (push) Blocked by required conditions
CI / integration test | determine publish changes (push) Blocked by required conditions
CI / integration test | registries (push) Blocked by required conditions
CI / build binary | windows x86_64 (push) Blocked by required conditions
CI / build binary | windows aarch64 (push) Blocked by required conditions
CI / build binary | msrv (push) Blocked by required conditions
CI / smoke test | windows aarch64 (push) Blocked by required conditions
CI / integration test | deadsnakes python3.9 on ubuntu (push) Blocked by required conditions
CI / check system | python on ubuntu (push) Blocked by required conditions
CI / integration test | uv publish (push) Blocked by required conditions
CI / integration test | uv_build (push) Blocked by required conditions
CI / check cache | ubuntu (push) Blocked by required conditions
CI / check cache | macos aarch64 (push) Blocked by required conditions
CI / check system | python on debian (push) Blocked by required conditions
CI / check system | python on fedora (push) Blocked by required conditions
CI / check system | python on rocky linux 8 (push) Blocked by required conditions
CI / check system | python on rocky linux 9 (push) Blocked by required conditions
CI / check system | graalpy on ubuntu (push) Blocked by required conditions
CI / check system | pypy on ubuntu (push) Blocked by required conditions
CI / check system | pyston (push) Blocked by required conditions
CI / check system | python on macos aarch64 (push) Blocked by required conditions
CI / check system | homebrew python on macos aarch64 (push) Blocked by required conditions
CI / check system | x86-64 python on macos aarch64 (push) Blocked by required conditions
CI / check system | python on macos x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86-64 (push) Blocked by required conditions
CI / check system | python3.10 on windows x86 (push) Blocked by required conditions
CI / check system | python3.13 on windows x86-64 (push) Blocked by required conditions
CI / check system | x86-64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | aarch64 python3.13 on windows aarch64 (push) Blocked by required conditions
CI / check system | windows registry (push) Blocked by required conditions
CI / check system | python3.12 via chocolatey (push) Blocked by required conditions
CI / check system | python3.9 via pyenv (push) Blocked by required conditions
CI / check system | python3.13 (push) Blocked by required conditions
CI / check system | conda3.11 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.8 on macos aarch64 (push) Blocked by required conditions
CI / check system | conda3.11 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on linux x86-64 (push) Blocked by required conditions
CI / check system | conda3.11 on windows x86-64 (push) Blocked by required conditions
CI / check system | conda3.8 on windows x86-64 (push) Blocked by required conditions
CI / check system | amazonlinux (push) Blocked by required conditions
CI / check system | embedded python3.10 on windows x86-64 (push) Blocked by required conditions
CI / benchmarks | walltime aarch64 linux (push) Blocked by required conditions
CI / benchmarks | instrumented (push) Blocked by required conditions
zizmor / Run zizmor (push) Waiting to run

This commit is contained in:
William Woodruff 2025-09-11 16:17:07 -04:00 committed by GitHub
parent a0f8359012
commit 21a92c1632
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -584,14 +584,14 @@ pub async fn check_url(
if let Some(remote_hash) = archived_file.hashes.first() {
// We accept the risk for TOCTOU errors here, since we already read the file once before the
// streaming upload to compute the hash for the form metadata.
let local_hash = hash_file(file, Hasher::from(remote_hash.algorithm))
let local_hash = &hash_file(file, vec![Hasher::from(remote_hash.algorithm)])
.await
.map_err(|err| {
PublishError::PublishPrepare(
file.to_path_buf(),
Box::new(PublishPrepareError::Io(err)),
)
})?;
})?[0];
if local_hash.digest == remote_hash.digest {
debug!(
"Found {filename} in the registry with matching hash {}",
@ -611,13 +611,20 @@ pub async fn check_url(
}
}
/// Calculate the SHA256 of a file.
async fn hash_file(path: impl AsRef<Path>, hasher: Hasher) -> Result<HashDigest, io::Error> {
/// Calculate the requested hashes of a file.
async fn hash_file(
path: impl AsRef<Path>,
hashers: Vec<Hasher>,
) -> Result<Vec<HashDigest>, io::Error> {
debug!("Hashing {}", path.as_ref().display());
let file = BufReader::new(File::open(path.as_ref()).await?);
let mut hashers = vec![hasher];
let mut hashers = hashers;
HashReader::new(file, &mut hashers).finish().await?;
Ok(HashDigest::from(hashers.remove(0)))
Ok(hashers
.into_iter()
.map(HashDigest::from)
.collect::<Vec<_>>())
}
// Not in `uv-metadata` because we only support tar files here.
@ -693,7 +700,24 @@ impl FormMetadata {
file: &Path,
filename: &DistFilename,
) -> Result<Self, PublishPrepareError> {
let hash_hex = hash_file(file, Hasher::from(HashAlgorithm::Sha256)).await?;
let hashes = hash_file(
file,
vec![
Hasher::from(HashAlgorithm::Sha256),
Hasher::from(HashAlgorithm::Blake2b),
],
)
.await?;
let sha256_hash = hashes
.iter()
.find(|hash| hash.algorithm == HashAlgorithm::Sha256)
.unwrap();
let blake2b_hash = hashes
.iter()
.find(|hash| hash.algorithm == HashAlgorithm::Blake2b)
.unwrap();
let Metadata23 {
metadata_version,
@ -728,7 +752,8 @@ impl FormMetadata {
let mut form_metadata = vec![
(":action", "file_upload".to_string()),
("sha256_digest", hash_hex.digest.to_string()),
("sha256_digest", sha256_hash.digest.to_string()),
("blake2_256_digest", blake2b_hash.digest.to_string()),
("protocol_version", "1".to_string()),
("metadata_version", metadata_version.clone()),
// Twine transforms the name with `re.sub("[^A-Za-z0-9.]+", "-", name)`
@ -1040,9 +1065,10 @@ mod tests {
.iter()
.map(|(k, v)| format!("{k}: {v}"))
.join("\n");
assert_snapshot!(&formatted_metadata, @r###"
assert_snapshot!(&formatted_metadata, @r"
:action: file_upload
sha256_digest: 89fa05cffa7f457658373b85de302d24d0c205ceda2819a8739e324b75e9430b
blake2_256_digest: 40ab79b48c4e289e4990f7e689177adae4096c07a634034eb1d10c0b6700e4d2
protocol_version: 1
metadata_version: 2.3
name: tqdm
@ -1088,7 +1114,7 @@ mod tests {
project_urls: Documentation, https://github.com/unknown/tqdm#readme
project_urls: Issues, https://github.com/unknown/tqdm/issues
project_urls: Source, https://github.com/unknown/tqdm
"###);
");
let client = BaseClientBuilder::default().build();
let (request, _) = build_upload_request(
@ -1128,7 +1154,7 @@ mod tests {
},
headers: {
"content-type": "multipart/form-data; boundary=[...]",
"content-length": "6803",
"content-length": "7000",
"accept": "application/json;q=0.9, text/plain;q=0.8, text/html;q=0.7",
"authorization": Sensitive,
},
@ -1154,9 +1180,10 @@ mod tests {
.iter()
.map(|(k, v)| format!("{k}: {v}"))
.join("\n");
assert_snapshot!(&formatted_metadata, @r###"
assert_snapshot!(&formatted_metadata, @r#"
:action: file_upload
sha256_digest: 0d88ca657bc6b64995ca416e0c59c71af85cc10015d940fa446c42a8b485ee1c
blake2_256_digest: 33d4e92517a16e3fa0c0893de0c7e4d46a2c38adab148dd2ff66eb47481d19cd
protocol_version: 1
metadata_version: 2.1
name: tqdm
@ -1240,7 +1267,7 @@ mod tests {
requires_dist: ipywidgets >=6 ; extra == 'notebook'
requires_dist: slack-sdk ; extra == 'slack'
requires_dist: requests ; extra == 'telegram'
"###);
"#);
let client = BaseClientBuilder::default().build();
let (request, _) = build_upload_request(
@ -1280,7 +1307,7 @@ mod tests {
},
headers: {
"content-type": "multipart/form-data; boundary=[...]",
"content-length": "19330",
"content-length": "19527",
"accept": "application/json;q=0.9, text/plain;q=0.8, text/html;q=0.7",
"authorization": Sensitive,
},