From d2d431552449c47aaff6814537a3508c09e744cb Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 31 Oct 2026 09:37:09 -0700 Subject: [PATCH] .ai import: a Zstandard frame that ends in a zero byte keeps it The reader cut every trailing zero byte off the Zstandard editing data before decoding it, to drop the padding after the last frame. A frame can end in a zero byte itself (a raw or RLE block, or a checksum whose last byte is zero), and cutting it left the frame short, so the file failed to open with "its Zstandard compression is damaged". The reader now decodes frame after frame and stops when only zeros are left. --- crates/eps/src/import/ai.rs | 8 ++++---- crates/eps/src/import/ai/tests.rs | 13 +++++++++++++ 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/crates/eps/src/import/ai.rs b/crates/eps/src/import/ai.rs index 291171f78..5e60e7e81 100644 --- a/crates/eps/src/import/ai.rs +++ b/crates/eps/src/import/ai.rs @@ -158,11 +158,11 @@ impl Codec { flate2::read::ZlibDecoder::new(data).take(max + 1).read_to_end(&mut out).map_err(|_| damaged("its zlib compression"))?; } Codec::Zstd => { - // The data may be several frames one after the other, padded with zeros. - let end = data.iter().rposition(|b| *b != 0).map_or(0, |i| i + 1); - let mut src = data.get(..end).unwrap_or_default(); + // The data may be several frames one after the other, padded with zeros. A frame + // can itself end in a zero byte, so the padding is what is left after the last frame. + let mut src = data; let mut frames = 0usize; - while !src.is_empty() { + while src.iter().any(|b| *b != 0) { frames += 1; if frames > MAX_FRAMES { return Err(damaged("its Zstandard compression")); diff --git a/crates/eps/src/import/ai/tests.rs b/crates/eps/src/import/ai/tests.rs index cfb6d2b34..34b35e13e 100644 --- a/crates/eps/src/import/ai/tests.rs +++ b/crates/eps/src/import/ai/tests.rs @@ -1472,6 +1472,19 @@ fn many_zstandard_frames_are_read_in_one_pass_and_capped() { assert!(started.elapsed() < std::time::Duration::from_secs(20), "{:?}", started.elapsed()); } +/// A Zstandard frame that ends in a zero byte keeps it: only the zeros after the last frame are +/// padding. +#[test] +fn a_zstandard_frame_ending_in_a_zero_byte_is_read_whole() { + // Magic number, a single-segment header with a 1-byte content size of 3, and one raw block + // (last, 3 bytes) holding `ab` and a zero byte. + let frame = [0x28, 0xB5, 0x2F, 0xFD, 0x20, 3, 0x19, 0, 0, b'a', b'b', 0]; + for padding in [0, 64] { + let raw = [&b"%AI24_ZStandard_Data"[..], &frame, &frame, &vec![0; padding]].concat(); + assert_eq!(decode_private(&raw).unwrap(), b"ab\0ab\0"); + } +} + #[test] fn closing_brackets_without_their_opening_dont_rescan_the_stack() { let n = 200_000;