Skip to content

Commit ed08556

Browse files
committed
gh-154490: Only keep permission bits of the mode argument in ZipFile.mkdir()
The file type bits of a caller-provided mode were previously mixed into the entry's external attributes, so a mode such as 0o107777 could mark the created entry as a socket instead of a directory. Sanitize the mode with & 0o7777, matching what stat.S_IMODE does, before combining it with S_IFDIR. Also spell the default mode as 0o777 instead of 511 in the signature and documentation, for consistency with os.mkdir().
1 parent a2a8466 commit ed08556

4 files changed

Lines changed: 31 additions & 6 deletions

File tree

‎Doc/library/zipfile.rst‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -538,17 +538,23 @@ ZipFile objects
538538
If set, it uses this value as the modification timestamp for the file
539539
written into the ZIP archive, instead of using the current time.
540540

541-
.. method:: ZipFile.mkdir(zinfo_or_directory, mode=511)
541+
.. method:: ZipFile.mkdir(zinfo_or_directory, mode=0o777)
542542

543543
Create a directory inside the archive. If *zinfo_or_directory* is a string,
544544
a directory is created inside the archive with the mode that is specified in
545-
the *mode* argument. If, however, *zinfo_or_directory* is
546-
a :class:`ZipInfo` instance then the *mode* argument is ignored.
545+
the *mode* argument. Only the permission bits of *mode* are used; other
546+
bits are ignored. If, however, *zinfo_or_directory* is a :class:`ZipInfo`
547+
instance then the *mode* argument is ignored.
547548

548549
The archive must be opened with mode ``'w'``, ``'x'`` or ``'a'``.
549550

550551
.. versionadded:: 3.11
551552

553+
.. versionchanged:: next
554+
Bits of *mode* other than the permission bits are now ignored.
555+
Previously, file type bits in *mode* could mark the created entry as
556+
something other than a directory.
557+
552558

553559
.. method:: ZipFile.remove(zinfo_or_arcname)
554560

‎Lib/test/test_zipfile/test_core.py‎

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5478,10 +5478,24 @@ def test_mkdir(self):
54785478
self.assertEqual(old_zinfo.filename, "directory4/")
54795479
self.assertEqual(old_zinfo.external_attr, new_zinfo.external_attr)
54805480

5481+
# gh-154490: file type bits in *mode* must not leak into the
5482+
# entry's external attributes; only the permission bits are kept.
5483+
zf.mkdir("directory5", mode=0o107777) # S_IFREG | 0o7777
5484+
zinfo = zf.filelist[4]
5485+
self.assertEqual(zinfo.filename, "directory5/")
5486+
self.assertEqual(zinfo.external_attr, (0o47777 << 16) | 0x10)
5487+
5488+
zf.mkdir("directory6", mode=0o120555) # S_IFLNK | 0o555
5489+
zinfo = zf.filelist[5]
5490+
self.assertEqual(zinfo.filename, "directory6/")
5491+
self.assertEqual(zinfo.external_attr, (0o40555 << 16) | 0x10)
5492+
54815493
target = os.path.join(TESTFN2, "target")
54825494
os.mkdir(target)
54835495
zf.extractall(target)
5484-
self.assertEqual(set(os.listdir(target)), {"directory", "directory2", "directory3", "directory4"})
5496+
self.assertEqual(set(os.listdir(target)),
5497+
{"directory", "directory2", "directory3",
5498+
"directory4", "directory5", "directory6"})
54855499

54865500
def test_create_directory_with_write(self):
54875501
with zipfile.ZipFile(TESTFN, "w") as zf:

‎Lib/zipfile/__init__.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2579,7 +2579,7 @@ def writestr(self, zinfo_or_arcname, data,
25792579
with self.open(zinfo, mode='w') as dest:
25802580
dest.write(data)
25812581

2582-
def mkdir(self, zinfo_or_directory_name, mode=511):
2582+
def mkdir(self, zinfo_or_directory_name, mode=0o777):
25832583
"""Creates a directory inside the zip archive."""
25842584
if isinstance(zinfo_or_directory_name, ZipInfo):
25852585
zinfo = zinfo_or_directory_name
@@ -2592,7 +2592,7 @@ def mkdir(self, zinfo_or_directory_name, mode=511):
25922592
zinfo = ZipInfo(directory_name)
25932593
zinfo.compress_size = 0
25942594
zinfo.CRC = 0
2595-
zinfo.external_attr = ((0o40000 | mode) & 0xFFFF) << 16
2595+
zinfo.external_attr = (0o40000 | (mode & 0o7777)) << 16
25962596
zinfo.file_size = 0
25972597
zinfo.external_attr |= 0x10
25982598
else:
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix :meth:`zipfile.ZipFile.mkdir` so that only the permission bits of the
2+
*mode* argument are stored. Previously, file type bits in *mode* could mark
3+
the created entry as something other than a directory. Also change the
4+
default value of *mode* in the signature and documentation from ``511`` to
5+
the equivalent but clearer ``0o777``.

0 commit comments

Comments
 (0)