version: reject an empty debian revision

DebianVersion::parse accepted '1.0-' (empty revision after rsplit on
the last hyphen), where dpkg rejects it with 'revision number is
empty'; downstream filename construction produced garbage like
'foo_1.0-.dsc'. Keep the start-digit warning-only semantics of dpkg
(no new check there) and the accepted '1.0--1' split.
This commit is contained in:
2026-09-17 19:52:32 +02:00
parent 93296c26c1
commit 54274e9079
+27
View File
@@ -48,6 +48,12 @@ impl DebianVersion {
} }
} }
if let Some(rev) = &debian_revision { if let Some(rev) = &debian_revision {
if rev.is_empty() {
// dpkg rejects a trailing hyphen: "bad syntax: revision
// number is empty". Native versions (no `-` at all) are
// handled above and stay valid.
return Err(format!("empty debian revision in '{}'", raw));
}
for c in rev.chars() { for c in rev.chars() {
if !(c.is_ascii_alphanumeric() || matches!(c, '.' | '+' | '~') || !c.is_ascii()) { if !(c.is_ascii_alphanumeric() || matches!(c, '.' | '+' | '~') || !c.is_ascii()) {
return Err(format!( return Err(format!(
@@ -329,6 +335,27 @@ mod tests {
assert!(DebianVersion::parse("1.0~rc1-2").is_ok()); assert!(DebianVersion::parse("1.0~rc1-2").is_ok());
} }
/// dpkg rejects a trailing `-` ("bad syntax: revision number is
/// empty") but accepts `1.0--1`, where the revision is the text after
/// the *last* hyphen (upstream `1.0-` + revision `1`).
#[test]
fn version_empty_revision() {
let err = DebianVersion::parse("1.0-").unwrap_err();
assert!(err.contains("empty"), "unexpected message: {err}");
assert!(DebianVersion::parse("1.0-").is_err());
// Epoch variants take the same path.
assert!(DebianVersion::parse("3:1.0-").is_err());
assert!(DebianVersion::parse("1.0-1").is_ok());
// Native versions (no revision at all) are still fine.
assert!(DebianVersion::parse("1.0").is_ok());
assert!(DebianVersion::parse("3:1.0").is_ok());
let v = DebianVersion::parse("1.0--1").unwrap();
assert_eq!(v.upstream, "1.0-");
assert_eq!(v.debian_revision.as_deref(), Some("1"));
}
fn cmp_sign(a: &DebianVersion, b: &DebianVersion) -> i32 { fn cmp_sign(a: &DebianVersion, b: &DebianVersion) -> i32 {
match a.cmp(b) { match a.cmp(b) {
std::cmp::Ordering::Less => -1, std::cmp::Ordering::Less => -1,