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.
This commit is contained in:
@@ -17,6 +17,7 @@ regex = "1"
|
|||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
tokio = { version = "1.41.1", features = ["full"] }
|
tokio = { version = "1.41.1", features = ["full"] }
|
||||||
sha2 = "0.10.8"
|
sha2 = "0.10.8"
|
||||||
|
md-5 = "0.10"
|
||||||
hex = "0.4.3"
|
hex = "0.4.3"
|
||||||
log = "0.4.28"
|
log = "0.4.28"
|
||||||
indicatif = "0.18.3"
|
indicatif = "0.18.3"
|
||||||
|
|||||||
+56
-5
@@ -39,8 +39,47 @@ pub struct FileEntry {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
/// Size of the file
|
/// Size of the file
|
||||||
pub size: u64,
|
pub size: u64,
|
||||||
/// SHA256 hash for the file
|
/// Checksum hash for the file
|
||||||
pub sha256: String,
|
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
|
/// 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();
|
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();
|
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() {
|
for line in checksums.lines() {
|
||||||
let parts: Vec<&str> = line.split_whitespace().collect();
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
||||||
if parts.len() >= 3 {
|
if parts.len() >= 3 {
|
||||||
files.push(FileEntry {
|
files.push(FileEntry {
|
||||||
sha256: parts[0].to_string(),
|
checksum: parts[0].to_string(),
|
||||||
size: parts[1].parse().unwrap_or(0),
|
size: parts[1].parse().unwrap_or(0),
|
||||||
name: parts[2].to_string(),
|
name: parts[2].to_string(),
|
||||||
|
checksum_algo: algo,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+31
-10
@@ -83,7 +83,6 @@ fn clone_repo(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
@@ -221,6 +220,7 @@ fn checkout_pristine_tar(package_dir: &Path, filename: &str) -> Result<(), Box<d
|
|||||||
async fn download_file_checksum(
|
async fn download_file_checksum(
|
||||||
url: &str,
|
url: &str,
|
||||||
checksum: &str,
|
checksum: &str,
|
||||||
|
algo: crate::package_info::ChecksumAlgo,
|
||||||
target_dir: &Path,
|
target_dir: &Path,
|
||||||
progress: ProgressCallback<'_>,
|
progress: ProgressCallback<'_>,
|
||||||
) -> Result<(), Box<dyn Error>> {
|
) -> Result<(), Box<dyn Error>> {
|
||||||
@@ -242,11 +242,13 @@ async fn download_file_checksum(
|
|||||||
|
|
||||||
// Download chunk by chunk to disk, while updating hasher for checksum
|
// Download chunk by chunk to disk, while updating hasher for checksum
|
||||||
let mut stream = response.bytes_stream();
|
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<u8> = Vec::with_capacity(total_size as usize);
|
||||||
while let Some(item) = stream.next().await {
|
while let Some(item) = stream.next().await {
|
||||||
let chunk = item?;
|
let chunk = item?;
|
||||||
file.write_all(&chunk)?;
|
file.write_all(&chunk)?;
|
||||||
hasher.update(&chunk);
|
buffer.extend_from_slice(&chunk);
|
||||||
|
|
||||||
if let Some(cb) = progress {
|
if let Some(cb) = progress {
|
||||||
index = min(index + chunk.len(), total_size as usize);
|
index = min(index + chunk.len(), total_size as usize);
|
||||||
@@ -254,9 +256,8 @@ async fn download_file_checksum(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify checksum
|
// Verify checksum using the algorithm specified for this file
|
||||||
let result = hasher.finalize();
|
let calculated_checksum = algo.hex_digest(&buffer);
|
||||||
let calculated_checksum = hex::encode(result);
|
|
||||||
if calculated_checksum != checksum {
|
if calculated_checksum != checksum {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Checksum mismatch! Expected {}, got {}",
|
"Checksum mismatch! Expected {}, got {}",
|
||||||
@@ -323,7 +324,18 @@ async fn fetch_orig_tarball(
|
|||||||
.files
|
.files
|
||||||
.iter()
|
.iter()
|
||||||
.find(|f| f.name.contains(".orig.tar."))
|
.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::<Vec<_>>()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
let filename = &orig_file.name;
|
let filename = &orig_file.name;
|
||||||
|
|
||||||
// 1. Try executing pristine-tar
|
// 1. Try executing pristine-tar
|
||||||
@@ -343,7 +355,8 @@ async fn fetch_orig_tarball(
|
|||||||
let target_dir = cwd.unwrap_or_else(|| Path::new("."));
|
let target_dir = cwd.unwrap_or_else(|| Path::new("."));
|
||||||
download_file_checksum(
|
download_file_checksum(
|
||||||
format!("{}/{}", info.archive_url, filename).as_str(),
|
format!("{}/{}", info.archive_url, filename).as_str(),
|
||||||
&orig_file.sha256,
|
&orig_file.checksum,
|
||||||
|
orig_file.checksum_algo,
|
||||||
target_dir,
|
target_dir,
|
||||||
progress,
|
progress,
|
||||||
)
|
)
|
||||||
@@ -373,7 +386,8 @@ async fn fetch_dsc_file(
|
|||||||
|
|
||||||
download_file_checksum(
|
download_file_checksum(
|
||||||
format!("{}/{}", info.archive_url, filename).as_str(),
|
format!("{}/{}", info.archive_url, filename).as_str(),
|
||||||
&dsc_file.sha256,
|
&dsc_file.checksum,
|
||||||
|
dsc_file.checksum_algo,
|
||||||
target_dir,
|
target_dir,
|
||||||
progress,
|
progress,
|
||||||
)
|
)
|
||||||
@@ -397,7 +411,14 @@ async fn fetch_archive_sources(
|
|||||||
|
|
||||||
for file in &info.stanza.files {
|
for file in &info.stanza.files {
|
||||||
let url = format!("{}/{}", info.archive_url, file.name);
|
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
|
// Extract all tar archives, merging extracted directories
|
||||||
if file.name.ends_with(".tar.gz") || file.name.ends_with(".tar.xz") {
|
if file.name.ends_with(".tar.gz") || file.name.ends_with(".tar.xz") {
|
||||||
|
|||||||
Reference in New Issue
Block a user