Skip to content
Closed
29 changes: 11 additions & 18 deletions Lib/tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2034,38 +2034,31 @@ def addfile(self, tarinfo, fileobj=None):

def extractall(self, path=".", members=None, *, numeric_owner=False):
"""Extract all members from the archive to the current working
directory and set owner, modification time and permissions on
directories afterwards. `path' specifies a different directory
directory and set owner, modification time and permissions
afterwards. `path' specifies a different directory
to extract to. `members' is optional and must be a subset of the
list returned by getmembers(). If `numeric_owner` is True, only
the numbers for user/group names are used and not the names.
"""
directories = []

if members is None:
members = self

for tarinfo in members:
if tarinfo.isdir():
# Extract directories with a safe mode.
directories.append(tarinfo)
tarinfo = copy.copy(tarinfo)
tarinfo.mode = 0o700
# Extract tarfile contents with a safe mode.
tarinfo = copy.copy(tarinfo)
tarinfo.mode = 0o700
# Do not set_attrs directories, as we will do that further down
self.extract(tarinfo, path, set_attrs=not tarinfo.isdir(),
numeric_owner=numeric_owner)

# Reverse sort directories.
directories.sort(key=lambda a: a.name)
directories.reverse()

# Set correct owner, mtime and filemode on directories.
for tarinfo in directories:
dirpath = os.path.join(path, tarinfo.name)
# Set correct owner, mtime and filemode
for tarinfo in sorted (members, key=lambda a: a.name, reverse=True):
member_path = os.path.join(path, tarinfo.name)
try:
self.chown(tarinfo, dirpath, numeric_owner=numeric_owner)
self.utime(tarinfo, dirpath)
self.chmod(tarinfo, dirpath)
self.chown(tarinfo, member_path, numeric_owner=numeric_owner)
self.utime(tarinfo, member_path)
self.chmod(tarinfo, member_path)
except ExtractError as e:
if self.errorlevel > 1:
raise
Expand Down
23 changes: 23 additions & 0 deletions Lib/test/test_tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2976,6 +2976,29 @@ def test_keyword_only(self, mock_geteuid):
tarfl.extract, filename_1, TEMPDIR, False, True)


class ReadonlyArchivedFileTest(TarTest):

def setUp(self):
self.file_name = os.path.join(TEMPDIR, 'read_only_file.txt')
with open(self.file_name, 'w') as outfile:
outfile.write('')

os.chmod(self.file_name, 0o444)

def tearDown(self):
os.remove(self.file_name)

def test_extract_doubly_added_file(self):
# gh-74623: tarring a readonly file twice, then extracting,
# should succeed.
with tarfile.open(tmpname, 'w') as tarfl:
tarfl.add(self.file_name)
tarfl.add(self.file_name)

with tarfile.open(tmpname) as tarfl:
tarfl.extractall()


def setUpModule():
os_helper.unlink(TEMPDIR)
os.makedirs(TEMPDIR)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Avoid untar errors when write-protected files are tarred twice. Patch by
Catherine Devlin.