2014-08-31 01:52:28 +02:00
|
|
|
from __future__ import absolute_import
|
|
|
|
|
2011-04-29 01:04:03 +02:00
|
|
|
import cgi
|
2013-08-16 14:04:27 +02:00
|
|
|
import email.utils
|
2014-09-10 03:35:25 +02:00
|
|
|
import json
|
2014-08-31 01:52:28 +02:00
|
|
|
import logging
|
2010-07-06 06:12:30 +02:00
|
|
|
import mimetypes
|
2011-04-29 01:04:03 +02:00
|
|
|
import os
|
2013-04-19 03:22:37 +02:00
|
|
|
import platform
|
2011-04-29 01:04:03 +02:00
|
|
|
import re
|
2010-07-06 06:12:30 +02:00
|
|
|
import shutil
|
2011-04-29 01:04:03 +02:00
|
|
|
import sys
|
2019-08-09 02:39:12 +02:00
|
|
|
from contextlib import contextmanager
|
2013-02-16 19:02:41 +01:00
|
|
|
|
2019-08-20 19:42:10 +02:00
|
|
|
from pip._vendor import requests, six, urllib3
|
2017-05-16 12:16:30 +02:00
|
|
|
from pip._vendor.cachecontrol import CacheControlAdapter
|
2019-09-15 23:01:32 +02:00
|
|
|
from pip._vendor.cachecontrol.cache import BaseCache
|
2017-05-16 12:16:30 +02:00
|
|
|
from pip._vendor.cachecontrol.caches import FileCache
|
|
|
|
from pip._vendor.requests.adapters import BaseAdapter, HTTPAdapter
|
|
|
|
from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
|
|
|
|
from pip._vendor.requests.models import CONTENT_CHUNK_SIZE, Response
|
|
|
|
from pip._vendor.requests.structures import CaseInsensitiveDict
|
|
|
|
from pip._vendor.requests.utils import get_netrc_auth
|
2019-08-21 11:19:02 +02:00
|
|
|
from pip._vendor.six import PY2
|
2017-07-17 19:15:17 +02:00
|
|
|
# NOTE: XMLRPC Client is not annotated in typeshed as on 2017-07-17, which is
|
|
|
|
# why we ignore the type on this import
|
|
|
|
from pip._vendor.six.moves import xmlrpc_client # type: ignore
|
2014-09-12 00:40:45 +02:00
|
|
|
from pip._vendor.six.moves.urllib import parse as urllib_parse
|
|
|
|
|
2013-04-19 03:22:37 +02:00
|
|
|
import pip
|
2017-08-31 17:48:18 +02:00
|
|
|
from pip._internal.exceptions import HashMismatch, InstallationError
|
2018-05-29 22:14:51 +02:00
|
|
|
from pip._internal.models.index import PyPI
|
2019-06-21 17:11:26 +02:00
|
|
|
# Import ssl from compat so the initial import occurs in only one place.
|
2019-08-20 19:42:10 +02:00
|
|
|
from pip._internal.utils.compat import HAS_TLS, ipaddress, ssl
|
2017-08-31 17:48:18 +02:00
|
|
|
from pip._internal.utils.encoding import auto_decode
|
2019-09-15 23:01:32 +02:00
|
|
|
from pip._internal.utils.filesystem import (
|
|
|
|
adjacent_tmp_file,
|
|
|
|
check_path_owner,
|
|
|
|
copy2_fixed,
|
|
|
|
replace,
|
|
|
|
)
|
2017-08-31 17:48:18 +02:00
|
|
|
from pip._internal.utils.glibc import libc_ver
|
|
|
|
from pip._internal.utils.misc import (
|
2019-07-22 06:45:27 +02:00
|
|
|
ask,
|
|
|
|
ask_input,
|
|
|
|
ask_password,
|
|
|
|
ask_path_exists,
|
|
|
|
backup_dir,
|
2019-07-14 11:00:05 +02:00
|
|
|
build_url_from_netloc,
|
2019-07-22 06:45:27 +02:00
|
|
|
consume,
|
|
|
|
display_path,
|
2019-09-15 23:01:32 +02:00
|
|
|
ensure_dir,
|
2019-07-22 06:45:27 +02:00
|
|
|
format_size,
|
|
|
|
get_installed_version,
|
2019-08-21 12:22:57 +02:00
|
|
|
hide_url,
|
2019-08-26 01:26:01 +02:00
|
|
|
parse_netloc,
|
2019-08-21 11:19:02 +02:00
|
|
|
path_to_display,
|
2019-07-22 06:45:27 +02:00
|
|
|
path_to_url,
|
|
|
|
remove_auth_from_url,
|
|
|
|
rmtree,
|
|
|
|
split_auth_netloc_from_url,
|
|
|
|
splitext,
|
2017-05-16 12:16:30 +02:00
|
|
|
)
|
2017-08-31 17:48:18 +02:00
|
|
|
from pip._internal.utils.temp_dir import TempDirectory
|
2018-10-26 17:07:27 +02:00
|
|
|
from pip._internal.utils.typing import MYPY_CHECK_RUNNING
|
2017-08-31 17:48:18 +02:00
|
|
|
from pip._internal.utils.ui import DownloadProgressProvider
|
2019-09-18 02:27:35 +02:00
|
|
|
from pip._internal.utils.unpacking import unpack_file
|
2019-09-10 12:13:16 +02:00
|
|
|
from pip._internal.utils.urls import get_url_scheme, url_to_path
|
2017-08-31 17:48:18 +02:00
|
|
|
from pip._internal.vcs import vcs
|
2014-04-24 13:29:57 +02:00
|
|
|
|
2018-10-26 17:07:27 +02:00
|
|
|
if MYPY_CHECK_RUNNING:
|
2019-02-22 12:17:07 +01:00
|
|
|
from typing import (
|
2019-08-20 19:42:10 +02:00
|
|
|
IO, Callable, Dict, Iterator, List, Optional, Text, Tuple, Union,
|
2018-12-16 10:16:39 +01:00
|
|
|
)
|
2019-05-07 23:01:41 +02:00
|
|
|
from optparse import Values
|
2019-08-21 11:19:02 +02:00
|
|
|
|
|
|
|
from mypy_extensions import TypedDict
|
|
|
|
|
2019-02-22 12:17:07 +01:00
|
|
|
from pip._internal.models.link import Link
|
|
|
|
from pip._internal.utils.hashes import Hashes
|
2019-05-28 20:09:00 +02:00
|
|
|
from pip._internal.vcs.versioncontrol import AuthInfo, VersionControl
|
2018-10-26 17:07:27 +02:00
|
|
|
|
2019-07-03 14:33:28 +02:00
|
|
|
Credentials = Tuple[str, str, str]
|
2019-08-26 01:26:01 +02:00
|
|
|
SecureOrigin = Tuple[str, str, Optional[Union[int, str]]]
|
2019-07-03 14:33:28 +02:00
|
|
|
|
2019-08-21 11:19:02 +02:00
|
|
|
if PY2:
|
|
|
|
CopytreeKwargs = TypedDict(
|
|
|
|
'CopytreeKwargs',
|
|
|
|
{
|
|
|
|
'ignore': Callable[[str, List[str]], List[str]],
|
|
|
|
'symlinks': bool,
|
|
|
|
},
|
|
|
|
total=False,
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
CopytreeKwargs = TypedDict(
|
|
|
|
'CopytreeKwargs',
|
|
|
|
{
|
|
|
|
'copy_function': Callable[[str, str], None],
|
|
|
|
'ignore': Callable[[str, List[str]], List[str]],
|
|
|
|
'ignore_dangling_symlinks': bool,
|
|
|
|
'symlinks': bool,
|
|
|
|
},
|
|
|
|
total=False,
|
|
|
|
)
|
|
|
|
|
2019-07-03 14:33:28 +02:00
|
|
|
|
2013-08-16 14:04:27 +02:00
|
|
|
__all__ = ['get_file_content',
|
2019-09-10 12:13:16 +02:00
|
|
|
'path_to_url',
|
|
|
|
'unpack_vcs_link',
|
2019-08-15 23:00:55 +02:00
|
|
|
'unpack_file_url', 'is_file_url',
|
2019-04-17 15:25:45 +02:00
|
|
|
'unpack_http_url', 'unpack_url',
|
|
|
|
'parse_content_disposition', 'sanitize_content_filename']
|
2010-07-06 06:12:30 +02:00
|
|
|
|
2010-07-02 14:27:22 +02:00
|
|
|
|
2014-08-31 01:52:28 +02:00
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
2019-05-07 23:01:41 +02:00
|
|
|
try:
|
|
|
|
import keyring # noqa
|
|
|
|
except ImportError:
|
|
|
|
keyring = None
|
|
|
|
except Exception as exc:
|
|
|
|
logger.warning("Keyring is skipped due to an exception: %s",
|
|
|
|
str(exc))
|
|
|
|
keyring = None
|
|
|
|
|
2019-08-20 19:42:10 +02:00
|
|
|
|
|
|
|
SECURE_ORIGINS = [
|
|
|
|
# protocol, hostname, port
|
|
|
|
# Taken from Chrome's list of secure origins (See: http://bit.ly/1qrySKC)
|
|
|
|
("https", "*", "*"),
|
|
|
|
("*", "localhost", "*"),
|
|
|
|
("*", "127.0.0.0/8", "*"),
|
|
|
|
("*", "::1/128", "*"),
|
|
|
|
("file", "*", None),
|
|
|
|
# ssh is always secure.
|
|
|
|
("ssh", "*", "*"),
|
|
|
|
] # type: List[SecureOrigin]
|
|
|
|
|
|
|
|
|
2019-02-17 00:37:48 +01:00
|
|
|
# These are environment variables present when running under various
|
|
|
|
# CI systems. For each variable, some CI systems that use the variable
|
|
|
|
# are indicated. The collection was chosen so that for each of a number
|
|
|
|
# of popular systems, at least one of the environment variables is used.
|
|
|
|
# This list is used to provide some indication of and lower bound for
|
|
|
|
# CI traffic to PyPI. Thus, it is okay if the list is not comprehensive.
|
|
|
|
# For more background, see: https://github.com/pypa/pip/issues/5499
|
2019-02-18 08:03:51 +01:00
|
|
|
CI_ENVIRONMENT_VARIABLES = (
|
2019-02-17 00:37:48 +01:00
|
|
|
# Azure Pipelines
|
|
|
|
'BUILD_BUILDID',
|
|
|
|
# Jenkins
|
|
|
|
'BUILD_ID',
|
|
|
|
# AppVeyor, CircleCI, Codeship, Gitlab CI, Shippable, Travis CI
|
|
|
|
'CI',
|
2019-05-22 22:40:01 +02:00
|
|
|
# Explicit environment variable.
|
|
|
|
'PIP_IS_CI',
|
2019-02-18 08:03:51 +01:00
|
|
|
)
|
2019-02-17 00:37:48 +01:00
|
|
|
|
|
|
|
|
|
|
|
def looks_like_ci():
|
|
|
|
# type: () -> bool
|
|
|
|
"""
|
|
|
|
Return whether it looks like pip is running under CI.
|
|
|
|
"""
|
2019-02-18 08:03:51 +01:00
|
|
|
# We don't use the method of checking for a tty (e.g. using isatty())
|
|
|
|
# because some CI systems mimic a tty (e.g. Travis CI). Thus that
|
|
|
|
# method doesn't provide definitive information in either direction.
|
2019-02-17 00:37:48 +01:00
|
|
|
return any(name in os.environ for name in CI_ENVIRONMENT_VARIABLES)
|
|
|
|
|
|
|
|
|
2013-08-16 14:04:27 +02:00
|
|
|
def user_agent():
|
2014-09-10 03:35:25 +02:00
|
|
|
"""
|
|
|
|
Return a string representing the user agent.
|
|
|
|
"""
|
|
|
|
data = {
|
|
|
|
"installer": {"name": "pip", "version": pip.__version__},
|
|
|
|
"python": platform.python_version(),
|
|
|
|
"implementation": {
|
|
|
|
"name": platform.python_implementation(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
if data["implementation"]["name"] == 'CPython':
|
|
|
|
data["implementation"]["version"] = platform.python_version()
|
|
|
|
elif data["implementation"]["name"] == 'PyPy':
|
|
|
|
if sys.pypy_version_info.releaselevel == 'final':
|
|
|
|
pypy_version_info = sys.pypy_version_info[:3]
|
|
|
|
else:
|
|
|
|
pypy_version_info = sys.pypy_version_info
|
|
|
|
data["implementation"]["version"] = ".".join(
|
|
|
|
[str(x) for x in pypy_version_info]
|
|
|
|
)
|
|
|
|
elif data["implementation"]["name"] == 'Jython':
|
|
|
|
# Complete Guess
|
|
|
|
data["implementation"]["version"] = platform.python_version()
|
|
|
|
elif data["implementation"]["name"] == 'IronPython':
|
|
|
|
# Complete Guess
|
|
|
|
data["implementation"]["version"] = platform.python_version()
|
|
|
|
|
|
|
|
if sys.platform.startswith("linux"):
|
2016-09-01 13:47:47 +02:00
|
|
|
from pip._vendor import distro
|
2016-08-12 15:51:44 +02:00
|
|
|
distro_infos = dict(filter(
|
2014-09-10 03:35:25 +02:00
|
|
|
lambda x: x[1],
|
2016-08-12 15:51:44 +02:00
|
|
|
zip(["name", "version", "id"], distro.linux_distribution()),
|
2014-09-10 03:35:25 +02:00
|
|
|
))
|
|
|
|
libc = dict(filter(
|
|
|
|
lambda x: x[1],
|
2016-07-10 05:17:04 +02:00
|
|
|
zip(["lib", "version"], libc_ver()),
|
2014-09-10 03:35:25 +02:00
|
|
|
))
|
|
|
|
if libc:
|
2016-08-12 15:51:44 +02:00
|
|
|
distro_infos["libc"] = libc
|
|
|
|
if distro_infos:
|
|
|
|
data["distro"] = distro_infos
|
2014-09-10 03:35:25 +02:00
|
|
|
|
|
|
|
if sys.platform.startswith("darwin") and platform.mac_ver()[0]:
|
2016-11-06 18:24:43 +01:00
|
|
|
data["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]}
|
2014-09-10 03:35:25 +02:00
|
|
|
|
|
|
|
if platform.system():
|
|
|
|
data.setdefault("system", {})["name"] = platform.system()
|
|
|
|
|
|
|
|
if platform.release():
|
|
|
|
data.setdefault("system", {})["release"] = platform.release()
|
|
|
|
|
|
|
|
if platform.machine():
|
|
|
|
data["cpu"] = platform.machine()
|
|
|
|
|
2017-03-18 18:46:23 +01:00
|
|
|
if HAS_TLS:
|
2018-03-30 17:26:45 +02:00
|
|
|
data["openssl_version"] = ssl.OPENSSL_VERSION
|
2016-01-02 20:06:41 +01:00
|
|
|
|
2017-03-22 23:59:43 +01:00
|
|
|
setuptools_version = get_installed_version("setuptools")
|
|
|
|
if setuptools_version is not None:
|
|
|
|
data["setuptools_version"] = setuptools_version
|
|
|
|
|
2019-02-17 00:37:48 +01:00
|
|
|
# Use None rather than False so as not to give the impression that
|
|
|
|
# pip knows it is not being run under CI. Rather, it is a null or
|
|
|
|
# inconclusive result. Also, we include some value rather than no
|
|
|
|
# value to make it easier to know that the check has been run.
|
|
|
|
data["ci"] = True if looks_like_ci() else None
|
|
|
|
|
2019-03-31 11:37:02 +02:00
|
|
|
user_data = os.environ.get("PIP_USER_AGENT_USER_DATA")
|
|
|
|
if user_data is not None:
|
|
|
|
data["user_data"] = user_data
|
|
|
|
|
2014-10-06 13:42:15 +02:00
|
|
|
return "{data[installer][name]}/{data[installer][version]} {json}".format(
|
|
|
|
data=data,
|
|
|
|
json=json.dumps(data, separators=(",", ":"), sort_keys=True),
|
|
|
|
)
|
2013-04-19 03:22:37 +02:00
|
|
|
|
|
|
|
|
2019-02-12 17:52:49 +01:00
|
|
|
def _get_keyring_auth(url, username):
|
|
|
|
"""Return the tuple auth for a given url from keyring."""
|
|
|
|
if not url or not keyring:
|
|
|
|
return None
|
|
|
|
|
|
|
|
try:
|
|
|
|
try:
|
|
|
|
get_credential = keyring.get_credential
|
|
|
|
except AttributeError:
|
|
|
|
pass
|
|
|
|
else:
|
|
|
|
logger.debug("Getting credentials from keyring for %s", url)
|
|
|
|
cred = get_credential(url, username)
|
|
|
|
if cred is not None:
|
|
|
|
return cred.username, cred.password
|
|
|
|
return None
|
|
|
|
|
|
|
|
if username:
|
|
|
|
logger.debug("Getting password from keyring for %s", url)
|
|
|
|
password = keyring.get_password(url, username)
|
|
|
|
if password:
|
|
|
|
return username, password
|
|
|
|
|
|
|
|
except Exception as exc:
|
|
|
|
logger.warning("Keyring is skipped due to an exception: %s",
|
|
|
|
str(exc))
|
|
|
|
|
|
|
|
|
2013-08-18 10:34:04 +02:00
|
|
|
class MultiDomainBasicAuth(AuthBase):
|
|
|
|
|
2019-02-12 17:52:49 +01:00
|
|
|
def __init__(self, prompting=True, index_urls=None):
|
|
|
|
# type: (bool, Optional[Values]) -> None
|
2013-08-18 10:34:04 +02:00
|
|
|
self.prompting = prompting
|
2019-02-12 17:52:49 +01:00
|
|
|
self.index_urls = index_urls
|
2018-12-18 10:40:40 +01:00
|
|
|
self.passwords = {} # type: Dict[str, AuthInfo]
|
2019-02-12 17:52:49 +01:00
|
|
|
# When the user is prompted to enter credentials and keyring is
|
|
|
|
# available, we will offer to save them. If the user accepts,
|
|
|
|
# this value is set to the credentials they entered. After the
|
|
|
|
# request authenticates, the caller should call
|
|
|
|
# ``save_credentials`` to save these.
|
2019-07-03 14:33:28 +02:00
|
|
|
self._credentials_to_save = None # type: Optional[Credentials]
|
2019-02-12 17:52:49 +01:00
|
|
|
|
|
|
|
def _get_index_url(self, url):
|
|
|
|
"""Return the original index URL matching the requested URL.
|
|
|
|
|
|
|
|
Cached or dynamically generated credentials may work against
|
|
|
|
the original index URL rather than just the netloc.
|
|
|
|
|
|
|
|
The provided url should have had its username and password
|
|
|
|
removed already. If the original index url had credentials then
|
|
|
|
they will be included in the return value.
|
|
|
|
|
|
|
|
Returns None if no matching index was found, or if --no-index
|
|
|
|
was specified by the user.
|
|
|
|
"""
|
|
|
|
if not url or not self.index_urls:
|
|
|
|
return None
|
|
|
|
|
|
|
|
for u in self.index_urls:
|
|
|
|
prefix = remove_auth_from_url(u).rstrip("/") + "/"
|
|
|
|
if url.startswith(prefix):
|
|
|
|
return u
|
|
|
|
|
|
|
|
def _get_new_credentials(self, original_url, allow_netrc=True,
|
|
|
|
allow_keyring=True):
|
|
|
|
"""Find and return credentials for the specified URL."""
|
|
|
|
# Split the credentials and netloc from the url.
|
|
|
|
url, netloc, url_user_password = split_auth_netloc_from_url(
|
|
|
|
original_url)
|
|
|
|
|
|
|
|
# Start with the credentials embedded in the url
|
|
|
|
username, password = url_user_password
|
|
|
|
if username is not None and password is not None:
|
|
|
|
logger.debug("Found credentials in url for %s", netloc)
|
|
|
|
return url_user_password
|
|
|
|
|
|
|
|
# Find a matching index url for this request
|
|
|
|
index_url = self._get_index_url(url)
|
|
|
|
if index_url:
|
|
|
|
# Split the credentials from the url.
|
|
|
|
index_info = split_auth_netloc_from_url(index_url)
|
|
|
|
if index_info:
|
|
|
|
index_url, _, index_url_user_password = index_info
|
|
|
|
logger.debug("Found index url %s", index_url)
|
|
|
|
|
|
|
|
# If an index URL was found, try its embedded credentials
|
|
|
|
if index_url and index_url_user_password[0] is not None:
|
|
|
|
username, password = index_url_user_password
|
|
|
|
if username is not None and password is not None:
|
|
|
|
logger.debug("Found credentials in index url for %s", netloc)
|
|
|
|
return index_url_user_password
|
2013-08-18 10:34:04 +02:00
|
|
|
|
2019-02-12 17:52:49 +01:00
|
|
|
# Get creds from netrc if we still don't have them
|
|
|
|
if allow_netrc:
|
|
|
|
netrc_auth = get_netrc_auth(original_url)
|
|
|
|
if netrc_auth:
|
|
|
|
logger.debug("Found credentials in netrc for %s", netloc)
|
|
|
|
return netrc_auth
|
|
|
|
|
|
|
|
# If we don't have a password and keyring is available, use it.
|
|
|
|
if allow_keyring:
|
|
|
|
# The index url is more specific than the netloc, so try it first
|
|
|
|
kr_auth = (_get_keyring_auth(index_url, username) or
|
|
|
|
_get_keyring_auth(netloc, username))
|
|
|
|
if kr_auth:
|
|
|
|
logger.debug("Found credentials in keyring for %s", netloc)
|
|
|
|
return kr_auth
|
|
|
|
|
2019-08-04 16:26:41 +02:00
|
|
|
return username, password
|
2019-02-12 17:52:49 +01:00
|
|
|
|
|
|
|
def _get_url_and_credentials(self, original_url):
|
|
|
|
"""Return the credentials to use for the provided URL.
|
|
|
|
|
|
|
|
If allowed, netrc and keyring may be used to obtain the
|
|
|
|
correct credentials.
|
|
|
|
|
|
|
|
Returns (url_without_credentials, username, password). Note
|
|
|
|
that even if the original URL contains credentials, this
|
|
|
|
function may return a different username and password.
|
|
|
|
"""
|
|
|
|
url, netloc, _ = split_auth_netloc_from_url(original_url)
|
2013-08-18 10:34:04 +02:00
|
|
|
|
|
|
|
# Use any stored credentials that we have for this netloc
|
|
|
|
username, password = self.passwords.get(netloc, (None, None))
|
|
|
|
|
2019-08-04 16:26:41 +02:00
|
|
|
if username is None and password is None:
|
|
|
|
# No stored credentials. Acquire new credentials without prompting
|
|
|
|
# the user. (e.g. from netrc, keyring, or the URL itself)
|
2019-02-12 17:52:49 +01:00
|
|
|
username, password = self._get_new_credentials(original_url)
|
2016-09-18 22:48:42 +02:00
|
|
|
|
2019-08-04 16:26:41 +02:00
|
|
|
if username is not None or password is not None:
|
|
|
|
# Convert the username and password if they're None, so that
|
|
|
|
# this netloc will show up as "cached" in the conditional above.
|
|
|
|
# Further, HTTPBasicAuth doesn't accept None, so it makes sense to
|
|
|
|
# cache the value that is going to be used.
|
|
|
|
username = username or ""
|
|
|
|
password = password or ""
|
|
|
|
|
|
|
|
# Store any acquired credentials.
|
2013-08-18 10:34:04 +02:00
|
|
|
self.passwords[netloc] = (username, password)
|
|
|
|
|
2019-08-04 16:26:41 +02:00
|
|
|
assert (
|
|
|
|
# Credentials were found
|
|
|
|
(username is not None and password is not None) or
|
|
|
|
# Credentials were not found
|
|
|
|
(username is None and password is None)
|
|
|
|
), "Could not load credentials from url: {}".format(original_url)
|
|
|
|
|
2019-02-12 17:52:49 +01:00
|
|
|
return url, username, password
|
|
|
|
|
|
|
|
def __call__(self, req):
|
|
|
|
# Get credentials for this request
|
|
|
|
url, username, password = self._get_url_and_credentials(req.url)
|
|
|
|
|
|
|
|
# Set the url of the request to the url without any credentials
|
|
|
|
req.url = url
|
|
|
|
|
|
|
|
if username is not None and password is not None:
|
2013-08-18 10:34:04 +02:00
|
|
|
# Send the basic auth with this request
|
2019-02-12 17:52:49 +01:00
|
|
|
req = HTTPBasicAuth(username, password)(req)
|
2013-08-18 10:34:04 +02:00
|
|
|
|
|
|
|
# Attach a hook to handle 401 responses
|
|
|
|
req.register_hook("response", self.handle_401)
|
|
|
|
|
|
|
|
return req
|
|
|
|
|
2019-02-12 17:52:49 +01:00
|
|
|
# Factored out to allow for easy patching in tests
|
|
|
|
def _prompt_for_password(self, netloc):
|
|
|
|
username = ask_input("User for %s: " % netloc)
|
|
|
|
if not username:
|
|
|
|
return None, None
|
|
|
|
auth = _get_keyring_auth(netloc, username)
|
|
|
|
if auth:
|
|
|
|
return auth[0], auth[1], False
|
|
|
|
password = ask_password("Password: ")
|
|
|
|
return username, password, True
|
|
|
|
|
|
|
|
# Factored out to allow for easy patching in tests
|
|
|
|
def _should_save_password_to_keyring(self):
|
|
|
|
if not keyring:
|
|
|
|
return False
|
|
|
|
return ask("Save credentials to keyring [y/N]: ", ["y", "n"]) == "y"
|
|
|
|
|
2013-08-18 10:34:04 +02:00
|
|
|
def handle_401(self, resp, **kwargs):
|
|
|
|
# We only care about 401 responses, anything else we want to just
|
|
|
|
# pass through the actual response
|
|
|
|
if resp.status_code != 401:
|
|
|
|
return resp
|
|
|
|
|
2016-05-27 02:47:27 +02:00
|
|
|
# We are not able to prompt the user so simply return the response
|
2013-08-18 10:34:04 +02:00
|
|
|
if not self.prompting:
|
|
|
|
return resp
|
|
|
|
|
2014-09-12 00:40:45 +02:00
|
|
|
parsed = urllib_parse.urlparse(resp.url)
|
2013-08-18 10:34:04 +02:00
|
|
|
|
|
|
|
# Prompt the user for a new username and password
|
2019-02-12 17:52:49 +01:00
|
|
|
username, password, save = self._prompt_for_password(parsed.netloc)
|
2013-08-18 10:34:04 +02:00
|
|
|
|
|
|
|
# Store the new username and password to use for future requests
|
2019-02-12 17:52:49 +01:00
|
|
|
self._credentials_to_save = None
|
|
|
|
if username is not None and password is not None:
|
2013-08-18 10:34:04 +02:00
|
|
|
self.passwords[parsed.netloc] = (username, password)
|
|
|
|
|
2019-02-12 17:52:49 +01:00
|
|
|
# Prompt to save the password to keyring
|
|
|
|
if save and self._should_save_password_to_keyring():
|
|
|
|
self._credentials_to_save = (parsed.netloc, username, password)
|
|
|
|
|
2013-08-18 10:34:04 +02:00
|
|
|
# Consume content and release the original connection to allow our new
|
|
|
|
# request to reuse the same one.
|
|
|
|
resp.content
|
|
|
|
resp.raw.release_conn()
|
|
|
|
|
|
|
|
# Add our new username and password to the request
|
|
|
|
req = HTTPBasicAuth(username or "", password or "")(resp.request)
|
2018-08-17 18:07:45 +02:00
|
|
|
req.register_hook("response", self.warn_on_401)
|
2013-08-18 10:34:04 +02:00
|
|
|
|
2019-02-12 17:52:49 +01:00
|
|
|
# On successful request, save the credentials that were used to
|
|
|
|
# keyring. (Note that if the user responded "no" above, this member
|
|
|
|
# is not set and nothing will be saved.)
|
|
|
|
if self._credentials_to_save:
|
|
|
|
req.register_hook("response", self.save_credentials)
|
|
|
|
|
2013-08-18 10:34:04 +02:00
|
|
|
# Send our new request
|
|
|
|
new_resp = resp.connection.send(req, **kwargs)
|
|
|
|
new_resp.history.append(resp)
|
|
|
|
|
|
|
|
return new_resp
|
|
|
|
|
2018-08-17 18:07:45 +02:00
|
|
|
def warn_on_401(self, resp, **kwargs):
|
2019-02-12 17:52:49 +01:00
|
|
|
"""Response callback to warn about incorrect credentials."""
|
2018-08-17 15:25:53 +02:00
|
|
|
if resp.status_code == 401:
|
|
|
|
logger.warning('401 Error, Credentials not correct for %s',
|
|
|
|
resp.request.url)
|
2018-07-27 00:14:16 +02:00
|
|
|
|
2019-02-12 17:52:49 +01:00
|
|
|
def save_credentials(self, resp, **kwargs):
|
|
|
|
"""Response callback to save credentials on success."""
|
|
|
|
assert keyring is not None, "should never reach here without keyring"
|
|
|
|
if not keyring:
|
|
|
|
return
|
|
|
|
|
|
|
|
creds = self._credentials_to_save
|
|
|
|
self._credentials_to_save = None
|
|
|
|
if creds and resp.status_code < 400:
|
|
|
|
try:
|
|
|
|
logger.info('Saving credentials to keyring')
|
|
|
|
keyring.set_password(*creds)
|
|
|
|
except Exception:
|
|
|
|
logger.exception('Failed to save credentials')
|
|
|
|
|
2013-08-18 10:34:04 +02:00
|
|
|
|
2013-08-16 14:04:27 +02:00
|
|
|
class LocalFSAdapter(BaseAdapter):
|
|
|
|
|
|
|
|
def send(self, request, stream=None, timeout=None, verify=None, cert=None,
|
|
|
|
proxies=None):
|
2014-05-06 18:54:14 +02:00
|
|
|
pathname = url_to_path(request.url)
|
2013-08-16 14:04:27 +02:00
|
|
|
|
|
|
|
resp = Response()
|
|
|
|
resp.status_code = 200
|
2014-05-06 18:54:14 +02:00
|
|
|
resp.url = request.url
|
2013-08-16 14:04:27 +02:00
|
|
|
|
2014-04-03 06:16:45 +02:00
|
|
|
try:
|
|
|
|
stats = os.stat(pathname)
|
|
|
|
except OSError as exc:
|
|
|
|
resp.status_code = 404
|
|
|
|
resp.raw = exc
|
|
|
|
else:
|
|
|
|
modified = email.utils.formatdate(stats.st_mtime, usegmt=True)
|
|
|
|
content_type = mimetypes.guess_type(pathname)[0] or "text/plain"
|
|
|
|
resp.headers = CaseInsensitiveDict({
|
|
|
|
"Content-Type": content_type,
|
|
|
|
"Content-Length": stats.st_size,
|
|
|
|
"Last-Modified": modified,
|
|
|
|
})
|
|
|
|
|
2014-05-06 19:09:59 +02:00
|
|
|
resp.raw = open(pathname, "rb")
|
2014-04-03 06:16:45 +02:00
|
|
|
resp.close = resp.raw.close
|
2013-08-16 14:04:27 +02:00
|
|
|
|
|
|
|
return resp
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2019-08-09 02:39:12 +02:00
|
|
|
@contextmanager
|
|
|
|
def suppressed_cache_errors():
|
|
|
|
"""If we can't access the cache then we can just skip caching and process
|
|
|
|
requests as if caching wasn't enabled.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
yield
|
2019-09-15 23:03:49 +02:00
|
|
|
except (OSError, IOError):
|
2019-08-09 02:39:12 +02:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2019-09-15 23:01:32 +02:00
|
|
|
class SafeFileCache(BaseCache):
|
2014-04-24 13:29:57 +02:00
|
|
|
"""
|
|
|
|
A file based cache which is safe to use even when the target directory may
|
|
|
|
not be accessible or writable.
|
|
|
|
"""
|
|
|
|
|
2019-09-15 23:02:16 +02:00
|
|
|
def __init__(self, directory):
|
|
|
|
# type: (str) -> None
|
2019-08-09 02:39:12 +02:00
|
|
|
assert directory is not None, "Cache directory must not be None."
|
2019-09-15 23:01:32 +02:00
|
|
|
super(SafeFileCache, self).__init__()
|
|
|
|
self.directory = directory
|
2014-12-31 01:39:37 +01:00
|
|
|
|
2019-09-15 22:47:00 +02:00
|
|
|
def _get_cache_path(self, name):
|
|
|
|
# type: (str) -> str
|
|
|
|
# From cachecontrol.caches.file_cache.FileCache._fn, brought into our
|
|
|
|
# class for backwards-compatibility and to avoid using a non-public
|
|
|
|
# method.
|
|
|
|
hashed = FileCache.encode(name)
|
|
|
|
parts = list(hashed[:5]) + [hashed]
|
|
|
|
return os.path.join(self.directory, *parts)
|
|
|
|
|
2019-09-15 22:35:08 +02:00
|
|
|
def get(self, key):
|
|
|
|
# type: (str) -> Optional[bytes]
|
2019-09-15 23:01:32 +02:00
|
|
|
path = self._get_cache_path(key)
|
2019-08-09 02:39:12 +02:00
|
|
|
with suppressed_cache_errors():
|
2019-09-15 23:01:32 +02:00
|
|
|
with open(path, 'rb') as f:
|
|
|
|
return f.read()
|
2014-04-24 13:29:57 +02:00
|
|
|
|
2019-09-15 22:35:08 +02:00
|
|
|
def set(self, key, value):
|
|
|
|
# type: (str, bytes) -> None
|
2019-09-15 23:01:32 +02:00
|
|
|
path = self._get_cache_path(key)
|
2019-08-09 02:39:12 +02:00
|
|
|
with suppressed_cache_errors():
|
2019-09-15 23:01:32 +02:00
|
|
|
ensure_dir(os.path.dirname(path))
|
|
|
|
|
|
|
|
with adjacent_tmp_file(path) as f:
|
|
|
|
f.write(value)
|
|
|
|
|
|
|
|
replace(f.name, path)
|
2014-04-24 13:29:57 +02:00
|
|
|
|
2019-09-15 22:35:08 +02:00
|
|
|
def delete(self, key):
|
|
|
|
# type: (str) -> None
|
2019-09-15 23:01:32 +02:00
|
|
|
path = self._get_cache_path(key)
|
2019-08-09 02:39:12 +02:00
|
|
|
with suppressed_cache_errors():
|
2019-09-15 23:01:32 +02:00
|
|
|
os.remove(path)
|
2014-04-24 13:29:57 +02:00
|
|
|
|
|
|
|
|
2014-12-20 23:16:04 +01:00
|
|
|
class InsecureHTTPAdapter(HTTPAdapter):
|
|
|
|
|
|
|
|
def cert_verify(self, conn, url, verify, cert):
|
|
|
|
conn.cert_reqs = 'CERT_NONE'
|
|
|
|
conn.ca_certs = None
|
|
|
|
|
|
|
|
|
2013-08-16 14:04:27 +02:00
|
|
|
class PipSession(requests.Session):
|
|
|
|
|
2018-10-26 17:07:27 +02:00
|
|
|
timeout = None # type: Optional[int]
|
2013-08-16 14:04:27 +02:00
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
2019-08-20 19:42:10 +02:00
|
|
|
"""
|
2019-08-20 21:58:31 +02:00
|
|
|
:param trusted_hosts: Domains not to emit warnings for when not using
|
2019-08-20 19:42:10 +02:00
|
|
|
HTTPS.
|
|
|
|
"""
|
2014-04-24 13:29:57 +02:00
|
|
|
retries = kwargs.pop("retries", 0)
|
|
|
|
cache = kwargs.pop("cache", None)
|
2019-08-20 21:58:31 +02:00
|
|
|
trusted_hosts = kwargs.pop("trusted_hosts", []) # type: List[str]
|
2019-02-12 17:52:49 +01:00
|
|
|
index_urls = kwargs.pop("index_urls", None)
|
2014-01-09 10:07:51 +01:00
|
|
|
|
2013-08-16 14:04:27 +02:00
|
|
|
super(PipSession, self).__init__(*args, **kwargs)
|
|
|
|
|
2019-08-20 19:42:10 +02:00
|
|
|
# Namespace the attribute with "pip_" just in case to prevent
|
|
|
|
# possible conflicts with the base class.
|
2019-08-26 01:26:01 +02:00
|
|
|
self.pip_trusted_origins = [] # type: List[Tuple[str, Optional[int]]]
|
2019-08-20 19:42:10 +02:00
|
|
|
|
2013-08-16 14:04:27 +02:00
|
|
|
# Attach our User Agent to the request
|
|
|
|
self.headers["User-Agent"] = user_agent()
|
|
|
|
|
2013-08-18 10:34:04 +02:00
|
|
|
# Attach our Authentication handler to the session
|
2019-02-12 17:52:49 +01:00
|
|
|
self.auth = MultiDomainBasicAuth(index_urls=index_urls)
|
2013-08-18 10:34:04 +02:00
|
|
|
|
2014-12-02 08:55:44 +01:00
|
|
|
# Create our urllib3.Retry instance which will allow us to customize
|
|
|
|
# how we handle retries.
|
|
|
|
retries = urllib3.Retry(
|
|
|
|
# Set the total number of retries that a particular request can
|
|
|
|
# have.
|
|
|
|
total=retries,
|
|
|
|
|
|
|
|
# A 503 error from PyPI typically means that the Fastly -> Origin
|
2016-06-10 21:27:07 +02:00
|
|
|
# connection got interrupted in some way. A 503 error in general
|
2014-12-02 08:55:44 +01:00
|
|
|
# is typically considered a transient error so we'll go ahead and
|
|
|
|
# retry it.
|
2017-12-25 10:53:27 +01:00
|
|
|
# A 500 may indicate transient error in Amazon S3
|
|
|
|
# A 520 or 527 - may indicate transient error in CloudFlare
|
2017-10-24 10:55:42 +02:00
|
|
|
status_forcelist=[500, 503, 520, 527],
|
2014-12-02 08:55:44 +01:00
|
|
|
|
|
|
|
# Add a small amount of back off between failed requests in
|
|
|
|
# order to prevent hammering the service.
|
|
|
|
backoff_factor=0.25,
|
|
|
|
)
|
|
|
|
|
2019-08-09 02:39:12 +02:00
|
|
|
# Check to ensure that the directory containing our cache directory
|
|
|
|
# is owned by the user current executing pip. If it does not exist
|
|
|
|
# we will check the parent directory until we find one that does exist.
|
|
|
|
if cache and not check_path_owner(cache):
|
|
|
|
logger.warning(
|
|
|
|
"The directory '%s' or its parent directory is not owned by "
|
|
|
|
"the current user and the cache has been disabled. Please "
|
|
|
|
"check the permissions and owner of that directory. If "
|
|
|
|
"executing pip with sudo, you may want sudo's -H flag.",
|
|
|
|
cache,
|
|
|
|
)
|
|
|
|
cache = None
|
|
|
|
|
2014-12-20 23:16:04 +01:00
|
|
|
# We want to _only_ cache responses on securely fetched origins. We do
|
|
|
|
# this because we can't validate the response of an insecurely fetched
|
|
|
|
# origin, and we don't want someone to be able to poison the cache and
|
2015-10-20 05:13:48 +02:00
|
|
|
# require manual eviction from the cache to fix it.
|
2014-04-24 13:29:57 +02:00
|
|
|
if cache:
|
2014-12-20 23:16:04 +01:00
|
|
|
secure_adapter = CacheControlAdapter(
|
2019-09-15 23:02:16 +02:00
|
|
|
cache=SafeFileCache(cache),
|
2014-04-24 13:29:57 +02:00
|
|
|
max_retries=retries,
|
|
|
|
)
|
|
|
|
else:
|
2014-12-20 23:16:04 +01:00
|
|
|
secure_adapter = HTTPAdapter(max_retries=retries)
|
|
|
|
|
|
|
|
# Our Insecure HTTPAdapter disables HTTPS validation. It does not
|
|
|
|
# support caching (see above) so we'll use it for all http:// URLs as
|
|
|
|
# well as any https:// host that we've marked as ignoring TLS errors
|
|
|
|
# for.
|
|
|
|
insecure_adapter = InsecureHTTPAdapter(max_retries=retries)
|
2019-06-13 08:56:46 +02:00
|
|
|
# Save this for later use in add_insecure_host().
|
|
|
|
self._insecure_adapter = insecure_adapter
|
2014-04-24 13:29:57 +02:00
|
|
|
|
2014-12-20 23:27:11 +01:00
|
|
|
self.mount("https://", secure_adapter)
|
|
|
|
self.mount("http://", insecure_adapter)
|
2014-01-09 10:07:51 +01:00
|
|
|
|
2013-08-16 14:04:27 +02:00
|
|
|
# Enable file:// urls
|
|
|
|
self.mount("file://", LocalFSAdapter())
|
|
|
|
|
2019-08-20 21:58:31 +02:00
|
|
|
for host in trusted_hosts:
|
2019-08-20 19:42:10 +02:00
|
|
|
self.add_trusted_host(host, suppress_logging=True)
|
|
|
|
|
|
|
|
def add_trusted_host(self, host, source=None, suppress_logging=False):
|
|
|
|
# type: (str, Optional[str], bool) -> None
|
|
|
|
"""
|
|
|
|
:param host: It is okay to provide a host that has previously been
|
|
|
|
added.
|
|
|
|
:param source: An optional source string, for logging where the host
|
|
|
|
string came from.
|
|
|
|
"""
|
|
|
|
if not suppress_logging:
|
|
|
|
msg = 'adding trusted host: {!r}'.format(host)
|
|
|
|
if source is not None:
|
|
|
|
msg += ' (from {})'.format(source)
|
|
|
|
logger.info(msg)
|
|
|
|
|
2019-08-26 01:26:01 +02:00
|
|
|
host_port = parse_netloc(host)
|
|
|
|
if host_port not in self.pip_trusted_origins:
|
|
|
|
self.pip_trusted_origins.append(host_port)
|
2019-06-13 08:56:46 +02:00
|
|
|
|
2019-07-14 11:00:05 +02:00
|
|
|
self.mount(build_url_from_netloc(host) + '/', self._insecure_adapter)
|
2019-08-26 01:26:01 +02:00
|
|
|
if not host_port[1]:
|
2019-07-14 11:00:05 +02:00
|
|
|
# Mount wildcard ports for the same host.
|
|
|
|
self.mount(
|
|
|
|
build_url_from_netloc(host) + ':',
|
|
|
|
self._insecure_adapter
|
|
|
|
)
|
2014-12-20 23:27:11 +01:00
|
|
|
|
2019-08-20 19:42:10 +02:00
|
|
|
def iter_secure_origins(self):
|
|
|
|
# type: () -> Iterator[SecureOrigin]
|
|
|
|
for secure_origin in SECURE_ORIGINS:
|
|
|
|
yield secure_origin
|
2019-08-26 01:26:01 +02:00
|
|
|
for host, port in self.pip_trusted_origins:
|
|
|
|
yield ('*', host, '*' if port is None else port)
|
2019-08-20 19:42:10 +02:00
|
|
|
|
2019-08-20 19:51:28 +02:00
|
|
|
def is_secure_origin(self, location):
|
|
|
|
# type: (Link) -> bool
|
2019-08-20 19:42:10 +02:00
|
|
|
# Determine if this url used a secure transport mechanism
|
|
|
|
parsed = urllib_parse.urlparse(str(location))
|
2019-08-20 20:16:34 +02:00
|
|
|
origin_protocol, origin_host, origin_port = (
|
|
|
|
parsed.scheme, parsed.hostname, parsed.port,
|
|
|
|
)
|
2019-08-20 19:42:10 +02:00
|
|
|
|
|
|
|
# The protocol to use to see if the protocol matches.
|
|
|
|
# Don't count the repository type as part of the protocol: in
|
|
|
|
# cases such as "git+ssh", only use "ssh". (I.e., Only verify against
|
|
|
|
# the last scheme.)
|
2019-08-20 20:16:34 +02:00
|
|
|
origin_protocol = origin_protocol.rsplit('+', 1)[-1]
|
2019-08-20 19:42:10 +02:00
|
|
|
|
|
|
|
# Determine if our origin is a secure origin by looking through our
|
|
|
|
# hardcoded list of secure origins, as well as any additional ones
|
|
|
|
# configured on this PackageFinder instance.
|
|
|
|
for secure_origin in self.iter_secure_origins():
|
2019-08-20 20:16:34 +02:00
|
|
|
secure_protocol, secure_host, secure_port = secure_origin
|
|
|
|
if origin_protocol != secure_protocol and secure_protocol != "*":
|
2019-08-20 19:42:10 +02:00
|
|
|
continue
|
|
|
|
|
|
|
|
try:
|
|
|
|
# We need to do this decode dance to ensure that we have a
|
|
|
|
# unicode object, even on Python 2.x.
|
|
|
|
addr = ipaddress.ip_address(
|
2019-08-20 20:16:34 +02:00
|
|
|
origin_host
|
2019-08-20 19:42:10 +02:00
|
|
|
if (
|
2019-08-20 20:16:34 +02:00
|
|
|
isinstance(origin_host, six.text_type) or
|
|
|
|
origin_host is None
|
2019-08-20 19:42:10 +02:00
|
|
|
)
|
2019-08-20 20:16:34 +02:00
|
|
|
else origin_host.decode("utf8")
|
2019-08-20 19:42:10 +02:00
|
|
|
)
|
|
|
|
network = ipaddress.ip_network(
|
2019-08-20 20:16:34 +02:00
|
|
|
secure_host
|
|
|
|
if isinstance(secure_host, six.text_type)
|
|
|
|
# setting secure_host to proper Union[bytes, str]
|
2019-08-20 19:42:10 +02:00
|
|
|
# creates problems in other places
|
2019-08-20 20:16:34 +02:00
|
|
|
else secure_host.decode("utf8") # type: ignore
|
2019-08-20 19:42:10 +02:00
|
|
|
)
|
|
|
|
except ValueError:
|
|
|
|
# We don't have both a valid address or a valid network, so
|
|
|
|
# we'll check this origin against hostnames.
|
2019-08-20 20:16:34 +02:00
|
|
|
if (origin_host and
|
|
|
|
origin_host.lower() != secure_host.lower() and
|
|
|
|
secure_host != "*"):
|
2019-08-20 19:42:10 +02:00
|
|
|
continue
|
|
|
|
else:
|
|
|
|
# We have a valid address and network, so see if the address
|
|
|
|
# is contained within the network.
|
|
|
|
if addr not in network:
|
|
|
|
continue
|
|
|
|
|
2019-08-20 20:16:34 +02:00
|
|
|
# Check to see if the port matches.
|
|
|
|
if (origin_port != secure_port and
|
|
|
|
secure_port != "*" and
|
|
|
|
secure_port is not None):
|
2019-08-20 19:42:10 +02:00
|
|
|
continue
|
|
|
|
|
|
|
|
# If we've gotten here, then this origin matches the current
|
|
|
|
# secure origin and we should return True
|
|
|
|
return True
|
|
|
|
|
|
|
|
# If we've gotten to this point, then the origin isn't secure and we
|
|
|
|
# will not accept it as a valid location to search. We will however
|
|
|
|
# log a warning that we are ignoring it.
|
|
|
|
logger.warning(
|
|
|
|
"The repository located at %s is not a trusted or secure host and "
|
|
|
|
"is being ignored. If this repository is available via HTTPS we "
|
|
|
|
"recommend you use HTTPS instead, otherwise you may silence "
|
|
|
|
"this warning and allow it anyway with '--trusted-host %s'.",
|
2019-08-20 20:16:34 +02:00
|
|
|
origin_host,
|
|
|
|
origin_host,
|
2019-08-20 19:42:10 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
return False
|
|
|
|
|
2013-08-16 14:04:27 +02:00
|
|
|
def request(self, method, url, *args, **kwargs):
|
|
|
|
# Allow setting a default timeout on a session
|
|
|
|
kwargs.setdefault("timeout", self.timeout)
|
|
|
|
|
2013-08-18 05:50:32 +02:00
|
|
|
# Dispatch the actual request
|
|
|
|
return super(PipSession, self).request(method, url, *args, **kwargs)
|
2013-08-16 14:04:27 +02:00
|
|
|
|
|
|
|
|
|
|
|
def get_file_content(url, comes_from=None, session=None):
|
2018-12-16 10:16:39 +01:00
|
|
|
# type: (str, Optional[str], Optional[PipSession]) -> Tuple[str, Text]
|
2010-07-02 16:53:07 +02:00
|
|
|
"""Gets the content of a file; it may be a filename, file: URL, or
|
2016-11-09 14:25:40 +01:00
|
|
|
http: URL. Returns (location, content). Content is unicode.
|
|
|
|
|
|
|
|
:param url: File path or url.
|
|
|
|
:param comes_from: Origin description of requirements.
|
|
|
|
:param session: Instance of pip.download.PipSession.
|
|
|
|
"""
|
2013-08-16 14:04:27 +02:00
|
|
|
if session is None:
|
2014-05-07 01:25:44 +02:00
|
|
|
raise TypeError(
|
|
|
|
"get_file_content() missing 1 required keyword argument: 'session'"
|
|
|
|
)
|
2013-08-16 14:04:27 +02:00
|
|
|
|
2019-09-10 12:13:16 +02:00
|
|
|
scheme = get_url_scheme(url)
|
2019-07-21 04:51:10 +02:00
|
|
|
|
|
|
|
if scheme in ['http', 'https']:
|
|
|
|
# FIXME: catch some errors
|
|
|
|
resp = session.get(url)
|
|
|
|
resp.raise_for_status()
|
|
|
|
return resp.url, resp.text
|
|
|
|
|
|
|
|
elif scheme == 'file':
|
|
|
|
if comes_from and comes_from.startswith('http'):
|
2010-07-02 16:53:07 +02:00
|
|
|
raise InstallationError(
|
|
|
|
'Requirements file %s references URL %s, which is local'
|
|
|
|
% (comes_from, url))
|
2019-07-21 04:51:10 +02:00
|
|
|
|
|
|
|
path = url.split(':', 1)[1]
|
|
|
|
path = path.replace('\\', '/')
|
|
|
|
match = _url_slash_drive_re.match(path)
|
|
|
|
if match:
|
|
|
|
path = match.group(1) + ':' + path.split('|', 1)[1]
|
|
|
|
path = urllib_parse.unquote(path)
|
|
|
|
if path.startswith('/'):
|
|
|
|
path = '/' + path.lstrip('/')
|
|
|
|
url = path
|
|
|
|
|
2010-08-31 03:01:39 +02:00
|
|
|
try:
|
2016-02-12 23:41:21 +01:00
|
|
|
with open(url, 'rb') as f:
|
|
|
|
content = auto_decode(f.read())
|
2014-01-27 14:02:10 +01:00
|
|
|
except IOError as exc:
|
2014-01-27 15:07:10 +01:00
|
|
|
raise InstallationError(
|
|
|
|
'Could not open requirements file: %s' % str(exc)
|
|
|
|
)
|
2010-07-02 16:53:07 +02:00
|
|
|
return url, content
|
|
|
|
|
|
|
|
|
|
|
|
_url_slash_drive_re = re.compile(r'/*([a-z])\|', re.I)
|
|
|
|
|
|
|
|
|
2015-04-25 05:25:27 +02:00
|
|
|
def unpack_vcs_link(link, location):
|
2019-08-21 12:22:57 +02:00
|
|
|
# type: (Link, str) -> None
|
2010-07-06 06:12:30 +02:00
|
|
|
vcs_backend = _get_used_vcs_backend(link)
|
2019-08-21 12:22:57 +02:00
|
|
|
assert vcs_backend is not None
|
|
|
|
vcs_backend.unpack(location, url=hide_url(link.url))
|
2010-07-06 06:12:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
def _get_used_vcs_backend(link):
|
2019-05-10 02:25:04 +02:00
|
|
|
# type: (Link) -> Optional[VersionControl]
|
2019-04-14 10:13:50 +02:00
|
|
|
"""
|
|
|
|
Return a VersionControl object or None.
|
|
|
|
"""
|
2019-05-10 02:25:04 +02:00
|
|
|
for vcs_backend in vcs.backends:
|
|
|
|
if link.scheme in vcs_backend.schemes:
|
2010-07-06 06:12:30 +02:00
|
|
|
return vcs_backend
|
2019-05-10 02:25:04 +02:00
|
|
|
return None
|
2010-07-06 06:12:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
def is_file_url(link):
|
2018-12-16 10:16:39 +01:00
|
|
|
# type: (Link) -> bool
|
2010-07-06 06:12:30 +02:00
|
|
|
return link.url.lower().startswith('file:')
|
|
|
|
|
|
|
|
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
def is_dir_url(link):
|
2018-12-16 10:16:39 +01:00
|
|
|
# type: (Link) -> bool
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
"""Return whether a file:// Link points to a directory.
|
2010-07-06 06:12:30 +02:00
|
|
|
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
``link`` must not have any other scheme but file://. Call is_file_url()
|
|
|
|
first.
|
2010-07-06 06:12:30 +02:00
|
|
|
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
"""
|
2019-09-20 02:07:25 +02:00
|
|
|
link_path = url_to_path(link.url)
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
return os.path.isdir(link_path)
|
2010-07-06 06:12:30 +02:00
|
|
|
|
|
|
|
|
2015-02-11 16:23:57 +01:00
|
|
|
def _progress_indicator(iterable, *args, **kwargs):
|
|
|
|
return iterable
|
|
|
|
|
|
|
|
|
2018-12-16 10:16:39 +01:00
|
|
|
def _download_url(
|
|
|
|
resp, # type: Response
|
|
|
|
link, # type: Link
|
|
|
|
content_file, # type: IO
|
2019-07-03 14:33:28 +02:00
|
|
|
hashes, # type: Optional[Hashes]
|
2018-12-16 10:16:39 +01:00
|
|
|
progress_bar # type: str
|
|
|
|
):
|
|
|
|
# type: (...) -> None
|
2010-07-06 06:12:30 +02:00
|
|
|
try:
|
2013-08-16 14:04:27 +02:00
|
|
|
total_length = int(resp.headers['content-length'])
|
2011-07-16 16:17:24 +02:00
|
|
|
except (ValueError, KeyError, TypeError):
|
2010-07-06 06:12:30 +02:00
|
|
|
total_length = 0
|
2014-08-31 01:52:28 +02:00
|
|
|
|
|
|
|
cached_resp = getattr(resp, "from_cache", False)
|
2015-01-03 05:52:40 +01:00
|
|
|
if logger.getEffectiveLevel() > logging.INFO:
|
|
|
|
show_progress = False
|
|
|
|
elif cached_resp:
|
2014-08-31 01:52:28 +02:00
|
|
|
show_progress = False
|
|
|
|
elif total_length > (40 * 1000):
|
|
|
|
show_progress = True
|
|
|
|
elif not total_length:
|
|
|
|
show_progress = True
|
|
|
|
else:
|
|
|
|
show_progress = False
|
|
|
|
|
2010-07-06 06:12:30 +02:00
|
|
|
show_url = link.show_url
|
2015-01-06 18:41:49 +01:00
|
|
|
|
|
|
|
def resp_read(chunk_size):
|
|
|
|
try:
|
|
|
|
# Special case for urllib3.
|
|
|
|
for chunk in resp.raw.stream(
|
|
|
|
chunk_size,
|
2015-09-03 23:38:34 +02:00
|
|
|
# We use decode_content=False here because we don't
|
2015-01-06 18:41:49 +01:00
|
|
|
# want urllib3 to mess with the raw bytes we get
|
|
|
|
# from the server. If we decompress inside of
|
|
|
|
# urllib3 then we cannot verify the checksum
|
|
|
|
# because the checksum will be of the compressed
|
|
|
|
# file. This breakage will only occur if the
|
|
|
|
# server adds a Content-Encoding header, which
|
|
|
|
# depends on how the server was configured:
|
|
|
|
# - Some servers will notice that the file isn't a
|
|
|
|
# compressible file and will leave the file alone
|
|
|
|
# and with an empty Content-Encoding
|
|
|
|
# - Some servers will notice that the file is
|
|
|
|
# already compressed and will leave the file
|
|
|
|
# alone and will add a Content-Encoding: gzip
|
|
|
|
# header
|
|
|
|
# - Some servers won't notice anything at all and
|
|
|
|
# will take a file that's already been compressed
|
|
|
|
# and compress it again and set the
|
|
|
|
# Content-Encoding: gzip header
|
|
|
|
#
|
|
|
|
# By setting this not to decode automatically we
|
|
|
|
# hope to eliminate problems with the second case.
|
|
|
|
decode_content=False):
|
|
|
|
yield chunk
|
|
|
|
except AttributeError:
|
|
|
|
# Standard file-like object.
|
|
|
|
while True:
|
|
|
|
chunk = resp.raw.read(chunk_size)
|
|
|
|
if not chunk:
|
|
|
|
break
|
|
|
|
yield chunk
|
|
|
|
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
def written_chunks(chunks):
|
|
|
|
for chunk in chunks:
|
|
|
|
content_file.write(chunk)
|
|
|
|
yield chunk
|
|
|
|
|
2015-02-11 16:23:57 +01:00
|
|
|
progress_indicator = _progress_indicator
|
2015-01-06 18:41:49 +01:00
|
|
|
|
|
|
|
if link.netloc == PyPI.netloc:
|
|
|
|
url = show_url
|
|
|
|
else:
|
|
|
|
url = link.url_without_fragment
|
|
|
|
|
|
|
|
if show_progress: # We don't show progress on cached responses
|
2016-12-22 15:59:55 +01:00
|
|
|
progress_indicator = DownloadProgressProvider(progress_bar,
|
|
|
|
max=total_length)
|
2015-01-06 18:41:49 +01:00
|
|
|
if total_length:
|
2015-09-11 20:41:58 +02:00
|
|
|
logger.info("Downloading %s (%s)", url, format_size(total_length))
|
2014-08-31 01:52:28 +02:00
|
|
|
else:
|
2014-12-14 01:32:36 +01:00
|
|
|
logger.info("Downloading %s", url)
|
2015-01-06 18:41:49 +01:00
|
|
|
elif cached_resp:
|
|
|
|
logger.info("Using cached %s", url)
|
|
|
|
else:
|
|
|
|
logger.info("Downloading %s", url)
|
2014-08-31 01:52:28 +02:00
|
|
|
|
2016-02-28 18:44:35 +01:00
|
|
|
downloaded_chunks = written_chunks(
|
|
|
|
progress_indicator(
|
|
|
|
resp_read(CONTENT_CHUNK_SIZE),
|
|
|
|
CONTENT_CHUNK_SIZE
|
|
|
|
)
|
|
|
|
)
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
if hashes:
|
2015-09-25 00:53:39 +02:00
|
|
|
hashes.check_against_chunks(downloaded_chunks)
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
else:
|
|
|
|
consume(downloaded_chunks)
|
2010-07-06 06:12:30 +02:00
|
|
|
|
|
|
|
|
2015-12-18 19:16:56 +01:00
|
|
|
def _copy_file(filename, location, link):
|
2010-07-06 06:12:30 +02:00
|
|
|
copy = True
|
|
|
|
download_location = os.path.join(location, link.filename)
|
|
|
|
if os.path.exists(download_location):
|
2012-01-20 14:34:45 +01:00
|
|
|
response = ask_path_exists(
|
2016-08-30 04:21:31 +02:00
|
|
|
'The file %s exists. (i)gnore, (w)ipe, (b)ackup, (a)abort' %
|
|
|
|
display_path(download_location), ('i', 'w', 'b', 'a'))
|
2010-07-06 06:12:30 +02:00
|
|
|
if response == 'i':
|
|
|
|
copy = False
|
|
|
|
elif response == 'w':
|
2014-08-31 01:52:28 +02:00
|
|
|
logger.warning('Deleting %s', display_path(download_location))
|
2010-07-06 06:12:30 +02:00
|
|
|
os.remove(download_location)
|
|
|
|
elif response == 'b':
|
|
|
|
dest_file = backup_dir(download_location)
|
2014-08-31 01:52:28 +02:00
|
|
|
logger.warning(
|
|
|
|
'Backing up %s to %s',
|
|
|
|
display_path(download_location),
|
|
|
|
display_path(dest_file),
|
2014-01-27 15:07:10 +01:00
|
|
|
)
|
2010-07-06 06:12:30 +02:00
|
|
|
shutil.move(download_location, dest_file)
|
2016-08-30 04:21:31 +02:00
|
|
|
elif response == 'a':
|
|
|
|
sys.exit(-1)
|
2010-07-06 06:12:30 +02:00
|
|
|
if copy:
|
|
|
|
shutil.copy(filename, download_location)
|
2014-08-31 01:52:28 +02:00
|
|
|
logger.info('Saved %s', display_path(download_location))
|
2010-07-06 06:12:30 +02:00
|
|
|
|
|
|
|
|
2018-12-16 10:16:39 +01:00
|
|
|
def unpack_http_url(
|
|
|
|
link, # type: Link
|
|
|
|
location, # type: str
|
|
|
|
download_dir=None, # type: Optional[str]
|
|
|
|
session=None, # type: Optional[PipSession]
|
|
|
|
hashes=None, # type: Optional[Hashes]
|
|
|
|
progress_bar="on" # type: str
|
|
|
|
):
|
|
|
|
# type: (...) -> None
|
2013-08-16 14:04:27 +02:00
|
|
|
if session is None:
|
2014-05-07 01:25:44 +02:00
|
|
|
raise TypeError(
|
|
|
|
"unpack_http_url() missing 1 required keyword argument: 'session'"
|
|
|
|
)
|
2013-08-16 14:04:27 +02:00
|
|
|
|
2017-06-01 14:54:29 +02:00
|
|
|
with TempDirectory(kind="unpack") as temp_dir:
|
|
|
|
# If a download dir is specified, is the file already downloaded there?
|
|
|
|
already_downloaded_path = None
|
|
|
|
if download_dir:
|
|
|
|
already_downloaded_path = _check_download_dir(link,
|
|
|
|
download_dir,
|
|
|
|
hashes)
|
2012-08-17 09:33:11 +02:00
|
|
|
|
2017-06-01 14:54:29 +02:00
|
|
|
if already_downloaded_path:
|
|
|
|
from_path = already_downloaded_path
|
|
|
|
content_type = mimetypes.guess_type(from_path)[0]
|
|
|
|
else:
|
|
|
|
# let's download to a tmp dir
|
|
|
|
from_path, content_type = _download_http_url(link,
|
|
|
|
session,
|
|
|
|
temp_dir.path,
|
|
|
|
hashes,
|
|
|
|
progress_bar)
|
|
|
|
|
|
|
|
# unpack the archive to the build dir location. even when only
|
|
|
|
# downloading archives, they have to be unpacked to parse dependencies
|
2019-09-19 07:03:28 +02:00
|
|
|
unpack_file(from_path, location, content_type)
|
2017-06-01 14:54:29 +02:00
|
|
|
|
|
|
|
# a download dir is specified; let's copy the archive there
|
|
|
|
if download_dir and not already_downloaded_path:
|
|
|
|
_copy_file(from_path, download_dir, link)
|
|
|
|
|
|
|
|
if not already_downloaded_path:
|
|
|
|
os.unlink(from_path)
|
2014-02-01 20:41:55 +01:00
|
|
|
|
|
|
|
|
2019-08-21 11:19:02 +02:00
|
|
|
def _copy2_ignoring_special_files(src, dest):
|
|
|
|
# type: (str, str) -> None
|
|
|
|
"""Copying special files is not supported, but as a convenience to users
|
|
|
|
we skip errors copying them. This supports tools that may create e.g.
|
|
|
|
socket files in the project source directory.
|
|
|
|
"""
|
|
|
|
try:
|
|
|
|
copy2_fixed(src, dest)
|
|
|
|
except shutil.SpecialFileError as e:
|
|
|
|
# SpecialFileError may be raised due to either the source or
|
|
|
|
# destination. If the destination was the cause then we would actually
|
|
|
|
# care, but since the destination directory is deleted prior to
|
|
|
|
# copy we ignore all of them assuming it is caused by the source.
|
|
|
|
logger.warning(
|
|
|
|
"Ignoring special file error '%s' encountered copying %s to %s.",
|
|
|
|
str(e),
|
|
|
|
path_to_display(src),
|
|
|
|
path_to_display(dest),
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _copy_source_tree(source, target):
|
|
|
|
# type: (str, str) -> None
|
|
|
|
def ignore(d, names):
|
|
|
|
# Pulling in those directories can potentially be very slow,
|
|
|
|
# exclude the following directories if they appear in the top
|
|
|
|
# level dir (and only it).
|
|
|
|
# See discussion at https://github.com/pypa/pip/pull/6770
|
|
|
|
return ['.tox', '.nox'] if d == source else []
|
|
|
|
|
|
|
|
kwargs = dict(ignore=ignore, symlinks=True) # type: CopytreeKwargs
|
|
|
|
|
|
|
|
if not PY2:
|
|
|
|
# Python 2 does not support copy_function, so we only ignore
|
|
|
|
# errors on special file copy in Python 3.
|
|
|
|
kwargs['copy_function'] = _copy2_ignoring_special_files
|
|
|
|
|
|
|
|
shutil.copytree(source, target, **kwargs)
|
|
|
|
|
|
|
|
|
2018-12-16 10:16:39 +01:00
|
|
|
def unpack_file_url(
|
|
|
|
link, # type: Link
|
|
|
|
location, # type: str
|
|
|
|
download_dir=None, # type: Optional[str]
|
|
|
|
hashes=None # type: Optional[Hashes]
|
|
|
|
):
|
|
|
|
# type: (...) -> None
|
2014-05-23 10:39:12 +02:00
|
|
|
"""Unpack link into location.
|
2014-02-01 20:41:55 +01:00
|
|
|
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
If download_dir is provided and link points to a file, make a copy
|
|
|
|
of the link file inside download_dir.
|
|
|
|
"""
|
2019-09-20 02:07:25 +02:00
|
|
|
link_path = url_to_path(link.url)
|
2014-02-01 20:41:55 +01:00
|
|
|
# If it's a url to a local directory
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
if is_dir_url(link):
|
2014-02-01 20:41:55 +01:00
|
|
|
if os.path.isdir(location):
|
|
|
|
rmtree(location)
|
2019-08-21 11:19:02 +02:00
|
|
|
_copy_source_tree(link_path, location)
|
2014-05-23 10:39:12 +02:00
|
|
|
if download_dir:
|
2014-08-31 01:52:28 +02:00
|
|
|
logger.info('Link is a directory, ignoring download_dir')
|
2014-02-01 20:41:55 +01:00
|
|
|
return
|
|
|
|
|
2015-10-09 18:27:10 +02:00
|
|
|
# If --require-hashes is off, `hashes` is either empty, the
|
2016-06-10 21:27:07 +02:00
|
|
|
# link's embedded hash, or MissingHashes; it is required to
|
2015-10-09 18:27:10 +02:00
|
|
|
# match. If --require-hashes is on, we are satisfied by any
|
|
|
|
# hash in `hashes` matching: a URL-based or an option-based
|
|
|
|
# one; no internet-sourced hash will be in `hashes`.
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
if hashes:
|
|
|
|
hashes.check_against_path(link_path)
|
2014-02-01 23:04:58 +01:00
|
|
|
|
2014-02-01 20:41:55 +01:00
|
|
|
# If a download dir is specified, is the file already there and valid?
|
2014-05-26 00:16:46 +02:00
|
|
|
already_downloaded_path = None
|
2014-02-01 20:41:55 +01:00
|
|
|
if download_dir:
|
2015-09-25 00:53:39 +02:00
|
|
|
already_downloaded_path = _check_download_dir(link,
|
|
|
|
download_dir,
|
|
|
|
hashes)
|
2014-02-01 20:41:55 +01:00
|
|
|
|
2014-05-26 00:16:46 +02:00
|
|
|
if already_downloaded_path:
|
|
|
|
from_path = already_downloaded_path
|
2014-02-01 20:41:55 +01:00
|
|
|
else:
|
|
|
|
from_path = link_path
|
2014-02-01 23:04:58 +01:00
|
|
|
|
2014-02-01 20:41:55 +01:00
|
|
|
content_type = mimetypes.guess_type(from_path)[0]
|
2014-02-01 23:04:58 +01:00
|
|
|
|
|
|
|
# unpack the archive to the build dir location. even when only downloading
|
|
|
|
# archives, they have to be unpacked to parse dependencies
|
2019-09-19 07:03:28 +02:00
|
|
|
unpack_file(from_path, location, content_type)
|
2014-02-01 23:04:58 +01:00
|
|
|
|
|
|
|
# a download dir is specified and not already downloaded
|
2014-05-26 00:16:46 +02:00
|
|
|
if download_dir and not already_downloaded_path:
|
2015-12-18 19:16:56 +01:00
|
|
|
_copy_file(from_path, download_dir, link)
|
2014-06-28 00:26:40 +02:00
|
|
|
|
|
|
|
|
|
|
|
class PipXmlrpcTransport(xmlrpc_client.Transport):
|
|
|
|
"""Provide a `xmlrpclib.Transport` implementation via a `PipSession`
|
|
|
|
object.
|
|
|
|
"""
|
2016-05-26 12:41:31 +02:00
|
|
|
|
2014-06-28 00:26:40 +02:00
|
|
|
def __init__(self, index_url, session, use_datetime=False):
|
|
|
|
xmlrpc_client.Transport.__init__(self, use_datetime)
|
2014-09-12 00:40:45 +02:00
|
|
|
index_parts = urllib_parse.urlparse(index_url)
|
2014-06-28 00:26:40 +02:00
|
|
|
self._scheme = index_parts.scheme
|
|
|
|
self._session = session
|
|
|
|
|
|
|
|
def request(self, host, handler, request_body, verbose=False):
|
|
|
|
parts = (self._scheme, host, handler, None, None, None)
|
2014-09-12 00:40:45 +02:00
|
|
|
url = urllib_parse.urlunparse(parts)
|
2014-06-28 00:26:40 +02:00
|
|
|
try:
|
2014-07-04 00:25:21 +02:00
|
|
|
headers = {'Content-Type': 'text/xml'}
|
|
|
|
response = self._session.post(url, data=request_body,
|
|
|
|
headers=headers, stream=True)
|
2014-06-28 00:26:40 +02:00
|
|
|
response.raise_for_status()
|
|
|
|
self.verbose = verbose
|
|
|
|
return self.parse_response(response.raw)
|
|
|
|
except requests.HTTPError as exc:
|
2014-08-31 01:52:28 +02:00
|
|
|
logger.critical(
|
|
|
|
"HTTP error %s while getting %s",
|
|
|
|
exc.response.status_code, url,
|
|
|
|
)
|
2014-06-28 00:26:40 +02:00
|
|
|
raise
|
2014-05-16 16:36:06 +02:00
|
|
|
|
|
|
|
|
2018-12-16 10:16:39 +01:00
|
|
|
def unpack_url(
|
2019-07-03 14:33:28 +02:00
|
|
|
link, # type: Link
|
|
|
|
location, # type: str
|
2018-12-16 10:16:39 +01:00
|
|
|
download_dir=None, # type: Optional[str]
|
|
|
|
session=None, # type: Optional[PipSession]
|
|
|
|
hashes=None, # type: Optional[Hashes]
|
|
|
|
progress_bar="on" # type: str
|
|
|
|
):
|
|
|
|
# type: (...) -> None
|
2014-05-23 10:39:12 +02:00
|
|
|
"""Unpack link.
|
|
|
|
If link is a VCS link:
|
|
|
|
if only_download, export into download_dir and ignore location
|
|
|
|
else unpack into location
|
|
|
|
for other types of link:
|
|
|
|
- unpack into location
|
|
|
|
- if download_dir, copy the file into download_dir
|
|
|
|
- if only_download, mark location for deletion
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
|
|
|
|
:param hashes: A Hashes object, one of whose embedded hashes must match,
|
2015-10-09 18:27:10 +02:00
|
|
|
or HashMismatch will be raised. If the Hashes is empty, no matches are
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
required, and unhashable types of requirements (like VCS ones, which
|
|
|
|
would ordinarily raise HashUnsupported) are allowed.
|
2014-05-16 16:36:06 +02:00
|
|
|
"""
|
|
|
|
# non-editable vcs urls
|
2019-08-15 23:00:55 +02:00
|
|
|
if link.is_vcs:
|
2015-04-25 05:25:27 +02:00
|
|
|
unpack_vcs_link(link, location)
|
2014-05-16 16:36:06 +02:00
|
|
|
|
|
|
|
# file urls
|
|
|
|
elif is_file_url(link):
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
unpack_file_url(link, location, download_dir, hashes=hashes)
|
2014-05-16 16:36:06 +02:00
|
|
|
|
|
|
|
# http urls
|
|
|
|
else:
|
2014-09-14 19:18:11 +02:00
|
|
|
if session is None:
|
|
|
|
session = PipSession()
|
|
|
|
|
2014-05-16 16:36:06 +02:00
|
|
|
unpack_http_url(
|
|
|
|
link,
|
|
|
|
location,
|
|
|
|
download_dir,
|
|
|
|
session,
|
2016-12-22 15:59:55 +01:00
|
|
|
hashes=hashes,
|
|
|
|
progress_bar=progress_bar
|
2014-05-16 16:36:06 +02:00
|
|
|
)
|
2014-05-26 00:16:46 +02:00
|
|
|
|
|
|
|
|
2019-04-17 15:25:45 +02:00
|
|
|
def sanitize_content_filename(filename):
|
|
|
|
# type: (str) -> str
|
|
|
|
"""
|
|
|
|
Sanitize the "filename" value from a Content-Disposition header.
|
|
|
|
"""
|
|
|
|
return os.path.basename(filename)
|
|
|
|
|
|
|
|
|
|
|
|
def parse_content_disposition(content_disposition, default_filename):
|
|
|
|
# type: (str, str) -> str
|
|
|
|
"""
|
|
|
|
Parse the "filename" value from a Content-Disposition header, and
|
|
|
|
return the default filename if the result is empty.
|
|
|
|
"""
|
|
|
|
_type, params = cgi.parse_header(content_disposition)
|
|
|
|
filename = params.get('filename')
|
|
|
|
if filename:
|
|
|
|
# We need to sanitize the filename to prevent directory traversal
|
|
|
|
# in case the filename contains ".." path parts.
|
|
|
|
filename = sanitize_content_filename(filename)
|
|
|
|
return filename or default_filename
|
|
|
|
|
|
|
|
|
2018-12-16 10:16:39 +01:00
|
|
|
def _download_http_url(
|
|
|
|
link, # type: Link
|
|
|
|
session, # type: PipSession
|
|
|
|
temp_dir, # type: str
|
2019-07-03 14:33:28 +02:00
|
|
|
hashes, # type: Optional[Hashes]
|
2018-12-16 10:16:39 +01:00
|
|
|
progress_bar # type: str
|
|
|
|
):
|
|
|
|
# type: (...) -> Tuple[str, str]
|
2014-05-26 00:16:46 +02:00
|
|
|
"""Download link url into temp_dir using provided session"""
|
|
|
|
target_url = link.url.split('#', 1)[0]
|
|
|
|
try:
|
|
|
|
resp = session.get(
|
|
|
|
target_url,
|
|
|
|
# We use Accept-Encoding: identity here because requests
|
|
|
|
# defaults to accepting compressed responses. This breaks in
|
|
|
|
# a variety of ways depending on how the server is configured.
|
|
|
|
# - Some servers will notice that the file isn't a compressible
|
|
|
|
# file and will leave the file alone and with an empty
|
|
|
|
# Content-Encoding
|
|
|
|
# - Some servers will notice that the file is already
|
|
|
|
# compressed and will leave the file alone and will add a
|
|
|
|
# Content-Encoding: gzip header
|
|
|
|
# - Some servers won't notice anything at all and will take
|
|
|
|
# a file that's already been compressed and compress it again
|
|
|
|
# and set the Content-Encoding: gzip header
|
|
|
|
# By setting this to request only the identity encoding We're
|
|
|
|
# hoping to eliminate the third case. Hopefully there does not
|
|
|
|
# exist a server which when given a file will notice it is
|
|
|
|
# already compressed and that you're not asking for a
|
|
|
|
# compressed file and will then decompress it before sending
|
|
|
|
# because if that's the case I don't think it'll ever be
|
|
|
|
# possible to make this work.
|
|
|
|
headers={"Accept-Encoding": "identity"},
|
|
|
|
stream=True,
|
|
|
|
)
|
|
|
|
resp.raise_for_status()
|
|
|
|
except requests.HTTPError as exc:
|
2014-08-31 01:52:28 +02:00
|
|
|
logger.critical(
|
|
|
|
"HTTP error %s while getting %s", exc.response.status_code, link,
|
|
|
|
)
|
2014-05-26 00:16:46 +02:00
|
|
|
raise
|
|
|
|
|
|
|
|
content_type = resp.headers.get('content-type', '')
|
|
|
|
filename = link.filename # fallback
|
|
|
|
# Have a look at the Content-Disposition header for a better guess
|
|
|
|
content_disposition = resp.headers.get('content-disposition')
|
|
|
|
if content_disposition:
|
2019-04-17 15:25:45 +02:00
|
|
|
filename = parse_content_disposition(content_disposition, filename)
|
2019-07-03 14:33:28 +02:00
|
|
|
ext = splitext(filename)[1] # type: Optional[str]
|
2014-05-26 00:16:46 +02:00
|
|
|
if not ext:
|
|
|
|
ext = mimetypes.guess_extension(content_type)
|
|
|
|
if ext:
|
|
|
|
filename += ext
|
|
|
|
if not ext and link.url != resp.url:
|
|
|
|
ext = os.path.splitext(resp.url)[1]
|
|
|
|
if ext:
|
|
|
|
filename += ext
|
|
|
|
file_path = os.path.join(temp_dir, filename)
|
2014-10-02 23:45:37 +02:00
|
|
|
with open(file_path, 'wb') as content_file:
|
2016-12-22 15:59:55 +01:00
|
|
|
_download_url(resp, link, content_file, hashes, progress_bar)
|
2014-05-26 00:16:46 +02:00
|
|
|
return file_path, content_type
|
|
|
|
|
|
|
|
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
def _check_download_dir(link, download_dir, hashes):
|
2019-07-03 14:33:28 +02:00
|
|
|
# type: (Link, str, Optional[Hashes]) -> Optional[str]
|
2014-05-26 00:16:46 +02:00
|
|
|
""" Check download_dir for previously downloaded file with correct hash
|
|
|
|
If a correct file is found return its path else None
|
|
|
|
"""
|
|
|
|
download_path = os.path.join(download_dir, link.filename)
|
|
|
|
if os.path.exists(download_path):
|
|
|
|
# If already downloaded, does its hash match?
|
2014-08-31 01:52:28 +02:00
|
|
|
logger.info('File was already downloaded %s', download_path)
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
if hashes:
|
2014-05-26 00:16:46 +02:00
|
|
|
try:
|
Add checks against requirements-file-dwelling hashes for most kinds of packages. Close #1175.
* Add --require-hashes option. This is handy in deployment scripts to force application authors to hash their requirements. It is also a convenient way to get pip to show computed hashes for a virgin, unhashed requirements file. Eventually, additions to `pip freeze` should fill a superset of this use case.
* In --require-hashes mode, at least one hash is required to match for each requirement.
* Option-based requirements (--sha256=...) turn on --require-hashes mode implicitly.
* Internet-derived URL-based hashes are "necessary but not sufficient": they do not satisfy --require-hashes mode when they match, but they are still used to guard against transmission errors.
* Other URL-based requirements (#md5=...) are treated just like flag-based ones, except they don't turn on --require-hashes.
* Complain informatively, with the most devastating errors first so you don't chase your tail all day only to run up against a brick wall at the end. This also means we don't complain that a hash is missing, only for the user to find, after fixing it, that we have no idea how to even compute a hash for that type of requirement.
* Complain about unpinned requirements when hash-checking mode is on, lest they cause the user surprise later.
* Complain about missing hashes.
* Complain about requirement types we don't know how to hash (like VCS ones and local dirs).
* Have InstallRequirement keep its original Link around (original_link) so we can differentiate between URL hashes from requirements files and ones downloaded from the (untrustworthy) internet.
* Remove test_download_hashes, which is obsolete. Similar coverage is provided in test_utils.TestHashes and the various hash cases in test_req.py.
2015-09-09 19:01:53 +02:00
|
|
|
hashes.check_against_path(download_path)
|
2014-05-26 00:16:46 +02:00
|
|
|
except HashMismatch:
|
2014-08-31 01:52:28 +02:00
|
|
|
logger.warning(
|
2015-09-11 20:41:58 +02:00
|
|
|
'Previously-downloaded file %s has bad hash. '
|
|
|
|
'Re-downloading.',
|
2014-08-31 01:52:28 +02:00
|
|
|
download_path
|
2014-05-26 00:16:46 +02:00
|
|
|
)
|
|
|
|
os.unlink(download_path)
|
|
|
|
return None
|
|
|
|
return download_path
|
|
|
|
return None
|