diff --git a/Lib/test/test_zipfile64.py b/Lib/test/test_zipfile64.py index 498d464db3e..7dea8a32120 100644 --- a/Lib/test/test_zipfile64.py +++ b/Lib/test/test_zipfile64.py @@ -18,7 +18,7 @@ import sys from io import StringIO from tempfile import TemporaryFile -from test.support import TESTFN, run_unittest, requires_zlib +from test.support import TESTFN, requires_zlib TESTFN2 = TESTFN + "2" @@ -92,7 +92,7 @@ class OtherTests(unittest.TestCase): def testMoreThan64kFiles(self): # This test checks that more than 64k files can be added to an archive, # and that the resulting archive can be read properly by ZipFile - zipf = zipfile.ZipFile(TESTFN, mode="w", allowZip64=False) + zipf = zipfile.ZipFile(TESTFN, mode="w", allowZip64=True) zipf.debug = 100 numfiles = (1 << 16) * 3//2 for i in range(numfiles): @@ -105,14 +105,47 @@ class OtherTests(unittest.TestCase): for i in range(numfiles): content = zipf2.read("foo%08d" % i).decode('ascii') self.assertEqual(content, "%d" % (i**3 % 57)) + zipf2.close() + + def testMoreThan64kFilesAppend(self): + zipf = zipfile.ZipFile(TESTFN, mode="w", allowZip64=False) + zipf.debug = 100 + numfiles = (1 << 16) - 1 + for i in range(numfiles): + zipf.writestr("foo%08d" % i, "%d" % (i**3 % 57)) + self.assertEqual(len(zipf.namelist()), numfiles) + with self.assertRaises(zipfile.LargeZipFile): + zipf.writestr("foo%08d" % numfiles, b'') + self.assertEqual(len(zipf.namelist()), numfiles) zipf.close() + zipf = zipfile.ZipFile(TESTFN, mode="a", allowZip64=False) + zipf.debug = 100 + self.assertEqual(len(zipf.namelist()), numfiles) + with self.assertRaises(zipfile.LargeZipFile): + zipf.writestr("foo%08d" % numfiles, b'') + self.assertEqual(len(zipf.namelist()), numfiles) + zipf.close() + + zipf = zipfile.ZipFile(TESTFN, mode="a", allowZip64=True) + zipf.debug = 100 + self.assertEqual(len(zipf.namelist()), numfiles) + numfiles2 = (1 << 16) * 3//2 + for i in range(numfiles, numfiles2): + zipf.writestr("foo%08d" % i, "%d" % (i**3 % 57)) + self.assertEqual(len(zipf.namelist()), numfiles2) + zipf.close() + + zipf2 = zipfile.ZipFile(TESTFN, mode="r") + self.assertEqual(len(zipf2.namelist()), numfiles2) + for i in range(numfiles2): + content = zipf2.read("foo%08d" % i).decode('ascii') + self.assertEqual(content, "%d" % (i**3 % 57)) + zipf2.close() + def tearDown(self): support.unlink(TESTFN) support.unlink(TESTFN2) -def test_main(): - run_unittest(TestsWithSourceFile, OtherTests) - if __name__ == "__main__": - test_main() + unittest.main() diff --git a/Lib/zipfile.py b/Lib/zipfile.py index c57765753f1..e5b78186352 100644 --- a/Lib/zipfile.py +++ b/Lib/zipfile.py @@ -50,7 +50,7 @@ error = BadZipfile = BadZipFile # Pre-3.2 compatibility names ZIP64_LIMIT = (1 << 31) - 1 -ZIP_FILECOUNT_LIMIT = 1 << 16 +ZIP_FILECOUNT_LIMIT = (1 << 16) - 1 ZIP_MAX_COMMENT = (1 << 16) - 1 # constants for Zip file compression methods @@ -1304,13 +1304,17 @@ class ZipFile: raise RuntimeError( "Attempt to write ZIP archive that was already closed") _check_compression(zinfo.compress_type) - if zinfo.file_size > ZIP64_LIMIT: - if not self._allowZip64: - raise LargeZipFile("Filesize would require ZIP64 extensions") - if zinfo.header_offset > ZIP64_LIMIT: - if not self._allowZip64: - raise LargeZipFile( - "Zipfile size would require ZIP64 extensions") + if not self._allowZip64: + requires_zip64 = None + if len(self.filelist) >= ZIP_FILECOUNT_LIMIT: + requires_zip64 = "Files count" + elif zinfo.file_size > ZIP64_LIMIT: + requires_zip64 = "Filesize" + elif zinfo.header_offset > ZIP64_LIMIT: + requires_zip64 = "Zipfile size" + if requires_zip64: + raise LargeZipFile(requires_zip64 + + " would require ZIP64 extensions") def write(self, filename, arcname=None, compress_type=None): """Put the bytes from filename into the archive under the name @@ -1464,10 +1468,8 @@ class ZipFile: try: if self.mode in ("w", "a") and self._didModify: # write ending records - count = 0 pos1 = self.fp.tell() for zinfo in self.filelist: # write central directory - count = count + 1 dt = zinfo.date_time dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2) @@ -1531,13 +1533,21 @@ class ZipFile: pos2 = self.fp.tell() # Write end-of-zip-archive record - centDirCount = count + centDirCount = len(self.filelist) centDirSize = pos2 - pos1 centDirOffset = pos1 - if (centDirCount >= ZIP_FILECOUNT_LIMIT or - centDirOffset > ZIP64_LIMIT or - centDirSize > ZIP64_LIMIT): + requires_zip64 = None + if centDirCount > ZIP_FILECOUNT_LIMIT: + requires_zip64 = "Files count" + elif centDirOffset > ZIP64_LIMIT: + requires_zip64 = "Central directory offset" + elif centDirSize > ZIP64_LIMIT: + requires_zip64 = "Central directory size" + if requires_zip64: # Need to write the ZIP64 end-of-archive records + if not self._allowZip64: + raise LargeZipFile(requires_zip64 + + " would require ZIP64 extensions") zip64endrec = struct.pack( structEndArchive64, stringEndArchive64, 44, 45, 45, 0, 0, centDirCount, centDirCount, diff --git a/Misc/NEWS b/Misc/NEWS index bf5dd5b8542..df1be15f013 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -13,6 +13,9 @@ Core and Builtins Library ------- +- Issue #21866: ZipFile.close() no longer writes ZIP64 central directory + records if allowZip64 is false. + - Issue #22415: Fixed debugging output of the GROUPREF_EXISTS opcode in the re module. Removed trailing spaces in debugging output.