diff --git a/Tests/LibCompress/TestDeflate.cpp b/Tests/LibCompress/TestDeflate.cpp index 43a12c8461d..45df5fcaa3a 100644 --- a/Tests/LibCompress/TestDeflate.cpp +++ b/Tests/LibCompress/TestDeflate.cpp @@ -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 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 const compressed { diff --git a/Userland/Libraries/LibCompress/Deflate.cpp b/Userland/Libraries/LibCompress/Deflate.cpp index 35f2f0d260f..cd6f7cbcc3e 100644 --- a/Userland/Libraries/LibCompress/Deflate.cpp +++ b/Userland/Libraries/LibCompress/Deflate.cpp @@ -290,6 +290,12 @@ ErrorOr 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);