From 30b2efc6bb9b552c7e65ea33b1cf2e8417c79eeb Mon Sep 17 00:00:00 2001 From: compiler-segfault <2671715549@qq.com> Date: Thu, 5 Nov 2026 07:42:24 +0800 Subject: [PATCH] gh-160001: Honor the zdict of zlib._ZlibDecompressor on wrapped streams _ZlibDecompressor only installed its dictionary in the constructor, which covers raw deflate (wbits < 0). For a zlib-wrapped stream zlib reports Z_NEED_DICT from inflate() once it has read the header, and decompress_buf() treated that as an error path that never set an exception, so the method returned NULL and the caller got a SystemError. Install the dictionary from the decompression loop when one was supplied, and raise zlib.error when the stream requires a dictionary that was never given, matching what zlib.Decompress and zlib.decompressobj do for the same input. --- Lib/test/test_zlib.py | 39 +++++++++++++++++++ ...-11-05-07-30-00.gh-issue-160001.Zq4WnH.rst | 4 ++ Modules/zlibmodule.c | 12 +++++- 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-11-05-07-30-00.gh-issue-160001.Zq4WnH.rst diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py index 3243448043b5c21..a2468ab0ae6a387 100644 --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -1153,6 +1153,45 @@ def testDecompress(self): text = zlibd.decompress(self.DATA) self.assertEqual(text, self.TEXT) + def testDecompressZdictWrapped(self): + # gh-160001: for a zlib-wrapped stream zlib only announces the + # dictionary (``Z_NEED_DICT``) after the header, so it has to be + # applied from the decompression loop rather than the constructor. + zdict = b"hello world " * 10 + compressor = zlib.compressobj(zdict=zdict) + data = compressor.compress(b"hello world hello") + compressor.flush() + + decompressor = zlib._ZlibDecompressor(zdict=zdict) + self.assertEqual(decompressor.decompress(data), b"hello world hello") + + def testDecompressZdictWrappedChunks(self): + zdict = b"hello world " * 10 + compressor = zlib.compressobj(zdict=zdict) + data = compressor.compress(b"hello world hello") + compressor.flush() + + decompressor = zlib._ZlibDecompressor(zdict=zdict) + result = b"" + for index in range(0, len(data), 3): + result += decompressor.decompress(data[index:index + 3]) + + self.assertEqual(result, b"hello world hello") + + def testDecompressZdictRaw(self): + zdict = b"hello world " * 10 + compressor = zlib.compressobj(wbits=-zlib.MAX_WBITS, zdict=zdict) + data = compressor.compress(b"hello world hello") + compressor.flush() + + decompressor = zlib._ZlibDecompressor(-zlib.MAX_WBITS, zdict=zdict) + self.assertEqual(decompressor.decompress(data), b"hello world hello") + + def testDecompressFdictHeaderWithoutZdict(self): + # A zlib header with the FDICT flag set asks for a dictionary that was + # never provided; that is a zlib error, not an internal error. + fdict_header = b"\x78\xbb" + b"\x00\x00\x00\x01\x03\x00" + + with self.assertRaises(zlib.error): + zlib._ZlibDecompressor().decompress(fdict_header) + def testDecompressChunks10(self): zlibd = zlib._ZlibDecompressor() text = b'' diff --git a/Misc/NEWS.d/next/Library/2026-11-05-07-30-00.gh-issue-160001.Zq4WnH.rst b/Misc/NEWS.d/next/Library/2026-11-05-07-30-00.gh-issue-160001.Zq4WnH.rst new file mode 100644 index 000000000000000..0e09743f828c9d6 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-11-05-07-30-00.gh-issue-160001.Zq4WnH.rst @@ -0,0 +1,4 @@ +:mod:`zlib`: Fix ``zlib._ZlibDecompressor`` failing with :exc:`SystemError` +instead of decompressing when a ``zdict`` is passed for a zlib-wrapped stream. +A stream that requires a dictionary which was not provided now raises +:exc:`zlib.error`, matching ``zlib.Decompress``. diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index 31533fa047af0b6..8b5d188a801b8dc 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -1534,13 +1534,21 @@ decompress_buf(ZlibDecompressor *self, Py_ssize_t max_length) break; default: if (err == Z_NEED_DICT) { - goto error; + if (self->zdict == NULL) { + zlib_error(state, self->zst, err, + "while decompressing data"); + goto error; + } + if (set_inflate_zdict_ZlibDecompressor(state, self) < 0) { + goto error; + } + break; } else { break; } } - } while (self->zst.avail_out == 0); + } while (self->zst.avail_out == 0 || err == Z_NEED_DICT); } while(err != Z_STREAM_END && self->avail_in_real != 0); if (err == Z_STREAM_END) {