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
+68 -23
View File
@@ -40,7 +40,7 @@ pub fn generate_entry(
version.to_string()
} else {
// TODO: Pass these flags from CLI
compute_new_version(&old_version, false, false, false)
compute_new_version(&old_version, false, false, false)?
};
let (maintainer_name, maintainer_email) = get_maintainer_info()?;
@@ -68,7 +68,7 @@ fn compute_new_version(
is_ubuntu: bool,
is_rebuild: bool,
is_nmu: bool,
) -> String {
) -> Result<String, Box<dyn std::error::Error>> {
if is_ubuntu {
return increment_suffix(old_version, "ubuntu");
}
@@ -86,7 +86,7 @@ fn compute_new_version(
}
/// Increment a version number by 1, for a given suffix
fn increment_suffix(version: &str, suffix: &str) -> String {
fn increment_suffix(version: &str, suffix: &str) -> Result<String, Box<dyn std::error::Error>> {
// If suffix is empty, we just look for trailing digits
// If suffix is not empty, we look for suffix followed by digits
@@ -100,19 +100,33 @@ fn increment_suffix(version: &str, suffix: &str) -> String {
if let Some(caps) = re.captures(version) {
let num_str = caps.get(1).unwrap().as_str();
let num: u32 = num_str.parse().unwrap();
// Parse as u64 so that large trailing numbers (e.g. date-based
// versions like '1.0-20250123123456', which do not fit in a u32)
// still increment normally
let num: u64 = num_str.parse().map_err(|_| {
format!(
"Cannot increment version '{version}': trailing number '{num_str}' \
is too large to be incremented. Specify a version explicitly instead."
)
})?;
let range = caps.get(1).unwrap().range();
let new_num = num.checked_add(1).ok_or_else(|| {
format!(
"Cannot increment version '{version}': trailing number {num} \
is too large to be incremented. Specify a version explicitly instead."
)
})?;
let mut new_ver = version.to_string();
new_ver.replace_range(range, &(num + 1).to_string());
return new_ver;
new_ver.replace_range(range, &new_num.to_string());
return Ok(new_ver);
}
// If pattern not found, append suffix + "1"
// But if suffix is empty, we default to appending "-1" (standard Debian revision start)
if suffix.is_empty() {
format!("{}-1", version)
Ok(format!("{}-1", version))
} else {
format!("{}{}{}", version, suffix, 1)
Ok(format!("{}{}{}", version, suffix, 1))
}
}
@@ -381,70 +395,101 @@ mod tests {
fn test_compute_new_version() {
// Debian upload
assert_eq!(
compute_new_version("15.2.0-8", false, false, false),
compute_new_version("15.2.0-8", false, false, false).unwrap(),
"15.2.0-9"
);
assert_eq!(
compute_new_version("15.2.0-9", false, false, false),
compute_new_version("15.2.0-9", false, false, false).unwrap(),
"15.2.0-10"
);
// Ubuntu upload
assert_eq!(
compute_new_version("15.2.0-9", true, false, false),
compute_new_version("15.2.0-9", true, false, false).unwrap(),
"15.2.0-9ubuntu1"
);
assert_eq!(
compute_new_version("15.2.0-9ubuntu1", true, false, false),
compute_new_version("15.2.0-9ubuntu1", true, false, false).unwrap(),
"15.2.0-9ubuntu2"
);
// No change rebuild
assert_eq!(
compute_new_version("15.2.0-9", false, true, false),
compute_new_version("15.2.0-9", false, true, false).unwrap(),
"15.2.0-9build1"
);
assert_eq!(
compute_new_version("15.2.0-9build1", false, true, false),
compute_new_version("15.2.0-9build1", false, true, false).unwrap(),
"15.2.0-9build2"
);
// Rebuild of Ubuntu version
assert_eq!(
compute_new_version("15.2.0-9ubuntu1", false, true, false),
compute_new_version("15.2.0-9ubuntu1", false, true, false).unwrap(),
"15.2.0-9ubuntu1build1"
);
// NMU
// Native
assert_eq!(compute_new_version("1.0", false, false, true), "1.0+nmu1");
assert_eq!(
compute_new_version("1.0+nmu1", false, false, true),
compute_new_version("1.0", false, false, true).unwrap(),
"1.0+nmu1"
);
assert_eq!(
compute_new_version("1.0+nmu1", false, false, true).unwrap(),
"1.0+nmu2"
);
// Non-native
assert_eq!(compute_new_version("1.0-1", false, false, true), "1.0-1.1");
assert_eq!(
compute_new_version("1.0-1.1", false, false, true),
compute_new_version("1.0-1", false, false, true).unwrap(),
"1.0-1.1"
);
assert_eq!(
compute_new_version("1.0-1.1", false, false, true).unwrap(),
"1.0-1.2"
);
// NMU of NMU?
assert_eq!(
compute_new_version("1.0-1.2", false, false, true),
compute_new_version("1.0-1.2", false, false, true).unwrap(),
"1.0-1.3"
);
// Native package uploads
assert_eq!(compute_new_version("1.0", false, false, false), "1.1");
assert_eq!(compute_new_version("1.0.5", false, false, false), "1.0.6");
assert_eq!(
compute_new_version("20241126", false, false, false),
compute_new_version("1.0", false, false, false).unwrap(),
"1.1"
);
assert_eq!(
compute_new_version("1.0.5", false, false, false).unwrap(),
"1.0.6"
);
assert_eq!(
compute_new_version("20241126", false, false, false).unwrap(),
"20241127"
);
}
#[test]
fn test_compute_new_version_large_trailing_number() {
// Date-based versions with a trailing number larger than u32::MAX
// must increment normally (they fit in a u64)
assert_eq!(
compute_new_version("1.0-20250123123456", false, false, false).unwrap(),
"1.0-20250123123457"
);
// A number that does not even fit in a u64 yields a clear error
// instead of panicking
let err = compute_new_version("1.0-99999999999999999999999999", false, false, false);
assert!(err.is_err());
// u64::MAX itself cannot be incremented
let err = compute_new_version("1.0-18446744073709551615", false, false, false);
assert!(err.is_err());
}
#[test]
fn test_get_maintainer_info() {
// Test with env vars