From 0dfcfc8b59610422def8ec49d2319ec51380e9fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20v=2E=20L=C3=B6wis?= Date: Sat, 24 Jan 2009 14:00:33 +0000 Subject: [PATCH] Issue #4710: Extract directories properly in the zipfile module; allow adding directories to a zipfile. --- Lib/test/test_zipfile.py | 26 +++++++++++++++++++++++++- Lib/test/zipdir.zip | Bin 0 -> 374 bytes Lib/zipfile.py | 23 ++++++++++++++++++++--- Misc/NEWS | 3 +++ 4 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 Lib/test/zipdir.zip diff --git a/Lib/test/test_zipfile.py b/Lib/test/test_zipfile.py index 9cc586028a2..fcb55d1523a 100644 --- a/Lib/test/test_zipfile.py +++ b/Lib/test/test_zipfile.py @@ -11,9 +11,10 @@ from tempfile import TemporaryFile from random import randint, random import test.test_support as support -from test.test_support import TESTFN, run_unittest +from test.test_support import TESTFN, run_unittest, findfile TESTFN2 = TESTFN + "2" +TESTFNDIR = TESTFN + "d" FIXEDTEST_SIZE = 1000 SMALL_TEST_DATA = [('_ziptest1', '1q2w3e4r5t'), @@ -1011,6 +1012,28 @@ class TestsWithMultipleOpens(unittest.TestCase): def tearDown(self): os.remove(TESTFN2) +class TestWithDirectory(unittest.TestCase): + def setUp(self): + os.mkdir(TESTFN2) + + def testExtractDir(self): + zipf = zipfile.ZipFile(findfile("zipdir.zip")) + zipf.extractall(TESTFN2) + self.assertTrue(os.path.isdir(os.path.join(TESTFN2, "a"))) + self.assertTrue(os.path.isdir(os.path.join(TESTFN2, "a", "b"))) + self.assertTrue(os.path.exists(os.path.join(TESTFN2, "a", "b", "c"))) + + def testStoreDir(self): + os.mkdir(os.path.join(TESTFN2, "x")) + zipf = zipfile.ZipFile(TESTFN, "w") + zipf.write(os.path.join(TESTFN2, "x"), "x") + self.assertTrue(zipf.filelist[0].filename.endswith("x/")) + + def tearDown(self): + shutil.rmtree(TESTFN2) + if os.path.exists(TESTFN): + os.remove(TESTFN) + class UniversalNewlineTests(unittest.TestCase): def setUp(self): @@ -1119,6 +1142,7 @@ class UniversalNewlineTests(unittest.TestCase): def test_main(): run_unittest(TestsWithSourceFile, TestZip64InSmallFiles, OtherTests, PyZipFileTests, DecryptionTests, TestsWithMultipleOpens, + TestWithDirectory, UniversalNewlineTests, TestsWithRandomBinaryFiles) if __name__ == "__main__": diff --git a/Lib/test/zipdir.zip b/Lib/test/zipdir.zip new file mode 100644 index 0000000000000000000000000000000000000000..ac21d7a1edd2ca1177f071465ca141ab405b1927 GIT binary patch literal 374 zcmWIWW@h1H0D-@|RIR`aD8a-a%8;lZ8p6rIT-%Z4IUR^YD_9s_Fu!09fNNshjjD+S ztSJeit&wPLtWa&qVCz8oKp0^iBa=8YE?4qG-7CQG))B-6xtSH>=FkcTh5&DbW=z*2 uYc>UI2Du%m9b!Lz?W_zi?FI&&mdJ6*Ca-0n$rA90maKRyZ91 literal 0 HcmV?d00001 diff --git a/Lib/zipfile.py b/Lib/zipfile.py index eae9d1f038c..31063535c8d 100644 --- a/Lib/zipfile.py +++ b/Lib/zipfile.py @@ -2,7 +2,7 @@ Read and write ZIP files. """ import struct, os, time, sys, shutil -import binascii, cStringIO +import binascii, cStringIO, stat try: import zlib # We may need its compression method @@ -952,11 +952,11 @@ class ZipFile: """ # build the destination pathname, replacing # forward slashes to platform specific separators. - if targetpath[-1:] == "/": + if targetpath[-1:] in (os.path.sep, os.path.altsep): targetpath = targetpath[:-1] # don't include leading "/" from file name if present - if os.path.isabs(member.filename): + if member.filename[0] == '/': targetpath = os.path.join(targetpath, member.filename[1:]) else: targetpath = os.path.join(targetpath, member.filename) @@ -968,6 +968,10 @@ class ZipFile: if upperdirs and not os.path.exists(upperdirs): os.makedirs(upperdirs) + if member.filename[-1] == '/': + os.mkdir(targetpath) + return targetpath + source = self.open(member, pwd=pwd) target = file(targetpath, "wb") shutil.copyfileobj(source, target) @@ -1007,6 +1011,7 @@ class ZipFile: "Attempt to write to ZIP archive that was already closed") st = os.stat(filename) + isdir = stat.S_ISDIR(st.st_mode) mtime = time.localtime(st.st_mtime) date_time = mtime[0:6] # Create ZipInfo instance to store file information @@ -1015,6 +1020,8 @@ class ZipFile: arcname = os.path.normpath(os.path.splitdrive(arcname)[1]) while arcname[0] in (os.sep, os.altsep): arcname = arcname[1:] + if isdir: + arcname += '/' zinfo = ZipInfo(arcname, date_time) zinfo.external_attr = (st[0] & 0xFFFF) << 16L # Unix attributes if compress_type is None: @@ -1028,6 +1035,16 @@ class ZipFile: self._writecheck(zinfo) self._didModify = True + + if isdir: + zinfo.file_size = 0 + zinfo.compress_size = 0 + zinfo.CRC = 0 + self.filelist.append(zinfo) + self.NameToInfo[zinfo.filename] = zinfo + self.fp.write(zinfo.FileHeader()) + return + fp = open(filename, "rb") # Must overwrite CRC and sizes with correct data later zinfo.CRC = CRC = 0 diff --git a/Misc/NEWS b/Misc/NEWS index 52693202919..8bdbeb587c7 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -145,6 +145,9 @@ Core and Builtins Library ------- +- Issue #4710: Extract directories properly in the zipfile module; + allow adding directories to a zipfile. + - Issue #3807: _multiprocessing build fails when configure is passed --without-threads argument. When this occurs, _multiprocessing will be disabled, and not compiled.