Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2bcbc14

Browse files
committed
Fixes Issue #19081: When a zipimport .zip file in sys.path being imported from
is modified during the lifetime of the Python process after zipimport has already cached the zip's table of contents we detect this and recover rather than read bad data from the .zip (causing odd import errors).
1 parent a21acb5 commit 2bcbc14

3 files changed

Lines changed: 328 additions & 66 deletions

File tree

Lib/test/test_zipimport.py

Lines changed: 85 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,27 @@ def module_path_to_dotted_name(path):
4646
pyc_ext = ('.pyc' if __debug__ else '.pyo')
4747

4848

49+
def _write_zip_package(zipname, files,
50+
data_to_prepend=b"", compression=ZIP_STORED):
51+
z = ZipFile(zipname, "w")
52+
try:
53+
for name, (mtime, data) in files.items():
54+
zinfo = ZipInfo(name, time.localtime(mtime))
55+
zinfo.compress_type = compression
56+
z.writestr(zinfo, data)
57+
finally:
58+
z.close()
59+
60+
if data_to_prepend:
61+
# Prepend data to the start of the zipfile
62+
with open(zipname, "rb") as f:
63+
zip_data = f.read()
64+
65+
with open(zipname, "wb") as f:
66+
f.write(data_to_prepend)
67+
f.write(zip_data)
68+
69+
4970
class UncompressedZipImportTestCase(ImportHooksBaseTestCase):
5071

5172
compression = ZIP_STORED
@@ -58,23 +79,9 @@ def setUp(self):
5879
ImportHooksBaseTestCase.setUp(self)
5980

6081
def doTest(self, expected_ext, files, *modules, **kw):
61-
z = ZipFile(TEMP_ZIP, "w")
82+
_write_zip_package(TEMP_ZIP, files, data_to_prepend=kw.get("stuff"),
83+
compression=self.compression)
6284
try:
63-
for name, (mtime, data) in files.items():
64-
zinfo = ZipInfo(name, time.localtime(mtime))
65-
zinfo.compress_type = self.compression
66-
z.writestr(zinfo, data)
67-
z.close()
68-
69-
stuff = kw.get("stuff", None)
70-
if stuff is not None:
71-
# Prepend 'stuff' to the start of the zipfile
72-
with open(TEMP_ZIP, "rb") as f:
73-
data = f.read()
74-
with open(TEMP_ZIP, "wb") as f:
75-
f.write(stuff)
76-
f.write(data)
77-
7885
sys.path.insert(0, TEMP_ZIP)
7986

