mirror of
https://github.com/pypa/pip
synced 2023-12-13 21:30:23 +01:00
189 lines
6.8 KiB
Python
189 lines
6.8 KiB
Python
import os
|
|
import shutil
|
|
import stat
|
|
import sys
|
|
import tarfile
|
|
import tempfile
|
|
import time
|
|
import zipfile
|
|
|
|
import pytest
|
|
|
|
from pip._internal.exceptions import InstallationError
|
|
from pip._internal.utils.unpacking import (
|
|
is_within_directory,
|
|
untar_file,
|
|
unzip_file,
|
|
)
|
|
|
|
|
|
class TestUnpackArchives(object):
|
|
"""
|
|
test_tar.tgz/test_tar.zip have content as follows engineered to confirm 3
|
|
things:
|
|
1) confirm that reg files, dirs, and symlinks get unpacked
|
|
2) permissions are not preserved (and go by the 022 umask)
|
|
3) reg files with *any* execute perms, get chmod +x
|
|
|
|
file.txt 600 regular file
|
|
symlink.txt 777 symlink to file.txt
|
|
script_owner.sh 700 script where owner can execute
|
|
script_group.sh 610 script where group can execute
|
|
script_world.sh 601 script where world can execute
|
|
dir 744 directory
|
|
dir/dirfile 622 regular file
|
|
4) the file contents are extracted correctly (though the content of
|
|
each file isn't currently unique)
|
|
|
|
"""
|
|
|
|
def setup(self):
|
|
self.tempdir = tempfile.mkdtemp()
|
|
self.old_mask = os.umask(0o022)
|
|
self.symlink_expected_mode = None
|
|
|
|
def teardown(self):
|
|
os.umask(self.old_mask)
|
|
shutil.rmtree(self.tempdir, ignore_errors=True)
|
|
|
|
def mode(self, path):
|
|
return stat.S_IMODE(os.stat(path).st_mode)
|
|
|
|
def confirm_files(self):
|
|
# expectations based on 022 umask set above and the unpack logic that
|
|
# sets execute permissions, not preservation
|
|
for fname, expected_mode, test, expected_contents in [
|
|
('file.txt', 0o644, os.path.isfile, b'file\n'),
|
|
# We don't test the "symlink.txt" contents for now.
|
|
('symlink.txt', 0o644, os.path.isfile, None),
|
|
('script_owner.sh', 0o755, os.path.isfile, b'file\n'),
|
|
('script_group.sh', 0o755, os.path.isfile, b'file\n'),
|
|
('script_world.sh', 0o755, os.path.isfile, b'file\n'),
|
|
('dir', 0o755, os.path.isdir, None),
|
|
(os.path.join('dir', 'dirfile'), 0o644, os.path.isfile, b''),
|
|
]:
|
|
path = os.path.join(self.tempdir, fname)
|
|
if path.endswith('symlink.txt') and sys.platform == 'win32':
|
|
# no symlinks created on windows
|
|
continue
|
|
assert test(path), path
|
|
if expected_contents is not None:
|
|
with open(path, mode='rb') as f:
|
|
contents = f.read()
|
|
assert contents == expected_contents, 'fname: {}'.format(fname)
|
|
if sys.platform == 'win32':
|
|
# the permissions tests below don't apply in windows
|
|
# due to os.chmod being a noop
|
|
continue
|
|
mode = self.mode(path)
|
|
assert mode == expected_mode, (
|
|
"mode: {}, expected mode: {}".format(mode, expected_mode)
|
|
)
|
|
|
|
def make_zip_file(self, filename, file_list):
|
|
"""
|
|
Create a zip file for test case
|
|
"""
|
|
test_zip = os.path.join(self.tempdir, filename)
|
|
with zipfile.ZipFile(test_zip, 'w') as myzip:
|
|
for item in file_list:
|
|
myzip.writestr(item, 'file content')
|
|
return test_zip
|
|
|
|
def make_tar_file(self, filename, file_list):
|
|
"""
|
|
Create a tar file for test case
|
|
"""
|
|
test_tar = os.path.join(self.tempdir, filename)
|
|
with tarfile.open(test_tar, 'w') as mytar:
|
|
for item in file_list:
|
|
file_tarinfo = tarfile.TarInfo(item)
|
|
mytar.addfile(file_tarinfo, 'file content')
|
|
return test_tar
|
|
|
|
def test_unpack_tgz(self, data):
|
|
"""
|
|
Test unpacking a *.tgz, and setting execute permissions
|
|
"""
|
|
test_file = data.packages.joinpath("test_tar.tgz")
|
|
untar_file(test_file, self.tempdir)
|
|
self.confirm_files()
|
|
# Check the timestamp of an extracted file
|
|
file_txt_path = os.path.join(self.tempdir, 'file.txt')
|
|
mtime = time.gmtime(os.stat(file_txt_path).st_mtime)
|
|
assert mtime[0:6] == (2013, 8, 16, 5, 13, 37), mtime
|
|
|
|
def test_unpack_zip(self, data):
|
|
"""
|
|
Test unpacking a *.zip, and setting execute permissions
|
|
"""
|
|
test_file = data.packages.joinpath("test_zip.zip")
|
|
unzip_file(test_file, self.tempdir)
|
|
self.confirm_files()
|
|
|
|
def test_unpack_zip_failure(self):
|
|
"""
|
|
Test unpacking a *.zip with file containing .. path
|
|
and expect exception
|
|
"""
|
|
files = ['regular_file.txt', os.path.join('..', 'outside_file.txt')]
|
|
test_zip = self.make_zip_file('test_zip.zip', files)
|
|
with pytest.raises(InstallationError) as e:
|
|
unzip_file(test_zip, self.tempdir)
|
|
assert 'trying to install outside target directory' in str(e.value)
|
|
|
|
def test_unpack_zip_success(self):
|
|
"""
|
|
Test unpacking a *.zip with regular files,
|
|
no file will be installed outside target directory after unpack
|
|
so no exception raised
|
|
"""
|
|
files = [
|
|
'regular_file1.txt',
|
|
os.path.join('dir', 'dir_file1.txt'),
|
|
os.path.join('dir', '..', 'dir_file2.txt'),
|
|
]
|
|
test_zip = self.make_zip_file('test_zip.zip', files)
|
|
unzip_file(test_zip, self.tempdir)
|
|
|
|
def test_unpack_tar_failure(self):
|
|
"""
|
|
Test unpacking a *.tar with file containing .. path
|
|
and expect exception
|
|
"""
|
|
files = ['regular_file.txt', os.path.join('..', 'outside_file.txt')]
|
|
test_tar = self.make_tar_file('test_tar.tar', files)
|
|
with pytest.raises(InstallationError) as e:
|
|
untar_file(test_tar, self.tempdir)
|
|
assert 'trying to install outside target directory' in str(e.value)
|
|
|
|
def test_unpack_tar_success(self):
|
|
"""
|
|
Test unpacking a *.tar with regular files,
|
|
no file will be installed outside target directory after unpack
|
|
so no exception raised
|
|
"""
|
|
files = [
|
|
'regular_file1.txt',
|
|
os.path.join('dir', 'dir_file1.txt'),
|
|
os.path.join('dir', '..', 'dir_file2.txt'),
|
|
]
|
|
test_tar = self.make_tar_file('test_tar.tar', files)
|
|
untar_file(test_tar, self.tempdir)
|
|
|
|
|
|
@pytest.mark.parametrize('args, expected', [
|
|
# Test the second containing the first.
|
|
(('parent/sub', 'parent/'), False),
|
|
# Test the first not ending in a trailing slash.
|
|
(('parent', 'parent/foo'), True),
|
|
# Test target containing `..` but still inside the parent.
|
|
(('parent/', 'parent/foo/../bar'), True),
|
|
# Test target within the parent
|
|
(('parent/', 'parent/sub'), True),
|
|
# Test target outside parent
|
|
(('parent/', 'parent/../sub'), False),
|
|
])
|
|
def test_is_within_directory(args, expected):
|
|
result = is_within_directory(*args)
|
|
assert result == expected
|