From e1668d5d80b2fcc655a89ff36bb03c2a5b2ad6be Mon Sep 17 00:00:00 2001 From: Valentin Haudiquet Date: Fri, 24 Jul 2026 12:56:31 +0200 Subject: [PATCH] pull: support Sources.gz without Checksums-Sha256 The Ubuntu development series (stonking) Sources.gz no longer ships a Checksums-Sha256 field, only Checksums-Sha512 and the legacy Files (MD5) field. The package_info parser only read Checksums-Sha256, so the file list ended up empty and fetch_orig_tarball panicked on Option::unwrap() when looking for the orig tarball. - Add a ChecksumAlgo enum (Md5/Sha256/Sha512) to FileEntry, replacing the hardcoded sha256 field, and parse the strongest available checksum field (Sha256 > Sha512 > MD5). - Make download_file_checksum verify against the correct algorithm instead of always using SHA-256. - Replace the unwrap() on the orig tarball search with a proper error listing the available files, so future regressions fail clearly instead of panicking. - Add md-5 dependency for MD5 verification. --- Cargo.toml | 1 + src/package_info.rs | 61 +++++++++++++++++++++++++++++++++++++++++---- src/pull.rs | 41 ++++++++++++++++++++++-------- 3 files changed, 88 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 064a6e6..1076d43 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ regex = "1" chrono = "0.4" tokio = { version = "1.41.1", features = ["full"] } sha2 = "0.10.8" +md-5 = "0.10" hex = "0.4.3" log = "0.4.28" indicatif = "0.18.3" diff --git a/src/package_info.rs b/src/package_info.rs index fbcf584..d410fc4 100644 --- a/src/package_info.rs +++ b/src/package_info.rs @@ -39,8 +39,47 @@ pub struct FileEntry { pub name: String, /// Size of the file pub size: u64, - /// SHA256 hash for the file - pub sha256: String, + /// Checksum hash for the file + pub checksum: String, + /// Algorithm used for the checksum + pub checksum_algo: ChecksumAlgo, +} + +/// Checksum algorithm used for a file entry +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum ChecksumAlgo { + /// MD5 (legacy 'Files' field) + Md5, + /// SHA-256 ('Checksums-Sha256' field) + Sha256, + /// SHA-512 ('Checksums-Sha512' field) + Sha512, +} + +impl ChecksumAlgo { + /// Compute the hex-encoded digest of the given data using this algorithm + pub fn hex_digest(&self, data: &[u8]) -> String { + match self { + ChecksumAlgo::Md5 => { + use md5::{Digest, Md5}; + let mut hasher = Md5::new(); + hasher.update(data); + hex::encode(hasher.finalize()) + } + ChecksumAlgo::Sha256 => { + use sha2::{Digest, Sha256}; + let mut hasher = Sha256::new(); + hasher.update(data); + hex::encode(hasher.finalize()) + } + ChecksumAlgo::Sha512 => { + use sha2::{Digest, Sha512}; + let mut hasher = Sha512::new(); + hasher.update(data); + hex::encode(hasher.finalize()) + } + } + } } /// A package 'stanza' as found is 'Sources.gz' files, containing basic information about a source package @@ -138,16 +177,28 @@ impl Iterator for DebianSources { return self.next(); } - // Parse package files + // Parse package files. + // Prefer the strongest available checksum field: Checksums-Sha256, + // then Checksums-Sha512, then the legacy 'Files' (MD5) field. + // Some archives (e.g. the Ubuntu development series) no longer ship + // Checksums-Sha256, so falling back is required to keep working. let mut files = Vec::new(); - if let Some(checksums) = fields.get("Checksums-Sha256") { + let (checksum_field, algo) = if fields.contains_key("Checksums-Sha256") { + ("Checksums-Sha256", ChecksumAlgo::Sha256) + } else if fields.contains_key("Checksums-Sha512") { + ("Checksums-Sha512", ChecksumAlgo::Sha512) + } else { + ("Files", ChecksumAlgo::Md5) + }; + if let Some(checksums) = fields.get(checksum_field) { for line in checksums.lines() { let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() >= 3 { files.push(FileEntry { - sha256: parts[0].to_string(), + checksum: parts[0].to_string(), size: parts[1].parse().unwrap_or(0), name: parts[2].to_string(), + checksum_algo: algo, }); } } diff --git a/src/pull.rs b/src/pull.rs index 932f5aa..520f316 100644 --- a/src/pull.rs +++ b/src/pull.rs @@ -83,7 +83,6 @@ fn clone_repo( } } -use sha2::{Digest, Sha256}; use std::fs::File; use std::io::Write; @@ -221,6 +220,7 @@ fn checkout_pristine_tar(package_dir: &Path, filename: &str) -> Result<(), Box, ) -> Result<(), Box> { @@ -242,11 +242,13 @@ async fn download_file_checksum( // Download chunk by chunk to disk, while updating hasher for checksum let mut stream = response.bytes_stream(); - let mut hasher = Sha256::new(); + // Accumulate the downloaded bytes so we can compute the final digest with the + // correct algorithm once the download is complete. + let mut buffer: Vec = Vec::with_capacity(total_size as usize); while let Some(item) = stream.next().await { let chunk = item?; file.write_all(&chunk)?; - hasher.update(&chunk); + buffer.extend_from_slice(&chunk); if let Some(cb) = progress { index = min(index + chunk.len(), total_size as usize); @@ -254,9 +256,8 @@ async fn download_file_checksum( } } - // Verify checksum - let result = hasher.finalize(); - let calculated_checksum = hex::encode(result); + // Verify checksum using the algorithm specified for this file + let calculated_checksum = algo.hex_digest(&buffer); if calculated_checksum != checksum { return Err(format!( "Checksum mismatch! Expected {}, got {}", @@ -323,7 +324,18 @@ async fn fetch_orig_tarball( .files .iter() .find(|f| f.name.contains(".orig.tar.")) - .unwrap(); + .ok_or_else(|| { + format!( + "Could not find orig tarball in file list for package '{}'. \ + Available files: {:?}", + info.stanza.package, + info.stanza + .files + .iter() + .map(|f| &f.name) + .collect::>() + ) + })?; let filename = &orig_file.name; // 1. Try executing pristine-tar @@ -343,7 +355,8 @@ async fn fetch_orig_tarball( let target_dir = cwd.unwrap_or_else(|| Path::new(".")); download_file_checksum( format!("{}/{}", info.archive_url, filename).as_str(), - &orig_file.sha256, + &orig_file.checksum, + orig_file.checksum_algo, target_dir, progress, ) @@ -373,7 +386,8 @@ async fn fetch_dsc_file( download_file_checksum( format!("{}/{}", info.archive_url, filename).as_str(), - &dsc_file.sha256, + &dsc_file.checksum, + dsc_file.checksum_algo, target_dir, progress, ) @@ -397,7 +411,14 @@ async fn fetch_archive_sources( for file in &info.stanza.files { let url = format!("{}/{}", info.archive_url, file.name); - download_file_checksum(&url, &file.sha256, package_dir, progress).await?; + download_file_checksum( + &url, + &file.checksum, + file.checksum_algo, + package_dir, + progress, + ) + .await?; // Extract all tar archives, merging extracted directories if file.name.ends_with(".tar.gz") || file.name.ends_with(".tar.xz") {