Skip to content

Commit fb375d5

Browse files
committed
zipfile: restore Unix permissions after extraction
1 parent acefff9 commit fb375d5

2 files changed

Lines changed: 27 additions & 0 deletions

File tree

Lib/test/test_zipfile/test_core.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1715,6 +1715,25 @@ def test_extract_all_with_target_pathlike(self):
17151715
with temp_dir() as extdir:
17161716
self._test_extract_all_with_target(FakePath(extdir))
17171717

1718+
@unittest.skipUnless(hasattr(os, 'chmod'), 'requires os.chmod')
1719+
@unittest.skipIf(sys.platform == 'win32', 'Unix permissions only')
1720+
def test_extract_preserves_unix_permissions(self):
1721+
"""_extract_member must apply Unix mode stored in external_attr."""
1722+
info = zipfile.ZipInfo('secret.txt')
1723+
info.external_attr = (stat.S_IFREG | 0o600) << 16
1724+
with zipfile.ZipFile(TESTFN2, 'w') as zf:
1725+
zf.writestr(info, b'data')
1726+
1727+
with temp_dir() as extdir:
1728+
with zipfile.ZipFile(TESTFN2, 'r') as zf:
1729+
zf.extract('secret.txt', path=extdir)
1730+
1731+
extracted = os.path.join(extdir, 'secret.txt')
1732+
actual = stat.S_IMODE(os.stat(extracted).st_mode)
1733+
self.assertEqual(actual, 0o600, f'expected 0o600, got 0o{actual:03o}')
1734+
1735+
unlink(TESTFN2)
1736+
17181737
def check_file(self, filename, content):
17191738
self.assertTrue(os.path.isfile(filename))
17201739
with open(filename, 'rb') as f:

Lib/zipfile/__init__.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1933,12 +1933,20 @@ def _extract_member(self, member, targetpath, pwd):
19331933
except FileExistsError:
19341934
if not os.path.isdir(targetpath):
19351935
raise
1936+
unix_mode = member.external_attr >> 16
1937+
if unix_mode:
1938+
os.chmod(targetpath, unix_mode)
19361939
return targetpath
19371940

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

1945+
# Restore Unix permissions stored in the upper 16 bits of external_attr.
1946+
unix_mode = member.external_attr >> 16
1947+
if unix_mode:
1948+
os.chmod(targetpath, unix_mode)
1949+
19421950
return targetpath
19431951

19441952
def _writecheck(self, zinfo):

0 commit comments

Comments
 (0)