Handle malformed remote and edge-case data instead of panicking

- distro_info: malformed CSV rows are skipped with a warning, dates
  that fail to parse become None, and all plain HTTP requests go
  through a shared reqwest client with connect/total timeouts
- package_info: the Sources stanza iterator is iterative (a crafted
  index with many blank stanzas overflowed the stack), stanzas missing
  a Version are skipped, and failed series/pocket probes are summarized
  in the final 'not found' error instead of being silently dropped
- pull: no double unwrap on the remote-derived artifact filename, an
  empty series list is an error, and streaming downloads get a
  per-request timeout
- deb/cross: dpkg-architecture output parsing skips unexpected lines
  and its exit status is checked, as is dpkg --add-architecture
- changelog: version increments parse as u64 with checked arithmetic
  (1.0-20250123123456 used to panic on the u32 parse)
This commit is contained in:
2026-09-16 02:44:45 +02:00
parent 6a5c5a7106
commit f72b35acfa
5 changed files with 489 additions and 142 deletions
+25 -7
View File
@@ -310,8 +310,14 @@ async fn download_file_checksum(
target_dir: &Path,
progress: ProgressCallback<'_>,
) -> Result<(), Box<dyn Error>> {
// Download with reqwest
let response = reqwest::get(url).await?;
// Download with the shared client (connect timeout). Large orig tarballs
// can legitimately take longer than the client's default total timeout,
// so use a generous per-request timeout for streaming downloads
let response = crate::distro_info::http_client()
.get(url)
.timeout(std::time::Duration::from_secs(30 * 60))
.send()
.await?;
if !response.status().is_success() {
return Err(format!("Failed to download '{}' : {}", url, response.status()).into());
}
@@ -322,7 +328,12 @@ async fn download_file_checksum(
let mut index = 0;
// Target file: extract file name from URL
let filename = Path::new(url).file_name().unwrap().to_str().unwrap();
let filename = Path::new(url)
.file_name()
.and_then(|f| f.to_str())
.ok_or_else(|| {
format!("Could not determine a file name from URL '{url}' to download the package file")
})?;
let path = target_dir.join(filename);
let mut file = File::create(path)?;
@@ -650,10 +661,17 @@ pub async fn pull(
// we target the development branch, i.e. the default branch
// Only use Ubuntu-specific branch naming if the VCS is from Launchpad
let is_launchpad_vcs = url.contains("launchpad.net");
let branch_name = if crate::distro_info::get_ordered_series_name(package_info.dist.as_str())
.await?[0]
!= *series
{
let series_list =
crate::distro_info::get_ordered_series_name(package_info.dist.as_str()).await?;
let latest_series = series_list.first().ok_or_else(|| {
format!(
"No series information available for distribution '{}', \
cannot determine its development series to select the git branch. \
The 'distro-info' package provides this data.",
package_info.dist
)
})?;
let branch_name = if latest_series != series {
if package_info.dist == "ubuntu" && is_launchpad_vcs {
Some(format!("{}/{}", package_info.dist, series))
} else {