From b062030a03e782fd495ddcbdab8496d01457f7f4 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Sat, 24 Oct 2026 07:59:45 +0200 Subject: [PATCH] Reject a leading '+' in ACI indices and RGB triples in Color::parse (#1878) Found while testing crates/color/src/lib.rs:115 and 126 Co-authored-by: Claude Opus 5.5 Co-authored-by: Brandon Thomas --- crates/color/src/lib.rs | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/crates/color/src/lib.rs b/crates/color/src/lib.rs index 2d60e3ac..e99f8d26 100644 --- a/crates/color/src/lib.rs +++ b/crates/color/src/lib.rs @@ -111,9 +111,9 @@ impl Color { _ => {} } if let Some(rest) = t.strip_prefix("color ") { - return rest.trim().parse::().ok().filter(|i| *i >= 1).map(Color::Index); + return parse_digits::(rest.trim()).filter(|i| *i >= 1).map(Color::Index); } - if let Ok(i) = t.parse::() { + if let Some(i) = parse_digits::(&t) { return match i { 0 => Some(Color::ByBlock), 256 => Some(Color::ByLayer), @@ -124,14 +124,22 @@ impl Color { if t.starts_with('#') { return Rgb::parse_hex(&t).map(Color::True); } - let parts: Vec<_> = t.split(',').map(|p| p.trim().parse::()).collect(); - if let [Ok(r), Ok(g), Ok(b)] = parts.as_slice() { + let parts: Vec<_> = t.split(',').map(|p| parse_digits::(p.trim())).collect(); + if let [Some(r), Some(g), Some(b)] = parts.as_slice() { return Some(Color::True(Rgb(*r, *g, *b))); } None } } +/// Parse an unsigned integer made of ASCII digits only (no sign, unlike `str::parse`). +fn parse_digits(s: &str) -> Option { + if s.is_empty() || !s.bytes().all(|b| b.is_ascii_digit()) { + return None; + } + s.parse().ok() +} + /// The RGB of ACI `i` (0 and out-of-range map to white/7). pub fn aci_rgb(i: u8) -> Rgb { PALETTE.get(usize::from(i)).copied().unwrap_or(Rgb(255, 255, 255)) @@ -234,6 +242,19 @@ mod tests { assert_eq!(Rgb::parse_hex("#012345"), Some(Rgb(1, 35, 69))); } + #[test] + fn parse_rejects_plus_sign() { + assert_eq!(Color::parse("+5"), None); + assert_eq!(Color::parse(" +256"), None); + assert_eq!(Color::parse("color +5"), None); + assert_eq!(Color::parse("+1,+2,+3"), None); + assert_eq!(Color::parse("1,+2,3"), None); + assert_eq!(Color::parse("5"), Some(Color::Index(5))); + assert_eq!(Color::parse("256"), Some(Color::ByLayer)); + assert_eq!(Color::parse("color 5"), Some(Color::Index(5))); + assert_eq!(Color::parse("1, 2, 3"), Some(Color::True(Rgb(1, 2, 3)))); + } + #[test] fn parse_hex_rejects_repeated_hash() { assert_eq!(Rgb::parse_hex("##ff0000"), None);