Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
3 changes: 2 additions & 1 deletion Lib/tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2532,7 +2532,8 @@ def extract(self, member, path="", set_attrs=True, *, numeric_owner=False,
tarinfo, unfiltered = self._get_extract_tarinfo(
member, filter_function, path)
if tarinfo is not None:
self._extract_one(tarinfo, path, set_attrs, numeric_owner)
self._extract_one(tarinfo, path, set_attrs, numeric_owner,
filter_function=filter_function)

def _get_extract_tarinfo(self, member, filter_function, path):
"""Get (filtered, unfiltered) TarInfos from *member*
Expand Down
92 changes: 92 additions & 0 deletions Lib/test/test_tarfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -4515,6 +4515,98 @@ def test_chmod_outside_dir(self):
st_mode = cc.outerdir.stat().st_mode
self.assertNotEqual(st_mode & 0o777, 0o777)

@symlink_test
@unittest.skipUnless(hasattr(os, 'chown'), "missing os.chown")
@unittest.skipUnless(hasattr(os, 'lchown'), "missing os.lchown")
@unittest.skipUnless(hasattr(os, 'geteuid'), "missing os.geteuid")
@support.subTests('link_type', (tarfile.SYMTYPE, tarfile.LNKTYPE))
def test_chown_links_on_extract(self, link_type):
with ArchiveMaker() as arc:
arc.add("test.txt",
uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
arc.add("link",
type=link_type,
linkname='test.txt',
uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')

with (
os_helper.temp_dir() as tmpdir,
arc.open() as tar,
unittest.mock.patch("os.chown") as mock_chown,
unittest.mock.patch("os.lchown") as mock_lchown,
unittest.mock.patch("os.geteuid") as mock_geteuid,
):
# Set UID to 0 so chown() is attempted.
mock_geteuid.return_value = 0
tar.extract("link", path=tmpdir, filter='data')
extract_path = os.path.join(tmpdir, "link")

if link_type == tarfile.SYMTYPE:
mock_chown.assert_not_called()
mock_lchown.assert_called_once_with(extract_path, -1, -1)
else:
mock_chown.assert_has_calls([
unittest.mock.call(extract_path, -1, -1),
unittest.mock.call(extract_path, -1, -1)
])
mock_lchown.assert_not_called()

@symlink_test
@unittest.skipUnless(hasattr(os, 'chown'), "missing os.chown")
@unittest.skipUnless(hasattr(os, 'lchown'), "missing os.lchown")
@unittest.skipUnless(hasattr(os, 'geteuid'), "missing os.geteuid")
@support.subTests('link_type', (tarfile.SYMTYPE, tarfile.LNKTYPE))
def test_chown_links_on_extractall(self, link_type):
with ArchiveMaker() as arc:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These could be refactored, but due to the nature of this PR I don't think we need to delay with such things.

arc.add("test.txt",
uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
arc.add("link",
type=link_type,
linkname='test.txt',
uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')

with (
os_helper.temp_dir() as tmpdir,
arc.open() as tar,
unittest.mock.patch("os.chown") as mock_chown,
unittest.mock.patch("os.lchown") as mock_lchown,
unittest.mock.patch("os.geteuid") as mock_geteuid,
):
# Set UID to 0 so chown() is attempted.
mock_geteuid.return_value = 0
tar.extractall(path=tmpdir, filter='data')
extract_link_path = os.path.join(tmpdir, "link")
extract_file_path = os.path.join(tmpdir, "test.txt")

if link_type == tarfile.SYMTYPE:
mock_chown.assert_called_once_with(extract_file_path, -1, -1)
mock_lchown.assert_called_once_with(extract_link_path, -1, -1)
else:
mock_chown.assert_has_calls([
unittest.mock.call(extract_file_path, -1, -1),
unittest.mock.call(extract_link_path, -1, -1)
])
mock_lchown.assert_not_called()

def test_extract_filters_target(self):
# Test that when extract() falls back to extracting (rather than
# linking) a hardlink target, it filters the target.
with ArchiveMaker() as arc:
arc.add("target")
arc.add("link", hardlink_to="target")
def testing_filter(member, path):
if member.name == 'target':
# target: set read-only
return member.replace(mode=stat.S_IRUSR)
# link: don't overwrite the mode
return member.replace(mode=None)
tempdir = pathlib.Path(TEMPDIR) / 'extract'
with os_helper.temp_dir(tempdir), arc.open() as tar:
tar.extract("link", path=tempdir, filter=testing_filter)
path = tempdir / 'link'
if os_helper.can_chmod():
self.assertFalse(path.stat().st_mode & stat.S_IWUSR)

def test_link_fallback_normalizes(self):
# Make sure hardlink fallbacks work for non-normalized paths for all
# filters
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
The :meth:`tarfile.TarFile.extract` method now applies the given filter when
it extracts a link target from the archive as a fallback.
Loading