|
15 | 15 | import unittest.mock |
16 | 16 | import tarfile |
17 | 17 |
|
| 18 | +from functools import cache |
18 | 19 | from test import archiver_tests |
19 | 20 | from test import support |
20 | 21 | from test.support import os_helper |
@@ -2777,7 +2778,7 @@ def test_useful_error_message_when_modules_missing(self): |
2777 | 2778 | str(excinfo.exception), |
2778 | 2779 | ) |
2779 | 2780 |
|
2780 | | - @unittest.skipUnless(os_helper.can_symlink(), 'requires symlink support') |
| 2781 | + @os_helper.skip_unless_symlink |
2781 | 2782 | @unittest.skipUnless(hasattr(os, 'chmod'), "missing os.chmod") |
2782 | 2783 | @unittest.mock.patch('os.chmod') |
2783 | 2784 | def test_deferred_directory_attributes_update(self, mock_chmod): |
@@ -3663,6 +3664,37 @@ class TestExtractionFilters(unittest.TestCase): |
3663 | 3664 | # The destination for the extraction, within `outerdir` |
3664 | 3665 | destdir = outerdir / 'dest' |
3665 | 3666 |
|
| 3667 | + @classmethod |
| 3668 | + def setUpClass(cls): |
| 3669 | + # Posix and Windows have different pathname resolution: |
| 3670 | + # either symlink or a '..' component resolve first. |
| 3671 | + # Let's see which we are on. |
| 3672 | + if os_helper.can_symlink(): |
| 3673 | + testpath = os.path.join(TEMPDIR, 'resolution_test') |
| 3674 | + os.mkdir(testpath) |
| 3675 | + |
| 3676 | + # testpath/current links to `.` which is all of: |
| 3677 | + # - `testpath` |
| 3678 | + # - `testpath/current` |
| 3679 | + # - `testpath/current/current` |
| 3680 | + # - etc. |
| 3681 | + os.symlink('.', os.path.join(testpath, 'current')) |
| 3682 | + |
| 3683 | + # we'll test where `testpath/current/../file` ends up |
| 3684 | + with open(os.path.join(testpath, 'current', '..', 'file'), 'w'): |
| 3685 | + pass |
| 3686 | + |
| 3687 | + if os.path.exists(os.path.join(testpath, 'file')): |
| 3688 | + # Windows collapses 'current\..' to '.' first, leaving |
| 3689 | + # 'testpath\file' |
| 3690 | + cls.dotdot_resolves_early = True |
| 3691 | + elif os.path.exists(os.path.join(testpath, '..', 'file')): |
| 3692 | + # Posix resolves 'current' to '.' first, leaving |
| 3693 | + # 'testpath/../file' |
| 3694 | + cls.dotdot_resolves_early = False |
| 3695 | + else: |
| 3696 | + raise AssertionError('Could not determine link resolution') |
| 3697 | + |
3666 | 3698 | @contextmanager |
3667 | 3699 | def check_context(self, tar, filter, *, check_flag=True): |
3668 | 3700 | """Extracts `tar` to `self.destdir` and allows checking the result |
@@ -3809,23 +3841,21 @@ def test_parent_symlink(self): |
3809 | 3841 | arc.add('current', symlink_to='.') |
3810 | 3842 |
|
3811 | 3843 | # effectively points to ./../ |
3812 | | - arc.add('parent', symlink_to='current/..') |
| 3844 | + if self.dotdot_resolves_early: |
| 3845 | + arc.add('parent', symlink_to='current/../..') |
| 3846 | + else: |
| 3847 | + arc.add('parent', symlink_to='current/..') |
3813 | 3848 |
|
3814 | 3849 | arc.add('parent/evil') |
3815 | 3850 |
|
3816 | 3851 | if os_helper.can_symlink(): |
3817 | 3852 | 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 |
| 3853 | + self.expect_file('current', symlink_to='.') |
| 3854 | + if self.dotdot_resolves_early: |
| 3855 | + self.expect_file('parent', symlink_to='current/../..') |
3825 | 3856 | else: |
3826 | | - self.expect_file('current', symlink_to='.') |
3827 | 3857 | self.expect_file('parent', symlink_to='current/..') |
3828 | | - self.expect_file('../evil') |
| 3858 | + self.expect_file('../evil') |
3829 | 3859 |
|
3830 | 3860 | with self.check_context(arc.open(), 'tar'): |
3831 | 3861 | self.expect_exception( |
@@ -3927,35 +3957,6 @@ def test_parent_symlink2(self): |
3927 | 3957 | # Test interplaying symlinks |
3928 | 3958 | # Inspired by 'dirsymlink2b' in jwilk/traversal-archives |
3929 | 3959 |
|
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 | 3960 | with ArchiveMaker() as arc: |
3960 | 3961 |
|
3961 | 3962 | # `current` links to `.` which is both the destination directory |
@@ -3991,7 +3992,7 @@ def test_parent_symlink2(self): |
3991 | 3992 |
|
3992 | 3993 | with self.check_context(arc.open(), 'data'): |
3993 | 3994 | if os_helper.can_symlink(): |
3994 | | - if dotdot_resolves_early: |
| 3995 | + if self.dotdot_resolves_early: |
3995 | 3996 | # Fail when extracting a file outside destination |
3996 | 3997 | self.expect_exception( |
3997 | 3998 | tarfile.OutsideDestinationError, |
@@ -4039,8 +4040,8 @@ def test_absolute_symlink(self): |
4039 | 4040 | tarfile.AbsoluteLinkError, |
4040 | 4041 | "'parent' is a link to an absolute path") |
4041 | 4042 |
|
4042 | | - @unittest.skipUnless(os_helper.can_symlink(), 'requires symlink support') |
4043 | 4043 | @symlink_test |
| 4044 | + @os_helper.skip_unless_symlink |
4044 | 4045 | def test_symlink_target_sanitized_on_windows(self): |
4045 | 4046 | with ArchiveMaker() as arc: |
4046 | 4047 | arc.add('link', symlink_to="relative/test/path") |
|
0 commit comments