From 7d19724e370f383ce003536e7c02b384a3c913fa Mon Sep 17 00:00:00 2001 From: Samartha <136691411+gruffelixir@users.noreply.github.com> Date: Fri, 25 Sep 2026 22:26:30 +0530 Subject: [PATCH 1/3] gh-158169: Clear unconsumed_tail at EOF --- Lib/test/test_zlib.py | 12 ++++++++++++ .../2026-09-25-22-15-54.gh-issue-158169.ZlibTail.rst | 2 ++ Modules/zlibmodule.c | 2 ++ 3 files changed, 16 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-09-25-22-15-54.gh-issue-158169.ZlibTail.rst diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py index 205916579b5f49d..5d9df19d5560352 100644 --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -822,6 +822,18 @@ def test_decompress_unused_data(self): self.assertEqual(dco.unconsumed_tail, b'') self.assertEqual(dco.unused_data, remainder) + def test_decompress_unconsumed_tail_after_eof(self): + source = bytes(range(256)) * 4 + remainder = b'extra' + dco = zlib.decompressobj() + chunks = [dco.decompress(zlib.compress(source) + remainder, 100)] + while not dco.eof: + chunks.append(dco.decompress(dco.unconsumed_tail, 100)) + + self.assertEqual(b''.join(chunks), source) + self.assertEqual(dco.unconsumed_tail, b'') + self.assertEqual(dco.unused_data, remainder) + # issue27164 def test_decompress_raw_with_dictionary(self): zdict = b'abcdefghijklmnopqrstuvwxyz' diff --git a/Misc/NEWS.d/next/Library/2026-09-25-22-15-54.gh-issue-158169.ZlibTail.rst b/Misc/NEWS.d/next/Library/2026-09-25-22-15-54.gh-issue-158169.ZlibTail.rst new file mode 100644 index 000000000000000..f8913f819b89f5e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-25-22-15-54.gh-issue-158169.ZlibTail.rst @@ -0,0 +1,2 @@ +Clear ``Decompress.unconsumed_tail`` once decompression reaches EOF. Trailing +input is available only through ``Decompress.unused_data``. diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index 56c119db7d10c05..7efbd3d49088cbb 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -838,6 +838,8 @@ save_unconsumed_input(compobject *self, Py_buffer *data, int err) return -1; } Py_SETREF(self->unused_data, new_unused_data); + /* These bytes are now in unused_data, not unconsumed_tail. */ + self->zst.next_in += left_size; self->zst.avail_in = 0; } } From 36be2bcfd3c0fd7234bc82d69fa206064dfe6447 Mon Sep 17 00:00:00 2001 From: Samartha <136691411+gruffelixir@users.noreply.github.com> Date: Sat, 26 Sep 2026 02:26:43 +0530 Subject: [PATCH 2/3] gh-158169: Simplify zlib EOF regression test --- Lib/test/test_zlib.py | 12 ++++++------ .../2026-09-25-22-15-54.gh-issue-158169.ZlibTail.rst | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_zlib.py b/Lib/test/test_zlib.py index 5d9df19d5560352..dcb4eac1b779520 100644 --- a/Lib/test/test_zlib.py +++ b/Lib/test/test_zlib.py @@ -823,14 +823,14 @@ def test_decompress_unused_data(self): self.assertEqual(dco.unused_data, remainder) def test_decompress_unconsumed_tail_after_eof(self): - source = bytes(range(256)) * 4 - remainder = b'extra' + source = b'abcdefghijklmnopqrstuvwxyz' + remainder = b'0123456789' dco = zlib.decompressobj() - chunks = [dco.decompress(zlib.compress(source) + remainder, 100)] - while not dco.eof: - chunks.append(dco.decompress(dco.unconsumed_tail, 100)) + data = dco.decompress(zlib.compress(source) + remainder, 1) + data += dco.decompress(dco.unconsumed_tail) - self.assertEqual(b''.join(chunks), source) + self.assertTrue(dco.eof) + self.assertEqual(data, source) self.assertEqual(dco.unconsumed_tail, b'') self.assertEqual(dco.unused_data, remainder) diff --git a/Misc/NEWS.d/next/Library/2026-09-25-22-15-54.gh-issue-158169.ZlibTail.rst b/Misc/NEWS.d/next/Library/2026-09-25-22-15-54.gh-issue-158169.ZlibTail.rst index f8913f819b89f5e..7c965451c056a44 100644 --- a/Misc/NEWS.d/next/Library/2026-09-25-22-15-54.gh-issue-158169.ZlibTail.rst +++ b/Misc/NEWS.d/next/Library/2026-09-25-22-15-54.gh-issue-158169.ZlibTail.rst @@ -1,2 +1,2 @@ -Clear ``Decompress.unconsumed_tail`` once decompression reaches EOF. Trailing -input is available only through ``Decompress.unused_data``. +Fix :attr:`zlib.Decompress.unconsumed_tail` duplicating +:attr:`~zlib.Decompress.unused_data` after end of stream when *max_length* was used. From c85a9ae7043fb97fe8f45f7fd84014b474cb9a2f Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Sat, 26 Sep 2026 10:00:43 +0100 Subject: [PATCH 3/3] Apply suggestion from @StanFromIreland --- Modules/zlibmodule.c | 1 - 1 file changed, 1 deletion(-) diff --git a/Modules/zlibmodule.c b/Modules/zlibmodule.c index 7efbd3d49088cbb..31533fa047af0b6 100644 --- a/Modules/zlibmodule.c +++ b/Modules/zlibmodule.c @@ -838,7 +838,6 @@ save_unconsumed_input(compobject *self, Py_buffer *data, int err) return -1; } Py_SETREF(self->unused_data, new_unused_data); - /* These bytes are now in unused_data, not unconsumed_tail. */ self->zst.next_in += left_size; self->zst.avail_in = 0; }