8087
mod = __import__(".".join(modules), globals(), locals(),
@@ -89,7 +96,8 @@ def doTest(self, expected_ext, files, *modules, **kw):
8996
self.assertEqual(file, os.path.join(TEMP_ZIP,
9097
*modules) + expected_ext)
9198
finally:
92-
z.close()
99+
while TEMP_ZIP in sys.path:
100+
sys.path.remove(TEMP_ZIP)
93101
os.remove(TEMP_ZIP)
94102

95103
def testAFakeZlib(self):
@@ -395,10 +403,67 @@ class CompressedZipImportTestCase(UncompressedZipImportTestCase):
395403
compression = ZIP_DEFLATED
396404

397405

406+
class ZipFileModifiedAfterImportTestCase(ImportHooksBaseTestCase):
407+
def setUp(self):
408+
zipimport._zip_directory_cache.clear()
409+
zipimport._zip_stat_cache.clear()
410+
ImportHooksBaseTestCase.setUp(self)
411+
412+
def tearDown(self):
413+
ImportHooksBaseTestCase.tearDown(self)
414+
if os.path.exists(TEMP_ZIP):
415+
os.remove(TEMP_ZIP)
416+
417+
def testZipFileChangesAfterFirstImport(self):
418+
"""Alter the zip file after caching its index and try an import."""
419+
packdir = TESTPACK + os.sep
420+
files = {packdir + "__init__" + pyc_ext: (NOW, test_pyc),
421+
packdir + TESTMOD + ".py": (NOW, "test_value = 38\n"),
422+
"ziptest_a.py": (NOW, "test_value = 23\n"),
423+
"ziptest_b.py": (NOW, "test_value = 42\n"),
424+
"ziptest_c.py": (NOW, "test_value = 1337\n")}
425+
zipfile_path = TEMP_ZIP
426+
_write_zip_package(zipfile_path, files)
427+
self.assertTrue(os.path.exists(zipfile_path))
428+
sys.path.insert(0, zipfile_path)
429+
430+
# Import something out of the zipfile and confirm it is correct.
431+
testmod = __import__(TESTPACK + "." + TESTMOD,
432+
globals(), locals(), ["__dummy__"])
433+
self.assertEqual(testmod.test_value, 38)
434+
# Import something else out of the zipfile and confirm it is correct.
435+
ziptest_b = __import__("ziptest_b", globals(), locals(), ["test_value"])
436+
self.assertEqual(ziptest_b.test_value, 42)
437+
438+
# Truncate and fill the zip file with non-zip garbage.
439+
with open(zipfile_path, "rb") as orig_zip_file:
440+
orig_zip_file_contents = orig_zip_file.read()
441+
with open(zipfile_path, "wb") as byebye_valid_zip_file:
442+
byebye_valid_zip_file.write(b"Tear down this wall!\n"*1987)
443+
# Now that the zipfile has been replaced, import something else from it
444+
# which should fail as the file contents are now garbage.
445+
with self.assertRaises(ImportError):
446+
ziptest_a = __import__("ziptest_a", globals(), locals(),
447+
["test_value"])
448+
self.assertEqual(ziptest_a.test_value, 23)
449+
450+
# Now lets make it a valid zipfile that has some garbage at the start.
451+
# This alters all of the offsets within the file
452+
with open(zipfile_path, "wb") as new_zip_file:
453+
new_zip_file.write(b"X"*1991) # The year Python was created.
454+
new_zip_file.write(orig_zip_file_contents)
455+
456+
# Now that the zip file has been "restored" to a valid but different
457+
# zipfile the zipimporter should *successfully* re-read the new zip
458+
# file's end of file central index and be able to import from it again.
459+
ziptest_c = __import__("ziptest_c", globals(), locals(), ["test_value"])
460+
self.assertEqual(ziptest_c.test_value, 1337)
461+
462+
398463
class BadFileZipImportTestCase(unittest.TestCase):
399464
def assertZipFailure(self, filename):
400-
self.assertRaises(zipimport.ZipImportError,
401-
zipimport.zipimporter, filename)
465+
with self.assertRaises(zipimport.ZipImportError):
466+
zipimport.zipimporter(filename)
402467

403468
def testNoFile(self):
404469
self.assertZipFailure('AdfjdkFJKDFJjdklfjs')
@@ -472,6 +537,7 @@ def test_main():
472537
UncompressedZipImportTestCase,
473538
CompressedZipImportTestCase,
474539
BadFileZipImportTestCase,
540+
ZipFileModifiedAfterImportTestCase,
475541
)
476542
finally:
477543
support.unlink(TESTMOD)

Misc/NEWS

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ What's New in Python 3.3.4 release candidate 1?
1010
Core and Builtins
1111
-----------------
1212

13+
- Issue #19081: When a zipimport .zip file in sys.path being imported from
14+
is modified during the lifetime of the Python process after zipimport has
15+
already cached the zip's table of contents we detect this and recover
16+
rather than read bad data from the .zip (causing odd import errors).
17+
1318
- Issue #17432: Drop UCS2 from names of Unicode functions in python3.def.
1419

1520
- Issue #19969: PyBytes_FromFormatV() now raises an OverflowError if "%c"

0 commit comments

Comments
 (0)