mirror of
				https://github.com/python/cpython.git
				synced 2025-10-30 21:21:22 +00:00 
			
		
		
		
	Patch #1446489 (zipfile: support for ZIP64)
This commit is contained in:
		
							parent
							
								
									0eac11826a
								
							
						
					
					
						commit
						143cefb846
					
				
					 5 changed files with 665 additions and 63 deletions
				
			
		|  | @ -17,7 +17,8 @@ understanding of the format, as defined in | ||||||
| Note}. | Note}. | ||||||
| 
 | 
 | ||||||
| This module does not currently handle ZIP files which have appended | This module does not currently handle ZIP files which have appended | ||||||
| comments, or multi-disk ZIP files. | comments, or multi-disk ZIP files. It can handle ZIP files that use the  | ||||||
|  | ZIP64 extensions (that is ZIP files that are more than 4 GByte in size). | ||||||
| 
 | 
 | ||||||
| The available attributes of this module are: | The available attributes of this module are: | ||||||
| 
 | 
 | ||||||
|  | @ -25,6 +26,11 @@ The available attributes of this module are: | ||||||
|   The error raised for bad ZIP files. |   The error raised for bad ZIP files. | ||||||
| \end{excdesc} | \end{excdesc} | ||||||
| 
 | 
 | ||||||
|  | \begin{excdesc}{LargeZipFile} | ||||||
|  |   The error raised when a ZIP file would require ZIP64 functionality but that | ||||||
|  |   has not been enabled. | ||||||
|  | \end{excdesc} | ||||||
|  | 
 | ||||||
| \begin{classdesc*}{ZipFile} | \begin{classdesc*}{ZipFile} | ||||||
|   The class for reading and writing ZIP files.  See |   The class for reading and writing ZIP files.  See | ||||||
|   ``\citetitle{ZipFile Objects}'' (section \ref{zipfile-objects}) for |   ``\citetitle{ZipFile Objects}'' (section \ref{zipfile-objects}) for | ||||||
|  | @ -77,7 +83,7 @@ The available attributes of this module are: | ||||||
| 
 | 
 | ||||||
| \subsection{ZipFile Objects \label{zipfile-objects}} | \subsection{ZipFile Objects \label{zipfile-objects}} | ||||||
| 
 | 
 | ||||||
| \begin{classdesc}{ZipFile}{file\optional{, mode\optional{, compression}}}  | \begin{classdesc}{ZipFile}{file\optional{, mode\optional{, compression\optional{, allowZip64}}}}  | ||||||
|   Open a ZIP file, where \var{file} can be either a path to a file |   Open a ZIP file, where \var{file} can be either a path to a file | ||||||
|   (a string) or a file-like object.  The \var{mode} parameter |   (a string) or a file-like object.  The \var{mode} parameter | ||||||
|   should be \code{'r'} to read an existing file, \code{'w'} to |   should be \code{'r'} to read an existing file, \code{'w'} to | ||||||
|  | @ -100,6 +106,12 @@ cat myzip.zip >> python.exe | ||||||
|   is specified but the \refmodule{zlib} module is not available, |   is specified but the \refmodule{zlib} module is not available, | ||||||
|   \exception{RuntimeError} is also raised.  The default is |   \exception{RuntimeError} is also raised.  The default is | ||||||
|   \constant{ZIP_STORED}.  |   \constant{ZIP_STORED}.  | ||||||
|  |   If \var{allowZip64} is \code{True} zipfile will create zipfiles that use | ||||||
|  |   the ZIP64 extensions when the zipfile is larger than 2GBytes. If it is  | ||||||
|  |   false (the default) zipfile will raise an exception when the zipfile would | ||||||
|  |   require ZIP64 extensions. ZIP64 extensions are disabled by default because | ||||||
|  |   the default zip and unzip commands on Unix (the InfoZIP utilities) don't  | ||||||
|  |   support these extensions. | ||||||
| \end{classdesc} | \end{classdesc} | ||||||
| 
 | 
 | ||||||
| \begin{methoddesc}{close}{} | \begin{methoddesc}{close}{} | ||||||
|  | @ -132,8 +144,8 @@ cat myzip.zip >> python.exe | ||||||
| \end{methoddesc} | \end{methoddesc} | ||||||
| 
 | 
 | ||||||
| \begin{methoddesc}{testzip}{} | \begin{methoddesc}{testzip}{} | ||||||
|   Read all the files in the archive and check their CRC's.  Return the |   Read all the files in the archive and check their CRC's and file | ||||||
|   name of the first bad file, or else return \code{None}. |   headers.  Return the name of the first bad file, or else return \code{None}. | ||||||
| \end{methoddesc} | \end{methoddesc} | ||||||
| 
 | 
 | ||||||
