Skip to content

Commit 2f20f09

Browse files
authored
Merge pull request #284 from ptweezy/wbits-format-detection
Fix format detection in the `isal_zlib.decompressobj()` function with a `wbits` of 40 to 47
2 parents 5fc95ed + d90fe5f commit 2f20f09

3 files changed

Lines changed: 70 additions & 1 deletion

File tree

‎CHANGELOG.rst‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,13 @@ version 1.8.1-dev
4848
input buffer when ``max_length`` split the end of a stream. At the end of a
4949
stream, ``isal_zlib.Decompress.unconsumed_tail`` is empty, matching the
5050
CPython fix for gh-158169.
51+
+ Fix ``isal_zlib.decompressobj`` with a ``wbits`` of 40 to 47, which detects
52+
zlib or gzip data. It failed on gzip data when the first ``decompress()``
53+
call got fewer than 2 bytes, for example, when the data arrived byte by
54+
byte.
55+
+ Fix ``isal_zlib.decompressobj`` with a ``wbits`` of 40 to 47 after a
56+
``flush()`` call that came before any data. It then failed on both zlib and
57+
gzip data.
5158
+ ``igzip_threaded.open`` raises ``ValueError`` for a ``block_size`` of about
5259
3.6 GiB or more and ``TypeError`` for a ``compresslevel`` that is not an
5360
integer. Both raised ``SystemError`` before.

‎src/isal/isal_zlibmodule.c‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,10 +149,13 @@ static inline int zlib_mem_level_to_isal(int mem_level) {
149149
return ZLIB_MEM_LEVEL_TO_ISAL[mem_level];
150150
}
151151

152+
/* A gzip stream starts with 31, 139. A zlib stream cannot start with 31,
153+
because the low 4 bits of its first byte are the compression method 8.
154+
So the first byte is enough, even when the data arrives byte by byte. */
152155
static inline int
153156
data_is_gzip(Py_buffer *data){
154157
uint8_t *buf = (uint8_t *)data->buf;
155-
return (data->len > 1) && (buf[0] == 31) && (buf[1] == 139);
158+
return (data->len > 0) && (buf[0] == 31);
156159
}
157160

158161

@@ -1010,6 +1013,11 @@ isal_zlib_Decompress_decompress_impl(decompobject *self, Py_buffer *data,
10101013
ENTER_ZLIB(self);
10111014

10121015
if (!self->method_set) {
1016+
if (data->len == 0) {
1017+
/* Detect the format when the first byte arrives. */
1018+
LEAVE_ZLIB(self);
1019+
return PyBytes_FromStringAndSize(NULL, 0);
1020+
}
10131021
if (data_is_gzip(data)){
10141022
self->zst.crc_flag = ISAL_GZIP;
10151023
}
@@ -1181,6 +1189,14 @@ isal_zlib_Decompress_flush_impl(decompobject *self, Py_ssize_t length)
11811189

11821190
ENTER_ZLIB(self);
11831191

1192+
if (!self->method_set) {
1193+
/* No data yet, so the format is unknown. isal_inflate would start a
1194+
raw deflate block, and then decode the zlib or gzip header that
1195+
comes next as deflate data. */
1196+
LEAVE_ZLIB(self);
1197+
return PyBytes_FromStringAndSize(NULL, 0);
1198+
}
1199+
11841200
if (PyObject_GetBuffer(self->unconsumed_tail, &data, PyBUF_SIMPLE) == -1) {
11851201
LEAVE_ZLIB(self);
11861202
return NULL;

‎tests/test_compat.py‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,52 @@ def test_unused_data(unused_size, wbits):
263263
assert decompressor.unused_data == unused_data
264264

265265

266+
@pytest.mark.parametrize("wbits", [15, 31])
267+
def test_decompressobj_detects_format_byte_by_byte(wbits):
268+
data = DATA[:1000]
269+
compressor = zlib.compressobj(wbits=wbits)
270+
compressed = compressor.compress(data) + compressor.flush()
271+
decompressor = isal_zlib.decompressobj(wbits=47)
272+
result = b"".join(decompressor.decompress(compressed[i:i + 1])
273+
for i in range(len(compressed)))
274+
assert result == data
275+
assert decompressor.eof
276+
277+
278+
@pytest.mark.parametrize("wbits", [15, 31])
279+
def test_decompressobj_detects_format_after_empty_input(wbits):
280+
data = DATA[:1000]
281+
compressor = zlib.compressobj(wbits=wbits)
282+
compressed = compressor.compress(data) + compressor.flush()
283+
for module in (zlib, isal_zlib):
284+
decompressor = module.decompressobj(wbits=47)
285+
assert decompressor.decompress(b"") == b""
286+
assert decompressor.decompress(compressed[:1]) == b""
287+
assert decompressor.decompress(compressed[1:] + b"unused") == data
288+
assert decompressor.eof
289+
assert decompressor.unused_data == b"unused"
290+
291+
292+
@pytest.mark.parametrize("wbits", [15, 31])
293+
def test_decompressobj_detects_format_after_flush(wbits):
294+
data = DATA[:1000]
295+
compressor = zlib.compressobj(wbits=wbits)
296+
compressed = compressor.compress(data) + compressor.flush()
297+
for module in (zlib, isal_zlib):
298+
decompressor = module.decompressobj(wbits=47)
299+
assert decompressor.flush() == b""
300+
assert decompressor.decompress(compressed) == data
301+
assert decompressor.eof
302+
303+
304+
@pytest.mark.parametrize("data", [b"", b"\x1f", b"\x78"])
305+
def test_decompress_detect_format_incomplete(data):
306+
with pytest.raises(zlib.error):
307+
zlib.decompress(data, wbits=47)
308+
with pytest.raises(isal_zlib.error):
309+
isal_zlib.decompress(data, wbits=47)
310+
311+
266312
@pytest.mark.parametrize("wbits", [-15, 15, 31])
267313
def test_decompressobj_unconsumed_tail_after_eof(wbits):
268314
source = b"abcdefghijklmnopqrstuvwxyz"

0 commit comments

Comments
 (0)