From 352abea9fce5ef73d01761f32ddd9d80150bac72 Mon Sep 17 00:00:00 2001 From: NetterSilver <133535268+Silvertree2010@users.noreply.github.com> Date: Mon, 19 Oct 2026 12:47:19 +0200 Subject: [PATCH] Truncate ASE swatch names that overflow the u16 length field Found while testing crates/color/src/ase.rs:131-132 Co-Authored-By: Claude Haiku 5.5 Co-Authored-By: Claude Sonnet 5.5 --- crates/color/src/ase.rs | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/color/src/ase.rs b/crates/color/src/ase.rs index 6cfd7203..f44e3dba 100644 --- a/crates/color/src/ase.rs +++ b/crates/color/src/ase.rs @@ -128,8 +128,14 @@ pub fn write(swatches: &[Swatch]) -> Vec { continue; } let mut b = Vec::new(); - let units: Vec = s.name.encode_utf16().chain(std::iter::once(0)).collect(); - b.extend((units.len() as u16).to_be_bytes()); + // The length field is a u16 that counts the terminating NUL: truncate longer names. + let mut units: Vec = s.name.encode_utf16().take(usize::from(u16::MAX) - 1).collect(); + // Do not leave half of a surrogate pair at the cut. + if units.last().is_some_and(|u| (0xD800..0xDC00).contains(u)) { + units.pop(); + } + units.push(0); + b.extend(u16::try_from(units.len()).unwrap_or(u16::MAX).to_be_bytes()); for u in units { b.extend(u.to_be_bytes()); } @@ -233,6 +239,21 @@ mod tests { assert_eq!(read(&write(&sw[1..])).map(|w| w.len()), Ok(MAX_COLORS), "up to the limit"); } + #[test] + fn over_long_name_is_truncated_to_fit() { + let sw = vec![Swatch { + name: "x".repeat(70_000), + value: SwatchValue::Color { color: Color::Gray { k: 0.5 }, color_type: ColorType::Process }, + locked: false, + named: true, + hidden: false, + }]; + let back = read(&write(&sw)).unwrap(); + assert_eq!(back.len(), 1, "the swatch must survive the round trip"); + assert_eq!(back[0].name, "x".repeat(usize::from(u16::MAX) - 1)); + assert_eq!(back[0].value, sw[0].value); + } + #[test] fn out_of_range_components_are_clamped() { let mut f = Vec::new();