checksums: add SHA-512 and a Checksums-* field parser

The checksum model only carried md5/sha1/sha256 while deb-buildinfo(5)
defines Checksums-Sha512, and there was no way to parse a Checksums-*
field body back into entries. Add ChecksumKind::Sha512 (computed
alongside the others), a field parser validating the
'<hex> <size> <name>' grammar, and an only-if-populated
Checksums-Sha512 emission in .buildinfo — deliberately dormant in the
dpkg-parity flows, which never emit it, and .dsc/.changes untouched.
This commit is contained in:
2026-09-17 19:44:54 +02:00
parent d6bad9fbbe
commit 93296c26c1
5 changed files with 364 additions and 4 deletions
+80
View File
@@ -296,6 +296,12 @@ pub fn render_buildinfo(input: &BuildInfoInput) -> Paragraph {
p.set("Checksums-Md5", &input.checksums.field_md5());
p.set("Checksums-Sha1", &input.checksums.field_sha1());
p.set("Checksums-Sha256", &input.checksums.field_sha256());
// Only-if-populated: entries merged from a `.dsc` carry no SHA-512
// (dpkg only records sha1/sha256 there), and an incomplete checksum
// list must never be rendered.
if let Some(sha512) = input.checksums.field_sha512() {
p.set("Checksums-Sha512", &sha512);
}
}
p.set("Build-Origin", &input.build_origin);
p.set("Build-Architecture", &input.build_architecture);
@@ -494,4 +500,78 @@ Architecture: amd64
);
assert_eq!(p.get("Format"), Some("1.0"));
}
/// `Checksums-Sha512` is emitted (after `Checksums-Sha256`) only when
/// every distributed file has a SHA-512 digest; entries merged without
/// one (e.g. taken from a `.dsc`) omit the field entirely instead of
/// rendering an incomplete checksum list.
#[test]
fn checksums_sha512_emitted_only_when_populated() {
let dir = tempfile::tempdir().unwrap();
let artifact = dir.path().join("hello_1.0_all.deb");
std::fs::write(&artifact, b"deb payload").unwrap();
let mut checksums = FileChecksums::new();
checksums.add_file(&artifact).unwrap();
let mk_input = |checksums: FileChecksums| BuildInfoInput {
source: "hello".to_string(),
binaries: vec![],
architecture: "all".to_string(),
version: "1.0".to_string(),
binary_only_changes: None,
build_origin: "debian".to_string(),
build_architecture: "amd64".to_string(),
build_date: "Sat, 22 Aug 2026 23:08:42 +0200".to_string(),
checksums,
installed_build_depends: String::new(),
environment: String::new(),
};
// All digests computed: the field is present and parses back.
let p = render_buildinfo(&mk_input(checksums.clone()));
let keys: Vec<&str> = p.iter().map(|(k, _)| k).collect();
assert_eq!(
keys,
vec![
"Format",
"Source",
"Architecture",
"Version",
"Checksums-Md5",
"Checksums-Sha1",
"Checksums-Sha256",
"Checksums-Sha512",
"Build-Origin",
"Build-Architecture",
"Build-Date",
]
);
let sha512_field = p.get("Checksums-Sha512").unwrap();
let parsed =
FileChecksums::parse_field(crate::debian::ChecksumKind::Sha512, sha512_field).unwrap();
assert_eq!(parsed.len(), 1);
assert_eq!(parsed[0].0, "hello_1.0_all.deb");
assert_eq!(
parsed[0].1.sha512,
checksums.get("hello_1.0_all.deb").unwrap().sha512
);
// An entry without SHA-512 (as merged from a `.dsc`) suppresses the
// field; the other Checksums fields keep listing every file.
checksums.insert_entry(
"hello_1.0.orig.tar.xz",
crate::debian::ChecksumEntry {
size: 3,
md5: checksums.get("hello_1.0_all.deb").unwrap().md5.clone(),
sha1: String::new(),
sha256: String::new(),
sha512: String::new(),
},
);
let p = render_buildinfo(&mk_input(checksums));
assert!(p.get("Checksums-Sha512").is_none());
let sha256_lines = p.get("Checksums-Sha256").unwrap().lines();
assert_eq!(sha256_lines.filter(|l| !l.is_empty()).count(), 2);
}
}