Skip to content

Commit f04ef07

Browse files
committed
gh-153702: Prevent writing improper extra fields to local file entry
1 parent 65585ca commit f04ef07

3 files changed

Lines changed: 64 additions & 3 deletions

File tree

Lib/test/test_zipfile/test_core.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1189,6 +1189,53 @@ def test_generated_valid_zip64_extra(self):
11891189
self.assertEqual(zinfo.header_offset, expected_header_offset)
11901190
self.assertEqual(zf.read(zinfo), expected_content)
11911191

1192+
def test_generated_extra_in_local_entry(self):
1193+
"""Should not write duplicated or improper fields to local file entry."""
1194+
# zip64
1195+
fh = io.BytesIO()
1196+
with zipfile.ZipFile(fh, 'w') as zh:
1197+
zinfo = zipfile.ZipInfo('strfile')
1198+
zinfo.extra = (
1199+
# zip64
1200+
struct.pack('<HHQQ', 0x0001, 16, 0, 0) +
1201+
# unicode comment
1202+
struct.pack('<HHBL5s', 0x6375, 10, 1,
1203+
zipfile.crc32(b'\u4e00'), b'\u4e00') +
1204+
# invalid tail
1205+
b'zzz'
1206+
)
1207+
zh.writestr(zinfo, self.data)
1208+
1209+
fh.seek(zinfo.header_offset)
1210+
entry = fh.read(zh.start_dir - zinfo.header_offset - zinfo.compress_size)
1211+
header = struct.unpack_from(zipfile.structFileHeader, entry)
1212+
extra = entry[-header[zipfile._FH_EXTRA_FIELD_LENGTH]:]
1213+
self.assertEqual(extra, (
1214+
struct.pack('<HHQQ', 0x0001, 16, 25889, 25889) +
1215+
b'zzz'
1216+
))
1217+
1218+
# no zip64
1219+
fh = io.BytesIO()
1220+
with zipfile.ZipFile(fh, 'w') as zh:
1221+
zinfo = zipfile.ZipInfo('strfile')
1222+
zinfo.extra = (
1223+
# zip64
1224+
struct.pack('<HHQQ', 0x0001, 16, 0, 0) +
1225+
# unicode comment
1226+
struct.pack('<HHBL5s', 0x6375, 10, 1,
1227+
zipfile.crc32(b'\u4e00'), b'\u4e00') +
1228+
# invalid tail
1229+
b'zzz'
1230+
)
1231+
zh.writestr(zinfo, 'foo')
1232+
1233+
fh.seek(zinfo.header_offset)
1234+
entry = fh.read(zh.start_dir - zinfo.header_offset - zinfo.compress_size)
1235+
header = struct.unpack_from(zipfile.structFileHeader, entry)
1236+
extra = entry[-header[zipfile._FH_EXTRA_FIELD_LENGTH]:]
1237+
self.assertEqual(extra, b'zzz')
1238+
11921239
def test_force_zip64(self):
11931240
"""Test that forcing zip64 extensions correctly notes this in the zip file"""
11941241

Lib/zipfile/__init__.py

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,7 @@ def FileHeader(self, zip64=None):
541541
compress_size = self.compress_size
542542
file_size = self.file_size
543543

544-
extra = self.extra
544+
extra = self._get_local_extra()
545545

546546
min_version = 0
547547
if zip64 is None:
@@ -550,8 +550,11 @@ def FileHeader(self, zip64=None):
550550
zip64 = file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT
551551
if zip64:
552552
fmt = '<HHQQ'
553-
extra = extra + struct.pack(fmt,
554-
1, struct.calcsize(fmt)-4, file_size, compress_size)
553+
extra = struct.pack(
554+
fmt, 1, struct.calcsize(fmt)-4,
555+
file_size, compress_size
556+
) + extra
557+
555558
file_size = 0xffffffff
556559
compress_size = 0xffffffff
557560
min_version = ZIP64_VERSION
@@ -628,6 +631,14 @@ def _decodeExtra(self, filename_crc):
628631

629632
extra = extra[ln+4:]
630633

634+
def _get_local_extra(self):
635+
return _Extra.strip(self.extra, (
636+
# should be added on demand later
637+
0x0001, # Zip64
638+
# should only exist in central directory
639+
0x6375, # Unicode Comment
640+
))
641+
631642
@classmethod
632643
def from_file(cls, filename, arcname=None, *, strict_timestamps=True):
633644
"""Construct an appropriate ZipInfo for a file on the filesystem.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix an issue where duplicated or improper extra fields were written to
2+
the local file entry if :attr:`ZipInfo.extra <zipfile.ZipInfo.extra>`
3+
had them.

0 commit comments

Comments
 (0)