@@ -4437,6 +4437,98 @@ def test_chmod_outside_dir(self):
44374437 st_mode = cc .outerdir .stat ().st_mode
44384438 self .assertNotEqual (st_mode & 0o777 , 0o777 )
44394439
4440+ @symlink_test
4441+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
4442+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
4443+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
4444+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
4445+ def test_chown_links_on_extract (self , link_type ):
4446+ with ArchiveMaker () as arc :
4447+ arc .add ("test.txt" ,
4448+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4449+ arc .add ("link" ,
4450+ type = link_type ,
4451+ linkname = 'test.txt' ,
4452+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4453+
4454+ with (
4455+ os_helper .temp_dir () as tmpdir ,
4456+ arc .open () as tar ,
4457+ unittest .mock .patch ("os.chown" ) as mock_chown ,
4458+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
4459+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
4460+ ):
4461+ # Set UID to 0 so chown() is attempted.
4462+ mock_geteuid .return_value = 0
4463+ tar .extract ("link" , path = tmpdir , filter = 'data' )
4464+ extract_path = os .path .join (tmpdir , "link" )
4465+
4466+ if link_type == tarfile .SYMTYPE :
4467+ mock_chown .assert_not_called ()
4468+ mock_lchown .assert_called_once_with (extract_path , - 1 , - 1 )
4469+ else :
4470+ mock_chown .assert_has_calls ([
4471+ unittest .mock .call (extract_path , - 1 , - 1 ),
4472+ unittest .mock .call (extract_path , - 1 , - 1 )
4473+ ])
4474+ mock_lchown .assert_not_called ()
4475+
4476+ @symlink_test
4477+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
4478+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
4479+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
4480+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
4481+ def test_chown_links_on_extractall (self , link_type ):
4482+ with ArchiveMaker () as arc :
4483+ arc .add ("test.txt" ,
4484+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4485+ arc .add ("link" ,
4486+ type = link_type ,
4487+ linkname = 'test.txt' ,
4488+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4489+
4490+ with (
4491+ os_helper .temp_dir () as tmpdir ,
4492+ arc .open () as tar ,
4493+ unittest .mock .patch ("os.chown" ) as mock_chown ,
4494+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
4495+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
4496+ ):
4497+ # Set UID to 0 so chown() is attempted.
4498+ mock_geteuid .return_value = 0
4499+ tar .extractall (path = tmpdir , filter = 'data' )
4500+ extract_link_path = os .path .join (tmpdir , "link" )
4501+ extract_file_path = os .path .join (tmpdir , "test.txt" )
4502+
4503+ if link_type == tarfile .SYMTYPE :
4504+ mock_chown .assert_called_once_with (extract_file_path , - 1 , - 1 )
4505+ mock_lchown .assert_called_once_with (extract_link_path , - 1 , - 1 )
4506+ else :
4507+ mock_chown .assert_has_calls ([
4508+ unittest .mock .call (extract_file_path , - 1 , - 1 ),
4509+ unittest .mock .call (extract_link_path , - 1 , - 1 )
4510+ ])
4511+ mock_lchown .assert_not_called ()
4512+
4513+ def test_extract_filters_target (self ):
4514+ # Test that when extract() falls back to extracting (rather than
4515+ # linking) a hardlink target, it filters the target.
4516+ with ArchiveMaker () as arc :
4517+ arc .add ("target" )
4518+ arc .add ("link" , hardlink_to = "target" )
4519+ def testing_filter (member , path ):
4520+ if member .name == 'target' :
4521+ # target: set read-only
4522+ return member .replace (mode = stat .S_IRUSR )
4523+ # link: don't overwrite the mode
4524+ return member .replace (mode = None )
4525+ tempdir = pathlib .Path (TEMPDIR ) / 'extract'
4526+ with os_helper .temp_dir (tempdir ), arc .open () as tar :
4527+ tar .extract ("link" , path = tempdir , filter = testing_filter )
4528+ path = tempdir / 'link'
4529+ if os_helper .can_chmod ():
4530+ self .assertFalse (path .stat ().st_mode & stat .S_IWUSR )
4531+
44404532 def test_link_fallback_normalizes (self ):
44414533 # Make sure hardlink fallbacks work for non-normalized paths for all
44424534 # filters
0 commit comments