@@ -4077,6 +4077,98 @@ def test_chmod_outside_dir(self):
40774077 st_mode = cc .outerdir .stat ().st_mode
40784078 self .assertNotEqual (st_mode & 0o777 , 0o777 )
40794079
4080+ @symlink_test
4081+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
4082+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
4083+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
4084+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
4085+ def test_chown_links_on_extract (self , link_type ):
4086+ with ArchiveMaker () as arc :
4087+ arc .add ("test.txt" ,
4088+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4089+ arc .add ("link" ,
4090+ type = link_type ,
4091+ linkname = 'test.txt' ,
4092+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4093+
4094+ with (
4095+ os_helper .temp_dir () as tmpdir ,
4096+ arc .open () as tar ,
4097+ unittest .mock .patch ("os.chown" ) as mock_chown ,
4098+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
4099+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
4100+ ):
4101+ # Set UID to 0 so chown() is attempted.
4102+ mock_geteuid .return_value = 0
4103+ tar .extract ("link" , path = tmpdir , filter = 'data' )
4104+ extract_path = os .path .join (tmpdir , "link" )
4105+
4106+ if link_type == tarfile .SYMTYPE :
4107+ mock_chown .assert_not_called ()
4108+ mock_lchown .assert_called_once_with (extract_path , - 1 , - 1 )
4109+ else :
4110+ mock_chown .assert_has_calls ([
4111+ unittest .mock .call (extract_path , - 1 , - 1 ),
4112+ unittest .mock .call (extract_path , - 1 , - 1 )
4113+ ])
4114+ mock_lchown .assert_not_called ()
4115+
4116+ @symlink_test
4117+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
4118+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
4119+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
4120+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
4121+ def test_chown_links_on_extractall (self , link_type ):
4122+ with ArchiveMaker () as arc :
4123+ arc .add ("test.txt" ,
4124+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4125+ arc .add ("link" ,
4126+ type = link_type ,
4127+ linkname = 'test.txt' ,
4128+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4129+
4130+ with (
4131+ os_helper .temp_dir () as tmpdir ,
4132+ arc .open () as tar ,
4133+ unittest .mock .patch ("os.chown" ) as mock_chown ,
4134+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
4135+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
4136+ ):
4137+ # Set UID to 0 so chown() is attempted.
4138+ mock_geteuid .return_value = 0
4139+ tar .extractall (path = tmpdir , filter = 'data' )
4140+ extract_link_path = os .path .join (tmpdir , "link" )
4141+ extract_file_path = os .path .join (tmpdir , "test.txt" )
4142+
4143+ if link_type == tarfile .SYMTYPE :
4144+ mock_chown .assert_called_once_with (extract_file_path , - 1 , - 1 )
4145+ mock_lchown .assert_called_once_with (extract_link_path , - 1 , - 1 )
4146+ else :
4147+ mock_chown .assert_has_calls ([
4148+ unittest .mock .call (extract_file_path , - 1 , - 1 ),
4149+ unittest .mock .call (extract_link_path , - 1 , - 1 )
4150+ ])
4151+ mock_lchown .assert_not_called ()
4152+
4153+ def test_extract_filters_target (self ):
4154+ # Test that when extract() falls back to extracting (rather than
4155+ # linking) a hardlink target, it filters the target.
4156+ with ArchiveMaker () as arc :
4157+ arc .add ("target" )
4158+ arc .add ("link" , hardlink_to = "target" )
4159+ def testing_filter (member , path ):
4160+ if member .name == 'target' :
4161+ # target: set read-only
4162+ return member .replace (mode = stat .S_IRUSR )
4163+ # link: don't overwrite the mode
4164+ return member .replace (mode = None )
4165+ tempdir = pathlib .Path (TEMPDIR ) / 'extract'
4166+ with os_helper .temp_dir (tempdir ), arc .open () as tar :
4167+ tar .extract ("link" , path = tempdir , filter = testing_filter )
4168+ path = tempdir / 'link'
4169+ if os_helper .can_chmod ():
4170+ self .assertFalse (path .stat ().st_mode & stat .S_IWUSR )
4171+
40804172 def test_link_fallback_normalizes (self ):
40814173 # Make sure hardlink fallbacks work for non-normalized paths for all
40824174 # filters
0 commit comments