| \begin{methoddesc}{write}{filename\optional{, arcname\optional{, | \begin{methoddesc}{write}{filename\optional{, arcname\optional{, | ||||||
|  | @ -284,10 +296,6 @@ Instances have the following attributes: | ||||||
|   Byte offset to the file header. |   Byte offset to the file header. | ||||||
| \end{memberdesc} | \end{memberdesc} | ||||||
| 
 | 
 | ||||||
| \begin{memberdesc}[ZipInfo]{file_offset} |  | ||||||
|   Byte offset to the start of the file data. |  | ||||||
| \end{memberdesc} |  | ||||||
| 
 |  | ||||||
| \begin{memberdesc}[ZipInfo]{CRC} | \begin{memberdesc}[ZipInfo]{CRC} | ||||||
|   CRC-32 of the uncompressed file. |   CRC-32 of the uncompressed file. | ||||||
| \end{memberdesc} | \end{memberdesc} | ||||||
|  |  | ||||||
|  | @ -4,7 +4,7 @@ | ||||||
| except ImportError: | except ImportError: | ||||||
|     zlib = None |     zlib = None | ||||||
| 
 | 
 | ||||||
| import zipfile, os, unittest | import zipfile, os, unittest, sys, shutil | ||||||
| 
 | 
 | ||||||
| from StringIO import StringIO | from StringIO import StringIO | ||||||
| from tempfile import TemporaryFile | from tempfile import TemporaryFile | ||||||
|  | @ -28,14 +28,70 @@ def zipTest(self, f, compression): | ||||||
|         zipfp = zipfile.ZipFile(f, "w", compression) |         zipfp = zipfile.ZipFile(f, "w", compression) | ||||||
|         zipfp.write(TESTFN, "another"+os.extsep+"name") |         zipfp.write(TESTFN, "another"+os.extsep+"name") | ||||||
|         zipfp.write(TESTFN, TESTFN) |         zipfp.write(TESTFN, TESTFN) | ||||||
|  |         zipfp.writestr("strfile", self.data) | ||||||
|         zipfp.close() |         zipfp.close() | ||||||
| 
 | 
 | ||||||
|         # Read the ZIP archive |         # Read the ZIP archive | ||||||
|         zipfp = zipfile.ZipFile(f, "r", compression) |         zipfp = zipfile.ZipFile(f, "r", compression) | ||||||
|         self.assertEqual(zipfp.read(TESTFN), self.data) |         self.assertEqual(zipfp.read(TESTFN), self.data) | ||||||
|         self.assertEqual(zipfp.read("another"+os.extsep+"name"), self.data) |         self.assertEqual(zipfp.read("another"+os.extsep+"name"), self.data) | ||||||
|  |         self.assertEqual(zipfp.read("strfile"), self.data) | ||||||
|  | 
 | ||||||
|  |         # Print the ZIP directory | ||||||
|  |         fp = StringIO() | ||||||
|  |         stdout = sys.stdout | ||||||
|  |         try: | ||||||
|  |             sys.stdout = fp | ||||||
|  | 
 | ||||||
|  |             zipfp.printdir() | ||||||
|  |         finally: | ||||||
|  |             sys.stdout = stdout | ||||||
|  |          | ||||||
|  |         directory = fp.getvalue() | ||||||
|  |         lines = directory.splitlines() | ||||||
|  |         self.assertEquals(len(lines), 4) # Number of files + header | ||||||
|  | 
 | ||||||
|  |         self.assert_('File Name' in lines[0]) | ||||||
|  |         self.assert_('Modified' in lines[0]) | ||||||
|  |         self.assert_('Size' in lines[0]) | ||||||
|  | 
 | ||||||
|  |         fn, date, time, size = lines[1].split() | ||||||
|  |         self.assertEquals(fn, 'another.name') | ||||||
|  |         # XXX: timestamp is not tested | ||||||
|  |         self.assertEquals(size, str(len(self.data))) | ||||||
|  | 
 | ||||||
|  |         # Check the namelist | ||||||
|  |         names = zipfp.namelist() | ||||||
|  |         self.assertEquals(len(names), 3) | ||||||
|  |         self.assert_(TESTFN in names) | ||||||
|  |         self.assert_("another"+os.extsep+"name" in names) | ||||||
|  |         self.assert_("strfile" in names) | ||||||
|  | 
 | ||||||
|  |         # Check infolist | ||||||
|  |         infos = zipfp.infolist() | ||||||
|  |         names = [ i.filename for i in infos ] | ||||||
|  |         self.assertEquals(len(names), 3) | ||||||
|  |         self.assert_(TESTFN in names) | ||||||
|  |         self.assert_("another"+os.extsep+"name" in names) | ||||||
|  |         self.assert_("strfile" in names) | ||||||
|  |         for i in infos: | ||||||
|  |             self.assertEquals(i.file_size, len(self.data)) | ||||||
|  | 
 | ||||||
|  |         # check getinfo | ||||||
|  |         for nm in (TESTFN, "another"+os.extsep+"name", "strfile"): | ||||||
|  |             info = zipfp.getinfo(nm) | ||||||
|  |             self.assertEquals(info.filename, nm) | ||||||
|  |             self.assertEquals(info.file_size, len(self.data)) | ||||||
|  | 
 | ||||||
|  |         # Check that testzip doesn't raise an exception | ||||||
|  |         zipfp.testzip() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|         zipfp.close() |         zipfp.close() | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|     def testStored(self): |     def testStored(self): | ||||||
|         for f in (TESTFN2, TemporaryFile(), StringIO()): |         for f in (TESTFN2, TemporaryFile(), StringIO()): | ||||||
|             self.zipTest(f, zipfile.ZIP_STORED) |             self.zipTest(f, zipfile.ZIP_STORED) | ||||||
|  | @ -59,6 +115,197 @@ def tearDown(self): | ||||||
|         os.remove(TESTFN) |         os.remove(TESTFN) | ||||||
|         os.remove(TESTFN2) |         os.remove(TESTFN2) | ||||||
| 
 | 
 | ||||||
|  | class TestZip64InSmallFiles(unittest.TestCase): | ||||||
|  |     # These tests test the ZIP64 functionality without using large files, | ||||||
|  |     # see test_zipfile64 for proper tests. | ||||||
|  | 
 | ||||||
|  |     def setUp(self): | ||||||
|  |         self._limit = zipfile.ZIP64_LIMIT | ||||||
|  |         zipfile.ZIP64_LIMIT = 5 | ||||||
|  | 
 | ||||||
|  |         line_gen = ("Test of zipfile line %d." % i for i in range(0, 1000)) | ||||||
|  |         self.data = '\n'.join(line_gen) | ||||||
|  | 
 | ||||||
|  |         # Make a source file with some lines | ||||||
|  |         fp = open(TESTFN, "wb") | ||||||
|  |         fp.write(self.data) | ||||||
|  |         fp.close() | ||||||
|  | 
 | ||||||
|  |     def largeFileExceptionTest(self, f, compression): | ||||||
|  |         zipfp = zipfile.ZipFile(f, "w", compression) | ||||||
|  |         self.assertRaises(zipfile.LargeZipFile,  | ||||||
|  |                 zipfp.write, TESTFN, "another"+os.extsep+"name") | ||||||
|  |         zipfp.close() | ||||||
|  | 
 | ||||||
|  |     def largeFileExceptionTest2(self, f, compression): | ||||||
|  |         zipfp = zipfile.ZipFile(f, "w", compression) | ||||||
|  |         self.assertRaises(zipfile.LargeZipFile,  | ||||||
|  |                 zipfp.writestr, "another"+os.extsep+"name", self.data) | ||||||
|  |         zipfp.close() | ||||||
|  | 
 | ||||||
|  |     def testLargeFileException(self): | ||||||
|  |         for f in (TESTFN2, TemporaryFile(), StringIO()): | ||||||
|  |             self.largeFileExceptionTest(f, zipfile.ZIP_STORED) | ||||||
|  |             self.largeFileExceptionTest2(f, zipfile.ZIP_STORED) | ||||||
|  | 
 | ||||||
|  |     def zipTest(self, f, compression): | ||||||
|  |         # Create the ZIP archive | ||||||
|  |         zipfp = zipfile.ZipFile(f, "w", compression, allowZip64=True) | ||||||
|  |         zipfp.write(TESTFN, "another"+os.extsep+"name") | ||||||
|  |         zipfp.write(TESTFN, TESTFN) | ||||||
|  |         zipfp.writestr("strfile", self.data) | ||||||
|  |         zipfp.close() | ||||||
|  | 
 | ||||||
|  |         # Read the ZIP archive | ||||||
|  |         zipfp = zipfile.ZipFile(f, "r", compression) | ||||||
|  |         self.assertEqual(zipfp.read(TESTFN), self.data) | ||||||
|  |         self.assertEqual(zipfp.read("another"+os.extsep+"name"), self.data) | ||||||
|  |         self.assertEqual(zipfp.read("strfile"), self.data) | ||||||
|  | 
 | ||||||
|  |         # Print the ZIP directory | ||||||
|  |         fp = StringIO() | ||||||
|  |         stdout = sys.stdout | ||||||
|  |         try: | ||||||
|  |             sys.stdout = fp | ||||||
|  | 
 | ||||||
|  |             zipfp.printdir() | ||||||
|  |         finally: | ||||||
|  |             sys.stdout = stdout | ||||||
|  |          | ||||||
|  |         directory = fp.getvalue() | ||||||
|  |         lines = directory.splitlines() | ||||||
|  |         self.assertEquals(len(lines), 4) # Number of files + header | ||||||
|  | 
 | ||||||
|  |         self.assert_('File Name' in lines[0]) | ||||||
|  |         self.assert_('Modified' in lines[0]) | ||||||
|  |         self.assert_('Size' in lines[0]) | ||||||
|  | 
 | ||||||
|  |         fn, date, time, size = lines[1].split() | ||||||
|  |         self.assertEquals(fn, 'another.name') | ||||||
|  |         # XXX: timestamp is not tested | ||||||
|  |         self.assertEquals(size, str(len(self.data))) | ||||||
|  | 
 | ||||||
|  |         # Check the namelist | ||||||
|  |         names = zipfp.namelist() | ||||||
|  |         self.assertEquals(len(names), 3) | ||||||
|  |         self.assert_(TESTFN in names) | ||||||
|  |         self.assert_("another"+os.extsep+"name" in names) | ||||||
|  |         self.assert_("strfile" in names) | ||||||
|  | 
 | ||||||
|  |         # Check infolist | ||||||
|  |         infos = zipfp.infolist() | ||||||
|  |         names = [ i.filename for i in infos ] | ||||||
|  |         self.assertEquals(len(names), 3) | ||||||
|  |         self.assert_(TESTFN in names) | ||||||
|  |         self.assert_("another"+os.extsep+"name" in names) | ||||||
|  |         self.assert_("strfile" in names) | ||||||
|  |         for i in infos: | ||||||
|  |             self.assertEquals(i.file_size, len(self.data)) | ||||||
|  | 
 | ||||||
|  |         # check getinfo | ||||||
|  |         for nm in (TESTFN, "another"+os.extsep+"name", "strfile"): | ||||||
|  |             info = zipfp.getinfo(nm) | ||||||
|  |             self.assertEquals(info.filename, nm) | ||||||
|  |             self.assertEquals(info.file_size, len(self.data)) | ||||||
|  | 
 | ||||||
|  |         # Check that testzip doesn't raise an exception | ||||||
|  |         zipfp.testzip() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         zipfp.close() | ||||||
|  | 
 | ||||||
|  |     def testStored(self): | ||||||
|  |         for f in (TESTFN2, TemporaryFile(), StringIO()): | ||||||
|  |             self.zipTest(f, zipfile.ZIP_STORED) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     if zlib: | ||||||
|  |         def testDeflated(self): | ||||||
|  |             for f in (TESTFN2, TemporaryFile(), StringIO()): | ||||||
|  |                 self.zipTest(f, zipfile.ZIP_DEFLATED) | ||||||
|  | 
 | ||||||
|  |     def testAbsoluteArcnames(self): | ||||||
|  |         zipfp = zipfile.ZipFile(TESTFN2, "w", zipfile.ZIP_STORED, allowZip64=True) | ||||||
|  |         zipfp.write(TESTFN, "/absolute") | ||||||
|  |         zipfp.close() | ||||||
|  | 
 | ||||||
|  |         zipfp = zipfile.ZipFile(TESTFN2, "r", zipfile.ZIP_STORED) | ||||||
|  |         self.assertEqual(zipfp.namelist(), ["absolute"]) | ||||||
|  |         zipfp.close() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     def tearDown(self): | ||||||
|  |         zipfile.ZIP64_LIMIT = self._limit | ||||||
|  |         os.remove(TESTFN) | ||||||
|  |         os.remove(TESTFN2) | ||||||
|  | 
 | ||||||
|  | class PyZipFileTests(unittest.TestCase): | ||||||
|  |     def testWritePyfile(self): | ||||||
|  |         zipfp  = zipfile.PyZipFile(TemporaryFile(), "w") | ||||||
|  |         fn = __file__ | ||||||
|  |         if fn.endswith('.pyc') or fn.endswith('.pyo'): | ||||||
|  |             fn = fn[:-1] | ||||||
|  | 
 | ||||||
|  |         zipfp.writepy(fn) | ||||||
|  | 
 | ||||||
|  |         bn = os.path.basename(fn) | ||||||
|  |         self.assert_(bn not in zipfp.namelist()) | ||||||
|  |         self.assert_(bn + 'o' in zipfp.namelist() or bn + 'c' in zipfp.namelist()) | ||||||
|  |         zipfp.close() | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         zipfp  = zipfile.PyZipFile(TemporaryFile(), "w") | ||||||
|  |         fn = __file__ | ||||||
|  |         if fn.endswith('.pyc') or fn.endswith('.pyo'): | ||||||
|  |             fn = fn[:-1] | ||||||
|  | 
 | ||||||
|  |         zipfp.writepy(fn, "testpackage") | ||||||
|  | 
 | ||||||
|  |         bn = "%s/%s"%("testpackage", os.path.basename(fn)) | ||||||
|  |         self.assert_(bn not in zipfp.namelist()) | ||||||
|  |         self.assert_(bn + 'o' in zipfp.namelist() or bn + 'c' in zipfp.namelist()) | ||||||
|  |         zipfp.close() | ||||||
|  | 
 | ||||||
|  |     def testWritePythonPackage(self): | ||||||
|  |         import email | ||||||
|  |         packagedir = os.path.dirname(email.__file__) | ||||||
|  | 
 | ||||||
|  |         zipfp  = zipfile.PyZipFile(TemporaryFile(), "w") | ||||||
|  |         zipfp.writepy(packagedir) | ||||||
|  | 
 | ||||||
|  |         # Check for a couple of modules at different levels of the hieararchy | ||||||
|  |         names = zipfp.namelist() | ||||||
|  |         self.assert_('email/__init__.pyo' in names or 'email/__init__.pyc' in names) | ||||||
|  |         self.assert_('email/mime/text.pyo' in names or 'email/mime/text.pyc' in names) | ||||||
|  | 
 | ||||||
|  |     def testWritePythonDirectory(self): | ||||||
|  |         os.mkdir(TESTFN2) | ||||||
|  |         try: | ||||||
|  |             fp = open(os.path.join(TESTFN2, "mod1.py"), "w") | ||||||
|  |             fp.write("print 42\n") | ||||||
|  |             fp.close() | ||||||
|  | 
 | ||||||
|  |             fp = open(os.path.join(TESTFN2, "mod2.py"), "w") | ||||||
|  |             fp.write("print 42 * 42\n") | ||||||
|  |             fp.close() | ||||||
|  | 
 | ||||||
|  |             fp = open(os.path.join(TESTFN2, "mod2.txt"), "w") | ||||||
|  |             fp.write("bla bla bla\n") | ||||||
|  |             fp.close() | ||||||
|  | 
 | ||||||
|  |             zipfp  = zipfile.PyZipFile(TemporaryFile(), "w") | ||||||
|  |             zipfp.writepy(TESTFN2) | ||||||
|  | 
 | ||||||
|  |             names = zipfp.namelist() | ||||||
|  |             self.assert_('mod1.pyc' in names or 'mod1.pyo' in names) | ||||||
|  |             self.assert_('mod2.pyc' in names or 'mod2.pyo' in names) | ||||||
|  |             self.assert_('mod2.txt' not in names) | ||||||
|  | 
 | ||||||
|  |         finally: | ||||||
|  |             shutil.rmtree(TESTFN2) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| class OtherTests(unittest.TestCase): | class OtherTests(unittest.TestCase): | ||||||
|     def testCloseErroneousFile(self): |     def testCloseErroneousFile(self): | ||||||
|         # This test checks that the ZipFile constructor closes the file object |         # This test checks that the ZipFile constructor closes the file object | ||||||
|  | @ -103,7 +350,8 @@ def testClosedZipRaisesRuntimeError(self): | ||||||
|         self.assertRaises(RuntimeError, zipf.testzip) |         self.assertRaises(RuntimeError, zipf.testzip) | ||||||
| 
 | 
 | ||||||
| def test_main(): | def test_main(): | ||||||
|     run_unittest(TestsWithSourceFile, OtherTests) |     run_unittest(TestsWithSourceFile, TestZip64InSmallFiles, OtherTests, PyZipFileTests) | ||||||
|  |     #run_unittest(TestZip64InSmallFiles) | ||||||
| 
 | 
 | ||||||
| if __name__ == "__main__": | if __name__ == "__main__": | ||||||
|     test_main() |     test_main() | ||||||
|  |  | ||||||
							
								
								
									
										67
									
								
								Lib/test/test_zipfile64.py
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								Lib/test/test_zipfile64.py
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,67 @@ | ||||||
|  | # Tests of the full ZIP64 functionality of zipfile | ||||||
|  | # The test_support.requires call is the only reason for keeping this separate | ||||||
|  | # from test_zipfile | ||||||
|  | from test import test_support | ||||||
|  | test_support.requires( | ||||||
|  |         'largefile',  | ||||||
|  |         'test requires loads of disk-space bytes and a long time to run' | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  | # We can test part of the module without zlib. | ||||||
|  | try: | ||||||
|  |     import zlib | ||||||
|  | except ImportError: | ||||||
|  |     zlib = None | ||||||
|  | 
 | ||||||
|  | import zipfile, os, unittest | ||||||
|  | 
 | ||||||
|  | from StringIO import StringIO | ||||||
|  | from tempfile import TemporaryFile | ||||||
|  | 
 | ||||||
|  | from test.test_support import TESTFN, run_unittest | ||||||
|  | 
 | ||||||
|  | TESTFN2 = TESTFN + "2" | ||||||
|  | 
 | ||||||
|  | class TestsWithSourceFile(unittest.TestCase): | ||||||
|  |     def setUp(self): | ||||||
|  |         line_gen = ("Test of zipfile line %d." % i for i in range(0, 1000000)) | ||||||
|  |         self.data = '\n'.join(line_gen) | ||||||
|  | 
 | ||||||
|  |         # Make a source file with some lines | ||||||
|  |         fp = open(TESTFN, "wb") | ||||||
|  |         fp.write(self.data) | ||||||
|  |         fp.close() | ||||||
|  | 
 | ||||||
|  |     def zipTest(self, f, compression): | ||||||
|  |         # Create the ZIP archive | ||||||
|  |         filecount = int(((1 << 32) / len(self.data)) * 1.5) | ||||||
|  |         zipfp = zipfile.ZipFile(f, "w", compression, allowZip64=True) | ||||||
|  | 
 | ||||||
|  |         for num in range(filecount): | ||||||
|  |             zipfp.writestr("testfn%d"%(num,), self.data) | ||||||
|  |         zipfp.close() | ||||||
|  | 
 | ||||||
|  |         # Read the ZIP archive | ||||||
|  |         zipfp = zipfile.ZipFile(f, "r", compression) | ||||||
|  |         for num in range(filecount): | ||||||
|  |             self.assertEqual(zipfp.read("testfn%d"%(num,)), self.data) | ||||||
|  |         zipfp.close() | ||||||
|  | 
 | ||||||
|  |     def testStored(self): | ||||||
|  |         for f in (TESTFN2, TemporaryFile()): | ||||||
|  |             self.zipTest(f, zipfile.ZIP_STORED) | ||||||
|  | 
 | ||||||
|  |     if zlib: | ||||||
|  |         def testDeflated(self): | ||||||
|  |             for f in (TESTFN2, TemporaryFile()): | ||||||
|  |                 self.zipTest(f, zipfile.ZIP_DEFLATED) | ||||||
|  | 
 | ||||||
|  |     def tearDown(self): | ||||||
|  |         os.remove(TESTFN) | ||||||
|  |         os.remove(TESTFN2) | ||||||
|  | 
 | ||||||
|  | def test_main(): | ||||||
|  |     run_unittest(TestsWithSourceFile) | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     test_main() | ||||||
							
								
								
									
										384
									
								
								Lib/zipfile.py
									
										
									
									
									
								
							
							
						
						
									
										384
									
								
								Lib/zipfile.py
									
										
									
									
									
								
							|  | @ -1,7 +1,8 @@ | ||||||
| "Read and write ZIP files." | """ | ||||||
| 
 | Read and write ZIP files. | ||||||
|  | """ | ||||||
| import struct, os, time, sys | import struct, os, time, sys | ||||||
| import binascii | import binascii, cStringIO | ||||||
| 
 | 
 | ||||||
| try: | try: | ||||||
|     import zlib # We may need its compression method |     import zlib # We may need its compression method | ||||||
|  | @ -9,12 +10,22 @@ | ||||||
|     zlib = None |     zlib = None | ||||||
| 
 | 
 | ||||||
| __all__ = ["BadZipfile", "error", "ZIP_STORED", "ZIP_DEFLATED", "is_zipfile", | __all__ = ["BadZipfile", "error", "ZIP_STORED", "ZIP_DEFLATED", "is_zipfile", | ||||||
|            "ZipInfo", "ZipFile", "PyZipFile"] |            "ZipInfo", "ZipFile", "PyZipFile", "LargeZipFile" ] | ||||||
| 
 | 
 | ||||||
| class BadZipfile(Exception): | class BadZipfile(Exception): | ||||||
|     pass |     pass | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | class LargeZipFile(Exception): | ||||||
|  |     """  | ||||||
|  |     Raised when writing a zipfile, the zipfile requires ZIP64 extensions | ||||||
|  |     and those extensions are disabled. | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
| error = BadZipfile      # The exception raised by this module | error = BadZipfile      # The exception raised by this module | ||||||
| 
 | 
 | ||||||
|  | ZIP64_LIMIT= (1 << 31) - 1 | ||||||
|  | 
 | ||||||
| # constants for Zip file compression methods | # constants for Zip file compression methods | ||||||
| ZIP_STORED = 0 | ZIP_STORED = 0 | ||||||
| ZIP_DEFLATED = 8 | ZIP_DEFLATED = 8 | ||||||
|  | @ -27,6 +38,11 @@ class BadZipfile(Exception): | ||||||
| stringCentralDir = "PK\001\002"   # magic number for central directory | stringCentralDir = "PK\001\002"   # magic number for central directory | ||||||
| structFileHeader = "<4s2B4HlLL2H"  # 12 items, file header record, 30 bytes | structFileHeader = "<4s2B4HlLL2H"  # 12 items, file header record, 30 bytes | ||||||
| stringFileHeader = "PK\003\004"   # magic number for file header | stringFileHeader = "PK\003\004"   # magic number for file header | ||||||
|  | structEndArchive64Locator = "<4slql" # 4 items, locate Zip64 header, 20 bytes | ||||||
|  | stringEndArchive64Locator = "PK\x06\x07" # magic token for locator header | ||||||
|  | structEndArchive64 = "<4sqhhllqqqq" # 10 items, end of archive (Zip64), 56 bytes | ||||||
|  | stringEndArchive64 = "PK\x06\x06" # magic token for Zip64 header | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| # indexes of entries in the central directory structure | # indexes of entries in the central directory structure | ||||||
| _CD_SIGNATURE = 0 | _CD_SIGNATURE = 0 | ||||||
|  | @ -75,6 +91,40 @@ def is_zipfile(filename): | ||||||
|         pass |         pass | ||||||
|     return False |     return False | ||||||
| 
 | 
 | ||||||
|  | def _EndRecData64(fpin, offset, endrec): | ||||||
|  |     """ | ||||||
|  |     Read the ZIP64 end-of-archive records and use that to update endrec | ||||||
|  |     """ | ||||||
|  |     locatorSize = struct.calcsize(structEndArchive64Locator) | ||||||
|  |     fpin.seek(offset - locatorSize, 2) | ||||||
|  |     data = fpin.read(locatorSize) | ||||||
|  |     sig, diskno, reloff, disks = struct.unpack(structEndArchive64Locator, data) | ||||||
|  |     if sig != stringEndArchive64Locator:  | ||||||
|  |         return endrec | ||||||
|  | 
 | ||||||
|  |     if diskno != 0 or disks != 1: | ||||||
|  |         raise BadZipfile("zipfiles that span multiple disks are not supported") | ||||||
|  | 
 | ||||||
|  |     # Assume no 'zip64 extensible data'  | ||||||
|  |     endArchiveSize = struct.calcsize(structEndArchive64) | ||||||
|  |     fpin.seek(offset - locatorSize - endArchiveSize, 2) | ||||||
|  |     data = fpin.read(endArchiveSize) | ||||||
|  |     sig, sz, create_version, read_version, disk_num, disk_dir, \ | ||||||
|  |             dircount, dircount2, dirsize, diroffset = \ | ||||||
|  |             struct.unpack(structEndArchive64, data) | ||||||
|  |     if sig != stringEndArchive64:  | ||||||
|  |         return endrec | ||||||
|  | 
 | ||||||
|  |     # Update the original endrec using data from the ZIP64 record | ||||||
|  |     endrec[1] = disk_num | ||||||
|  |     endrec[2] = disk_dir | ||||||
|  |     endrec[3] = dircount | ||||||
|  |     endrec[4] = dircount2 | ||||||
|  |     endrec[5] = dirsize | ||||||
|  |     endrec[6] = diroffset | ||||||
|  |     return endrec | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
| def _EndRecData(fpin): | def _EndRecData(fpin): | ||||||
|     """Return data from the "End of Central Directory" record, or None. |     """Return data from the "End of Central Directory" record, or None. | ||||||
| 
 | 
 | ||||||
|  | @ -88,6 +138,8 @@ def _EndRecData(fpin): | ||||||
|         endrec = list(endrec) |         endrec = list(endrec) | ||||||
|         endrec.append("")               # Append the archive comment |         endrec.append("")               # Append the archive comment | ||||||
|         endrec.append(filesize - 22)    # Append the record start offset |         endrec.append(filesize - 22)    # Append the record start offset | ||||||
|  |         if endrec[-4] == -1 or endrec[-4] == 0xffffffff: | ||||||
|  |             return _EndRecData64(fpin, -22, endrec) | ||||||
|         return endrec |         return endrec | ||||||
|     # Search the last END_BLOCK bytes of the file for the record signature. |     # Search the last END_BLOCK bytes of the file for the record signature. | ||||||
|     # The comment is appended to the ZIP file and has a 16 bit length. |     # The comment is appended to the ZIP file and has a 16 bit length. | ||||||
|  | @ -106,25 +158,50 @@ def _EndRecData(fpin): | ||||||
|             # Append the archive comment and start offset |             # Append the archive comment and start offset | ||||||
|             endrec.append(comment) |             endrec.append(comment) | ||||||
|             endrec.append(filesize - END_BLOCK + start) |             endrec.append(filesize - END_BLOCK + start) | ||||||
|  |             if endrec[-4] == -1 or endrec[-4] == 0xffffffff: | ||||||
|  |                 return _EndRecData64(fpin, - END_BLOCK + start, endrec) | ||||||
|             return endrec |             return endrec | ||||||
|     return      # Error, return None |     return      # Error, return None | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class ZipInfo: | class ZipInfo (object): | ||||||
|     """Class with attributes describing each file in the ZIP archive.""" |     """Class with attributes describing each file in the ZIP archive.""" | ||||||
| 
 | 
 | ||||||
|  |     __slots__ = ( | ||||||
|  |             'orig_filename', | ||||||
|  |             'filename', | ||||||
|  |             'date_time', | ||||||
|  |             'compress_type', | ||||||
|  |             'comment', | ||||||
|  |             'extra', | ||||||
|  |             'create_system', | ||||||
|  |             'create_version', | ||||||
|  |             'extract_version', | ||||||
|  |             'reserved', | ||||||
|  |             'flag_bits', | ||||||
|  |             'volume', | ||||||
|  |             'internal_attr', | ||||||
|  |             'external_attr', | ||||||
|  |             'header_offset', | ||||||
|  |             'CRC', | ||||||
|  |             'compress_size', | ||||||
|  |             'file_size', | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|     def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)): |     def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)): | ||||||
|         self.orig_filename = filename   # Original file name in archive |         self.orig_filename = filename   # Original file name in archive | ||||||
| # Terminate the file name at the first null byte.  Null bytes in file | 
 | ||||||
| # names are used as tricks by viruses in archives. |         # Terminate the file name at the first null byte.  Null bytes in file | ||||||
|  |         # names are used as tricks by viruses in archives. | ||||||
|         null_byte = filename.find(chr(0)) |         null_byte = filename.find(chr(0)) | ||||||
|         if null_byte >= 0: |         if null_byte >= 0: | ||||||
|             filename = filename[0:null_byte] |             filename = filename[0:null_byte] | ||||||
| # This is used to ensure paths in generated ZIP files always use |         # This is used to ensure paths in generated ZIP files always use | ||||||
| # forward slashes as the directory separator, as required by the |         # forward slashes as the directory separator, as required by the | ||||||
| # ZIP format specification. |         # ZIP format specification. | ||||||
|         if os.sep != "/": |         if os.sep != "/" and os.sep in filename: | ||||||
|             filename = filename.replace(os.sep, "/") |             filename = filename.replace(os.sep, "/") | ||||||
|  | 
 | ||||||
|         self.filename = filename        # Normalized file name |         self.filename = filename        # Normalized file name | ||||||
|         self.date_time = date_time      # year, month, day, hour, min, sec |         self.date_time = date_time      # year, month, day, hour, min, sec | ||||||
|         # Standard values: |         # Standard values: | ||||||
|  | @ -145,7 +222,6 @@ def __init__(self, filename="NoName", date_time=(1980,1,1,0,0,0)): | ||||||
|         self.external_attr = 0          # External file attributes |         self.external_attr = 0          # External file attributes | ||||||
|         # Other attributes are set by class ZipFile: |         # Other attributes are set by class ZipFile: | ||||||
|         # header_offset         Byte offset to the file header |         # header_offset         Byte offset to the file header | ||||||
|         # file_offset           Byte offset to the start of the file data |  | ||||||
|         # CRC                   CRC-32 of the uncompressed file |         # CRC                   CRC-32 of the uncompressed file | ||||||
|         # compress_size         Size of the compressed file |         # compress_size         Size of the compressed file | ||||||
|         # file_size             Size of the uncompressed file |         # file_size             Size of the uncompressed file | ||||||
|  | @ -162,29 +238,85 @@ def FileHeader(self): | ||||||
|             CRC = self.CRC |             CRC = self.CRC | ||||||
|             compress_size = self.compress_size |             compress_size = self.compress_size | ||||||
|             file_size = self.file_size |             file_size = self.file_size | ||||||
|  | 
 | ||||||
|  |         extra = self.extra | ||||||
|  | 
 | ||||||
|  |         if file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT: | ||||||
|  |             # File is larger than what fits into a 4 byte integer, | ||||||
|  |             # fall back to the ZIP64 extension | ||||||
|  |             fmt = '<hhqq' | ||||||
|  |             extra = extra + struct.pack(fmt, | ||||||
|  |                     1, struct.calcsize(fmt)-4, file_size, compress_size) | ||||||
|  |             file_size = 0xffffffff # -1 | ||||||
|  |             compress_size = 0xffffffff # -1 | ||||||
|  |             self.extract_version = max(45, self.extract_version) | ||||||
|  |             self.create_version = max(45, self.extract_version) | ||||||
|  | 
 | ||||||
|         header = struct.pack(structFileHeader, stringFileHeader, |         header = struct.pack(structFileHeader, stringFileHeader, | ||||||
|                  self.extract_version, self.reserved, self.flag_bits, |                  self.extract_version, self.reserved, self.flag_bits, | ||||||
|                  self.compress_type, dostime, dosdate, CRC, |                  self.compress_type, dostime, dosdate, CRC, | ||||||
|                  compress_size, file_size, |                  compress_size, file_size, | ||||||
|                  len(self.filename), len(self.extra)) |                  len(self.filename), len(extra)) | ||||||
|         return header + self.filename + self.extra |         return header + self.filename + extra | ||||||
|  | 
 | ||||||
|  |     def _decodeExtra(self): | ||||||
|  |         # Try to decode the extra field. | ||||||
|  |         extra = self.extra | ||||||
|  |         unpack = struct.unpack | ||||||
|  |         while extra: | ||||||
|  |             tp, ln = unpack('<hh', extra[:4]) | ||||||
|  |             if tp == 1: | ||||||
|  |                 if ln >= 24: | ||||||
|  |                     counts = unpack('<qqq', extra[4:28]) | ||||||
|  |                 elif ln == 16: | ||||||
|  |                     counts = unpack('<qq', extra[4:20]) | ||||||
|  |                 elif ln == 8: | ||||||
|  |                     counts = unpack('<q', extra[4:12]) | ||||||
|  |                 elif ln == 0: | ||||||
|  |                     counts = () | ||||||
|  |                 else: | ||||||
|  |                     raise RuntimeError, "Corrupt extra field %s"%(ln,) | ||||||
|  | 
 | ||||||
|  |                 idx = 0 | ||||||
|  | 
 | ||||||
|  |                 # ZIP64 extension (large files and/or large archives) | ||||||
|  |                 if self.file_size == -1 or self.file_size == 0xFFFFFFFFL: | ||||||
|  |                     self.file_size = counts[idx] | ||||||
|  |                     idx += 1 | ||||||
|  | 
 | ||||||
|  |                 if self.compress_size == -1 or self.compress_size == 0xFFFFFFFFL: | ||||||
|  |                     self.compress_size = counts[idx] | ||||||
|  |                     idx += 1 | ||||||
|  | 
 | ||||||
|  |                 if self.header_offset == -1 or self.header_offset == 0xffffffffL: | ||||||
|  |                     old = self.header_offset | ||||||
|  |                     self.header_offset = counts[idx] | ||||||
|  |                     idx+=1 | ||||||
|  | 
 | ||||||
|  |             extra = extra[ln+4:] | ||||||
|                 |                 | ||||||
| 
 | 
 | ||||||
| class ZipFile: | class ZipFile: | ||||||
|     """ Class with methods to open, read, write, close, list zip files. |     """ Class with methods to open, read, write, close, list zip files. | ||||||
| 
 | 
 | ||||||
|     z = ZipFile(file, mode="r", compression=ZIP_STORED) |     z = ZipFile(file, mode="r", compression=ZIP_STORED, allowZip64=True) | ||||||
| 
 | 
 | ||||||
|     file: Either the path to the file, or a file-like object. |     file: Either the path to the file, or a file-like object. | ||||||
|           If it is a path, the file will be opened and closed by ZipFile. |           If it is a path, the file will be opened and closed by ZipFile. | ||||||
|     mode: The mode can be either read "r", write "w" or append "a". |     mode: The mode can be either read "r", write "w" or append "a". | ||||||
|     compression: ZIP_STORED (no compression) or ZIP_DEFLATED (requires zlib). |     compression: ZIP_STORED (no compression) or ZIP_DEFLATED (requires zlib). | ||||||
|  |     allowZip64: if True ZipFile will create files with ZIP64 extensions when | ||||||
|  |                 needed, otherwise it will raise an exception when this would | ||||||
|  |                 be necessary. | ||||||
|  | 
 | ||||||
|     """ |     """ | ||||||
| 
 | 
 | ||||||
|     fp = None                   # Set here since __del__ checks it |     fp = None                   # Set here since __del__ checks it | ||||||
| 
 | 
 | ||||||
|     def __init__(self, file, mode="r", compression=ZIP_STORED): |     def __init__(self, file, mode="r", compression=ZIP_STORED, allowZip64=False): | ||||||
|         """Open the ZIP file with mode read "r", write "w" or append "a".""" |         """Open the ZIP file with mode read "r", write "w" or append "a".""" | ||||||
|  |         self._allowZip64 = allowZip64 | ||||||
|  |         self._didModify = False | ||||||
|         if compression == ZIP_STORED: |         if compression == ZIP_STORED: | ||||||
|             pass |             pass | ||||||
|         elif compression == ZIP_DEFLATED: |         elif compression == ZIP_DEFLATED: | ||||||
|  | @ -250,7 +382,10 @@ def _RealGetContents(self): | ||||||
|         offset_cd = endrec[6]   # offset of central directory |         offset_cd = endrec[6]   # offset of central directory | ||||||
|         self.comment = endrec[8]        # archive comment |         self.comment = endrec[8]        # archive comment | ||||||
|         # endrec[9] is the offset of the "End of Central Dir" record |         # endrec[9] is the offset of the "End of Central Dir" record | ||||||
|         x = endrec[9] - size_cd |         if endrec[9] > ZIP64_LIMIT: | ||||||
|  |             x = endrec[9] - size_cd - 56 - 20 | ||||||
|  |         else: | ||||||
|  |             x = endrec[9] - size_cd | ||||||
|         # "concat" is zero, unless zip was concatenated to another file |         # "concat" is zero, unless zip was concatenated to another file | ||||||
|         concat = x - offset_cd |         concat = x - offset_cd | ||||||
|         if self.debug > 2: |         if self.debug > 2: | ||||||
|  | @ -258,6 +393,8 @@ def _RealGetContents(self): | ||||||
|         # self.start_dir:  Position of start of central directory |         # self.start_dir:  Position of start of central directory | ||||||
|         self.start_dir = offset_cd + concat |         self.start_dir = offset_cd + concat | ||||||
|         fp.seek(self.start_dir, 0) |         fp.seek(self.start_dir, 0) | ||||||
|  |         data = fp.read(size_cd) | ||||||
|  |         fp = cStringIO.StringIO(data) | ||||||
|         total = 0 |         total = 0 | ||||||
|         while total < size_cd: |         while total < size_cd: | ||||||
|             centdir = fp.read(46) |             centdir = fp.read(46) | ||||||
|  | @ -275,8 +412,7 @@ def _RealGetContents(self): | ||||||
|             total = (total + centdir[_CD_FILENAME_LENGTH] |             total = (total + centdir[_CD_FILENAME_LENGTH] | ||||||
|                      + centdir[_CD_EXTRA_FIELD_LENGTH] |                      + centdir[_CD_EXTRA_FIELD_LENGTH] | ||||||
|                      + centdir[_CD_COMMENT_LENGTH]) |                      + centdir[_CD_COMMENT_LENGTH]) | ||||||
|             x.header_offset = centdir[_CD_LOCAL_HEADER_OFFSET] + concat |             x.header_offset = centdir[_CD_LOCAL_HEADER_OFFSET] | ||||||
|             # file_offset must be computed below... |  | ||||||
|             (x.create_version, x.create_system, x.extract_version, x.reserved, |             (x.create_version, x.create_system, x.extract_version, x.reserved, | ||||||
|                 x.flag_bits, x.compress_type, t, d, |                 x.flag_bits, x.compress_type, t, d, | ||||||
|                 x.CRC, x.compress_size, x.file_size) = centdir[1:12] |                 x.CRC, x.compress_size, x.file_size) = centdir[1:12] | ||||||
|  | @ -284,28 +420,14 @@ def _RealGetContents(self): | ||||||
|             # Convert date/time code to (year, month, day, hour, min, sec) |             # Convert date/time code to (year, month, day, hour, min, sec) | ||||||
|             x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F, |             x.date_time = ( (d>>9)+1980, (d>>5)&0xF, d&0x1F, | ||||||
|                                      t>>11, (t>>5)&0x3F, (t&0x1F) * 2 ) |                                      t>>11, (t>>5)&0x3F, (t&0x1F) * 2 ) | ||||||
|  | 
 | ||||||
|  |             x._decodeExtra() | ||||||
|  |             x.header_offset = x.header_offset + concat | ||||||
|             self.filelist.append(x) |             self.filelist.append(x) | ||||||
|             self.NameToInfo[x.filename] = x |             self.NameToInfo[x.filename] = x | ||||||
|             if self.debug > 2: |             if self.debug > 2: | ||||||
|                 print "total", total |                 print "total", total | ||||||
|         for data in self.filelist: | 
 | ||||||
|             fp.seek(data.header_offset, 0) |  | ||||||
|             fheader = fp.read(30) |  | ||||||
|             if fheader[0:4] != stringFileHeader: |  | ||||||
|                 raise BadZipfile, "Bad magic number for file header" |  | ||||||
|             fheader = struct.unpack(structFileHeader, fheader) |  | ||||||
|             # file_offset is computed here, since the extra field for |  | ||||||
|             # the central directory and for the local file header |  | ||||||
|             # refer to different fields, and they can have different |  | ||||||
|             # lengths |  | ||||||
|             data.file_offset = (data.header_offset + 30 |  | ||||||
|                                 + fheader[_FH_FILENAME_LENGTH] |  | ||||||
|                                 + fheader[_FH_EXTRA_FIELD_LENGTH]) |  | ||||||
|             fname = fp.read(fheader[_FH_FILENAME_LENGTH]) |  | ||||||
|             if fname != data.orig_filename: |  | ||||||
|                 raise RuntimeError, \ |  | ||||||
|                       'File name in directory "%s" and header "%s" differ.' % ( |  | ||||||
|                           data.orig_filename, fname) |  | ||||||
| 
 | 
 | ||||||
|     def namelist(self): |     def namelist(self): | ||||||
|         """Return a list of file names in the archive.""" |         """Return a list of file names in the archive.""" | ||||||
|  | @ -334,6 +456,7 @@ def testzip(self): | ||||||
|             except BadZipfile: |             except BadZipfile: | ||||||
|                 return zinfo.filename |                 return zinfo.filename | ||||||
| 
 | 
 | ||||||
|  | 
 | ||||||
|     def getinfo(self, name): |     def getinfo(self, name): | ||||||
|         """Return the instance of ZipInfo given 'name'.""" |         """Return the instance of ZipInfo given 'name'.""" | ||||||
|         return self.NameToInfo[name] |         return self.NameToInfo[name] | ||||||
|  | @ -347,7 +470,24 @@ def read(self, name): | ||||||
|                   "Attempt to read ZIP archive that was already closed" |                   "Attempt to read ZIP archive that was already closed" | ||||||
|         zinfo = self.getinfo(name) |         zinfo = self.getinfo(name) | ||||||
|         filepos = self.fp.tell() |         filepos = self.fp.tell() | ||||||
|         self.fp.seek(zinfo.file_offset, 0) | 
 | ||||||
|  |         self.fp.seek(zinfo.header_offset, 0) | ||||||
|  | 
 | ||||||
|  |         # Skip the file header: | ||||||
|  |         fheader = self.fp.read(30) | ||||||
|  |         if fheader[0:4] != stringFileHeader: | ||||||
|  |             raise BadZipfile, "Bad magic number for file header" | ||||||
|  | 
 | ||||||
|  |         fheader = struct.unpack(structFileHeader, fheader) | ||||||
|  |         fname = self.fp.read(fheader[_FH_FILENAME_LENGTH]) | ||||||
|  |         if fheader[_FH_EXTRA_FIELD_LENGTH]: | ||||||
|  |             self.fp.read(fheader[_FH_EXTRA_FIELD_LENGTH]) | ||||||
|  | 
 | ||||||
|  |         if fname != zinfo.orig_filename: | ||||||
|  |             raise BadZipfile, \ | ||||||
|  |                       'File name in directory "%s" and header "%s" differ.' % ( | ||||||
|  |                           zinfo.orig_filename, fname) | ||||||
|  | 
 | ||||||
|         bytes = self.fp.read(zinfo.compress_size) |         bytes = self.fp.read(zinfo.compress_size) | ||||||
|         self.fp.seek(filepos, 0) |         self.fp.seek(filepos, 0) | ||||||
|         if zinfo.compress_type == ZIP_STORED: |         if zinfo.compress_type == ZIP_STORED: | ||||||
|  | @ -388,6 +528,12 @@ def _writecheck(self, zinfo): | ||||||
|         if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED): |         if zinfo.compress_type not in (ZIP_STORED, ZIP_DEFLATED): | ||||||
|             raise RuntimeError, \ |             raise RuntimeError, \ | ||||||
|                   "That compression method is not supported" |                   "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") | ||||||
| 
 | 
 | ||||||
|     def write(self, filename, arcname=None, compress_type=None): |     def write(self, filename, arcname=None, compress_type=None): | ||||||
|         """Put the bytes from filename into the archive under the name |         """Put the bytes from filename into the archive under the name | ||||||
|  | @ -407,16 +553,19 @@ def write(self, filename, arcname=None, compress_type=None): | ||||||
|             zinfo.compress_type = self.compression |             zinfo.compress_type = self.compression | ||||||
|         else: |         else: | ||||||
|             zinfo.compress_type = compress_type |             zinfo.compress_type = compress_type | ||||||
|         self._writecheck(zinfo) | 
 | ||||||
|         fp = open(filename, "rb") |         zinfo.file_size = st.st_size | ||||||
|         zinfo.flag_bits = 0x00 |         zinfo.flag_bits = 0x00 | ||||||
|         zinfo.header_offset = self.fp.tell()    # Start of header bytes |         zinfo.header_offset = self.fp.tell()    # Start of header bytes | ||||||
|  | 
 | ||||||
|  |         self._writecheck(zinfo) | ||||||
|  |         self._didModify = True | ||||||
|  |         fp = open(filename, "rb") | ||||||
|         # Must overwrite CRC and sizes with correct data later |         # Must overwrite CRC and sizes with correct data later | ||||||
|         zinfo.CRC = CRC = 0 |         zinfo.CRC = CRC = 0 | ||||||
|         zinfo.compress_size = compress_size = 0 |         zinfo.compress_size = compress_size = 0 | ||||||
|         zinfo.file_size = file_size = 0 |         zinfo.file_size = file_size = 0 | ||||||
|         self.fp.write(zinfo.FileHeader()) |         self.fp.write(zinfo.FileHeader()) | ||||||
|         zinfo.file_offset = self.fp.tell()      # Start of file bytes |  | ||||||
|         if zinfo.compress_type == ZIP_DEFLATED: |         if zinfo.compress_type == ZIP_DEFLATED: | ||||||
|             cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, |             cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, | ||||||
|                  zlib.DEFLATED, -15) |                  zlib.DEFLATED, -15) | ||||||
|  | @ -461,8 +610,10 @@ def writestr(self, zinfo_or_arcname, bytes): | ||||||
|             zinfo.compress_type = self.compression |             zinfo.compress_type = self.compression | ||||||
|         else: |         else: | ||||||
|             zinfo = zinfo_or_arcname |             zinfo = zinfo_or_arcname | ||||||
|         self._writecheck(zinfo) |  | ||||||
|         zinfo.file_size = len(bytes)            # Uncompressed size |         zinfo.file_size = len(bytes)            # Uncompressed size | ||||||
|  |         zinfo.header_offset = self.fp.tell()    # Start of header bytes | ||||||
|  |         self._writecheck(zinfo) | ||||||
|  |         self._didModify = True | ||||||
|         zinfo.CRC = binascii.crc32(bytes)       # CRC-32 checksum |         zinfo.CRC = binascii.crc32(bytes)       # CRC-32 checksum | ||||||
|         if zinfo.compress_type == ZIP_DEFLATED: |         if zinfo.compress_type == ZIP_DEFLATED: | ||||||
|             co = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, |             co = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, | ||||||
|  | @ -473,8 +624,8 @@ def writestr(self, zinfo_or_arcname, bytes): | ||||||
|             zinfo.compress_size = zinfo.file_size |             zinfo.compress_size = zinfo.file_size | ||||||
|         zinfo.header_offset = self.fp.tell()    # Start of header bytes |         zinfo.header_offset = self.fp.tell()    # Start of header bytes | ||||||
|         self.fp.write(zinfo.FileHeader()) |         self.fp.write(zinfo.FileHeader()) | ||||||
|         zinfo.file_offset = self.fp.tell()      # Start of file bytes |  | ||||||
|         self.fp.write(bytes) |         self.fp.write(bytes) | ||||||
|  |         self.fp.flush() | ||||||
|         if zinfo.flag_bits & 0x08: |         if zinfo.flag_bits & 0x08: | ||||||
|             # Write CRC and file sizes after the file data |             # Write CRC and file sizes after the file data | ||||||
|             self.fp.write(struct.pack("<lLL", zinfo.CRC, zinfo.compress_size, |             self.fp.write(struct.pack("<lLL", zinfo.CRC, zinfo.compress_size, | ||||||
|  | @ -491,7 +642,8 @@ def close(self): | ||||||
|         records.""" |         records.""" | ||||||
|         if self.fp is None: |         if self.fp is None: | ||||||
|             return |             return | ||||||
|         if self.mode in ("w", "a"):             # write ending records |          | ||||||
|  |         if self.mode in ("w", "a") and self._didModify: # write ending records | ||||||
|             count = 0 |             count = 0 | ||||||
|             pos1 = self.fp.tell() |             pos1 = self.fp.tell() | ||||||
|             for zinfo in self.filelist:         # write central directory |             for zinfo in self.filelist:         # write central directory | ||||||
|  | @ -499,23 +651,72 @@ def close(self): | ||||||
|                 dt = zinfo.date_time |                 dt = zinfo.date_time | ||||||
|                 dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] |                 dosdate = (dt[0] - 1980) << 9 | dt[1] << 5 | dt[2] | ||||||
|                 dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2) |                 dostime = dt[3] << 11 | dt[4] << 5 | (dt[5] // 2) | ||||||
|  |                 extra = [] | ||||||
|  |                 if zinfo.file_size > ZIP64_LIMIT \ | ||||||
|  |                         or zinfo.compress_size > ZIP64_LIMIT: | ||||||
|  |                     extra.append(zinfo.file_size) | ||||||
|  |                     extra.append(zinfo.compress_size) | ||||||
|  |                     file_size = 0xffffffff #-1 | ||||||
|  |                     compress_size = 0xffffffff #-1 | ||||||
|  |                 else: | ||||||
|  |                     file_size = zinfo.file_size | ||||||
|  |                     compress_size = zinfo.compress_size | ||||||
|  | 
 | ||||||
|  |                 if zinfo.header_offset > ZIP64_LIMIT: | ||||||
|  |                     extra.append(zinfo.header_offset) | ||||||
|  |                     header_offset = 0xffffffff #-1 | ||||||
|  |                 else: | ||||||
|  |                     header_offset = zinfo.header_offset | ||||||
|  | 
 | ||||||
|  |                 extra_data = zinfo.extra | ||||||
|  |                 if extra: | ||||||
|  |                     # Append a ZIP64 field to the extra's | ||||||
|  |                     extra_data = struct.pack( | ||||||
|  |                             '<hh' + 'q'*len(extra), | ||||||
|  |                             1, 8*len(extra), *extra) + extra_data | ||||||
|  |                      | ||||||
|  |                     extract_version = max(45, zinfo.extract_version) | ||||||
|  |                     create_version = max(45, zinfo.create_version) | ||||||
|  |                 else: | ||||||
|  |                     extract_version = zinfo.extract_version | ||||||
|  |                     create_version = zinfo.create_version | ||||||
|  | 
 | ||||||
|                 centdir = struct.pack(structCentralDir, |                 centdir = struct.pack(structCentralDir, | ||||||
|                   stringCentralDir, zinfo.create_version, |                   stringCentralDir, create_version, | ||||||
|                   zinfo.create_system, zinfo.extract_version, zinfo.reserved, |                   zinfo.create_system, extract_version, zinfo.reserved, | ||||||
|                   zinfo.flag_bits, zinfo.compress_type, dostime, dosdate, |                   zinfo.flag_bits, zinfo.compress_type, dostime, dosdate, | ||||||
|                   zinfo.CRC, zinfo.compress_size, zinfo.file_size, |                   zinfo.CRC, compress_size, file_size, | ||||||
|                   len(zinfo.filename), len(zinfo.extra), len(zinfo.comment), |                   len(zinfo.filename), len(extra_data), len(zinfo.comment), | ||||||
|                   0, zinfo.internal_attr, zinfo.external_attr, |                   0, zinfo.internal_attr, zinfo.external_attr, | ||||||
|                   zinfo.header_offset) |                   header_offset) | ||||||
|                 self.fp.write(centdir) |                 self.fp.write(centdir) | ||||||
|                 self.fp.write(zinfo.filename) |                 self.fp.write(zinfo.filename) | ||||||
|                 self.fp.write(zinfo.extra) |                 self.fp.write(extra_data) | ||||||
|                 self.fp.write(zinfo.comment) |                 self.fp.write(zinfo.comment) | ||||||
|  | 
 | ||||||
|             pos2 = self.fp.tell() |             pos2 = self.fp.tell() | ||||||
|             # Write end-of-zip-archive record |             # Write end-of-zip-archive record | ||||||
|             endrec = struct.pack(structEndArchive, stringEndArchive, |             if pos1 > ZIP64_LIMIT: | ||||||
|                      0, 0, count, count, pos2 - pos1, pos1, 0) |                 # Need to write the ZIP64 end-of-archive records | ||||||
|             self.fp.write(endrec) |                 zip64endrec = struct.pack( | ||||||
|  |                         structEndArchive64, stringEndArchive64, | ||||||
|  |                         44, 45, 45, 0, 0, count, count, pos2 - pos1, pos1) | ||||||
|  |                 self.fp.write(zip64endrec) | ||||||
|  | 
 | ||||||
|  |                 zip64locrec = struct.pack( | ||||||
|  |                         structEndArchive64Locator,  | ||||||
|  |                         stringEndArchive64Locator, 0, pos2, 1) | ||||||
|  |                 self.fp.write(zip64locrec) | ||||||
|  | 
 | ||||||
|  |                 pos3 = self.fp.tell() | ||||||
|  |                 endrec = struct.pack(structEndArchive, stringEndArchive, | ||||||
|  |                          0, 0, count, count, pos2 - pos1, 0xffffffff, 0) # -1, 0) | ||||||
|  |                 self.fp.write(endrec) | ||||||
|  | 
 | ||||||
|  |             else: | ||||||
|  |                 endrec = struct.pack(structEndArchive, stringEndArchive, | ||||||
|  |                          0, 0, count, count, pos2 - pos1, pos1, 0) | ||||||
|  |                 self.fp.write(endrec) | ||||||
|             self.fp.flush() |             self.fp.flush() | ||||||
|         if not self._filePassed: |         if not self._filePassed: | ||||||
|             self.fp.close() |             self.fp.close() | ||||||
|  | @ -619,3 +820,80 @@ def _get_codename(self, pathname, basename): | ||||||
|         if basename: |         if basename: | ||||||
|             archivename = "%s/%s" % (basename, archivename) |             archivename = "%s/%s" % (basename, archivename) | ||||||
|         return (fname, archivename) |         return (fname, archivename) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def main(args = None): | ||||||
|  |     import textwrap | ||||||
|  |     USAGE=textwrap.dedent("""\ | ||||||
|  |         Usage: | ||||||
|  |             zipfile.py -l zipfile.zip        # Show listing of a zipfile | ||||||
|  |             zipfile.py -t zipfile.zip        # Test if a zipfile is valid | ||||||
|  |             zipfile.py -e zipfile.zip target # Extract zipfile into target dir | ||||||
|  |             zipfile.py -c zipfile.zip src ... # Create zipfile from sources | ||||||
|  |         """) | ||||||
|  |     if args is None: | ||||||
|  |         args = sys.argv[1:] | ||||||
|  | 
 | ||||||
|  |     if not args or args[0] not in ('-l', '-c', '-e', '-t'): | ||||||
|  |         print USAGE | ||||||
|  |         sys.exit(1) | ||||||
|  | 
 | ||||||
|  |     if args[0] == '-l': | ||||||
|  |         if len(args) != 2: | ||||||
|  |             print USAGE | ||||||
|  |             sys.exit(1) | ||||||
|  |         zf = ZipFile(args[1], 'r') | ||||||
|  |         zf.printdir() | ||||||
|  |         zf.close() | ||||||
|  | 
 | ||||||
|  |     elif args[0] == '-t': | ||||||
|  |         if len(args) != 2: | ||||||
|  |             print USAGE | ||||||
|  |             sys.exit(1) | ||||||
|  |         zf = ZipFile(args[1], 'r') | ||||||
|  |         zf.testzip() | ||||||
|  |         print "Done testing" | ||||||
|  | 
 | ||||||
|  |     elif args[0] == '-e': | ||||||
|  |         if len(args) != 3: | ||||||
|  |             print USAGE | ||||||
|  |             sys.exit(1) | ||||||
|  | 
 | ||||||
|  |         zf = ZipFile(args[1], 'r') | ||||||
|  |         out = args[2] | ||||||
|  |         for path in zf.namelist(): | ||||||
|  |             if path.startswith('./'):  | ||||||
|  |                 tgt = os.path.join(out, path[2:]) | ||||||
|  |             else: | ||||||
|  |                 tgt = os.path.join(out, path) | ||||||
|  | 
 | ||||||
|  |             tgtdir = os.path.dirname(tgt) | ||||||
|  |             if not os.path.exists(tgtdir): | ||||||
|  |                 os.makedirs(tgtdir) | ||||||
|  |             fp = open(tgt, 'wb') | ||||||
|  |             fp.write(zf.read(path)) | ||||||
|  |             fp.close() | ||||||
|  |         zf.close() | ||||||
|  | 
 | ||||||
|  |     elif args[0] == '-c': | ||||||
|  |         if len(args) < 3: | ||||||
|  |             print USAGE | ||||||
|  |             sys.exit(1) | ||||||
|  | 
 | ||||||
|  |         def addToZip(zf, path, zippath): | ||||||
|  |             if os.path.isfile(path): | ||||||
|  |                 zf.write(path, zippath, ZIP_DEFLATED) | ||||||
|  |             elif os.path.isdir(path): | ||||||
|  |                 for nm in os.listdir(path): | ||||||
|  |                     addToZip(zf,  | ||||||
|  |                             os.path.join(path, nm), os.path.join(zippath, nm)) | ||||||
|  |             # else: ignore  | ||||||
|  | 
 | ||||||
|  |         zf = ZipFile(args[1], 'w', allowZip64=True) | ||||||
|  |         for src in args[2:]: | ||||||
|  |             addToZip(zf, src, os.path.basename(src)) | ||||||
|  | 
 | ||||||
|  |         zf.close() | ||||||
|  | 
 | ||||||
|  | if __name__ == "__main__": | ||||||
|  |     main() | ||||||
|  |  | ||||||
|  | @ -152,6 +152,7 @@ Extension Modules | ||||||
|   aborts the db transaction safely when a modifier callback fails. |   aborts the db transaction safely when a modifier callback fails. | ||||||
|   Fixes SF python patch/bug #1408584. |   Fixes SF python patch/bug #1408584. | ||||||
| 
 | 
 | ||||||
|  | - Patch #1446489: add support for the ZIP64 extensions to zipfile.  | ||||||
| 
 | 
 | ||||||
| Library | Library | ||||||
| ------- | ------- | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Ronald Oussoren
						Ronald Oussoren