Reject empty arguments in parse_css color functions

Found while testing crates/color/src/lib.rs:247 (css_args, used by the rgb/rgba and hsl/hsla arms of parse_css at :159 and :178)

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
NetterSilver
2026-10-31 03:02:39 +01:00
co-authored by Claude Haiku 5.5
parent 57a51af8bb
commit 9ae43a4c4e
2 changed files with 24 additions and 5 deletions
+15 -5
View File
@@ -157,7 +157,7 @@ impl Color {
return Self::from_hex(&t);
}
if let Some(inner) = t.strip_prefix("rgba(").or_else(|| t.strip_prefix("rgb(")).and_then(|r| r.strip_suffix(')')) {
let parts = css_args(inner);
let parts = css_args(inner)?;
let num = |i: usize| -> Option<f32> {
let p = parts.get(i)?;
if let Some(pc) = p.strip_suffix('%') {
@@ -176,7 +176,7 @@ impl Color {
return Some(Self::new(num(0)?, num(1)?, num(2)?, alpha));
}
if let Some(inner) = t.strip_prefix("hsla(").or_else(|| t.strip_prefix("hsl(")).and_then(|r| r.strip_suffix(')')) {
let parts = css_args(inner);
let parts = css_args(inner)?;
let h = parts.first()?.trim_end_matches("deg").parse::<f32>().ok()?;
let s = parts.get(1)?.trim_end_matches('%').parse::<f32>().ok()? / 100.0;
let l = parts.get(2)?.trim_end_matches('%').parse::<f32>().ok()? / 100.0;
@@ -244,14 +244,24 @@ impl Color {
}
}
fn css_args(inner: &str) -> Vec<String> {
inner.replace('/', ",").split([',', ' ']).map(str::trim).filter(|p| !p.is_empty()).map(str::to_string).collect()
/// Split CSS function arguments on `,` and `/` (each may be surrounded by spaces) and on runs of
/// whitespace. An empty argument between two separators makes the whole list invalid.
fn css_args(inner: &str) -> Option<Vec<String>> {
let mut out = Vec::new();
for seg in inner.replace('/', ",").split(',') {
let before = out.len();
out.extend(seg.split_whitespace().map(str::to_string));
if out.len() == before {
return None;
}
}
Some(out)
}
/// The arguments of `lch()` / `oklch()`: lightness, chroma, hue and alpha. Percentages are of
/// `l_full` / `c_full`; `none` is 0.
fn css_polar(inner: &str, l_full: f32, c_full: f32) -> Option<([f32; 3], f32)> {
let parts = css_args(inner);
let parts = css_args(inner)?;
if !(3..=4).contains(&parts.len()) {
return None;
}
+9
View File
@@ -148,3 +148,12 @@ fn css_hsl_rejects_non_finite() {
assert_eq!(Color::parse_css(s), None, "{s}");
}
}
#[test]
fn css_rejects_empty_arguments() {
for s in ["rgb(255,,0,0)", "rgb(,1,2,3)", "rgb(1,2,3,)", "hsl(10,,50%,50%)", "rgb(1,2,,)", "oklch(0.5,,0.1,20)"] {
assert_eq!(Color::parse_css(s), None, "{s}");
}
assert!(Color::parse_css("rgb(255 0 0 / 50%)").is_some());
assert!(Color::parse_css("hsl(120deg 50% 50%)").is_some());
}