Skip to content

Commit d2b2f5e

Browse files
miss-islingtonencukousethmlarson
authored
[3.11] gh-151987: Pass filter_function to TarFile._extract_one() during .extract() (GH-151988) (#152612)
gh-151987: Pass filter_function to TarFile._extract_one() during .extract() (GH-151988) (cherry picked from commit 7ccdbab) Co-authored-by: Petr Viktorin <encukou@gmail.com> Co-authored-by: Seth Michael Larson <seth@python.org>
1 parent 10a13be commit d2b2f5e

3 files changed

Lines changed: 96 additions & 1 deletion

File tree

Lib/tarfile.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2402,7 +2402,8 @@ def extract(self, member, path="", set_attrs=True, *, numeric_owner=False,
24022402
tarinfo, unfiltered = self._get_extract_tarinfo(
24032403
member, filter_function, path)
24042404
if tarinfo is not None:
2405-
self._extract_one(tarinfo, path, set_attrs, numeric_owner)
2405+
self._extract_one(tarinfo, path, set_attrs, numeric_owner,
2406+
filter_function=filter_function)
24062407

24072408
def _get_extract_tarinfo(self, member, filter_function, path):
24082409
"""Get (filtered, unfiltered) TarInfos from *member*

Lib/test/test_tarfile.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4160,6 +4160,98 @@ def test_chmod_outside_dir(self):
41604160
st_mode = cc.outerdir.stat().st_mode
41614161
self.assertNotEqual(st_mode & 0o777, 0o777)
41624162

4163+
@symlink_test
4164+
@unittest.skipUnless(hasattr(os, 'chown'), "missing os.chown")
4165+
@unittest.skipUnless(hasattr(os, 'lchown'), "missing os.lchown")
4166+
@unittest.skipUnless(hasattr(os, 'geteuid'), "missing os.geteuid")
4167+
@support.subTests('link_type', (tarfile.SYMTYPE, tarfile.LNKTYPE))
4168+
def test_chown_links_on_extract(self, link_type):
4169+
with ArchiveMaker() as arc:
4170+
arc.add("test.txt",
4171+
uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
4172+
arc.add("link",
4173+
type=link_type,
4174+
linkname='test.txt',
4175+
uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
4176+
4177+
with (
4178+
os_helper.temp_dir() as tmpdir,
4179+
arc.open() as tar,
4180+
unittest.mock.patch("os.chown") as mock_chown,
4181+
unittest.mock.patch("os.lchown") as mock_lchown,
4182+
unittest.mock.patch("os.geteuid") as mock_geteuid,
4183+
):
4184+
# Set UID to 0 so chown() is attempted.
4185+
mock_geteuid.return_value = 0
4186+
tar.extract("link", path=tmpdir, filter='data')
4187+
extract_path = os.path.join(tmpdir, "link")
4188+
4189+
if link_type == tarfile.SYMTYPE:
4190+
mock_chown.assert_not_called()
4191+
mock_lchown.assert_called_once_with(extract_path, -1, -1)
4192+
else:
4193+
mock_chown.assert_has_calls([
4194+
unittest.mock.call(extract_path, -1, -1),
4195+
unittest.mock.call(extract_path, -1, -1)
4196+
])
4197+
mock_lchown.assert_not_called()
4198+
4199+
@symlink_test
4200+
@unittest.skipUnless(hasattr(os, 'chown'), "missing os.chown")
4201+
@unittest.skipUnless(hasattr(os, 'lchown'), "missing os.lchown")
4202+
@unittest.skipUnless(hasattr(os, 'geteuid'), "missing os.geteuid")
4203+
@support.subTests('link_type', (tarfile.SYMTYPE, tarfile.LNKTYPE))
4204+
def test_chown_links_on_extractall(self, link_type):
4205+
with ArchiveMaker() as arc:
4206+
arc.add("test.txt",
4207+
uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
4208+
arc.add("link",
4209+
type=link_type,
4210+
linkname='test.txt',
4211+
uid=1337, gid=1337, uname="", gname="", mode='-rwxr-xr-x')
4212+
4213+
with (
4214+
os_helper.temp_dir() as tmpdir,
4215+
arc.open() as tar,
4216+
unittest.mock.patch("os.chown") as mock_chown,
4217+
unittest.mock.patch("os.lchown") as mock_lchown,
4218+
unittest.mock.patch("os.geteuid") as mock_geteuid,
4219+
):
4220+
# Set UID to 0 so chown() is attempted.
4221+
mock_geteuid.return_value = 0
4222+
tar.extractall(path=tmpdir, filter='data')
4223+
extract_link_path = os.path.join(tmpdir, "link")
4224+
extract_file_path = os.path.join(tmpdir, "test.txt")
4225+
4226+
if link_type == tarfile.SYMTYPE:
4227+
mock_chown.assert_called_once_with(extract_file_path, -1, -1)
4228+
mock_lchown.assert_called_once_with(extract_link_path, -1, -1)
4229+
else:
4230+
mock_chown.assert_has_calls([
4231+
unittest.mock.call(extract_file_path, -1, -1),
4232+
unittest.mock.call(extract_link_path, -1, -1)
4233+
])
4234+
mock_lchown.assert_not_called()
4235+
4236+
def test_extract_filters_target(self):
4237+
# Test that when extract() falls back to extracting (rather than
4238+
# linking) a hardlink target, it filters the target.
4239+
with ArchiveMaker() as arc:
4240+
arc.add("target")
4241+
arc.add("link", hardlink_to="target")
4242+
def testing_filter(member, path):
4243+
if member.name == 'target':
4244+
# target: set read-only
4245+
return member.replace(mode=stat.S_IRUSR)
4246+
# link: don't overwrite the mode
4247+
return member.replace(mode=None)
4248+
tempdir = pathlib.Path(TEMPDIR) / 'extract'
4249+
with os_helper.temp_dir(tempdir), arc.open() as tar:
4250+
tar.extract("link", path=tempdir, filter=testing_filter)
4251+
path = tempdir / 'link'
4252+
if os_helper.can_chmod():
4253+
self.assertFalse(path.stat().st_mode & stat.S_IWUSR)
4254+
41634255
def test_link_fallback_normalizes(self):
41644256
# Make sure hardlink fallbacks work for non-normalized paths for all
41654257
# filters
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The :meth:`tarfile.TarFile.extract` method now applies the given filter when
2+
it extracts a link target from the archive as a fallback.

0 commit comments

Comments
 (0)