From ac277285ec9727882286c1dbaad858e0f68f07c3 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Sun, 28 Jun 2026 20:53:43 +0800 Subject: [PATCH 1/4] gh-152486: Fix duplicated zip64 extra in local file entry --- Lib/test/test_zipfile/test_core.py | 28 +++++++++++++++++++ Lib/zipfile/__init__.py | 9 ++++-- ...-06-29-03-43-41.gh-issue-152486.kASYh8.rst | 1 + 3 files changed, 36 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-06-29-03-43-41.gh-issue-152486.kASYh8.rst diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index 4f20209927e7b3d..abc47ea625c83bc 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -1189,6 +1189,34 @@ def test_generated_valid_zip64_extra(self): self.assertEqual(zinfo.header_offset, expected_header_offset) self.assertEqual(zf.read(zinfo), expected_content) + def test_generated_valid_zip64_extra_in_local_entry(self): + """Should not write duplicated zip64 fields to the local file entry.""" + fh = io.BytesIO() + with zipfile.ZipFile(fh, 'w') as zh: + zinfo = zipfile.ZipInfo('strfile') + zinfo.extra = ( + # zip64 (should be stripped) + b'\x01\x00\x10\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + + # invalid tail + b'zzz' + ) + zh.writestr(zinfo, self.data) + + fh.seek(0) + fh.seek(zinfo.header_offset) + entry = fh.read(zh.start_dir - zinfo.header_offset - zinfo.compress_size) + header = struct.unpack_from(zipfile.structFileHeader, entry) + extra_bytes = entry[-header[zipfile._FH_EXTRA_FIELD_LENGTH]:] + self.assertEqual(extra_bytes, ( + b'\x01\x00\x10\x00' + b'!e\x00\x00\x00\x00\x00\x00' + b'!e\x00\x00\x00\x00\x00\x00' + b'zzz' + )) + def test_force_zip64(self): """Test that forcing zip64 extensions correctly notes this in the zip file""" diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py index 418933a2e8d9e87..6a3d49fd9a9d338 100644 --- a/Lib/zipfile/__init__.py +++ b/Lib/zipfile/__init__.py @@ -550,8 +550,13 @@ def FileHeader(self, zip64=None): zip64 = file_size > ZIP64_LIMIT or compress_size > ZIP64_LIMIT if zip64: fmt = '` already contained a Zip64 field. From d467cd7da87878ec3a9264771e7b13db08a79766 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Mon, 13 Jul 2026 18:51:41 +0800 Subject: [PATCH 2/4] Strip bad fields for local entry --- Lib/test/test_zipfile/test_core.py | 22 ++++++++++++++++++++++ Lib/zipfile/__init__.py | 11 +++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index abc47ea625c83bc..d166d226903e6d8 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -1217,6 +1217,28 @@ def test_generated_valid_zip64_extra_in_local_entry(self): b'zzz' )) + # should strip zip64 field if zip64 not used + fh = io.BytesIO() + with zipfile.ZipFile(fh, 'w') as zh: + zinfo = zipfile.ZipInfo('strfile') + zinfo.extra = ( + # zip64 (should be stripped) + b'\x01\x00\x10\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + b'\x00\x00\x00\x00\x00\x00\x00\x00' + + # invalid tail + b'zzz' + ) + zh.writestr(zinfo, 'foo') + + fh.seek(0) + fh.seek(zinfo.header_offset) + entry = fh.read(zh.start_dir - zinfo.header_offset - zinfo.compress_size) + header = struct.unpack_from(zipfile.structFileHeader, entry) + extra_bytes = entry[-header[zipfile._FH_EXTRA_FIELD_LENGTH]:] + self.assertEqual(extra_bytes, b'zzz') + def test_force_zip64(self): """Test that forcing zip64 extensions correctly notes this in the zip file""" diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py index 6a3d49fd9a9d338..6914316c49e59d2 100644 --- a/Lib/zipfile/__init__.py +++ b/Lib/zipfile/__init__.py @@ -541,7 +541,14 @@ def FileHeader(self, zip64=None): compress_size = self.compress_size file_size = self.file_size - extra = self.extra + # Strip extra fields that should not exist in the local entry or should + # be determined later. (self.extra can be read from central directory + # when read in append mode) + extra = _Extra.strip(self.extra, ( + 0x0001, # Zip64 + 0x0017, # Strong Encryption Header + 0x6375, # Unicode Comment + )) min_version = 0 if zip64 is None: @@ -555,7 +562,7 @@ def FileHeader(self, zip64=None): extra = struct.pack( fmt, 1, struct.calcsize(fmt)-4, file_size, compress_size - ) + _Extra.strip(extra, (1,)) + ) + extra file_size = 0xffffffff compress_size = 0xffffffff From 71a554482b65b582c87b401d2c16ffd950cd325e Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Mon, 13 Jul 2026 19:16:52 +0800 Subject: [PATCH 3/4] Update news --- .../Library/2026-06-29-03-43-41.gh-issue-152486.kASYh8.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2026-06-29-03-43-41.gh-issue-152486.kASYh8.rst b/Misc/NEWS.d/next/Library/2026-06-29-03-43-41.gh-issue-152486.kASYh8.rst index 62ce56a7e641eb9..737be6680d2b1a8 100644 --- a/Misc/NEWS.d/next/Library/2026-06-29-03-43-41.gh-issue-152486.kASYh8.rst +++ b/Misc/NEWS.d/next/Library/2026-06-29-03-43-41.gh-issue-152486.kASYh8.rst @@ -1 +1,4 @@ -Fix an issue where duplicated Zip64 extra fields were written if :attr:`ZipInfo.extra ` already contained a Zip64 field. +Fix an issue where duplicated Zip64 extra fields or other invalid +fields were written if :attr:`ZipInfo.extra ` +already contained them (which could be read from central directory in +append mode). From 150689c99ec5ab2cc932e1948aff72b5296e73a9 Mon Sep 17 00:00:00 2001 From: Danny Lin Date: Mon, 13 Jul 2026 19:53:15 +0800 Subject: [PATCH 4/4] Revise code comment and news --- Lib/zipfile/__init__.py | 3 +-- .../2026-06-29-03-43-41.gh-issue-152486.kASYh8.rst | 8 ++++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py index 6914316c49e59d2..00a7eb06ab3fdaf 100644 --- a/Lib/zipfile/__init__.py +++ b/Lib/zipfile/__init__.py @@ -542,8 +542,7 @@ def FileHeader(self, zip64=None): file_size = self.file_size # Strip extra fields that should not exist in the local entry or should - # be determined later. (self.extra can be read from central directory - # when read in append mode) + # be determined later. extra = _Extra.strip(self.extra, ( 0x0001, # Zip64 0x0017, # Strong Encryption Header diff --git a/Misc/NEWS.d/next/Library/2026-06-29-03-43-41.gh-issue-152486.kASYh8.rst b/Misc/NEWS.d/next/Library/2026-06-29-03-43-41.gh-issue-152486.kASYh8.rst index 737be6680d2b1a8..dacc975e464bc4f 100644 --- a/Misc/NEWS.d/next/Library/2026-06-29-03-43-41.gh-issue-152486.kASYh8.rst +++ b/Misc/NEWS.d/next/Library/2026-06-29-03-43-41.gh-issue-152486.kASYh8.rst @@ -1,4 +1,4 @@ -Fix an issue where duplicated Zip64 extra fields or other invalid -fields were written if :attr:`ZipInfo.extra ` -already contained them (which could be read from central directory in -append mode). +Fix an issue where duplicated Zip64 extra fields or other improper +fields were written to the local file entry if +:attr:`ZipInfo.extra ` contained them. +