@@ -2777,7 +2777,7 @@ def test_useful_error_message_when_modules_missing(self):
2777
2777
str (excinfo .exception ),
2778
2778
)
2779
2779
2780
- @unittest . skipUnless ( os_helper .can_symlink (), 'requires symlink support' )
2780
+ @os_helper .skip_unless_symlink
2781
2781
@unittest .skipUnless (hasattr (os , 'chmod' ), "missing os.chmod" )
2782
2782
@unittest .mock .patch ('os.chmod' )
2783
2783
def test_deferred_directory_attributes_update (self , mock_chmod ):
@@ -3663,6 +3663,39 @@ class TestExtractionFilters(unittest.TestCase):
3663
3663
# The destination for the extraction, within `outerdir`
3664
3664
destdir = outerdir / 'dest'
3665
3665
3666
+ @classmethod
3667
+ def setUpClass (cls ):
3668
+ # Posix and Windows have different pathname resolution:
3669
+ # either symlink or a '..' component resolve first.
3670
+ # Let's see which we are on.
3671
+ if os_helper .can_symlink ():
3672
+ testpath = os .path .join (TEMPDIR , 'resolution_test' )
3673
+ os .mkdir (testpath )
3674
+
3675
+ # testpath/current links to `.` which is all of:
3676
+ # - `testpath`
3677
+ # - `testpath/current`
3678
+ # - `testpath/current/current`
3679
+ # - etc.
3680
+ os .symlink ('.' , os .path .join (testpath , 'current' ))
3681
+
3682
+ # we'll test where `testpath/current/../file` ends up
3683
+ with open (os .path .join (testpath , 'current' , '..' , 'file' ), 'w' ):
3684
+ pass
3685
+
3686
+ if os .path .exists (os .path .join (testpath , 'file' )):
3687
+ # Windows collapses 'current\..' to '.' first, leaving
3688
+ # 'testpath\file'
3689
+ cls .dotdot_resolves_early = True
3690
+ elif os .path .exists (os .path .join (testpath , '..' , 'file' )):
3691
+ # Posix resolves 'current' to '.' first, leaving
3692
+ # 'testpath/../file'
3693
+ cls .dotdot_resolves_early = False
3694
+ else :
3695
+ raise AssertionError ('Could not determine link resolution' )
3696
+ else :
3697
+ cls .dotdot_resolves_early = True
3698
+
3666
3699
@contextmanager
3667
3700
def check_context (self , tar , filter , * , check_flag = True ):
3668
3701
"""Extracts `tar` to `self.destdir` and allows checking the result
@@ -3809,23 +3842,21 @@ def test_parent_symlink(self):
3809
3842
arc .add ('current' , symlink_to = '.' )
3810
3843
3811
3844
# effectively points to ./../
3812
- arc .add ('parent' , symlink_to = 'current/..' )
3845
+ if self .dotdot_resolves_early :
3846
+ arc .add ('parent' , symlink_to = 'current/../..' )
3847
+ else :
3848
+ arc .add ('parent' , symlink_to = 'current/..' )
3813
3849
3814
3850
arc .add ('parent/evil' )
3815
3851
3816
3852
if os_helper .can_symlink ():
3817
3853
with self .check_context (arc .open (), 'fully_trusted' ):
3818
- if self .raised_exception is not None :
3819
- # Windows will refuse to create a file that's a symlink to itself
3820
- # (and tarfile doesn't swallow that exception)
3821
- self .expect_exception (FileExistsError )
3822
- # The other cases will fail with this error too.
3823
- # Skip the rest of this test.
3824
- return
3854
+ self .expect_file ('current' , symlink_to = '.' )
3855
+ if self .dotdot_resolves_early :
3856
+ self .expect_file ('parent' , symlink_to = 'current/../..' )
3825
3857
else :
3826
- self .expect_file ('current' , symlink_to = '.' )
3827
3858
self .expect_file ('parent' , symlink_to = 'current/..' )
3828
- self .expect_file ('../evil' )
3859
+ self .expect_file ('../evil' )
3829
3860
3830
3861
with self .check_context (arc .open (), 'tar' ):
3831
3862
self .expect_exception (
@@ -3927,35 +3958,6 @@ def test_parent_symlink2(self):
3927
3958
# Test interplaying symlinks
3928
3959
# Inspired by 'dirsymlink2b' in jwilk/traversal-archives
3929
3960
3930
- # Posix and Windows have different pathname resolution:
3931
- # either symlink or a '..' component resolve first.
3932
- # Let's see which we are on.
3933
- if os_helper .can_symlink ():
3934
- testpath = os .path .join (TEMPDIR , 'resolution_test' )
3935
- os .mkdir (testpath )
3936
-
3937
- # testpath/current links to `.` which is all of:
3938
- # - `testpath`
3939
- # - `testpath/current`
3940
- # - `testpath/current/current`
3941
- # - etc.
3942
- os .symlink ('.' , os .path .join (testpath , 'current' ))
3943
-
3944
- # we'll test where `testpath/current/../file` ends up
3945
- with open (os .path .join (testpath , 'current' , '..' , 'file' ), 'w' ):
3946
- pass
3947
-
3948
- if os .path .exists (os .path .join (testpath , 'file' )):
3949
- # Windows collapses 'current\..' to '.' first, leaving
3950
- # 'testpath\file'
3951
- dotdot_resolves_early = True
3952
- elif os .path .exists (os .path .join (testpath , '..' , 'file' )):
3953
- # Posix resolves 'current' to '.' first, leaving
3954
- # 'testpath/../file'
3955
- dotdot_resolves_early = False
3956
- else :
3957
- raise AssertionError ('Could not determine link resolution' )
3958
-
3959
3961
with ArchiveMaker () as arc :
3960
3962
3961
3963
# `current` links to `.` which is both the destination directory
@@ -3991,7 +3993,7 @@ def test_parent_symlink2(self):
3991
3993
3992
3994
with self .check_context (arc .open (), 'data' ):
3993
3995
if os_helper .can_symlink ():
3994
- if dotdot_resolves_early :
3996
+ if self . dotdot_resolves_early :
3995
3997
# Fail when extracting a file outside destination
3996
3998
self .expect_exception (
3997
3999
tarfile .OutsideDestinationError ,
@@ -4039,6 +4041,21 @@ def test_absolute_symlink(self):
4039
4041
tarfile .AbsoluteLinkError ,
4040
4042
"'parent' is a link to an absolute path" )
4041
4043
4044
+ @symlink_test
4045
+ @os_helper .skip_unless_symlink
4046
+ def test_symlink_target_seperator_rewrite_on_windows (self ):
4047
+ with ArchiveMaker () as arc :
4048
+ arc .add ('link' , symlink_to = "relative/test/path" )
4049
+
4050
+ with self .check_context (arc .open (), 'fully_trusted' ):
4051
+ self .expect_file ('link' , type = tarfile .SYMTYPE )
4052
+ link_path = os .path .normpath (self .destdir / "link" )
4053
+ link_target = os .readlink (link_path )
4054
+ if os .name == "nt" :
4055
+ self .assertEqual (link_target , "relative\\ test\\ path" )
4056
+ else :
4057
+ self .assertEqual (link_target , "relative/test/path" )
4058
+
4042
4059
def test_absolute_hardlink (self ):
4043
4060
# Test hardlink to an absolute path
4044
4061
# Inspired by 'dirsymlink' in https://github.com/jwilk/traversal-archives
0 commit comments