From eae6de23b20baaa164eec915b355ef7271b3150b Mon Sep 17 00:00:00 2001 From: tonghuaroot Date: Thu, 9 Jul 2026 16:42:28 +0800 Subject: [PATCH] gh-153426: Raise BadZipFile for an invalid UTF-8 filename in zipfile When a central directory or local file header entry sets the UTF-8 filename flag (general purpose bit 11) but stores bytes that are not valid UTF-8, opening the archive raised a raw UnicodeDecodeError instead of the BadZipFile that signals a corrupt archive. Wrap the bit-11 filename decode in both _RealGetContents and ZipFile.open to raise BadZipFile, mirroring the handling of the unicode path extra field, while leaving the metadata_encoding decode path unchanged. --- Lib/test/test_zipfile/test_core.py | 32 +++++++++++++++++++ Lib/zipfile/__init__.py | 14 ++++++-- ...-07-09-16-41-12.gh-issue-153426.Qh3uzJ.rst | 3 ++ 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2026-07-09-16-41-12.gh-issue-153426.Qh3uzJ.rst diff --git a/Lib/test/test_zipfile/test_core.py b/Lib/test/test_zipfile/test_core.py index 4f20209927e7b3d..fff0c39e7135564 100644 --- a/Lib/test/test_zipfile/test_core.py +++ b/Lib/test/test_zipfile/test_core.py @@ -5754,6 +5754,38 @@ def test_read_with_unsuitable_metadata_encoding(self): with self.assertRaises(UnicodeDecodeError): zipfile.ZipFile(TESTFN, "r", metadata_encoding='utf-8') + def test_read_invalid_utf8_filename(self): + # A file name marked as UTF-8 (general purpose bit 11) but holding + # bytes that are not valid UTF-8 is a corrupt archive, not a decoding + # error for the caller to handle. + def build(name, flag): + lfh = struct.pack(zipfile.structFileHeader, + zipfile.stringFileHeader, + 20, 0, flag, 0, 0, 0, 0, 0, 0, + len(name), 0) + name + cd = struct.pack(zipfile.structCentralDir, + zipfile.stringCentralDir, + 20, 0, 20, 0, flag, 0, 0, 0, 0, 0, 0, + len(name), 0, 0, 0, 0, 0, 0) + name + eocd = struct.pack(zipfile.structEndArchive, + zipfile.stringEndArchive, + 0, 0, 1, 1, len(cd), len(lfh), 0) + return lfh + cd + eocd + + # Central directory path (ZipFile()). + data = build(b'\xff\xfe', zipfile._MASK_UTF_FILENAME) + with self.assertRaisesRegex(zipfile.BadZipFile, 'is marked as UTF-8'): + zipfile.ZipFile(io.BytesIO(data)) + + # Local file header path (open()); the central directory name is + # valid UTF-8 so it is only the header decode that fails. Keep the + # replacement the same length so header offsets stay valid. + good = build(b'good', zipfile._MASK_UTF_FILENAME) + corrupt = good.replace(b'good', b'\xff\xfe\xff\xfe', 1) + with zipfile.ZipFile(io.BytesIO(corrupt)) as zipfp: + with self.assertRaisesRegex(zipfile.BadZipFile, 'is marked as UTF-8'): + zipfp.read('good') + def test_read_after_append(self): newname = '\u56db' # Han 'four' newname2 = 'fünf' # representable in cp437, but still stored as UTF-8 diff --git a/Lib/zipfile/__init__.py b/Lib/zipfile/__init__.py index 418933a2e8d9e87..4641700e8fa351d 100644 --- a/Lib/zipfile/__init__.py +++ b/Lib/zipfile/__init__.py @@ -2050,7 +2050,12 @@ def _RealGetContents(self): flags = centdir[_CD_FLAG_BITS] if flags & _MASK_UTF_FILENAME: # UTF-8 file names extension - filename = filename.decode('utf-8') + try: + filename = filename.decode('utf-8') + except UnicodeDecodeError as exc: + raise BadZipFile( + f"File name {filename!r} is marked as UTF-8 " + f"but is not valid UTF-8") from exc else: # Historical ZIP filename encoding filename = filename.decode(self.metadata_encoding or 'cp437') @@ -2231,7 +2236,12 @@ def open(self, name, mode="r", pwd=None, *, force_zip64=False): if fheader[_FH_GENERAL_PURPOSE_FLAG_BITS] & _MASK_UTF_FILENAME: # UTF-8 filename - fname_str = fname.decode("utf-8") + try: + fname_str = fname.decode("utf-8") + except UnicodeDecodeError as exc: + raise BadZipFile( + f"File name {fname!r} is marked as UTF-8 " + f"but is not valid UTF-8") from exc else: fname_str = fname.decode(self.metadata_encoding or "cp437") diff --git a/Misc/NEWS.d/next/Library/2026-07-09-16-41-12.gh-issue-153426.Qh3uzJ.rst b/Misc/NEWS.d/next/Library/2026-07-09-16-41-12.gh-issue-153426.Qh3uzJ.rst new file mode 100644 index 000000000000000..7556a312a62efeb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-07-09-16-41-12.gh-issue-153426.Qh3uzJ.rst @@ -0,0 +1,3 @@ +Raise :exc:`zipfile.BadZipFile` instead of :exc:`UnicodeDecodeError` when +opening a :class:`zipfile.ZipFile` whose member name is flagged as UTF-8 but +contains bytes that are not valid UTF-8.