Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions Lib/test/test_zipfile/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -1715,6 +1715,25 @@ def test_extract_all_with_target_pathlike(self):
with temp_dir() as extdir:
self._test_extract_all_with_target(FakePath(extdir))

@unittest.skipUnless(hasattr(os, 'chmod'), 'requires os.chmod')
@unittest.skipIf(sys.platform == 'win32', 'Unix permissions only')
def test_extract_preserves_unix_permissions(self):
"""_extract_member must apply Unix mode stored in external_attr."""
info = zipfile.ZipInfo('secret.txt')
info.external_attr = (stat.S_IFREG | 0o600) << 16
with zipfile.ZipFile(TESTFN2, 'w') as zf:
zf.writestr(info, b'data')

with temp_dir() as extdir:
with zipfile.ZipFile(TESTFN2, 'r') as zf:
zf.extract('secret.txt', path=extdir)

extracted = os.path.join(extdir, 'secret.txt')
actual = stat.S_IMODE(os.stat(extracted).st_mode)
self.assertEqual(actual, 0o600, f'expected 0o600, got 0o{actual:03o}')

unlink(TESTFN2)

def check_file(self, filename, content):
self.assertTrue(os.path.isfile(filename))
with open(filename, 'rb') as f:
Expand Down
8 changes: 8 additions & 0 deletions Lib/zipfile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1933,12 +1933,20 @@ def _extract_member(self, member, targetpath, pwd):
except FileExistsError:
if not os.path.isdir(targetpath):
raise
unix_mode = member.external_attr >> 16
if unix_mode:
os.chmod(targetpath, unix_mode)
return targetpath

with self.open(member, pwd=pwd) as source, \
open(targetpath, "wb") as target:
shutil.copyfileobj(source, target)

# Restore Unix permissions stored in the upper 16 bits of external_attr.
unix_mode = member.external_attr >> 16
if unix_mode:
os.chmod(targetpath, unix_mode)

return targetpath

def _writecheck(self, zinfo):
Expand Down
Loading