Skip to content

Commit fc2f913

Browse files
[3.14] gh-152845: Preserve ZIP encoding flags when appending (gh-161950) (gh-161961)
(cherry picked from commit 5151e7f) Co-authored-by: Kasra Ghodsi <[email protected]>
1 parent 14326da commit fc2f913

3 files changed

Lines changed: 68 additions & 3 deletions

File tree

‎Lib/test/test_zipfile/test_core.py‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2371,6 +2371,66 @@ def test_empty_zipfile(self):
23712371
self.fail("Unable to create empty ZIP file in 'a' mode")
23722372
zipf.close()
23732373

2374+
def test_append_preserves_ascii_utf8_flag(self):
2375+
comments = (b'', 'caf\N{LATIN SMALL LETTER E WITH ACUTE}'.encode('utf-8'))
2376+
updates = (
2377+
('append', lambda zf: zf.writestr('added.txt', b'added')),
2378+
('comment', lambda zf: setattr(zf, 'comment', b'archive comment')),
2379+
)
2380+
for comment, (operation, update) in itertools.product(comments, updates):
2381+
with self.subTest(comment=comment, operation=operation):
2382+
stream = io.BytesIO()
2383+
with zipfile.ZipFile(stream, 'w') as archive:
2384+
entry = zipfile.ZipInfo('file.txt')
2385+
entry.comment = comment
2386+
archive.writestr(entry, b'payload')
2387+
original = bytearray(stream.getvalue())
2388+
central_offset = original.index(b'PK\x01\x02')
2389+
struct.pack_into('<H', original, 6, 1 << 11)
2390+
struct.pack_into('<H', original, central_offset + 8, 1 << 11)
2391+
stream = io.BytesIO(original)
2392+
with zipfile.ZipFile(stream, 'a') as archive:
2393+
update(archive)
2394+
with zipfile.ZipFile(stream) as archive:
2395+
entry = archive.getinfo('file.txt')
2396+
self.assertEqual(entry.flag_bits, 1 << 11)
2397+
self.assertEqual(entry.comment, comment)
2398+
self.assertEqual(archive.read('file.txt'), b'payload')
2399+
self.assertIsNone(archive.testzip())
2400+
self.assertEqual(stream.getvalue()[:central_offset],
2401+
original[:central_offset])
2402+
2403+
def test_new_entry_filename_encoding_flags(self):
2404+
cases = (
2405+
('file.txt', b'\x82', 0, 'cp437', 0,
2406+
'\N{LATIN SMALL LETTER E WITH ACUTE}'),
2407+
('caf\N{LATIN SMALL LETTER E WITH ACUTE}.txt', b'\xc3\xa9',
2408+
0, 'utf-8', 1 << 11,
2409+
'\N{LATIN SMALL LETTER E WITH ACUTE}'),
2410+
('file.txt', b'\xc3\xa9', 1 << 11, 'utf-8', 1 << 11,
2411+
'\N{LATIN SMALL LETTER E WITH ACUTE}'),
2412+
('file.txt', b'', 1 << 11, 'utf-8', 1 << 11, ''),
2413+
('file.txt', b'ascii', 1 << 11, 'utf-8', 1 << 11, 'ascii'),
2414+
)
2415+
for (name, comment, initial_flags, encoding, expected_flags,
2416+
expected_comment) in cases:
2417+
with self.subTest(name=name, comment=comment,
2418+
initial_flags=initial_flags):
2419+
stream = io.BytesIO()
2420+
with zipfile.ZipFile(stream, 'w') as archive:
2421+
entry = zipfile.ZipInfo(name)
2422+
entry.flag_bits = initial_flags
2423+
entry.comment = comment
2424+
archive.writestr(entry, b'payload')
2425+
with zipfile.ZipFile(stream) as archive:
2426+
entry = archive.getinfo(name)
2427+
self.assertEqual(entry.flag_bits, expected_flags)
2428+
self.assertEqual(entry.comment, comment)
2429+
self.assertEqual(entry.comment.decode(encoding),
2430+
expected_comment)
2431+
self.assertEqual(archive.read(name), b'payload')
2432+
self.assertIsNone(archive.testzip())
2433+
23742434
def test_open_empty_file(self):
23752435
# Issue 1710703: Check that opening a file with less than 22 bytes
23762436
# raises a BadZipFile exception (rather than the previously unhelpful

‎Lib/zipfile/__init__.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -568,11 +568,11 @@ def FileHeader(self, zip64=None):
568568

569569
def _encodeFilenameFlags(self):
570570
if self.flag_bits & _MASK_UTF_FILENAME:
571-
encoding = 'ascii'
571+
encoding = 'utf-8'
572572
else:
573573
encoding = 'cp437'
574574
try:
575-
return self.filename.encode(encoding), self.flag_bits & ~_MASK_UTF_FILENAME
575+
return self.filename.encode(encoding), self.flag_bits
576576
except UnicodeEncodeError:
577577
return self.filename.encode('utf-8'), self.flag_bits | _MASK_UTF_FILENAME
578578

@@ -1842,7 +1842,9 @@ def _open_to_write(self, zinfo, force_zip64=False):
18421842
zinfo.compress_size = 0
18431843
zinfo.CRC = 0
18441844

1845-
zinfo.flag_bits = _MASK_UTF_FILENAME
1845+
zinfo.flag_bits &= _MASK_UTF_FILENAME
1846+
if not zinfo.filename.isascii():
1847+
zinfo.flag_bits |= _MASK_UTF_FILENAME
18461848
if zinfo.compress_type == ZIP_LZMA:
18471849
# Compressed data includes an end-of-stream (EOS) marker
18481850
zinfo.flag_bits |= _MASK_COMPRESS_OPTION_1
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix :mod:`zipfile` dropping the UTF-8 (EFS) flag for existing entries
2+
when rewriting an archive in append mode, and honor an explicitly set
3+
flag on new entries. Patch by Kasra Ghodsi.

0 commit comments

Comments
 (0)