diff --git a/crates/cms/src/lutfile.rs b/crates/cms/src/lutfile.rs index 40a66d7d18..af7f9106f8 100644 --- a/crates/cms/src/lutfile.rs +++ b/crates/cms/src/lutfile.rs @@ -246,9 +246,9 @@ pub fn parse_3dl(text: &str) -> Result { if line.is_empty() { continue; } - // Headers: `Mesh 4 12` and the like start with a letter; the `3DMESH` preamble with a digit. - let preamble = line.split_whitespace().next().is_some_and(|t| t.eq_ignore_ascii_case("3DMESH")); - if preamble || line.starts_with(|c: char| c.is_ascii_alphabetic()) { + // A header (`3DMESH`, `Mesh 4 12`…) starts with a word that isn't a number. `NaN` and + // `inf` are numbers: a row starting with one is a shaper or table row, read strictly. + if line.split_whitespace().next().is_none_or(|t| t.parse::().is_err()) { header = true; // The only header keyword that carries data. A malformed or out-of-range `Mesh` // line is ignored, as every text line always was. @@ -671,6 +671,17 @@ mod tests { } } + /// #9170 review: a shaper row that starts with `NaN` or `inf` is a shaper, not a header, so + /// it reaches the non-finite refusal instead of being skipped. + #[test] + fn three_dl_shaper_starting_non_finite_is_refused() { + let body = identity_3dl(&[0, 1, 2, 3], 4); + let body = body.split_once('\n').unwrap().1; + for shaper in ["NaN 341 682 1023", "inf 341 682 1023", "-inf 341 682 1023"] { + assert!(parse_3dl(&format!("{shaper}\n{body}")).is_err(), "{shaper}"); + } + } + #[test] fn cube_title_with_hash_round_trips() { let l = LutFile::from_fn("Look #3", 2, |c| c);