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:
+68
-23
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user