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;