mirror of
https://github.com/SerenityOS/serenity.git
synced 2026-11-07 04:29:30 -10:00
LibCompress: Tolerate Z_SYNC_FLUSH as final packet in stream
The stream in the PNG in #26595 contains a compressed block followed by an uncompressed block of size 0, followed by EOF. Such a block is apparently written for Z_SYNC_FLUSH. zlib is very lenient about data ending abruptly. Since it's enough for the PNGs written by ghostscript, allow an empty uncompressed packet at stream end to act as if it's the final packet, even if that's still stricter than zlib -- it's lenient enough for the current problem. We can always become even more lenient in the future if it we find something else that requires that. Fixes #26595.
This commit is contained in:
1 parent
838284f83a
commit
6497968ac7
2 files changed
+29
No files matched your search
@@ -83,6 +83,29 @@ TEST_CASE(deflate_decompress_compressed_block)
|
||||
EXPECT(decompressed == uncompressed.bytes());
|
||||
}
|
||||
|
||||
TEST_CASE(deflate_decompress_stream_ending_with_sync_flush)
|
||||
{
|
||||
// This is the output of compressing "This is a simple text file :)" with
|
||||
// Z_SYNC_FLUSH instead of Z_FINISH. The stream has bfinal=0 on all blocks
|
||||
// and ends with an empty uncompressed block (the sync flush marker).
|
||||
// Real-world PNG files can contain such streams. This shows that zlib is fine decompressing such streams:
|
||||
// python3 -c "import sys, zlib
|
||||
// c = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15)
|
||||
// sys.stdout.buffer.write(c.compress(b'This is a simple text file :)') + c.flush(zlib.Z_SYNC_FLUSH))" |
|
||||
// python3 -c "import sys, zlib; print(zlib.decompressobj(-15).decompress(sys.stdin.buffer.read()))"
|
||||
// To get the contents of `compressed`, pipe the first command into `xxd -i` instead.
|
||||
Array<u8, 32> const compressed {
|
||||
0x0A, 0xC9, 0xC8, 0x2C, 0x56, 0x00, 0xA2, 0x44, 0x85, 0xE2, 0xCC, 0xDC,
|
||||
0x82, 0x9C, 0x54, 0x85, 0x92, 0xD4, 0x8A, 0x12, 0x85, 0xB4, 0x4C, 0x20,
|
||||
0xCB, 0x4A, 0x13, 0x00, 0x00, 0x00, 0xFF, 0xFF
|
||||
};
|
||||
|
||||
auto const uncompressed = "This is a simple text file :)"sv;
|
||||
|
||||
auto const decompressed = TRY_OR_FAIL(Compress::DeflateDecompressor::decompress_all(compressed));
|
||||
EXPECT(decompressed == uncompressed.bytes());
|
||||
}
|
||||
|
||||
TEST_CASE(deflate_decompress_uncompressed_block)
|
||||
{
|
||||
Array<u8, 18> const compressed {
|
||||
|
||||
@@ -290,6 +290,12 @@ ErrorOr<Bytes> DeflateDecompressor::read_some(Bytes bytes)
|
||||
if ((length ^ 0xffff) != negated_length)
|
||||
return Error::from_string_literal("Calculated negated length does not equal stored negated length");
|
||||
|
||||
// Hack: Some deflate streams, for example in PNGs written by GhostScript 10.05.1's `gs`,
|
||||
// end with an empty uncompressed block (Z_SYNC_FLUSH) but do not set the BFINAL bit on that block.
|
||||
// Treat such a block at end of data as final, so we can decompress that data.
|
||||
if (length == 0 && m_input_stream->is_eof())
|
||||
m_read_final_block = true;
|
||||
|
||||
m_state = State::ReadingUncompressedBlock;
|
||||
new (&m_uncompressed_block) UncompressedBlock(*this, length);
|
||||
|
||||
|
||||
Reference in new issue
Block a user