@@ -4470,6 +4470,98 @@ def test_chmod_outside_dir(self):
44704470 st_mode = cc .outerdir .stat ().st_mode
44714471 self .assertNotEqual (st_mode & 0o777 , 0o777 )
44724472
4473+ @symlink_test
4474+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
4475+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
4476+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
4477+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
4478+ def test_chown_links_on_extract (self , link_type ):
4479+ with ArchiveMaker () as arc :
4480+ arc .add ("test.txt" ,
4481+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4482+ arc .add ("link" ,
4483+ type = link_type ,
4484+ linkname = 'test.txt' ,
4485+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4486+
4487+ with (
4488+ os_helper .temp_dir () as tmpdir ,
4489+ arc .open () as tar ,
4490+ unittest .mock .patch ("os.chown" ) as mock_chown ,
4491+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
4492+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
4493+ ):
4494+ # Set UID to 0 so chown() is attempted.
4495+ mock_geteuid .return_value = 0
4496+ tar .extract ("link" , path = tmpdir , filter = 'data' )
4497+ extract_path = os .path .join (tmpdir , "link" )
4498+
4499+ if link_type == tarfile .SYMTYPE :
4500+ mock_chown .assert_not_called ()
4501+ mock_lchown .assert_called_once_with (extract_path , - 1 , - 1 )
4502+ else :
4503+ mock_chown .assert_has_calls ([
4504+ unittest .mock .call (extract_path , - 1 , - 1 ),
4505+ unittest .mock .call (extract_path , - 1 , - 1 )
4506+ ])
4507+ mock_lchown .assert_not_called ()
4508+
4509+ @symlink_test
4510+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
4511+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
4512+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
4513+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
4514+ def test_chown_links_on_extractall (self , link_type ):
4515+ with ArchiveMaker () as arc :
4516+ arc .add ("test.txt" ,
4517+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4518+ arc .add ("link" ,
4519+ type = link_type ,
4520+ linkname = 'test.txt' ,
4521+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4522+
4523+ with (
4524+ os_helper .temp_dir () as tmpdir ,
4525+ arc .open () as tar ,
4526+ unittest .mock .patch ("os.chown" ) as mock_chown ,
4527+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
4528+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
4529+ ):
4530+ # Set UID to 0 so chown() is attempted.
4531+ mock_geteuid .return_value = 0
4532+ tar .extractall (path = tmpdir , filter = 'data' )
4533+ extract_link_path = os .path .join (tmpdir , "link" )
4534+ extract_file_path = os .path .join (tmpdir , "test.txt" )
4535+
4536+ if link_type == tarfile .SYMTYPE :
4537+ mock_chown .assert_called_once_with (extract_file_path , - 1 , - 1 )
4538+ mock_lchown .assert_called_once_with (extract_link_path , - 1 , - 1 )
4539+ else :
4540+ mock_chown .assert_has_calls ([
4541+ unittest .mock .call (extract_file_path , - 1 , - 1 ),
4542+ unittest .mock .call (extract_link_path , - 1 , - 1 )
4543+ ])
4544+ mock_lchown .assert_not_called ()
4545+
4546+ def test_extract_filters_target (self ):
4547+ # Test that when extract() falls back to extracting (rather than
4548+ # linking) a hardlink target, it filters the target.
4549+ with ArchiveMaker () as arc :
4550+ arc .add ("target" )
4551+ arc .add ("link" , hardlink_to = "target" )
4552+ def testing_filter (member , path ):
4553+ if member .name == 'target' :
4554+ # target: set read-only
4555+ return member .replace (mode = stat .S_IRUSR )
4556+ # link: don't overwrite the mode
4557+ return member .replace (mode = None )
4558+ tempdir = pathlib .Path (TEMPDIR ) / 'extract'
4559+ with os_helper .temp_dir (tempdir ), arc .open () as tar :
4560+ tar .extract ("link" , path = tempdir , filter = testing_filter )
4561+ path = tempdir / 'link'
4562+ if os_helper .can_chmod ():
4563+ self .assertFalse (path .stat ().st_mode & stat .S_IWUSR )
4564+
44734565 def test_link_fallback_normalizes (self ):
44744566 # Make sure hardlink fallbacks work for non-normalized paths for all
44754567 # filters
0 commit comments