Skip to content

Commit 998bcca

Browse files
committed
gh-156539: Validate duplicate ZIP members individually in testzip
Pass each ZipInfo directly to open() so testzip() does not resolve duplicate filenames to the last matching member. Co-authored-by: lipengyu <lipengyu@kylinos.cn>
1 parent 24e5a55 commit 998bcca

3 files changed

Lines changed: 29 additions & 3 deletions

File tree

Lib/test/test_zipfile/test_core.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4335,6 +4335,30 @@ def test_closed_zip_raises_ValueError(self):
43354335
f.write('zipfile test data')
43364336
self.assertRaises(ValueError, zipf.write, TESTFN)
43374337

4338+
def test_testzip_with_duplicate_names(self):
4339+
data = io.BytesIO()
4340+
with zipfile.ZipFile(data, mode="w") as zipf:
4341+
zipf.writestr("duplicate", b"corrupt")
4342+
with warnings.catch_warnings():
4343+
warnings.simplefilter("ignore")
4344+
zipf.writestr("duplicate", b"valid")
4345+
4346+
zipdata = bytearray(data.getvalue())
4347+
with zipfile.ZipFile(io.BytesIO(zipdata)) as zipf:
4348+
zinfo = zipf.infolist()[0]
4349+
file_header = struct.unpack_from(
4350+
zipfile.structFileHeader, zipdata, zinfo.header_offset
4351+
)
4352+
name_length, extra_length = file_header[-2:]
4353+
data_offset = (zinfo.header_offset + zipfile.sizeFileHeader
4354+
+ name_length + extra_length)
4355+
zipdata[data_offset] ^= 1
4356+
4357+
with zipfile.ZipFile(io.BytesIO(zipdata)) as zipf:
4358+
self.assertRaises(zipfile.BadZipFile, zipf.read,
4359+
zipf.infolist()[0])
4360+
self.assertEqual("duplicate", zipf.testzip())
4361+
43384362
def test_bad_constructor_mode(self):
43394363
"""Check that bad modes passed to ZipFile constructor are caught."""
43404364
self.assertRaises(ValueError, zipfile.ZipFile, TESTFN, "q")

Lib/zipfile/__init__.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2145,7 +2145,7 @@ def testzip(self):
21452145
try:
21462146
# Read by chunks, to avoid an OverflowError or a
21472147
# MemoryError with very large embedded files.
2148-
with self.open(zinfo.filename, "r") as f:
2148+
with self.open(zinfo, "r") as f:
21492149
while f.read(chunk_size): # Check CRC-32
21502150
pass
21512151
except BadZipFile:
@@ -2401,8 +2401,7 @@ def remove(self, zinfo_or_arcname):
24012401
except KeyError:
24022402
pass
24032403

2404-
# Avoid missing entry if there is another entry having the same name,
2405-
# to prevent an error on `testzip()`.
2404+
# Keep the last remaining entry with this name in NameToInfo.
24062405
# Reverse the order as NameToInfo normally stores the last added one.
24072406
for zi in reversed(self.filelist):
24082407
if zi.filename == zinfo.filename:
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :meth:`zipfile.ZipFile.testzip` to validate every member in a ZIP
2+
archive with duplicate names. It could previously miss corruption in an
3+
earlier member.

0 commit comments

Comments
 (0)