@@ -3961,6 +3961,98 @@ def test_chmod_outside_dir(self):
39613961 st_mode = cc .outerdir .stat ().st_mode
39623962 self .assertNotEqual (st_mode & 0o777 , 0o777 )
39633963
3964+ @symlink_test
3965+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
3966+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
3967+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
3968+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
3969+ def test_chown_links_on_extract (self , link_type ):
3970+ with ArchiveMaker () as arc :
3971+ arc .add ("test.txt" ,
3972+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
3973+ arc .add ("link" ,
3974+ type = link_type ,
3975+ linkname = 'test.txt' ,
3976+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
3977+
3978+ with (
3979+ os_helper .temp_dir () as tmpdir ,
3980+ arc .open () as tar ,
3981+ unittest .mock .patch ("os.chown" ) as mock_chown ,
3982+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
3983+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
3984+ ):
3985+ # Set UID to 0 so chown() is attempted.
3986+ mock_geteuid .return_value = 0
3987+ tar .extract ("link" , path = tmpdir , filter = 'data' )
3988+ extract_path = os .path .join (tmpdir , "link" )
3989+
3990+ if link_type == tarfile .SYMTYPE :
3991+ mock_chown .assert_not_called ()
3992+ mock_lchown .assert_called_once_with (extract_path , - 1 , - 1 )
3993+ else :
3994+ mock_chown .assert_has_calls ([
3995+ unittest .mock .call (extract_path , - 1 , - 1 ),
3996+ unittest .mock .call (extract_path , - 1 , - 1 )
3997+ ])
3998+ mock_lchown .assert_not_called ()
3999+
4000+ @symlink_test
4001+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
4002+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
4003+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
4004+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
4005+ def test_chown_links_on_extractall (self , link_type ):
4006+ with ArchiveMaker () as arc :
4007+ arc .add ("test.txt" ,
4008+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4009+ arc .add ("link" ,
4010+ type = link_type ,
4011+ linkname = 'test.txt' ,
4012+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4013+
4014+ with (
4015+ os_helper .temp_dir () as tmpdir ,
4016+ arc .open () as tar ,
4017+ unittest .mock .patch ("os.chown" ) as mock_chown ,
4018+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
4019+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
4020+ ):
4021+ # Set UID to 0 so chown() is attempted.
4022+ mock_geteuid .return_value = 0
4023+ tar .extractall (path = tmpdir , filter = 'data' )
4024+ extract_link_path = os .path .join (tmpdir , "link" )
4025+ extract_file_path = os .path .join (tmpdir , "test.txt" )
4026+
4027+ if link_type == tarfile .SYMTYPE :
4028+ mock_chown .assert_called_once_with (extract_file_path , - 1 , - 1 )
4029+ mock_lchown .assert_called_once_with (extract_link_path , - 1 , - 1 )
4030+ else :
4031+ mock_chown .assert_has_calls ([
4032+ unittest .mock .call (extract_file_path , - 1 , - 1 ),
4033+ unittest .mock .call (extract_link_path , - 1 , - 1 )
4034+ ])
4035+ mock_lchown .assert_not_called ()
4036+
4037+ def test_extract_filters_target (self ):
4038+ # Test that when extract() falls back to extracting (rather than
4039+ # linking) a hardlink target, it filters the target.
4040+ with ArchiveMaker () as arc :
4041+ arc .add ("target" )
4042+ arc .add ("link" , hardlink_to = "target" )
4043+ def testing_filter (member , path ):
4044+ if member .name == 'target' :
4045+ # target: set read-only
4046+ return member .replace (mode = stat .S_IRUSR )
4047+ # link: don't overwrite the mode
4048+ return member .replace (mode = None )
4049+ tempdir = pathlib .Path (TEMPDIR ) / 'extract'
4050+ with os_helper .temp_dir (tempdir ), arc .open () as tar :
4051+ tar .extract ("link" , path = tempdir , filter = testing_filter )
4052+ path = tempdir / 'link'
4053+ if os_helper .can_chmod ():
4054+ self .assertFalse (path .stat ().st_mode & stat .S_IWUSR )
4055+
39644056 def test_link_fallback_normalizes (self ):
39654057 # Make sure hardlink fallbacks work for non-normalized paths for all
39664058 # filters
0 commit comments