mirror of
https://github.com/Tautulli/Tautulli.git
synced 2025-07-11 07:46:07 -07:00
Update requests package to 2.10.0
This commit is contained in:
parent
7be651f5cf
commit
4043398e01
41 changed files with 3585 additions and 1534 deletions
|
@ -6,7 +6,7 @@
|
||||||
# /
|
# /
|
||||||
|
|
||||||
"""
|
"""
|
||||||
requests HTTP library
|
Requests HTTP library
|
||||||
~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Requests is an HTTP library, written in Python, for human beings. Basic GET
|
Requests is an HTTP library, written in Python, for human beings. Basic GET
|
||||||
|
@ -36,17 +36,17 @@ usage:
|
||||||
The other HTTP methods are supported - see `requests.api`. Full documentation
|
The other HTTP methods are supported - see `requests.api`. Full documentation
|
||||||
is at <http://python-requests.org>.
|
is at <http://python-requests.org>.
|
||||||
|
|
||||||
:copyright: (c) 2014 by Kenneth Reitz.
|
:copyright: (c) 2016 by Kenneth Reitz.
|
||||||
:license: Apache 2.0, see LICENSE for more details.
|
:license: Apache 2.0, see LICENSE for more details.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__title__ = 'requests'
|
__title__ = 'requests'
|
||||||
__version__ = '2.5.1'
|
__version__ = '2.10.0'
|
||||||
__build__ = 0x020501
|
__build__ = 0x021000
|
||||||
__author__ = 'Kenneth Reitz'
|
__author__ = 'Kenneth Reitz'
|
||||||
__license__ = 'Apache 2.0'
|
__license__ = 'Apache 2.0'
|
||||||
__copyright__ = 'Copyright 2014 Kenneth Reitz'
|
__copyright__ = 'Copyright 2016 Kenneth Reitz'
|
||||||
|
|
||||||
# Attempt to enable urllib3's SNI support, if possible
|
# Attempt to enable urllib3's SNI support, if possible
|
||||||
try:
|
try:
|
||||||
|
@ -55,6 +55,12 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
# urllib3's DependencyWarnings should be silenced.
|
||||||
|
from .packages.urllib3.exceptions import DependencyWarning
|
||||||
|
warnings.simplefilter('ignore', DependencyWarning)
|
||||||
|
|
||||||
from . import utils
|
from . import utils
|
||||||
from .models import Request, Response, PreparedRequest
|
from .models import Request, Response, PreparedRequest
|
||||||
from .api import request, get, head, post, patch, put, delete, options
|
from .api import request, get, head, post, patch, put, delete, options
|
||||||
|
@ -62,7 +68,8 @@ from .sessions import session, Session
|
||||||
from .status_codes import codes
|
from .status_codes import codes
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
RequestException, Timeout, URLRequired,
|
RequestException, Timeout, URLRequired,
|
||||||
TooManyRedirects, HTTPError, ConnectionError
|
TooManyRedirects, HTTPError, ConnectionError,
|
||||||
|
FileModeWarning, ConnectTimeout, ReadTimeout
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set default logging handler to avoid "No handler found" warnings.
|
# Set default logging handler to avoid "No handler found" warnings.
|
||||||
|
@ -75,3 +82,8 @@ except ImportError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
logging.getLogger(__name__).addHandler(NullHandler())
|
logging.getLogger(__name__).addHandler(NullHandler())
|
||||||
|
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
# FileModeWarnings go off per the default.
|
||||||
|
warnings.simplefilter('default', FileModeWarning, append=True)
|
||||||
|
|
|
@ -8,20 +8,24 @@ This module contains the transport adapters that Requests uses to define
|
||||||
and maintain connections.
|
and maintain connections.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import os.path
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
from .models import Response
|
from .models import Response
|
||||||
from .packages.urllib3 import Retry
|
|
||||||
from .packages.urllib3.poolmanager import PoolManager, proxy_from_url
|
from .packages.urllib3.poolmanager import PoolManager, proxy_from_url
|
||||||
from .packages.urllib3.response import HTTPResponse
|
from .packages.urllib3.response import HTTPResponse
|
||||||
from .packages.urllib3.util import Timeout as TimeoutSauce
|
from .packages.urllib3.util import Timeout as TimeoutSauce
|
||||||
|
from .packages.urllib3.util.retry import Retry
|
||||||
from .compat import urlparse, basestring
|
from .compat import urlparse, basestring
|
||||||
from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers,
|
from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers,
|
||||||
prepend_scheme_if_needed, get_auth_from_url, urldefragauth)
|
prepend_scheme_if_needed, get_auth_from_url, urldefragauth,
|
||||||
|
select_proxy, to_native_string)
|
||||||
from .structures import CaseInsensitiveDict
|
from .structures import CaseInsensitiveDict
|
||||||
|
from .packages.urllib3.exceptions import ClosedPoolError
|
||||||
from .packages.urllib3.exceptions import ConnectTimeoutError
|
from .packages.urllib3.exceptions import ConnectTimeoutError
|
||||||
from .packages.urllib3.exceptions import HTTPError as _HTTPError
|
from .packages.urllib3.exceptions import HTTPError as _HTTPError
|
||||||
from .packages.urllib3.exceptions import MaxRetryError
|
from .packages.urllib3.exceptions import MaxRetryError
|
||||||
|
from .packages.urllib3.exceptions import NewConnectionError
|
||||||
from .packages.urllib3.exceptions import ProxyError as _ProxyError
|
from .packages.urllib3.exceptions import ProxyError as _ProxyError
|
||||||
from .packages.urllib3.exceptions import ProtocolError
|
from .packages.urllib3.exceptions import ProtocolError
|
||||||
from .packages.urllib3.exceptions import ReadTimeoutError
|
from .packages.urllib3.exceptions import ReadTimeoutError
|
||||||
|
@ -29,12 +33,19 @@ from .packages.urllib3.exceptions import SSLError as _SSLError
|
||||||
from .packages.urllib3.exceptions import ResponseError
|
from .packages.urllib3.exceptions import ResponseError
|
||||||
from .cookies import extract_cookies_to_jar
|
from .cookies import extract_cookies_to_jar
|
||||||
from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError,
|
from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError,
|
||||||
ProxyError, RetryError)
|
ProxyError, RetryError, InvalidSchema)
|
||||||
from .auth import _basic_auth_str
|
from .auth import _basic_auth_str
|
||||||
|
|
||||||
|
try:
|
||||||
|
from .packages.urllib3.contrib.socks import SOCKSProxyManager
|
||||||
|
except ImportError:
|
||||||
|
def SOCKSProxyManager(*args, **kwargs):
|
||||||
|
raise InvalidSchema("Missing dependencies for SOCKS support.")
|
||||||
|
|
||||||
DEFAULT_POOLBLOCK = False
|
DEFAULT_POOLBLOCK = False
|
||||||
DEFAULT_POOLSIZE = 10
|
DEFAULT_POOLSIZE = 10
|
||||||
DEFAULT_RETRIES = 0
|
DEFAULT_RETRIES = 0
|
||||||
|
DEFAULT_POOL_TIMEOUT = None
|
||||||
|
|
||||||
|
|
||||||
class BaseAdapter(object):
|
class BaseAdapter(object):
|
||||||
|
@ -60,7 +71,7 @@ class HTTPAdapter(BaseAdapter):
|
||||||
|
|
||||||
:param pool_connections: The number of urllib3 connection pools to cache.
|
:param pool_connections: The number of urllib3 connection pools to cache.
|
||||||
:param pool_maxsize: The maximum number of connections to save in the pool.
|
:param pool_maxsize: The maximum number of connections to save in the pool.
|
||||||
:param int max_retries: The maximum number of retries each connection
|
:param max_retries: The maximum number of retries each connection
|
||||||
should attempt. Note, this applies only to failed DNS lookups, socket
|
should attempt. Note, this applies only to failed DNS lookups, socket
|
||||||
connections and connection timeouts, never to requests where data has
|
connections and connection timeouts, never to requests where data has
|
||||||
made it to the server. By default, Requests does not retry failed
|
made it to the server. By default, Requests does not retry failed
|
||||||
|
@ -103,7 +114,7 @@ class HTTPAdapter(BaseAdapter):
|
||||||
|
|
||||||
def __setstate__(self, state):
|
def __setstate__(self, state):
|
||||||
# Can't handle by adding 'proxy_manager' to self.__attrs__ because
|
# Can't handle by adding 'proxy_manager' to self.__attrs__ because
|
||||||
# because self.poolmanager uses a lambda function, which isn't pickleable.
|
# self.poolmanager uses a lambda function, which isn't pickleable.
|
||||||
self.proxy_manager = {}
|
self.proxy_manager = {}
|
||||||
self.config = {}
|
self.config = {}
|
||||||
|
|
||||||
|
@ -144,9 +155,22 @@ class HTTPAdapter(BaseAdapter):
|
||||||
:param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager.
|
:param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager.
|
||||||
:returns: ProxyManager
|
:returns: ProxyManager
|
||||||
"""
|
"""
|
||||||
if not proxy in self.proxy_manager:
|
if proxy in self.proxy_manager:
|
||||||
|
manager = self.proxy_manager[proxy]
|
||||||
|
elif proxy.lower().startswith('socks'):
|
||||||
|
username, password = get_auth_from_url(proxy)
|
||||||
|
manager = self.proxy_manager[proxy] = SOCKSProxyManager(
|
||||||
|
proxy,
|
||||||
|
username=username,
|
||||||
|
password=password,
|
||||||
|
num_pools=self._pool_connections,
|
||||||
|
maxsize=self._pool_maxsize,
|
||||||
|
block=self._pool_block,
|
||||||
|
**proxy_kwargs
|
||||||
|
)
|
||||||
|
else:
|
||||||
proxy_headers = self.proxy_headers(proxy)
|
proxy_headers = self.proxy_headers(proxy)
|
||||||
self.proxy_manager[proxy] = proxy_from_url(
|
manager = self.proxy_manager[proxy] = proxy_from_url(
|
||||||
proxy,
|
proxy,
|
||||||
proxy_headers=proxy_headers,
|
proxy_headers=proxy_headers,
|
||||||
num_pools=self._pool_connections,
|
num_pools=self._pool_connections,
|
||||||
|
@ -154,7 +178,7 @@ class HTTPAdapter(BaseAdapter):
|
||||||
block=self._pool_block,
|
block=self._pool_block,
|
||||||
**proxy_kwargs)
|
**proxy_kwargs)
|
||||||
|
|
||||||
return self.proxy_manager[proxy]
|
return manager
|
||||||
|
|
||||||
def cert_verify(self, conn, url, verify, cert):
|
def cert_verify(self, conn, url, verify, cert):
|
||||||
"""Verify a SSL certificate. This method should not be called from user
|
"""Verify a SSL certificate. This method should not be called from user
|
||||||
|
@ -181,10 +205,15 @@ class HTTPAdapter(BaseAdapter):
|
||||||
raise Exception("Could not find a suitable SSL CA certificate bundle.")
|
raise Exception("Could not find a suitable SSL CA certificate bundle.")
|
||||||
|
|
||||||
conn.cert_reqs = 'CERT_REQUIRED'
|
conn.cert_reqs = 'CERT_REQUIRED'
|
||||||
conn.ca_certs = cert_loc
|
|
||||||
|
if not os.path.isdir(cert_loc):
|
||||||
|
conn.ca_certs = cert_loc
|
||||||
|
else:
|
||||||
|
conn.ca_cert_dir = cert_loc
|
||||||
else:
|
else:
|
||||||
conn.cert_reqs = 'CERT_NONE'
|
conn.cert_reqs = 'CERT_NONE'
|
||||||
conn.ca_certs = None
|
conn.ca_certs = None
|
||||||
|
conn.ca_cert_dir = None
|
||||||
|
|
||||||
if cert:
|
if cert:
|
||||||
if not isinstance(cert, basestring):
|
if not isinstance(cert, basestring):
|
||||||
|
@ -237,8 +266,7 @@ class HTTPAdapter(BaseAdapter):
|
||||||
:param url: The URL to connect to.
|
:param url: The URL to connect to.
|
||||||
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
|
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
|
||||||
"""
|
"""
|
||||||
proxies = proxies or {}
|
proxy = select_proxy(url, proxies)
|
||||||
proxy = proxies.get(urlparse(url.lower()).scheme)
|
|
||||||
|
|
||||||
if proxy:
|
if proxy:
|
||||||
proxy = prepend_scheme_if_needed(proxy, 'http')
|
proxy = prepend_scheme_if_needed(proxy, 'http')
|
||||||
|
@ -255,10 +283,12 @@ class HTTPAdapter(BaseAdapter):
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Disposes of any internal state.
|
"""Disposes of any internal state.
|
||||||
|
|
||||||
Currently, this just closes the PoolManager, which closes pooled
|
Currently, this closes the PoolManager and any active ProxyManager,
|
||||||
connections.
|
which closes any pooled connections.
|
||||||
"""
|
"""
|
||||||
self.poolmanager.clear()
|
self.poolmanager.clear()
|
||||||
|
for proxy in self.proxy_manager.values():
|
||||||
|
proxy.clear()
|
||||||
|
|
||||||
def request_url(self, request, proxies):
|
def request_url(self, request, proxies):
|
||||||
"""Obtain the url to use when making the final request.
|
"""Obtain the url to use when making the final request.
|
||||||
|
@ -271,16 +301,20 @@ class HTTPAdapter(BaseAdapter):
|
||||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||||
|
|
||||||
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
|
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
|
||||||
:param proxies: A dictionary of schemes to proxy URLs.
|
:param proxies: A dictionary of schemes or schemes and hosts to proxy URLs.
|
||||||
"""
|
"""
|
||||||
proxies = proxies or {}
|
proxy = select_proxy(request.url, proxies)
|
||||||
scheme = urlparse(request.url).scheme
|
scheme = urlparse(request.url).scheme
|
||||||
proxy = proxies.get(scheme)
|
|
||||||
|
|
||||||
if proxy and scheme != 'https':
|
is_proxied_http_request = (proxy and scheme != 'https')
|
||||||
|
using_socks_proxy = False
|
||||||
|
if proxy:
|
||||||
|
proxy_scheme = urlparse(proxy).scheme.lower()
|
||||||
|
using_socks_proxy = proxy_scheme.startswith('socks')
|
||||||
|
|
||||||
|
url = request.path_url
|
||||||
|
if is_proxied_http_request and not using_socks_proxy:
|
||||||
url = urldefragauth(request.url)
|
url = urldefragauth(request.url)
|
||||||
else:
|
|
||||||
url = request.path_url
|
|
||||||
|
|
||||||
return url
|
return url
|
||||||
|
|
||||||
|
@ -309,7 +343,6 @@ class HTTPAdapter(BaseAdapter):
|
||||||
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
|
||||||
|
|
||||||
:param proxies: The url of the proxy being used for this request.
|
:param proxies: The url of the proxy being used for this request.
|
||||||
:param kwargs: Optional additional keyword arguments.
|
|
||||||
"""
|
"""
|
||||||
headers = {}
|
headers = {}
|
||||||
username, password = get_auth_from_url(proxy)
|
username, password = get_auth_from_url(proxy)
|
||||||
|
@ -326,8 +359,8 @@ class HTTPAdapter(BaseAdapter):
|
||||||
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
|
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
|
||||||
:param stream: (optional) Whether to stream the request content.
|
:param stream: (optional) Whether to stream the request content.
|
||||||
:param timeout: (optional) How long to wait for the server to send
|
:param timeout: (optional) How long to wait for the server to send
|
||||||
data before giving up, as a float, or a (`connect timeout, read
|
data before giving up, as a float, or a :ref:`(connect timeout,
|
||||||
timeout <user/advanced.html#timeouts>`_) tuple.
|
read timeout) <timeouts>` tuple.
|
||||||
:type timeout: float or tuple
|
:type timeout: float or tuple
|
||||||
:param verify: (optional) Whether to verify SSL certificates.
|
:param verify: (optional) Whether to verify SSL certificates.
|
||||||
:param cert: (optional) Any user-provided SSL certificate to be trusted.
|
:param cert: (optional) Any user-provided SSL certificate to be trusted.
|
||||||
|
@ -375,7 +408,7 @@ class HTTPAdapter(BaseAdapter):
|
||||||
if hasattr(conn, 'proxy_pool'):
|
if hasattr(conn, 'proxy_pool'):
|
||||||
conn = conn.proxy_pool
|
conn = conn.proxy_pool
|
||||||
|
|
||||||
low_conn = conn._get_conn(timeout=timeout)
|
low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
low_conn.putrequest(request.method,
|
low_conn.putrequest(request.method,
|
||||||
|
@ -394,7 +427,15 @@ class HTTPAdapter(BaseAdapter):
|
||||||
low_conn.send(b'\r\n')
|
low_conn.send(b'\r\n')
|
||||||
low_conn.send(b'0\r\n\r\n')
|
low_conn.send(b'0\r\n\r\n')
|
||||||
|
|
||||||
r = low_conn.getresponse()
|
# Receive the response from the server
|
||||||
|
try:
|
||||||
|
# For Python 2.7+ versions, use buffering of HTTP
|
||||||
|
# responses
|
||||||
|
r = low_conn.getresponse(buffering=True)
|
||||||
|
except TypeError:
|
||||||
|
# For compatibility with Python 2.6 versions and back
|
||||||
|
r = low_conn.getresponse()
|
||||||
|
|
||||||
resp = HTTPResponse.from_httplib(
|
resp = HTTPResponse.from_httplib(
|
||||||
r,
|
r,
|
||||||
pool=conn,
|
pool=conn,
|
||||||
|
@ -407,20 +448,25 @@ class HTTPAdapter(BaseAdapter):
|
||||||
# Then, reraise so that we can handle the actual exception.
|
# Then, reraise so that we can handle the actual exception.
|
||||||
low_conn.close()
|
low_conn.close()
|
||||||
raise
|
raise
|
||||||
else:
|
|
||||||
# All is well, return the connection to the pool.
|
|
||||||
conn._put_conn(low_conn)
|
|
||||||
|
|
||||||
except (ProtocolError, socket.error) as err:
|
except (ProtocolError, socket.error) as err:
|
||||||
raise ConnectionError(err, request=request)
|
raise ConnectionError(err, request=request)
|
||||||
|
|
||||||
except MaxRetryError as e:
|
except MaxRetryError as e:
|
||||||
if isinstance(e.reason, ConnectTimeoutError):
|
if isinstance(e.reason, ConnectTimeoutError):
|
||||||
raise ConnectTimeout(e, request=request)
|
# TODO: Remove this in 3.0.0: see #2811
|
||||||
|
if not isinstance(e.reason, NewConnectionError):
|
||||||
|
raise ConnectTimeout(e, request=request)
|
||||||
|
|
||||||
if isinstance(e.reason, ResponseError):
|
if isinstance(e.reason, ResponseError):
|
||||||
raise RetryError(e, request=request)
|
raise RetryError(e, request=request)
|
||||||
|
|
||||||
|
if isinstance(e.reason, _ProxyError):
|
||||||
|
raise ProxyError(e, request=request)
|
||||||
|
|
||||||
|
raise ConnectionError(e, request=request)
|
||||||
|
|
||||||
|
except ClosedPoolError as e:
|
||||||
raise ConnectionError(e, request=request)
|
raise ConnectionError(e, request=request)
|
||||||
|
|
||||||
except _ProxyError as e:
|
except _ProxyError as e:
|
||||||
|
|
|
@ -16,7 +16,6 @@ from . import sessions
|
||||||
|
|
||||||
def request(method, url, **kwargs):
|
def request(method, url, **kwargs):
|
||||||
"""Constructs and sends a :class:`Request <Request>`.
|
"""Constructs and sends a :class:`Request <Request>`.
|
||||||
Returns :class:`Response <Response>` object.
|
|
||||||
|
|
||||||
:param method: method for the new :class:`Request` object.
|
:param method: method for the new :class:`Request` object.
|
||||||
:param url: URL for the new :class:`Request` object.
|
:param url: URL for the new :class:`Request` object.
|
||||||
|
@ -25,18 +24,24 @@ def request(method, url, **kwargs):
|
||||||
:param json: (optional) json data to send in the body of the :class:`Request`.
|
:param json: (optional) json data to send in the body of the :class:`Request`.
|
||||||
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
|
||||||
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
|
||||||
:param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': ('filename', fileobj)}``) for multipart encoding upload.
|
:param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload.
|
||||||
|
``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')``
|
||||||
|
or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string
|
||||||
|
defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers
|
||||||
|
to add for the file.
|
||||||
:param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
|
:param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
|
||||||
:param timeout: (optional) How long to wait for the server to send data
|
:param timeout: (optional) How long to wait for the server to send data
|
||||||
before giving up, as a float, or a (`connect timeout, read timeout
|
before giving up, as a float, or a :ref:`(connect timeout, read
|
||||||
<user/advanced.html#timeouts>`_) tuple.
|
timeout) <timeouts>` tuple.
|
||||||
:type timeout: float or tuple
|
:type timeout: float or tuple
|
||||||
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
|
:param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed.
|
||||||
:type allow_redirects: bool
|
:type allow_redirects: bool
|
||||||
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
|
||||||
:param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided.
|
:param verify: (optional) whether the SSL cert will be verified. A CA_BUNDLE path can also be provided. Defaults to ``True``.
|
||||||
:param stream: (optional) if ``False``, the response content will be immediately downloaded.
|
:param stream: (optional) if ``False``, the response content will be immediately downloaded.
|
||||||
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
|
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
|
||||||
|
:return: :class:`Response <Response>` object
|
||||||
|
:rtype: requests.Response
|
||||||
|
|
||||||
Usage::
|
Usage::
|
||||||
|
|
||||||
|
@ -45,31 +50,34 @@ def request(method, url, **kwargs):
|
||||||
<Response [200]>
|
<Response [200]>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
session = sessions.Session()
|
# By using the 'with' statement we are sure the session is closed, thus we
|
||||||
response = session.request(method=method, url=url, **kwargs)
|
# avoid leaving sockets open which can trigger a ResourceWarning in some
|
||||||
# By explicitly closing the session, we avoid leaving sockets open which
|
# cases, and look like a memory leak in others.
|
||||||
# can trigger a ResourceWarning in some cases, and look like a memory leak
|
with sessions.Session() as session:
|
||||||
# in others.
|
return session.request(method=method, url=url, **kwargs)
|
||||||
session.close()
|
|
||||||
return response
|
|
||||||
|
|
||||||
|
|
||||||
def get(url, **kwargs):
|
def get(url, params=None, **kwargs):
|
||||||
"""Sends a GET request. Returns :class:`Response` object.
|
"""Sends a GET request.
|
||||||
|
|
||||||
:param url: URL for the new :class:`Request` object.
|
:param url: URL for the new :class:`Request` object.
|
||||||
|
:param params: (optional) Dictionary or bytes to be sent in the query string for the :class:`Request`.
|
||||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||||
|
:return: :class:`Response <Response>` object
|
||||||
|
:rtype: requests.Response
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs.setdefault('allow_redirects', True)
|
kwargs.setdefault('allow_redirects', True)
|
||||||
return request('get', url, **kwargs)
|
return request('get', url, params=params, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def options(url, **kwargs):
|
def options(url, **kwargs):
|
||||||
"""Sends a OPTIONS request. Returns :class:`Response` object.
|
"""Sends a OPTIONS request.
|
||||||
|
|
||||||
:param url: URL for the new :class:`Request` object.
|
:param url: URL for the new :class:`Request` object.
|
||||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||||
|
:return: :class:`Response <Response>` object
|
||||||
|
:rtype: requests.Response
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs.setdefault('allow_redirects', True)
|
kwargs.setdefault('allow_redirects', True)
|
||||||
|
@ -77,10 +85,12 @@ def options(url, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
def head(url, **kwargs):
|
def head(url, **kwargs):
|
||||||
"""Sends a HEAD request. Returns :class:`Response` object.
|
"""Sends a HEAD request.
|
||||||
|
|
||||||
:param url: URL for the new :class:`Request` object.
|
:param url: URL for the new :class:`Request` object.
|
||||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||||
|
:return: :class:`Response <Response>` object
|
||||||
|
:rtype: requests.Response
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kwargs.setdefault('allow_redirects', False)
|
kwargs.setdefault('allow_redirects', False)
|
||||||
|
@ -88,44 +98,52 @@ def head(url, **kwargs):
|
||||||
|
|
||||||
|
|
||||||
def post(url, data=None, json=None, **kwargs):
|
def post(url, data=None, json=None, **kwargs):
|
||||||
"""Sends a POST request. Returns :class:`Response` object.
|
"""Sends a POST request.
|
||||||
|
|
||||||
:param url: URL for the new :class:`Request` object.
|
:param url: URL for the new :class:`Request` object.
|
||||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||||
:param json: (optional) json data to send in the body of the :class:`Request`.
|
:param json: (optional) json data to send in the body of the :class:`Request`.
|
||||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||||
|
:return: :class:`Response <Response>` object
|
||||||
|
:rtype: requests.Response
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return request('post', url, data=data, json=json, **kwargs)
|
return request('post', url, data=data, json=json, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def put(url, data=None, **kwargs):
|
def put(url, data=None, **kwargs):
|
||||||
"""Sends a PUT request. Returns :class:`Response` object.
|
"""Sends a PUT request.
|
||||||
|
|
||||||
:param url: URL for the new :class:`Request` object.
|
:param url: URL for the new :class:`Request` object.
|
||||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||||
|
:return: :class:`Response <Response>` object
|
||||||
|
:rtype: requests.Response
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return request('put', url, data=data, **kwargs)
|
return request('put', url, data=data, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def patch(url, data=None, **kwargs):
|
def patch(url, data=None, **kwargs):
|
||||||
"""Sends a PATCH request. Returns :class:`Response` object.
|
"""Sends a PATCH request.
|
||||||
|
|
||||||
:param url: URL for the new :class:`Request` object.
|
:param url: URL for the new :class:`Request` object.
|
||||||
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
:param data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`.
|
||||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||||
|
:return: :class:`Response <Response>` object
|
||||||
|
:rtype: requests.Response
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return request('patch', url, data=data, **kwargs)
|
return request('patch', url, data=data, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def delete(url, **kwargs):
|
def delete(url, **kwargs):
|
||||||
"""Sends a DELETE request. Returns :class:`Response` object.
|
"""Sends a DELETE request.
|
||||||
|
|
||||||
:param url: URL for the new :class:`Request` object.
|
:param url: URL for the new :class:`Request` object.
|
||||||
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
:param \*\*kwargs: Optional arguments that ``request`` takes.
|
||||||
|
:return: :class:`Response <Response>` object
|
||||||
|
:rtype: requests.Response
|
||||||
"""
|
"""
|
||||||
|
|
||||||
return request('delete', url, **kwargs)
|
return request('delete', url, **kwargs)
|
||||||
|
|
|
@ -11,6 +11,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import threading
|
||||||
|
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
|
||||||
|
@ -46,6 +47,15 @@ class HTTPBasicAuth(AuthBase):
|
||||||
self.username = username
|
self.username = username
|
||||||
self.password = password
|
self.password = password
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return all([
|
||||||
|
self.username == getattr(other, 'username', None),
|
||||||
|
self.password == getattr(other, 'password', None)
|
||||||
|
])
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self == other
|
||||||
|
|
||||||
def __call__(self, r):
|
def __call__(self, r):
|
||||||
r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
|
r.headers['Authorization'] = _basic_auth_str(self.username, self.password)
|
||||||
return r
|
return r
|
||||||
|
@ -63,19 +73,27 @@ class HTTPDigestAuth(AuthBase):
|
||||||
def __init__(self, username, password):
|
def __init__(self, username, password):
|
||||||
self.username = username
|
self.username = username
|
||||||
self.password = password
|
self.password = password
|
||||||
self.last_nonce = ''
|
# Keep state in per-thread local storage
|
||||||
self.nonce_count = 0
|
self._thread_local = threading.local()
|
||||||
self.chal = {}
|
|
||||||
self.pos = None
|
def init_per_thread_state(self):
|
||||||
self.num_401_calls = 1
|
# Ensure state is initialized just once per-thread
|
||||||
|
if not hasattr(self._thread_local, 'init'):
|
||||||
|
self._thread_local.init = True
|
||||||
|
self._thread_local.last_nonce = ''
|
||||||
|
self._thread_local.nonce_count = 0
|
||||||
|
self._thread_local.chal = {}
|
||||||
|
self._thread_local.pos = None
|
||||||
|
self._thread_local.num_401_calls = None
|
||||||
|
|
||||||
def build_digest_header(self, method, url):
|
def build_digest_header(self, method, url):
|
||||||
|
|
||||||
realm = self.chal['realm']
|
realm = self._thread_local.chal['realm']
|
||||||
nonce = self.chal['nonce']
|
nonce = self._thread_local.chal['nonce']
|
||||||
qop = self.chal.get('qop')
|
qop = self._thread_local.chal.get('qop')
|
||||||
algorithm = self.chal.get('algorithm')
|
algorithm = self._thread_local.chal.get('algorithm')
|
||||||
opaque = self.chal.get('opaque')
|
opaque = self._thread_local.chal.get('opaque')
|
||||||
|
hash_utf8 = None
|
||||||
|
|
||||||
if algorithm is None:
|
if algorithm is None:
|
||||||
_algorithm = 'MD5'
|
_algorithm = 'MD5'
|
||||||
|
@ -103,7 +121,8 @@ class HTTPDigestAuth(AuthBase):
|
||||||
# XXX not implemented yet
|
# XXX not implemented yet
|
||||||
entdig = None
|
entdig = None
|
||||||
p_parsed = urlparse(url)
|
p_parsed = urlparse(url)
|
||||||
path = p_parsed.path
|
#: path is request-uri defined in RFC 2616 which should not be empty
|
||||||
|
path = p_parsed.path or "/"
|
||||||
if p_parsed.query:
|
if p_parsed.query:
|
||||||
path += '?' + p_parsed.query
|
path += '?' + p_parsed.query
|
||||||
|
|
||||||
|
@ -113,30 +132,32 @@ class HTTPDigestAuth(AuthBase):
|
||||||
HA1 = hash_utf8(A1)
|
HA1 = hash_utf8(A1)
|
||||||
HA2 = hash_utf8(A2)
|
HA2 = hash_utf8(A2)
|
||||||
|
|
||||||
if nonce == self.last_nonce:
|
if nonce == self._thread_local.last_nonce:
|
||||||
self.nonce_count += 1
|
self._thread_local.nonce_count += 1
|
||||||
else:
|
else:
|
||||||
self.nonce_count = 1
|
self._thread_local.nonce_count = 1
|
||||||
ncvalue = '%08x' % self.nonce_count
|
ncvalue = '%08x' % self._thread_local.nonce_count
|
||||||
s = str(self.nonce_count).encode('utf-8')
|
s = str(self._thread_local.nonce_count).encode('utf-8')
|
||||||
s += nonce.encode('utf-8')
|
s += nonce.encode('utf-8')
|
||||||
s += time.ctime().encode('utf-8')
|
s += time.ctime().encode('utf-8')
|
||||||
s += os.urandom(8)
|
s += os.urandom(8)
|
||||||
|
|
||||||
cnonce = (hashlib.sha1(s).hexdigest()[:16])
|
cnonce = (hashlib.sha1(s).hexdigest()[:16])
|
||||||
noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, HA2)
|
|
||||||
if _algorithm == 'MD5-SESS':
|
if _algorithm == 'MD5-SESS':
|
||||||
HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce))
|
HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce))
|
||||||
|
|
||||||
if qop is None:
|
if not qop:
|
||||||
respdig = KD(HA1, "%s:%s" % (nonce, HA2))
|
respdig = KD(HA1, "%s:%s" % (nonce, HA2))
|
||||||
elif qop == 'auth' or 'auth' in qop.split(','):
|
elif qop == 'auth' or 'auth' in qop.split(','):
|
||||||
|
noncebit = "%s:%s:%s:%s:%s" % (
|
||||||
|
nonce, ncvalue, cnonce, 'auth', HA2
|
||||||
|
)
|
||||||
respdig = KD(HA1, noncebit)
|
respdig = KD(HA1, noncebit)
|
||||||
else:
|
else:
|
||||||
# XXX handle auth-int.
|
# XXX handle auth-int.
|
||||||
return None
|
return None
|
||||||
|
|
||||||
self.last_nonce = nonce
|
self._thread_local.last_nonce = nonce
|
||||||
|
|
||||||
# XXX should the partial digests be encoded too?
|
# XXX should the partial digests be encoded too?
|
||||||
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
|
base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \
|
||||||
|
@ -155,28 +176,27 @@ class HTTPDigestAuth(AuthBase):
|
||||||
def handle_redirect(self, r, **kwargs):
|
def handle_redirect(self, r, **kwargs):
|
||||||
"""Reset num_401_calls counter on redirects."""
|
"""Reset num_401_calls counter on redirects."""
|
||||||
if r.is_redirect:
|
if r.is_redirect:
|
||||||
self.num_401_calls = 1
|
self._thread_local.num_401_calls = 1
|
||||||
|
|
||||||
def handle_401(self, r, **kwargs):
|
def handle_401(self, r, **kwargs):
|
||||||
"""Takes the given response and tries digest-auth, if needed."""
|
"""Takes the given response and tries digest-auth, if needed."""
|
||||||
|
|
||||||
if self.pos is not None:
|
if self._thread_local.pos is not None:
|
||||||
# Rewind the file position indicator of the body to where
|
# Rewind the file position indicator of the body to where
|
||||||
# it was to resend the request.
|
# it was to resend the request.
|
||||||
r.request.body.seek(self.pos)
|
r.request.body.seek(self._thread_local.pos)
|
||||||
num_401_calls = getattr(self, 'num_401_calls', 1)
|
|
||||||
s_auth = r.headers.get('www-authenticate', '')
|
s_auth = r.headers.get('www-authenticate', '')
|
||||||
|
|
||||||
if 'digest' in s_auth.lower() and num_401_calls < 2:
|
if 'digest' in s_auth.lower() and self._thread_local.num_401_calls < 2:
|
||||||
|
|
||||||
self.num_401_calls += 1
|
self._thread_local.num_401_calls += 1
|
||||||
pat = re.compile(r'digest ', flags=re.IGNORECASE)
|
pat = re.compile(r'digest ', flags=re.IGNORECASE)
|
||||||
self.chal = parse_dict_header(pat.sub('', s_auth, count=1))
|
self._thread_local.chal = parse_dict_header(pat.sub('', s_auth, count=1))
|
||||||
|
|
||||||
# Consume content and release the original connection
|
# Consume content and release the original connection
|
||||||
# to allow our new request to reuse the same one.
|
# to allow our new request to reuse the same one.
|
||||||
r.content
|
r.content
|
||||||
r.raw.release_conn()
|
r.close()
|
||||||
prep = r.request.copy()
|
prep = r.request.copy()
|
||||||
extract_cookies_to_jar(prep._cookies, r.request, r.raw)
|
extract_cookies_to_jar(prep._cookies, r.request, r.raw)
|
||||||
prep.prepare_cookies(prep._cookies)
|
prep.prepare_cookies(prep._cookies)
|
||||||
|
@ -189,21 +209,34 @@ class HTTPDigestAuth(AuthBase):
|
||||||
|
|
||||||
return _r
|
return _r
|
||||||
|
|
||||||
self.num_401_calls = 1
|
self._thread_local.num_401_calls = 1
|
||||||
return r
|
return r
|
||||||
|
|
||||||
def __call__(self, r):
|
def __call__(self, r):
|
||||||
|
# Initialize per-thread state, if needed
|
||||||
|
self.init_per_thread_state()
|
||||||
# If we have a saved nonce, skip the 401
|
# If we have a saved nonce, skip the 401
|
||||||
if self.last_nonce:
|
if self._thread_local.last_nonce:
|
||||||
r.headers['Authorization'] = self.build_digest_header(r.method, r.url)
|
r.headers['Authorization'] = self.build_digest_header(r.method, r.url)
|
||||||
try:
|
try:
|
||||||
self.pos = r.body.tell()
|
self._thread_local.pos = r.body.tell()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
# In the case of HTTPDigestAuth being reused and the body of
|
# In the case of HTTPDigestAuth being reused and the body of
|
||||||
# the previous request was a file-like object, pos has the
|
# the previous request was a file-like object, pos has the
|
||||||
# file position of the previous body. Ensure it's set to
|
# file position of the previous body. Ensure it's set to
|
||||||
# None.
|
# None.
|
||||||
self.pos = None
|
self._thread_local.pos = None
|
||||||
r.register_hook('response', self.handle_401)
|
r.register_hook('response', self.handle_401)
|
||||||
r.register_hook('response', self.handle_redirect)
|
r.register_hook('response', self.handle_redirect)
|
||||||
|
self._thread_local.num_401_calls = 1
|
||||||
|
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return all([
|
||||||
|
self.username == getattr(other, 'username', None),
|
||||||
|
self.password == getattr(other, 'password', None)
|
||||||
|
])
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self == other
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -21,58 +21,6 @@ is_py2 = (_ver[0] == 2)
|
||||||
#: Python 3.x?
|
#: Python 3.x?
|
||||||
is_py3 = (_ver[0] == 3)
|
is_py3 = (_ver[0] == 3)
|
||||||
|
|
||||||
#: Python 3.0.x
|
|
||||||
is_py30 = (is_py3 and _ver[1] == 0)
|
|
||||||
|
|
||||||
#: Python 3.1.x
|
|
||||||
is_py31 = (is_py3 and _ver[1] == 1)
|
|
||||||
|
|
||||||
#: Python 3.2.x
|
|
||||||
is_py32 = (is_py3 and _ver[1] == 2)
|
|
||||||
|
|
||||||
#: Python 3.3.x
|
|
||||||
is_py33 = (is_py3 and _ver[1] == 3)
|
|
||||||
|
|
||||||
#: Python 3.4.x
|
|
||||||
is_py34 = (is_py3 and _ver[1] == 4)
|
|
||||||
|
|
||||||
#: Python 2.7.x
|
|
||||||
is_py27 = (is_py2 and _ver[1] == 7)
|
|
||||||
|
|
||||||
#: Python 2.6.x
|
|
||||||
is_py26 = (is_py2 and _ver[1] == 6)
|
|
||||||
|
|
||||||
#: Python 2.5.x
|
|
||||||
is_py25 = (is_py2 and _ver[1] == 5)
|
|
||||||
|
|
||||||
#: Python 2.4.x
|
|
||||||
is_py24 = (is_py2 and _ver[1] == 4) # I'm assuming this is not by choice.
|
|
||||||
|
|
||||||
|
|
||||||
# ---------
|
|
||||||
# Platforms
|
|
||||||
# ---------
|
|
||||||
|
|
||||||
|
|
||||||
# Syntax sugar.
|
|
||||||
_ver = sys.version.lower()
|
|
||||||
|
|
||||||
is_pypy = ('pypy' in _ver)
|
|
||||||
is_jython = ('jython' in _ver)
|
|
||||||
is_ironpython = ('iron' in _ver)
|
|
||||||
|
|
||||||
# Assume CPython, if nothing else.
|
|
||||||
is_cpython = not any((is_pypy, is_jython, is_ironpython))
|
|
||||||
|
|
||||||
# Windows-based system.
|
|
||||||
is_windows = 'win32' in str(sys.platform).lower()
|
|
||||||
|
|
||||||
# Standard Linux 2+ system.
|
|
||||||
is_linux = ('linux' in str(sys.platform).lower())
|
|
||||||
is_osx = ('darwin' in str(sys.platform).lower())
|
|
||||||
is_hpux = ('hpux' in str(sys.platform).lower()) # Complete guess.
|
|
||||||
is_solaris = ('solar==' in str(sys.platform).lower()) # Complete guess.
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import simplejson as json
|
import simplejson as json
|
||||||
except (ImportError, SyntaxError):
|
except (ImportError, SyntaxError):
|
||||||
|
@ -99,7 +47,6 @@ if is_py2:
|
||||||
basestring = basestring
|
basestring = basestring
|
||||||
numeric_types = (int, long, float)
|
numeric_types = (int, long, float)
|
||||||
|
|
||||||
|
|
||||||
elif is_py3:
|
elif is_py3:
|
||||||
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
|
from urllib.parse import urlparse, urlunparse, urljoin, urlsplit, urlencode, quote, unquote, quote_plus, unquote_plus, urldefrag
|
||||||
from urllib.request import parse_http_list, getproxies, proxy_bypass
|
from urllib.request import parse_http_list, getproxies, proxy_bypass
|
||||||
|
|
|
@ -6,7 +6,9 @@ Compatibility code to be able to use `cookielib.CookieJar` with requests.
|
||||||
requests.utils imports from here, so be careful with imports.
|
requests.utils imports from here, so be careful with imports.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import copy
|
||||||
import time
|
import time
|
||||||
|
import calendar
|
||||||
import collections
|
import collections
|
||||||
from .compat import cookielib, urlparse, urlunparse, Morsel
|
from .compat import cookielib, urlparse, urlunparse, Morsel
|
||||||
|
|
||||||
|
@ -142,10 +144,13 @@ def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
|
||||||
"""
|
"""
|
||||||
clearables = []
|
clearables = []
|
||||||
for cookie in cookiejar:
|
for cookie in cookiejar:
|
||||||
if cookie.name == name:
|
if cookie.name != name:
|
||||||
if domain is None or domain == cookie.domain:
|
continue
|
||||||
if path is None or path == cookie.path:
|
if domain is not None and domain != cookie.domain:
|
||||||
clearables.append((cookie.domain, cookie.path, cookie.name))
|
continue
|
||||||
|
if path is not None and path != cookie.path:
|
||||||
|
continue
|
||||||
|
clearables.append((cookie.domain, cookie.path, cookie.name))
|
||||||
|
|
||||||
for domain, path, name in clearables:
|
for domain, path, name in clearables:
|
||||||
cookiejar.clear(domain, path, name)
|
cookiejar.clear(domain, path, name)
|
||||||
|
@ -157,26 +162,28 @@ class CookieConflictError(RuntimeError):
|
||||||
|
|
||||||
|
|
||||||
class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||||
"""Compatibility class; is a cookielib.CookieJar, but exposes a dict interface.
|
"""Compatibility class; is a cookielib.CookieJar, but exposes a dict
|
||||||
|
interface.
|
||||||
|
|
||||||
This is the CookieJar we create by default for requests and sessions that
|
This is the CookieJar we create by default for requests and sessions that
|
||||||
don't specify one, since some clients may expect response.cookies and
|
don't specify one, since some clients may expect response.cookies and
|
||||||
session.cookies to support dict operations.
|
session.cookies to support dict operations.
|
||||||
|
|
||||||
Don't use the dict interface internally; it's just for compatibility with
|
Requests does not use the dict interface internally; it's just for
|
||||||
with external client code. All `requests` code should work out of the box
|
compatibility with external client code. All requests code should work
|
||||||
with externally provided instances of CookieJar, e.g., LWPCookieJar and
|
out of the box with externally provided instances of ``CookieJar``, e.g.
|
||||||
FileCookieJar.
|
``LWPCookieJar`` and ``FileCookieJar``.
|
||||||
|
|
||||||
Caution: dictionary operations that are normally O(1) may be O(n).
|
|
||||||
|
|
||||||
Unlike a regular CookieJar, this class is pickleable.
|
Unlike a regular CookieJar, this class is pickleable.
|
||||||
"""
|
|
||||||
|
|
||||||
|
.. warning:: dictionary operations that are normally O(1) may be O(n).
|
||||||
|
"""
|
||||||
def get(self, name, default=None, domain=None, path=None):
|
def get(self, name, default=None, domain=None, path=None):
|
||||||
"""Dict-like get() that also supports optional domain and path args in
|
"""Dict-like get() that also supports optional domain and path args in
|
||||||
order to resolve naming collisions from using one cookie jar over
|
order to resolve naming collisions from using one cookie jar over
|
||||||
multiple domains. Caution: operation is O(n), not O(1)."""
|
multiple domains.
|
||||||
|
|
||||||
|
.. warning:: operation is O(n), not O(1)."""
|
||||||
try:
|
try:
|
||||||
return self._find_no_duplicates(name, domain, path)
|
return self._find_no_duplicates(name, domain, path)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -199,37 +206,38 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||||
return c
|
return c
|
||||||
|
|
||||||
def iterkeys(self):
|
def iterkeys(self):
|
||||||
"""Dict-like iterkeys() that returns an iterator of names of cookies from the jar.
|
"""Dict-like iterkeys() that returns an iterator of names of cookies
|
||||||
See itervalues() and iteritems()."""
|
from the jar. See itervalues() and iteritems()."""
|
||||||
for cookie in iter(self):
|
for cookie in iter(self):
|
||||||
yield cookie.name
|
yield cookie.name
|
||||||
|
|
||||||
def keys(self):
|
def keys(self):
|
||||||
"""Dict-like keys() that returns a list of names of cookies from the jar.
|
"""Dict-like keys() that returns a list of names of cookies from the
|
||||||
See values() and items()."""
|
jar. See values() and items()."""
|
||||||
return list(self.iterkeys())
|
return list(self.iterkeys())
|
||||||
|
|
||||||
def itervalues(self):
|
def itervalues(self):
|
||||||
"""Dict-like itervalues() that returns an iterator of values of cookies from the jar.
|
"""Dict-like itervalues() that returns an iterator of values of cookies
|
||||||
See iterkeys() and iteritems()."""
|
from the jar. See iterkeys() and iteritems()."""
|
||||||
for cookie in iter(self):
|
for cookie in iter(self):
|
||||||
yield cookie.value
|
yield cookie.value
|
||||||
|
|
||||||
def values(self):
|
def values(self):
|
||||||
"""Dict-like values() that returns a list of values of cookies from the jar.
|
"""Dict-like values() that returns a list of values of cookies from the
|
||||||
See keys() and items()."""
|
jar. See keys() and items()."""
|
||||||
return list(self.itervalues())
|
return list(self.itervalues())
|
||||||
|
|
||||||
def iteritems(self):
|
def iteritems(self):
|
||||||
"""Dict-like iteritems() that returns an iterator of name-value tuples from the jar.
|
"""Dict-like iteritems() that returns an iterator of name-value tuples
|
||||||
See iterkeys() and itervalues()."""
|
from the jar. See iterkeys() and itervalues()."""
|
||||||
for cookie in iter(self):
|
for cookie in iter(self):
|
||||||
yield cookie.name, cookie.value
|
yield cookie.name, cookie.value
|
||||||
|
|
||||||
def items(self):
|
def items(self):
|
||||||
"""Dict-like items() that returns a list of name-value tuples from the jar.
|
"""Dict-like items() that returns a list of name-value tuples from the
|
||||||
See keys() and values(). Allows client-code to call "dict(RequestsCookieJar)
|
jar. See keys() and values(). Allows client-code to call
|
||||||
and get a vanilla python dict of key value pairs."""
|
``dict(RequestsCookieJar)`` and get a vanilla python dict of key value
|
||||||
|
pairs."""
|
||||||
return list(self.iteritems())
|
return list(self.iteritems())
|
||||||
|
|
||||||
def list_domains(self):
|
def list_domains(self):
|
||||||
|
@ -259,8 +267,9 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||||
return False # there is only one domain in jar
|
return False # there is only one domain in jar
|
||||||
|
|
||||||
def get_dict(self, domain=None, path=None):
|
def get_dict(self, domain=None, path=None):
|
||||||
"""Takes as an argument an optional domain and path and returns a plain old
|
"""Takes as an argument an optional domain and path and returns a plain
|
||||||
Python dict of name-value pairs of cookies that meet the requirements."""
|
old Python dict of name-value pairs of cookies that meet the
|
||||||
|
requirements."""
|
||||||
dictionary = {}
|
dictionary = {}
|
||||||
for cookie in iter(self):
|
for cookie in iter(self):
|
||||||
if (domain is None or cookie.domain == domain) and (path is None
|
if (domain is None or cookie.domain == domain) and (path is None
|
||||||
|
@ -268,22 +277,31 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||||
dictionary[cookie.name] = cookie.value
|
dictionary[cookie.name] = cookie.value
|
||||||
return dictionary
|
return dictionary
|
||||||
|
|
||||||
|
def __contains__(self, name):
|
||||||
|
try:
|
||||||
|
return super(RequestsCookieJar, self).__contains__(name)
|
||||||
|
except CookieConflictError:
|
||||||
|
return True
|
||||||
|
|
||||||
def __getitem__(self, name):
|
def __getitem__(self, name):
|
||||||
"""Dict-like __getitem__() for compatibility with client code. Throws exception
|
"""Dict-like __getitem__() for compatibility with client code. Throws
|
||||||
if there are more than one cookie with name. In that case, use the more
|
exception if there are more than one cookie with name. In that case,
|
||||||
explicit get() method instead. Caution: operation is O(n), not O(1)."""
|
use the more explicit get() method instead.
|
||||||
|
|
||||||
|
.. warning:: operation is O(n), not O(1)."""
|
||||||
|
|
||||||
return self._find_no_duplicates(name)
|
return self._find_no_duplicates(name)
|
||||||
|
|
||||||
def __setitem__(self, name, value):
|
def __setitem__(self, name, value):
|
||||||
"""Dict-like __setitem__ for compatibility with client code. Throws exception
|
"""Dict-like __setitem__ for compatibility with client code. Throws
|
||||||
if there is already a cookie of that name in the jar. In that case, use the more
|
exception if there is already a cookie of that name in the jar. In that
|
||||||
explicit set() method instead."""
|
case, use the more explicit set() method instead."""
|
||||||
|
|
||||||
self.set(name, value)
|
self.set(name, value)
|
||||||
|
|
||||||
def __delitem__(self, name):
|
def __delitem__(self, name):
|
||||||
"""Deletes a cookie given a name. Wraps cookielib.CookieJar's remove_cookie_by_name()."""
|
"""Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s
|
||||||
|
``remove_cookie_by_name()``."""
|
||||||
remove_cookie_by_name(self, name)
|
remove_cookie_by_name(self, name)
|
||||||
|
|
||||||
def set_cookie(self, cookie, *args, **kwargs):
|
def set_cookie(self, cookie, *args, **kwargs):
|
||||||
|
@ -295,15 +313,16 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||||
"""Updates this jar with cookies from another CookieJar or dict-like"""
|
"""Updates this jar with cookies from another CookieJar or dict-like"""
|
||||||
if isinstance(other, cookielib.CookieJar):
|
if isinstance(other, cookielib.CookieJar):
|
||||||
for cookie in other:
|
for cookie in other:
|
||||||
self.set_cookie(cookie)
|
self.set_cookie(copy.copy(cookie))
|
||||||
else:
|
else:
|
||||||
super(RequestsCookieJar, self).update(other)
|
super(RequestsCookieJar, self).update(other)
|
||||||
|
|
||||||
def _find(self, name, domain=None, path=None):
|
def _find(self, name, domain=None, path=None):
|
||||||
"""Requests uses this method internally to get cookie values. Takes as args name
|
"""Requests uses this method internally to get cookie values. Takes as
|
||||||
and optional domain and path. Returns a cookie.value. If there are conflicting cookies,
|
args name and optional domain and path. Returns a cookie.value. If
|
||||||
_find arbitrarily chooses one. See _find_no_duplicates if you want an exception thrown
|
there are conflicting cookies, _find arbitrarily chooses one. See
|
||||||
if there are conflicting cookies."""
|
_find_no_duplicates if you want an exception thrown if there are
|
||||||
|
conflicting cookies."""
|
||||||
for cookie in iter(self):
|
for cookie in iter(self):
|
||||||
if cookie.name == name:
|
if cookie.name == name:
|
||||||
if domain is None or cookie.domain == domain:
|
if domain is None or cookie.domain == domain:
|
||||||
|
@ -313,10 +332,11 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||||
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
|
raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path))
|
||||||
|
|
||||||
def _find_no_duplicates(self, name, domain=None, path=None):
|
def _find_no_duplicates(self, name, domain=None, path=None):
|
||||||
"""__get_item__ and get call _find_no_duplicates -- never used in Requests internally.
|
"""Both ``__get_item__`` and ``get`` call this function: it's never
|
||||||
Takes as args name and optional domain and path. Returns a cookie.value.
|
used elsewhere in Requests. Takes as args name and optional domain and
|
||||||
Throws KeyError if cookie is not found and CookieConflictError if there are
|
path. Returns a cookie.value. Throws KeyError if cookie is not found
|
||||||
multiple cookies that match name and optionally domain and path."""
|
and CookieConflictError if there are multiple cookies that match name
|
||||||
|
and optionally domain and path."""
|
||||||
toReturn = None
|
toReturn = None
|
||||||
for cookie in iter(self):
|
for cookie in iter(self):
|
||||||
if cookie.name == name:
|
if cookie.name == name:
|
||||||
|
@ -350,6 +370,21 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping):
|
||||||
return new_cj
|
return new_cj
|
||||||
|
|
||||||
|
|
||||||
|
def _copy_cookie_jar(jar):
|
||||||
|
if jar is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if hasattr(jar, 'copy'):
|
||||||
|
# We're dealing with an instance of RequestsCookieJar
|
||||||
|
return jar.copy()
|
||||||
|
# We're dealing with a generic CookieJar instance
|
||||||
|
new_jar = copy.copy(jar)
|
||||||
|
new_jar.clear()
|
||||||
|
for cookie in jar:
|
||||||
|
new_jar.set_cookie(copy.copy(cookie))
|
||||||
|
return new_jar
|
||||||
|
|
||||||
|
|
||||||
def create_cookie(name, value, **kwargs):
|
def create_cookie(name, value, **kwargs):
|
||||||
"""Make a cookie from underspecified parameters.
|
"""Make a cookie from underspecified parameters.
|
||||||
|
|
||||||
|
@ -390,11 +425,15 @@ def morsel_to_cookie(morsel):
|
||||||
|
|
||||||
expires = None
|
expires = None
|
||||||
if morsel['max-age']:
|
if morsel['max-age']:
|
||||||
expires = time.time() + morsel['max-age']
|
try:
|
||||||
|
expires = int(time.time() + int(morsel['max-age']))
|
||||||
|
except ValueError:
|
||||||
|
raise TypeError('max-age: %s must be integer' % morsel['max-age'])
|
||||||
elif morsel['expires']:
|
elif morsel['expires']:
|
||||||
time_template = '%a, %d-%b-%Y %H:%M:%S GMT'
|
time_template = '%a, %d-%b-%Y %H:%M:%S GMT'
|
||||||
expires = time.mktime(
|
expires = calendar.timegm(
|
||||||
time.strptime(morsel['expires'], time_template)) - time.timezone
|
time.strptime(morsel['expires'], time_template)
|
||||||
|
)
|
||||||
return create_cookie(
|
return create_cookie(
|
||||||
comment=morsel['comment'],
|
comment=morsel['comment'],
|
||||||
comment_url=bool(morsel['comment']),
|
comment_url=bool(morsel['comment']),
|
||||||
|
|
|
@ -97,3 +97,18 @@ class StreamConsumedError(RequestException, TypeError):
|
||||||
|
|
||||||
class RetryError(RequestException):
|
class RetryError(RequestException):
|
||||||
"""Custom retries logic failed"""
|
"""Custom retries logic failed"""
|
||||||
|
|
||||||
|
|
||||||
|
# Warnings
|
||||||
|
|
||||||
|
|
||||||
|
class RequestsWarning(Warning):
|
||||||
|
"""Base warning for Requests."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FileModeWarning(RequestsWarning, DeprecationWarning):
|
||||||
|
"""
|
||||||
|
A file was opened in text mode, but Requests determined its binary length.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
|
@ -12,34 +12,23 @@ Available hooks:
|
||||||
The response generated from a Request.
|
The response generated from a Request.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
HOOKS = ['response']
|
HOOKS = ['response']
|
||||||
|
|
||||||
|
|
||||||
def default_hooks():
|
def default_hooks():
|
||||||
hooks = {}
|
return dict((event, []) for event in HOOKS)
|
||||||
for event in HOOKS:
|
|
||||||
hooks[event] = []
|
|
||||||
return hooks
|
|
||||||
|
|
||||||
# TODO: response is the only one
|
# TODO: response is the only one
|
||||||
|
|
||||||
|
|
||||||
def dispatch_hook(key, hooks, hook_data, **kwargs):
|
def dispatch_hook(key, hooks, hook_data, **kwargs):
|
||||||
"""Dispatches a hook dictionary on a given piece of data."""
|
"""Dispatches a hook dictionary on a given piece of data."""
|
||||||
|
|
||||||
hooks = hooks or dict()
|
hooks = hooks or dict()
|
||||||
|
hooks = hooks.get(key)
|
||||||
if key in hooks:
|
if hooks:
|
||||||
hooks = hooks.get(key)
|
|
||||||
|
|
||||||
if hasattr(hooks, '__call__'):
|
if hasattr(hooks, '__call__'):
|
||||||
hooks = [hooks]
|
hooks = [hooks]
|
||||||
|
|
||||||
for hook in hooks:
|
for hook in hooks:
|
||||||
_hook_data = hook(hook_data, **kwargs)
|
_hook_data = hook(hook_data, **kwargs)
|
||||||
if _hook_data is not None:
|
if _hook_data is not None:
|
||||||
hook_data = _hook_data
|
hook_data = _hook_data
|
||||||
|
|
||||||
return hook_data
|
return hook_data
|
||||||
|
|
|
@ -15,7 +15,7 @@ from .hooks import default_hooks
|
||||||
from .structures import CaseInsensitiveDict
|
from .structures import CaseInsensitiveDict
|
||||||
|
|
||||||
from .auth import HTTPBasicAuth
|
from .auth import HTTPBasicAuth
|
||||||
from .cookies import cookiejar_from_dict, get_cookie_header
|
from .cookies import cookiejar_from_dict, get_cookie_header, _copy_cookie_jar
|
||||||
from .packages.urllib3.fields import RequestField
|
from .packages.urllib3.fields import RequestField
|
||||||
from .packages.urllib3.filepost import encode_multipart_formdata
|
from .packages.urllib3.filepost import encode_multipart_formdata
|
||||||
from .packages.urllib3.util import parse_url
|
from .packages.urllib3.util import parse_url
|
||||||
|
@ -30,7 +30,8 @@ from .utils import (
|
||||||
iter_slices, guess_json_utf, super_len, to_native_string)
|
iter_slices, guess_json_utf, super_len, to_native_string)
|
||||||
from .compat import (
|
from .compat import (
|
||||||
cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO,
|
cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO,
|
||||||
is_py2, chardet, json, builtin_str, basestring)
|
is_py2, chardet, builtin_str, basestring)
|
||||||
|
from .compat import json as complexjson
|
||||||
from .status_codes import codes
|
from .status_codes import codes
|
||||||
|
|
||||||
#: The set of HTTP status codes that indicate an automatically
|
#: The set of HTTP status codes that indicate an automatically
|
||||||
|
@ -42,12 +43,11 @@ REDIRECT_STATI = (
|
||||||
codes.temporary_redirect, # 307
|
codes.temporary_redirect, # 307
|
||||||
codes.permanent_redirect, # 308
|
codes.permanent_redirect, # 308
|
||||||
)
|
)
|
||||||
|
|
||||||
DEFAULT_REDIRECT_LIMIT = 30
|
DEFAULT_REDIRECT_LIMIT = 30
|
||||||
CONTENT_CHUNK_SIZE = 10 * 1024
|
CONTENT_CHUNK_SIZE = 10 * 1024
|
||||||
ITER_CHUNK_SIZE = 512
|
ITER_CHUNK_SIZE = 512
|
||||||
|
|
||||||
json_dumps = json.dumps
|
|
||||||
|
|
||||||
|
|
||||||
class RequestEncodingMixin(object):
|
class RequestEncodingMixin(object):
|
||||||
@property
|
@property
|
||||||
|
@ -103,8 +103,10 @@ class RequestEncodingMixin(object):
|
||||||
"""Build the body for a multipart/form-data request.
|
"""Build the body for a multipart/form-data request.
|
||||||
|
|
||||||
Will successfully encode files when passed as a dict or a list of
|
Will successfully encode files when passed as a dict or a list of
|
||||||
2-tuples. Order is retained if data is a list of 2-tuples but arbitrary
|
tuples. Order is retained if data is a list of tuples but arbitrary
|
||||||
if parameters are supplied as a dict.
|
if parameters are supplied as a dict.
|
||||||
|
The tuples may be 2-tuples (filename, fileobj), 3-tuples (filename, fileobj, contentype)
|
||||||
|
or 4-tuples (filename, fileobj, contentype, custom_headers).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if (not files):
|
if (not files):
|
||||||
|
@ -143,13 +145,13 @@ class RequestEncodingMixin(object):
|
||||||
else:
|
else:
|
||||||
fn = guess_filename(v) or k
|
fn = guess_filename(v) or k
|
||||||
fp = v
|
fp = v
|
||||||
if isinstance(fp, str):
|
|
||||||
fp = StringIO(fp)
|
|
||||||
if isinstance(fp, bytes):
|
|
||||||
fp = BytesIO(fp)
|
|
||||||
|
|
||||||
rf = RequestField(name=k, data=fp.read(),
|
if isinstance(fp, (str, bytes, bytearray)):
|
||||||
filename=fn, headers=fh)
|
fdata = fp
|
||||||
|
else:
|
||||||
|
fdata = fp.read()
|
||||||
|
|
||||||
|
rf = RequestField(name=k, data=fdata, filename=fn, headers=fh)
|
||||||
rf.make_multipart(content_type=ft)
|
rf.make_multipart(content_type=ft)
|
||||||
new_fields.append(rf)
|
new_fields.append(rf)
|
||||||
|
|
||||||
|
@ -192,7 +194,7 @@ class Request(RequestHooksMixin):
|
||||||
:param headers: dictionary of headers to send.
|
:param headers: dictionary of headers to send.
|
||||||
:param files: dictionary of {filename: fileobject} files to multipart upload.
|
:param files: dictionary of {filename: fileobject} files to multipart upload.
|
||||||
:param data: the body to attach to the request. If a dictionary is provided, form-encoding will take place.
|
:param data: the body to attach to the request. If a dictionary is provided, form-encoding will take place.
|
||||||
:param json: json for the body to attach to the request (if data is not specified).
|
:param json: json for the body to attach to the request (if files or data is not specified).
|
||||||
:param params: dictionary of URL parameters to append to the URL.
|
:param params: dictionary of URL parameters to append to the URL.
|
||||||
:param auth: Auth handler or (user, pass) tuple.
|
:param auth: Auth handler or (user, pass) tuple.
|
||||||
:param cookies: dictionary or CookieJar of cookies to attach to this request.
|
:param cookies: dictionary or CookieJar of cookies to attach to this request.
|
||||||
|
@ -206,17 +208,8 @@ class Request(RequestHooksMixin):
|
||||||
<PreparedRequest [GET]>
|
<PreparedRequest [GET]>
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self,
|
def __init__(self, method=None, url=None, headers=None, files=None,
|
||||||
method=None,
|
data=None, params=None, auth=None, cookies=None, hooks=None, json=None):
|
||||||
url=None,
|
|
||||||
headers=None,
|
|
||||||
files=None,
|
|
||||||
data=None,
|
|
||||||
params=None,
|
|
||||||
auth=None,
|
|
||||||
cookies=None,
|
|
||||||
hooks=None,
|
|
||||||
json=None):
|
|
||||||
|
|
||||||
# Default empty dicts for dict params.
|
# Default empty dicts for dict params.
|
||||||
data = [] if data is None else data
|
data = [] if data is None else data
|
||||||
|
@ -295,8 +288,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||||
self.hooks = default_hooks()
|
self.hooks = default_hooks()
|
||||||
|
|
||||||
def prepare(self, method=None, url=None, headers=None, files=None,
|
def prepare(self, method=None, url=None, headers=None, files=None,
|
||||||
data=None, params=None, auth=None, cookies=None, hooks=None,
|
data=None, params=None, auth=None, cookies=None, hooks=None, json=None):
|
||||||
json=None):
|
|
||||||
"""Prepares the entire request with the given parameters."""
|
"""Prepares the entire request with the given parameters."""
|
||||||
|
|
||||||
self.prepare_method(method)
|
self.prepare_method(method)
|
||||||
|
@ -305,6 +297,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||||
self.prepare_cookies(cookies)
|
self.prepare_cookies(cookies)
|
||||||
self.prepare_body(data, files, json)
|
self.prepare_body(data, files, json)
|
||||||
self.prepare_auth(auth, url)
|
self.prepare_auth(auth, url)
|
||||||
|
|
||||||
# Note that prepare_auth must be last to enable authentication schemes
|
# Note that prepare_auth must be last to enable authentication schemes
|
||||||
# such as OAuth to work on a fully prepared request.
|
# such as OAuth to work on a fully prepared request.
|
||||||
|
|
||||||
|
@ -319,7 +312,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||||
p.method = self.method
|
p.method = self.method
|
||||||
p.url = self.url
|
p.url = self.url
|
||||||
p.headers = self.headers.copy() if self.headers is not None else None
|
p.headers = self.headers.copy() if self.headers is not None else None
|
||||||
p._cookies = self._cookies.copy() if self._cookies is not None else None
|
p._cookies = _copy_cookie_jar(self._cookies)
|
||||||
p.body = self.body
|
p.body = self.body
|
||||||
p.hooks = self.hooks
|
p.hooks = self.hooks
|
||||||
return p
|
return p
|
||||||
|
@ -328,12 +321,12 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||||
"""Prepares the given HTTP method."""
|
"""Prepares the given HTTP method."""
|
||||||
self.method = method
|
self.method = method
|
||||||
if self.method is not None:
|
if self.method is not None:
|
||||||
self.method = self.method.upper()
|
self.method = to_native_string(self.method.upper())
|
||||||
|
|
||||||
def prepare_url(self, url, params):
|
def prepare_url(self, url, params):
|
||||||
"""Prepares the given HTTP URL."""
|
"""Prepares the given HTTP URL."""
|
||||||
#: Accept objects that have string representations.
|
#: Accept objects that have string representations.
|
||||||
#: We're unable to blindy call unicode/str functions
|
#: We're unable to blindly call unicode/str functions
|
||||||
#: as this will include the bytestring indicator (b'')
|
#: as this will include the bytestring indicator (b'')
|
||||||
#: on python 3.x.
|
#: on python 3.x.
|
||||||
#: https://github.com/kennethreitz/requests/pull/2238
|
#: https://github.com/kennethreitz/requests/pull/2238
|
||||||
|
@ -356,8 +349,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||||
raise InvalidURL(*e.args)
|
raise InvalidURL(*e.args)
|
||||||
|
|
||||||
if not scheme:
|
if not scheme:
|
||||||
raise MissingSchema("Invalid URL {0!r}: No schema supplied. "
|
error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?")
|
||||||
"Perhaps you meant http://{0}?".format(url))
|
error = error.format(to_native_string(url, 'utf8'))
|
||||||
|
|
||||||
|
raise MissingSchema(error)
|
||||||
|
|
||||||
if not host:
|
if not host:
|
||||||
raise InvalidURL("Invalid URL %r: No host supplied" % url)
|
raise InvalidURL("Invalid URL %r: No host supplied" % url)
|
||||||
|
@ -392,6 +387,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||||
if isinstance(fragment, str):
|
if isinstance(fragment, str):
|
||||||
fragment = fragment.encode('utf-8')
|
fragment = fragment.encode('utf-8')
|
||||||
|
|
||||||
|
if isinstance(params, (str, bytes)):
|
||||||
|
params = to_native_string(params)
|
||||||
|
|
||||||
enc_params = self._encode_params(params)
|
enc_params = self._encode_params(params)
|
||||||
if enc_params:
|
if enc_params:
|
||||||
if query:
|
if query:
|
||||||
|
@ -421,9 +419,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||||
content_type = None
|
content_type = None
|
||||||
length = None
|
length = None
|
||||||
|
|
||||||
if json is not None:
|
if not data and json is not None:
|
||||||
content_type = 'application/json'
|
content_type = 'application/json'
|
||||||
body = json_dumps(json)
|
body = complexjson.dumps(json)
|
||||||
|
|
||||||
is_stream = all([
|
is_stream = all([
|
||||||
hasattr(data, '__iter__'),
|
hasattr(data, '__iter__'),
|
||||||
|
@ -441,7 +439,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||||
if files:
|
if files:
|
||||||
raise NotImplementedError('Streamed bodies and files are mutually exclusive.')
|
raise NotImplementedError('Streamed bodies and files are mutually exclusive.')
|
||||||
|
|
||||||
if length is not None:
|
if length:
|
||||||
self.headers['Content-Length'] = builtin_str(length)
|
self.headers['Content-Length'] = builtin_str(length)
|
||||||
else:
|
else:
|
||||||
self.headers['Transfer-Encoding'] = 'chunked'
|
self.headers['Transfer-Encoding'] = 'chunked'
|
||||||
|
@ -450,7 +448,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||||
if files:
|
if files:
|
||||||
(body, content_type) = self._encode_files(files, data)
|
(body, content_type) = self._encode_files(files, data)
|
||||||
else:
|
else:
|
||||||
if data and json is None:
|
if data:
|
||||||
body = self._encode_params(data)
|
body = self._encode_params(data)
|
||||||
if isinstance(data, basestring) or hasattr(data, 'read'):
|
if isinstance(data, basestring) or hasattr(data, 'read'):
|
||||||
content_type = None
|
content_type = None
|
||||||
|
@ -467,9 +465,11 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||||
|
|
||||||
def prepare_content_length(self, body):
|
def prepare_content_length(self, body):
|
||||||
if hasattr(body, 'seek') and hasattr(body, 'tell'):
|
if hasattr(body, 'seek') and hasattr(body, 'tell'):
|
||||||
|
curr_pos = body.tell()
|
||||||
body.seek(0, 2)
|
body.seek(0, 2)
|
||||||
self.headers['Content-Length'] = builtin_str(body.tell())
|
end_pos = body.tell()
|
||||||
body.seek(0, 0)
|
self.headers['Content-Length'] = builtin_str(max(0, end_pos - curr_pos))
|
||||||
|
body.seek(curr_pos, 0)
|
||||||
elif body is not None:
|
elif body is not None:
|
||||||
l = super_len(body)
|
l = super_len(body)
|
||||||
if l:
|
if l:
|
||||||
|
@ -500,7 +500,15 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||||
self.prepare_content_length(self.body)
|
self.prepare_content_length(self.body)
|
||||||
|
|
||||||
def prepare_cookies(self, cookies):
|
def prepare_cookies(self, cookies):
|
||||||
"""Prepares the given HTTP cookie data."""
|
"""Prepares the given HTTP cookie data.
|
||||||
|
|
||||||
|
This function eventually generates a ``Cookie`` header from the
|
||||||
|
given cookies using cookielib. Due to cookielib's design, the header
|
||||||
|
will not be regenerated if it already exists, meaning this function
|
||||||
|
can only be called once for the life of the
|
||||||
|
:class:`PreparedRequest <PreparedRequest>` object. Any subsequent calls
|
||||||
|
to ``prepare_cookies`` will have no actual effect, unless the "Cookie"
|
||||||
|
header is removed beforehand."""
|
||||||
|
|
||||||
if isinstance(cookies, cookielib.CookieJar):
|
if isinstance(cookies, cookielib.CookieJar):
|
||||||
self._cookies = cookies
|
self._cookies = cookies
|
||||||
|
@ -513,6 +521,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin):
|
||||||
|
|
||||||
def prepare_hooks(self, hooks):
|
def prepare_hooks(self, hooks):
|
||||||
"""Prepares the given hooks."""
|
"""Prepares the given hooks."""
|
||||||
|
# hooks can be passed as None to the prepare method and to this
|
||||||
|
# method. To prevent iterating over None, simply use an empty list
|
||||||
|
# if hooks is False-y
|
||||||
|
hooks = hooks or []
|
||||||
for event in hooks:
|
for event in hooks:
|
||||||
self.register_hook(event, hooks[event])
|
self.register_hook(event, hooks[event])
|
||||||
|
|
||||||
|
@ -523,16 +535,8 @@ class Response(object):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__attrs__ = [
|
__attrs__ = [
|
||||||
'_content',
|
'_content', 'status_code', 'headers', 'url', 'history',
|
||||||
'status_code',
|
'encoding', 'reason', 'cookies', 'elapsed', 'request'
|
||||||
'headers',
|
|
||||||
'url',
|
|
||||||
'history',
|
|
||||||
'encoding',
|
|
||||||
'reason',
|
|
||||||
'cookies',
|
|
||||||
'elapsed',
|
|
||||||
'request',
|
|
||||||
]
|
]
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -572,7 +576,11 @@ class Response(object):
|
||||||
self.cookies = cookiejar_from_dict({})
|
self.cookies = cookiejar_from_dict({})
|
||||||
|
|
||||||
#: The amount of time elapsed between sending the request
|
#: The amount of time elapsed between sending the request
|
||||||
#: and the arrival of the response (as a timedelta)
|
#: and the arrival of the response (as a timedelta).
|
||||||
|
#: This property specifically measures the time taken between sending
|
||||||
|
#: the first byte of the request and finishing parsing the headers. It
|
||||||
|
#: is therefore unaffected by consuming the response content or the
|
||||||
|
#: value of the ``stream`` keyword argument.
|
||||||
self.elapsed = datetime.timedelta(0)
|
self.elapsed = datetime.timedelta(0)
|
||||||
|
|
||||||
#: The :class:`PreparedRequest <PreparedRequest>` object to which this
|
#: The :class:`PreparedRequest <PreparedRequest>` object to which this
|
||||||
|
@ -630,7 +638,7 @@ class Response(object):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_permanent_redirect(self):
|
def is_permanent_redirect(self):
|
||||||
"""True if this Response one of the permanant versions of redirect"""
|
"""True if this Response one of the permanent versions of redirect"""
|
||||||
return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect))
|
return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect))
|
||||||
|
|
||||||
@property
|
@property
|
||||||
|
@ -648,9 +656,10 @@ class Response(object):
|
||||||
If decode_unicode is True, content will be decoded using the best
|
If decode_unicode is True, content will be decoded using the best
|
||||||
available encoding based on the response.
|
available encoding based on the response.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def generate():
|
def generate():
|
||||||
try:
|
# Special case for urllib3.
|
||||||
# Special case for urllib3.
|
if hasattr(self.raw, 'stream'):
|
||||||
try:
|
try:
|
||||||
for chunk in self.raw.stream(chunk_size, decode_content=True):
|
for chunk in self.raw.stream(chunk_size, decode_content=True):
|
||||||
yield chunk
|
yield chunk
|
||||||
|
@ -660,7 +669,7 @@ class Response(object):
|
||||||
raise ContentDecodingError(e)
|
raise ContentDecodingError(e)
|
||||||
except ReadTimeoutError as e:
|
except ReadTimeoutError as e:
|
||||||
raise ConnectionError(e)
|
raise ConnectionError(e)
|
||||||
except AttributeError:
|
else:
|
||||||
# Standard file-like object.
|
# Standard file-like object.
|
||||||
while True:
|
while True:
|
||||||
chunk = self.raw.read(chunk_size)
|
chunk = self.raw.read(chunk_size)
|
||||||
|
@ -688,6 +697,8 @@ class Response(object):
|
||||||
"""Iterates over the response data, one line at a time. When
|
"""Iterates over the response data, one line at a time. When
|
||||||
stream=True is set on the request, this avoids reading the
|
stream=True is set on the request, this avoids reading the
|
||||||
content at once into memory for large responses.
|
content at once into memory for large responses.
|
||||||
|
|
||||||
|
.. note:: This method is not reentrant safe.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pending = None
|
pending = None
|
||||||
|
@ -789,14 +800,16 @@ class Response(object):
|
||||||
encoding = guess_json_utf(self.content)
|
encoding = guess_json_utf(self.content)
|
||||||
if encoding is not None:
|
if encoding is not None:
|
||||||
try:
|
try:
|
||||||
return json.loads(self.content.decode(encoding), **kwargs)
|
return complexjson.loads(
|
||||||
|
self.content.decode(encoding), **kwargs
|
||||||
|
)
|
||||||
except UnicodeDecodeError:
|
except UnicodeDecodeError:
|
||||||
# Wrong UTF codec detected; usually because it's not UTF-8
|
# Wrong UTF codec detected; usually because it's not UTF-8
|
||||||
# but some other 8-bit codec. This is an RFC violation,
|
# but some other 8-bit codec. This is an RFC violation,
|
||||||
# and the server didn't bother to tell us what codec *was*
|
# and the server didn't bother to tell us what codec *was*
|
||||||
# used.
|
# used.
|
||||||
pass
|
pass
|
||||||
return json.loads(self.text, **kwargs)
|
return complexjson.loads(self.text, **kwargs)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def links(self):
|
def links(self):
|
||||||
|
@ -822,10 +835,10 @@ class Response(object):
|
||||||
http_error_msg = ''
|
http_error_msg = ''
|
||||||
|
|
||||||
if 400 <= self.status_code < 500:
|
if 400 <= self.status_code < 500:
|
||||||
http_error_msg = '%s Client Error: %s' % (self.status_code, self.reason)
|
http_error_msg = '%s Client Error: %s for url: %s' % (self.status_code, self.reason, self.url)
|
||||||
|
|
||||||
elif 500 <= self.status_code < 600:
|
elif 500 <= self.status_code < 600:
|
||||||
http_error_msg = '%s Server Error: %s' % (self.status_code, self.reason)
|
http_error_msg = '%s Server Error: %s for url: %s' % (self.status_code, self.reason, self.url)
|
||||||
|
|
||||||
if http_error_msg:
|
if http_error_msg:
|
||||||
raise HTTPError(http_error_msg, response=self)
|
raise HTTPError(http_error_msg, response=self)
|
||||||
|
@ -836,4 +849,7 @@ class Response(object):
|
||||||
|
|
||||||
*Note: Should not normally need to be called explicitly.*
|
*Note: Should not normally need to be called explicitly.*
|
||||||
"""
|
"""
|
||||||
|
if not self._content_consumed:
|
||||||
|
return self.raw.close()
|
||||||
|
|
||||||
return self.raw.release_conn()
|
return self.raw.release_conn()
|
||||||
|
|
11
lib/requests/packages/README.rst
Normal file
11
lib/requests/packages/README.rst
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
If you are planning to submit a pull request to requests with any changes in
|
||||||
|
this library do not go any further. These are independent libraries which we
|
||||||
|
vendor into requests. Any changes necessary to these libraries must be made in
|
||||||
|
them and submitted as separate pull requests to those libraries.
|
||||||
|
|
||||||
|
urllib3 pull requests go here: https://github.com/shazow/urllib3
|
||||||
|
|
||||||
|
chardet pull requests go here: https://github.com/chardet/chardet
|
||||||
|
|
||||||
|
See https://github.com/kennethreitz/requests/pull/1812#issuecomment-30854316
|
||||||
|
for the reasoning behind this.
|
|
@ -1,3 +1,36 @@
|
||||||
from __future__ import absolute_import
|
'''
|
||||||
|
Debian and other distributions "unbundle" requests' vendored dependencies, and
|
||||||
|
rewrite all imports to use the global versions of ``urllib3`` and ``chardet``.
|
||||||
|
The problem with this is that not only requests itself imports those
|
||||||
|
dependencies, but third-party code outside of the distros' control too.
|
||||||
|
|
||||||
from . import urllib3
|
In reaction to these problems, the distro maintainers replaced
|
||||||
|
``requests.packages`` with a magical "stub module" that imports the correct
|
||||||
|
modules. The implementations were varying in quality and all had severe
|
||||||
|
problems. For example, a symlink (or hardlink) that links the correct modules
|
||||||
|
into place introduces problems regarding object identity, since you now have
|
||||||
|
two modules in `sys.modules` with the same API, but different identities::
|
||||||
|
|
||||||
|
requests.packages.urllib3 is not urllib3
|
||||||
|
|
||||||
|
With version ``2.5.2``, requests started to maintain its own stub, so that
|
||||||
|
distro-specific breakage would be reduced to a minimum, even though the whole
|
||||||
|
issue is not requests' fault in the first place. See
|
||||||
|
https://github.com/kennethreitz/requests/pull/2375 for the corresponding pull
|
||||||
|
request.
|
||||||
|
'''
|
||||||
|
|
||||||
|
from __future__ import absolute_import
|
||||||
|
import sys
|
||||||
|
|
||||||
|
try:
|
||||||
|
from . import urllib3
|
||||||
|
except ImportError:
|
||||||
|
import urllib3
|
||||||
|
sys.modules['%s.urllib3' % __name__] = urllib3
|
||||||
|
|
||||||
|
try:
|
||||||
|
from . import chardet
|
||||||
|
except ImportError:
|
||||||
|
import chardet
|
||||||
|
sys.modules['%s.chardet' % __name__] = chardet
|
||||||
|
|
|
@ -2,10 +2,8 @@
|
||||||
urllib3 - Thread-safe connection pooling and re-using.
|
urllib3 - Thread-safe connection pooling and re-using.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
|
from __future__ import absolute_import
|
||||||
__license__ = 'MIT'
|
import warnings
|
||||||
__version__ = 'dev'
|
|
||||||
|
|
||||||
|
|
||||||
from .connectionpool import (
|
from .connectionpool import (
|
||||||
HTTPConnectionPool,
|
HTTPConnectionPool,
|
||||||
|
@ -32,8 +30,30 @@ except ImportError:
|
||||||
def emit(self, record):
|
def emit(self, record):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)'
|
||||||
|
__license__ = 'MIT'
|
||||||
|
__version__ = '1.15.1'
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'HTTPConnectionPool',
|
||||||
|
'HTTPSConnectionPool',
|
||||||
|
'PoolManager',
|
||||||
|
'ProxyManager',
|
||||||
|
'HTTPResponse',
|
||||||
|
'Retry',
|
||||||
|
'Timeout',
|
||||||
|
'add_stderr_logger',
|
||||||
|
'connection_from_url',
|
||||||
|
'disable_warnings',
|
||||||
|
'encode_multipart_formdata',
|
||||||
|
'get_host',
|
||||||
|
'make_headers',
|
||||||
|
'proxy_from_url',
|
||||||
|
)
|
||||||
|
|
||||||
logging.getLogger(__name__).addHandler(NullHandler())
|
logging.getLogger(__name__).addHandler(NullHandler())
|
||||||
|
|
||||||
|
|
||||||
def add_stderr_logger(level=logging.DEBUG):
|
def add_stderr_logger(level=logging.DEBUG):
|
||||||
"""
|
"""
|
||||||
Helper for quickly adding a StreamHandler to the logger. Useful for
|
Helper for quickly adding a StreamHandler to the logger. Useful for
|
||||||
|
@ -48,16 +68,26 @@ def add_stderr_logger(level=logging.DEBUG):
|
||||||
handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s'))
|
handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s'))
|
||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
logger.setLevel(level)
|
logger.setLevel(level)
|
||||||
logger.debug('Added a stderr logging handler to logger: %s' % __name__)
|
logger.debug('Added a stderr logging handler to logger: %s', __name__)
|
||||||
return handler
|
return handler
|
||||||
|
|
||||||
# ... Clean up.
|
# ... Clean up.
|
||||||
del NullHandler
|
del NullHandler
|
||||||
|
|
||||||
|
|
||||||
# Set security warning to only go off once by default.
|
# All warning filters *must* be appended unless you're really certain that they
|
||||||
import warnings
|
# shouldn't be: otherwise, it's very hard for users to use most Python
|
||||||
warnings.simplefilter('always', exceptions.SecurityWarning)
|
# mechanisms to silence them.
|
||||||
|
# SecurityWarning's always go off by default.
|
||||||
|
warnings.simplefilter('always', exceptions.SecurityWarning, append=True)
|
||||||
|
# SubjectAltNameWarning's should go off once per host
|
||||||
|
warnings.simplefilter('default', exceptions.SubjectAltNameWarning, append=True)
|
||||||
|
# InsecurePlatformWarning's don't vary between requests, so we keep it default.
|
||||||
|
warnings.simplefilter('default', exceptions.InsecurePlatformWarning,
|
||||||
|
append=True)
|
||||||
|
# SNIMissingWarnings should go off only once.
|
||||||
|
warnings.simplefilter('default', exceptions.SNIMissingWarning, append=True)
|
||||||
|
|
||||||
|
|
||||||
def disable_warnings(category=exceptions.HTTPWarning):
|
def disable_warnings(category=exceptions.HTTPWarning):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
from collections import Mapping, MutableMapping
|
from collections import Mapping, MutableMapping
|
||||||
try:
|
try:
|
||||||
from threading import RLock
|
from threading import RLock
|
||||||
except ImportError: # Platform-specific: No threads available
|
except ImportError: # Platform-specific: No threads available
|
||||||
class RLock:
|
class RLock:
|
||||||
def __enter__(self):
|
def __enter__(self):
|
||||||
pass
|
pass
|
||||||
|
@ -10,11 +11,11 @@ except ImportError: # Platform-specific: No threads available
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
try: # Python 2.7+
|
try: # Python 2.7+
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from .packages.ordered_dict import OrderedDict
|
from .packages.ordered_dict import OrderedDict
|
||||||
from .packages.six import iterkeys, itervalues
|
from .packages.six import iterkeys, itervalues, PY3
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict']
|
__all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict']
|
||||||
|
@ -129,25 +130,82 @@ class HTTPHeaderDict(MutableMapping):
|
||||||
'foo=bar, baz=quxx'
|
'foo=bar, baz=quxx'
|
||||||
>>> headers['Content-Length']
|
>>> headers['Content-Length']
|
||||||
'7'
|
'7'
|
||||||
|
|
||||||
If you want to access the raw headers with their original casing
|
|
||||||
for debugging purposes you can access the private ``._data`` attribute
|
|
||||||
which is a normal python ``dict`` that maps the case-insensitive key to a
|
|
||||||
list of tuples stored as (case-sensitive-original-name, value). Using the
|
|
||||||
structure from above as our example:
|
|
||||||
|
|
||||||
>>> headers._data
|
|
||||||
{'set-cookie': [('Set-Cookie', 'foo=bar'), ('set-cookie', 'baz=quxx')],
|
|
||||||
'content-length': [('content-length', '7')]}
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, headers=None, **kwargs):
|
def __init__(self, headers=None, **kwargs):
|
||||||
self._data = {}
|
super(HTTPHeaderDict, self).__init__()
|
||||||
if headers is None:
|
self._container = OrderedDict()
|
||||||
headers = {}
|
if headers is not None:
|
||||||
self.update(headers, **kwargs)
|
if isinstance(headers, HTTPHeaderDict):
|
||||||
|
self._copy_from(headers)
|
||||||
|
else:
|
||||||
|
self.extend(headers)
|
||||||
|
if kwargs:
|
||||||
|
self.extend(kwargs)
|
||||||
|
|
||||||
def add(self, key, value):
|
def __setitem__(self, key, val):
|
||||||
|
self._container[key.lower()] = (key, val)
|
||||||
|
return self._container[key.lower()]
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
val = self._container[key.lower()]
|
||||||
|
return ', '.join(val[1:])
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
del self._container[key.lower()]
|
||||||
|
|
||||||
|
def __contains__(self, key):
|
||||||
|
return key.lower() in self._container
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, Mapping) and not hasattr(other, 'keys'):
|
||||||
|
return False
|
||||||
|
if not isinstance(other, type(self)):
|
||||||
|
other = type(self)(other)
|
||||||
|
return (dict((k.lower(), v) for k, v in self.itermerged()) ==
|
||||||
|
dict((k.lower(), v) for k, v in other.itermerged()))
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
return not self.__eq__(other)
|
||||||
|
|
||||||
|
if not PY3: # Python 2
|
||||||
|
iterkeys = MutableMapping.iterkeys
|
||||||
|
itervalues = MutableMapping.itervalues
|
||||||
|
|
||||||
|
__marker = object()
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return len(self._container)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
# Only provide the originally cased names
|
||||||
|
for vals in self._container.values():
|
||||||
|
yield vals[0]
|
||||||
|
|
||||||
|
def pop(self, key, default=__marker):
|
||||||
|
'''D.pop(k[,d]) -> v, remove specified key and return the corresponding value.
|
||||||
|
If key is not found, d is returned if given, otherwise KeyError is raised.
|
||||||
|
'''
|
||||||
|
# Using the MutableMapping function directly fails due to the private marker.
|
||||||
|
# Using ordinary dict.pop would expose the internal structures.
|
||||||
|
# So let's reinvent the wheel.
|
||||||
|
try:
|
||||||
|
value = self[key]
|
||||||
|
except KeyError:
|
||||||
|
if default is self.__marker:
|
||||||
|
raise
|
||||||
|
return default
|
||||||
|
else:
|
||||||
|
del self[key]
|
||||||
|
return value
|
||||||
|
|
||||||
|
def discard(self, key):
|
||||||
|
try:
|
||||||
|
del self[key]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def add(self, key, val):
|
||||||
"""Adds a (name, value) pair, doesn't overwrite the value if it already
|
"""Adds a (name, value) pair, doesn't overwrite the value if it already
|
||||||
exists.
|
exists.
|
||||||
|
|
||||||
|
@ -156,43 +214,111 @@ class HTTPHeaderDict(MutableMapping):
|
||||||
>>> headers['foo']
|
>>> headers['foo']
|
||||||
'bar, baz'
|
'bar, baz'
|
||||||
"""
|
"""
|
||||||
self._data.setdefault(key.lower(), []).append((key, value))
|
key_lower = key.lower()
|
||||||
|
new_vals = key, val
|
||||||
|
# Keep the common case aka no item present as fast as possible
|
||||||
|
vals = self._container.setdefault(key_lower, new_vals)
|
||||||
|
if new_vals is not vals:
|
||||||
|
# new_vals was not inserted, as there was a previous one
|
||||||
|
if isinstance(vals, list):
|
||||||
|
# If already several items got inserted, we have a list
|
||||||
|
vals.append(val)
|
||||||
|
else:
|
||||||
|
# vals should be a tuple then, i.e. only one item so far
|
||||||
|
# Need to convert the tuple to list for further extension
|
||||||
|
self._container[key_lower] = [vals[0], vals[1], val]
|
||||||
|
|
||||||
|
def extend(self, *args, **kwargs):
|
||||||
|
"""Generic import function for any type of header-like object.
|
||||||
|
Adapted version of MutableMapping.update in order to insert items
|
||||||
|
with self.add instead of self.__setitem__
|
||||||
|
"""
|
||||||
|
if len(args) > 1:
|
||||||
|
raise TypeError("extend() takes at most 1 positional "
|
||||||
|
"arguments ({0} given)".format(len(args)))
|
||||||
|
other = args[0] if len(args) >= 1 else ()
|
||||||
|
|
||||||
|
if isinstance(other, HTTPHeaderDict):
|
||||||
|
for key, val in other.iteritems():
|
||||||
|
self.add(key, val)
|
||||||
|
elif isinstance(other, Mapping):
|
||||||
|
for key in other:
|
||||||
|
self.add(key, other[key])
|
||||||
|
elif hasattr(other, "keys"):
|
||||||
|
for key in other.keys():
|
||||||
|
self.add(key, other[key])
|
||||||
|
else:
|
||||||
|
for key, value in other:
|
||||||
|
self.add(key, value)
|
||||||
|
|
||||||
|
for key, value in kwargs.items():
|
||||||
|
self.add(key, value)
|
||||||
|
|
||||||
def getlist(self, key):
|
def getlist(self, key):
|
||||||
"""Returns a list of all the values for the named field. Returns an
|
"""Returns a list of all the values for the named field. Returns an
|
||||||
empty list if the key doesn't exist."""
|
empty list if the key doesn't exist."""
|
||||||
return self[key].split(', ') if key in self else []
|
try:
|
||||||
|
vals = self._container[key.lower()]
|
||||||
|
except KeyError:
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
if isinstance(vals, tuple):
|
||||||
|
return [vals[1]]
|
||||||
|
else:
|
||||||
|
return vals[1:]
|
||||||
|
|
||||||
def copy(self):
|
# Backwards compatibility for httplib
|
||||||
h = HTTPHeaderDict()
|
getheaders = getlist
|
||||||
for key in self._data:
|
getallmatchingheaders = getlist
|
||||||
for rawkey, value in self._data[key]:
|
iget = getlist
|
||||||
h.add(rawkey, value)
|
|
||||||
return h
|
|
||||||
|
|
||||||
def __eq__(self, other):
|
|
||||||
if not isinstance(other, Mapping):
|
|
||||||
return False
|
|
||||||
other = HTTPHeaderDict(other)
|
|
||||||
return dict((k1, self[k1]) for k1 in self._data) == \
|
|
||||||
dict((k2, other[k2]) for k2 in other._data)
|
|
||||||
|
|
||||||
def __getitem__(self, key):
|
|
||||||
values = self._data[key.lower()]
|
|
||||||
return ', '.join(value[1] for value in values)
|
|
||||||
|
|
||||||
def __setitem__(self, key, value):
|
|
||||||
self._data[key.lower()] = [(key, value)]
|
|
||||||
|
|
||||||
def __delitem__(self, key):
|
|
||||||
del self._data[key.lower()]
|
|
||||||
|
|
||||||
def __len__(self):
|
|
||||||
return len(self._data)
|
|
||||||
|
|
||||||
def __iter__(self):
|
|
||||||
for headers in itervalues(self._data):
|
|
||||||
yield headers[0][0]
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '%s(%r)' % (self.__class__.__name__, dict(self.items()))
|
return "%s(%s)" % (type(self).__name__, dict(self.itermerged()))
|
||||||
|
|
||||||
|
def _copy_from(self, other):
|
||||||
|
for key in other:
|
||||||
|
val = other.getlist(key)
|
||||||
|
if isinstance(val, list):
|
||||||
|
# Don't need to convert tuples
|
||||||
|
val = list(val)
|
||||||
|
self._container[key.lower()] = [key] + val
|
||||||
|
|
||||||
|
def copy(self):
|
||||||
|
clone = type(self)()
|
||||||
|
clone._copy_from(self)
|
||||||
|
return clone
|
||||||
|
|
||||||
|
def iteritems(self):
|
||||||
|
"""Iterate over all header lines, including duplicate ones."""
|
||||||
|
for key in self:
|
||||||
|
vals = self._container[key.lower()]
|
||||||
|
for val in vals[1:]:
|
||||||
|
yield vals[0], val
|
||||||
|
|
||||||
|
def itermerged(self):
|
||||||
|
"""Iterate over all headers, merging duplicate ones together."""
|
||||||
|
for key in self:
|
||||||
|
val = self._container[key.lower()]
|
||||||
|
yield val[0], ', '.join(val[1:])
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
return list(self.iteritems())
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_httplib(cls, message): # Python 2
|
||||||
|
"""Read headers from a Python 2 httplib message object."""
|
||||||
|
# python2.7 does not expose a proper API for exporting multiheaders
|
||||||
|
# efficiently. This function re-reads raw lines from the message
|
||||||
|
# object and extracts the multiheaders properly.
|
||||||
|
headers = []
|
||||||
|
|
||||||
|
for line in message.headers:
|
||||||
|
if line.startswith((' ', '\t')):
|
||||||
|
key, value = headers[-1]
|
||||||
|
headers[-1] = (key, value + '\r\n' + line.rstrip())
|
||||||
|
continue
|
||||||
|
|
||||||
|
key, value = line.split(':', 1)
|
||||||
|
headers.append((key, value.strip()))
|
||||||
|
|
||||||
|
return cls(headers)
|
||||||
|
|
|
@ -1,23 +1,21 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
import datetime
|
import datetime
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import socket
|
import socket
|
||||||
from socket import timeout as SocketTimeout
|
from socket import error as SocketError, timeout as SocketTimeout
|
||||||
import warnings
|
import warnings
|
||||||
from .packages import six
|
from .packages import six
|
||||||
|
|
||||||
try: # Python 3
|
try: # Python 3
|
||||||
from http.client import HTTPConnection as _HTTPConnection, HTTPException
|
from http.client import HTTPConnection as _HTTPConnection
|
||||||
|
from http.client import HTTPException # noqa: unused in this module
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from httplib import HTTPConnection as _HTTPConnection, HTTPException
|
from httplib import HTTPConnection as _HTTPConnection
|
||||||
|
from httplib import HTTPException # noqa: unused in this module
|
||||||
|
|
||||||
class DummyConnection(object):
|
|
||||||
"Used to detect a failed ConnectionCls import."
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
try: # Compiled with SSL?
|
try: # Compiled with SSL?
|
||||||
HTTPSConnection = DummyConnection
|
|
||||||
import ssl
|
import ssl
|
||||||
BaseSSLError = ssl.SSLError
|
BaseSSLError = ssl.SSLError
|
||||||
except (ImportError, AttributeError): # Platform-specific: No SSL.
|
except (ImportError, AttributeError): # Platform-specific: No SSL.
|
||||||
|
@ -36,11 +34,12 @@ except NameError: # Python 2:
|
||||||
|
|
||||||
|
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
|
NewConnectionError,
|
||||||
ConnectTimeoutError,
|
ConnectTimeoutError,
|
||||||
|
SubjectAltNameWarning,
|
||||||
SystemTimeWarning,
|
SystemTimeWarning,
|
||||||
SecurityWarning,
|
|
||||||
)
|
)
|
||||||
from .packages.ssl_match_hostname import match_hostname
|
from .packages.ssl_match_hostname import match_hostname, CertificateError
|
||||||
|
|
||||||
from .util.ssl_ import (
|
from .util.ssl_ import (
|
||||||
resolve_cert_reqs,
|
resolve_cert_reqs,
|
||||||
|
@ -52,6 +51,10 @@ from .util.ssl_ import (
|
||||||
|
|
||||||
from .util import connection
|
from .util import connection
|
||||||
|
|
||||||
|
from ._collections import HTTPHeaderDict
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
port_by_scheme = {
|
port_by_scheme = {
|
||||||
'http': 80,
|
'http': 80,
|
||||||
'https': 443,
|
'https': 443,
|
||||||
|
@ -60,6 +63,11 @@ port_by_scheme = {
|
||||||
RECENT_DATE = datetime.date(2014, 1, 1)
|
RECENT_DATE = datetime.date(2014, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
class DummyConnection(object):
|
||||||
|
"""Used to detect a failed ConnectionCls import."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class HTTPConnection(_HTTPConnection, object):
|
class HTTPConnection(_HTTPConnection, object):
|
||||||
"""
|
"""
|
||||||
Based on httplib.HTTPConnection but provides an extra constructor
|
Based on httplib.HTTPConnection but provides an extra constructor
|
||||||
|
@ -133,11 +141,15 @@ class HTTPConnection(_HTTPConnection, object):
|
||||||
conn = connection.create_connection(
|
conn = connection.create_connection(
|
||||||
(self.host, self.port), self.timeout, **extra_kw)
|
(self.host, self.port), self.timeout, **extra_kw)
|
||||||
|
|
||||||
except SocketTimeout:
|
except SocketTimeout as e:
|
||||||
raise ConnectTimeoutError(
|
raise ConnectTimeoutError(
|
||||||
self, "Connection to %s timed out. (connect timeout=%s)" %
|
self, "Connection to %s timed out. (connect timeout=%s)" %
|
||||||
(self.host, self.timeout))
|
(self.host, self.timeout))
|
||||||
|
|
||||||
|
except SocketError as e:
|
||||||
|
raise NewConnectionError(
|
||||||
|
self, "Failed to establish a new connection: %s" % e)
|
||||||
|
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
def _prepare_conn(self, conn):
|
def _prepare_conn(self, conn):
|
||||||
|
@ -155,6 +167,38 @@ class HTTPConnection(_HTTPConnection, object):
|
||||||
conn = self._new_conn()
|
conn = self._new_conn()
|
||||||
self._prepare_conn(conn)
|
self._prepare_conn(conn)
|
||||||
|
|
||||||
|
def request_chunked(self, method, url, body=None, headers=None):
|
||||||
|
"""
|
||||||
|
Alternative to the common request method, which sends the
|
||||||
|
body with chunked encoding and not as one block
|
||||||
|
"""
|
||||||
|
headers = HTTPHeaderDict(headers if headers is not None else {})
|
||||||
|
skip_accept_encoding = 'accept-encoding' in headers
|
||||||
|
self.putrequest(method, url, skip_accept_encoding=skip_accept_encoding)
|
||||||
|
for header, value in headers.items():
|
||||||
|
self.putheader(header, value)
|
||||||
|
if 'transfer-encoding' not in headers:
|
||||||
|
self.putheader('Transfer-Encoding', 'chunked')
|
||||||
|
self.endheaders()
|
||||||
|
|
||||||
|
if body is not None:
|
||||||
|
stringish_types = six.string_types + (six.binary_type,)
|
||||||
|
if isinstance(body, stringish_types):
|
||||||
|
body = (body,)
|
||||||
|
for chunk in body:
|
||||||
|
if not chunk:
|
||||||
|
continue
|
||||||
|
if not isinstance(chunk, six.binary_type):
|
||||||
|
chunk = chunk.encode('utf8')
|
||||||
|
len_str = hex(len(chunk))[2:]
|
||||||
|
self.send(len_str.encode('utf-8'))
|
||||||
|
self.send(b'\r\n')
|
||||||
|
self.send(chunk)
|
||||||
|
self.send(b'\r\n')
|
||||||
|
|
||||||
|
# After the if clause, to always have a closed body
|
||||||
|
self.send(b'0\r\n\r\n')
|
||||||
|
|
||||||
|
|
||||||
class HTTPSConnection(HTTPConnection):
|
class HTTPSConnection(HTTPConnection):
|
||||||
default_port = port_by_scheme['https']
|
default_port = port_by_scheme['https']
|
||||||
|
@ -185,19 +229,25 @@ class VerifiedHTTPSConnection(HTTPSConnection):
|
||||||
"""
|
"""
|
||||||
cert_reqs = None
|
cert_reqs = None
|
||||||
ca_certs = None
|
ca_certs = None
|
||||||
|
ca_cert_dir = None
|
||||||
ssl_version = None
|
ssl_version = None
|
||||||
assert_fingerprint = None
|
assert_fingerprint = None
|
||||||
|
|
||||||
def set_cert(self, key_file=None, cert_file=None,
|
def set_cert(self, key_file=None, cert_file=None,
|
||||||
cert_reqs=None, ca_certs=None,
|
cert_reqs=None, ca_certs=None,
|
||||||
assert_hostname=None, assert_fingerprint=None):
|
assert_hostname=None, assert_fingerprint=None,
|
||||||
|
ca_cert_dir=None):
|
||||||
|
|
||||||
|
if (ca_certs or ca_cert_dir) and cert_reqs is None:
|
||||||
|
cert_reqs = 'CERT_REQUIRED'
|
||||||
|
|
||||||
self.key_file = key_file
|
self.key_file = key_file
|
||||||
self.cert_file = cert_file
|
self.cert_file = cert_file
|
||||||
self.cert_reqs = cert_reqs
|
self.cert_reqs = cert_reqs
|
||||||
self.ca_certs = ca_certs
|
|
||||||
self.assert_hostname = assert_hostname
|
self.assert_hostname = assert_hostname
|
||||||
self.assert_fingerprint = assert_fingerprint
|
self.assert_fingerprint = assert_fingerprint
|
||||||
|
self.ca_certs = ca_certs and os.path.expanduser(ca_certs)
|
||||||
|
self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir)
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
# Add certificate verification
|
# Add certificate verification
|
||||||
|
@ -234,6 +284,7 @@ class VerifiedHTTPSConnection(HTTPSConnection):
|
||||||
self.sock = ssl_wrap_socket(conn, self.key_file, self.cert_file,
|
self.sock = ssl_wrap_socket(conn, self.key_file, self.cert_file,
|
||||||
cert_reqs=resolved_cert_reqs,
|
cert_reqs=resolved_cert_reqs,
|
||||||
ca_certs=self.ca_certs,
|
ca_certs=self.ca_certs,
|
||||||
|
ca_cert_dir=self.ca_cert_dir,
|
||||||
server_hostname=hostname,
|
server_hostname=hostname,
|
||||||
ssl_version=resolved_ssl_version)
|
ssl_version=resolved_ssl_version)
|
||||||
|
|
||||||
|
@ -245,18 +296,35 @@ class VerifiedHTTPSConnection(HTTPSConnection):
|
||||||
cert = self.sock.getpeercert()
|
cert = self.sock.getpeercert()
|
||||||
if not cert.get('subjectAltName', ()):
|
if not cert.get('subjectAltName', ()):
|
||||||
warnings.warn((
|
warnings.warn((
|
||||||
'Certificate has no `subjectAltName`, falling back to check for a `commonName` for now. '
|
'Certificate for {0} has no `subjectAltName`, falling back to check for a '
|
||||||
'This feature is being removed by major browsers and deprecated by RFC 2818. '
|
'`commonName` for now. This feature is being removed by major browsers and '
|
||||||
'(See https://github.com/shazow/urllib3/issues/497 for details.)'),
|
'deprecated by RFC 2818. (See https://github.com/shazow/urllib3/issues/497 '
|
||||||
SecurityWarning
|
'for details.)'.format(hostname)),
|
||||||
|
SubjectAltNameWarning
|
||||||
)
|
)
|
||||||
match_hostname(cert, self.assert_hostname or hostname)
|
_match_hostname(cert, self.assert_hostname or hostname)
|
||||||
|
|
||||||
self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED
|
self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED or
|
||||||
or self.assert_fingerprint is not None)
|
self.assert_fingerprint is not None)
|
||||||
|
|
||||||
|
|
||||||
|
def _match_hostname(cert, asserted_hostname):
|
||||||
|
try:
|
||||||
|
match_hostname(cert, asserted_hostname)
|
||||||
|
except CertificateError as e:
|
||||||
|
log.error(
|
||||||
|
'Certificate did not match expected hostname: %s. '
|
||||||
|
'Certificate: %s', asserted_hostname, cert
|
||||||
|
)
|
||||||
|
# Add cert to exception and reraise so client code can inspect
|
||||||
|
# the cert when catching the exception, if they want to
|
||||||
|
e._peer_cert = cert
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
if ssl:
|
if ssl:
|
||||||
# Make a copy for testing.
|
# Make a copy for testing.
|
||||||
UnverifiedHTTPSConnection = HTTPSConnection
|
UnverifiedHTTPSConnection = HTTPSConnection
|
||||||
HTTPSConnection = VerifiedHTTPSConnection
|
HTTPSConnection = VerifiedHTTPSConnection
|
||||||
|
else:
|
||||||
|
HTTPSConnection = DummyConnection
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
import errno
|
import errno
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
@ -10,13 +11,15 @@ try: # Python 3
|
||||||
from queue import LifoQueue, Empty, Full
|
from queue import LifoQueue, Empty, Full
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from Queue import LifoQueue, Empty, Full
|
from Queue import LifoQueue, Empty, Full
|
||||||
import Queue as _ # Platform-specific: Windows
|
# Queue is imported for side effects on MS Windows
|
||||||
|
import Queue as _unused_module_Queue # noqa: unused
|
||||||
|
|
||||||
|
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
ClosedPoolError,
|
ClosedPoolError,
|
||||||
ProtocolError,
|
ProtocolError,
|
||||||
EmptyPoolError,
|
EmptyPoolError,
|
||||||
|
HeaderParsingError,
|
||||||
HostChangedError,
|
HostChangedError,
|
||||||
LocationValueError,
|
LocationValueError,
|
||||||
MaxRetryError,
|
MaxRetryError,
|
||||||
|
@ -25,6 +28,7 @@ from .exceptions import (
|
||||||
SSLError,
|
SSLError,
|
||||||
TimeoutError,
|
TimeoutError,
|
||||||
InsecureRequestWarning,
|
InsecureRequestWarning,
|
||||||
|
NewConnectionError,
|
||||||
)
|
)
|
||||||
from .packages.ssl_match_hostname import CertificateError
|
from .packages.ssl_match_hostname import CertificateError
|
||||||
from .packages import six
|
from .packages import six
|
||||||
|
@ -32,15 +36,16 @@ from .connection import (
|
||||||
port_by_scheme,
|
port_by_scheme,
|
||||||
DummyConnection,
|
DummyConnection,
|
||||||
HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection,
|
HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection,
|
||||||
HTTPException, BaseSSLError, ConnectionError
|
HTTPException, BaseSSLError,
|
||||||
)
|
)
|
||||||
from .request import RequestMethods
|
from .request import RequestMethods
|
||||||
from .response import HTTPResponse
|
from .response import HTTPResponse
|
||||||
|
|
||||||
from .util.connection import is_connection_dropped
|
from .util.connection import is_connection_dropped
|
||||||
|
from .util.response import assert_header_parsing
|
||||||
from .util.retry import Retry
|
from .util.retry import Retry
|
||||||
from .util.timeout import Timeout
|
from .util.timeout import Timeout
|
||||||
from .util.url import get_host
|
from .util.url import get_host, Url
|
||||||
|
|
||||||
|
|
||||||
xrange = six.moves.xrange
|
xrange = six.moves.xrange
|
||||||
|
@ -50,7 +55,7 @@ log = logging.getLogger(__name__)
|
||||||
_Default = object()
|
_Default = object()
|
||||||
|
|
||||||
|
|
||||||
## Pool objects
|
# Pool objects
|
||||||
class ConnectionPool(object):
|
class ConnectionPool(object):
|
||||||
"""
|
"""
|
||||||
Base class for all connection pools, such as
|
Base class for all connection pools, such as
|
||||||
|
@ -65,6 +70,11 @@ class ConnectionPool(object):
|
||||||
raise LocationValueError("No host specified.")
|
raise LocationValueError("No host specified.")
|
||||||
|
|
||||||
# httplib doesn't like it when we include brackets in ipv6 addresses
|
# httplib doesn't like it when we include brackets in ipv6 addresses
|
||||||
|
# Specifically, if we include brackets but also pass the port then
|
||||||
|
# httplib crazily doubles up the square brackets on the Host header.
|
||||||
|
# Instead, we need to make sure we never pass ``None`` as the port.
|
||||||
|
# However, for backward compatibility reasons we can't actually
|
||||||
|
# *assert* that.
|
||||||
self.host = host.strip('[]')
|
self.host = host.strip('[]')
|
||||||
self.port = port
|
self.port = port
|
||||||
|
|
||||||
|
@ -72,6 +82,21 @@ class ConnectionPool(object):
|
||||||
return '%s(host=%r, port=%r)' % (type(self).__name__,
|
return '%s(host=%r, port=%r)' % (type(self).__name__,
|
||||||
self.host, self.port)
|
self.host, self.port)
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
self.close()
|
||||||
|
# Return False to re-raise any potential exceptions
|
||||||
|
return False
|
||||||
|
|
||||||
|
def close():
|
||||||
|
"""
|
||||||
|
Close all pooled connections and disable the pool.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252
|
# This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252
|
||||||
_blocking_errnos = set([errno.EAGAIN, errno.EWOULDBLOCK])
|
_blocking_errnos = set([errno.EAGAIN, errno.EWOULDBLOCK])
|
||||||
|
|
||||||
|
@ -105,7 +130,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
|
|
||||||
:param maxsize:
|
:param maxsize:
|
||||||
Number of connections to save that can be reused. More than 1 is useful
|
Number of connections to save that can be reused. More than 1 is useful
|
||||||
in multithreaded situations. If ``block`` is set to false, more
|
in multithreaded situations. If ``block`` is set to False, more
|
||||||
connections will be created but they will not be saved once they've
|
connections will be created but they will not be saved once they've
|
||||||
been used.
|
been used.
|
||||||
|
|
||||||
|
@ -184,8 +209,8 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
Return a fresh :class:`HTTPConnection`.
|
Return a fresh :class:`HTTPConnection`.
|
||||||
"""
|
"""
|
||||||
self.num_connections += 1
|
self.num_connections += 1
|
||||||
log.info("Starting new HTTP connection (%d): %s" %
|
log.info("Starting new HTTP connection (%d): %s",
|
||||||
(self.num_connections, self.host))
|
self.num_connections, self.host)
|
||||||
|
|
||||||
conn = self.ConnectionCls(host=self.host, port=self.port,
|
conn = self.ConnectionCls(host=self.host, port=self.port,
|
||||||
timeout=self.timeout.connect_timeout,
|
timeout=self.timeout.connect_timeout,
|
||||||
|
@ -220,7 +245,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
|
|
||||||
# If this is a persistent connection, check if it got disconnected
|
# If this is a persistent connection, check if it got disconnected
|
||||||
if conn and is_connection_dropped(conn):
|
if conn and is_connection_dropped(conn):
|
||||||
log.info("Resetting dropped connection: %s" % self.host)
|
log.info("Resetting dropped connection: %s", self.host)
|
||||||
conn.close()
|
conn.close()
|
||||||
if getattr(conn, 'auto_open', 1) == 0:
|
if getattr(conn, 'auto_open', 1) == 0:
|
||||||
# This is a proxied connection that has been mutated by
|
# This is a proxied connection that has been mutated by
|
||||||
|
@ -253,7 +278,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
except Full:
|
except Full:
|
||||||
# This should never happen if self.block == True
|
# This should never happen if self.block == True
|
||||||
log.warning(
|
log.warning(
|
||||||
"Connection pool is full, discarding connection: %s" %
|
"Connection pool is full, discarding connection: %s",
|
||||||
self.host)
|
self.host)
|
||||||
|
|
||||||
# Connection never got put back into the pool, close it.
|
# Connection never got put back into the pool, close it.
|
||||||
|
@ -266,6 +291,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
"""
|
"""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def _prepare_proxy(self, conn):
|
||||||
|
# Nothing to do for HTTP connections.
|
||||||
|
pass
|
||||||
|
|
||||||
def _get_timeout(self, timeout):
|
def _get_timeout(self, timeout):
|
||||||
""" Helper that always returns a :class:`urllib3.util.Timeout` """
|
""" Helper that always returns a :class:`urllib3.util.Timeout` """
|
||||||
if timeout is _Default:
|
if timeout is _Default:
|
||||||
|
@ -295,7 +324,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
if 'timed out' in str(err) or 'did not complete (read)' in str(err): # Python 2.6
|
if 'timed out' in str(err) or 'did not complete (read)' in str(err): # Python 2.6
|
||||||
raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value)
|
raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value)
|
||||||
|
|
||||||
def _make_request(self, conn, method, url, timeout=_Default,
|
def _make_request(self, conn, method, url, timeout=_Default, chunked=False,
|
||||||
**httplib_request_kw):
|
**httplib_request_kw):
|
||||||
"""
|
"""
|
||||||
Perform a request on a given urllib connection object taken from our
|
Perform a request on a given urllib connection object taken from our
|
||||||
|
@ -327,7 +356,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
|
|
||||||
# conn.request() calls httplib.*.request, not the method in
|
# conn.request() calls httplib.*.request, not the method in
|
||||||
# urllib3.request. It also calls makefile (recv) on the socket.
|
# urllib3.request. It also calls makefile (recv) on the socket.
|
||||||
conn.request(method, url, **httplib_request_kw)
|
if chunked:
|
||||||
|
conn.request_chunked(method, url, **httplib_request_kw)
|
||||||
|
else:
|
||||||
|
conn.request(method, url, **httplib_request_kw)
|
||||||
|
|
||||||
# Reset the timeout for the recv() on the socket
|
# Reset the timeout for the recv() on the socket
|
||||||
read_timeout = timeout_obj.read_timeout
|
read_timeout = timeout_obj.read_timeout
|
||||||
|
@ -349,7 +381,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
|
|
||||||
# Receive the response from the server
|
# Receive the response from the server
|
||||||
try:
|
try:
|
||||||
try: # Python 2.7+, use buffering of HTTP responses
|
try: # Python 2.7, use buffering of HTTP responses
|
||||||
httplib_response = conn.getresponse(buffering=True)
|
httplib_response = conn.getresponse(buffering=True)
|
||||||
except TypeError: # Python 2.6 and older
|
except TypeError: # Python 2.6 and older
|
||||||
httplib_response = conn.getresponse()
|
httplib_response = conn.getresponse()
|
||||||
|
@ -359,11 +391,21 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
|
|
||||||
# AppEngine doesn't have a version attr.
|
# AppEngine doesn't have a version attr.
|
||||||
http_version = getattr(conn, '_http_vsn_str', 'HTTP/?')
|
http_version = getattr(conn, '_http_vsn_str', 'HTTP/?')
|
||||||
log.debug("\"%s %s %s\" %s %s" % (method, url, http_version,
|
log.debug("\"%s %s %s\" %s %s", method, url, http_version,
|
||||||
httplib_response.status,
|
httplib_response.status, httplib_response.length)
|
||||||
httplib_response.length))
|
|
||||||
|
try:
|
||||||
|
assert_header_parsing(httplib_response.msg)
|
||||||
|
except HeaderParsingError as hpe: # Platform-specific: Python 3
|
||||||
|
log.warning(
|
||||||
|
'Failed to parse headers (url=%s): %s',
|
||||||
|
self._absolute_url(url), hpe, exc_info=True)
|
||||||
|
|
||||||
return httplib_response
|
return httplib_response
|
||||||
|
|
||||||
|
def _absolute_url(self, path):
|
||||||
|
return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
"""
|
"""
|
||||||
Close all pooled connections and disable the pool.
|
Close all pooled connections and disable the pool.
|
||||||
|
@ -401,7 +443,8 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
|
|
||||||
def urlopen(self, method, url, body=None, headers=None, retries=None,
|
def urlopen(self, method, url, body=None, headers=None, retries=None,
|
||||||
redirect=True, assert_same_host=True, timeout=_Default,
|
redirect=True, assert_same_host=True, timeout=_Default,
|
||||||
pool_timeout=None, release_conn=None, **response_kw):
|
pool_timeout=None, release_conn=None, chunked=False,
|
||||||
|
**response_kw):
|
||||||
"""
|
"""
|
||||||
Get a connection from the pool and perform an HTTP request. This is the
|
Get a connection from the pool and perform an HTTP request. This is the
|
||||||
lowest level call for making a request, so you'll need to specify all
|
lowest level call for making a request, so you'll need to specify all
|
||||||
|
@ -478,6 +521,11 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
back into the pool. If None, it takes the value of
|
back into the pool. If None, it takes the value of
|
||||||
``response_kw.get('preload_content', True)``.
|
``response_kw.get('preload_content', True)``.
|
||||||
|
|
||||||
|
:param chunked:
|
||||||
|
If True, urllib3 will send the body using chunked transfer
|
||||||
|
encoding. Otherwise, urllib3 will send the body using the standard
|
||||||
|
content-length form. Defaults to False.
|
||||||
|
|
||||||
:param \**response_kw:
|
:param \**response_kw:
|
||||||
Additional parameters are passed to
|
Additional parameters are passed to
|
||||||
:meth:`urllib3.response.HTTPResponse.from_httplib`
|
:meth:`urllib3.response.HTTPResponse.from_httplib`
|
||||||
|
@ -508,20 +556,32 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
# complains about UnboundLocalError.
|
# complains about UnboundLocalError.
|
||||||
err = None
|
err = None
|
||||||
|
|
||||||
|
# Keep track of whether we cleanly exited the except block. This
|
||||||
|
# ensures we do proper cleanup in finally.
|
||||||
|
clean_exit = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Request a connection from the queue.
|
# Request a connection from the queue.
|
||||||
|
timeout_obj = self._get_timeout(timeout)
|
||||||
conn = self._get_conn(timeout=pool_timeout)
|
conn = self._get_conn(timeout=pool_timeout)
|
||||||
|
|
||||||
|
conn.timeout = timeout_obj.connect_timeout
|
||||||
|
|
||||||
|
is_new_proxy_conn = self.proxy is not None and not getattr(conn, 'sock', None)
|
||||||
|
if is_new_proxy_conn:
|
||||||
|
self._prepare_proxy(conn)
|
||||||
|
|
||||||
# Make the request on the httplib connection object.
|
# Make the request on the httplib connection object.
|
||||||
httplib_response = self._make_request(conn, method, url,
|
httplib_response = self._make_request(conn, method, url,
|
||||||
timeout=timeout,
|
timeout=timeout_obj,
|
||||||
body=body, headers=headers)
|
body=body, headers=headers,
|
||||||
|
chunked=chunked)
|
||||||
|
|
||||||
# If we're going to release the connection in ``finally:``, then
|
# If we're going to release the connection in ``finally:``, then
|
||||||
# the request doesn't need to know about the connection. Otherwise
|
# the response doesn't need to know about the connection. Otherwise
|
||||||
# it will also try to release it and we'll have a double-release
|
# it will also try to release it and we'll have a double-release
|
||||||
# mess.
|
# mess.
|
||||||
response_conn = not release_conn and conn
|
response_conn = conn if not release_conn else None
|
||||||
|
|
||||||
# Import httplib's response into our own wrapper object
|
# Import httplib's response into our own wrapper object
|
||||||
response = HTTPResponse.from_httplib(httplib_response,
|
response = HTTPResponse.from_httplib(httplib_response,
|
||||||
|
@ -529,10 +589,8 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
connection=response_conn,
|
connection=response_conn,
|
||||||
**response_kw)
|
**response_kw)
|
||||||
|
|
||||||
# else:
|
# Everything went great!
|
||||||
# The connection will be put back into the pool when
|
clean_exit = True
|
||||||
# ``response.release_conn()`` is called (implicitly by
|
|
||||||
# ``response.read()``)
|
|
||||||
|
|
||||||
except Empty:
|
except Empty:
|
||||||
# Timed out by queue.
|
# Timed out by queue.
|
||||||
|
@ -542,32 +600,41 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
# Close the connection. If a connection is reused on which there
|
# Close the connection. If a connection is reused on which there
|
||||||
# was a Certificate error, the next request will certainly raise
|
# was a Certificate error, the next request will certainly raise
|
||||||
# another Certificate error.
|
# another Certificate error.
|
||||||
if conn:
|
clean_exit = False
|
||||||
conn.close()
|
|
||||||
conn = None
|
|
||||||
raise SSLError(e)
|
raise SSLError(e)
|
||||||
|
|
||||||
except (TimeoutError, HTTPException, SocketError, ConnectionError) as e:
|
except SSLError:
|
||||||
if conn:
|
# Treat SSLError separately from BaseSSLError to preserve
|
||||||
# Discard the connection for these exceptions. It will be
|
# traceback.
|
||||||
# be replaced during the next _get_conn() call.
|
clean_exit = False
|
||||||
conn.close()
|
raise
|
||||||
conn = None
|
|
||||||
|
|
||||||
stacktrace = sys.exc_info()[2]
|
except (TimeoutError, HTTPException, SocketError, ProtocolError) as e:
|
||||||
if isinstance(e, SocketError) and self.proxy:
|
# Discard the connection for these exceptions. It will be
|
||||||
|
# be replaced during the next _get_conn() call.
|
||||||
|
clean_exit = False
|
||||||
|
|
||||||
|
if isinstance(e, (SocketError, NewConnectionError)) and self.proxy:
|
||||||
e = ProxyError('Cannot connect to proxy.', e)
|
e = ProxyError('Cannot connect to proxy.', e)
|
||||||
elif isinstance(e, (SocketError, HTTPException)):
|
elif isinstance(e, (SocketError, HTTPException)):
|
||||||
e = ProtocolError('Connection aborted.', e)
|
e = ProtocolError('Connection aborted.', e)
|
||||||
|
|
||||||
retries = retries.increment(method, url, error=e,
|
retries = retries.increment(method, url, error=e, _pool=self,
|
||||||
_pool=self, _stacktrace=stacktrace)
|
_stacktrace=sys.exc_info()[2])
|
||||||
retries.sleep()
|
retries.sleep()
|
||||||
|
|
||||||
# Keep track of the error for the retry warning.
|
# Keep track of the error for the retry warning.
|
||||||
err = e
|
err = e
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
|
if not clean_exit:
|
||||||
|
# We hit some kind of exception, handled or otherwise. We need
|
||||||
|
# to throw the connection away unless explicitly told not to.
|
||||||
|
# Close the connection, set the variable to None, and make sure
|
||||||
|
# we put the None back in the pool to avoid leaking it.
|
||||||
|
conn = conn and conn.close()
|
||||||
|
release_conn = True
|
||||||
|
|
||||||
if release_conn:
|
if release_conn:
|
||||||
# Put the connection back to be reused. If the connection is
|
# Put the connection back to be reused. If the connection is
|
||||||
# expired then it will be None, which will get replaced with a
|
# expired then it will be None, which will get replaced with a
|
||||||
|
@ -577,7 +644,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
if not conn:
|
if not conn:
|
||||||
# Try again
|
# Try again
|
||||||
log.warning("Retrying (%r) after connection "
|
log.warning("Retrying (%r) after connection "
|
||||||
"broken by '%r': %s" % (retries, err, url))
|
"broken by '%r': %s", retries, err, url)
|
||||||
return self.urlopen(method, url, body, headers, retries,
|
return self.urlopen(method, url, body, headers, retries,
|
||||||
redirect, assert_same_host,
|
redirect, assert_same_host,
|
||||||
timeout=timeout, pool_timeout=pool_timeout,
|
timeout=timeout, pool_timeout=pool_timeout,
|
||||||
|
@ -593,26 +660,39 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods):
|
||||||
retries = retries.increment(method, url, response=response, _pool=self)
|
retries = retries.increment(method, url, response=response, _pool=self)
|
||||||
except MaxRetryError:
|
except MaxRetryError:
|
||||||
if retries.raise_on_redirect:
|
if retries.raise_on_redirect:
|
||||||
|
# Release the connection for this response, since we're not
|
||||||
|
# returning it to be released manually.
|
||||||
|
response.release_conn()
|
||||||
raise
|
raise
|
||||||
return response
|
return response
|
||||||
|
|
||||||
log.info("Redirecting %s -> %s" % (url, redirect_location))
|
log.info("Redirecting %s -> %s", url, redirect_location)
|
||||||
return self.urlopen(method, redirect_location, body, headers,
|
return self.urlopen(
|
||||||
retries=retries, redirect=redirect,
|
method, redirect_location, body, headers,
|
||||||
assert_same_host=assert_same_host,
|
retries=retries, redirect=redirect,
|
||||||
timeout=timeout, pool_timeout=pool_timeout,
|
assert_same_host=assert_same_host,
|
||||||
release_conn=release_conn, **response_kw)
|
timeout=timeout, pool_timeout=pool_timeout,
|
||||||
|
release_conn=release_conn, **response_kw)
|
||||||
|
|
||||||
# Check if we should retry the HTTP response.
|
# Check if we should retry the HTTP response.
|
||||||
if retries.is_forced_retry(method, status_code=response.status):
|
if retries.is_forced_retry(method, status_code=response.status):
|
||||||
retries = retries.increment(method, url, response=response, _pool=self)
|
try:
|
||||||
|
retries = retries.increment(method, url, response=response, _pool=self)
|
||||||
|
except MaxRetryError:
|
||||||
|
if retries.raise_on_status:
|
||||||
|
# Release the connection for this response, since we're not
|
||||||
|
# returning it to be released manually.
|
||||||
|
response.release_conn()
|
||||||
|
raise
|
||||||
|
return response
|
||||||
retries.sleep()
|
retries.sleep()
|
||||||
log.info("Forced retry: %s" % url)
|
log.info("Forced retry: %s", url)
|
||||||
return self.urlopen(method, url, body, headers,
|
return self.urlopen(
|
||||||
retries=retries, redirect=redirect,
|
method, url, body, headers,
|
||||||
assert_same_host=assert_same_host,
|
retries=retries, redirect=redirect,
|
||||||
timeout=timeout, pool_timeout=pool_timeout,
|
assert_same_host=assert_same_host,
|
||||||
release_conn=release_conn, **response_kw)
|
timeout=timeout, pool_timeout=pool_timeout,
|
||||||
|
release_conn=release_conn, **response_kw)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -629,10 +709,10 @@ class HTTPSConnectionPool(HTTPConnectionPool):
|
||||||
``assert_hostname`` and ``host`` in this order to verify connections.
|
``assert_hostname`` and ``host`` in this order to verify connections.
|
||||||
If ``assert_hostname`` is False, no verification is done.
|
If ``assert_hostname`` is False, no verification is done.
|
||||||
|
|
||||||
The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs`` and
|
The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``,
|
||||||
``ssl_version`` are only used if :mod:`ssl` is available and are fed into
|
``ca_cert_dir``, and ``ssl_version`` are only used if :mod:`ssl` is
|
||||||
:meth:`urllib3.util.ssl_wrap_socket` to upgrade the connection socket
|
available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade
|
||||||
into an SSL socket.
|
the connection socket into an SSL socket.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
scheme = 'https'
|
scheme = 'https'
|
||||||
|
@ -645,15 +725,20 @@ class HTTPSConnectionPool(HTTPConnectionPool):
|
||||||
key_file=None, cert_file=None, cert_reqs=None,
|
key_file=None, cert_file=None, cert_reqs=None,
|
||||||
ca_certs=None, ssl_version=None,
|
ca_certs=None, ssl_version=None,
|
||||||
assert_hostname=None, assert_fingerprint=None,
|
assert_hostname=None, assert_fingerprint=None,
|
||||||
**conn_kw):
|
ca_cert_dir=None, **conn_kw):
|
||||||
|
|
||||||
HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize,
|
HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize,
|
||||||
block, headers, retries, _proxy, _proxy_headers,
|
block, headers, retries, _proxy, _proxy_headers,
|
||||||
**conn_kw)
|
**conn_kw)
|
||||||
|
|
||||||
|
if ca_certs and cert_reqs is None:
|
||||||
|
cert_reqs = 'CERT_REQUIRED'
|
||||||
|
|
||||||
self.key_file = key_file
|
self.key_file = key_file
|
||||||
self.cert_file = cert_file
|
self.cert_file = cert_file
|
||||||
self.cert_reqs = cert_reqs
|
self.cert_reqs = cert_reqs
|
||||||
self.ca_certs = ca_certs
|
self.ca_certs = ca_certs
|
||||||
|
self.ca_cert_dir = ca_cert_dir
|
||||||
self.ssl_version = ssl_version
|
self.ssl_version = ssl_version
|
||||||
self.assert_hostname = assert_hostname
|
self.assert_hostname = assert_hostname
|
||||||
self.assert_fingerprint = assert_fingerprint
|
self.assert_fingerprint = assert_fingerprint
|
||||||
|
@ -669,38 +754,40 @@ class HTTPSConnectionPool(HTTPConnectionPool):
|
||||||
cert_file=self.cert_file,
|
cert_file=self.cert_file,
|
||||||
cert_reqs=self.cert_reqs,
|
cert_reqs=self.cert_reqs,
|
||||||
ca_certs=self.ca_certs,
|
ca_certs=self.ca_certs,
|
||||||
|
ca_cert_dir=self.ca_cert_dir,
|
||||||
assert_hostname=self.assert_hostname,
|
assert_hostname=self.assert_hostname,
|
||||||
assert_fingerprint=self.assert_fingerprint)
|
assert_fingerprint=self.assert_fingerprint)
|
||||||
conn.ssl_version = self.ssl_version
|
conn.ssl_version = self.ssl_version
|
||||||
|
|
||||||
if self.proxy is not None:
|
|
||||||
# Python 2.7+
|
|
||||||
try:
|
|
||||||
set_tunnel = conn.set_tunnel
|
|
||||||
except AttributeError: # Platform-specific: Python 2.6
|
|
||||||
set_tunnel = conn._set_tunnel
|
|
||||||
|
|
||||||
if sys.version_info <= (2, 6, 4) and not self.proxy_headers: # Python 2.6.4 and older
|
|
||||||
set_tunnel(self.host, self.port)
|
|
||||||
else:
|
|
||||||
set_tunnel(self.host, self.port, self.proxy_headers)
|
|
||||||
|
|
||||||
# Establish tunnel connection early, because otherwise httplib
|
|
||||||
# would improperly set Host: header to proxy's IP:port.
|
|
||||||
conn.connect()
|
|
||||||
|
|
||||||
return conn
|
return conn
|
||||||
|
|
||||||
|
def _prepare_proxy(self, conn):
|
||||||
|
"""
|
||||||
|
Establish tunnel connection early, because otherwise httplib
|
||||||
|
would improperly set Host: header to proxy's IP:port.
|
||||||
|
"""
|
||||||
|
# Python 2.7+
|
||||||
|
try:
|
||||||
|
set_tunnel = conn.set_tunnel
|
||||||
|
except AttributeError: # Platform-specific: Python 2.6
|
||||||
|
set_tunnel = conn._set_tunnel
|
||||||
|
|
||||||
|
if sys.version_info <= (2, 6, 4) and not self.proxy_headers: # Python 2.6.4 and older
|
||||||
|
set_tunnel(self.host, self.port)
|
||||||
|
else:
|
||||||
|
set_tunnel(self.host, self.port, self.proxy_headers)
|
||||||
|
|
||||||
|
conn.connect()
|
||||||
|
|
||||||
def _new_conn(self):
|
def _new_conn(self):
|
||||||
"""
|
"""
|
||||||
Return a fresh :class:`httplib.HTTPSConnection`.
|
Return a fresh :class:`httplib.HTTPSConnection`.
|
||||||
"""
|
"""
|
||||||
self.num_connections += 1
|
self.num_connections += 1
|
||||||
log.info("Starting new HTTPS connection (%d): %s"
|
log.info("Starting new HTTPS connection (%d): %s",
|
||||||
% (self.num_connections, self.host))
|
self.num_connections, self.host)
|
||||||
|
|
||||||
if not self.ConnectionCls or self.ConnectionCls is DummyConnection:
|
if not self.ConnectionCls or self.ConnectionCls is DummyConnection:
|
||||||
# Platform-specific: Python without ssl
|
|
||||||
raise SSLError("Can't connect to HTTPS URL because the SSL "
|
raise SSLError("Can't connect to HTTPS URL because the SSL "
|
||||||
"module is not available.")
|
"module is not available.")
|
||||||
|
|
||||||
|
@ -755,6 +842,7 @@ def connection_from_url(url, **kw):
|
||||||
>>> r = conn.request('GET', '/')
|
>>> r = conn.request('GET', '/')
|
||||||
"""
|
"""
|
||||||
scheme, host, port = get_host(url)
|
scheme, host, port = get_host(url)
|
||||||
|
port = port or port_by_scheme.get(scheme, 80)
|
||||||
if scheme == 'https':
|
if scheme == 'https':
|
||||||
return HTTPSConnectionPool(host, port=port, **kw)
|
return HTTPSConnectionPool(host, port=port, **kw)
|
||||||
else:
|
else:
|
||||||
|
|
231
lib/requests/packages/urllib3/contrib/appengine.py
Normal file
231
lib/requests/packages/urllib3/contrib/appengine.py
Normal file
|
@ -0,0 +1,231 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
from ..exceptions import (
|
||||||
|
HTTPError,
|
||||||
|
HTTPWarning,
|
||||||
|
MaxRetryError,
|
||||||
|
ProtocolError,
|
||||||
|
TimeoutError,
|
||||||
|
SSLError
|
||||||
|
)
|
||||||
|
|
||||||
|
from ..packages.six import BytesIO
|
||||||
|
from ..request import RequestMethods
|
||||||
|
from ..response import HTTPResponse
|
||||||
|
from ..util.timeout import Timeout
|
||||||
|
from ..util.retry import Retry
|
||||||
|
|
||||||
|
try:
|
||||||
|
from google.appengine.api import urlfetch
|
||||||
|
except ImportError:
|
||||||
|
urlfetch = None
|
||||||
|
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class AppEnginePlatformWarning(HTTPWarning):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AppEnginePlatformError(HTTPError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AppEngineManager(RequestMethods):
|
||||||
|
"""
|
||||||
|
Connection manager for Google App Engine sandbox applications.
|
||||||
|
|
||||||
|
This manager uses the URLFetch service directly instead of using the
|
||||||
|
emulated httplib, and is subject to URLFetch limitations as described in
|
||||||
|
the App Engine documentation here:
|
||||||
|
|
||||||
|
https://cloud.google.com/appengine/docs/python/urlfetch
|
||||||
|
|
||||||
|
Notably it will raise an AppEnginePlatformError if:
|
||||||
|
* URLFetch is not available.
|
||||||
|
* If you attempt to use this on GAEv2 (Managed VMs), as full socket
|
||||||
|
support is available.
|
||||||
|
* If a request size is more than 10 megabytes.
|
||||||
|
* If a response size is more than 32 megabtyes.
|
||||||
|
* If you use an unsupported request method such as OPTIONS.
|
||||||
|
|
||||||
|
Beyond those cases, it will raise normal urllib3 errors.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, headers=None, retries=None, validate_certificate=True):
|
||||||
|
if not urlfetch:
|
||||||
|
raise AppEnginePlatformError(
|
||||||
|
"URLFetch is not available in this environment.")
|
||||||
|
|
||||||
|
if is_prod_appengine_mvms():
|
||||||
|
raise AppEnginePlatformError(
|
||||||
|
"Use normal urllib3.PoolManager instead of AppEngineManager"
|
||||||
|
"on Managed VMs, as using URLFetch is not necessary in "
|
||||||
|
"this environment.")
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
"urllib3 is using URLFetch on Google App Engine sandbox instead "
|
||||||
|
"of sockets. To use sockets directly instead of URLFetch see "
|
||||||
|
"https://urllib3.readthedocs.org/en/latest/contrib.html.",
|
||||||
|
AppEnginePlatformWarning)
|
||||||
|
|
||||||
|
RequestMethods.__init__(self, headers)
|
||||||
|
self.validate_certificate = validate_certificate
|
||||||
|
|
||||||
|
self.retries = retries or Retry.DEFAULT
|
||||||
|
|
||||||
|
def __enter__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||||
|
# Return False to re-raise any potential exceptions
|
||||||
|
return False
|
||||||
|
|
||||||
|
def urlopen(self, method, url, body=None, headers=None,
|
||||||
|
retries=None, redirect=True, timeout=Timeout.DEFAULT_TIMEOUT,
|
||||||
|
**response_kw):
|
||||||
|
|
||||||
|
retries = self._get_retries(retries, redirect)
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = urlfetch.fetch(
|
||||||
|
url,
|
||||||
|
payload=body,
|
||||||
|
method=method,
|
||||||
|
headers=headers or {},
|
||||||
|
allow_truncated=False,
|
||||||
|
follow_redirects=(
|
||||||
|
redirect and
|
||||||
|
retries.redirect != 0 and
|
||||||
|
retries.total),
|
||||||
|
deadline=self._get_absolute_timeout(timeout),
|
||||||
|
validate_certificate=self.validate_certificate,
|
||||||
|
)
|
||||||
|
except urlfetch.DeadlineExceededError as e:
|
||||||
|
raise TimeoutError(self, e)
|
||||||
|
|
||||||
|
except urlfetch.InvalidURLError as e:
|
||||||
|
if 'too large' in str(e):
|
||||||
|
raise AppEnginePlatformError(
|
||||||
|
"URLFetch request too large, URLFetch only "
|
||||||
|
"supports requests up to 10mb in size.", e)
|
||||||
|
raise ProtocolError(e)
|
||||||
|
|
||||||
|
except urlfetch.DownloadError as e:
|
||||||
|
if 'Too many redirects' in str(e):
|
||||||
|
raise MaxRetryError(self, url, reason=e)
|
||||||
|
raise ProtocolError(e)
|
||||||
|
|
||||||
|
except urlfetch.ResponseTooLargeError as e:
|
||||||
|
raise AppEnginePlatformError(
|
||||||
|
"URLFetch response too large, URLFetch only supports"
|
||||||
|
"responses up to 32mb in size.", e)
|
||||||
|
|
||||||
|
except urlfetch.SSLCertificateError as e:
|
||||||
|
raise SSLError(e)
|
||||||
|
|
||||||
|
except urlfetch.InvalidMethodError as e:
|
||||||
|
raise AppEnginePlatformError(
|
||||||
|
"URLFetch does not support method: %s" % method, e)
|
||||||
|
|
||||||
|
http_response = self._urlfetch_response_to_http_response(
|
||||||
|
response, **response_kw)
|
||||||
|
|
||||||
|
# Check for redirect response
|
||||||
|
if (http_response.get_redirect_location() and
|
||||||
|
retries.raise_on_redirect and redirect):
|
||||||
|
raise MaxRetryError(self, url, "too many redirects")
|
||||||
|
|
||||||
|
# Check if we should retry the HTTP response.
|
||||||
|
if retries.is_forced_retry(method, status_code=http_response.status):
|
||||||
|
retries = retries.increment(
|
||||||
|
method, url, response=http_response, _pool=self)
|
||||||
|
log.info("Forced retry: %s", url)
|
||||||
|
retries.sleep()
|
||||||
|
return self.urlopen(
|
||||||
|
method, url,
|
||||||
|
body=body, headers=headers,
|
||||||
|
retries=retries, redirect=redirect,
|
||||||
|
timeout=timeout, **response_kw)
|
||||||
|
|
||||||
|
return http_response
|
||||||
|
|
||||||
|
def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw):
|
||||||
|
|
||||||
|
if is_prod_appengine():
|
||||||
|
# Production GAE handles deflate encoding automatically, but does
|
||||||
|
# not remove the encoding header.
|
||||||
|
content_encoding = urlfetch_resp.headers.get('content-encoding')
|
||||||
|
|
||||||
|
if content_encoding == 'deflate':
|
||||||
|
del urlfetch_resp.headers['content-encoding']
|
||||||
|
|
||||||
|
transfer_encoding = urlfetch_resp.headers.get('transfer-encoding')
|
||||||
|
# We have a full response's content,
|
||||||
|
# so let's make sure we don't report ourselves as chunked data.
|
||||||
|
if transfer_encoding == 'chunked':
|
||||||
|
encodings = transfer_encoding.split(",")
|
||||||
|
encodings.remove('chunked')
|
||||||
|
urlfetch_resp.headers['transfer-encoding'] = ','.join(encodings)
|
||||||
|
|
||||||
|
return HTTPResponse(
|
||||||
|
# In order for decoding to work, we must present the content as
|
||||||
|
# a file-like object.
|
||||||
|
body=BytesIO(urlfetch_resp.content),
|
||||||
|
headers=urlfetch_resp.headers,
|
||||||
|
status=urlfetch_resp.status_code,
|
||||||
|
**response_kw
|
||||||
|
)
|
||||||
|
|
||||||
|
def _get_absolute_timeout(self, timeout):
|
||||||
|
if timeout is Timeout.DEFAULT_TIMEOUT:
|
||||||
|
return 5 # 5s is the default timeout for URLFetch.
|
||||||
|
if isinstance(timeout, Timeout):
|
||||||
|
if timeout._read is not timeout._connect:
|
||||||
|
warnings.warn(
|
||||||
|
"URLFetch does not support granular timeout settings, "
|
||||||
|
"reverting to total timeout.", AppEnginePlatformWarning)
|
||||||
|
return timeout.total
|
||||||
|
return timeout
|
||||||
|
|
||||||
|
def _get_retries(self, retries, redirect):
|
||||||
|
if not isinstance(retries, Retry):
|
||||||
|
retries = Retry.from_int(
|
||||||
|
retries, redirect=redirect, default=self.retries)
|
||||||
|
|
||||||
|
if retries.connect or retries.read or retries.redirect:
|
||||||
|
warnings.warn(
|
||||||
|
"URLFetch only supports total retries and does not "
|
||||||
|
"recognize connect, read, or redirect retry parameters.",
|
||||||
|
AppEnginePlatformWarning)
|
||||||
|
|
||||||
|
return retries
|
||||||
|
|
||||||
|
|
||||||
|
def is_appengine():
|
||||||
|
return (is_local_appengine() or
|
||||||
|
is_prod_appengine() or
|
||||||
|
is_prod_appengine_mvms())
|
||||||
|
|
||||||
|
|
||||||
|
def is_appengine_sandbox():
|
||||||
|
return is_appengine() and not is_prod_appengine_mvms()
|
||||||
|
|
||||||
|
|
||||||
|
def is_local_appengine():
|
||||||
|
return ('APPENGINE_RUNTIME' in os.environ and
|
||||||
|
'Development/' in os.environ['SERVER_SOFTWARE'])
|
||||||
|
|
||||||
|
|
||||||
|
def is_prod_appengine():
|
||||||
|
return ('APPENGINE_RUNTIME' in os.environ and
|
||||||
|
'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and
|
||||||
|
not is_prod_appengine_mvms())
|
||||||
|
|
||||||
|
|
||||||
|
def is_prod_appengine_mvms():
|
||||||
|
return os.environ.get('GAE_VM', False) == 'true'
|
|
@ -3,6 +3,7 @@ NTLM authenticating pool, contributed by erikcederstran
|
||||||
|
|
||||||
Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
|
Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10
|
||||||
"""
|
"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from http.client import HTTPSConnection
|
from http.client import HTTPSConnection
|
||||||
|
@ -42,8 +43,8 @@ class NTLMConnectionPool(HTTPSConnectionPool):
|
||||||
# Performs the NTLM handshake that secures the connection. The socket
|
# Performs the NTLM handshake that secures the connection. The socket
|
||||||
# must be kept open while requests are performed.
|
# must be kept open while requests are performed.
|
||||||
self.num_connections += 1
|
self.num_connections += 1
|
||||||
log.debug('Starting NTLM HTTPS connection no. %d: https://%s%s' %
|
log.debug('Starting NTLM HTTPS connection no. %d: https://%s%s',
|
||||||
(self.num_connections, self.host, self.authurl))
|
self.num_connections, self.host, self.authurl)
|
||||||
|
|
||||||
headers = {}
|
headers = {}
|
||||||
headers['Connection'] = 'Keep-Alive'
|
headers['Connection'] = 'Keep-Alive'
|
||||||
|
@ -55,13 +56,13 @@ class NTLMConnectionPool(HTTPSConnectionPool):
|
||||||
# Send negotiation message
|
# Send negotiation message
|
||||||
headers[req_header] = (
|
headers[req_header] = (
|
||||||
'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(self.rawuser))
|
'NTLM %s' % ntlm.create_NTLM_NEGOTIATE_MESSAGE(self.rawuser))
|
||||||
log.debug('Request headers: %s' % headers)
|
log.debug('Request headers: %s', headers)
|
||||||
conn.request('GET', self.authurl, None, headers)
|
conn.request('GET', self.authurl, None, headers)
|
||||||
res = conn.getresponse()
|
res = conn.getresponse()
|
||||||
reshdr = dict(res.getheaders())
|
reshdr = dict(res.getheaders())
|
||||||
log.debug('Response status: %s %s' % (res.status, res.reason))
|
log.debug('Response status: %s %s', res.status, res.reason)
|
||||||
log.debug('Response headers: %s' % reshdr)
|
log.debug('Response headers: %s', reshdr)
|
||||||
log.debug('Response data: %s [...]' % res.read(100))
|
log.debug('Response data: %s [...]', res.read(100))
|
||||||
|
|
||||||
# Remove the reference to the socket, so that it can not be closed by
|
# Remove the reference to the socket, so that it can not be closed by
|
||||||
# the response object (we want to keep the socket open)
|
# the response object (we want to keep the socket open)
|
||||||
|
@ -86,12 +87,12 @@ class NTLMConnectionPool(HTTPSConnectionPool):
|
||||||
self.pw,
|
self.pw,
|
||||||
NegotiateFlags)
|
NegotiateFlags)
|
||||||
headers[req_header] = 'NTLM %s' % auth_msg
|
headers[req_header] = 'NTLM %s' % auth_msg
|
||||||
log.debug('Request headers: %s' % headers)
|
log.debug('Request headers: %s', headers)
|
||||||
conn.request('GET', self.authurl, None, headers)
|
conn.request('GET', self.authurl, None, headers)
|
||||||
res = conn.getresponse()
|
res = conn.getresponse()
|
||||||
log.debug('Response status: %s %s' % (res.status, res.reason))
|
log.debug('Response status: %s %s', res.status, res.reason)
|
||||||
log.debug('Response headers: %s' % dict(res.getheaders()))
|
log.debug('Response headers: %s', dict(res.getheaders()))
|
||||||
log.debug('Response data: %s [...]' % res.read()[:100])
|
log.debug('Response data: %s [...]', res.read()[:100])
|
||||||
if res.status != 200:
|
if res.status != 200:
|
||||||
if res.status == 401:
|
if res.status == 401:
|
||||||
raise Exception('Server rejected request: wrong '
|
raise Exception('Server rejected request: wrong '
|
||||||
|
|
|
@ -38,13 +38,12 @@ Module Variables
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
:var DEFAULT_SSL_CIPHER_LIST: The list of supported SSL/TLS cipher suites.
|
:var DEFAULT_SSL_CIPHER_LIST: The list of supported SSL/TLS cipher suites.
|
||||||
Default: ``ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:
|
|
||||||
ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:!aNULL:!MD5:!DSS``
|
|
||||||
|
|
||||||
.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication
|
.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication
|
||||||
.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit)
|
.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit)
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT
|
from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT
|
||||||
|
@ -55,9 +54,17 @@ except SyntaxError as e:
|
||||||
import OpenSSL.SSL
|
import OpenSSL.SSL
|
||||||
from pyasn1.codec.der import decoder as der_decoder
|
from pyasn1.codec.der import decoder as der_decoder
|
||||||
from pyasn1.type import univ, constraint
|
from pyasn1.type import univ, constraint
|
||||||
from socket import _fileobject, timeout
|
from socket import timeout, error as SocketError
|
||||||
|
|
||||||
|
try: # Platform-specific: Python 2
|
||||||
|
from socket import _fileobject
|
||||||
|
except ImportError: # Platform-specific: Python 3
|
||||||
|
_fileobject = None
|
||||||
|
from urllib3.packages.backports.makefile import backport_makefile
|
||||||
|
|
||||||
import ssl
|
import ssl
|
||||||
import select
|
import select
|
||||||
|
import six
|
||||||
|
|
||||||
from .. import connection
|
from .. import connection
|
||||||
from .. import util
|
from .. import util
|
||||||
|
@ -73,6 +80,12 @@ _openssl_versions = {
|
||||||
ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,
|
ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if hasattr(ssl, 'PROTOCOL_TLSv1_1') and hasattr(OpenSSL.SSL, 'TLSv1_1_METHOD'):
|
||||||
|
_openssl_versions[ssl.PROTOCOL_TLSv1_1] = OpenSSL.SSL.TLSv1_1_METHOD
|
||||||
|
|
||||||
|
if hasattr(ssl, 'PROTOCOL_TLSv1_2') and hasattr(OpenSSL.SSL, 'TLSv1_2_METHOD'):
|
||||||
|
_openssl_versions[ssl.PROTOCOL_TLSv1_2] = OpenSSL.SSL.TLSv1_2_METHOD
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD})
|
_openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD})
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -81,27 +94,14 @@ except AttributeError:
|
||||||
_openssl_verify = {
|
_openssl_verify = {
|
||||||
ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE,
|
ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE,
|
||||||
ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER,
|
ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER,
|
||||||
ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER
|
ssl.CERT_REQUIRED:
|
||||||
+ OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
|
OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT,
|
||||||
}
|
}
|
||||||
|
|
||||||
# A secure default.
|
DEFAULT_SSL_CIPHER_LIST = util.ssl_.DEFAULT_CIPHERS.encode('ascii')
|
||||||
# Sources for more information on TLS ciphers:
|
|
||||||
#
|
|
||||||
# - https://wiki.mozilla.org/Security/Server_Side_TLS
|
|
||||||
# - https://www.ssllabs.com/projects/best-practices/index.html
|
|
||||||
# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
|
|
||||||
#
|
|
||||||
# The general intent is:
|
|
||||||
# - Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE),
|
|
||||||
# - prefer ECDHE over DHE for better performance,
|
|
||||||
# - prefer any AES-GCM over any AES-CBC for better performance and security,
|
|
||||||
# - use 3DES as fallback which is secure but slow,
|
|
||||||
# - disable NULL authentication, MD5 MACs and DSS for security reasons.
|
|
||||||
DEFAULT_SSL_CIPHER_LIST = "ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:" + \
|
|
||||||
"ECDH+AES128:DH+AES:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+3DES:" + \
|
|
||||||
"!aNULL:!MD5:!DSS"
|
|
||||||
|
|
||||||
|
# OpenSSL will only write 16K at a time
|
||||||
|
SSL_WRITE_BLOCKSIZE = 16384
|
||||||
|
|
||||||
orig_util_HAS_SNI = util.HAS_SNI
|
orig_util_HAS_SNI = util.HAS_SNI
|
||||||
orig_connection_ssl_wrap_socket = connection.ssl_wrap_socket
|
orig_connection_ssl_wrap_socket = connection.ssl_wrap_socket
|
||||||
|
@ -112,6 +112,7 @@ def inject_into_urllib3():
|
||||||
|
|
||||||
connection.ssl_wrap_socket = ssl_wrap_socket
|
connection.ssl_wrap_socket = ssl_wrap_socket
|
||||||
util.HAS_SNI = HAS_SNI
|
util.HAS_SNI = HAS_SNI
|
||||||
|
util.IS_PYOPENSSL = True
|
||||||
|
|
||||||
|
|
||||||
def extract_from_urllib3():
|
def extract_from_urllib3():
|
||||||
|
@ -119,9 +120,10 @@ def extract_from_urllib3():
|
||||||
|
|
||||||
connection.ssl_wrap_socket = orig_connection_ssl_wrap_socket
|
connection.ssl_wrap_socket = orig_connection_ssl_wrap_socket
|
||||||
util.HAS_SNI = orig_util_HAS_SNI
|
util.HAS_SNI = orig_util_HAS_SNI
|
||||||
|
util.IS_PYOPENSSL = False
|
||||||
|
|
||||||
|
|
||||||
### Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
|
# Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
|
||||||
class SubjectAltName(BaseSubjectAltName):
|
class SubjectAltName(BaseSubjectAltName):
|
||||||
'''ASN.1 implementation for subjectAltNames support'''
|
'''ASN.1 implementation for subjectAltNames support'''
|
||||||
|
|
||||||
|
@ -132,7 +134,7 @@ class SubjectAltName(BaseSubjectAltName):
|
||||||
constraint.ValueSizeConstraint(1, 1024)
|
constraint.ValueSizeConstraint(1, 1024)
|
||||||
|
|
||||||
|
|
||||||
### Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
|
# Note: This is a slightly bug-fixed version of same from ndg-httpsclient.
|
||||||
def get_subj_alt_name(peer_cert):
|
def get_subj_alt_name(peer_cert):
|
||||||
# Search through extensions
|
# Search through extensions
|
||||||
dns_name = []
|
dns_name = []
|
||||||
|
@ -143,7 +145,7 @@ def get_subj_alt_name(peer_cert):
|
||||||
for i in range(peer_cert.get_extension_count()):
|
for i in range(peer_cert.get_extension_count()):
|
||||||
ext = peer_cert.get_extension(i)
|
ext = peer_cert.get_extension(i)
|
||||||
ext_name = ext.get_short_name()
|
ext_name = ext.get_short_name()
|
||||||
if ext_name != 'subjectAltName':
|
if ext_name != b'subjectAltName':
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# PyOpenSSL returns extension data in ASN.1 encoded form
|
# PyOpenSSL returns extension data in ASN.1 encoded form
|
||||||
|
@ -175,13 +177,17 @@ class WrappedSocket(object):
|
||||||
self.socket = socket
|
self.socket = socket
|
||||||
self.suppress_ragged_eofs = suppress_ragged_eofs
|
self.suppress_ragged_eofs = suppress_ragged_eofs
|
||||||
self._makefile_refs = 0
|
self._makefile_refs = 0
|
||||||
|
self._closed = False
|
||||||
|
|
||||||
def fileno(self):
|
def fileno(self):
|
||||||
return self.socket.fileno()
|
return self.socket.fileno()
|
||||||
|
|
||||||
def makefile(self, mode, bufsize=-1):
|
# Copy-pasted from Python 3.5 source code
|
||||||
self._makefile_refs += 1
|
def _decref_socketios(self):
|
||||||
return _fileobject(self, mode, bufsize, close=True)
|
if self._makefile_refs > 0:
|
||||||
|
self._makefile_refs -= 1
|
||||||
|
if self._closed:
|
||||||
|
self.close()
|
||||||
|
|
||||||
def recv(self, *args, **kwargs):
|
def recv(self, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
|
@ -189,6 +195,11 @@ class WrappedSocket(object):
|
||||||
except OpenSSL.SSL.SysCallError as e:
|
except OpenSSL.SSL.SysCallError as e:
|
||||||
if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):
|
if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):
|
||||||
return b''
|
return b''
|
||||||
|
else:
|
||||||
|
raise SocketError(str(e))
|
||||||
|
except OpenSSL.SSL.ZeroReturnError as e:
|
||||||
|
if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
|
||||||
|
return b''
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
except OpenSSL.SSL.WantReadError:
|
except OpenSSL.SSL.WantReadError:
|
||||||
|
@ -201,6 +212,27 @@ class WrappedSocket(object):
|
||||||
else:
|
else:
|
||||||
return data
|
return data
|
||||||
|
|
||||||
|
def recv_into(self, *args, **kwargs):
|
||||||
|
try:
|
||||||
|
return self.connection.recv_into(*args, **kwargs)
|
||||||
|
except OpenSSL.SSL.SysCallError as e:
|
||||||
|
if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'):
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
raise SocketError(str(e))
|
||||||
|
except OpenSSL.SSL.ZeroReturnError as e:
|
||||||
|
if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN:
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
except OpenSSL.SSL.WantReadError:
|
||||||
|
rd, wd, ed = select.select(
|
||||||
|
[self.socket], [], [], self.socket.gettimeout())
|
||||||
|
if not rd:
|
||||||
|
raise timeout('The read operation timed out')
|
||||||
|
else:
|
||||||
|
return self.recv_into(*args, **kwargs)
|
||||||
|
|
||||||
def settimeout(self, timeout):
|
def settimeout(self, timeout):
|
||||||
return self.socket.settimeout(timeout)
|
return self.socket.settimeout(timeout)
|
||||||
|
|
||||||
|
@ -216,13 +248,22 @@ class WrappedSocket(object):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
def sendall(self, data):
|
def sendall(self, data):
|
||||||
while len(data):
|
total_sent = 0
|
||||||
sent = self._send_until_done(data)
|
while total_sent < len(data):
|
||||||
data = data[sent:]
|
sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE])
|
||||||
|
total_sent += sent
|
||||||
|
|
||||||
|
def shutdown(self):
|
||||||
|
# FIXME rethrow compatible exceptions should we ever use this
|
||||||
|
self.connection.shutdown()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
if self._makefile_refs < 1:
|
if self._makefile_refs < 1:
|
||||||
return self.connection.shutdown()
|
try:
|
||||||
|
self._closed = True
|
||||||
|
return self.connection.close()
|
||||||
|
except OpenSSL.SSL.Error:
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
self._makefile_refs -= 1
|
self._makefile_refs -= 1
|
||||||
|
|
||||||
|
@ -257,13 +298,23 @@ class WrappedSocket(object):
|
||||||
self._makefile_refs -= 1
|
self._makefile_refs -= 1
|
||||||
|
|
||||||
|
|
||||||
|
if _fileobject: # Platform-specific: Python 2
|
||||||
|
def makefile(self, mode, bufsize=-1):
|
||||||
|
self._makefile_refs += 1
|
||||||
|
return _fileobject(self, mode, bufsize, close=True)
|
||||||
|
else: # Platform-specific: Python 3
|
||||||
|
makefile = backport_makefile
|
||||||
|
|
||||||
|
WrappedSocket.makefile = makefile
|
||||||
|
|
||||||
|
|
||||||
def _verify_callback(cnx, x509, err_no, err_depth, return_code):
|
def _verify_callback(cnx, x509, err_no, err_depth, return_code):
|
||||||
return err_no == 0
|
return err_no == 0
|
||||||
|
|
||||||
|
|
||||||
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
||||||
ca_certs=None, server_hostname=None,
|
ca_certs=None, server_hostname=None,
|
||||||
ssl_version=None):
|
ssl_version=None, ca_cert_dir=None):
|
||||||
ctx = OpenSSL.SSL.Context(_openssl_versions[ssl_version])
|
ctx = OpenSSL.SSL.Context(_openssl_versions[ssl_version])
|
||||||
if certfile:
|
if certfile:
|
||||||
keyfile = keyfile or certfile # Match behaviour of the normal python ssl library
|
keyfile = keyfile or certfile # Match behaviour of the normal python ssl library
|
||||||
|
@ -272,15 +323,15 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
||||||
ctx.use_privatekey_file(keyfile)
|
ctx.use_privatekey_file(keyfile)
|
||||||
if cert_reqs != ssl.CERT_NONE:
|
if cert_reqs != ssl.CERT_NONE:
|
||||||
ctx.set_verify(_openssl_verify[cert_reqs], _verify_callback)
|
ctx.set_verify(_openssl_verify[cert_reqs], _verify_callback)
|
||||||
if ca_certs:
|
if ca_certs or ca_cert_dir:
|
||||||
try:
|
try:
|
||||||
ctx.load_verify_locations(ca_certs, None)
|
ctx.load_verify_locations(ca_certs, ca_cert_dir)
|
||||||
except OpenSSL.SSL.Error as e:
|
except OpenSSL.SSL.Error as e:
|
||||||
raise ssl.SSLError('bad ca_certs: %r' % ca_certs, e)
|
raise ssl.SSLError('bad ca_certs: %r' % ca_certs, e)
|
||||||
else:
|
else:
|
||||||
ctx.set_default_verify_paths()
|
ctx.set_default_verify_paths()
|
||||||
|
|
||||||
# Disable TLS compression to migitate CRIME attack (issue #309)
|
# Disable TLS compression to mitigate CRIME attack (issue #309)
|
||||||
OP_NO_COMPRESSION = 0x20000
|
OP_NO_COMPRESSION = 0x20000
|
||||||
ctx.set_options(OP_NO_COMPRESSION)
|
ctx.set_options(OP_NO_COMPRESSION)
|
||||||
|
|
||||||
|
@ -288,16 +339,20 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
||||||
ctx.set_cipher_list(DEFAULT_SSL_CIPHER_LIST)
|
ctx.set_cipher_list(DEFAULT_SSL_CIPHER_LIST)
|
||||||
|
|
||||||
cnx = OpenSSL.SSL.Connection(ctx, sock)
|
cnx = OpenSSL.SSL.Connection(ctx, sock)
|
||||||
|
if isinstance(server_hostname, six.text_type): # Platform-specific: Python 3
|
||||||
|
server_hostname = server_hostname.encode('utf-8')
|
||||||
cnx.set_tlsext_host_name(server_hostname)
|
cnx.set_tlsext_host_name(server_hostname)
|
||||||
cnx.set_connect_state()
|
cnx.set_connect_state()
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
cnx.do_handshake()
|
cnx.do_handshake()
|
||||||
except OpenSSL.SSL.WantReadError:
|
except OpenSSL.SSL.WantReadError:
|
||||||
select.select([sock], [], [])
|
rd, _, _ = select.select([sock], [], [], sock.gettimeout())
|
||||||
|
if not rd:
|
||||||
|
raise timeout('select timed out')
|
||||||
continue
|
continue
|
||||||
except OpenSSL.SSL.Error as e:
|
except OpenSSL.SSL.Error as e:
|
||||||
raise ssl.SSLError('bad handshake', e)
|
raise ssl.SSLError('bad handshake: %r' % e)
|
||||||
break
|
break
|
||||||
|
|
||||||
return WrappedSocket(cnx, sock)
|
return WrappedSocket(cnx, sock)
|
||||||
|
|
172
lib/requests/packages/urllib3/contrib/socks.py
Normal file
172
lib/requests/packages/urllib3/contrib/socks.py
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
SOCKS support for urllib3
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This contrib module contains provisional support for SOCKS proxies from within
|
||||||
|
urllib3. This module supports SOCKS4 (specifically the SOCKS4A variant) and
|
||||||
|
SOCKS5. To enable its functionality, either install PySocks or install this
|
||||||
|
module with the ``socks`` extra.
|
||||||
|
|
||||||
|
Known Limitations:
|
||||||
|
|
||||||
|
- Currently PySocks does not support contacting remote websites via literal
|
||||||
|
IPv6 addresses. Any such connection attempt will fail.
|
||||||
|
- Currently PySocks does not support IPv6 connections to the SOCKS proxy. Any
|
||||||
|
such connection attempt will fail.
|
||||||
|
"""
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
try:
|
||||||
|
import socks
|
||||||
|
except ImportError:
|
||||||
|
import warnings
|
||||||
|
from ..exceptions import DependencyWarning
|
||||||
|
|
||||||
|
warnings.warn((
|
||||||
|
'SOCKS support in urllib3 requires the installation of optional '
|
||||||
|
'dependencies: specifically, PySocks. For more information, see '
|
||||||
|
'https://urllib3.readthedocs.org/en/latest/contrib.html#socks-proxies'
|
||||||
|
),
|
||||||
|
DependencyWarning
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
from socket import error as SocketError, timeout as SocketTimeout
|
||||||
|
|
||||||
|
from ..connection import (
|
||||||
|
HTTPConnection, HTTPSConnection
|
||||||
|
)
|
||||||
|
from ..connectionpool import (
|
||||||
|
HTTPConnectionPool, HTTPSConnectionPool
|
||||||
|
)
|
||||||
|
from ..exceptions import ConnectTimeoutError, NewConnectionError
|
||||||
|
from ..poolmanager import PoolManager
|
||||||
|
from ..util.url import parse_url
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ssl
|
||||||
|
except ImportError:
|
||||||
|
ssl = None
|
||||||
|
|
||||||
|
|
||||||
|
class SOCKSConnection(HTTPConnection):
|
||||||
|
"""
|
||||||
|
A plain-text HTTP connection that connects via a SOCKS proxy.
|
||||||
|
"""
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self._socks_options = kwargs.pop('_socks_options')
|
||||||
|
super(SOCKSConnection, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def _new_conn(self):
|
||||||
|
"""
|
||||||
|
Establish a new connection via the SOCKS proxy.
|
||||||
|
"""
|
||||||
|
extra_kw = {}
|
||||||
|
if self.source_address:
|
||||||
|
extra_kw['source_address'] = self.source_address
|
||||||
|
|
||||||
|
if self.socket_options:
|
||||||
|
extra_kw['socket_options'] = self.socket_options
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = socks.create_connection(
|
||||||
|
(self.host, self.port),
|
||||||
|
proxy_type=self._socks_options['socks_version'],
|
||||||
|
proxy_addr=self._socks_options['proxy_host'],
|
||||||
|
proxy_port=self._socks_options['proxy_port'],
|
||||||
|
proxy_username=self._socks_options['username'],
|
||||||
|
proxy_password=self._socks_options['password'],
|
||||||
|
timeout=self.timeout,
|
||||||
|
**extra_kw
|
||||||
|
)
|
||||||
|
|
||||||
|
except SocketTimeout as e:
|
||||||
|
raise ConnectTimeoutError(
|
||||||
|
self, "Connection to %s timed out. (connect timeout=%s)" %
|
||||||
|
(self.host, self.timeout))
|
||||||
|
|
||||||
|
except socks.ProxyError as e:
|
||||||
|
# This is fragile as hell, but it seems to be the only way to raise
|
||||||
|
# useful errors here.
|
||||||
|
if e.socket_err:
|
||||||
|
error = e.socket_err
|
||||||
|
if isinstance(error, SocketTimeout):
|
||||||
|
raise ConnectTimeoutError(
|
||||||
|
self,
|
||||||
|
"Connection to %s timed out. (connect timeout=%s)" %
|
||||||
|
(self.host, self.timeout)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise NewConnectionError(
|
||||||
|
self,
|
||||||
|
"Failed to establish a new connection: %s" % error
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
raise NewConnectionError(
|
||||||
|
self,
|
||||||
|
"Failed to establish a new connection: %s" % e
|
||||||
|
)
|
||||||
|
|
||||||
|
except SocketError as e: # Defensive: PySocks should catch all these.
|
||||||
|
raise NewConnectionError(
|
||||||
|
self, "Failed to establish a new connection: %s" % e)
|
||||||
|
|
||||||
|
return conn
|
||||||
|
|
||||||
|
|
||||||
|
# We don't need to duplicate the Verified/Unverified distinction from
|
||||||
|
# urllib3/connection.py here because the HTTPSConnection will already have been
|
||||||
|
# correctly set to either the Verified or Unverified form by that module. This
|
||||||
|
# means the SOCKSHTTPSConnection will automatically be the correct type.
|
||||||
|
class SOCKSHTTPSConnection(SOCKSConnection, HTTPSConnection):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SOCKSHTTPConnectionPool(HTTPConnectionPool):
|
||||||
|
ConnectionCls = SOCKSConnection
|
||||||
|
|
||||||
|
|
||||||
|
class SOCKSHTTPSConnectionPool(HTTPSConnectionPool):
|
||||||
|
ConnectionCls = SOCKSHTTPSConnection
|
||||||
|
|
||||||
|
|
||||||
|
class SOCKSProxyManager(PoolManager):
|
||||||
|
"""
|
||||||
|
A version of the urllib3 ProxyManager that routes connections via the
|
||||||
|
defined SOCKS proxy.
|
||||||
|
"""
|
||||||
|
pool_classes_by_scheme = {
|
||||||
|
'http': SOCKSHTTPConnectionPool,
|
||||||
|
'https': SOCKSHTTPSConnectionPool,
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, proxy_url, username=None, password=None,
|
||||||
|
num_pools=10, headers=None, **connection_pool_kw):
|
||||||
|
parsed = parse_url(proxy_url)
|
||||||
|
|
||||||
|
if parsed.scheme == 'socks5':
|
||||||
|
socks_version = socks.PROXY_TYPE_SOCKS5
|
||||||
|
elif parsed.scheme == 'socks4':
|
||||||
|
socks_version = socks.PROXY_TYPE_SOCKS4
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
"Unable to determine SOCKS version from %s" % proxy_url
|
||||||
|
)
|
||||||
|
|
||||||
|
self.proxy_url = proxy_url
|
||||||
|
|
||||||
|
socks_options = {
|
||||||
|
'socks_version': socks_version,
|
||||||
|
'proxy_host': parsed.host,
|
||||||
|
'proxy_port': parsed.port,
|
||||||
|
'username': username,
|
||||||
|
'password': password,
|
||||||
|
}
|
||||||
|
connection_pool_kw['_socks_options'] = socks_options
|
||||||
|
|
||||||
|
super(SOCKSProxyManager, self).__init__(
|
||||||
|
num_pools, headers, **connection_pool_kw
|
||||||
|
)
|
||||||
|
|
||||||
|
self.pool_classes_by_scheme = SOCKSProxyManager.pool_classes_by_scheme
|
|
@ -1,16 +1,17 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
|
# Base Exceptions
|
||||||
|
|
||||||
## Base Exceptions
|
|
||||||
|
|
||||||
class HTTPError(Exception):
|
class HTTPError(Exception):
|
||||||
"Base exception used by this module."
|
"Base exception used by this module."
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class HTTPWarning(Warning):
|
class HTTPWarning(Warning):
|
||||||
"Base warning used by this module."
|
"Base warning used by this module."
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class PoolError(HTTPError):
|
class PoolError(HTTPError):
|
||||||
"Base exception for errors caused within a pool."
|
"Base exception for errors caused within a pool."
|
||||||
def __init__(self, pool, message):
|
def __init__(self, pool, message):
|
||||||
|
@ -57,7 +58,7 @@ class ProtocolError(HTTPError):
|
||||||
ConnectionError = ProtocolError
|
ConnectionError = ProtocolError
|
||||||
|
|
||||||
|
|
||||||
## Leaf Exceptions
|
# Leaf Exceptions
|
||||||
|
|
||||||
class MaxRetryError(RequestError):
|
class MaxRetryError(RequestError):
|
||||||
"""Raised when the maximum number of retries is exceeded.
|
"""Raised when the maximum number of retries is exceeded.
|
||||||
|
@ -113,6 +114,11 @@ class ConnectTimeoutError(TimeoutError):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NewConnectionError(ConnectTimeoutError, PoolError):
|
||||||
|
"Raised when we fail to establish a new connection. Usually ECONNREFUSED."
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class EmptyPoolError(PoolError):
|
class EmptyPoolError(PoolError):
|
||||||
"Raised when a pool runs out of connections and no more are allowed."
|
"Raised when a pool runs out of connections and no more are allowed."
|
||||||
pass
|
pass
|
||||||
|
@ -149,6 +155,11 @@ class SecurityWarning(HTTPWarning):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SubjectAltNameWarning(SecurityWarning):
|
||||||
|
"Warned when connecting to a host with a certificate missing a SAN."
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class InsecureRequestWarning(SecurityWarning):
|
class InsecureRequestWarning(SecurityWarning):
|
||||||
"Warned when making an unverified HTTPS request."
|
"Warned when making an unverified HTTPS request."
|
||||||
pass
|
pass
|
||||||
|
@ -157,3 +168,42 @@ class InsecureRequestWarning(SecurityWarning):
|
||||||
class SystemTimeWarning(SecurityWarning):
|
class SystemTimeWarning(SecurityWarning):
|
||||||
"Warned when system time is suspected to be wrong"
|
"Warned when system time is suspected to be wrong"
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class InsecurePlatformWarning(SecurityWarning):
|
||||||
|
"Warned when certain 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 ProxySchemeUnknown(AssertionError, ValueError):
|
||||||
|
"ProxyManager does not support the supplied scheme"
|
||||||
|
# TODO(t-8ch): Stop inheriting from AssertionError in v2.0.
|
||||||
|
|
||||||
|
def __init__(self, scheme):
|
||||||
|
message = "Not supported proxy scheme %s" % scheme
|
||||||
|
super(ProxySchemeUnknown, self).__init__(message)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
import email.utils
|
import email.utils
|
||||||
import mimetypes
|
import mimetypes
|
||||||
|
|
||||||
|
@ -35,11 +36,11 @@ def format_header_param(name, value):
|
||||||
result = '%s="%s"' % (name, value)
|
result = '%s="%s"' % (name, value)
|
||||||
try:
|
try:
|
||||||
result.encode('ascii')
|
result.encode('ascii')
|
||||||
except UnicodeEncodeError:
|
except (UnicodeEncodeError, UnicodeDecodeError):
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
return result
|
return result
|
||||||
if not six.PY3: # Python 2:
|
if not six.PY3 and isinstance(value, six.text_type): # Python 2:
|
||||||
value = value.encode('utf-8')
|
value = value.encode('utf-8')
|
||||||
value = email.utils.encode_rfc2231(value, 'utf-8')
|
value = email.utils.encode_rfc2231(value, 'utf-8')
|
||||||
value = '%s*=%s' % (name, value)
|
value = '%s*=%s' % (name, value)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
import codecs
|
import codecs
|
||||||
|
|
||||||
from uuid import uuid4
|
from uuid import uuid4
|
||||||
|
|
|
@ -2,3 +2,4 @@ from __future__ import absolute_import
|
||||||
|
|
||||||
from . import ssl_match_hostname
|
from . import ssl_match_hostname
|
||||||
|
|
||||||
|
__all__ = ('ssl_match_hostname', )
|
||||||
|
|
53
lib/requests/packages/urllib3/packages/backports/makefile.py
Normal file
53
lib/requests/packages/urllib3/packages/backports/makefile.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""
|
||||||
|
backports.makefile
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Backports the Python 3 ``socket.makefile`` method for use with anything that
|
||||||
|
wants to create a "fake" socket object.
|
||||||
|
"""
|
||||||
|
import io
|
||||||
|
|
||||||
|
from socket import SocketIO
|
||||||
|
|
||||||
|
|
||||||
|
def backport_makefile(self, mode="r", buffering=None, encoding=None,
|
||||||
|
errors=None, newline=None):
|
||||||
|
"""
|
||||||
|
Backport of ``socket.makefile`` from Python 3.5.
|
||||||
|
"""
|
||||||
|
if not set(mode) <= set(["r", "w", "b"]):
|
||||||
|
raise ValueError(
|
||||||
|
"invalid mode %r (only r, w, b allowed)" % (mode,)
|
||||||
|
)
|
||||||
|
writing = "w" in mode
|
||||||
|
reading = "r" in mode or not writing
|
||||||
|
assert reading or writing
|
||||||
|
binary = "b" in mode
|
||||||
|
rawmode = ""
|
||||||
|
if reading:
|
||||||
|
rawmode += "r"
|
||||||
|
if writing:
|
||||||
|
rawmode += "w"
|
||||||
|
raw = SocketIO(self, rawmode)
|
||||||
|
self._makefile_refs += 1
|
||||||
|
if buffering is None:
|
||||||
|
buffering = -1
|
||||||
|
if buffering < 0:
|
||||||
|
buffering = io.DEFAULT_BUFFER_SIZE
|
||||||
|
if buffering == 0:
|
||||||
|
if not binary:
|
||||||
|
raise ValueError("unbuffered streams must be binary")
|
||||||
|
return raw
|
||||||
|
if reading and writing:
|
||||||
|
buffer = io.BufferedRWPair(raw, raw, buffering)
|
||||||
|
elif reading:
|
||||||
|
buffer = io.BufferedReader(raw, buffering)
|
||||||
|
else:
|
||||||
|
assert writing
|
||||||
|
buffer = io.BufferedWriter(raw, buffering)
|
||||||
|
if binary:
|
||||||
|
return buffer
|
||||||
|
text = io.TextIOWrapper(buffer, encoding, errors, newline)
|
||||||
|
text.mode = mode
|
||||||
|
return text
|
|
@ -1,3 +1,4 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
try: # Python 3
|
try: # Python 3
|
||||||
|
@ -8,7 +9,7 @@ except ImportError:
|
||||||
from ._collections import RecentlyUsedContainer
|
from ._collections import RecentlyUsedContainer
|
||||||
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
|
from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool
|
||||||
from .connectionpool import port_by_scheme
|
from .connectionpool import port_by_scheme
|
||||||
from .exceptions import LocationValueError
|
from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown
|
||||||
from .request import RequestMethods
|
from .request import RequestMethods
|
||||||
from .util.url import parse_url
|
from .util.url import parse_url
|
||||||
from .util.retry import Retry
|
from .util.retry import Retry
|
||||||
|
@ -17,16 +18,16 @@ from .util.retry import Retry
|
||||||
__all__ = ['PoolManager', 'ProxyManager', 'proxy_from_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')
|
||||||
|
|
||||||
pool_classes_by_scheme = {
|
pool_classes_by_scheme = {
|
||||||
'http': HTTPConnectionPool,
|
'http': HTTPConnectionPool,
|
||||||
'https': HTTPSConnectionPool,
|
'https': HTTPSConnectionPool,
|
||||||
}
|
}
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs',
|
|
||||||
'ssl_version')
|
|
||||||
|
|
||||||
|
|
||||||
class PoolManager(RequestMethods):
|
class PoolManager(RequestMethods):
|
||||||
"""
|
"""
|
||||||
|
@ -64,6 +65,17 @@ class PoolManager(RequestMethods):
|
||||||
self.pools = RecentlyUsedContainer(num_pools,
|
self.pools = RecentlyUsedContainer(num_pools,
|
||||||
dispose_func=lambda p: p.close())
|
dispose_func=lambda p: p.close())
|
||||||
|
|
||||||
|
# Locally set the pool classes so other PoolManagers can override them.
|
||||||
|
self.pool_classes_by_scheme = pool_classes_by_scheme
|
||||||
|
|
||||||
|
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):
|
def _new_pool(self, scheme, host, port):
|
||||||
"""
|
"""
|
||||||
Create a new :class:`ConnectionPool` based on host, port and scheme.
|
Create a new :class:`ConnectionPool` based on host, port and scheme.
|
||||||
|
@ -72,7 +84,7 @@ class PoolManager(RequestMethods):
|
||||||
by :meth:`connection_from_url` and companion methods. It is intended
|
by :meth:`connection_from_url` and companion methods. It is intended
|
||||||
to be overridden for customization.
|
to be overridden for customization.
|
||||||
"""
|
"""
|
||||||
pool_cls = pool_classes_by_scheme[scheme]
|
pool_cls = self.pool_classes_by_scheme[scheme]
|
||||||
kwargs = self.connection_pool_kw
|
kwargs = self.connection_pool_kw
|
||||||
if scheme == 'http':
|
if scheme == 'http':
|
||||||
kwargs = self.connection_pool_kw.copy()
|
kwargs = self.connection_pool_kw.copy()
|
||||||
|
@ -167,10 +179,17 @@ class PoolManager(RequestMethods):
|
||||||
if not isinstance(retries, Retry):
|
if not isinstance(retries, Retry):
|
||||||
retries = Retry.from_int(retries, redirect=redirect)
|
retries = Retry.from_int(retries, redirect=redirect)
|
||||||
|
|
||||||
kw['retries'] = retries.increment(method, redirect_location)
|
try:
|
||||||
|
retries = retries.increment(method, url, response=response, _pool=conn)
|
||||||
|
except MaxRetryError:
|
||||||
|
if retries.raise_on_redirect:
|
||||||
|
raise
|
||||||
|
return response
|
||||||
|
|
||||||
|
kw['retries'] = retries
|
||||||
kw['redirect'] = redirect
|
kw['redirect'] = redirect
|
||||||
|
|
||||||
log.info("Redirecting %s -> %s" % (url, redirect_location))
|
log.info("Redirecting %s -> %s", url, redirect_location)
|
||||||
return self.urlopen(method, redirect_location, **kw)
|
return self.urlopen(method, redirect_location, **kw)
|
||||||
|
|
||||||
|
|
||||||
|
@ -212,8 +231,8 @@ class ProxyManager(PoolManager):
|
||||||
port = port_by_scheme.get(proxy.scheme, 80)
|
port = port_by_scheme.get(proxy.scheme, 80)
|
||||||
proxy = proxy._replace(port=port)
|
proxy = proxy._replace(port=port)
|
||||||
|
|
||||||
assert proxy.scheme in ("http", "https"), \
|
if proxy.scheme not in ("http", "https"):
|
||||||
'Not supported proxy scheme %s' % proxy.scheme
|
raise ProxySchemeUnknown(proxy.scheme)
|
||||||
|
|
||||||
self.proxy = proxy
|
self.proxy = proxy
|
||||||
self.proxy_headers = proxy_headers or {}
|
self.proxy_headers = proxy_headers or {}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
try:
|
try:
|
||||||
from urllib.parse import urlencode
|
from urllib.parse import urlencode
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -71,14 +72,22 @@ class RequestMethods(object):
|
||||||
headers=headers,
|
headers=headers,
|
||||||
**urlopen_kw)
|
**urlopen_kw)
|
||||||
|
|
||||||
def request_encode_url(self, method, url, fields=None, **urlopen_kw):
|
def request_encode_url(self, method, url, fields=None, headers=None,
|
||||||
|
**urlopen_kw):
|
||||||
"""
|
"""
|
||||||
Make a request using :meth:`urlopen` with the ``fields`` encoded in
|
Make a request using :meth:`urlopen` with the ``fields`` encoded in
|
||||||
the url. This is useful for request methods like GET, HEAD, DELETE, etc.
|
the url. This is useful for request methods like GET, HEAD, DELETE, etc.
|
||||||
"""
|
"""
|
||||||
|
if headers is None:
|
||||||
|
headers = self.headers
|
||||||
|
|
||||||
|
extra_kw = {'headers': headers}
|
||||||
|
extra_kw.update(urlopen_kw)
|
||||||
|
|
||||||
if fields:
|
if fields:
|
||||||
url += '?' + urlencode(fields)
|
url += '?' + urlencode(fields)
|
||||||
return self.urlopen(method, url, **urlopen_kw)
|
|
||||||
|
return self.urlopen(method, url, **extra_kw)
|
||||||
|
|
||||||
def request_encode_body(self, method, url, fields=None, headers=None,
|
def request_encode_body(self, method, url, fields=None, headers=None,
|
||||||
encode_multipart=True, multipart_boundary=None,
|
encode_multipart=True, multipart_boundary=None,
|
||||||
|
@ -125,7 +134,8 @@ class RequestMethods(object):
|
||||||
|
|
||||||
if fields:
|
if fields:
|
||||||
if 'body' in urlopen_kw:
|
if 'body' in urlopen_kw:
|
||||||
raise TypeError('request got values for both \'fields\' and \'body\', can only specify one.')
|
raise TypeError(
|
||||||
|
"request got values for both 'fields' and 'body', can only specify one.")
|
||||||
|
|
||||||
if encode_multipart:
|
if encode_multipart:
|
||||||
body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary)
|
body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary)
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from contextlib import contextmanager
|
||||||
import zlib
|
import zlib
|
||||||
import io
|
import io
|
||||||
from socket import timeout as SocketTimeout
|
from socket import timeout as SocketTimeout
|
||||||
|
from socket import error as SocketError
|
||||||
|
|
||||||
from ._collections import HTTPHeaderDict
|
from ._collections import HTTPHeaderDict
|
||||||
from .exceptions import ProtocolError, DecodeError, ReadTimeoutError
|
from .exceptions import (
|
||||||
from .packages.six import string_types as basestring, binary_type
|
ProtocolError, DecodeError, ReadTimeoutError, ResponseNotChunked
|
||||||
|
)
|
||||||
|
from .packages.six import string_types as basestring, binary_type, PY3
|
||||||
|
from .packages.six.moves import http_client as httplib
|
||||||
from .connection import HTTPException, BaseSSLError
|
from .connection import HTTPException, BaseSSLError
|
||||||
from .util.response import is_fp_closed
|
from .util.response import is_fp_closed, is_response_to_head
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DeflateDecoder(object):
|
class DeflateDecoder(object):
|
||||||
|
@ -21,6 +26,9 @@ class DeflateDecoder(object):
|
||||||
return getattr(self._obj, name)
|
return getattr(self._obj, name)
|
||||||
|
|
||||||
def decompress(self, data):
|
def decompress(self, data):
|
||||||
|
if not data:
|
||||||
|
return data
|
||||||
|
|
||||||
if not self._first_try:
|
if not self._first_try:
|
||||||
return self._obj.decompress(data)
|
return self._obj.decompress(data)
|
||||||
|
|
||||||
|
@ -36,9 +44,23 @@ class DeflateDecoder(object):
|
||||||
self._data = None
|
self._data = None
|
||||||
|
|
||||||
|
|
||||||
|
class GzipDecoder(object):
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self._obj = zlib.decompressobj(16 + zlib.MAX_WBITS)
|
||||||
|
|
||||||
|
def __getattr__(self, name):
|
||||||
|
return getattr(self._obj, name)
|
||||||
|
|
||||||
|
def decompress(self, data):
|
||||||
|
if not data:
|
||||||
|
return data
|
||||||
|
return self._obj.decompress(data)
|
||||||
|
|
||||||
|
|
||||||
def _get_decoder(mode):
|
def _get_decoder(mode):
|
||||||
if mode == 'gzip':
|
if mode == 'gzip':
|
||||||
return zlib.decompressobj(16 + zlib.MAX_WBITS)
|
return GzipDecoder()
|
||||||
|
|
||||||
return DeflateDecoder()
|
return DeflateDecoder()
|
||||||
|
|
||||||
|
@ -76,9 +98,10 @@ class HTTPResponse(io.IOBase):
|
||||||
strict=0, preload_content=True, decode_content=True,
|
strict=0, preload_content=True, decode_content=True,
|
||||||
original_response=None, pool=None, connection=None):
|
original_response=None, pool=None, connection=None):
|
||||||
|
|
||||||
self.headers = HTTPHeaderDict()
|
if isinstance(headers, HTTPHeaderDict):
|
||||||
if headers:
|
self.headers = headers
|
||||||
self.headers.update(headers)
|
else:
|
||||||
|
self.headers = HTTPHeaderDict(headers)
|
||||||
self.status = status
|
self.status = status
|
||||||
self.version = version
|
self.version = version
|
||||||
self.reason = reason
|
self.reason = reason
|
||||||
|
@ -100,6 +123,16 @@ class HTTPResponse(io.IOBase):
|
||||||
if hasattr(body, 'read'):
|
if hasattr(body, 'read'):
|
||||||
self._fp = body
|
self._fp = body
|
||||||
|
|
||||||
|
# Are we using the chunked-style of transfer encoding?
|
||||||
|
self.chunked = False
|
||||||
|
self.chunk_left = None
|
||||||
|
tr_enc = self.headers.get('transfer-encoding', '').lower()
|
||||||
|
# Don't incur the penalty of creating a list and then discarding it
|
||||||
|
encodings = (enc.strip() for enc in tr_enc.split(","))
|
||||||
|
if "chunked" in encodings:
|
||||||
|
self.chunked = True
|
||||||
|
|
||||||
|
# If requested, preload the body.
|
||||||
if preload_content and not self._body:
|
if preload_content and not self._body:
|
||||||
self._body = self.read(decode_content=decode_content)
|
self._body = self.read(decode_content=decode_content)
|
||||||
|
|
||||||
|
@ -140,6 +173,102 @@ class HTTPResponse(io.IOBase):
|
||||||
"""
|
"""
|
||||||
return self._fp_bytes_read
|
return self._fp_bytes_read
|
||||||
|
|
||||||
|
def _init_decoder(self):
|
||||||
|
"""
|
||||||
|
Set-up the _decoder attribute if necessar.
|
||||||
|
"""
|
||||||
|
# Note: content-encoding value should be case-insensitive, per RFC 7230
|
||||||
|
# Section 3.2
|
||||||
|
content_encoding = self.headers.get('content-encoding', '').lower()
|
||||||
|
if self._decoder is None and content_encoding in self.CONTENT_DECODERS:
|
||||||
|
self._decoder = _get_decoder(content_encoding)
|
||||||
|
|
||||||
|
def _decode(self, data, decode_content, flush_decoder):
|
||||||
|
"""
|
||||||
|
Decode the data passed in and potentially flush the decoder.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
if decode_content and self._decoder:
|
||||||
|
data = self._decoder.decompress(data)
|
||||||
|
except (IOError, zlib.error) as e:
|
||||||
|
content_encoding = self.headers.get('content-encoding', '').lower()
|
||||||
|
raise DecodeError(
|
||||||
|
"Received response with content-encoding: %s, but "
|
||||||
|
"failed to decode it." % content_encoding, e)
|
||||||
|
|
||||||
|
if flush_decoder and decode_content:
|
||||||
|
data += self._flush_decoder()
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def _flush_decoder(self):
|
||||||
|
"""
|
||||||
|
Flushes the decoder. Should only be called if the decoder is actually
|
||||||
|
being used.
|
||||||
|
"""
|
||||||
|
if self._decoder:
|
||||||
|
buf = self._decoder.decompress(b'')
|
||||||
|
return buf + self._decoder.flush()
|
||||||
|
|
||||||
|
return b''
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def _error_catcher(self):
|
||||||
|
"""
|
||||||
|
Catch low-level python exceptions, instead re-raising urllib3
|
||||||
|
variants, so that low-level exceptions are not leaked in the
|
||||||
|
high-level api.
|
||||||
|
|
||||||
|
On exit, release the connection back to the pool.
|
||||||
|
"""
|
||||||
|
clean_exit = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
yield
|
||||||
|
|
||||||
|
except SocketTimeout:
|
||||||
|
# FIXME: Ideally we'd like to include the url in the ReadTimeoutError but
|
||||||
|
# there is yet no clean way to get at it from this context.
|
||||||
|
raise ReadTimeoutError(self._pool, None, 'Read timed out.')
|
||||||
|
|
||||||
|
except BaseSSLError as e:
|
||||||
|
# FIXME: Is there a better way to differentiate between SSLErrors?
|
||||||
|
if 'read operation timed out' not in str(e): # Defensive:
|
||||||
|
# This shouldn't happen but just in case we're missing an edge
|
||||||
|
# case, let's avoid swallowing SSL errors.
|
||||||
|
raise
|
||||||
|
|
||||||
|
raise ReadTimeoutError(self._pool, None, 'Read timed out.')
|
||||||
|
|
||||||
|
except (HTTPException, SocketError) as e:
|
||||||
|
# This includes IncompleteRead.
|
||||||
|
raise ProtocolError('Connection broken: %r' % e, e)
|
||||||
|
|
||||||
|
# If no exception is thrown, we should avoid cleaning up
|
||||||
|
# unnecessarily.
|
||||||
|
clean_exit = True
|
||||||
|
finally:
|
||||||
|
# If we didn't terminate cleanly, we need to throw away our
|
||||||
|
# connection.
|
||||||
|
if not clean_exit:
|
||||||
|
# The response may not be closed but we're not going to use it
|
||||||
|
# anymore so close it now to ensure that the connection is
|
||||||
|
# released back to the pool.
|
||||||
|
if self._original_response:
|
||||||
|
self._original_response.close()
|
||||||
|
|
||||||
|
# Closing the response may not actually be sufficient to close
|
||||||
|
# everything, so if we have a hold of the connection close that
|
||||||
|
# too.
|
||||||
|
if self._connection:
|
||||||
|
self._connection.close()
|
||||||
|
|
||||||
|
# If we hold the original response but it's closed now, we should
|
||||||
|
# return the connection back to the pool.
|
||||||
|
if self._original_response and self._original_response.isclosed():
|
||||||
|
self.release_conn()
|
||||||
|
|
||||||
def read(self, amt=None, decode_content=None, cache_content=False):
|
def read(self, amt=None, decode_content=None, cache_content=False):
|
||||||
"""
|
"""
|
||||||
Similar to :meth:`httplib.HTTPResponse.read`, but with two additional
|
Similar to :meth:`httplib.HTTPResponse.read`, but with two additional
|
||||||
|
@ -161,12 +290,7 @@ class HTTPResponse(io.IOBase):
|
||||||
after having ``.read()`` the file object. (Overridden if ``amt`` is
|
after having ``.read()`` the file object. (Overridden if ``amt`` is
|
||||||
set.)
|
set.)
|
||||||
"""
|
"""
|
||||||
# Note: content-encoding value should be case-insensitive, per RFC 7230
|
self._init_decoder()
|
||||||
# Section 3.2
|
|
||||||
content_encoding = self.headers.get('content-encoding', '').lower()
|
|
||||||
if self._decoder is None:
|
|
||||||
if content_encoding in self.CONTENT_DECODERS:
|
|
||||||
self._decoder = _get_decoder(content_encoding)
|
|
||||||
if decode_content is None:
|
if decode_content is None:
|
||||||
decode_content = self.decode_content
|
decode_content = self.decode_content
|
||||||
|
|
||||||
|
@ -174,67 +298,36 @@ class HTTPResponse(io.IOBase):
|
||||||
return
|
return
|
||||||
|
|
||||||
flush_decoder = False
|
flush_decoder = False
|
||||||
|
data = None
|
||||||
|
|
||||||
try:
|
with self._error_catcher():
|
||||||
try:
|
if amt is None:
|
||||||
if amt is None:
|
# cStringIO doesn't like amt=None
|
||||||
# cStringIO doesn't like amt=None
|
data = self._fp.read()
|
||||||
data = self._fp.read()
|
flush_decoder = True
|
||||||
|
else:
|
||||||
|
cache_content = False
|
||||||
|
data = self._fp.read(amt)
|
||||||
|
if amt != 0 and not data: # Platform-specific: Buggy versions of Python.
|
||||||
|
# Close the connection when no data is returned
|
||||||
|
#
|
||||||
|
# This is redundant to what httplib/http.client _should_
|
||||||
|
# already do. However, versions of python released before
|
||||||
|
# December 15, 2012 (http://bugs.python.org/issue16298) do
|
||||||
|
# not properly close the connection in all cases. There is
|
||||||
|
# no harm in redundantly calling close.
|
||||||
|
self._fp.close()
|
||||||
flush_decoder = True
|
flush_decoder = True
|
||||||
else:
|
|
||||||
cache_content = False
|
|
||||||
data = self._fp.read(amt)
|
|
||||||
if amt != 0 and not data: # Platform-specific: Buggy versions of Python.
|
|
||||||
# Close the connection when no data is returned
|
|
||||||
#
|
|
||||||
# This is redundant to what httplib/http.client _should_
|
|
||||||
# already do. However, versions of python released before
|
|
||||||
# December 15, 2012 (http://bugs.python.org/issue16298) do
|
|
||||||
# not properly close the connection in all cases. There is
|
|
||||||
# no harm in redundantly calling close.
|
|
||||||
self._fp.close()
|
|
||||||
flush_decoder = True
|
|
||||||
|
|
||||||
except SocketTimeout:
|
|
||||||
# FIXME: Ideally we'd like to include the url in the ReadTimeoutError but
|
|
||||||
# there is yet no clean way to get at it from this context.
|
|
||||||
raise ReadTimeoutError(self._pool, None, 'Read timed out.')
|
|
||||||
|
|
||||||
except BaseSSLError as e:
|
|
||||||
# FIXME: Is there a better way to differentiate between SSLErrors?
|
|
||||||
if not 'read operation timed out' in str(e): # Defensive:
|
|
||||||
# This shouldn't happen but just in case we're missing an edge
|
|
||||||
# case, let's avoid swallowing SSL errors.
|
|
||||||
raise
|
|
||||||
|
|
||||||
raise ReadTimeoutError(self._pool, None, 'Read timed out.')
|
|
||||||
|
|
||||||
except HTTPException as e:
|
|
||||||
# This includes IncompleteRead.
|
|
||||||
raise ProtocolError('Connection broken: %r' % e, e)
|
|
||||||
|
|
||||||
|
if data:
|
||||||
self._fp_bytes_read += len(data)
|
self._fp_bytes_read += len(data)
|
||||||
|
|
||||||
try:
|
data = self._decode(data, decode_content, flush_decoder)
|
||||||
if decode_content and self._decoder:
|
|
||||||
data = self._decoder.decompress(data)
|
|
||||||
except (IOError, zlib.error) as e:
|
|
||||||
raise DecodeError(
|
|
||||||
"Received response with content-encoding: %s, but "
|
|
||||||
"failed to decode it." % content_encoding, e)
|
|
||||||
|
|
||||||
if flush_decoder and decode_content and self._decoder:
|
|
||||||
buf = self._decoder.decompress(binary_type())
|
|
||||||
data += buf + self._decoder.flush()
|
|
||||||
|
|
||||||
if cache_content:
|
if cache_content:
|
||||||
self._body = data
|
self._body = data
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|
||||||
finally:
|
|
||||||
if self._original_response and self._original_response.isclosed():
|
|
||||||
self.release_conn()
|
|
||||||
|
|
||||||
def stream(self, amt=2**16, decode_content=None):
|
def stream(self, amt=2**16, decode_content=None):
|
||||||
"""
|
"""
|
||||||
|
@ -252,11 +345,15 @@ class HTTPResponse(io.IOBase):
|
||||||
If True, will attempt to decode the body based on the
|
If True, will attempt to decode the body based on the
|
||||||
'content-encoding' header.
|
'content-encoding' header.
|
||||||
"""
|
"""
|
||||||
while not is_fp_closed(self._fp):
|
if self.chunked:
|
||||||
data = self.read(amt=amt, decode_content=decode_content)
|
for line in self.read_chunked(amt, decode_content=decode_content):
|
||||||
|
yield line
|
||||||
|
else:
|
||||||
|
while not is_fp_closed(self._fp):
|
||||||
|
data = self.read(amt=amt, decode_content=decode_content)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
yield data
|
yield data
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_httplib(ResponseCls, r, **response_kw):
|
def from_httplib(ResponseCls, r, **response_kw):
|
||||||
|
@ -267,14 +364,17 @@ class HTTPResponse(io.IOBase):
|
||||||
Remaining parameters are passed to the HTTPResponse constructor, along
|
Remaining parameters are passed to the HTTPResponse constructor, along
|
||||||
with ``original_response=r``.
|
with ``original_response=r``.
|
||||||
"""
|
"""
|
||||||
|
headers = r.msg
|
||||||
|
|
||||||
headers = HTTPHeaderDict()
|
if not isinstance(headers, HTTPHeaderDict):
|
||||||
for k, v in r.getheaders():
|
if PY3: # Python 3
|
||||||
headers.add(k, v)
|
headers = HTTPHeaderDict(headers.items())
|
||||||
|
else: # Python 2
|
||||||
|
headers = HTTPHeaderDict.from_httplib(headers)
|
||||||
|
|
||||||
# HTTPResponse objects in Python 3 don't have a .strict attribute
|
# HTTPResponse objects in Python 3 don't have a .strict attribute
|
||||||
strict = getattr(r, 'strict', 0)
|
strict = getattr(r, 'strict', 0)
|
||||||
return ResponseCls(body=r,
|
resp = ResponseCls(body=r,
|
||||||
headers=headers,
|
headers=headers,
|
||||||
status=r.status,
|
status=r.status,
|
||||||
version=r.version,
|
version=r.version,
|
||||||
|
@ -282,6 +382,7 @@ class HTTPResponse(io.IOBase):
|
||||||
strict=strict,
|
strict=strict,
|
||||||
original_response=r,
|
original_response=r,
|
||||||
**response_kw)
|
**response_kw)
|
||||||
|
return resp
|
||||||
|
|
||||||
# Backwards-compatibility methods for httplib.HTTPResponse
|
# Backwards-compatibility methods for httplib.HTTPResponse
|
||||||
def getheaders(self):
|
def getheaders(self):
|
||||||
|
@ -295,6 +396,9 @@ class HTTPResponse(io.IOBase):
|
||||||
if not self.closed:
|
if not self.closed:
|
||||||
self._fp.close()
|
self._fp.close()
|
||||||
|
|
||||||
|
if self._connection:
|
||||||
|
self._connection.close()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def closed(self):
|
def closed(self):
|
||||||
if self._fp is None:
|
if self._fp is None:
|
||||||
|
@ -331,3 +435,92 @@ class HTTPResponse(io.IOBase):
|
||||||
else:
|
else:
|
||||||
b[:len(temp)] = temp
|
b[:len(temp)] = temp
|
||||||
return len(temp)
|
return len(temp)
|
||||||
|
|
||||||
|
def _update_chunk_length(self):
|
||||||
|
# First, we'll figure out length of a chunk and then
|
||||||
|
# we'll try to read it from socket.
|
||||||
|
if self.chunk_left is not None:
|
||||||
|
return
|
||||||
|
line = self._fp.fp.readline()
|
||||||
|
line = line.split(b';', 1)[0]
|
||||||
|
try:
|
||||||
|
self.chunk_left = int(line, 16)
|
||||||
|
except ValueError:
|
||||||
|
# Invalid chunked protocol response, abort.
|
||||||
|
self.close()
|
||||||
|
raise httplib.IncompleteRead(line)
|
||||||
|
|
||||||
|
def _handle_chunk(self, amt):
|
||||||
|
returned_chunk = None
|
||||||
|
if amt is None:
|
||||||
|
chunk = self._fp._safe_read(self.chunk_left)
|
||||||
|
returned_chunk = chunk
|
||||||
|
self._fp._safe_read(2) # Toss the CRLF at the end of the chunk.
|
||||||
|
self.chunk_left = None
|
||||||
|
elif amt < self.chunk_left:
|
||||||
|
value = self._fp._safe_read(amt)
|
||||||
|
self.chunk_left = self.chunk_left - amt
|
||||||
|
returned_chunk = value
|
||||||
|
elif amt == self.chunk_left:
|
||||||
|
value = self._fp._safe_read(amt)
|
||||||
|
self._fp._safe_read(2) # Toss the CRLF at the end of the chunk.
|
||||||
|
self.chunk_left = None
|
||||||
|
returned_chunk = value
|
||||||
|
else: # amt > self.chunk_left
|
||||||
|
returned_chunk = self._fp._safe_read(self.chunk_left)
|
||||||
|
self._fp._safe_read(2) # Toss the CRLF at the end of the chunk.
|
||||||
|
self.chunk_left = None
|
||||||
|
return returned_chunk
|
||||||
|
|
||||||
|
def read_chunked(self, amt=None, decode_content=None):
|
||||||
|
"""
|
||||||
|
Similar to :meth:`HTTPResponse.read`, but with an additional
|
||||||
|
parameter: ``decode_content``.
|
||||||
|
|
||||||
|
:param decode_content:
|
||||||
|
If True, will attempt to decode the body based on the
|
||||||
|
'content-encoding' header.
|
||||||
|
"""
|
||||||
|
self._init_decoder()
|
||||||
|
# FIXME: Rewrite this method and make it a class with a better structured logic.
|
||||||
|
if not self.chunked:
|
||||||
|
raise ResponseNotChunked(
|
||||||
|
"Response is not chunked. "
|
||||||
|
"Header 'transfer-encoding: chunked' is missing.")
|
||||||
|
|
||||||
|
# Don't bother reading the body of a HEAD request.
|
||||||
|
if self._original_response and is_response_to_head(self._original_response):
|
||||||
|
self._original_response.close()
|
||||||
|
return
|
||||||
|
|
||||||
|
with self._error_catcher():
|
||||||
|
while True:
|
||||||
|
self._update_chunk_length()
|
||||||
|
if self.chunk_left == 0:
|
||||||
|
break
|
||||||
|
chunk = self._handle_chunk(amt)
|
||||||
|
decoded = self._decode(chunk, decode_content=decode_content,
|
||||||
|
flush_decoder=False)
|
||||||
|
if decoded:
|
||||||
|
yield decoded
|
||||||
|
|
||||||
|
if decode_content:
|
||||||
|
# On CPython and PyPy, we should never need to flush the
|
||||||
|
# decoder. However, on Jython we *might* need to, so
|
||||||
|
# lets defensively do it anyway.
|
||||||
|
decoded = self._flush_decoder()
|
||||||
|
if decoded: # Platform-specific: Jython.
|
||||||
|
yield decoded
|
||||||
|
|
||||||
|
# Chunk content ends with \r\n: discard it.
|
||||||
|
while True:
|
||||||
|
line = self._fp.fp.readline()
|
||||||
|
if not line:
|
||||||
|
# Some sites may not end with '\r\n'.
|
||||||
|
break
|
||||||
|
if line == b'\r\n':
|
||||||
|
break
|
||||||
|
|
||||||
|
# We read everything; close the "file".
|
||||||
|
if self._original_response:
|
||||||
|
self._original_response.close()
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
# For backwards compatibility, provide imports that used to be here.
|
# For backwards compatibility, provide imports that used to be here.
|
||||||
from .connection import is_connection_dropped
|
from .connection import is_connection_dropped
|
||||||
from .request import make_headers
|
from .request import make_headers
|
||||||
|
@ -5,6 +6,7 @@ from .response import is_fp_closed
|
||||||
from .ssl_ import (
|
from .ssl_ import (
|
||||||
SSLContext,
|
SSLContext,
|
||||||
HAS_SNI,
|
HAS_SNI,
|
||||||
|
IS_PYOPENSSL,
|
||||||
assert_fingerprint,
|
assert_fingerprint,
|
||||||
resolve_cert_reqs,
|
resolve_cert_reqs,
|
||||||
resolve_ssl_version,
|
resolve_ssl_version,
|
||||||
|
@ -22,3 +24,23 @@ from .url import (
|
||||||
split_first,
|
split_first,
|
||||||
Url,
|
Url,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
__all__ = (
|
||||||
|
'HAS_SNI',
|
||||||
|
'IS_PYOPENSSL',
|
||||||
|
'SSLContext',
|
||||||
|
'Retry',
|
||||||
|
'Timeout',
|
||||||
|
'Url',
|
||||||
|
'assert_fingerprint',
|
||||||
|
'current_time',
|
||||||
|
'is_connection_dropped',
|
||||||
|
'is_fp_closed',
|
||||||
|
'get_host',
|
||||||
|
'parse_url',
|
||||||
|
'make_headers',
|
||||||
|
'resolve_cert_reqs',
|
||||||
|
'resolve_ssl_version',
|
||||||
|
'split_first',
|
||||||
|
'ssl_wrap_socket',
|
||||||
|
)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
import socket
|
import socket
|
||||||
try:
|
try:
|
||||||
from select import poll, POLLIN
|
from select import poll, POLLIN
|
||||||
|
@ -60,6 +61,8 @@ def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
|
||||||
"""
|
"""
|
||||||
|
|
||||||
host, port = address
|
host, port = address
|
||||||
|
if host.startswith('['):
|
||||||
|
host = host.strip('[]')
|
||||||
err = None
|
err = None
|
||||||
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
|
for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM):
|
||||||
af, socktype, proto, canonname, sa = res
|
af, socktype, proto, canonname, sa = res
|
||||||
|
@ -78,15 +81,16 @@ def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
|
||||||
sock.connect(sa)
|
sock.connect(sa)
|
||||||
return sock
|
return sock
|
||||||
|
|
||||||
except socket.error as _:
|
except socket.error as e:
|
||||||
err = _
|
err = e
|
||||||
if sock is not None:
|
if sock is not None:
|
||||||
sock.close()
|
sock.close()
|
||||||
|
sock = None
|
||||||
|
|
||||||
if err is not None:
|
if err is not None:
|
||||||
raise err
|
raise err
|
||||||
else:
|
|
||||||
raise socket.error("getaddrinfo returns an empty list")
|
raise socket.error("getaddrinfo returns an empty list")
|
||||||
|
|
||||||
|
|
||||||
def _set_socket_options(sock, options):
|
def _set_socket_options(sock, options):
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
|
|
||||||
from ..packages.six import b
|
from ..packages.six import b
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
|
from ..packages.six.moves import http_client as httplib
|
||||||
|
|
||||||
|
from ..exceptions import HeaderParsingError
|
||||||
|
|
||||||
|
|
||||||
def is_fp_closed(obj):
|
def is_fp_closed(obj):
|
||||||
"""
|
"""
|
||||||
Checks whether a given file-like object is closed.
|
Checks whether a given file-like object is closed.
|
||||||
|
@ -20,3 +26,49 @@ def is_fp_closed(obj):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
raise ValueError("Unable to determine whether fp is closed.")
|
raise ValueError("Unable to determine whether fp is closed.")
|
||||||
|
|
||||||
|
|
||||||
|
def assert_header_parsing(headers):
|
||||||
|
"""
|
||||||
|
Asserts whether all headers have been successfully parsed.
|
||||||
|
Extracts encountered errors from the result of parsing headers.
|
||||||
|
|
||||||
|
Only works on Python 3.
|
||||||
|
|
||||||
|
:param headers: Headers to verify.
|
||||||
|
:type headers: `httplib.HTTPMessage`.
|
||||||
|
|
||||||
|
:raises urllib3.exceptions.HeaderParsingError:
|
||||||
|
If parsing errors are found.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# This will fail silently if we pass in the wrong kind of parameter.
|
||||||
|
# To make debugging easier add an explicit check.
|
||||||
|
if not isinstance(headers, httplib.HTTPMessage):
|
||||||
|
raise TypeError('expected httplib.Message, got {0}.'.format(
|
||||||
|
type(headers)))
|
||||||
|
|
||||||
|
defects = getattr(headers, 'defects', None)
|
||||||
|
get_payload = getattr(headers, 'get_payload', None)
|
||||||
|
|
||||||
|
unparsed_data = None
|
||||||
|
if get_payload: # Platform-specific: Python 3.
|
||||||
|
unparsed_data = get_payload()
|
||||||
|
|
||||||
|
if defects or unparsed_data:
|
||||||
|
raise HeaderParsingError(defects=defects, unparsed_data=unparsed_data)
|
||||||
|
|
||||||
|
|
||||||
|
def is_response_to_head(response):
|
||||||
|
"""
|
||||||
|
Checks whether the request of a response has been a HEAD-request.
|
||||||
|
Handles the quirks of AppEngine.
|
||||||
|
|
||||||
|
:param conn:
|
||||||
|
:type conn: :class:`httplib.HTTPResponse`
|
||||||
|
"""
|
||||||
|
# FIXME: Can we do this somehow without accessing private httplib _method?
|
||||||
|
method = response._method
|
||||||
|
if isinstance(method, int): # Platform-specific: Appengine
|
||||||
|
return method == 3
|
||||||
|
return method.upper() == 'HEAD'
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
import time
|
import time
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
@ -94,13 +95,18 @@ class Retry(object):
|
||||||
|
|
||||||
seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep
|
seconds. If the backoff_factor is 0.1, then :func:`.sleep` will sleep
|
||||||
for [0.1s, 0.2s, 0.4s, ...] between retries. It will never be longer
|
for [0.1s, 0.2s, 0.4s, ...] between retries. It will never be longer
|
||||||
than :attr:`Retry.MAX_BACKOFF`.
|
than :attr:`Retry.BACKOFF_MAX`.
|
||||||
|
|
||||||
By default, backoff is disabled (set to 0).
|
By default, backoff is disabled (set to 0).
|
||||||
|
|
||||||
:param bool raise_on_redirect: Whether, if the number of redirects is
|
:param bool raise_on_redirect: Whether, if the number of redirects is
|
||||||
exhausted, to raise a MaxRetryError, or to return a response with a
|
exhausted, to raise a MaxRetryError, or to return a response with a
|
||||||
response code in the 3xx range.
|
response code in the 3xx range.
|
||||||
|
|
||||||
|
:param bool raise_on_status: Similar meaning to ``raise_on_redirect``:
|
||||||
|
whether we should raise an exception, or return a response,
|
||||||
|
if status falls in ``status_forcelist`` range and retries have
|
||||||
|
been exhausted.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
DEFAULT_METHOD_WHITELIST = frozenset([
|
DEFAULT_METHOD_WHITELIST = frozenset([
|
||||||
|
@ -111,7 +117,8 @@ class Retry(object):
|
||||||
|
|
||||||
def __init__(self, total=10, connect=None, read=None, redirect=None,
|
def __init__(self, total=10, connect=None, read=None, redirect=None,
|
||||||
method_whitelist=DEFAULT_METHOD_WHITELIST, status_forcelist=None,
|
method_whitelist=DEFAULT_METHOD_WHITELIST, status_forcelist=None,
|
||||||
backoff_factor=0, raise_on_redirect=True, _observed_errors=0):
|
backoff_factor=0, raise_on_redirect=True, raise_on_status=True,
|
||||||
|
_observed_errors=0):
|
||||||
|
|
||||||
self.total = total
|
self.total = total
|
||||||
self.connect = connect
|
self.connect = connect
|
||||||
|
@ -126,7 +133,8 @@ class Retry(object):
|
||||||
self.method_whitelist = method_whitelist
|
self.method_whitelist = method_whitelist
|
||||||
self.backoff_factor = backoff_factor
|
self.backoff_factor = backoff_factor
|
||||||
self.raise_on_redirect = raise_on_redirect
|
self.raise_on_redirect = raise_on_redirect
|
||||||
self._observed_errors = _observed_errors # TODO: use .history instead?
|
self.raise_on_status = raise_on_status
|
||||||
|
self._observed_errors = _observed_errors # TODO: use .history instead?
|
||||||
|
|
||||||
def new(self, **kw):
|
def new(self, **kw):
|
||||||
params = dict(
|
params = dict(
|
||||||
|
@ -136,6 +144,7 @@ class Retry(object):
|
||||||
status_forcelist=self.status_forcelist,
|
status_forcelist=self.status_forcelist,
|
||||||
backoff_factor=self.backoff_factor,
|
backoff_factor=self.backoff_factor,
|
||||||
raise_on_redirect=self.raise_on_redirect,
|
raise_on_redirect=self.raise_on_redirect,
|
||||||
|
raise_on_status=self.raise_on_status,
|
||||||
_observed_errors=self._observed_errors,
|
_observed_errors=self._observed_errors,
|
||||||
)
|
)
|
||||||
params.update(kw)
|
params.update(kw)
|
||||||
|
@ -152,7 +161,7 @@ class Retry(object):
|
||||||
|
|
||||||
redirect = bool(redirect) and None
|
redirect = bool(redirect) and None
|
||||||
new_retries = cls(retries, redirect=redirect)
|
new_retries = cls(retries, redirect=redirect)
|
||||||
log.debug("Converted retries value: %r -> %r" % (retries, new_retries))
|
log.debug("Converted retries value: %r -> %r", retries, new_retries)
|
||||||
return new_retries
|
return new_retries
|
||||||
|
|
||||||
def get_backoff_time(self):
|
def get_backoff_time(self):
|
||||||
|
@ -190,7 +199,7 @@ class Retry(object):
|
||||||
return isinstance(err, (ReadTimeoutError, ProtocolError))
|
return isinstance(err, (ReadTimeoutError, ProtocolError))
|
||||||
|
|
||||||
def is_forced_retry(self, method, status_code):
|
def is_forced_retry(self, method, status_code):
|
||||||
""" Is this method/response retryable? (Based on method/codes whitelists)
|
""" Is this method/status code retryable? (Based on method/codes whitelists)
|
||||||
"""
|
"""
|
||||||
if self.method_whitelist and method.upper() not in self.method_whitelist:
|
if self.method_whitelist and method.upper() not in self.method_whitelist:
|
||||||
return False
|
return False
|
||||||
|
@ -206,7 +215,8 @@ class Retry(object):
|
||||||
|
|
||||||
return min(retry_counts) < 0
|
return min(retry_counts) < 0
|
||||||
|
|
||||||
def increment(self, method=None, url=None, response=None, error=None, _pool=None, _stacktrace=None):
|
def increment(self, method=None, url=None, response=None, error=None,
|
||||||
|
_pool=None, _stacktrace=None):
|
||||||
""" Return a new Retry object with incremented retry counters.
|
""" Return a new Retry object with incremented retry counters.
|
||||||
|
|
||||||
:param response: A response object, or None, if the server did not
|
:param response: A response object, or None, if the server did not
|
||||||
|
@ -270,11 +280,10 @@ class Retry(object):
|
||||||
if new_retry.is_exhausted():
|
if new_retry.is_exhausted():
|
||||||
raise MaxRetryError(_pool, url, error or ResponseError(cause))
|
raise MaxRetryError(_pool, url, error or ResponseError(cause))
|
||||||
|
|
||||||
log.debug("Incremented Retry for (url='%s'): %r" % (url, new_retry))
|
log.debug("Incremented Retry for (url='%s'): %r", url, new_retry)
|
||||||
|
|
||||||
return new_retry
|
return new_retry
|
||||||
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return ('{cls.__name__}(total={self.total}, connect={self.connect}, '
|
return ('{cls.__name__}(total={self.total}, connect={self.connect}, '
|
||||||
'read={self.read}, redirect={self.redirect})').format(
|
'read={self.read}, redirect={self.redirect})').format(
|
||||||
|
|
|
@ -1,17 +1,46 @@
|
||||||
from binascii import hexlify, unhexlify
|
from __future__ import absolute_import
|
||||||
from hashlib import md5, sha1
|
import errno
|
||||||
|
import warnings
|
||||||
|
import hmac
|
||||||
|
|
||||||
from ..exceptions import SSLError
|
from binascii import hexlify, unhexlify
|
||||||
|
from hashlib import md5, sha1, sha256
|
||||||
|
|
||||||
|
from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning
|
||||||
|
|
||||||
|
|
||||||
SSLContext = None
|
SSLContext = None
|
||||||
HAS_SNI = False
|
HAS_SNI = False
|
||||||
create_default_context = None
|
create_default_context = None
|
||||||
|
IS_PYOPENSSL = False
|
||||||
|
|
||||||
|
# Maps the length of a digest to a possible hash function producing this digest
|
||||||
|
HASHFUNC_MAP = {
|
||||||
|
32: md5,
|
||||||
|
40: sha1,
|
||||||
|
64: sha256,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _const_compare_digest_backport(a, b):
|
||||||
|
"""
|
||||||
|
Compare two digests of equal length in constant time.
|
||||||
|
|
||||||
|
The digests must be of type str/bytes.
|
||||||
|
Returns True if the digests match, and False otherwise.
|
||||||
|
"""
|
||||||
|
result = abs(len(a) - len(b))
|
||||||
|
for l, r in zip(bytearray(a), bytearray(b)):
|
||||||
|
result |= l ^ r
|
||||||
|
return result == 0
|
||||||
|
|
||||||
|
|
||||||
|
_const_compare_digest = getattr(hmac, 'compare_digest',
|
||||||
|
_const_compare_digest_backport)
|
||||||
|
|
||||||
import errno
|
|
||||||
import ssl
|
|
||||||
|
|
||||||
try: # Test for SSL features
|
try: # Test for SSL features
|
||||||
|
import ssl
|
||||||
from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23
|
from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23
|
||||||
from ssl import HAS_SNI # Has SNI?
|
from ssl import HAS_SNI # Has SNI?
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -24,14 +53,24 @@ except ImportError:
|
||||||
OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000
|
OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000
|
||||||
OP_NO_COMPRESSION = 0x20000
|
OP_NO_COMPRESSION = 0x20000
|
||||||
|
|
||||||
try:
|
# A secure default.
|
||||||
from ssl import _DEFAULT_CIPHERS
|
# Sources for more information on TLS ciphers:
|
||||||
except ImportError:
|
#
|
||||||
_DEFAULT_CIPHERS = (
|
# - https://wiki.mozilla.org/Security/Server_Side_TLS
|
||||||
'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
|
# - https://www.ssllabs.com/projects/best-practices/index.html
|
||||||
'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:ECDH+RC4:'
|
# - https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
|
||||||
'DH+RC4:RSA+RC4:!aNULL:!eNULL:!MD5'
|
#
|
||||||
)
|
# The general intent is:
|
||||||
|
# - Prefer cipher suites that offer perfect forward secrecy (DHE/ECDHE),
|
||||||
|
# - prefer ECDHE over DHE for better performance,
|
||||||
|
# - prefer any AES-GCM over any AES-CBC for better performance and security,
|
||||||
|
# - use 3DES as fallback which is secure but slow,
|
||||||
|
# - disable NULL authentication, MD5 MACs and DSS for security reasons.
|
||||||
|
DEFAULT_CIPHERS = (
|
||||||
|
'ECDH+AESGCM:DH+AESGCM:ECDH+AES256:DH+AES256:ECDH+AES128:DH+AES:ECDH+HIGH:'
|
||||||
|
'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:'
|
||||||
|
'!eNULL:!MD5'
|
||||||
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from ssl import SSLContext # Modern SSL?
|
from ssl import SSLContext # Modern SSL?
|
||||||
|
@ -39,7 +78,8 @@ except ImportError:
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
class SSLContext(object): # Platform-specific: Python 2 & 3.1
|
class SSLContext(object): # Platform-specific: Python 2 & 3.1
|
||||||
supports_set_ciphers = sys.version_info >= (2, 7)
|
supports_set_ciphers = ((2, 7) <= sys.version_info < (3,) or
|
||||||
|
(3, 2) <= sys.version_info)
|
||||||
|
|
||||||
def __init__(self, protocol_version):
|
def __init__(self, protocol_version):
|
||||||
self.protocol = protocol_version
|
self.protocol = protocol_version
|
||||||
|
@ -56,8 +96,11 @@ except ImportError:
|
||||||
self.certfile = certfile
|
self.certfile = certfile
|
||||||
self.keyfile = keyfile
|
self.keyfile = keyfile
|
||||||
|
|
||||||
def load_verify_locations(self, location):
|
def load_verify_locations(self, cafile=None, capath=None):
|
||||||
self.ca_certs = location
|
self.ca_certs = cafile
|
||||||
|
|
||||||
|
if capath is not None:
|
||||||
|
raise SSLError("CA directories not supported in older Pythons")
|
||||||
|
|
||||||
def set_ciphers(self, cipher_suite):
|
def set_ciphers(self, cipher_suite):
|
||||||
if not self.supports_set_ciphers:
|
if not self.supports_set_ciphers:
|
||||||
|
@ -68,13 +111,23 @@ except ImportError:
|
||||||
)
|
)
|
||||||
self.ciphers = cipher_suite
|
self.ciphers = cipher_suite
|
||||||
|
|
||||||
def wrap_socket(self, socket, server_hostname=None):
|
def wrap_socket(self, socket, server_hostname=None, server_side=False):
|
||||||
|
warnings.warn(
|
||||||
|
'A true SSLContext object is not available. This prevents '
|
||||||
|
'urllib3 from configuring SSL appropriately and may cause '
|
||||||
|
'certain SSL connections to fail. You can upgrade to a newer '
|
||||||
|
'version of Python to solve this. For more information, see '
|
||||||
|
'https://urllib3.readthedocs.org/en/latest/security.html'
|
||||||
|
'#insecureplatformwarning.',
|
||||||
|
InsecurePlatformWarning
|
||||||
|
)
|
||||||
kwargs = {
|
kwargs = {
|
||||||
'keyfile': self.keyfile,
|
'keyfile': self.keyfile,
|
||||||
'certfile': self.certfile,
|
'certfile': self.certfile,
|
||||||
'ca_certs': self.ca_certs,
|
'ca_certs': self.ca_certs,
|
||||||
'cert_reqs': self.verify_mode,
|
'cert_reqs': self.verify_mode,
|
||||||
'ssl_version': self.protocol,
|
'ssl_version': self.protocol,
|
||||||
|
'server_side': server_side,
|
||||||
}
|
}
|
||||||
if self.supports_set_ciphers: # Platform-specific: Python 2.7+
|
if self.supports_set_ciphers: # Platform-specific: Python 2.7+
|
||||||
return wrap_socket(socket, ciphers=self.ciphers, **kwargs)
|
return wrap_socket(socket, ciphers=self.ciphers, **kwargs)
|
||||||
|
@ -92,30 +145,21 @@ def assert_fingerprint(cert, fingerprint):
|
||||||
Fingerprint as string of hexdigits, can be interspersed by colons.
|
Fingerprint as string of hexdigits, can be interspersed by colons.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Maps the length of a digest to a possible hash function producing
|
|
||||||
# this digest.
|
|
||||||
hashfunc_map = {
|
|
||||||
16: md5,
|
|
||||||
20: sha1
|
|
||||||
}
|
|
||||||
|
|
||||||
fingerprint = fingerprint.replace(':', '').lower()
|
fingerprint = fingerprint.replace(':', '').lower()
|
||||||
digest_length, odd = divmod(len(fingerprint), 2)
|
digest_length = len(fingerprint)
|
||||||
|
hashfunc = HASHFUNC_MAP.get(digest_length)
|
||||||
if odd or digest_length not in hashfunc_map:
|
if not hashfunc:
|
||||||
raise SSLError('Fingerprint is of invalid length.')
|
raise SSLError(
|
||||||
|
'Fingerprint of invalid length: {0}'.format(fingerprint))
|
||||||
|
|
||||||
# We need encode() here for py32; works on py2 and p33.
|
# We need encode() here for py32; works on py2 and p33.
|
||||||
fingerprint_bytes = unhexlify(fingerprint.encode())
|
fingerprint_bytes = unhexlify(fingerprint.encode())
|
||||||
|
|
||||||
hashfunc = hashfunc_map[digest_length]
|
|
||||||
|
|
||||||
cert_digest = hashfunc(cert).digest()
|
cert_digest = hashfunc(cert).digest()
|
||||||
|
|
||||||
if not cert_digest == fingerprint_bytes:
|
if not _const_compare_digest(cert_digest, fingerprint_bytes):
|
||||||
raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".'
|
raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".'
|
||||||
.format(hexlify(fingerprint_bytes),
|
.format(fingerprint, hexlify(cert_digest)))
|
||||||
hexlify(cert_digest)))
|
|
||||||
|
|
||||||
|
|
||||||
def resolve_cert_reqs(candidate):
|
def resolve_cert_reqs(candidate):
|
||||||
|
@ -157,7 +201,7 @@ def resolve_ssl_version(candidate):
|
||||||
return candidate
|
return candidate
|
||||||
|
|
||||||
|
|
||||||
def create_urllib3_context(ssl_version=None, cert_reqs=ssl.CERT_REQUIRED,
|
def create_urllib3_context(ssl_version=None, cert_reqs=None,
|
||||||
options=None, ciphers=None):
|
options=None, ciphers=None):
|
||||||
"""All arguments have the same meaning as ``ssl_wrap_socket``.
|
"""All arguments have the same meaning as ``ssl_wrap_socket``.
|
||||||
|
|
||||||
|
@ -194,6 +238,9 @@ def create_urllib3_context(ssl_version=None, cert_reqs=ssl.CERT_REQUIRED,
|
||||||
"""
|
"""
|
||||||
context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23)
|
context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23)
|
||||||
|
|
||||||
|
# Setting the default here, as we may have no ssl module on import
|
||||||
|
cert_reqs = ssl.CERT_REQUIRED if cert_reqs is None else cert_reqs
|
||||||
|
|
||||||
if options is None:
|
if options is None:
|
||||||
options = 0
|
options = 0
|
||||||
# SSLv2 is easily broken and is considered harmful and dangerous
|
# SSLv2 is easily broken and is considered harmful and dangerous
|
||||||
|
@ -207,20 +254,23 @@ def create_urllib3_context(ssl_version=None, cert_reqs=ssl.CERT_REQUIRED,
|
||||||
context.options |= options
|
context.options |= options
|
||||||
|
|
||||||
if getattr(context, 'supports_set_ciphers', True): # Platform-specific: Python 2.6
|
if getattr(context, 'supports_set_ciphers', True): # Platform-specific: Python 2.6
|
||||||
context.set_ciphers(ciphers or _DEFAULT_CIPHERS)
|
context.set_ciphers(ciphers or DEFAULT_CIPHERS)
|
||||||
|
|
||||||
context.verify_mode = cert_reqs
|
context.verify_mode = cert_reqs
|
||||||
if getattr(context, 'check_hostname', None) is not None: # Platform-specific: Python 3.2
|
if getattr(context, 'check_hostname', None) is not None: # Platform-specific: Python 3.2
|
||||||
context.check_hostname = (context.verify_mode == ssl.CERT_REQUIRED)
|
# We do our own verification, including fingerprints and alternative
|
||||||
|
# hostnames. So disable it here
|
||||||
|
context.check_hostname = False
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
||||||
ca_certs=None, server_hostname=None,
|
ca_certs=None, server_hostname=None,
|
||||||
ssl_version=None, ciphers=None, ssl_context=None):
|
ssl_version=None, ciphers=None, ssl_context=None,
|
||||||
|
ca_cert_dir=None):
|
||||||
"""
|
"""
|
||||||
All arguments except for server_hostname and ssl_context have the same
|
All arguments except for server_hostname, ssl_context, and ca_cert_dir have
|
||||||
meaning as they do when using :func:`ssl.wrap_socket`.
|
the same meaning as they do when using :func:`ssl.wrap_socket`.
|
||||||
|
|
||||||
:param server_hostname:
|
:param server_hostname:
|
||||||
When SNI is supported, the expected hostname of the certificate
|
When SNI is supported, the expected hostname of the certificate
|
||||||
|
@ -230,15 +280,19 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
||||||
:param ciphers:
|
:param ciphers:
|
||||||
A string of ciphers we wish the client to support. This is not
|
A string of ciphers we wish the client to support. This is not
|
||||||
supported on Python 2.6 as the ssl module does not support it.
|
supported on Python 2.6 as the ssl module does not support it.
|
||||||
|
:param ca_cert_dir:
|
||||||
|
A directory containing CA certificates in multiple separate files, as
|
||||||
|
supported by OpenSSL's -CApath flag or the capath argument to
|
||||||
|
SSLContext.load_verify_locations().
|
||||||
"""
|
"""
|
||||||
context = ssl_context
|
context = ssl_context
|
||||||
if context is None:
|
if context is None:
|
||||||
context = create_urllib3_context(ssl_version, cert_reqs,
|
context = create_urllib3_context(ssl_version, cert_reqs,
|
||||||
ciphers=ciphers)
|
ciphers=ciphers)
|
||||||
|
|
||||||
if ca_certs:
|
if ca_certs or ca_cert_dir:
|
||||||
try:
|
try:
|
||||||
context.load_verify_locations(ca_certs)
|
context.load_verify_locations(ca_certs, ca_cert_dir)
|
||||||
except IOError as e: # Platform-specific: Python 2.6, 2.7, 3.2
|
except IOError as e: # Platform-specific: Python 2.6, 2.7, 3.2
|
||||||
raise SSLError(e)
|
raise SSLError(e)
|
||||||
# Py33 raises FileNotFoundError which subclasses OSError
|
# Py33 raises FileNotFoundError which subclasses OSError
|
||||||
|
@ -247,8 +301,20 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
|
||||||
if e.errno == errno.ENOENT:
|
if e.errno == errno.ENOENT:
|
||||||
raise SSLError(e)
|
raise SSLError(e)
|
||||||
raise
|
raise
|
||||||
|
|
||||||
if certfile:
|
if certfile:
|
||||||
context.load_cert_chain(certfile, keyfile)
|
context.load_cert_chain(certfile, keyfile)
|
||||||
if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI
|
if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI
|
||||||
return context.wrap_socket(sock, server_hostname=server_hostname)
|
return context.wrap_socket(sock, server_hostname=server_hostname)
|
||||||
|
|
||||||
|
warnings.warn(
|
||||||
|
'An HTTPS request has been made, but the SNI (Subject Name '
|
||||||
|
'Indication) extension to TLS is not available on this platform. '
|
||||||
|
'This may cause the server to present an incorrect TLS '
|
||||||
|
'certificate, which can cause validation failures. You can upgrade to '
|
||||||
|
'a newer version of Python to solve this. For more information, see '
|
||||||
|
'https://urllib3.readthedocs.org/en/latest/security.html'
|
||||||
|
'#snimissingwarning.',
|
||||||
|
SNIMissingWarning
|
||||||
|
)
|
||||||
return context.wrap_socket(sock)
|
return context.wrap_socket(sock)
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
# The default socket timeout, used by httplib to indicate that no timeout was
|
# The default socket timeout, used by httplib to indicate that no timeout was
|
||||||
# specified by the user
|
# specified by the user
|
||||||
from socket import _GLOBAL_DEFAULT_TIMEOUT
|
from socket import _GLOBAL_DEFAULT_TIMEOUT
|
||||||
|
@ -9,6 +10,7 @@ from ..exceptions import TimeoutStateError
|
||||||
# urllib3
|
# urllib3
|
||||||
_Default = object()
|
_Default = object()
|
||||||
|
|
||||||
|
|
||||||
def current_time():
|
def current_time():
|
||||||
"""
|
"""
|
||||||
Retrieve the current time. This function is mocked out in unit testing.
|
Retrieve the current time. This function is mocked out in unit testing.
|
||||||
|
@ -226,9 +228,9 @@ class Timeout(object):
|
||||||
has not yet been called on this object.
|
has not yet been called on this object.
|
||||||
"""
|
"""
|
||||||
if (self.total is not None and
|
if (self.total is not None and
|
||||||
self.total is not self.DEFAULT_TIMEOUT and
|
self.total is not self.DEFAULT_TIMEOUT and
|
||||||
self._read is not None and
|
self._read is not None and
|
||||||
self._read is not self.DEFAULT_TIMEOUT):
|
self._read is not self.DEFAULT_TIMEOUT):
|
||||||
# In case the connect timeout has not yet been established.
|
# In case the connect timeout has not yet been established.
|
||||||
if self._start_connect is None:
|
if self._start_connect is None:
|
||||||
return self._read
|
return self._read
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from __future__ import absolute_import
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
|
||||||
from ..exceptions import LocationParseError
|
from ..exceptions import LocationParseError
|
||||||
|
@ -15,6 +16,8 @@ class Url(namedtuple('Url', url_attrs)):
|
||||||
|
|
||||||
def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None,
|
def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None,
|
||||||
query=None, fragment=None):
|
query=None, fragment=None):
|
||||||
|
if path and not path.startswith('/'):
|
||||||
|
path = '/' + path
|
||||||
return super(Url, cls).__new__(cls, scheme, auth, host, port, path,
|
return super(Url, cls).__new__(cls, scheme, auth, host, port, path,
|
||||||
query, fragment)
|
query, fragment)
|
||||||
|
|
||||||
|
@ -83,6 +86,7 @@ class Url(namedtuple('Url', url_attrs)):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.url
|
return self.url
|
||||||
|
|
||||||
|
|
||||||
def split_first(s, delims):
|
def split_first(s, delims):
|
||||||
"""
|
"""
|
||||||
Given a string and an iterable of delimiters, split on the first found
|
Given a string and an iterable of delimiters, split on the first found
|
||||||
|
@ -113,7 +117,7 @@ def split_first(s, delims):
|
||||||
if min_idx is None or min_idx < 0:
|
if min_idx is None or min_idx < 0:
|
||||||
return s, '', None
|
return s, '', None
|
||||||
|
|
||||||
return s[:min_idx], s[min_idx+1:], min_delim
|
return s[:min_idx], s[min_idx + 1:], min_delim
|
||||||
|
|
||||||
|
|
||||||
def parse_url(url):
|
def parse_url(url):
|
||||||
|
@ -204,6 +208,7 @@ def parse_url(url):
|
||||||
|
|
||||||
return Url(scheme, auth, host, port, path, query, fragment)
|
return Url(scheme, auth, host, port, path, query, fragment)
|
||||||
|
|
||||||
|
|
||||||
def get_host(url):
|
def get_host(url):
|
||||||
"""
|
"""
|
||||||
Deprecated. Use :func:`.parse_url` instead.
|
Deprecated. Use :func:`.parse_url` instead.
|
||||||
|
|
|
@ -62,12 +62,11 @@ def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
|
||||||
merged_setting = dict_class(to_key_val_list(session_setting))
|
merged_setting = dict_class(to_key_val_list(session_setting))
|
||||||
merged_setting.update(to_key_val_list(request_setting))
|
merged_setting.update(to_key_val_list(request_setting))
|
||||||
|
|
||||||
# Remove keys that are set to None.
|
# Remove keys that are set to None. Extract keys first to avoid altering
|
||||||
for (k, v) in request_setting.items():
|
# the dictionary during iteration.
|
||||||
if v is None:
|
none_keys = [k for (k, v) in merged_setting.items() if v is None]
|
||||||
del merged_setting[k]
|
for key in none_keys:
|
||||||
|
del merged_setting[key]
|
||||||
merged_setting = dict((k, v) for (k, v) in merged_setting.items() if v is not None)
|
|
||||||
|
|
||||||
return merged_setting
|
return merged_setting
|
||||||
|
|
||||||
|
@ -90,7 +89,7 @@ def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
|
||||||
|
|
||||||
class SessionRedirectMixin(object):
|
class SessionRedirectMixin(object):
|
||||||
def resolve_redirects(self, resp, req, stream=False, timeout=None,
|
def resolve_redirects(self, resp, req, stream=False, timeout=None,
|
||||||
verify=True, cert=None, proxies=None):
|
verify=True, cert=None, proxies=None, **adapter_kwargs):
|
||||||
"""Receives a Response. Returns a generator of Responses."""
|
"""Receives a Response. Returns a generator of Responses."""
|
||||||
|
|
||||||
i = 0
|
i = 0
|
||||||
|
@ -111,13 +110,12 @@ class SessionRedirectMixin(object):
|
||||||
resp.raw.read(decode_content=False)
|
resp.raw.read(decode_content=False)
|
||||||
|
|
||||||
if i >= self.max_redirects:
|
if i >= self.max_redirects:
|
||||||
raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects)
|
raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects, response=resp)
|
||||||
|
|
||||||
# Release the connection back into the pool.
|
# Release the connection back into the pool.
|
||||||
resp.close()
|
resp.close()
|
||||||
|
|
||||||
url = resp.headers['location']
|
url = resp.headers['location']
|
||||||
method = req.method
|
|
||||||
|
|
||||||
# Handle redirection without scheme (see: RFC 1808 Section 4)
|
# Handle redirection without scheme (see: RFC 1808 Section 4)
|
||||||
if url.startswith('//'):
|
if url.startswith('//'):
|
||||||
|
@ -141,22 +139,7 @@ class SessionRedirectMixin(object):
|
||||||
if resp.is_permanent_redirect and req.url != prepared_request.url:
|
if resp.is_permanent_redirect and req.url != prepared_request.url:
|
||||||
self.redirect_cache[req.url] = prepared_request.url
|
self.redirect_cache[req.url] = prepared_request.url
|
||||||
|
|
||||||
# http://tools.ietf.org/html/rfc7231#section-6.4.4
|
self.rebuild_method(prepared_request, resp)
|
||||||
if (resp.status_code == codes.see_other and
|
|
||||||
method != 'HEAD'):
|
|
||||||
method = 'GET'
|
|
||||||
|
|
||||||
# Do what the browsers do, despite standards...
|
|
||||||
# First, turn 302s into GETs.
|
|
||||||
if resp.status_code == codes.found and method != 'HEAD':
|
|
||||||
method = 'GET'
|
|
||||||
|
|
||||||
# Second, if a POST is responded to with a 301, turn it into a GET.
|
|
||||||
# This bizarre behaviour is explained in Issue 1704.
|
|
||||||
if resp.status_code == codes.moved and method == 'POST':
|
|
||||||
method = 'GET'
|
|
||||||
|
|
||||||
prepared_request.method = method
|
|
||||||
|
|
||||||
# https://github.com/kennethreitz/requests/issues/1084
|
# https://github.com/kennethreitz/requests/issues/1084
|
||||||
if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect):
|
if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect):
|
||||||
|
@ -171,7 +154,10 @@ class SessionRedirectMixin(object):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
extract_cookies_to_jar(prepared_request._cookies, prepared_request, resp.raw)
|
# Extract any cookies sent on the response to the cookiejar
|
||||||
|
# in the new request. Because we've mutated our copied prepared
|
||||||
|
# request, use the old one that we haven't yet touched.
|
||||||
|
extract_cookies_to_jar(prepared_request._cookies, req, resp.raw)
|
||||||
prepared_request._cookies.update(self.cookies)
|
prepared_request._cookies.update(self.cookies)
|
||||||
prepared_request.prepare_cookies(prepared_request._cookies)
|
prepared_request.prepare_cookies(prepared_request._cookies)
|
||||||
|
|
||||||
|
@ -190,6 +176,7 @@ class SessionRedirectMixin(object):
|
||||||
cert=cert,
|
cert=cert,
|
||||||
proxies=proxies,
|
proxies=proxies,
|
||||||
allow_redirects=False,
|
allow_redirects=False,
|
||||||
|
**adapter_kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
|
extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
|
||||||
|
@ -259,6 +246,28 @@ class SessionRedirectMixin(object):
|
||||||
|
|
||||||
return new_proxies
|
return new_proxies
|
||||||
|
|
||||||
|
def rebuild_method(self, prepared_request, response):
|
||||||
|
"""When being redirected we may want to change the method of the request
|
||||||
|
based on certain specs or browser behavior.
|
||||||
|
"""
|
||||||
|
method = prepared_request.method
|
||||||
|
|
||||||
|
# http://tools.ietf.org/html/rfc7231#section-6.4.4
|
||||||
|
if response.status_code == codes.see_other and method != 'HEAD':
|
||||||
|
method = 'GET'
|
||||||
|
|
||||||
|
# Do what the browsers do, despite standards...
|
||||||
|
# First, turn 302s into GETs.
|
||||||
|
if response.status_code == codes.found and method != 'HEAD':
|
||||||
|
method = 'GET'
|
||||||
|
|
||||||
|
# Second, if a POST is responded to with a 301, turn it into a GET.
|
||||||
|
# This bizarre behaviour is explained in Issue 1704.
|
||||||
|
if response.status_code == codes.moved and method == 'POST':
|
||||||
|
method = 'GET'
|
||||||
|
|
||||||
|
prepared_request.method = method
|
||||||
|
|
||||||
|
|
||||||
class Session(SessionRedirectMixin):
|
class Session(SessionRedirectMixin):
|
||||||
"""A Requests session.
|
"""A Requests session.
|
||||||
|
@ -270,7 +279,13 @@ class Session(SessionRedirectMixin):
|
||||||
>>> import requests
|
>>> import requests
|
||||||
>>> s = requests.Session()
|
>>> s = requests.Session()
|
||||||
>>> s.get('http://httpbin.org/get')
|
>>> s.get('http://httpbin.org/get')
|
||||||
200
|
<Response [200]>
|
||||||
|
|
||||||
|
Or as a context manager::
|
||||||
|
|
||||||
|
>>> with requests.Session() as s:
|
||||||
|
>>> s.get('http://httpbin.org/get')
|
||||||
|
<Response [200]>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__attrs__ = [
|
__attrs__ = [
|
||||||
|
@ -290,9 +305,9 @@ class Session(SessionRedirectMixin):
|
||||||
#: :class:`Request <Request>`.
|
#: :class:`Request <Request>`.
|
||||||
self.auth = None
|
self.auth = None
|
||||||
|
|
||||||
#: Dictionary mapping protocol to the URL of the proxy (e.g.
|
#: Dictionary mapping protocol or protocol and host to the URL of the proxy
|
||||||
#: {'http': 'foo.bar:3128'}) to be used on each
|
#: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to
|
||||||
#: :class:`Request <Request>`.
|
#: be used on each :class:`Request <Request>`.
|
||||||
self.proxies = {}
|
self.proxies = {}
|
||||||
|
|
||||||
#: Event-handling hooks.
|
#: Event-handling hooks.
|
||||||
|
@ -316,7 +331,8 @@ class Session(SessionRedirectMixin):
|
||||||
#: limit, a :class:`TooManyRedirects` exception is raised.
|
#: limit, a :class:`TooManyRedirects` exception is raised.
|
||||||
self.max_redirects = DEFAULT_REDIRECT_LIMIT
|
self.max_redirects = DEFAULT_REDIRECT_LIMIT
|
||||||
|
|
||||||
#: Should we trust the environment?
|
#: Trust environment settings for proxy configuration, default
|
||||||
|
#: authentication and similar.
|
||||||
self.trust_env = True
|
self.trust_env = True
|
||||||
|
|
||||||
#: A CookieJar containing all currently outstanding cookies set on this
|
#: A CookieJar containing all currently outstanding cookies set on this
|
||||||
|
@ -401,8 +417,8 @@ class Session(SessionRedirectMixin):
|
||||||
:param url: URL for the new :class:`Request` object.
|
:param url: URL for the new :class:`Request` object.
|
||||||
:param params: (optional) Dictionary or bytes to be sent in the query
|
:param params: (optional) Dictionary or bytes to be sent in the query
|
||||||
string for the :class:`Request`.
|
string for the :class:`Request`.
|
||||||
:param data: (optional) Dictionary or bytes to send in the body of the
|
:param data: (optional) Dictionary, bytes, or file-like object to send
|
||||||
:class:`Request`.
|
in the body of the :class:`Request`.
|
||||||
:param json: (optional) json to send in the body of the
|
:param json: (optional) json to send in the body of the
|
||||||
:class:`Request`.
|
:class:`Request`.
|
||||||
:param headers: (optional) Dictionary of HTTP Headers to send with the
|
:param headers: (optional) Dictionary of HTTP Headers to send with the
|
||||||
|
@ -414,23 +430,21 @@ class Session(SessionRedirectMixin):
|
||||||
:param auth: (optional) Auth tuple or callable to enable
|
:param auth: (optional) Auth tuple or callable to enable
|
||||||
Basic/Digest/Custom HTTP Auth.
|
Basic/Digest/Custom HTTP Auth.
|
||||||
:param timeout: (optional) How long to wait for the server to send
|
:param timeout: (optional) How long to wait for the server to send
|
||||||
data before giving up, as a float, or a (`connect timeout, read
|
data before giving up, as a float, or a :ref:`(connect timeout,
|
||||||
timeout <user/advanced.html#timeouts>`_) tuple.
|
read timeout) <timeouts>` tuple.
|
||||||
:type timeout: float or tuple
|
:type timeout: float or tuple
|
||||||
:param allow_redirects: (optional) Set to True by default.
|
:param allow_redirects: (optional) Set to True by default.
|
||||||
:type allow_redirects: bool
|
:type allow_redirects: bool
|
||||||
:param proxies: (optional) Dictionary mapping protocol to the URL of
|
:param proxies: (optional) Dictionary mapping protocol or protocol and
|
||||||
the proxy.
|
hostname to the URL of the proxy.
|
||||||
:param stream: (optional) whether to immediately download the response
|
:param stream: (optional) whether to immediately download the response
|
||||||
content. Defaults to ``False``.
|
content. Defaults to ``False``.
|
||||||
:param verify: (optional) if ``True``, the SSL cert will be verified.
|
:param verify: (optional) whether the SSL cert will be verified.
|
||||||
A CA_BUNDLE path can also be provided.
|
A CA_BUNDLE path can also be provided. Defaults to ``True``.
|
||||||
:param cert: (optional) if String, path to ssl client cert file (.pem).
|
:param cert: (optional) if String, path to ssl client cert file (.pem).
|
||||||
If Tuple, ('cert', 'key') pair.
|
If Tuple, ('cert', 'key') pair.
|
||||||
"""
|
:rtype: requests.Response
|
||||||
|
"""
|
||||||
method = to_native_string(method)
|
|
||||||
|
|
||||||
# Create the Request.
|
# Create the Request.
|
||||||
req = Request(
|
req = Request(
|
||||||
method = method.upper(),
|
method = method.upper(),
|
||||||
|
@ -543,26 +557,24 @@ class Session(SessionRedirectMixin):
|
||||||
|
|
||||||
# It's possible that users might accidentally send a Request object.
|
# It's possible that users might accidentally send a Request object.
|
||||||
# Guard against that specific failure case.
|
# Guard against that specific failure case.
|
||||||
if not isinstance(request, PreparedRequest):
|
if isinstance(request, Request):
|
||||||
raise ValueError('You can only send PreparedRequests.')
|
raise ValueError('You can only send PreparedRequests.')
|
||||||
|
|
||||||
checked_urls = set()
|
|
||||||
while request.url in self.redirect_cache:
|
|
||||||
checked_urls.add(request.url)
|
|
||||||
new_url = self.redirect_cache.get(request.url)
|
|
||||||
if new_url in checked_urls:
|
|
||||||
break
|
|
||||||
request.url = new_url
|
|
||||||
|
|
||||||
# Set up variables needed for resolve_redirects and dispatching of hooks
|
# Set up variables needed for resolve_redirects and dispatching of hooks
|
||||||
allow_redirects = kwargs.pop('allow_redirects', True)
|
allow_redirects = kwargs.pop('allow_redirects', True)
|
||||||
stream = kwargs.get('stream')
|
stream = kwargs.get('stream')
|
||||||
timeout = kwargs.get('timeout')
|
|
||||||
verify = kwargs.get('verify')
|
|
||||||
cert = kwargs.get('cert')
|
|
||||||
proxies = kwargs.get('proxies')
|
|
||||||
hooks = request.hooks
|
hooks = request.hooks
|
||||||
|
|
||||||
|
# Resolve URL in redirect cache, if available.
|
||||||
|
if allow_redirects:
|
||||||
|
checked_urls = set()
|
||||||
|
while request.url in self.redirect_cache:
|
||||||
|
checked_urls.add(request.url)
|
||||||
|
new_url = self.redirect_cache.get(request.url)
|
||||||
|
if new_url in checked_urls:
|
||||||
|
break
|
||||||
|
request.url = new_url
|
||||||
|
|
||||||
# Get the appropriate adapter to use
|
# Get the appropriate adapter to use
|
||||||
adapter = self.get_adapter(url=request.url)
|
adapter = self.get_adapter(url=request.url)
|
||||||
|
|
||||||
|
@ -588,12 +600,7 @@ class Session(SessionRedirectMixin):
|
||||||
extract_cookies_to_jar(self.cookies, request, r.raw)
|
extract_cookies_to_jar(self.cookies, request, r.raw)
|
||||||
|
|
||||||
# Redirect resolving generator.
|
# Redirect resolving generator.
|
||||||
gen = self.resolve_redirects(r, request,
|
gen = self.resolve_redirects(r, request, **kwargs)
|
||||||
stream=stream,
|
|
||||||
timeout=timeout,
|
|
||||||
verify=verify,
|
|
||||||
cert=cert,
|
|
||||||
proxies=proxies)
|
|
||||||
|
|
||||||
# Resolve redirects if allowed.
|
# Resolve redirects if allowed.
|
||||||
history = [resp for resp in gen] if allow_redirects else []
|
history = [resp for resp in gen] if allow_redirects else []
|
||||||
|
@ -636,7 +643,7 @@ class Session(SessionRedirectMixin):
|
||||||
'cert': cert}
|
'cert': cert}
|
||||||
|
|
||||||
def get_adapter(self, url):
|
def get_adapter(self, url):
|
||||||
"""Returns the appropriate connnection adapter for the given URL."""
|
"""Returns the appropriate connection adapter for the given URL."""
|
||||||
for (prefix, adapter) in self.adapters.items():
|
for (prefix, adapter) in self.adapters.items():
|
||||||
|
|
||||||
if url.lower().startswith(prefix):
|
if url.lower().startswith(prefix):
|
||||||
|
|
|
@ -53,6 +53,7 @@ _codes = {
|
||||||
416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'),
|
416: ('requested_range_not_satisfiable', 'requested_range', 'range_not_satisfiable'),
|
||||||
417: ('expectation_failed',),
|
417: ('expectation_failed',),
|
||||||
418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'),
|
418: ('im_a_teapot', 'teapot', 'i_am_a_teapot'),
|
||||||
|
421: ('misdirected_request',),
|
||||||
422: ('unprocessable_entity', 'unprocessable'),
|
422: ('unprocessable_entity', 'unprocessable'),
|
||||||
423: ('locked',),
|
423: ('locked',),
|
||||||
424: ('failed_dependency', 'dependency'),
|
424: ('failed_dependency', 'dependency'),
|
||||||
|
@ -78,11 +79,12 @@ _codes = {
|
||||||
507: ('insufficient_storage',),
|
507: ('insufficient_storage',),
|
||||||
509: ('bandwidth_limit_exceeded', 'bandwidth'),
|
509: ('bandwidth_limit_exceeded', 'bandwidth'),
|
||||||
510: ('not_extended',),
|
510: ('not_extended',),
|
||||||
|
511: ('network_authentication_required', 'network_auth', 'network_authentication'),
|
||||||
}
|
}
|
||||||
|
|
||||||
codes = LookupDict(name='status_codes')
|
codes = LookupDict(name='status_codes')
|
||||||
|
|
||||||
for (code, titles) in list(_codes.items()):
|
for code, titles in _codes.items():
|
||||||
for title in titles:
|
for title in titles:
|
||||||
setattr(codes, title, code)
|
setattr(codes, title, code)
|
||||||
if not title.startswith('\\'):
|
if not title.startswith('\\'):
|
||||||
|
|
|
@ -10,6 +10,8 @@ Data structures that power Requests.
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
|
from .compat import OrderedDict
|
||||||
|
|
||||||
|
|
||||||
class CaseInsensitiveDict(collections.MutableMapping):
|
class CaseInsensitiveDict(collections.MutableMapping):
|
||||||
"""
|
"""
|
||||||
|
@ -40,7 +42,7 @@ class CaseInsensitiveDict(collections.MutableMapping):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def __init__(self, data=None, **kwargs):
|
def __init__(self, data=None, **kwargs):
|
||||||
self._store = dict()
|
self._store = OrderedDict()
|
||||||
if data is None:
|
if data is None:
|
||||||
data = {}
|
data = {}
|
||||||
self.update(data, **kwargs)
|
self.update(data, **kwargs)
|
||||||
|
|
|
@ -14,9 +14,7 @@ import codecs
|
||||||
import collections
|
import collections
|
||||||
import io
|
import io
|
||||||
import os
|
import os
|
||||||
import platform
|
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import warnings
|
import warnings
|
||||||
|
@ -25,10 +23,11 @@ from . import __version__
|
||||||
from . import certs
|
from . import certs
|
||||||
from .compat import parse_http_list as _parse_list_header
|
from .compat import parse_http_list as _parse_list_header
|
||||||
from .compat import (quote, urlparse, bytes, str, OrderedDict, unquote, is_py2,
|
from .compat import (quote, urlparse, bytes, str, OrderedDict, unquote, is_py2,
|
||||||
builtin_str, getproxies, proxy_bypass, urlunparse)
|
builtin_str, getproxies, proxy_bypass, urlunparse,
|
||||||
|
basestring)
|
||||||
from .cookies import RequestsCookieJar, cookiejar_from_dict
|
from .cookies import RequestsCookieJar, cookiejar_from_dict
|
||||||
from .structures import CaseInsensitiveDict
|
from .structures import CaseInsensitiveDict
|
||||||
from .exceptions import InvalidURL
|
from .exceptions import InvalidURL, FileModeWarning
|
||||||
|
|
||||||
_hush_pyflakes = (RequestsCookieJar,)
|
_hush_pyflakes = (RequestsCookieJar,)
|
||||||
|
|
||||||
|
@ -47,26 +46,54 @@ def dict_to_sequence(d):
|
||||||
|
|
||||||
|
|
||||||
def super_len(o):
|
def super_len(o):
|
||||||
|
total_length = 0
|
||||||
|
current_position = 0
|
||||||
|
|
||||||
if hasattr(o, '__len__'):
|
if hasattr(o, '__len__'):
|
||||||
return len(o)
|
total_length = len(o)
|
||||||
|
|
||||||
if hasattr(o, 'len'):
|
elif hasattr(o, 'len'):
|
||||||
return o.len
|
total_length = o.len
|
||||||
|
|
||||||
if hasattr(o, 'fileno'):
|
elif hasattr(o, 'getvalue'):
|
||||||
|
# e.g. BytesIO, cStringIO.StringIO
|
||||||
|
total_length = len(o.getvalue())
|
||||||
|
|
||||||
|
elif hasattr(o, 'fileno'):
|
||||||
try:
|
try:
|
||||||
fileno = o.fileno()
|
fileno = o.fileno()
|
||||||
except io.UnsupportedOperation:
|
except io.UnsupportedOperation:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
return os.fstat(fileno).st_size
|
total_length = os.fstat(fileno).st_size
|
||||||
|
|
||||||
if hasattr(o, 'getvalue'):
|
# Having used fstat to determine the file length, we need to
|
||||||
# e.g. BytesIO, cStringIO.StringIO
|
# confirm that this file was opened up in binary mode.
|
||||||
return len(o.getvalue())
|
if 'b' not in o.mode:
|
||||||
|
warnings.warn((
|
||||||
|
"Requests has determined the content-length for this "
|
||||||
|
"request using the binary size of the file: however, the "
|
||||||
|
"file has been opened in text mode (i.e. without the 'b' "
|
||||||
|
"flag in the mode). This may lead to an incorrect "
|
||||||
|
"content-length. In Requests 3.0, support will be removed "
|
||||||
|
"for files in text mode."),
|
||||||
|
FileModeWarning
|
||||||
|
)
|
||||||
|
|
||||||
|
if hasattr(o, 'tell'):
|
||||||
|
try:
|
||||||
|
current_position = o.tell()
|
||||||
|
except (OSError, IOError):
|
||||||
|
# This can happen in some weird situations, such as when the file
|
||||||
|
# is actually a special file descriptor like stdin. In this
|
||||||
|
# instance, we don't know what the length is, so set it to zero and
|
||||||
|
# let requests chunk it instead.
|
||||||
|
current_position = total_length
|
||||||
|
|
||||||
|
return max(0, total_length - current_position)
|
||||||
|
|
||||||
|
|
||||||
def get_netrc_auth(url):
|
def get_netrc_auth(url, raise_errors=False):
|
||||||
"""Returns the Requests tuple auth for a given url from netrc."""
|
"""Returns the Requests tuple auth for a given url from netrc."""
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -93,8 +120,12 @@ def get_netrc_auth(url):
|
||||||
|
|
||||||
ri = urlparse(url)
|
ri = urlparse(url)
|
||||||
|
|
||||||
# Strip port numbers from netloc
|
# Strip port numbers from netloc. This weird `if...encode`` dance is
|
||||||
host = ri.netloc.split(':')[0]
|
# used for Python 3.2, which doesn't support unicode literals.
|
||||||
|
splitstr = b':'
|
||||||
|
if isinstance(url, str):
|
||||||
|
splitstr = splitstr.decode('ascii')
|
||||||
|
host = ri.netloc.split(splitstr)[0]
|
||||||
|
|
||||||
try:
|
try:
|
||||||
_netrc = netrc(netrc_path).authenticators(host)
|
_netrc = netrc(netrc_path).authenticators(host)
|
||||||
|
@ -104,8 +135,9 @@ def get_netrc_auth(url):
|
||||||
return (_netrc[login_i], _netrc[2])
|
return (_netrc[login_i], _netrc[2])
|
||||||
except (NetrcParseError, IOError):
|
except (NetrcParseError, IOError):
|
||||||
# If there was a parsing error or a permissions issue reading the file,
|
# If there was a parsing error or a permissions issue reading the file,
|
||||||
# we'll just skip netrc auth
|
# we'll just skip netrc auth unless explicitly asked to raise errors.
|
||||||
pass
|
if raise_errors:
|
||||||
|
raise
|
||||||
|
|
||||||
# AppEngine hackiness.
|
# AppEngine hackiness.
|
||||||
except (ImportError, AttributeError):
|
except (ImportError, AttributeError):
|
||||||
|
@ -115,7 +147,8 @@ def get_netrc_auth(url):
|
||||||
def guess_filename(obj):
|
def guess_filename(obj):
|
||||||
"""Tries to guess the filename of the given object."""
|
"""Tries to guess the filename of the given object."""
|
||||||
name = getattr(obj, 'name', None)
|
name = getattr(obj, 'name', None)
|
||||||
if name and isinstance(name, builtin_str) and name[0] != '<' and name[-1] != '>':
|
if (name and isinstance(name, basestring) and name[0] != '<' and
|
||||||
|
name[-1] != '>'):
|
||||||
return os.path.basename(name)
|
return os.path.basename(name)
|
||||||
|
|
||||||
|
|
||||||
|
@ -418,10 +451,18 @@ def requote_uri(uri):
|
||||||
This function passes the given URI through an unquote/quote cycle to
|
This function passes the given URI through an unquote/quote cycle to
|
||||||
ensure that it is fully and consistently quoted.
|
ensure that it is fully and consistently quoted.
|
||||||
"""
|
"""
|
||||||
# Unquote only the unreserved characters
|
safe_with_percent = "!#$%&'()*+,/:;=?@[]~"
|
||||||
# Then quote only illegal characters (do not quote reserved, unreserved,
|
safe_without_percent = "!#$&'()*+,/:;=?@[]~"
|
||||||
# or '%')
|
try:
|
||||||
return quote(unquote_unreserved(uri), safe="!#$%&'()*+,/:;=?@[]~")
|
# Unquote only the unreserved characters
|
||||||
|
# Then quote only illegal characters (do not quote reserved,
|
||||||
|
# unreserved, or '%')
|
||||||
|
return quote(unquote_unreserved(uri), safe=safe_with_percent)
|
||||||
|
except InvalidURL:
|
||||||
|
# We couldn't unquote the given URI, so let's try quoting it, but
|
||||||
|
# there may be unquoted '%'s in the URI. We need to make sure they're
|
||||||
|
# properly quoted so they do not cause issues elsewhere.
|
||||||
|
return quote(uri, safe=safe_without_percent)
|
||||||
|
|
||||||
|
|
||||||
def address_in_network(ip, net):
|
def address_in_network(ip, net):
|
||||||
|
@ -488,7 +529,9 @@ def should_bypass_proxies(url):
|
||||||
if no_proxy:
|
if no_proxy:
|
||||||
# We need to check whether we match here. We need to see if we match
|
# We need to check whether we match here. We need to see if we match
|
||||||
# the end of the netloc, both with and without the port.
|
# the end of the netloc, both with and without the port.
|
||||||
no_proxy = no_proxy.replace(' ', '').split(',')
|
no_proxy = (
|
||||||
|
host for host in no_proxy.replace(' ', '').split(',') if host
|
||||||
|
)
|
||||||
|
|
||||||
ip = netloc.split(':')[0]
|
ip = netloc.split(':')[0]
|
||||||
if is_ipv4_address(ip):
|
if is_ipv4_address(ip):
|
||||||
|
@ -519,6 +562,7 @@ def should_bypass_proxies(url):
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_environ_proxies(url):
|
def get_environ_proxies(url):
|
||||||
"""Return a dict of environment proxies."""
|
"""Return a dict of environment proxies."""
|
||||||
if should_bypass_proxies(url):
|
if should_bypass_proxies(url):
|
||||||
|
@ -527,35 +571,26 @@ def get_environ_proxies(url):
|
||||||
return getproxies()
|
return getproxies()
|
||||||
|
|
||||||
|
|
||||||
|
def select_proxy(url, proxies):
|
||||||
|
"""Select a proxy for the url, if applicable.
|
||||||
|
|
||||||
|
:param url: The url being for the request
|
||||||
|
:param proxies: A dictionary of schemes or schemes and hosts to proxy URLs
|
||||||
|
"""
|
||||||
|
proxies = proxies or {}
|
||||||
|
urlparts = urlparse(url)
|
||||||
|
if urlparts.hostname is None:
|
||||||
|
proxy = None
|
||||||
|
else:
|
||||||
|
proxy = proxies.get(urlparts.scheme+'://'+urlparts.hostname)
|
||||||
|
if proxy is None:
|
||||||
|
proxy = proxies.get(urlparts.scheme)
|
||||||
|
return proxy
|
||||||
|
|
||||||
|
|
||||||
def default_user_agent(name="python-requests"):
|
def default_user_agent(name="python-requests"):
|
||||||
"""Return a string representing the default user agent."""
|
"""Return a string representing the default user agent."""
|
||||||
_implementation = platform.python_implementation()
|
return '%s/%s' % (name, __version__)
|
||||||
|
|
||||||
if _implementation == 'CPython':
|
|
||||||
_implementation_version = platform.python_version()
|
|
||||||
elif _implementation == 'PyPy':
|
|
||||||
_implementation_version = '%s.%s.%s' % (sys.pypy_version_info.major,
|
|
||||||
sys.pypy_version_info.minor,
|
|
||||||
sys.pypy_version_info.micro)
|
|
||||||
if sys.pypy_version_info.releaselevel != 'final':
|
|
||||||
_implementation_version = ''.join([_implementation_version, sys.pypy_version_info.releaselevel])
|
|
||||||
elif _implementation == 'Jython':
|
|
||||||
_implementation_version = platform.python_version() # Complete Guess
|
|
||||||
elif _implementation == 'IronPython':
|
|
||||||
_implementation_version = platform.python_version() # Complete Guess
|
|
||||||
else:
|
|
||||||
_implementation_version = 'Unknown'
|
|
||||||
|
|
||||||
try:
|
|
||||||
p_system = platform.system()
|
|
||||||
p_release = platform.release()
|
|
||||||
except IOError:
|
|
||||||
p_system = 'Unknown'
|
|
||||||
p_release = 'Unknown'
|
|
||||||
|
|
||||||
return " ".join(['%s/%s' % (name, __version__),
|
|
||||||
'%s/%s' % (_implementation, _implementation_version),
|
|
||||||
'%s/%s' % (p_system, p_release)])
|
|
||||||
|
|
||||||
|
|
||||||
def default_headers():
|
def default_headers():
|
||||||
|
@ -576,21 +611,19 @@ def parse_header_links(value):
|
||||||
|
|
||||||
links = []
|
links = []
|
||||||
|
|
||||||
replace_chars = " '\""
|
replace_chars = ' \'"'
|
||||||
|
|
||||||
for val in re.split(", *<", value):
|
for val in re.split(', *<', value):
|
||||||
try:
|
try:
|
||||||
url, params = val.split(";", 1)
|
url, params = val.split(';', 1)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
url, params = val, ''
|
url, params = val, ''
|
||||||
|
|
||||||
link = {}
|
link = {'url': url.strip('<> \'"')}
|
||||||
|
|
||||||
link["url"] = url.strip("<> '\"")
|
for param in params.split(';'):
|
||||||
|
|
||||||
for param in params.split(";"):
|
|
||||||
try:
|
try:
|
||||||
key, value = param.split("=")
|
key, value = param.split('=')
|
||||||
except ValueError:
|
except ValueError:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
@ -637,8 +670,8 @@ def guess_json_utf(data):
|
||||||
|
|
||||||
|
|
||||||
def prepend_scheme_if_needed(url, new_scheme):
|
def prepend_scheme_if_needed(url, new_scheme):
|
||||||
'''Given a URL that may or may not have a scheme, prepend the given scheme.
|
"""Given a URL that may or may not have a scheme, prepend the given scheme.
|
||||||
Does not replace a present scheme with the one provided as an argument.'''
|
Does not replace a present scheme with the one provided as an argument."""
|
||||||
scheme, netloc, path, params, query, fragment = urlparse(url, new_scheme)
|
scheme, netloc, path, params, query, fragment = urlparse(url, new_scheme)
|
||||||
|
|
||||||
# urlparse is a finicky beast, and sometimes decides that there isn't a
|
# urlparse is a finicky beast, and sometimes decides that there isn't a
|
||||||
|
@ -669,8 +702,6 @@ def to_native_string(string, encoding='ascii'):
|
||||||
string in the native string type, encoding and decoding where necessary.
|
string in the native string type, encoding and decoding where necessary.
|
||||||
This assumes ASCII unless told otherwise.
|
This assumes ASCII unless told otherwise.
|
||||||
"""
|
"""
|
||||||
out = None
|
|
||||||
|
|
||||||
if isinstance(string, builtin_str):
|
if isinstance(string, builtin_str):
|
||||||
out = string
|
out = string
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue