@@ -4160,6 +4160,98 @@ def test_chmod_outside_dir(self):
41604160 st_mode = cc .outerdir .stat ().st_mode
41614161 self .assertNotEqual (st_mode & 0o777 , 0o777 )
41624162
4163+ @symlink_test
4164+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
4165+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
4166+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
4167+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
4168+ def test_chown_links_on_extract (self , link_type ):
4169+ with ArchiveMaker () as arc :
4170+ arc .add ("test.txt" ,
4171+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4172+ arc .add ("link" ,
4173+ type = link_type ,
4174+ linkname = 'test.txt' ,
4175+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4176+
4177+ with (
4178+ os_helper .temp_dir () as tmpdir ,
4179+ arc .open () as tar ,
4180+ unittest .mock .patch ("os.chown" ) as mock_chown ,
4181+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
4182+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
4183+ ):
4184+ # Set UID to 0 so chown() is attempted.
4185+ mock_geteuid .return_value = 0
4186+ tar .extract ("link" , path = tmpdir , filter = 'data' )
4187+ extract_path = os .path .join (tmpdir , "link" )
4188+
4189+ if link_type == tarfile .SYMTYPE :
4190+ mock_chown .assert_not_called ()
4191+ mock_lchown .assert_called_once_with (extract_path , - 1 , - 1 )
4192+ else :
4193+ mock_chown .assert_has_calls ([
4194+ unittest .mock .call (extract_path , - 1 , - 1 ),
4195+ unittest .mock .call (extract_path , - 1 , - 1 )
4196+ ])
4197+ mock_lchown .assert_not_called ()
4198+
4199+ @symlink_test
4200+ @unittest .skipUnless (hasattr (os , 'chown' ), "missing os.chown" )
4201+ @unittest .skipUnless (hasattr (os , 'lchown' ), "missing os.lchown" )
4202+ @unittest .skipUnless (hasattr (os , 'geteuid' ), "missing os.geteuid" )
4203+ @support .subTests ('link_type' , (tarfile .SYMTYPE , tarfile .LNKTYPE ))
4204+ def test_chown_links_on_extractall (self , link_type ):
4205+ with ArchiveMaker () as arc :
4206+ arc .add ("test.txt" ,
4207+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4208+ arc .add ("link" ,
4209+ type = link_type ,
4210+ linkname = 'test.txt' ,
4211+ uid = 1337 , gid = 1337 , uname = "" , gname = "" , mode = '-rwxr-xr-x' )
4212+
4213+ with (
4214+ os_helper .temp_dir () as tmpdir ,
4215+ arc .open () as tar ,
4216+ unittest .mock .patch ("os.chown" ) as mock_chown ,
4217+ unittest .mock .patch ("os.lchown" ) as mock_lchown ,
4218+ unittest .mock .patch ("os.geteuid" ) as mock_geteuid ,
4219+ ):
4220+ # Set UID to 0 so chown() is attempted.
4221+ mock_geteuid .return_value = 0
4222+ tar .extractall (path = tmpdir , filter = 'data' )
4223+ extract_link_path = os .path .join (tmpdir , "link" )
4224+ extract_file_path = os .path .join (tmpdir , "test.txt" )
4225+
4226+ if link_type == tarfile .SYMTYPE :
4227+ mock_chown .assert_called_once_with (extract_file_path , - 1 , - 1 )
4228+ mock_lchown .assert_called_once_with (extract_link_path , - 1 , - 1 )
4229+ else :
4230+ mock_chown .assert_has_calls ([
4231+ unittest .mock .call (extract_file_path , - 1 , - 1 ),
4232+ unittest .mock .call (extract_link_path , - 1 , - 1 )
4233+ ])
4234+ mock_lchown .assert_not_called ()
4235+
4236+ def test_extract_filters_target (self ):
4237+ # Test that when extract() falls back to extracting (rather than
4238+ # linking) a hardlink target, it filters the target.
4239+ with ArchiveMaker () as arc :
4240+ arc .add ("target" )
4241+ arc .add ("link" , hardlink_to = "target" )
4242+ def testing_filter (member , path ):
4243+ if member .name == 'target' :
4244+ # target: set read-only
4245+ return member .replace (mode = stat .S_IRUSR )
4246+ # link: don't overwrite the mode
4247+ return member .replace (mode = None )
4248+ tempdir = pathlib .Path (TEMPDIR ) / 'extract'
4249+ with os_helper .temp_dir (tempdir ), arc .open () as tar :
4250+ tar .extract ("link" , path = tempdir , filter = testing_filter )
4251+ path = tempdir / 'link'
4252+ if os_helper .can_chmod ():
4253+ self .assertFalse (path .stat ().st_mode & stat .S_IWUSR )
4254+
41634255 def test_link_fallback_normalizes (self ):
41644256 # Make sure hardlink fallbacks work for non-normalized paths for all
41654257 # filters
0 commit comments