@@ -4539,6 +4539,98 @@ def test_chmod_outside_dir(self):
45394539 st_mode = cc .outerdir .stat ().st_mode
45404540 self .assertNotEqual (st_mode & 0o777 , 0o777 )
45414541
4542+ @symlink_test
4543+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
4544+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
4545+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
4546+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
4547+ def test_chown_links_on_extract (self , link_type ):
4548+ with ArchiveMaker () as arc :
4549+ arc .add ("test.txt" ,
4550+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4551+ arc .add ("link" ,
4552+ type = link_type ,
4553+ linkname = 'test.txt' ,
4554+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4555+
4556+ with (
4557+ os_helper .temp_dir () as tmpdir ,
4558+ arc .open () as tar ,
4559+ unittest .mock .patch ("os.chown" ) as mock_chown ,
4560+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
4561+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
4562+ ):
4563+ # Set UID to 0 so chown() is attempted.
4564+ mock_geteuid .return_value = 0
4565+ tar .extract ("link" , path = tmpdir , filter = 'data' )
4566+ extract_path = os .path .join (tmpdir , "link" )
4567+
4568+ if link_type == tarfile .SYMTYPE :
4569+ mock_chown .assert_not_called ()
4570+ mock_lchown .assert_called_once_with (extract_path , - 1 , - 1 )
4571+ else :
4572+ mock_chown .assert_has_calls ([
4573+ unittest .mock .call (extract_path , - 1 , - 1 ),
4574+ unittest .mock .call (extract_path , - 1 , - 1 )
4575+ ])
4576+ mock_lchown .assert_not_called ()
4577+
4578+ @symlink_test
4579+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
4580+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
4581+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
4582+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
4583+ def test_chown_links_on_extractall (self , link_type ):
4584+ with ArchiveMaker () as arc :
4585+ arc .add ("test.txt" ,
4586+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4587+ arc .add ("link" ,
4588+ type = link_type ,
4589+ linkname = 'test.txt' ,
4590+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4591+
4592+ with (
4593+ os_helper .temp_dir () as tmpdir ,
4594+ arc .open () as tar ,
4595+ unittest .mock .patch ("os.chown" ) as mock_chown ,
4596+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
4597+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
4598+ ):
4599+ # Set UID to 0 so chown() is attempted.
4600+ mock_geteuid .return_value = 0
4601+ tar .extractall (path = tmpdir , filter = 'data' )
4602+ extract_link_path = os .path .join (tmpdir , "link" )
4603+ extract_file_path = os .path .join (tmpdir , "test.txt" )
4604+
4605+ if link_type == tarfile .SYMTYPE :
4606+ mock_chown .assert_called_once_with (extract_file_path , - 1 , - 1 )
4607+ mock_lchown .assert_called_once_with (extract_link_path , - 1 , - 1 )
4608+ else :
4609+ mock_chown .assert_has_calls ([
4610+ unittest .mock .call (extract_file_path , - 1 , - 1 ),
4611+ unittest .mock .call (extract_link_path , - 1 , - 1 )
4612+ ])
4613+ mock_lchown .assert_not_called ()
4614+
4615+ def test_extract_filters_target (self ):
4616+ # Test that when extract() falls back to extracting (rather than
4617+ # linking) a hardlink target, it filters the target.
4618+ with ArchiveMaker () as arc :
4619+ arc .add ("target" )
4620+ arc .add ("link" , hardlink_to = "target" )
4621+ def testing_filter (member , path ):
4622+ if member .name == 'target' :
4623+ # target: set read-only
4624+ return member .replace (mode = stat .S_IRUSR )
4625+ # link: don't overwrite the mode
4626+ return member .replace (mode = None )
4627+ tempdir = pathlib .Path (TEMPDIR ) / 'extract'
4628+ with os_helper .temp_dir (tempdir ), arc .open () as tar :
4629+ tar .extract ("link" , path = tempdir , filter = testing_filter )
4630+ path = tempdir / 'link'
4631+ if os_helper .can_chmod ():
4632+ self .assertFalse (path .stat ().st_mode & stat .S_IWUSR )
4633+
45424634 def test_link_fallback_normalizes (self ):
45434635 # Make sure hardlink fallbacks work for non-normalized paths for all
45444636 # filters
0 commit comments