https://github.com/python/cpython/commit/1ce279ed32c8a6b7850111a21efc39448926ab91
commit: 1ce279ed32c8a6b7850111a21efc39448926ab91
branch: main
author: digit50 <[email protected]>
committer: gpshead <[email protected]>
date: 2026-11-10T06:45:40Z
summary:

gh-159643: Fix EOFError in tarfile stream mode with bz2, xz and zstd (GH-159647)

In stream mode (`r|bz2`, `r|xz`, `r|zst`, and `r|*` when it detects one of 
them), `_Stream._read` called `decompress(b"")` on a decompressor that had 
already reached the end of its data, because an exhausted `BZ2Decompressor`, 
`LZMADecompressor` or `ZstdDecompressor` reports `needs_input` as false. That 
raised a bare `EOFError` whenever tarfile read past the end of the compressed 
stream, for example with `ignore_zeros=True` or on an archive without 
end-of-archive blocks.

Stop the read loop once the decompressor reports `eof`. This restores the 
pre-gh-121109 results for the cases above. Data after the end of the compressed 
stream is now always ignored; before gh-121109 it was ignored only if it 
arrived in the same read as the end of the stream, and raised `EOFError` 
otherwise. The gzip branch is unchanged.

---------

Co-authored-by: Gregory P. Smith <[email protected]>

files:
A Misc/NEWS.d/next/Library/2026-10-17-19-33-13.gh-issue-159643.EJfe0IXetV.rst
M Lib/tarfile.py
M Lib/test/test_tarfile.py

diff --git a/Lib/tarfile.py b/Lib/tarfile.py
index abf7786812a063..b052ca0dd40729 100644
--- a/Lib/tarfile.py
+++ b/Lib/tarfile.py
@@ -594,6 +594,8 @@ def _read(self, size):
             else:
                 # Other decompressors have needs_input.
                 # decompress() can buffer data internally.
+                if self.cmp.eof:
+                    break
                 if self.cmp.needs_input:
                     cbuf = self.fileobj.read(self.bufsize)
                     if not cbuf:
diff --git a/Lib/test/test_tarfile.py b/Lib/test/test_tarfile.py
index 2ad6d0452ce29a..1927ff227d5356 100644
--- a/Lib/test/test_tarfile.py
+++ b/Lib/test/test_tarfile.py
@@ -1061,6 +1061,44 @@ def test_compare_members(self):
         finally:
             tar1.close()
 
+    def test_ignore_zeros_concatenated(self):
+        # gh-159643: reading past the end of the compressed data
+        # must not raise EOFError.
+        with self.open(tmpname, "wb") as fobj:
+            for name in ("a", "b"):
+                with tarfile.open(fileobj=fobj, mode="w") as tar:
+                    tar.addfile(tarfile.TarInfo(name))
+        with tarfile.open(tmpname, self.mode, ignore_zeros=True) as tar:
+            self.assertEqual(tar.getnames(), ["a", "b"])
+
+    def test_no_end_of_archive_blocks(self):
+        # gh-159643
+        tarinfo = tarfile.TarInfo("a")
+        tarinfo.size = 5
+        with self.open(tmpname, "wb") as fobj:
+            fobj.write(tarinfo.tobuf())
+            fobj.write(b"hello".ljust(tarfile.BLOCKSIZE, b"\0"))
+        with tarfile.open(tmpname, self.mode) as tar:
+            self.assertEqual(tar.getnames(), ["a"])
+
+    def test_data_after_end_of_stream(self):
+        # gh-159643: with a small bufsize, the compressed data spans
+        # several reads of the file and the padding that follows it
+        # is not read together with the end of the compressed data.
+        bufsize = tarfile.BLOCKSIZE
+        data = Random(0).randbytes(8 * bufsize)
+        tarinfo = tarfile.TarInfo("a")
+        tarinfo.size = len(data)
+        with self.open(tmpname, "wb") as fobj:
+            with tarfile.open(fileobj=fobj, mode="w") as tar:
+                tar.addfile(tarinfo, io.BytesIO(data))
+        with open(tmpname, "ab") as fobj:
+            fobj.write(bytes(4 * bufsize))
+        with tarfile.open(tmpname, self.mode, bufsize=bufsize,
+                          ignore_zeros=True) as tar:
+            members = [(m.name, tar.extractfile(m).read()) for m in tar]
+        self.assertEqual(members, [("a", data)])
+
 class GzipStreamReadTest(GzipTest, GzipReadTestBase, StreamReadTest):
     pass
 
diff --git 
a/Misc/NEWS.d/next/Library/2026-10-17-19-33-13.gh-issue-159643.EJfe0IXetV.rst 
b/Misc/NEWS.d/next/Library/2026-10-17-19-33-13.gh-issue-159643.EJfe0IXetV.rst
new file mode 100644
index 00000000000000..a76e54de38cc4e
--- /dev/null
+++ 
b/Misc/NEWS.d/next/Library/2026-10-17-19-33-13.gh-issue-159643.EJfe0IXetV.rst
@@ -0,0 +1,3 @@
+Fix :exc:`EOFError` raised by :mod:`tarfile` in stream mode with bz2, xz or
+zstd compression when reading past the end of the compressed data, for
+example with *ignore_zeros* or an archive without end-of-archive blocks.

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to