https://github.com/python/cpython/commit/2789841a48f1edd8cfe1a9dc6e6c2b54c117d401
commit: 2789841a48f1edd8cfe1a9dc6e6c2b54c117d401
branch: main
author: Gregory P. Smith <[email protected]>
committer: gpshead <[email protected]>
date: 2026-11-01T18:23:38Z
summary:
gh-161094: Fix `zipfile.is_zipfile()` raising `ValueError` on file-like objects
(GH-161095)
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.
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 ab1995e91286e68..84000f472c4df9e 100644
--- a/Lib/test/test_zipfile/test_core.py
+++ b/Lib/test/test_zipfile/test_core.py
@@ -4300,6 +4300,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 5963f84bdad2996..3110759b29f2713 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]