2013-05-28 23:58:08 +02:00
|
|
|
import os
|
2019-08-21 11:19:02 +02:00
|
|
|
import shutil
|
2019-12-14 01:53:40 +01:00
|
|
|
from shutil import rmtree
|
2013-05-28 23:58:08 +02:00
|
|
|
from tempfile import mkdtemp
|
2021-02-10 11:38:21 +01:00
|
|
|
from unittest.mock import Mock, patch
|
2013-05-28 23:58:08 +02:00
|
|
|
|
2017-06-13 14:17:00 +02:00
|
|
|
import pytest
|
2014-09-12 00:40:45 +02:00
|
|
|
|
2019-10-14 00:44:23 +02:00
|
|
|
from pip._internal.exceptions import HashMismatch
|
|
|
|
from pip._internal.models.link import Link
|
2019-12-06 02:26:53 +01:00
|
|
|
from pip._internal.network.download import Downloader
|
2019-10-14 00:44:23 +02:00
|
|
|
from pip._internal.network.session import PipSession
|
2020-08-09 10:55:33 +02:00
|
|
|
from pip._internal.operations.prepare import _copy_source_tree, unpack_url
|
2017-08-31 17:48:18 +02:00
|
|
|
from pip._internal.utils.hashes import Hashes
|
2019-09-24 10:56:42 +02:00
|
|
|
from pip._internal.utils.urls import path_to_url
|
2020-09-23 16:27:09 +02:00
|
|
|
from tests.lib.filesystem import get_filelist, make_socket_file, make_unreadable_file
|
2019-08-21 11:19:02 +02:00
|
|
|
from tests.lib.path import Path
|
2019-11-29 17:03:48 +01:00
|
|
|
from tests.lib.requests_mocks import MockResponse
|
2013-05-28 23:58:08 +02:00
|
|
|
|
|
|
|
|
2020-02-05 03:25:30 +01:00
|
|
|
def test_unpack_url_with_urllib_response_without_content_type(data):
|
2013-05-28 23:58:08 +02:00
|
|
|
"""
|
|
|
|
It should download and unpack files even if no Content-Type header exists
|
|
|
|
"""
|
2013-08-16 14:04:27 +02:00
|
|
|
_real_session = PipSession()
|
|
|
|
|
|
|
|
def _fake_session_get(*args, **kwargs):
|
|
|
|
resp = _real_session.get(*args, **kwargs)
|
|
|
|
del resp.headers["Content-Type"]
|
2013-05-28 23:58:08 +02:00
|
|
|
return resp
|
|
|
|
|
2013-08-16 14:04:27 +02:00
|
|
|
session = Mock()
|
|
|
|
session.get = _fake_session_get
|
2020-08-11 17:56:37 +02:00
|
|
|
download = Downloader(session, progress_bar="on")
|
2013-08-16 14:04:27 +02:00
|
|
|
|
2019-07-02 07:00:32 +02:00
|
|
|
uri = path_to_url(data.packages.joinpath("simple-1.0.tar.gz"))
|
2013-08-16 14:04:27 +02:00
|
|
|
link = Link(uri)
|
|
|
|
temp_dir = mkdtemp()
|
|
|
|
try:
|
2020-02-05 03:25:30 +01:00
|
|
|
unpack_url(
|
2014-01-28 15:17:51 +01:00
|
|
|
link,
|
|
|
|
temp_dir,
|
2020-08-11 17:56:37 +02:00
|
|
|
download=download,
|
2019-11-12 04:34:52 +01:00
|
|
|
download_dir=None,
|
2013-08-16 14:04:27 +02:00
|
|
|
)
|
2017-12-15 06:56:04 +01:00
|
|
|
assert set(os.listdir(temp_dir)) == {
|
2014-01-28 15:17:51 +01:00
|
|
|
'PKG-INFO', 'setup.cfg', 'setup.py', 'simple', 'simple.egg-info'
|
2017-12-15 06:56:04 +01:00
|
|
|
}
|
2013-08-16 14:04:27 +02:00
|
|
|
finally:
|
|
|
|
rmtree(temp_dir)
|
2013-05-28 23:58:08 +02:00
|
|
|
|
|
|
|
|
2020-05-03 18:48:24 +02:00
|
|
|
@patch("pip._internal.network.download.raise_for_status")
|
|
|
|
def test_download_http_url__no_directory_traversal(mock_raise_for_status,
|
|
|
|
tmpdir):
|
2019-04-17 15:25:45 +02:00
|
|
|
"""
|
|
|
|
Test that directory traversal doesn't happen on download when the
|
|
|
|
Content-Disposition header contains a filename with a ".." path part.
|
|
|
|
"""
|
|
|
|
mock_url = 'http://www.example.com/whatever.tgz'
|
|
|
|
contents = b'downloaded'
|
|
|
|
link = Link(mock_url)
|
|
|
|
|
|
|
|
session = Mock()
|
|
|
|
resp = MockResponse(contents)
|
|
|
|
resp.url = mock_url
|
|
|
|
resp.headers = {
|
|
|
|
# Set the content-type to a random value to prevent
|
|
|
|
# mimetypes.guess_extension from guessing the extension.
|
|
|
|
'content-type': 'random',
|
|
|
|
'content-disposition': 'attachment;filename="../out_dir_file"'
|
|
|
|
}
|
|
|
|
session.get.return_value = resp
|
2020-08-11 17:56:37 +02:00
|
|
|
download = Downloader(session, progress_bar="on")
|
2019-04-17 15:25:45 +02:00
|
|
|
|
2019-07-02 07:00:32 +02:00
|
|
|
download_dir = tmpdir.joinpath('download')
|
2019-04-17 15:25:45 +02:00
|
|
|
os.mkdir(download_dir)
|
2020-08-11 17:56:37 +02:00
|
|
|
file_path, content_type = download(link, download_dir)
|
2019-04-17 15:25:45 +02:00
|
|
|
# The file should be downloaded to download_dir.
|
|
|
|
actual = os.listdir(download_dir)
|
|
|
|
assert actual == ['out_dir_file']
|
2020-05-03 18:48:24 +02:00
|
|
|
mock_raise_for_status.assert_called_once_with(resp)
|
2019-04-17 15:25:45 +02:00
|
|
|
|
|
|
|
|
2019-08-21 11:19:02 +02:00
|
|
|
@pytest.fixture
|
|
|
|
def clean_project(tmpdir_factory, data):
|
|
|
|
tmpdir = Path(str(tmpdir_factory.mktemp("clean_project")))
|
|
|
|
new_project_dir = tmpdir.joinpath("FSPkg")
|
|
|
|
path = data.packages.joinpath("FSPkg")
|
|
|
|
shutil.copytree(path, new_project_dir)
|
|
|
|
return new_project_dir
|
|
|
|
|
|
|
|
|
2020-05-12 01:53:08 +02:00
|
|
|
def test_copy_source_tree(clean_project, tmpdir):
|
|
|
|
target = tmpdir.joinpath("target")
|
|
|
|
expected_files = get_filelist(clean_project)
|
|
|
|
assert len(expected_files) == 3
|
|
|
|
|
|
|
|
_copy_source_tree(clean_project, target)
|
|
|
|
|
|
|
|
copied_files = get_filelist(target)
|
|
|
|
assert expected_files == copied_files
|
|
|
|
|
|
|
|
|
2021-02-10 07:44:34 +01:00
|
|
|
@pytest.mark.skipif("sys.platform == 'win32'")
|
2020-05-12 01:53:08 +02:00
|
|
|
def test_copy_source_tree_with_socket(clean_project, tmpdir, caplog):
|
|
|
|
target = tmpdir.joinpath("target")
|
|
|
|
expected_files = get_filelist(clean_project)
|
|
|
|
socket_path = str(clean_project.joinpath("aaa"))
|
|
|
|
make_socket_file(socket_path)
|
|
|
|
|
|
|
|
_copy_source_tree(clean_project, target)
|
|
|
|
|
|
|
|
copied_files = get_filelist(target)
|
|
|
|
assert expected_files == copied_files
|
|
|
|
|
|
|
|
# Warning should have been logged.
|
|
|
|
assert len(caplog.records) == 1
|
|
|
|
record = caplog.records[0]
|
|
|
|
assert record.levelname == 'WARNING'
|
|
|
|
assert socket_path in record.message
|
|
|
|
|
|
|
|
|
2021-02-10 07:44:34 +01:00
|
|
|
@pytest.mark.skipif("sys.platform == 'win32'")
|
2020-05-12 01:53:08 +02:00
|
|
|
def test_copy_source_tree_with_socket_fails_with_no_socket_error(
|
|
|
|
clean_project, tmpdir
|
|
|
|
):
|
|
|
|
target = tmpdir.joinpath("target")
|
|
|
|
expected_files = get_filelist(clean_project)
|
|
|
|
make_socket_file(clean_project.joinpath("aaa"))
|
|
|
|
unreadable_file = clean_project.joinpath("bbb")
|
|
|
|
make_unreadable_file(unreadable_file)
|
|
|
|
|
|
|
|
with pytest.raises(shutil.Error) as e:
|
|
|
|
_copy_source_tree(clean_project, target)
|
|
|
|
|
|
|
|
errored_files = [err[0] for err in e.value.args[0]]
|
|
|
|
assert len(errored_files) == 1
|
|
|
|
assert unreadable_file in errored_files
|
|
|
|
|
|
|
|
copied_files = get_filelist(target)
|
|
|
|
# All files without errors should have been copied.
|
|
|
|
assert expected_files == copied_files
|
|
|
|
|
|
|
|
|
|
|
|
def test_copy_source_tree_with_unreadable_dir_fails(clean_project, tmpdir):
|
|
|
|
target = tmpdir.joinpath("target")
|
|
|
|
expected_files = get_filelist(clean_project)
|
|
|
|
unreadable_file = clean_project.joinpath("bbb")
|
|
|
|
make_unreadable_file(unreadable_file)
|
|
|
|
|
|
|
|
with pytest.raises(shutil.Error) as e:
|
|
|
|
_copy_source_tree(clean_project, target)
|
|
|
|
|
|
|
|
errored_files = [err[0] for err in e.value.args[0]]
|
|
|
|
assert len(errored_files) == 1
|
|
|
|
assert unreadable_file in errored_files
|
|
|
|
|
|
|
|
copied_files = get_filelist(target)
|
|
|
|
# All files without errors should have been copied.
|
|
|
|
assert expected_files == copied_files
|
|
|
|
|
|
|
|
|
2020-12-24 22:23:07 +01:00
|
|
|
class Test_unpack_url:
|
2014-02-01 20:41:55 +01:00
|
|
|
|
|
|
|
def prep(self, tmpdir, data):
|
2019-07-02 07:00:32 +02:00
|
|
|
self.build_dir = tmpdir.joinpath('build')
|
|
|
|
self.download_dir = tmpdir.joinpath('download')
|
2014-02-01 20:41:55 +01:00
|
|
|
os.mkdir(self.build_dir)
|
|
|
|
os.mkdir(self.download_dir)
|
|
|
|
self.dist_file = "simple-1.0.tar.gz"
|
|
|
|
self.dist_file2 = "simple-2.0.tar.gz"
|
2019-07-02 07:00:32 +02:00
|
|
|
self.dist_path = data.packages.joinpath(self.dist_file)
|
|
|
|
self.dist_path2 = data.packages.joinpath(self.dist_file2)
|
2014-02-01 20:41:55 +01:00
|
|
|
self.dist_url = Link(path_to_url(self.dist_path))
|
|
|
|
self.dist_url2 = Link(path_to_url(self.dist_path2))
|
2020-08-11 17:56:37 +02:00
|
|
|
self.no_download = Mock(side_effect=AssertionError)
|
2014-02-01 20:41:55 +01:00
|
|
|
|
2020-02-05 03:25:30 +01:00
|
|
|
def test_unpack_url_no_download(self, tmpdir, data):
|
2014-02-01 20:41:55 +01:00
|
|
|
self.prep(tmpdir, data)
|
2020-08-11 17:56:37 +02:00
|
|
|
unpack_url(self.dist_url, self.build_dir, self.no_download)
|
2014-02-01 20:41:55 +01:00
|
|
|
assert os.path.isdir(os.path.join(self.build_dir, 'simple'))
|
|
|
|
assert not os.path.isfile(
|
|
|
|
os.path.join(self.download_dir, self.dist_file))
|
|
|
|
|
2020-02-05 03:25:30 +01:00
|
|
|
def test_unpack_url_bad_hash(self, tmpdir, data,
|
|
|
|
monkeypatch):
|
2014-02-01 23:04:58 +01:00
|
|
|
"""
|
|
|
|
Test when the file url hash fragment is wrong
|
|
|
|
"""
|
|
|
|
self.prep(tmpdir, data)
|
2020-12-23 20:25:12 +01:00
|
|
|
url = f'{self.dist_url.url}#md5=bogus'
|
2019-06-23 01:02:42 +02:00
|
|
|
dist_url = Link(url)
|
2014-02-01 23:04:58 +01:00
|
|
|
with pytest.raises(HashMismatch):
|
2020-02-05 03:25:30 +01:00
|
|
|
unpack_url(dist_url,
|
|
|
|
self.build_dir,
|
2020-08-11 17:56:37 +02:00
|
|
|
download=self.no_download,
|
2020-02-05 03:25:30 +01:00
|
|
|
hashes=Hashes({'md5': ['bogus']}))
|
2014-02-01 23:04:58 +01:00
|
|
|
|
2020-02-05 03:25:30 +01:00
|
|
|
def test_unpack_url_thats_a_dir(self, tmpdir, data):
|
2014-02-01 20:41:55 +01:00
|
|
|
self.prep(tmpdir, data)
|
2019-07-02 07:00:32 +02:00
|
|
|
dist_path = data.packages.joinpath("FSPkg")
|
2014-02-01 20:41:55 +01:00
|
|
|
dist_url = Link(path_to_url(dist_path))
|
2020-02-05 03:25:30 +01:00
|
|
|
unpack_url(dist_url, self.build_dir,
|
2020-08-11 17:56:37 +02:00
|
|
|
download=self.no_download,
|
2020-02-05 03:25:30 +01:00
|
|
|
download_dir=self.download_dir)
|
2020-05-12 01:54:28 +02:00
|
|
|
assert os.path.isdir(os.path.join(self.build_dir, 'fspkg'))
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('exclude_dir', [
|
|
|
|
'.nox',
|
|
|
|
'.tox'
|
|
|
|
])
|
|
|
|
def test_unpack_url_excludes_expected_dirs(tmpdir, exclude_dir):
|
|
|
|
src_dir = tmpdir / 'src'
|
|
|
|
dst_dir = tmpdir / 'dst'
|
|
|
|
src_included_file = src_dir.joinpath('file.txt')
|
|
|
|
src_excluded_dir = src_dir.joinpath(exclude_dir)
|
|
|
|
src_excluded_file = src_dir.joinpath(exclude_dir, 'file.txt')
|
|
|
|
src_included_dir = src_dir.joinpath('subdir', exclude_dir)
|
|
|
|
|
|
|
|
# set up source directory
|
|
|
|
src_excluded_dir.mkdir(parents=True)
|
|
|
|
src_included_dir.mkdir(parents=True)
|
|
|
|
src_included_file.touch()
|
|
|
|
src_excluded_file.touch()
|
|
|
|
|
|
|
|
dst_included_file = dst_dir.joinpath('file.txt')
|
|
|
|
dst_excluded_dir = dst_dir.joinpath(exclude_dir)
|
|
|
|
dst_excluded_file = dst_dir.joinpath(exclude_dir, 'file.txt')
|
|
|
|
dst_included_dir = dst_dir.joinpath('subdir', exclude_dir)
|
|
|
|
|
|
|
|
src_link = Link(path_to_url(src_dir))
|
|
|
|
unpack_url(
|
|
|
|
src_link,
|
|
|
|
dst_dir,
|
|
|
|
Mock(side_effect=AssertionError),
|
|
|
|
download_dir=None
|
|
|
|
)
|
|
|
|
assert not os.path.isdir(dst_excluded_dir)
|
|
|
|
assert not os.path.isfile(dst_excluded_file)
|
|
|
|
assert os.path.isfile(dst_included_file)
|
|
|
|
assert os.path.isdir(dst_included_dir)
|