From 82d68c7fb5dca009cdc8cf9e2d04cf72cc151703 Mon Sep 17 00:00:00 2001 From: Vyron Vasileiadis Date: Wed, 29 Jul 2026 00:38:36 +0300 Subject: [PATCH] gh-154842: Reject repack() while a reading handle is open ZipFile.repack() moves member data, but a ZipExtFile from an earlier open() keeps its own absolute position, so it silently returned data from the wrong place and a full read failed with a misleading CRC error. Raise ValueError while _fileRefCnt shows an open reading handle, as the writing-handle case already does. --- Doc/library/zipfile.rst | 4 +++- Lib/test/test_zipfile/test_core.py | 12 ++++++++++++ Lib/zipfile/__init__.py | 5 +++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Doc/library/zipfile.rst b/Doc/library/zipfile.rst index 98d2a5e5cdf00e..d5e9348ec8ba00 100644 --- a/Doc/library/zipfile.rst +++ b/Doc/library/zipfile.rst @@ -581,7 +581,9 @@ ZipFile objects strict_descriptor=True[, chunk_size]) Rewrites the archive to remove unreferenced local file entries, shrinking - its file size. The archive must be opened with mode ``'a'``. + its file size. The archive must be opened with mode ``'a'``, and any file + object returned by :meth:`ZipFile.open` must be closed first, since + repacking moves the member data those objects read from. If *removed* is provided, it must be a sequence of :class:`ZipInfo` objects representing the recently removed members, and only their corresponding diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index cd498ba13e6f46..11a01d8daacf4d 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -2382,6 +2382,18 @@ def test_repack_writing(self, m_repack): zh.repack() m_repack.assert_not_called() + @mock.patch.object(zipfile, '_ZipRepacker') + def test_repack_reading(self, m_repack): + self._prepare_zip_from_test_files(TESTFN, self.test_files) + with zipfile.ZipFile(TESTFN, 'a') as zh: + with zh.open(self.test_files[0][0]): + with self.assertRaises(ValueError): + zh.repack() + m_repack.assert_not_called() + # Allowed once the reading handle is closed. + zh.repack() + m_repack.assert_called_once() + @mock.patch.object(zipfile, '_ZipRepacker') def test_repack_mode_r(self, m_repack): self._prepare_zip_from_test_files(TESTFN, self.test_files) diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py index 418933a2e8d9e8..aea5678ac84c29 100644 --- a/Lib/zipfile/__init__.py +++ b/Lib/zipfile/__init__.py @@ -2404,6 +2404,11 @@ def repack(self, removed=None, *, strict_descriptor=True, raise ValueError( "Can't write to ZIP archive while an open writing handle exists" ) + if self._fileRefCnt > 1: + raise ValueError( + "Can't repack the ZIP archive while an open reading handle " + "exists. Close the reading handle before repacking." + ) with self._lock: self._writing = True