Mercurial > repos > guerler > hhblits
comparison lib/python3.8/site-packages/pip/_internal/network/session.py @ 0:9e54283cc701 draft
"planemo upload commit d12c32a45bcd441307e632fca6d9af7d60289d44"
author | guerler |
---|---|
date | Mon, 27 Jul 2020 03:47:31 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:9e54283cc701 |
---|---|
1 """PipSession and supporting code, containing all pip-specific | |
2 network request configuration and behavior. | |
3 """ | |
4 | |
5 # The following comment should be removed at some point in the future. | |
6 # mypy: disallow-untyped-defs=False | |
7 | |
8 import email.utils | |
9 import json | |
10 import logging | |
11 import mimetypes | |
12 import os | |
13 import platform | |
14 import sys | |
15 import warnings | |
16 | |
17 from pip._vendor import requests, six, urllib3 | |
18 from pip._vendor.cachecontrol import CacheControlAdapter | |
19 from pip._vendor.requests.adapters import BaseAdapter, HTTPAdapter | |
20 from pip._vendor.requests.models import Response | |
21 from pip._vendor.requests.structures import CaseInsensitiveDict | |
22 from pip._vendor.six.moves.urllib import parse as urllib_parse | |
23 from pip._vendor.urllib3.exceptions import InsecureRequestWarning | |
24 | |
25 from pip import __version__ | |
26 from pip._internal.network.auth import MultiDomainBasicAuth | |
27 from pip._internal.network.cache import SafeFileCache | |
28 # Import ssl from compat so the initial import occurs in only one place. | |
29 from pip._internal.utils.compat import has_tls, ipaddress | |
30 from pip._internal.utils.glibc import libc_ver | |
31 from pip._internal.utils.misc import ( | |
32 build_url_from_netloc, | |
33 get_installed_version, | |
34 parse_netloc, | |
35 ) | |
36 from pip._internal.utils.typing import MYPY_CHECK_RUNNING | |
37 from pip._internal.utils.urls import url_to_path | |
38 | |
39 if MYPY_CHECK_RUNNING: | |
40 from typing import ( | |
41 Iterator, List, Optional, Tuple, Union, | |
42 ) | |
43 | |
44 from pip._internal.models.link import Link | |
45 | |
46 SecureOrigin = Tuple[str, str, Optional[Union[int, str]]] | |
47 | |
48 | |
49 logger = logging.getLogger(__name__) | |
50 | |
51 | |
52 # Ignore warning raised when using --trusted-host. | |
53 warnings.filterwarnings("ignore", category=InsecureRequestWarning) | |
54 | |
55 | |
56 SECURE_ORIGINS = [ | |
57 # protocol, hostname, port | |
58 # Taken from Chrome's list of secure origins (See: http://bit.ly/1qrySKC) | |
59 ("https", "*", "*"), | |
60 ("*", "localhost", "*"), | |
61 ("*", "127.0.0.0/8", "*"), | |
62 ("*", "::1/128", "*"), | |
63 ("file", "*", None), | |
64 # ssh is always secure. | |
65 ("ssh", "*", "*"), | |
66 ] # type: List[SecureOrigin] | |
67 | |
68 | |
69 # These are environment variables present when running under various | |
70 # CI systems. For each variable, some CI systems that use the variable | |
71 # are indicated. The collection was chosen so that for each of a number | |
72 # of popular systems, at least one of the environment variables is used. | |
73 # This list is used to provide some indication of and lower bound for | |
74 # CI traffic to PyPI. Thus, it is okay if the list is not comprehensive. | |
75 # For more background, see: https://github.com/pypa/pip/issues/5499 | |
76 CI_ENVIRONMENT_VARIABLES = ( | |
77 # Azure Pipelines | |
78 'BUILD_BUILDID', | |
79 # Jenkins | |
80 'BUILD_ID', | |
81 # AppVeyor, CircleCI, Codeship, Gitlab CI, Shippable, Travis CI | |
82 'CI', | |
83 # Explicit environment variable. | |
84 'PIP_IS_CI', | |
85 ) | |
86 | |
87 | |
88 def looks_like_ci(): | |
89 # type: () -> bool | |
90 """ | |
91 Return whether it looks like pip is running under CI. | |
92 """ | |
93 # We don't use the method of checking for a tty (e.g. using isatty()) | |
94 # because some CI systems mimic a tty (e.g. Travis CI). Thus that | |
95 # method doesn't provide definitive information in either direction. | |
96 return any(name in os.environ for name in CI_ENVIRONMENT_VARIABLES) | |
97 | |
98 | |
99 def user_agent(): | |
100 """ | |
101 Return a string representing the user agent. | |
102 """ | |
103 data = { | |
104 "installer": {"name": "pip", "version": __version__}, | |
105 "python": platform.python_version(), | |
106 "implementation": { | |
107 "name": platform.python_implementation(), | |
108 }, | |
109 } | |
110 | |
111 if data["implementation"]["name"] == 'CPython': | |
112 data["implementation"]["version"] = platform.python_version() | |
113 elif data["implementation"]["name"] == 'PyPy': | |
114 if sys.pypy_version_info.releaselevel == 'final': | |
115 pypy_version_info = sys.pypy_version_info[:3] | |
116 else: | |
117 pypy_version_info = sys.pypy_version_info | |
118 data["implementation"]["version"] = ".".join( | |
119 [str(x) for x in pypy_version_info] | |
120 ) | |
121 elif data["implementation"]["name"] == 'Jython': | |
122 # Complete Guess | |
123 data["implementation"]["version"] = platform.python_version() | |
124 elif data["implementation"]["name"] == 'IronPython': | |
125 # Complete Guess | |
126 data["implementation"]["version"] = platform.python_version() | |
127 | |
128 if sys.platform.startswith("linux"): | |
129 from pip._vendor import distro | |
130 distro_infos = dict(filter( | |
131 lambda x: x[1], | |
132 zip(["name", "version", "id"], distro.linux_distribution()), | |
133 )) | |
134 libc = dict(filter( | |
135 lambda x: x[1], | |
136 zip(["lib", "version"], libc_ver()), | |
137 )) | |
138 if libc: | |
139 distro_infos["libc"] = libc | |
140 if distro_infos: | |
141 data["distro"] = distro_infos | |
142 | |
143 if sys.platform.startswith("darwin") and platform.mac_ver()[0]: | |
144 data["distro"] = {"name": "macOS", "version": platform.mac_ver()[0]} | |
145 | |
146 if platform.system(): | |
147 data.setdefault("system", {})["name"] = platform.system() | |
148 | |
149 if platform.release(): | |
150 data.setdefault("system", {})["release"] = platform.release() | |
151 | |
152 if platform.machine(): | |
153 data["cpu"] = platform.machine() | |
154 | |
155 if has_tls(): | |
156 import _ssl as ssl | |
157 data["openssl_version"] = ssl.OPENSSL_VERSION | |
158 | |
159 setuptools_version = get_installed_version("setuptools") | |
160 if setuptools_version is not None: | |
161 data["setuptools_version"] = setuptools_version | |
162 | |
163 # Use None rather than False so as not to give the impression that | |
164 # pip knows it is not being run under CI. Rather, it is a null or | |
165 # inconclusive result. Also, we include some value rather than no | |
166 # value to make it easier to know that the check has been run. | |
167 data["ci"] = True if looks_like_ci() else None | |
168 | |
169 user_data = os.environ.get("PIP_USER_AGENT_USER_DATA") | |
170 if user_data is not None: | |
171 data["user_data"] = user_data | |
172 | |
173 return "{data[installer][name]}/{data[installer][version]} {json}".format( | |
174 data=data, | |
175 json=json.dumps(data, separators=(",", ":"), sort_keys=True), | |
176 ) | |
177 | |
178 | |
179 class LocalFSAdapter(BaseAdapter): | |
180 | |
181 def send(self, request, stream=None, timeout=None, verify=None, cert=None, | |
182 proxies=None): | |
183 pathname = url_to_path(request.url) | |
184 | |
185 resp = Response() | |
186 resp.status_code = 200 | |
187 resp.url = request.url | |
188 | |
189 try: | |
190 stats = os.stat(pathname) | |
191 except OSError as exc: | |
192 resp.status_code = 404 | |
193 resp.raw = exc | |
194 else: | |
195 modified = email.utils.formatdate(stats.st_mtime, usegmt=True) | |
196 content_type = mimetypes.guess_type(pathname)[0] or "text/plain" | |
197 resp.headers = CaseInsensitiveDict({ | |
198 "Content-Type": content_type, | |
199 "Content-Length": stats.st_size, | |
200 "Last-Modified": modified, | |
201 }) | |
202 | |
203 resp.raw = open(pathname, "rb") | |
204 resp.close = resp.raw.close | |
205 | |
206 return resp | |
207 | |
208 def close(self): | |
209 pass | |
210 | |
211 | |
212 class InsecureHTTPAdapter(HTTPAdapter): | |
213 | |
214 def cert_verify(self, conn, url, verify, cert): | |
215 super(InsecureHTTPAdapter, self).cert_verify( | |
216 conn=conn, url=url, verify=False, cert=cert | |
217 ) | |
218 | |
219 | |
220 class PipSession(requests.Session): | |
221 | |
222 timeout = None # type: Optional[int] | |
223 | |
224 def __init__(self, *args, **kwargs): | |
225 """ | |
226 :param trusted_hosts: Domains not to emit warnings for when not using | |
227 HTTPS. | |
228 """ | |
229 retries = kwargs.pop("retries", 0) | |
230 cache = kwargs.pop("cache", None) | |
231 trusted_hosts = kwargs.pop("trusted_hosts", []) # type: List[str] | |
232 index_urls = kwargs.pop("index_urls", None) | |
233 | |
234 super(PipSession, self).__init__(*args, **kwargs) | |
235 | |
236 # Namespace the attribute with "pip_" just in case to prevent | |
237 # possible conflicts with the base class. | |
238 self.pip_trusted_origins = [] # type: List[Tuple[str, Optional[int]]] | |
239 | |
240 # Attach our User Agent to the request | |
241 self.headers["User-Agent"] = user_agent() | |
242 | |
243 # Attach our Authentication handler to the session | |
244 self.auth = MultiDomainBasicAuth(index_urls=index_urls) | |
245 | |
246 # Create our urllib3.Retry instance which will allow us to customize | |
247 # how we handle retries. | |
248 retries = urllib3.Retry( | |
249 # Set the total number of retries that a particular request can | |
250 # have. | |
251 total=retries, | |
252 | |
253 # A 503 error from PyPI typically means that the Fastly -> Origin | |
254 # connection got interrupted in some way. A 503 error in general | |
255 # is typically considered a transient error so we'll go ahead and | |
256 # retry it. | |
257 # A 500 may indicate transient error in Amazon S3 | |
258 # A 520 or 527 - may indicate transient error in CloudFlare | |
259 status_forcelist=[500, 503, 520, 527], | |
260 | |
261 # Add a small amount of back off between failed requests in | |
262 # order to prevent hammering the service. | |
263 backoff_factor=0.25, | |
264 ) | |
265 | |
266 # We want to _only_ cache responses on securely fetched origins. We do | |
267 # this because we can't validate the response of an insecurely fetched | |
268 # origin, and we don't want someone to be able to poison the cache and | |
269 # require manual eviction from the cache to fix it. | |
270 if cache: | |
271 secure_adapter = CacheControlAdapter( | |
272 cache=SafeFileCache(cache), | |
273 max_retries=retries, | |
274 ) | |
275 else: | |
276 secure_adapter = HTTPAdapter(max_retries=retries) | |
277 | |
278 # Our Insecure HTTPAdapter disables HTTPS validation. It does not | |
279 # support caching (see above) so we'll use it for all http:// URLs as | |
280 # well as any https:// host that we've marked as ignoring TLS errors | |
281 # for. | |
282 insecure_adapter = InsecureHTTPAdapter(max_retries=retries) | |
283 # Save this for later use in add_insecure_host(). | |
284 self._insecure_adapter = insecure_adapter | |
285 | |
286 self.mount("https://", secure_adapter) | |
287 self.mount("http://", insecure_adapter) | |
288 | |
289 # Enable file:// urls | |
290 self.mount("file://", LocalFSAdapter()) | |
291 | |
292 for host in trusted_hosts: | |
293 self.add_trusted_host(host, suppress_logging=True) | |
294 | |
295 def add_trusted_host(self, host, source=None, suppress_logging=False): | |
296 # type: (str, Optional[str], bool) -> None | |
297 """ | |
298 :param host: It is okay to provide a host that has previously been | |
299 added. | |
300 :param source: An optional source string, for logging where the host | |
301 string came from. | |
302 """ | |
303 if not suppress_logging: | |
304 msg = 'adding trusted host: {!r}'.format(host) | |
305 if source is not None: | |
306 msg += ' (from {})'.format(source) | |
307 logger.info(msg) | |
308 | |
309 host_port = parse_netloc(host) | |
310 if host_port not in self.pip_trusted_origins: | |
311 self.pip_trusted_origins.append(host_port) | |
312 | |
313 self.mount(build_url_from_netloc(host) + '/', self._insecure_adapter) | |
314 if not host_port[1]: | |
315 # Mount wildcard ports for the same host. | |
316 self.mount( | |
317 build_url_from_netloc(host) + ':', | |
318 self._insecure_adapter | |
319 ) | |
320 | |
321 def iter_secure_origins(self): | |
322 # type: () -> Iterator[SecureOrigin] | |
323 for secure_origin in SECURE_ORIGINS: | |
324 yield secure_origin | |
325 for host, port in self.pip_trusted_origins: | |
326 yield ('*', host, '*' if port is None else port) | |
327 | |
328 def is_secure_origin(self, location): | |
329 # type: (Link) -> bool | |
330 # Determine if this url used a secure transport mechanism | |
331 parsed = urllib_parse.urlparse(str(location)) | |
332 origin_protocol, origin_host, origin_port = ( | |
333 parsed.scheme, parsed.hostname, parsed.port, | |
334 ) | |
335 | |
336 # The protocol to use to see if the protocol matches. | |
337 # Don't count the repository type as part of the protocol: in | |
338 # cases such as "git+ssh", only use "ssh". (I.e., Only verify against | |
339 # the last scheme.) | |
340 origin_protocol = origin_protocol.rsplit('+', 1)[-1] | |
341 | |
342 # Determine if our origin is a secure origin by looking through our | |
343 # hardcoded list of secure origins, as well as any additional ones | |
344 # configured on this PackageFinder instance. | |
345 for secure_origin in self.iter_secure_origins(): | |
346 secure_protocol, secure_host, secure_port = secure_origin | |
347 if origin_protocol != secure_protocol and secure_protocol != "*": | |
348 continue | |
349 | |
350 try: | |
351 addr = ipaddress.ip_address( | |
352 None | |
353 if origin_host is None | |
354 else six.ensure_text(origin_host) | |
355 ) | |
356 network = ipaddress.ip_network( | |
357 six.ensure_text(secure_host) | |
358 ) | |
359 except ValueError: | |
360 # We don't have both a valid address or a valid network, so | |
361 # we'll check this origin against hostnames. | |
362 if ( | |
363 origin_host and | |
364 origin_host.lower() != secure_host.lower() and | |
365 secure_host != "*" | |
366 ): | |
367 continue | |
368 else: | |
369 # We have a valid address and network, so see if the address | |
370 # is contained within the network. | |
371 if addr not in network: | |
372 continue | |
373 | |
374 # Check to see if the port matches. | |
375 if ( | |
376 origin_port != secure_port and | |
377 secure_port != "*" and | |
378 secure_port is not None | |
379 ): | |
380 continue | |
381 | |
382 # If we've gotten here, then this origin matches the current | |
383 # secure origin and we should return True | |
384 return True | |
385 | |
386 # If we've gotten to this point, then the origin isn't secure and we | |
387 # will not accept it as a valid location to search. We will however | |
388 # log a warning that we are ignoring it. | |
389 logger.warning( | |
390 "The repository located at %s is not a trusted or secure host and " | |
391 "is being ignored. If this repository is available via HTTPS we " | |
392 "recommend you use HTTPS instead, otherwise you may silence " | |
393 "this warning and allow it anyway with '--trusted-host %s'.", | |
394 origin_host, | |
395 origin_host, | |
396 ) | |
397 | |
398 return False | |
399 | |
400 def request(self, method, url, *args, **kwargs): | |
401 # Allow setting a default timeout on a session | |
402 kwargs.setdefault("timeout", self.timeout) | |
403 | |
404 # Dispatch the actual request | |
405 return super(PipSession, self).request(method, url, *args, **kwargs) |