Skip to content

Commit eae6de2

Browse files
committed
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.
1 parent f3fd9dc commit eae6de2

3 files changed

Lines changed: 47 additions & 2 deletions

File tree

Lib/test/test_zipfile/test_core.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5754,6 +5754,38 @@ def test_read_with_unsuitable_metadata_encoding(self):
57545754
with self.assertRaises(UnicodeDecodeError):
57555755
zipfile.ZipFile(TESTFN, "r", metadata_encoding='utf-8')
57565756

5757+
def test_read_invalid_utf8_filename(self):
5758+
# A file name marked as UTF-8 (general purpose bit 11) but holding
5759+
# bytes that are not valid UTF-8 is a corrupt archive, not a decoding
5760+
# error for the caller to handle.
5761+
def build(name, flag):
5762+
lfh = struct.pack(zipfile.structFileHeader,
5763+
zipfile.stringFileHeader,
5764+
20, 0, flag, 0, 0, 0, 0, 0, 0,
5765+
len(name), 0) + name
5766+
cd = struct.pack(zipfile.structCentralDir,
5767+
zipfile.stringCentralDir,
5768+
20, 0, 20, 0, flag, 0, 0, 0, 0, 0, 0,
5769+
len(name), 0, 0, 0, 0, 0, 0) + name
5770+
eocd = struct.pack(zipfile.structEndArchive,
5771+
zipfile.stringEndArchive,
5772+
0, 0, 1, 1, len(cd), len(lfh), 0)
5773+
return lfh + cd + eocd
5774+
5775+
# Central directory path (ZipFile()).
5776+
data = build(b'\xff\xfe', zipfile._MASK_UTF_FILENAME)
5777+
with self.assertRaisesRegex(zipfile.BadZipFile, 'is marked as UTF-8'):
5778+
zipfile.ZipFile(io.BytesIO(data))
5779+
5780+
# Local file header path (open()); the central directory name is
5781+
# valid UTF-8 so it is only the header decode that fails. Keep the
5782+
# replacement the same length so header offsets stay valid.
5783+
good = build(b'good', zipfile._MASK_UTF_FILENAME)
5784+
corrupt = good.replace(b'good', b'\xff\xfe\xff\xfe', 1)
5785+
with zipfile.ZipFile(io.BytesIO(corrupt)) as zipfp:
5786+
with self.assertRaisesRegex(zipfile.BadZipFile, 'is marked as UTF-8'):
5787+
zipfp.read('good')
5788+
57575789
def test_read_after_append(self):
57585790
newname = '\u56db' # Han 'four'
57595791
newname2 = 'fünf' # representable in cp437, but still stored as UTF-8

Lib/zipfile/__init__.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2050,7 +2050,12 @@ def _RealGetContents(self):
20502050
flags = centdir[_CD_FLAG_BITS]
20512051
if flags & _MASK_UTF_FILENAME:
20522052
# UTF-8 file names extension
2053-
filename = filename.decode('utf-8')
2053+
try:
2054+
filename = filename.decode('utf-8')
2055+
except UnicodeDecodeError as exc:
2056+
raise BadZipFile(
2057+
f"File name {filename!r} is marked as UTF-8 "
2058+
f"but is not valid UTF-8") from exc
20542059
else:
20552060
# Historical ZIP filename encoding
20562061
filename = filename.decode(self.metadata_encoding or 'cp437')
@@ -2231,7 +2236,12 @@ def open(self, name, mode="r", pwd=None, *, force_zip64=False):
22312236

22322237
if fheader[_FH_GENERAL_PURPOSE_FLAG_BITS] & _MASK_UTF_FILENAME:
22332238
# UTF-8 filename
2234-
fname_str = fname.decode("utf-8")
2239+
try:
2240+
fname_str = fname.decode("utf-8")
2241+
except UnicodeDecodeError as exc:
2242+
raise BadZipFile(
2243+
f"File name {fname!r} is marked as UTF-8 "
2244+
f"but is not valid UTF-8") from exc
22352245
else:
22362246
fname_str = fname.decode(self.metadata_encoding or "cp437")
22372247

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Raise :exc:`zipfile.BadZipFile` instead of :exc:`UnicodeDecodeError` when
2+
opening a :class:`zipfile.ZipFile` whose member name is flagged as UTF-8 but
3+
contains bytes that are not valid UTF-8.

0 commit comments

Comments
 (0)