@@ -4510,6 +4510,98 @@ def test_chmod_outside_dir(self):
45104510 st_mode = cc .outerdir .stat ().st_mode
45114511 self .assertNotEqual (st_mode & 0o777 , 0o777 )
45124512
4513+ @symlink_test
4514+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
4515+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
4516+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
4517+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
4518+ def test_chown_links_on_extract (self , link_type ):
4519+ with ArchiveMaker () as arc :
4520+ arc .add ("test.txt" ,
4521+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4522+ arc .add ("link" ,
4523+ type = link_type ,
4524+ linkname = 'test.txt' ,
4525+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4526+
4527+ with (
4528+ os_helper .temp_dir () as tmpdir ,
4529+ arc .open () as tar ,
4530+ unittest .mock .patch ("os.chown" ) as mock_chown ,
4531+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
4532+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
4533+ ):
4534+ # Set UID to 0 so chown() is attempted.
4535+ mock_geteuid .return_value = 0
4536+ tar .extract ("link" , path = tmpdir , filter = 'data' )
4537+ extract_path = os .path .join (tmpdir , "link" )
4538+
4539+ if link_type == tarfile .SYMTYPE :
4540+ mock_chown .assert_not_called ()
4541+ mock_lchown .assert_called_once_with (extract_path , - 1 , - 1 )
4542+ else :
4543+ mock_chown .assert_has_calls ([
4544+ unittest .mock .call (extract_path , - 1 , - 1 ),
4545+ unittest .mock .call (extract_path , - 1 , - 1 )
4546+ ])
4547+ mock_lchown .assert_not_called ()
4548+
4549+ @symlink_test
4550+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
4551+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
4552+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
4553+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
4554+ def test_chown_links_on_extractall (self , link_type ):
4555+ with ArchiveMaker () as arc :
4556+ arc .add ("test.txt" ,
4557+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4558+ arc .add ("link" ,
4559+ type = link_type ,
4560+ linkname = 'test.txt' ,
4561+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4562+
4563+ with (
4564+ os_helper .temp_dir () as tmpdir ,
4565+ arc .open () as tar ,
4566+ unittest .mock .patch ("os.chown" ) as mock_chown ,
4567+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
4568+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
4569+ ):
4570+ # Set UID to 0 so chown() is attempted.
4571+ mock_geteuid .return_value = 0
4572+ tar .extractall (path = tmpdir , filter = 'data' )
4573+ extract_link_path = os .path .join (tmpdir , "link" )
4574+ extract_file_path = os .path .join (tmpdir , "test.txt" )
4575+
4576+ if link_type == tarfile .SYMTYPE :
4577+ mock_chown .assert_called_once_with (extract_file_path , - 1 , - 1 )
4578+ mock_lchown .assert_called_once_with (extract_link_path , - 1 , - 1 )
4579+ else :
4580+ mock_chown .assert_has_calls ([
4581+ unittest .mock .call (extract_file_path , - 1 , - 1 ),
4582+ unittest .mock .call (extract_link_path , - 1 , - 1 )
4583+ ])
4584+ mock_lchown .assert_not_called ()
4585+
4586+ def test_extract_filters_target (self ):
4587+ # Test that when extract() falls back to extracting (rather than
4588+ # linking) a hardlink target, it filters the target.
4589+ with ArchiveMaker () as arc :
4590+ arc .add ("target" )
4591+ arc .add ("link" , hardlink_to = "target" )
4592+ def testing_filter (member , path ):
4593+ if member .name == 'target' :
4594+ # target: set read-only
4595+ return member .replace (mode = stat .S_IRUSR )
4596+ # link: don't overwrite the mode
4597+ return member .replace (mode = None )
4598+ tempdir = pathlib .Path (TEMPDIR ) / 'extract'
4599+ with os_helper .temp_dir (tempdir ), arc .open () as tar :
4600+ tar .extract ("link" , path = tempdir , filter = testing_filter )
4601+ path = tempdir / 'link'
4602+ if os_helper .can_chmod ():
4603+ self .assertFalse (path .stat ().st_mode & stat .S_IWUSR )
4604+
45134605 def test_link_fallback_normalizes (self ):
45144606 # Make sure hardlink fallbacks work for non-normalized paths for all
45154607 # filters
0 commit comments