Загрузить файлы в «venv/Lib/site-packages/pip/_vendor/urllib3»
This commit is contained in:
21
venv/Lib/site-packages/pip/_vendor/urllib3/LICENSE.txt
Normal file
21
venv/Lib/site-packages/pip/_vendor/urllib3/LICENSE.txt
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2008-2020 Andrey Petrov and contributors (see CONTRIBUTORS.txt)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
323
venv/Lib/site-packages/pip/_vendor/urllib3/exceptions.py
Normal file
323
venv/Lib/site-packages/pip/_vendor/urllib3/exceptions.py
Normal file
@@ -0,0 +1,323 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from .packages.six.moves.http_client import IncompleteRead as httplib_IncompleteRead
|
||||||
|
|
||||||
|
# Base Exceptions
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPError(Exception):
|
||||||
|
"""Base exception used by this module."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class HTTPWarning(Warning):
|
||||||
|
"""Base warning used by this module."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class PoolError(HTTPError):
|
||||||
|
"""Base exception for errors caused within a pool."""
|
||||||
|
|
||||||
|
def __init__(self, pool, message):
|
||||||
|
self.pool = pool
|
||||||
|
HTTPError.__init__(self, "%s: %s" % (pool, message))
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
# For pickling purposes.
|
||||||
|
return self.__class__, (None, None)
|
||||||
|
|
||||||
|
|
||||||
|
class RequestError(PoolError):
|
||||||
|
"""Base exception for PoolErrors that have associated URLs."""
|
||||||
|
|
||||||
|
def __init__(self, pool, url, message):
|
||||||
|
self.url = url
|
||||||
|
PoolError.__init__(self, pool, message)
|
||||||
|
|
||||||
|
def __reduce__(self):
|
||||||
|
# For pickling purposes.
|
||||||
|
return self.__class__, (None, self.url, None)
|
||||||
|
|
||||||
|
|
||||||
|
class SSLError(HTTPError):
|
||||||
|
"""Raised when SSL certificate fails in an HTTPS connection."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ProxyError(HTTPError):
|
||||||
|
"""Raised when the connection to a proxy fails."""
|
||||||
|
|
||||||
|
def __init__(self, message, error, *args):
|
||||||
|
super(ProxyError, self).__init__(message, error, *args)
|
||||||
|
self.original_error = error
|
||||||
|
|
||||||
|
|
||||||
|
class DecodeError(HTTPError):
|
||||||
|
"""Raised when automatic decoding based on Content-Type fails."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ProtocolError(HTTPError):
|
||||||
|
"""Raised when something unexpected happens mid-request/response."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
#: Renamed to ProtocolError but aliased for backwards compatibility.
|
||||||
|
ConnectionError = ProtocolError
|
||||||
|
|
||||||
|
|
||||||
|
# Leaf Exceptions
|
||||||
|
|
||||||
|
|
||||||
|
class MaxRetryError(RequestError):
|
||||||
|
"""Raised when the maximum number of retries is exceeded.
|
||||||
|
|
||||||
|
:param pool: The connection pool
|
||||||
|
:type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool`
|
||||||
|
:param string url: The requested Url
|
||||||
|
:param exceptions.Exception reason: The underlying error
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, pool, url, reason=None):
|
||||||
|
self.reason = reason
|
||||||
|
|
||||||
|
message = "Max retries exceeded with url: %s (Caused by %r)" % (url, reason)
|
||||||
|
|
||||||
|
RequestError.__init__(self, pool, url, message)
|
||||||
|
|
||||||
|
|
||||||
|
class HostChangedError(RequestError):
|
||||||
|
"""Raised when an existing pool gets a request for a foreign host."""
|
||||||
|
|
||||||
|
def __init__(self, pool, url, retries=3):
|
||||||
|
message = "Tried to open a foreign host with url: %s" % url
|
||||||
|
RequestError.__init__(self, pool, url, message)
|
||||||
|
self.retries = retries
|
||||||
|
|
||||||
|
|
||||||
|
class TimeoutStateError(HTTPError):
|
||||||
|
"""Raised when passing an invalid state to a timeout"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class TimeoutError(HTTPError):
|
||||||
|
"""Raised when a socket timeout error occurs.
|
||||||
|
|
||||||
|
Catching this error will catch both :exc:`ReadTimeoutErrors
|
||||||
|
<ReadTimeoutError>` and :exc:`ConnectTimeoutErrors <ConnectTimeoutError>`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ReadTimeoutError(TimeoutError, RequestError):
|
||||||
|
"""Raised when a socket timeout occurs while receiving data from a server"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# This timeout error does not have a URL attached and needs to inherit from the
|
||||||
|
# base HTTPError
|
||||||
|
class ConnectTimeoutError(TimeoutError):
|
||||||
|
"""Raised when a socket timeout occurs while connecting to a server"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NewConnectionError(ConnectTimeoutError, PoolError):
|
||||||
|
"""Raised when we fail to establish a new connection. Usually ECONNREFUSED."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyPoolError(PoolError):
|
||||||
|
"""Raised when a pool runs out of connections and no more are allowed."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ClosedPoolError(PoolError):
|
||||||
|
"""Raised when a request enters a pool after the pool has been closed."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LocationValueError(ValueError, HTTPError):
|
||||||
|
"""Raised when there is something wrong with a given URL input."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class LocationParseError(LocationValueError):
|
||||||
|
"""Raised when get_host or similar fails to parse the URL input."""
|
||||||
|
|
||||||
|
def __init__(self, location):
|
||||||
|
message = "Failed to parse: %s" % location
|
||||||
|
HTTPError.__init__(self, message)
|
||||||
|
|
||||||
|
self.location = location
|
||||||
|
|
||||||
|
|
||||||
|
class URLSchemeUnknown(LocationValueError):
|
||||||
|
"""Raised when a URL input has an unsupported scheme."""
|
||||||
|
|
||||||
|
def __init__(self, scheme):
|
||||||
|
message = "Not supported URL scheme %s" % scheme
|
||||||
|
super(URLSchemeUnknown, self).__init__(message)
|
||||||
|
|
||||||
|
self.scheme = scheme
|
||||||
|
|
||||||
|
|
||||||
|
class ResponseError(HTTPError):
|
||||||
|
"""Used as a container for an error reason supplied in a MaxRetryError."""
|
||||||
|
|
||||||
|
GENERIC_ERROR = "too many error responses"
|
||||||
|
SPECIFIC_ERROR = "too many {status_code} error responses"
|
||||||
|
|
||||||
|
|
||||||
|
class SecurityWarning(HTTPWarning):
|
||||||
|
"""Warned when performing security reducing actions"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SubjectAltNameWarning(SecurityWarning):
|
||||||
|
"""Warned when connecting to a host with a certificate missing a SAN."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InsecureRequestWarning(SecurityWarning):
|
||||||
|
"""Warned when making an unverified HTTPS request."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SystemTimeWarning(SecurityWarning):
|
||||||
|
"""Warned when system time is suspected to be wrong"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InsecurePlatformWarning(SecurityWarning):
|
||||||
|
"""Warned when certain TLS/SSL configuration is not available on a platform."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SNIMissingWarning(HTTPWarning):
|
||||||
|
"""Warned when making a HTTPS request without SNI available."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DependencyWarning(HTTPWarning):
|
||||||
|
"""
|
||||||
|
Warned when an attempt is made to import a module with missing optional
|
||||||
|
dependencies.
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ResponseNotChunked(ProtocolError, ValueError):
|
||||||
|
"""Response needs to be chunked in order to read it as chunks."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class BodyNotHttplibCompatible(HTTPError):
|
||||||
|
"""
|
||||||
|
Body should be :class:`http.client.HTTPResponse` like
|
||||||
|
(have an fp attribute which returns raw chunks) for read_chunked().
|
||||||
|
"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class IncompleteRead(HTTPError, httplib_IncompleteRead):
|
||||||
|
"""
|
||||||
|
Response length doesn't match expected Content-Length
|
||||||
|
|
||||||
|
Subclass of :class:`http.client.IncompleteRead` to allow int value
|
||||||
|
for ``partial`` to avoid creating large objects on streamed reads.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, partial, expected):
|
||||||
|
super(IncompleteRead, self).__init__(partial, expected)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "IncompleteRead(%i bytes read, %i more expected)" % (
|
||||||
|
self.partial,
|
||||||
|
self.expected,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidChunkLength(HTTPError, httplib_IncompleteRead):
|
||||||
|
"""Invalid chunk length in a chunked response."""
|
||||||
|
|
||||||
|
def __init__(self, response, length):
|
||||||
|
super(InvalidChunkLength, self).__init__(
|
||||||
|
response.tell(), response.length_remaining
|
||||||
|
)
|
||||||
|
self.response = response
|
||||||
|
self.length = length
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "InvalidChunkLength(got length %r, %i bytes read)" % (
|
||||||
|
self.length,
|
||||||
|
self.partial,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidHeader(HTTPError):
|
||||||
|
"""The header provided was somehow invalid."""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ProxySchemeUnknown(AssertionError, URLSchemeUnknown):
|
||||||
|
"""ProxyManager does not support the supplied scheme"""
|
||||||
|
|
||||||
|
# TODO(t-8ch): Stop inheriting from AssertionError in v2.0.
|
||||||
|
|
||||||
|
def __init__(self, scheme):
|
||||||
|
# 'localhost' is here because our URL parser parses
|
||||||
|
# localhost:8080 -> scheme=localhost, remove if we fix this.
|
||||||
|
if scheme == "localhost":
|
||||||
|
scheme = None
|
||||||
|
if scheme is None:
|
||||||
|
message = "Proxy URL had no scheme, should start with http:// or https://"
|
||||||
|
else:
|
||||||
|
message = (
|
||||||
|
"Proxy URL had unsupported scheme %s, should use http:// or https://"
|
||||||
|
% scheme
|
||||||
|
)
|
||||||
|
super(ProxySchemeUnknown, self).__init__(message)
|
||||||
|
|
||||||
|
|
||||||
|
class ProxySchemeUnsupported(ValueError):
|
||||||
|
"""Fetching HTTPS resources through HTTPS proxies is unsupported"""
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class HeaderParsingError(HTTPError):
|
||||||
|
"""Raised by assert_header_parsing, but we convert it to a log.warning statement."""
|
||||||
|
|
||||||
|
def __init__(self, defects, unparsed_data):
|
||||||
|
message = "%s, unparsed data: %r" % (defects or "Unknown", unparsed_data)
|
||||||
|
super(HeaderParsingError, self).__init__(message)
|
||||||
|
|
||||||
|
|
||||||
|
class UnrewindableBodyError(HTTPError):
|
||||||
|
"""urllib3 encountered an error when trying to rewind a body"""
|
||||||
|
|
||||||
|
pass
|
||||||
274
venv/Lib/site-packages/pip/_vendor/urllib3/fields.py
Normal file
274
venv/Lib/site-packages/pip/_vendor/urllib3/fields.py
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import email.utils
|
||||||
|
import mimetypes
|
||||||
|
import re
|
||||||
|
|
||||||
|
from .packages import six
|
||||||
|
|
||||||
|
|
||||||
|
def guess_content_type(filename, default="application/octet-stream"):
|
||||||
|
"""
|
||||||
|
Guess the "Content-Type" of a file.
|
||||||
|
|
||||||
|
:param filename:
|
||||||
|
The filename to guess the "Content-Type" of using :mod:`mimetypes`.
|
||||||
|
:param default:
|
||||||
|
If no "Content-Type" can be guessed, default to `default`.
|
||||||
|
"""
|
||||||
|
if filename:
|
||||||
|
return mimetypes.guess_type(filename)[0] or default
|
||||||
|
return default
|
||||||
|
|
||||||
|
|
||||||
|
def format_header_param_rfc2231(name, value):
|
||||||
|
"""
|
||||||
|
Helper function to format and quote a single header parameter using the
|
||||||
|
strategy defined in RFC 2231.
|
||||||
|
|
||||||
|
Particularly useful for header parameters which might contain
|
||||||
|
non-ASCII values, like file names. This follows
|
||||||
|
`RFC 2388 Section 4.4 <https://tools.ietf.org/html/rfc2388#section-4.4>`_.
|
||||||
|
|
||||||
|
:param name:
|
||||||
|
The name of the parameter, a string expected to be ASCII only.
|
||||||
|
:param value:
|
||||||
|
The value of the parameter, provided as ``bytes`` or `str``.
|
||||||
|
:ret:
|
||||||
|
An RFC-2231-formatted unicode string.
|
||||||
|
"""
|
||||||
|
if isinstance(value, six.binary_type):
|
||||||
|
value = value.decode("utf-8")
|
||||||
|
|
||||||
|
if not any(ch in value for ch in '"\\\r\n'):
|
||||||
|
result = u'%s="%s"' % (name, value)
|
||||||
|
try:
|
||||||
|
result.encode("ascii")
|
||||||
|
except (UnicodeEncodeError, UnicodeDecodeError):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
return result
|
||||||
|
|
||||||
|
if six.PY2: # Python 2:
|
||||||
|
value = value.encode("utf-8")
|
||||||
|
|
||||||
|
# encode_rfc2231 accepts an encoded string and returns an ascii-encoded
|
||||||
|
# string in Python 2 but accepts and returns unicode strings in Python 3
|
||||||
|
value = email.utils.encode_rfc2231(value, "utf-8")
|
||||||
|
value = "%s*=%s" % (name, value)
|
||||||
|
|
||||||
|
if six.PY2: # Python 2:
|
||||||
|
value = value.decode("utf-8")
|
||||||
|
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
_HTML5_REPLACEMENTS = {
|
||||||
|
u"\u0022": u"%22",
|
||||||
|
# Replace "\" with "\\".
|
||||||
|
u"\u005C": u"\u005C\u005C",
|
||||||
|
}
|
||||||
|
|
||||||
|
# All control characters from 0x00 to 0x1F *except* 0x1B.
|
||||||
|
_HTML5_REPLACEMENTS.update(
|
||||||
|
{
|
||||||
|
six.unichr(cc): u"%{:02X}".format(cc)
|
||||||
|
for cc in range(0x00, 0x1F + 1)
|
||||||
|
if cc not in (0x1B,)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _replace_multiple(value, needles_and_replacements):
|
||||||
|
def replacer(match):
|
||||||
|
return needles_and_replacements[match.group(0)]
|
||||||
|
|
||||||
|
pattern = re.compile(
|
||||||
|
r"|".join([re.escape(needle) for needle in needles_and_replacements.keys()])
|
||||||
|
)
|
||||||
|
|
||||||
|
result = pattern.sub(replacer, value)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def format_header_param_html5(name, value):
|
||||||
|
"""
|
||||||
|
Helper function to format and quote a single header parameter using the
|
||||||
|
HTML5 strategy.
|
||||||
|
|
||||||
|
Particularly useful for header parameters which might contain
|
||||||
|
non-ASCII values, like file names. This follows the `HTML5 Working Draft
|
||||||
|
Section 4.10.22.7`_ and matches the behavior of curl and modern browsers.
|
||||||
|
|
||||||
|
.. _HTML5 Working Draft Section 4.10.22.7:
|
||||||
|
https://w3c.github.io/html/sec-forms.html#multipart-form-data
|
||||||
|
|
||||||
|
:param name:
|
||||||
|
The name of the parameter, a string expected to be ASCII only.
|
||||||
|
:param value:
|
||||||
|
The value of the parameter, provided as ``bytes`` or `str``.
|
||||||
|
:ret:
|
||||||
|
A unicode string, stripped of troublesome characters.
|
||||||
|
"""
|
||||||
|
if isinstance(value, six.binary_type):
|
||||||
|
value = value.decode("utf-8")
|
||||||
|
|
||||||
|
value = _replace_multiple(value, _HTML5_REPLACEMENTS)
|
||||||
|
|
||||||
|
return u'%s="%s"' % (name, value)
|
||||||
|
|
||||||
|
|
||||||
|
# For backwards-compatibility.
|
||||||
|
format_header_param = format_header_param_html5
|
||||||
|
|
||||||
|
|
||||||
|
class RequestField(object):
|
||||||
|
"""
|
||||||
|
A data container for request body parameters.
|
||||||
|
|
||||||
|
:param name:
|
||||||
|
The name of this request field. Must be unicode.
|
||||||
|
:param data:
|
||||||
|
The data/value body.
|
||||||
|
:param filename:
|
||||||
|
An optional filename of the request field. Must be unicode.
|
||||||
|
:param headers:
|
||||||
|
An optional dict-like object of headers to initially use for the field.
|
||||||
|
:param header_formatter:
|
||||||
|
An optional callable that is used to encode and format the headers. By
|
||||||
|
default, this is :func:`format_header_param_html5`.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
name,
|
||||||
|
data,
|
||||||
|
filename=None,
|
||||||
|
headers=None,
|
||||||
|
header_formatter=format_header_param_html5,
|
||||||
|
):
|
||||||
|
self._name = name
|
||||||
|
self._filename = filename
|
||||||
|
self.data = data
|
||||||
|
self.headers = {}
|
||||||
|
if headers:
|
||||||
|
self.headers = dict(headers)
|
||||||
|
self.header_formatter = header_formatter
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_tuples(cls, fieldname, value, header_formatter=format_header_param_html5):
|
||||||
|
"""
|
||||||
|
A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters.
|
||||||
|
|
||||||
|
Supports constructing :class:`~urllib3.fields.RequestField` from
|
||||||
|
parameter of key/value strings AND key/filetuple. A filetuple is a
|
||||||
|
(filename, data, MIME type) tuple where the MIME type is optional.
|
||||||
|
For example::
|
||||||
|
|
||||||
|
'foo': 'bar',
|
||||||
|
'fakefile': ('foofile.txt', 'contents of foofile'),
|
||||||
|
'realfile': ('barfile.txt', open('realfile').read()),
|
||||||
|
'typedfile': ('bazfile.bin', open('bazfile').read(), 'image/jpeg'),
|
||||||
|
'nonamefile': 'contents of nonamefile field',
|
||||||
|
|
||||||
|
Field names and filenames must be unicode.
|
||||||
|
"""
|
||||||
|
if isinstance(value, tuple):
|
||||||
|
if len(value) == 3:
|
||||||
|
filename, data, content_type = value
|
||||||
|
else:
|
||||||
|
filename, data = value
|
||||||
|
content_type = guess_content_type(filename)
|
||||||
|
else:
|
||||||
|
filename = None
|
||||||
|
content_type = None
|
||||||
|
data = value
|
||||||
|
|
||||||
|
request_param = cls(
|
||||||
|
fieldname, data, filename=filename, header_formatter=header_formatter
|
||||||
|
)
|
||||||
|
request_param.make_multipart(content_type=content_type)
|
||||||
|
|
||||||
|
return request_param
|
||||||
|
|
||||||
|
def _render_part(self, name, value):
|
||||||
|
"""
|
||||||
|
Overridable helper function to format a single header parameter. By
|
||||||
|
default, this calls ``self.header_formatter``.
|
||||||
|
|
||||||
|
:param name:
|
||||||
|
The name of the parameter, a string expected to be ASCII only.
|
||||||
|
:param value:
|
||||||
|
The value of the parameter, provided as a unicode string.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return self.header_formatter(name, value)
|
||||||
|
|
||||||
|
def _render_parts(self, header_parts):
|
||||||
|
"""
|
||||||
|
Helper function to format and quote a single header.
|
||||||
|
|
||||||
|
Useful for single headers that are composed of multiple items. E.g.,
|
||||||
|
'Content-Disposition' fields.
|
||||||
|
|
||||||
|
:param header_parts:
|
||||||
|
A sequence of (k, v) tuples or a :class:`dict` of (k, v) to format
|
||||||
|
as `k1="v1"; k2="v2"; ...`.
|
||||||
|
"""
|
||||||
|
parts = []
|
||||||
|
iterable = header_parts
|
||||||
|
if isinstance(header_parts, dict):
|
||||||
|
iterable = header_parts.items()
|
||||||
|
|
||||||
|
for name, value in iterable:
|
||||||
|
if value is not None:
|
||||||
|
parts.append(self._render_part(name, value))
|
||||||
|
|
||||||
|
return u"; ".join(parts)
|
||||||
|
|
||||||
|
def render_headers(self):
|
||||||
|
"""
|
||||||
|
Renders the headers for this request field.
|
||||||
|
"""
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
sort_keys = ["Content-Disposition", "Content-Type", "Content-Location"]
|
||||||
|
for sort_key in sort_keys:
|
||||||
|
if self.headers.get(sort_key, False):
|
||||||
|
lines.append(u"%s: %s" % (sort_key, self.headers[sort_key]))
|
||||||
|
|
||||||
|
for header_name, header_value in self.headers.items():
|
||||||
|
if header_name not in sort_keys:
|
||||||
|
if header_value:
|
||||||
|
lines.append(u"%s: %s" % (header_name, header_value))
|
||||||
|
|
||||||
|
lines.append(u"\r\n")
|
||||||
|
return u"\r\n".join(lines)
|
||||||
|
|
||||||
|
def make_multipart(
|
||||||
|
self, content_disposition=None, content_type=None, content_location=None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Makes this request field into a multipart request field.
|
||||||
|
|
||||||
|
This method overrides "Content-Disposition", "Content-Type" and
|
||||||
|
"Content-Location" headers to the request parameter.
|
||||||
|
|
||||||
|
:param content_type:
|
||||||
|
The 'Content-Type' of the request body.
|
||||||
|
:param content_location:
|
||||||
|
The 'Content-Location' of the request body.
|
||||||
|
|
||||||
|
"""
|
||||||
|
self.headers["Content-Disposition"] = content_disposition or u"form-data"
|
||||||
|
self.headers["Content-Disposition"] += u"; ".join(
|
||||||
|
[
|
||||||
|
u"",
|
||||||
|
self._render_parts(
|
||||||
|
((u"name", self._name), (u"filename", self._filename))
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
self.headers["Content-Type"] = content_type
|
||||||
|
self.headers["Content-Location"] = content_location
|
||||||
98
venv/Lib/site-packages/pip/_vendor/urllib3/filepost.py
Normal file
98
venv/Lib/site-packages/pip/_vendor/urllib3/filepost.py
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import binascii
|
||||||
|
import codecs
|
||||||
|
import os
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
from .fields import RequestField
|
||||||
|
from .packages import six
|
||||||
|
from .packages.six import b
|
||||||
|
|
||||||
|
writer = codecs.lookup("utf-8")[3]
|
||||||
|
|
||||||
|
|
||||||
|
def choose_boundary():
|
||||||
|
"""
|
||||||
|
Our embarrassingly-simple replacement for mimetools.choose_boundary.
|
||||||
|
"""
|
||||||
|
boundary = binascii.hexlify(os.urandom(16))
|
||||||
|
if not six.PY2:
|
||||||
|
boundary = boundary.decode("ascii")
|
||||||
|
return boundary
|
||||||
|
|
||||||
|
|
||||||
|
def iter_field_objects(fields):
|
||||||
|
"""
|
||||||
|
Iterate over fields.
|
||||||
|
|
||||||
|
Supports list of (k, v) tuples and dicts, and lists of
|
||||||
|
:class:`~urllib3.fields.RequestField`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if isinstance(fields, dict):
|
||||||
|
i = six.iteritems(fields)
|
||||||
|
else:
|
||||||
|
i = iter(fields)
|
||||||
|
|
||||||
|
for field in i:
|
||||||
|
if isinstance(field, RequestField):
|
||||||
|
yield field
|
||||||
|
else:
|
||||||
|
yield RequestField.from_tuples(*field)
|
||||||
|
|
||||||
|
|
||||||
|
def iter_fields(fields):
|
||||||
|
"""
|
||||||
|
.. deprecated:: 1.6
|
||||||
|
|
||||||
|
Iterate over fields.
|
||||||
|
|
||||||
|
The addition of :class:`~urllib3.fields.RequestField` makes this function
|
||||||
|
obsolete. Instead, use :func:`iter_field_objects`, which returns
|
||||||
|
:class:`~urllib3.fields.RequestField` objects.
|
||||||
|
|
||||||
|
Supports list of (k, v) tuples and dicts.
|
||||||
|
"""
|
||||||
|
if isinstance(fields, dict):
|
||||||
|
return ((k, v) for k, v in six.iteritems(fields))
|
||||||
|
|
||||||
|
return ((k, v) for k, v in fields)
|
||||||
|
|
||||||
|
|
||||||
|
def encode_multipart_formdata(fields, boundary=None):
|
||||||
|
"""
|
||||||
|
Encode a dictionary of ``fields`` using the multipart/form-data MIME format.
|
||||||
|
|
||||||
|
:param fields:
|
||||||
|
Dictionary of fields or list of (key, :class:`~urllib3.fields.RequestField`).
|
||||||
|
|
||||||
|
:param boundary:
|
||||||
|
If not specified, then a random boundary will be generated using
|
||||||
|
:func:`urllib3.filepost.choose_boundary`.
|
||||||
|
"""
|
||||||
|
body = BytesIO()
|
||||||
|
if boundary is None:
|
||||||
|
boundary = choose_boundary()
|
||||||
|
|
||||||
|
for field in iter_field_objects(fields):
|
||||||
|
body.write(b("--%s\r\n" % (boundary)))
|
||||||
|
|
||||||
|
writer(body).write(field.render_headers())
|
||||||
|
data = field.data
|
||||||
|
|
||||||
|
if isinstance(data, int):
|
||||||
|
data = str(data) # Backwards compatibility
|
||||||
|
|
||||||
|
if isinstance(data, six.text_type):
|
||||||
|
writer(body).write(data)
|
||||||
|
else:
|
||||||
|
body.write(data)
|
||||||
|
|
||||||
|
body.write(b"\r\n")
|
||||||
|
|
||||||
|
body.write(b("--%s--\r\n" % (boundary)))
|
||||||
|
|
||||||
|
content_type = str("multipart/form-data; boundary=%s" % boundary)
|
||||||
|
|
||||||
|
return body.getvalue(), content_type
|
||||||
540
venv/Lib/site-packages/pip/_vendor/urllib3/poolmanager.py
Normal file
540
venv/Lib/site-packages/pip/_vendor/urllib3/poolmanager.py
Normal file
@@ -0,0 +1,540 @@
|
|||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import functools
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from ._collections import HTTPHeaderDict, RecentlyUsedContainer
|
||||||
|
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool, port_by_scheme
|
||||||
|
from .exceptions import (
|
||||||
|
LocationValueError,
|
||||||
|
MaxRetryError,
|
||||||
|
ProxySchemeUnknown,
|
||||||
|
ProxySchemeUnsupported,
|
||||||
|
URLSchemeUnknown,
|
||||||
|
)
|
||||||
|
from .packages import six
|
||||||
|
from .packages.six.moves.urllib.parse import urljoin
|
||||||
|
from .request import RequestMethods
|
||||||
|
from .util.proxy import connection_requires_http_tunnel
|
||||||
|
from .util.retry import Retry
|
||||||
|
from .util.url import parse_url
|
||||||
|
|
||||||
|
__all__ = ["PoolManager", "ProxyManager", "proxy_from_url"]
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
SSL_KEYWORDS = (
|
||||||
|
"key_file",
|
||||||
|
"cert_file",
|
||||||
|
"cert_reqs",
|
||||||
|
"ca_certs",
|
||||||
|
"ssl_version",
|
||||||
|
"ca_cert_dir",
|
||||||
|
"ssl_context",
|
||||||
|
"key_password",
|
||||||
|
"server_hostname",
|
||||||
|
)
|
||||||
|
|
||||||
|
# All known keyword arguments that could be provided to the pool manager, its
|
||||||
|
# pools, or the underlying connections. This is used to construct a pool key.
|
||||||
|
_key_fields = (
|
||||||
|
"key_scheme", # str
|
||||||
|
"key_host", # str
|
||||||
|
"key_port", # int
|
||||||
|
"key_timeout", # int or float or Timeout
|
||||||
|
"key_retries", # int or Retry
|
||||||
|
"key_strict", # bool
|
||||||
|
"key_block", # bool
|
||||||
|
"key_source_address", # str
|
||||||
|
"key_key_file", # str
|
||||||
|
"key_key_password", # str
|
||||||
|
"key_cert_file", # str
|
||||||
|
"key_cert_reqs", # str
|
||||||
|
"key_ca_certs", # str
|
||||||
|
"key_ssl_version", # str
|
||||||
|
"key_ca_cert_dir", # str
|
||||||
|
"key_ssl_context", # instance of ssl.SSLContext or urllib3.util.ssl_.SSLContext
|
||||||
|
"key_maxsize", # int
|
||||||
|
"key_headers", # dict
|
||||||
|
"key__proxy", # parsed proxy url
|
||||||
|
"key__proxy_headers", # dict
|
||||||
|
"key__proxy_config", # class
|
||||||
|
"key_socket_options", # list of (level (int), optname (int), value (int or str)) tuples
|
||||||
|
"key__socks_options", # dict
|
||||||
|
"key_assert_hostname", # bool or string
|
||||||
|
"key_assert_fingerprint", # str
|
||||||
|
"key_server_hostname", # str
|
||||||
|
)
|
||||||
|
|
||||||
|
#: The namedtuple class used to construct keys for the connection pool.
|
||||||
|
#: All custom key schemes should include the fields in this key at a minimum.
|
||||||
|
PoolKey = collections.namedtuple("PoolKey", _key_fields)
|
||||||
|
|
||||||
|
_proxy_config_fields = ("ssl_context", "use_forwarding_for_https")
|
||||||
|
ProxyConfig = collections.namedtuple("ProxyConfig", _proxy_config_fields)
|
||||||
|
|
||||||
|
|
||||||
|
def _default_key_normalizer(key_class, request_context):
|
||||||
|
"""
|
||||||
|
Create a pool key out of a request context dictionary.
|
||||||
|
|
||||||
|
According to RFC 3986, both the scheme and host are case-insensitive.
|
||||||
|
Therefore, this function normalizes both before constructing the pool
|
||||||
|
key for an HTTPS request. If you wish to change this behaviour, provide
|
||||||
|
alternate callables to ``key_fn_by_scheme``.
|
||||||
|
|
||||||
|
:param key_class:
|
||||||
|
The class to use when constructing the key. This should be a namedtuple
|
||||||
|
with the ``scheme`` and ``host`` keys at a minimum.
|
||||||
|
:type key_class: namedtuple
|
||||||
|
:param request_context:
|
||||||
|
A dictionary-like object that contain the context for a request.
|
||||||
|
:type request_context: dict
|
||||||
|
|
||||||
|
:return: A namedtuple that can be used as a connection pool key.
|
||||||
|
:rtype: PoolKey
|
||||||
|
"""
|
||||||
|
# Since we mutate the dictionary, make a copy first
|
||||||
|
context = request_context.copy()
|
||||||
|
context["scheme"] = context["scheme"].lower()
|
||||||
|
context["host"] = context["host"].lower()
|
||||||
|
|
||||||
|
# These are both dictionaries and need to be transformed into frozensets
|
||||||
|
for key in ("headers", "_proxy_headers", "_socks_options"):
|
||||||
|
if key in context and context[key] is not None:
|
||||||
|
context[key] = frozenset(context[key].items())
|
||||||
|
|
||||||
|
# The socket_options key may be a list and needs to be transformed into a
|
||||||
|
# tuple.
|
||||||
|
socket_opts = context.get("socket_options")
|
||||||
|
if socket_opts is not None:
|
||||||
|
context["socket_options"] = tuple(socket_opts)
|
||||||
|
|
||||||
|
# Map the kwargs to the names in the namedtuple - this is necessary since
|
||||||
|
# namedtuples can't have fields starting with '_'.
|
||||||
|
for key in list(context.keys()):
|
||||||
|
context["key_" + key] = context.pop(key)
|
||||||
|
|
||||||
|
# Default to ``None`` for keys missing from the context
|
||||||
|
for field in key_class._fields:
|
||||||
|
if field not in context:
|
||||||
|
context[field] = None
|
||||||
|
|
||||||
|
return key_class(**context)
|
||||||
|
|
||||||
|
|
||||||
|
#: A dictionary that maps a scheme to a callable that creates a pool key.
|
||||||
|
#: This can be used to alter the way pool keys are constructed, if desired.
|
||||||
|
#: Each PoolManager makes a copy of this dictionary so they can be configured
|
||||||
|
#: globally here, or individually on the instance.
|
||||||
|
key_fn_by_scheme = {
|
||||||
|
"http": functools.partial(_default_key_normalizer, PoolKey),
|
||||||
|
"https": functools.partial(_default_key_normalizer, PoolKey),
|
||||||
|
}
|
||||||
|
|
||||||
|
pool_classes_by_scheme = {"http": HTTPConnectionPool, "https": HTTPSConnectionPool}
|
||||||
|
|
||||||
|
|
||||||
|
class PoolManager(RequestMethods):
|
||||||
|
"""
|
||||||
|
Allows for arbitrary requests while transparently keeping track of
|
||||||
|
necessary connection pools for you.
|
||||||
|
|
||||||
|
:param num_pools:
|
||||||
|
Number of connection pools to cache before discarding the least
|
||||||
|
recently used pool.
|
||||||
|
|
||||||
|
:param headers:
|
||||||
|
Headers to include with all requests, unless other headers are given
|
||||||
|
explicitly.
|
||||||
|
|
||||||
|
:param \\**connection_pool_kw:
|
||||||
|
Additional parameters are used to create fresh
|
||||||
|
:class:`urllib3.connectionpool.ConnectionPool` instances.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
>>> manager = PoolManager(num_pools=2)
|
||||||
|
>>> r = manager.request('GET', 'http://google.com/')
|
||||||
|
>>> r = manager.request('GET', 'http://google.com/mail')
|
||||||
|
>>> r = manager.request('GET', 'http://yahoo.com/')
|
||||||
|
>>> len(manager.pools)
|
||||||
|
2
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
proxy = None
|
||||||
|
proxy_config = None
|
||||||
|
|
||||||
|
def __init__(self, num_pools=10, headers=None, **connection_pool_kw):
|
||||||
|
RequestMethods.__init__(self, headers)
|
||||||
|
self.connection_pool_kw = connection_pool_kw
|
||||||
|
self.pools = RecentlyUsedContainer(num_pools)
|
||||||
|
|
||||||
|
# Locally set the pool classes and keys so other PoolManagers can
|
||||||
|
# override them.
|
||||||
|
self.pool_classes_by_scheme = pool_classes_by_scheme
|
||||||
|
self.key_fn_by_scheme = key_fn_by_scheme.copy()
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
self.clear()
|
||||||
|
# Return False to re-raise any potential exceptions
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _new_pool(self, scheme, host, port, request_context=None):
|
||||||
|
"""
|
||||||
|
Create a new :class:`urllib3.connectionpool.ConnectionPool` based on host, port, scheme, and
|
||||||
|
any additional pool keyword arguments.
|
||||||
|
|
||||||
|
If ``request_context`` is provided, it is provided as keyword arguments
|
||||||
|
to the pool class used. This method is used to actually create the
|
||||||
|
connection pools handed out by :meth:`connection_from_url` and
|
||||||
|
companion methods. It is intended to be overridden for customization.
|
||||||
|
"""
|
||||||
|
pool_cls = self.pool_classes_by_scheme[scheme]
|
||||||
|
if request_context is None:
|
||||||
|
request_context = self.connection_pool_kw.copy()
|
||||||
|
|
||||||
|
# Although the context has everything necessary to create the pool,
|
||||||
|
# this function has historically only used the scheme, host, and port
|
||||||
|
# in the positional args. When an API change is acceptable these can
|
||||||
|
# be removed.
|
||||||
|
for key in ("scheme", "host", "port"):
|
||||||
|
request_context.pop(key, None)
|
||||||
|
|
||||||
|
if scheme == "http":
|
||||||
|
for kw in SSL_KEYWORDS:
|
||||||
|
request_context.pop(kw, None)
|
||||||
|
|
||||||
|
return pool_cls(host, port, **request_context)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
"""
|
||||||
|
Empty our store of pools and direct them all to close.
|
||||||
|
|
||||||
|
This will not affect in-flight connections, but they will not be
|
||||||
|
re-used after completion.
|
||||||
|
"""
|
||||||
|
self.pools.clear()
|
||||||
|
|
||||||
|
def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None):
|
||||||
|
"""
|
||||||
|
Get a :class:`urllib3.connectionpool.ConnectionPool` based on the host, port, and scheme.
|
||||||
|
|
||||||
|
If ``port`` isn't given, it will be derived from the ``scheme`` using
|
||||||
|
``urllib3.connectionpool.port_by_scheme``. If ``pool_kwargs`` is
|
||||||
|
provided, it is merged with the instance's ``connection_pool_kw``
|
||||||
|
variable and used to create the new connection pool, if one is
|
||||||
|
needed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not host:
|
||||||
|
raise LocationValueError("No host specified.")
|
||||||
|
|
||||||
|
request_context = self._merge_pool_kwargs(pool_kwargs)
|
||||||
|
request_context["scheme"] = scheme or "http"
|
||||||
|
if not port:
|
||||||
|
port = port_by_scheme.get(request_context["scheme"].lower(), 80)
|
||||||
|
request_context["port"] = port
|
||||||
|
request_context["host"] = host
|
||||||
|
|
||||||
|
return self.connection_from_context(request_context)
|
||||||
|
|
||||||
|
def connection_from_context(self, request_context):
|
||||||
|
"""
|
||||||
|
Get a :class:`urllib3.connectionpool.ConnectionPool` based on the request context.
|
||||||
|
|
||||||
|
``request_context`` must at least contain the ``scheme`` key and its
|
||||||
|
value must be a key in ``key_fn_by_scheme`` instance variable.
|
||||||
|
"""
|
||||||
|
scheme = request_context["scheme"].lower()
|
||||||
|
pool_key_constructor = self.key_fn_by_scheme.get(scheme)
|
||||||
|
if not pool_key_constructor:
|
||||||
|
raise URLSchemeUnknown(scheme)
|
||||||
|
pool_key = pool_key_constructor(request_context)
|
||||||
|
|
||||||
|
return self.connection_from_pool_key(pool_key, request_context=request_context)
|
||||||
|
|
||||||
|
def connection_from_pool_key(self, pool_key, request_context=None):
|
||||||
|
"""
|
||||||
|
Get a :class:`urllib3.connectionpool.ConnectionPool` based on the provided pool key.
|
||||||
|
|
||||||
|
``pool_key`` should be a namedtuple that only contains immutable
|
||||||
|
objects. At a minimum it must have the ``scheme``, ``host``, and
|
||||||
|
``port`` fields.
|
||||||
|
"""
|
||||||
|
with self.pools.lock:
|
||||||
|
# If the scheme, host, or port doesn't match existing open
|
||||||
|
# connections, open a new ConnectionPool.
|
||||||
|
pool = self.pools.get(pool_key)
|
||||||
|
if pool:
|
||||||
|
return pool
|
||||||
|
|
||||||
|
# Make a fresh ConnectionPool of the desired type
|
||||||
|
scheme = request_context["scheme"]
|
||||||
|
host = request_context["host"]
|
||||||
|
port = request_context["port"]
|
||||||
|
pool = self._new_pool(scheme, host, port, request_context=request_context)
|
||||||
|
self.pools[pool_key] = pool
|
||||||
|
|
||||||
|
return pool
|
||||||
|
|
||||||
|
def connection_from_url(self, url, pool_kwargs=None):
|
||||||
|
"""
|
||||||
|
Similar to :func:`urllib3.connectionpool.connection_from_url`.
|
||||||
|
|
||||||
|
If ``pool_kwargs`` is not provided and a new pool needs to be
|
||||||
|
constructed, ``self.connection_pool_kw`` is used to initialize
|
||||||
|
the :class:`urllib3.connectionpool.ConnectionPool`. If ``pool_kwargs``
|
||||||
|
is provided, it is used instead. Note that if a new pool does not
|
||||||
|
need to be created for the request, the provided ``pool_kwargs`` are
|
||||||
|
not used.
|
||||||
|
"""
|
||||||
|
u = parse_url(url)
|
||||||
|
return self.connection_from_host(
|
||||||
|
u.host, port=u.port, scheme=u.scheme, pool_kwargs=pool_kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
def _merge_pool_kwargs(self, override):
|
||||||
|
"""
|
||||||
|
Merge a dictionary of override values for self.connection_pool_kw.
|
||||||
|
|
||||||
|
This does not modify self.connection_pool_kw and returns a new dict.
|
||||||
|
Any keys in the override dictionary with a value of ``None`` are
|
||||||
|
removed from the merged dictionary.
|
||||||
|
"""
|
||||||
|
base_pool_kwargs = self.connection_pool_kw.copy()
|
||||||
|
if override:
|
||||||
|
for key, value in override.items():
|
||||||
|
if value is None:
|
||||||
|
try:
|
||||||
|
del base_pool_kwargs[key]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
base_pool_kwargs[key] = value
|
||||||
|
return base_pool_kwargs
|
||||||
|
|
||||||
|
def _proxy_requires_url_absolute_form(self, parsed_url):
|
||||||
|
"""
|
||||||
|
Indicates if the proxy requires the complete destination URL in the
|
||||||
|
request. Normally this is only needed when not using an HTTP CONNECT
|
||||||
|
tunnel.
|
||||||
|
"""
|
||||||
|
if self.proxy is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return not connection_requires_http_tunnel(
|
||||||
|
self.proxy, self.proxy_config, parsed_url.scheme
|
||||||
|
)
|
||||||
|
|
||||||
|
def _validate_proxy_scheme_url_selection(self, url_scheme):
|
||||||
|
"""
|
||||||
|
Validates that were not attempting to do TLS in TLS connections on
|
||||||
|
Python2 or with unsupported SSL implementations.
|
||||||
|
"""
|
||||||
|
if self.proxy is None or url_scheme != "https":
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.proxy.scheme != "https":
|
||||||
|
return
|
||||||
|
|
||||||
|
if six.PY2 and not self.proxy_config.use_forwarding_for_https:
|
||||||
|
raise ProxySchemeUnsupported(
|
||||||
|
"Contacting HTTPS destinations through HTTPS proxies "
|
||||||
|
"'via CONNECT tunnels' is not supported in Python 2"
|
||||||
|
)
|
||||||
|
|
||||||
|
def urlopen(self, method, url, redirect=True, **kw):
|
||||||
|
"""
|
||||||
|
Same as :meth:`urllib3.HTTPConnectionPool.urlopen`
|
||||||
|
with custom cross-host redirect logic and only sends the request-uri
|
||||||
|
portion of the ``url``.
|
||||||
|
|
||||||
|
The given ``url`` parameter must be absolute, such that an appropriate
|
||||||
|
:class:`urllib3.connectionpool.ConnectionPool` can be chosen for it.
|
||||||
|
"""
|
||||||
|
u = parse_url(url)
|
||||||
|
self._validate_proxy_scheme_url_selection(u.scheme)
|
||||||
|
|
||||||
|
conn = self.connection_from_host(u.host, port=u.port, scheme=u.scheme)
|
||||||
|
|
||||||
|
kw["assert_same_host"] = False
|
||||||
|
kw["redirect"] = False
|
||||||
|
|
||||||
|
if "headers" not in kw:
|
||||||
|
kw["headers"] = self.headers.copy()
|
||||||
|
|
||||||
|
if self._proxy_requires_url_absolute_form(u):
|
||||||
|
response = conn.urlopen(method, url, **kw)
|
||||||
|
else:
|
||||||
|
response = conn.urlopen(method, u.request_uri, **kw)
|
||||||
|
|
||||||
|
redirect_location = redirect and response.get_redirect_location()
|
||||||
|
if not redirect_location:
|
||||||
|
return response
|
||||||
|
|
||||||
|
# Support relative URLs for redirecting.
|
||||||
|
redirect_location = urljoin(url, redirect_location)
|
||||||
|
|
||||||
|
if response.status == 303:
|
||||||
|
# Change the method according to RFC 9110, Section 15.4.4.
|
||||||
|
method = "GET"
|
||||||
|
# And lose the body not to transfer anything sensitive.
|
||||||
|
kw["body"] = None
|
||||||
|
kw["headers"] = HTTPHeaderDict(kw["headers"])._prepare_for_method_change()
|
||||||
|
|
||||||
|
retries = kw.get("retries")
|
||||||
|
if not isinstance(retries, Retry):
|
||||||
|
retries = Retry.from_int(retries, redirect=redirect)
|
||||||
|
|
||||||
|
# Strip headers marked as unsafe to forward to the redirected location.
|
||||||
|
# Check remove_headers_on_redirect to avoid a potential network call within
|
||||||
|
# conn.is_same_host() which may use socket.gethostbyname() in the future.
|
||||||
|
if retries.remove_headers_on_redirect and not conn.is_same_host(
|
||||||
|
redirect_location
|
||||||
|
):
|
||||||
|
headers = list(six.iterkeys(kw["headers"]))
|
||||||
|
for header in headers:
|
||||||
|
if header.lower() in retries.remove_headers_on_redirect:
|
||||||
|
kw["headers"].pop(header, None)
|
||||||
|
|
||||||
|
try:
|
||||||
|
retries = retries.increment(method, url, response=response, _pool=conn)
|
||||||
|
except MaxRetryError:
|
||||||
|
if retries.raise_on_redirect:
|
||||||
|
response.drain_conn()
|
||||||
|
raise
|
||||||
|
return response
|
||||||
|
|
||||||
|
kw["retries"] = retries
|
||||||
|
kw["redirect"] = redirect
|
||||||
|
|
||||||
|
log.info("Redirecting %s -> %s", url, redirect_location)
|
||||||
|
|
||||||
|
response.drain_conn()
|
||||||
|
return self.urlopen(method, redirect_location, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
class ProxyManager(PoolManager):
|
||||||
|
"""
|
||||||
|
Behaves just like :class:`PoolManager`, but sends all requests through
|
||||||
|
the defined proxy, using the CONNECT method for HTTPS URLs.
|
||||||
|
|
||||||
|
:param proxy_url:
|
||||||
|
The URL of the proxy to be used.
|
||||||
|
|
||||||
|
:param proxy_headers:
|
||||||
|
A dictionary containing headers that will be sent to the proxy. In case
|
||||||
|
of HTTP they are being sent with each request, while in the
|
||||||
|
HTTPS/CONNECT case they are sent only once. Could be used for proxy
|
||||||
|
authentication.
|
||||||
|
|
||||||
|
:param proxy_ssl_context:
|
||||||
|
The proxy SSL context is used to establish the TLS connection to the
|
||||||
|
proxy when using HTTPS proxies.
|
||||||
|
|
||||||
|
:param use_forwarding_for_https:
|
||||||
|
(Defaults to False) If set to True will forward requests to the HTTPS
|
||||||
|
proxy to be made on behalf of the client instead of creating a TLS
|
||||||
|
tunnel via the CONNECT method. **Enabling this flag means that request
|
||||||
|
and response headers and content will be visible from the HTTPS proxy**
|
||||||
|
whereas tunneling keeps request and response headers and content
|
||||||
|
private. IP address, target hostname, SNI, and port are always visible
|
||||||
|
to an HTTPS proxy even when this flag is disabled.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> proxy = urllib3.ProxyManager('http://localhost:3128/')
|
||||||
|
>>> r1 = proxy.request('GET', 'http://google.com/')
|
||||||
|
>>> r2 = proxy.request('GET', 'http://httpbin.org/')
|
||||||
|
>>> len(proxy.pools)
|
||||||
|
1
|
||||||
|
>>> r3 = proxy.request('GET', 'https://httpbin.org/')
|
||||||
|
>>> r4 = proxy.request('GET', 'https://twitter.com/')
|
||||||
|
>>> len(proxy.pools)
|
||||||
|
3
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
proxy_url,
|
||||||
|
num_pools=10,
|
||||||
|
headers=None,
|
||||||
|
proxy_headers=None,
|
||||||
|
proxy_ssl_context=None,
|
||||||
|
use_forwarding_for_https=False,
|
||||||
|
**connection_pool_kw
|
||||||
|
):
|
||||||
|
|
||||||
|
if isinstance(proxy_url, HTTPConnectionPool):
|
||||||
|
proxy_url = "%s://%s:%i" % (
|
||||||
|
proxy_url.scheme,
|
||||||
|
proxy_url.host,
|
||||||
|
proxy_url.port,
|
||||||
|
)
|
||||||
|
proxy = parse_url(proxy_url)
|
||||||
|
|
||||||
|
if proxy.scheme not in ("http", "https"):
|
||||||
|
raise ProxySchemeUnknown(proxy.scheme)
|
||||||
|
|
||||||
|
if not proxy.port:
|
||||||
|
port = port_by_scheme.get(proxy.scheme, 80)
|
||||||
|
proxy = proxy._replace(port=port)
|
||||||
|
|
||||||
|
self.proxy = proxy
|
||||||
|
self.proxy_headers = proxy_headers or {}
|
||||||
|
self.proxy_ssl_context = proxy_ssl_context
|
||||||
|
self.proxy_config = ProxyConfig(proxy_ssl_context, use_forwarding_for_https)
|
||||||
|
|
||||||
|
connection_pool_kw["_proxy"] = self.proxy
|
||||||
|
connection_pool_kw["_proxy_headers"] = self.proxy_headers
|
||||||
|
connection_pool_kw["_proxy_config"] = self.proxy_config
|
||||||
|
|
||||||
|
super(ProxyManager, self).__init__(num_pools, headers, **connection_pool_kw)
|
||||||
|
|
||||||
|
def connection_from_host(self, host, port=None, scheme="http", pool_kwargs=None):
|
||||||
|
if scheme == "https":
|
||||||
|
return super(ProxyManager, self).connection_from_host(
|
||||||
|
host, port, scheme, pool_kwargs=pool_kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
return super(ProxyManager, self).connection_from_host(
|
||||||
|
self.proxy.host, self.proxy.port, self.proxy.scheme, pool_kwargs=pool_kwargs
|
||||||
|
)
|
||||||
|
|
||||||
|
def _set_proxy_headers(self, url, headers=None):
|
||||||
|
"""
|
||||||
|
Sets headers needed by proxies: specifically, the Accept and Host
|
||||||
|
headers. Only sets headers not provided by the user.
|
||||||
|
"""
|
||||||
|
headers_ = {"Accept": "*/*"}
|
||||||
|
|
||||||
|
netloc = parse_url(url).netloc
|
||||||
|
if netloc:
|
||||||
|
headers_["Host"] = netloc
|
||||||
|
|
||||||
|
if headers:
|
||||||
|
headers_.update(headers)
|
||||||
|
return headers_
|
||||||
|
|
||||||
|
def urlopen(self, method, url, redirect=True, **kw):
|
||||||
|
"Same as HTTP(S)ConnectionPool.urlopen, ``url`` must be absolute."
|
||||||
|
u = parse_url(url)
|
||||||
|
if not connection_requires_http_tunnel(self.proxy, self.proxy_config, u.scheme):
|
||||||
|
# For connections using HTTP CONNECT, httplib sets the necessary
|
||||||
|
# headers on the CONNECT to the proxy. If we're not using CONNECT,
|
||||||
|
# we'll definitely need to set 'Host' at the very least.
|
||||||
|
headers = kw.get("headers", self.headers)
|
||||||
|
kw["headers"] = self._set_proxy_headers(url, headers)
|
||||||
|
|
||||||
|
return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
def proxy_from_url(url, **kw):
|
||||||
|
return ProxyManager(proxy_url=url, **kw)
|
||||||
Reference in New Issue
Block a user