Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions Lib/test/test_zipfile/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1136,6 +1136,54 @@ def test_bad_zip64_end_of_central_dir(self):
zipfile.ZipFile(io.BytesIO(zipdata))
self.assertFalse(zipfile.is_zipfile(io.BytesIO(zipdata)))

def test_multi_disk_zip64_end_of_central_dir(self):
zipdata = self.make_zip64_file()
eocd64 = zipdata.rfind(zipfile.stringEndArchive64)
disk_fields_offset = eocd64 + struct.calcsize('<4sQ2H')

for disk_number, disk_start in ((1, 0), (0, 1), (1, 1)):
corrupted = bytearray(zipdata)
struct.pack_into(
'<LL', corrupted, disk_fields_offset,
disk_number, disk_start,
)
with self.subTest(
disk_number=disk_number,
disk_start=disk_start,
):
with self.assertRaisesRegex(zipfile.BadZipFile,
'multiple disks'):
zipfile.ZipFile(io.BytesIO(corrupted))
self.assertFalse(zipfile.is_zipfile(io.BytesIO(corrupted)))

def test_multi_disk_end_of_central_dir_with_zip64(self):
zipdata = self.make_zip64_file()
eocd = zipdata.rfind(zipfile.stringEndArchive)

for disk_number, disk_start in ((1, 0), (0, 1), (1, 1)):
corrupted = bytearray(zipdata)
struct.pack_into(
'<HH', corrupted, eocd + 4,
disk_number, disk_start,
)
with self.subTest(
disk_number=disk_number,
disk_start=disk_start,
):
with self.assertRaisesRegex(zipfile.BadZipFile,
'multiple disks'):
zipfile.ZipFile(io.BytesIO(corrupted))
self.assertFalse(zipfile.is_zipfile(io.BytesIO(corrupted)))

def test_zip64_end_of_central_dir_disk_sentinels(self):
zipdata = bytearray(self.make_zip64_file())
eocd = zipdata.rfind(zipfile.stringEndArchive)
struct.pack_into('<HH', zipdata, eocd + 4, 0xffff, 0xffff)

with zipfile.ZipFile(io.BytesIO(zipdata)) as zf:
self.assertEqual(zf.namelist(), ['test.txt'])
self.assertTrue(zipfile.is_zipfile(io.BytesIO(zipdata)))

def test_zip64_end_of_central_dir_record_not_found(self):
zipdata = self.make_zip64_file()
zipdata = zipdata.replace(b"PK\x06\x06", b'\x00'*4)
Expand Down Expand Up @@ -4246,6 +4294,34 @@ def test_damaged_zipfile(self):
fp = io.BytesIO(zipfiledata[:N])
self.assertRaises(zipfile.BadZipFile, zipfile.ZipFile, fp)

def test_multi_disk_end_of_central_dir(self):
for comment in (b'', b'comment'):
archive = io.BytesIO()
with zipfile.ZipFile(archive, 'w') as zf:
zf.writestr('entry.txt', b'payload')
zf.comment = comment
zipdata = archive.getvalue()
eocd = zipdata.rfind(zipfile.stringEndArchive)

for disk_number, disk_start in ((1, 0), (0, 1), (1, 1)):
corrupted = bytearray(zipdata)
struct.pack_into(
'<HH', corrupted, eocd + 4,
disk_number, disk_start,
)
with self.subTest(
comment=comment,
disk_number=disk_number,
disk_start=disk_start,
):
with self.assertRaisesRegex(zipfile.BadZipFile,
'multiple disks'):
zipfile.ZipFile(io.BytesIO(corrupted))
fp = io.BytesIO(corrupted)
pos = fp.seek(5)
self.assertFalse(zipfile.is_zipfile(fp))
self.assertEqual(fp.tell(), pos)

def test_is_zip_valid_file(self):
"""Check that is_zipfile() correctly identifies zip files."""
# - passing a filename
Expand Down
21 changes: 18 additions & 3 deletions Lib/zipfile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ def _check_zipfile(fp):
centdir = struct.unpack(structCentralDir, data) # CD is the right size
if centdir[_CD_SIGNATURE] == stringCentralDir:
return True # First central directory entry has correct magic number
except OSError:
except (OSError, BadZipFile):
pass
return False

Expand Down Expand Up @@ -282,6 +282,15 @@ def _handle_prepended_data(endrec, debug=0):

return offset_cd, concat


def _validate_end_record(endrec, *, allow_zip64_sentinels=False):
valid_disk_numbers = (0, 0xffff) if allow_zip64_sentinels else (0,)
if (endrec[_ECD_DISK_NUMBER] not in valid_disk_numbers or
endrec[_ECD_DISK_START] not in valid_disk_numbers):
raise BadZipFile("zipfiles that span multiple disks are not supported")
return endrec


def _EndRecData64(fpin, offset, endrec):
"""
Read the ZIP64 end-of-archive records and use that to update endrec
Expand Down Expand Up @@ -372,7 +381,10 @@ def _EndRecData(fpin):
endrec.append(filesize - sizeEndCentDir)

# Try to read the "Zip64 end of central directory" structure
return _EndRecData64(fpin, filesize - sizeEndCentDir, endrec)
_validate_end_record(endrec, allow_zip64_sentinels=True)
return _validate_end_record(
_EndRecData64(fpin, filesize - sizeEndCentDir, endrec)
)

# Either this is not a ZIP file, or it is a ZIP file with an archive
# comment. Search the end of the file for the "end of central directory"
Expand All @@ -396,7 +408,10 @@ def _EndRecData(fpin):
endrec.append(maxCommentStart + start)

# Try to read the "Zip64 end of central directory" structure
return _EndRecData64(fpin, maxCommentStart + start, endrec)
_validate_end_record(endrec, allow_zip64_sentinels=True)
return _validate_end_record(
_EndRecData64(fpin, maxCommentStart + start, endrec)
)

# Unable to find a valid end of central directory structure
return None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Reject ZIP archives whose end of central directory records indicate that
the archive spans multiple disks.
Loading