@@ -4040,6 +4040,96 @@ def test_chmod_outside_dir(self):
40404040 st_mode = cc .outerdir .stat ().st_mode
40414041 self .assertNotEqual (st_mode & 0o777 , 0o777 )
40424042
4043+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
4044+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
4045+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
4046+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
4047+ def test_chown_links_on_extract (self , link_type ):
4048+ with ArchiveMaker () as arc :
4049+ arc .add ("test.txt" ,
4050+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4051+ arc .add ("link" ,
4052+ type = link_type ,
4053+ linkname = 'test.txt' ,
4054+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4055+
4056+ with (
4057+ os_helper .temp_dir () as tmpdir ,
4058+ arc .open () as tar ,
4059+ unittest .mock .patch ("os.chown" ) as mock_chown ,
4060+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
4061+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
4062+ ):
4063+ # Set UID to 0 so chown() is attempted.
4064+ mock_geteuid .return_value = 0
4065+ tar .extract ("link" , path = tmpdir , filter = 'data' )
4066+ extract_path = os .path .join (tmpdir , "link" )
4067+
4068+ if link_type == tarfile .SYMTYPE :
4069+ mock_chown .assert_not_called ()
4070+ mock_lchown .assert_called_once_with (extract_path , - 1 , - 1 )
4071+ else :
4072+ mock_chown .assert_has_calls ([
4073+ unittest .mock .call (extract_path , - 1 , - 1 ),
4074+ unittest .mock .call (extract_path , - 1 , - 1 )
4075+ ])
4076+ mock_lchown .assert_not_called ()
4077+
4078+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
4079+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
4080+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
4081+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
4082+ def test_chown_links_on_extractall (self , link_type ):
4083+ with ArchiveMaker () as arc :
4084+ arc .add ("test.txt" ,
4085+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4086+ arc .add ("link" ,
4087+ type = link_type ,
4088+ linkname = 'test.txt' ,
4089+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4090+
4091+ with (
4092+ os_helper .temp_dir () as tmpdir ,
4093+ arc .open () as tar ,
4094+ unittest .mock .patch ("os.chown" ) as mock_chown ,
4095+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
4096+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
4097+ ):
4098+ # Set UID to 0 so chown() is attempted.
4099+ mock_geteuid .return_value = 0
4100+ tar .extractall (path = tmpdir , filter = 'data' )
4101+ extract_link_path = os .path .join (tmpdir , "link" )
4102+ extract_file_path = os .path .join (tmpdir , "test.txt" )
4103+
4104+ if link_type == tarfile .SYMTYPE :
4105+ mock_chown .assert_called_once_with (extract_file_path , - 1 , - 1 )
4106+ mock_lchown .assert_called_once_with (extract_link_path , - 1 , - 1 )
4107+ else :
4108+ mock_chown .assert_has_calls ([
4109+ unittest .mock .call (extract_file_path , - 1 , - 1 ),
4110+ unittest .mock .call (extract_link_path , - 1 , - 1 )
4111+ ])
4112+ mock_lchown .assert_not_called ()
4113+
4114+ def test_extract_filters_target (self ):
4115+ # Test that when extract() falls back to extracting (rather than
4116+ # linking) a hardlink target, it filters the target.
4117+ with ArchiveMaker () as arc :
4118+ arc .add ("target" )
4119+ arc .add ("link" , hardlink_to = "target" )
4120+ def testing_filter (member , path ):
4121+ if member .name == 'target' :
4122+ # target: set read-only
4123+ return member .replace (mode = stat .S_IRUSR )
4124+ # link: don't overwrite the mode
4125+ return member .replace (mode = None )
4126+ tempdir = pathlib .Path (TEMPDIR ) / 'extract'
4127+ with os_helper .temp_dir (tempdir ), arc .open () as tar :
4128+ tar .extract ("link" , path = tempdir , filter = testing_filter )
4129+ path = tempdir / 'link'
4130+ if sys .platform != 'win32' :
4131+ self .assertFalse (path .stat ().st_mode & stat .S_IWUSR )
4132+
40434133 def test_link_fallback_normalizes (self ):
40444134 # Make sure hardlink fallbacks work for non-normalized paths for all
40454135 # filters
0 commit comments