From 04f17f103afcee73c65709252b0dba6411df7665 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 31 Oct 2016 08:30:09 +0200 Subject: [PATCH] Issue #27517: LZMA compressor and decompressor no longer raise exceptions if given empty data twice. Patch by Benjamin Fogle. --- Lib/test/test_lzma.py | 38 ++++++++++++++++++++++++++++++++++++++ Misc/ACKS | 1 + Misc/NEWS | 3 +++ Modules/_lzmamodule.c | 5 +++++ 4 files changed, 47 insertions(+) diff --git a/Lib/test/test_lzma.py b/Lib/test/test_lzma.py index 16e89d5a9e9..b69da065e1d 100644 --- a/Lib/test/test_lzma.py +++ b/Lib/test/test_lzma.py @@ -136,6 +136,21 @@ class CompressorDecompressorTestCase(unittest.TestCase): self.assertTrue(lzd.eof) self.assertEqual(lzd.unused_data, b"") + def test_decompressor_chunks_empty(self): + lzd = LZMADecompressor() + out = [] + for i in range(0, len(COMPRESSED_XZ), 10): + self.assertFalse(lzd.eof) + out.append(lzd.decompress(b'')) + out.append(lzd.decompress(b'')) + out.append(lzd.decompress(b'')) + out.append(lzd.decompress(COMPRESSED_XZ[i:i+10])) + out = b"".join(out) + self.assertEqual(out, INPUT) + self.assertEqual(lzd.check, lzma.CHECK_CRC64) + self.assertTrue(lzd.eof) + self.assertEqual(lzd.unused_data, b"") + def test_decompressor_chunks_maxsize(self): lzd = LZMADecompressor() max_length = 100 @@ -273,6 +288,16 @@ class CompressorDecompressorTestCase(unittest.TestCase): lzd = LZMADecompressor(lzma.FORMAT_RAW, filters=FILTERS_RAW_4) self._test_decompressor(lzd, cdata, lzma.CHECK_NONE) + def test_roundtrip_raw_empty(self): + lzc = LZMACompressor(lzma.FORMAT_RAW, filters=FILTERS_RAW_4) + cdata = lzc.compress(INPUT) + cdata += lzc.compress(b'') + cdata += lzc.compress(b'') + cdata += lzc.compress(b'') + cdata += lzc.flush() + lzd = LZMADecompressor(lzma.FORMAT_RAW, filters=FILTERS_RAW_4) + self._test_decompressor(lzd, cdata, lzma.CHECK_NONE) + def test_roundtrip_chunks(self): lzc = LZMACompressor() cdata = [] @@ -283,6 +308,19 @@ class CompressorDecompressorTestCase(unittest.TestCase): lzd = LZMADecompressor() self._test_decompressor(lzd, cdata, lzma.CHECK_CRC64) + def test_roundtrip_empty_chunks(self): + lzc = LZMACompressor() + cdata = [] + for i in range(0, len(INPUT), 10): + cdata.append(lzc.compress(INPUT[i:i+10])) + cdata.append(lzc.compress(b'')) + cdata.append(lzc.compress(b'')) + cdata.append(lzc.compress(b'')) + cdata.append(lzc.flush()) + cdata = b"".join(cdata) + lzd = LZMADecompressor() + self._test_decompressor(lzd, cdata, lzma.CHECK_CRC64) + # LZMADecompressor intentionally does not handle concatenated streams. def test_decompressor_multistream(self): diff --git a/Misc/ACKS b/Misc/ACKS index 2ba9ea14cc7..46d6adf4134 100644 --- a/Misc/ACKS +++ b/Misc/ACKS @@ -446,6 +446,7 @@ Frederik Fix Tom Flanagan Matt Fleming Hernán Martínez Foffani +Benjamin Fogle Artem Fokin Arnaud Fontaine Michael Foord diff --git a/Misc/NEWS b/Misc/NEWS index d7f3d28264a..808501b3fcc 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -113,6 +113,9 @@ Core and Builtins Library ------- +- Issue #27517: LZMA compressor and decompressor no longer raise exceptions if + given empty data twice. Patch by Benjamin Fogle. + - Issue #28549: Fixed segfault in curses's addch() with ncurses6. - Issue #28449: tarfile.open() with mode "r" or "r:" now tries to open a tar diff --git a/Modules/_lzmamodule.c b/Modules/_lzmamodule.c index 74c301d47ad..e7b1ed9fb2b 100644 --- a/Modules/_lzmamodule.c +++ b/Modules/_lzmamodule.c @@ -527,6 +527,8 @@ compress(Compressor *c, uint8_t *data, size_t len, lzma_action action) Py_BEGIN_ALLOW_THREADS lzret = lzma_code(&c->lzs, action); data_size = (char *)c->lzs.next_out - PyBytes_AS_STRING(result); + if (lzret == LZMA_BUF_ERROR && len == 0 && c->lzs.avail_out > 0) + lzret = LZMA_OK; /* That wasn't a real error */ Py_END_ALLOW_THREADS if (catch_lzma_error(lzret)) goto error; @@ -906,6 +908,9 @@ decompress_buf(Decompressor *d, Py_ssize_t max_length) PyObject *result; lzma_stream *lzs = &d->lzs; + if (lzs->avail_in == 0) + return PyBytes_FromStringAndSize(NULL, 0); + if (max_length < 0 || max_length >= INITIAL_BUFFER_SIZE) result = PyBytes_FromStringAndSize(NULL, INITIAL_BUFFER_SIZE); else