.ai open: keep the last zero byte of a Zstandard frame in the editing data

The reader cut every trailing zero byte off the Zstandard data before
decoding it, to drop the padding after the last frame. A frame can end in
a zero byte itself, 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-10-31 08:53:28 -07:00
parent 25a193e5a9
commit 1812ccda08
2 changed files with 17 additions and 4 deletions
+4 -4
View File
@@ -141,11 +141,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
@@ -1219,6 +1219,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;