mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
zlib: clear unconsumed_tail when a bounded decompress reaches stream end (#8908)
* zlib: clear unconsumed_tail when a bounded decompress reaches stream end When a max_length-bounded Decompress.decompress() call reached the end of the stream, the bytes after the stream went to unused_data, but unconsumed_tail kept the previous call's tail, including input the call had already consumed. A later flush() then moved those stale bytes into unused_data. Empty unconsumed_tail at end of stream, as CPython's save_unconsumed_input() does since gh-158169. CPython releases before that change report the bytes after the stream in unconsumed_tail, so the snippet accepts either value when it runs under CPython. Fixes #8906 Assisted-by: Claude Code:claude-opus-5-5 * zlib: bind the unconsumed input with an if expression Choose between the empty slice and the rest of the input in one expression instead of reassigning a mutable binding. Assisted-by: Claude Code:claude-opus-5-5 * zlib: accept both valid tails in the snippet on every implementation The snippet no longer branches on sys.implementation. RustPython and CPython after gh-158169 give an empty tail; CPython before it gives the bytes after the stream. The stale tail of gh-8906 fails either way. Assisted-by: Claude Code:claude-opus-5-5
This commit is contained in:
1 parent
e8919b9632
commit
3805038dec
2 files changed
+45
-11
No files matched your search
@@ -670,17 +670,16 @@ impl Decompressor {
|
||||
};
|
||||
let consumed = (d.total_in() - prev_in) as usize;
|
||||
|
||||
// save unused input
|
||||
let unconsumed = &data[consumed..];
|
||||
if !unconsumed.is_empty() {
|
||||
if stream_end {
|
||||
unused_data.extend_from_slice(unconsumed);
|
||||
} else {
|
||||
*unconsumed_tail = unconsumed.to_vec();
|
||||
}
|
||||
} else if !unconsumed_tail.is_empty() {
|
||||
unconsumed_tail.clear();
|
||||
}
|
||||
// save unused input: past the end of the stream it is unused_data and
|
||||
// unconsumed_tail is emptied, otherwise it is the new unconsumed_tail
|
||||
let unconsumed: &[u8] = if stream_end {
|
||||
unused_data.extend_from_slice(&data[consumed..]);
|
||||
&[]
|
||||
} else {
|
||||
&data[consumed..]
|
||||
};
|
||||
unconsumed_tail.clear();
|
||||
unconsumed_tail.extend_from_slice(unconsumed);
|
||||
|
||||
Ok((ret, stream_end))
|
||||
}
|
||||
@@ -1005,4 +1004,22 @@ mod tests {
|
||||
assert_eq!(d.decompress(&encoded, None).unwrap(), b"later input");
|
||||
assert!(d.eof());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stream_end_clears_unconsumed_tail() {
|
||||
let source = b"x".repeat(20000);
|
||||
let data = [compress(&source, -1, MAX_WBITS).unwrap(), b"NEXT".to_vec()].concat();
|
||||
let mut d = Decompressor::new(MAX_WBITS, None).unwrap();
|
||||
let first = d.decompress(&data, Some(100)).unwrap();
|
||||
assert!(!d.eof());
|
||||
let tail = d.unconsumed_tail().to_vec();
|
||||
let rest = d.decompress(&tail, Some(100_000)).unwrap();
|
||||
assert!(d.eof());
|
||||
assert_eq!([first, rest].concat(), source);
|
||||
assert_eq!(d.unconsumed_tail(), b"");
|
||||
assert_eq!(d.unused_data(), b"NEXT");
|
||||
assert_eq!(d.flush(DEF_BUF_SIZE).unwrap(), b"");
|
||||
assert_eq!(d.unconsumed_tail(), b"");
|
||||
assert_eq!(d.unused_data(), b"NEXT");
|
||||
}
|
||||
}
|
||||
@@ -62,3 +62,20 @@ for text in compressed_lorem_list:
|
||||
|
||||
assert_raises(zlib.error, lambda: zlib.compress(b"123", -40))
|
||||
assert_raises(zlib.error, lambda: zlib.compress(b"123", 10))
|
||||
|
||||
# A max_length-bounded call that reaches the end of the stream keeps no
|
||||
# stale input in unconsumed_tail (gh-8906). The tail is empty, or, as in
|
||||
# CPython before gh-158169, the bytes after the stream.
|
||||
source = b"x" * 20000
|
||||
d = zlib.decompressobj()
|
||||
out = d.decompress(zlib.compress(source) + b"NEXT", 100)
|
||||
assert not d.eof
|
||||
out += d.decompress(d.unconsumed_tail, 100000)
|
||||
assert d.eof
|
||||
assert out == source
|
||||
assert d.unused_data == b"NEXT"
|
||||
tail = d.unconsumed_tail
|
||||
assert tail in (b"", b"NEXT")
|
||||
assert d.flush() == b""
|
||||
assert d.unconsumed_tail == tail
|
||||
assert d.unused_data == b"NEXT" + tail
|
||||
Reference in new issue
Block a user