From 45efb225835ed97573bf1f3bd57c0f664d19886d Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 23 Sep 2014 21:33:52 +0300 Subject: [PATCH] Issue #21866: ZipFile.close() no longer writes ZIP64 central directory records if allowZip64 is false. --- Lib/test/test_zipfile64.py | 37 ++++++++++++++++++++++++++++++++++++- Lib/zipfile.py | 37 ++++++++++++++++++++++++------------- Misc/NEWS | 3 +++ 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/Lib/test/test_zipfile64.py b/Lib/test/test_zipfile64.py index a6f3dca4b9c..a87baaa8991 100644 --- a/Lib/test/test_zipfile64.py +++ b/Lib/test/test_zipfile64.py @@ -99,7 +99,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") + zipf = zipfile.ZipFile(TESTFN, mode="w", allowZip64=True) zipf.debug = 100 numfiles = (1 << 16) * 3/2 for i in xrange(numfiles): @@ -111,8 +111,43 @@ class OtherTests(unittest.TestCase): self.assertEqual(len(zipf2.namelist()), numfiles) for i in xrange(numfiles): self.assertEqual(zipf2.read("foo%08d" % i), "%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): + self.assertEqual(zipf2.read("foo%08d" % i), "%d" % (i**3 % 57)) + zipf2.close() + def tearDown(self): test_support.unlink(TESTFN) test_support.unlink(TESTFN2) diff --git a/Lib/zipfile.py b/Lib/zipfile.py index fe71765af02..6722c409978 100644 --- a/Lib/zipfile.py +++ b/Lib/zipfile.py @@ -30,7 +30,7 @@ class LargeZipFile(Exception): error = BadZipfile # The exception raised by this module 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 @@ -1101,12 +1101,17 @@ class ZipFile(object): if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED): raise RuntimeError, \ "That compression method is not supported" - 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 @@ -1256,10 +1261,8 @@ class ZipFile(object): 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) @@ -1320,13 +1323,21 @@ class ZipFile(object): 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 39f6fce3d1a..5b69a1b5955 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -22,6 +22,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.