https://github.com/python/cpython/commit/be7dbf98e7d5227f14bc73d8b3f422f60c431d60 commit: be7dbf98e7d5227f14bc73d8b3f422f60c431d60 branch: 3.15 author: Miss Islington (bot) <[email protected]> committer: gpshead <[email protected]> date: 2026-11-09T23:52:17-08:00 summary:
[3.15] gh-159643: Fix EOFError in tarfile stream mode with bz2, xz and zstd (GH-159647) (#161733) 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. --------- (cherry picked from commit 1ce279ed32c8a6b7850111a21efc39448926ab91) Co-authored-by: digit50 <[email protected]> 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 66a918ca47e099..3ad82113a06418 100644 --- a/Lib/tarfile.py +++ b/Lib/tarfile.py @@ -593,6 +593,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 e16da56af42f12..d892a424d555c4 100644 --- a/Lib/test/test_tarfile.py +++ b/Lib/test/test_tarfile.py @@ -1060,6 +1060,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]
