Merge pull request #4945 from dotCooCoo/fix/ai-zstd-trailing-zero

.ai import: a Zstandard frame that ends in a zero byte keeps it
This commit is contained in:
BFlat
2026-11-01 01:32:23 +07:00
committed by GitHub
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;