https://github.com/python/cpython/commit/583d7b6881e95ed75a2a7b47b8b73225ecd07a51
commit: 583d7b6881e95ed75a2a7b47b8b73225ecd07a51
branch: 3.14
author: Gregory P. Smith <[email protected]>
committer: gpshead <[email protected]>
date: 2026-11-01T19:29:11Z
summary:
[3.14] gh-161094: Fix `zipfile.is_zipfile()` raising `ValueError` on file-like
objects (GH-161095) (#161121)
Return False instead of seeking to a negative offset when the end of
central directory record places the central directory before the start
of the file.
(cherry picked from commit 2789841a48f1edd8cfe1a9dc6e6c2b54c117d401)
files:
A Misc/NEWS.d/next/Library/2026-10-22-05-21-00.gh-issue-161094.MDjCNm.rst
M Lib/test/test_zipfile/test_core.py
M Lib/zipfile/__init__.py
diff --git a/Lib/test/test_zipfile/test_core.py
b/Lib/test/test_zipfile/test_core.py
index 33d2e51a42acaa7..8336d0f27a7cab7 100644
--- a/Lib/test/test_zipfile/test_core.py
+++ b/Lib/test/test_zipfile/test_core.py
@@ -2132,6 +2132,20 @@ def test_is_zip_valid_file(self):
self.assertTrue(zipfile.is_zipfile(fp))
self.assertEqual(fp.tell(), mid)
+ def test_is_zip_bad_central_directory_offset(self):
+ """Check that is_zipfile() rejects a central directory before the
file."""
+ fp = io.BytesIO()
+ with zipfile.ZipFile(fp, mode="w") as zipf:
+ zipf.writestr("foo.txt", b"O, for a Muse of Fire!")
+ data = bytearray(fp.getvalue())
+ endrec = len(data) - zipfile.sizeEndCentDir
+ struct.pack_into("<L", data, endrec + 12, len(data))
+ fp = io.BytesIO(data)
+ pos = fp.seek(5)
+ self.assertFalse(zipfile.is_zipfile(fp))
+ self.assertEqual(fp.tell(), pos)
+ self.assertRaises(zipfile.BadZipFile, zipfile.ZipFile,
io.BytesIO(data))
+
def test_non_existent_file_raises_OSError(self):
# make sure we don't raise an AttributeError when a
partially-constructed
# ZipFile instance is finalized; this tests for regression on SF
tracker
diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py
index b80000b8ef55eb3..cf0c69687be94f7 100644
--- a/Lib/zipfile/__init__.py
+++ b/Lib/zipfile/__init__.py
@@ -240,8 +240,9 @@ def _check_zipfile(fp):
return True # Empty zipfiles are still zipfiles
elif endrec[_ECD_DISK_NUMBER] == endrec[_ECD_DISK_START]:
# Central directory is on the same disk
- fp.seek(sum(_handle_prepended_data(endrec)))
- if endrec[_ECD_SIZE] >= sizeCentralDir:
+ start_dir = sum(_handle_prepended_data(endrec))
+ if start_dir >= 0 and endrec[_ECD_SIZE] >= sizeCentralDir:
+ fp.seek(start_dir)
data = fp.read(sizeCentralDir) # CD is where we expect
it to be
if len(data) == sizeCentralDir:
centdir = struct.unpack(structCentralDir, data) # CD
is the right size
diff --git
a/Misc/NEWS.d/next/Library/2026-10-22-05-21-00.gh-issue-161094.MDjCNm.rst
b/Misc/NEWS.d/next/Library/2026-10-22-05-21-00.gh-issue-161094.MDjCNm.rst
new file mode 100644
index 000000000000000..716da00504cc594
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-10-22-05-21-00.gh-issue-161094.MDjCNm.rst
@@ -0,0 +1,4 @@
+Fix :func:`zipfile.is_zipfile` raising :exc:`ValueError` instead of returning
+``False`` for a file-like object, such as :class:`io.BytesIO`, whose end of
+central directory record places the central directory before the start of the
+file.
_______________________________________________
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]