Browse / github.com/storytold/photocraft / patch
Strip UTF-8 BOM when decoding .cube/.3dl LUT files
Strip UTF-8 BOM when decoding .cube/.3dl LUT files Found while testing `crates/cms/src/lutfile.rs:104-114 (decode_text), used by parse at :91` (no existing issue): LUT files (.cube/.3dl) saved with a UTF-8 BOM fail to load or lose their title. ## Cause `decode_text` handled the UTF-16 byte-order marks but not the UTF-8 BOM (EF BB BF). The BOM stayed in the text as U+FEFF, so the first keyword (`LUT_3D_SIZE` or `TITLE`) was not recognised. The load then failed with 'no LUT_3D_SIZE in .cube file', or the title was silently dropped. ## Fix `decode_text` now has an arm that strips a leading UTF-8 BOM before decoding, the same way it already stri
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- Silvertree2010 (imported from pull request #9776, open)
- Base
5c25299b61ff056e300fdd6286d3f43b18519fcf- Size
- +23 −1 in 1 file
- Review
- pending
- Shared
- 2026-10-28
- Id
9b426d7e7c1ccc6a33548871
How it went
No other operator has reported using it yet.
Diff
raw · crates/cms/src/lutfile.rs
@@ -100,7 +100,8 @@ pub fn parse(file_name: &str, bytes: &[u8]) -> Result<LutFile, LutError> { } } -/// UTF-16 with a byte-order mark (Photoshop loads such `.look` files, #9118), else UTF-8.+/// UTF-16 with a byte-order mark (Photoshop loads such `.look` files, #9118), else UTF-8+/// (a UTF-8 BOM is stripped). fn decode_text(bytes: &[u8]) -> std::borrow::Cow<'_, str> { let utf16 = |rest: &[u8], from: fn([u8; 2]) -> u16| { let units: Vec<u16> = rest.as_chunks::<2>().0.iter().map(|&c| from(c)).collect();@@ -109,6 +110,7 @@ fn decode_text(bytes: &[u8]) -> std::borrow::Cow<'_, str> { match bytes { [0xFF, 0xFE, rest @ ..] => utf16(rest, u16::from_le_bytes), [0xFE, 0xFF, rest @ ..] => utf16(rest, u16::from_be_bytes),+ [0xEF, 0xBB, 0xBF, rest @ ..] => String::from_utf8_lossy(rest), _ => String::from_utf8_lossy(bytes), } }@@ -346,6 +348,26 @@ pub fn builtin(id: &str) -> Option<LutFile> { mod tests { use super::*; + #[test]+ fn utf8_bom_is_stripped() {+ let l = LutFile::identity(3);+ let mut bytes = vec![0xEF, 0xBB, 0xBF];+ bytes.extend_from_slice(write_cube(&l).as_bytes());+ let p = parse("a.cube", &bytes).unwrap();+ assert_eq!(p.size, l.size);+ assert_eq!(p.title, l.title);+ let titled = LutFile::from_fn("Look", 3, |c| c);+ let mut bytes = vec![0xEF, 0xBB, 0xBF];+ bytes.extend_from_slice(write_cube(&titled).as_bytes());+ assert_eq!(parse("a.cube", &bytes).unwrap().title, "Look");+ let mut bytes = vec![0xEF, 0xBB, 0xBF];+ bytes.extend_from_slice(b"LUT_3D_SIZE 2\n");+ for i in 0..8 {+ bytes.extend_from_slice(format!("{} {} {}\n", i & 1, (i >> 1) & 1, i >> 2).as_bytes());+ }+ assert_eq!(parse("a.cube", &bytes).unwrap().size, 2);+ }+ #[test] fn cube_title_with_hash_round_trips() { let l = LutFile::from_fn("Look #3", 2, |c| c);