.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.
This commit is contained in:
dotCooCoo
2026-11-01 01:22:08 +07:00
committed by bflatastic
parent d2418d6e7a
commit d2d4315524
2 changed files with 17 additions and 4 deletions
+4 -4
View File
@@ -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"));
+13
View File
@@ -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;