Skip to content

Commit 30b2efc

Browse files
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.
1 parent 341f80e commit 30b2efc

3 files changed

Lines changed: 53 additions & 2 deletions

File tree

‎Lib/test/test_zlib.py‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1153,6 +1153,45 @@ def testDecompress(self):
11531153
text = zlibd.decompress(self.DATA)
11541154
self.assertEqual(text, self.TEXT)
11551155

1156+
def testDecompressZdictWrapped(self):
1157+
# gh-160001: for a zlib-wrapped stream zlib only announces the
1158+
# dictionary (``Z_NEED_DICT``) after the header, so it has to be
1159+
# applied from the decompression loop rather than the constructor.
1160+
zdict = b"hello world " * 10
1161+
compressor = zlib.compressobj(zdict=zdict)
1162+
data = compressor.compress(b"hello world hello") + compressor.flush()
1163+
1164+
decompressor = zlib._ZlibDecompressor(zdict=zdict)
1165+
self.assertEqual(decompressor.decompress(data), b"hello world hello")
1166+
1167+
def testDecompressZdictWrappedChunks(self):
1168+
zdict = b"hello world " * 10
1169+
compressor = zlib.compressobj(zdict=zdict)
1170+
data = compressor.compress(b"hello world hello") + compressor.flush()
1171+
1172+
decompressor = zlib._ZlibDecompressor(zdict=zdict)
1173+
result = b""
1174+
for index in range(0, len(data), 3):
1175+
result += decompressor.decompress(data[index:index + 3])
1176+
1177+
self.assertEqual(result, b"hello world hello")
1178+
1179+
def testDecompressZdictRaw(self):
1180+
zdict = b"hello world " * 10
1181+
compressor = zlib.compressobj(wbits=-zlib.MAX_WBITS, zdict=zdict)
1182+
data = compressor.compress(b"hello world hello") + compressor.flush()
1183+
1184+
decompressor = zlib._ZlibDecompressor(-zlib.MAX_WBITS, zdict=zdict)
1185+
self.assertEqual(decompressor.decompress(data), b"hello world hello")
1186+
1187+
def testDecompressFdictHeaderWithoutZdict(self):
1188+
# A zlib header with the FDICT flag set asks for a dictionary that was
1189+
# never provided; that is a zlib error, not an internal error.
1190+
fdict_header = b"\x78\xbb" + b"\x00\x00\x00\x01\x03\x00"
1191+
1192+
with self.assertRaises(zlib.error):
1193+
zlib._ZlibDecompressor().decompress(fdict_header)
1194+
11561195
def testDecompressChunks10(self):
11571196
zlibd = zlib._ZlibDecompressor()
11581197
text = b''
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
:mod:`zlib`: Fix ``zlib._ZlibDecompressor`` failing with :exc:`SystemError`
2+
instead of decompressing when a ``zdict`` is passed for a zlib-wrapped stream.
3+
A stream that requires a dictionary which was not provided now raises
4+
:exc:`zlib.error`, matching ``zlib.Decompress``.

‎Modules/zlibmodule.c‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,13 +1534,21 @@ decompress_buf(ZlibDecompressor *self, Py_ssize_t max_length)
15341534
break;
15351535
default:
15361536
if (err == Z_NEED_DICT) {
1537-
goto error;
1537+
if (self->zdict == NULL) {
1538+
zlib_error(state, self->zst, err,
1539+
"while decompressing data");
1540+
goto error;
1541+
}
1542+
if (set_inflate_zdict_ZlibDecompressor(state, self) < 0) {
1543+
goto error;
1544+
}
1545+
break;
15381546
}
15391547
else {
15401548
break;
15411549
}
15421550
}
1543-
} while (self->zst.avail_out == 0);
1551+
} while (self->zst.avail_out == 0 || err == Z_NEED_DICT);
15441552
} while(err != Z_STREAM_END && self->avail_in_real != 0);
15451553

15461554
if (err == Z_STREAM_END) {

0 commit comments

Comments
 (0)