From ca187abd13052fee100909076358fca89b473e0f Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 11 Jan 2014 09:59:23 +0000 Subject: [PATCH 001/545] Unquote the auth after splitting the url. --- requests/utils.py | 11 +++++++---- test_requests.py | 6 +++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/requests/utils.py b/requests/utils.py index 7a4d44d5..168ff02e 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -635,11 +635,14 @@ def get_auth_from_url(url): """Given a url with authentication components, extract them into a tuple of username,password.""" if url: - url = unquote(url) parsed = urlparse(url) - return (parsed.username, parsed.password) - else: - return ('', '') + + try: + return (unquote(parsed.username), unquote(parsed.password)) + except AttributeError: + pass + + return ('', '') def to_native_string(string, encoding='ascii'): diff --git a/test_requests.py b/test_requests.py index 1e8e723f..4c1a3692 100755 --- a/test_requests.py +++ b/test_requests.py @@ -194,7 +194,7 @@ class RequestsTestCase(unittest.TestCase): assert r.json()['cookies']['foo'] == 'bar' # Make sure the session cj is still the custom one assert s.cookies is cj - + def test_param_cookiejar_works(self): cj = cookielib.CookieJar() cookiejar_from_dict({'foo' : 'bar'}, cj) @@ -705,6 +705,10 @@ class RequestsTestCase(unittest.TestCase): url = 'http://user%user:pass@complex.url.com/path?query=yes' assert ('user%user', 'pass') == requests.utils.get_auth_from_url(url) + def test_get_auth_from_url_encoded_hashes(self): + url = 'http://user:pass%23pass@complex.url.com/path?query=yes' + assert ('user', 'pass#pass') == requests.utils.get_auth_from_url(url) + def test_cannot_send_unprepared_requests(self): r = requests.Request(url=HTTPBIN) with pytest.raises(ValueError): From dddb41e34906c1b7eff389921c7b1bb6d734a433 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 11 Jan 2014 10:15:53 +0000 Subject: [PATCH 002/545] TypeError, not AttributeError on 3.3. --- requests/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/utils.py b/requests/utils.py index 168ff02e..13b649d3 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -639,7 +639,7 @@ def get_auth_from_url(url): try: return (unquote(parsed.username), unquote(parsed.password)) - except AttributeError: + except (AttributeError, TypeError): pass return ('', '') From b4ca6c95b60230ad604f8b89440fef18cfaa3a8c Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sun, 12 Jan 2014 09:39:32 +0000 Subject: [PATCH 003/545] Meet @sigmavirus24's demanding stylistic criteria. --- requests/utils.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/requests/utils.py b/requests/utils.py index 13b649d3..528e7c90 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -638,11 +638,13 @@ def get_auth_from_url(url): parsed = urlparse(url) try: - return (unquote(parsed.username), unquote(parsed.password)) + auth = (unquote(parsed.username), unquote(parsed.password)) except (AttributeError, TypeError): - pass + auth = ('', '') + else: + auth = ('', '') - return ('', '') + return auth def to_native_string(string, encoding='ascii'): From 2f98ef17924d296a4871d909e43f5e7e365d05a3 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sun, 12 Jan 2014 14:44:53 +0000 Subject: [PATCH 004/545] Be less defensive in get_auth_from_url. --- requests/utils.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/requests/utils.py b/requests/utils.py index 528e7c90..7b7ff0a7 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -634,14 +634,11 @@ def except_on_missing_scheme(url): def get_auth_from_url(url): """Given a url with authentication components, extract them into a tuple of username,password.""" - if url: - parsed = urlparse(url) + parsed = urlparse(url) - try: - auth = (unquote(parsed.username), unquote(parsed.password)) - except (AttributeError, TypeError): - auth = ('', '') - else: + try: + auth = (unquote(parsed.username), unquote(parsed.password)) + except (AttributeError, TypeError): auth = ('', '') return auth From 5b4e9aff0ea2d0876de33f57cece4b06fe4bc194 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sun, 12 Jan 2014 14:46:40 +0000 Subject: [PATCH 005/545] Don't need to unquote twice. --- requests/adapters.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index 43addb1b..dd10e959 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -286,10 +286,6 @@ class HTTPAdapter(BaseAdapter): username, password = get_auth_from_url(proxy) if username and password: - # Proxy auth usernames and passwords will be urlencoded, we need - # to decode them. - username = unquote(username) - password = unquote(password) headers['Proxy-Authorization'] = _basic_auth_str(username, password) From a6a83420662b4ae2edda6949cf584415d3495c8e Mon Sep 17 00:00:00 2001 From: Roberto Migli Date: Mon, 13 Jan 2014 13:32:00 +0100 Subject: [PATCH 006/545] Fixed parsing of username and password encoded in the URI --- requests/utils.py | 10 ++++++++-- test_requests.py | 11 ++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/requests/utils.py b/requests/utils.py index 7a4d44d5..73934110 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -635,9 +635,15 @@ def get_auth_from_url(url): """Given a url with authentication components, extract them into a tuple of username,password.""" if url: - url = unquote(url) parsed = urlparse(url) - return (parsed.username, parsed.password) + username = "" + password = "" + + if parsed.username is not None: + username = unquote(parsed.username) + if parsed.password is not None: + password = unquote(parsed.password) + return (username, password) else: return ('', '') diff --git a/test_requests.py b/test_requests.py index 1e8e723f..2f4c40a3 100755 --- a/test_requests.py +++ b/test_requests.py @@ -702,7 +702,7 @@ class RequestsTestCase(unittest.TestCase): assert ('user', 'pass pass') == requests.utils.get_auth_from_url(url) def test_get_auth_from_url_percent_chars(self): - url = 'http://user%user:pass@complex.url.com/path?query=yes' + url = 'http://user%25user:pass@complex.url.com/path?query=yes' assert ('user%user', 'pass') == requests.utils.get_auth_from_url(url) def test_cannot_send_unprepared_requests(self): @@ -1092,6 +1092,15 @@ class UtilsTestCase(unittest.TestCase): assert address_in_network('192.168.1.1', '192.168.1.0/24') assert not address_in_network('172.16.0.1', '192.168.1.0/24') + def test_get_auth_from_url(self): + from requests.utils import get_auth_from_url + from requests.compat import quote + percent_encoding_test_chars = "%!*'();:@&=+$,/?#[] " + url_address = "request.com/url.html#test" + url = "http://" + quote(percent_encoding_test_chars, '') + ':' + quote(percent_encoding_test_chars, '') + '@' + url_address + (username, password) = get_auth_from_url(url) + assert username == percent_encoding_test_chars + assert password == percent_encoding_test_chars class TestMorselToCookieExpires(unittest.TestCase): From eb124353af330f8e1249e568e521795652fefb11 Mon Sep 17 00:00:00 2001 From: Roberto Migli Date: Mon, 13 Jan 2014 16:29:46 +0100 Subject: [PATCH 007/545] Added comment for test test_get_auth_from_url --- test_requests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test_requests.py b/test_requests.py index 0a733104..63897bb5 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1097,6 +1097,7 @@ class UtilsTestCase(unittest.TestCase): assert not address_in_network('172.16.0.1', '192.168.1.0/24') def test_get_auth_from_url(self): + """ Ensures that username and password in well-encoded URI as per RFC 3986 are correclty extracted """ from requests.utils import get_auth_from_url from requests.compat import quote percent_encoding_test_chars = "%!*'();:@&=+$,/?#[] " From 476ab203bc00cb57a7be24fb500b95447294ec92 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 16 Jan 2014 08:37:29 +0000 Subject: [PATCH 008/545] Document contextlib.closing. --- docs/user/advanced.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 4e05303b..f5e8e59f 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -199,6 +199,20 @@ At this point only the response headers have been downloaded and the connection You can further control the workflow by use of the :class:`Response.iter_content ` and :class:`Response.iter_lines ` methods. Alternatively, you can read the undecoded body from the underlying urllib3 :class:`urllib3.HTTPResponse ` at :class:`Response.raw `. +If you set ``stream`` to ``True`` when making a request, Requests cannot +release the connection back to the pool unless you consume all the data or call +:class:`Response.close `. This can lead to +inefficiency with connections. If you find yourself partially reading request +bodies (or not reading them at all) while using ``stream=True``, you should +consider using ``contextlib.closing`` (`documented here`_), like this:: + + from contextlib import closing + + with closing(requests.get('http://httpbin.org/get', stream=True)) as r: + # Do things with the response here. + +.. _`documented here`: http://docs.python.org/2/library/contextlib.html#contextlib.closing + Keep-Alive ---------- From c3055863c6791c696d957cf90197798eb8621d2c Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 16 Jan 2014 15:12:40 -0800 Subject: [PATCH 009/545] Update NOTICE --- NOTICE | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/NOTICE b/NOTICE index b9a1de11..4f837dcd 100644 --- a/NOTICE +++ b/NOTICE @@ -62,34 +62,3 @@ License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 - -OAuthlib -======== - -Copyright (c) 2011 Idan Gazit and contributors -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - - 3. Neither the name of this project nor the names of its contributors may - be used to endorse or promote products derived from this software without - specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE -FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 799bf116fe7e4149ef119bb5e241e77749190278 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 18 Jan 2014 22:44:50 -0600 Subject: [PATCH 010/545] Add a small note about requests-toolbelt --- docs/user/quickstart.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 78b2cc65..116e5a12 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -264,6 +264,12 @@ If you want, you can send strings to be received as files:: ... } +In the event you are posting a very large file as a ``multipart/form-data`` +request, you may want to stream the request. By default, ``requests`` does not +support this, but there is a separate package which does - +``requests-toolbelt``. You should read `the toolbelt's documentation +` for more details about how to use it. + Response Status Codes --------------------- From f17ead413300e349b7080a3ee0be3c7e96ec55ec Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Tue, 21 Jan 2014 10:18:04 -0800 Subject: [PATCH 011/545] Typo in History --- HISTORY.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 3ac55986..23906935 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -14,7 +14,7 @@ Release History **Bugfixes** - Avoid many many exceptions from the buggy implementation of ``proxy_bypass`` on OS X in Python 2.6. -- Avoid crashing when attempting to get authentication credentions from ~/.netrc when running as a user without a home directory. +- Avoid crashing when attempting to get authentication credentials from ~/.netrc when running as a user without a home directory. - Use the correct pool size for pools of connections to proxies. - Fix iteration of ``CookieJar`` objects. - Ensure that cookies are persisted over redirect. From 3bd16ce9c5dc7a56078b80e12cf0d48208a24b64 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 21 Jan 2014 19:55:00 -0600 Subject: [PATCH 012/545] Add missing sled Thanks @kevinburke --- docs/user/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 116e5a12..3e802e19 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -268,7 +268,7 @@ In the event you are posting a very large file as a ``multipart/form-data`` request, you may want to stream the request. By default, ``requests`` does not support this, but there is a separate package which does - ``requests-toolbelt``. You should read `the toolbelt's documentation -` for more details about how to use it. +`_ for more details about how to use it. Response Status Codes From 2a6b835a5e08c7365b8a4d3bfea4dcd98ad2d205 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 22 Jan 2014 14:28:41 +0000 Subject: [PATCH 013/545] Update changelog for 2.2.1. --- HISTORY.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 23906935..e160494f 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,14 @@ Release History --------------- +2.2.1 (XXXX-XX-XX) +++++++++++++++++++ + +**Bugfixes** + +- Fixes incorrect parsing of proxy credentials that contain a literal or encoded '#' character. +- Assorted urllib3 fixes. + 2.2.0 (2014-01-09) ++++++++++++++++++ From d181577f3e34ac4bda2458f7b28405a5ed034d87 Mon Sep 17 00:00:00 2001 From: Donald Stufft Date: Wed, 22 Jan 2014 14:20:34 -0500 Subject: [PATCH 014/545] Update urllib3 to 9346c5c --- requests/packages/urllib3/connection.py | 82 +++++++++++++++++-- requests/packages/urllib3/connectionpool.py | 34 ++++---- .../packages/urllib3/contrib/pyopenssl.py | 48 ++++++++++- requests/packages/urllib3/filepost.py | 11 ++- requests/packages/urllib3/poolmanager.py | 4 +- requests/packages/urllib3/util.py | 5 ++ 6 files changed, 152 insertions(+), 32 deletions(-) diff --git a/requests/packages/urllib3/connection.py b/requests/packages/urllib3/connection.py index c3f302d3..21247745 100644 --- a/requests/packages/urllib3/connection.py +++ b/requests/packages/urllib3/connection.py @@ -8,9 +8,9 @@ import socket from socket import timeout as SocketTimeout try: # Python 3 - from http.client import HTTPConnection, HTTPException + from http.client import HTTPConnection as _HTTPConnection, HTTPException except ImportError: - from httplib import HTTPConnection, HTTPException + from httplib import HTTPConnection as _HTTPConnection, HTTPException class DummyConnection(object): "Used to detect a failed ConnectionCls import." @@ -24,9 +24,9 @@ try: # Compiled with SSL? pass try: # Python 3 - from http.client import HTTPSConnection + from http.client import HTTPSConnection as _HTTPSConnection except ImportError: - from httplib import HTTPSConnection + from httplib import HTTPSConnection as _HTTPSConnection import ssl BaseSSLError = ssl.SSLError @@ -45,6 +45,69 @@ from .util import ( ssl_wrap_socket, ) + +port_by_scheme = { + 'http': 80, + 'https': 443, +} + + +class HTTPConnection(_HTTPConnection, object): + default_port = port_by_scheme['http'] + + # By default, disable Nagle's Algorithm. + tcp_nodelay = 1 + + def _new_conn(self): + """ Establish a socket connection and set nodelay settings on it + + :return: a new socket connection + """ + try: + conn = socket.create_connection( + (self.host, self.port), + self.timeout, + self.source_address, + ) + except AttributeError: # Python 2.6 + conn = socket.create_connection( + (self.host, self.port), + self.timeout, + ) + conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, + self.tcp_nodelay) + return conn + + def _prepare_conn(self, conn): + self.sock = conn + if self._tunnel_host: + # TODO: Fix tunnel so it doesn't depend on self.sock state. + self._tunnel() + + def connect(self): + conn = self._new_conn() + self._prepare_conn(conn) + + +class HTTPSConnection(HTTPConnection): + default_port = port_by_scheme['https'] + + def __init__(self, host, port=None, key_file=None, cert_file=None, + strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None): + try: + HTTPConnection.__init__(self, host, port, strict, timeout, source_address) + except TypeError: # Python 2.6 + HTTPConnection.__init__(self, host, port, strict, timeout) + self.key_file = key_file + self.cert_file = cert_file + + def connect(self): + conn = self._new_conn() + self._prepare_conn(conn) + self.sock = ssl.wrap_socket(conn, self.key_file, self.cert_file) + + class VerifiedHTTPSConnection(HTTPSConnection): """ Based on httplib.HTTPSConnection but wraps the socket with @@ -73,9 +136,12 @@ class VerifiedHTTPSConnection(HTTPSConnection): timeout=self.timeout, ) except SocketTimeout: - raise ConnectTimeoutError( - self, "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout)) + raise ConnectTimeoutError( + self, "Connection to %s timed out. (connect timeout=%s)" % + (self.host, self.timeout)) + + sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, + self.tcp_nodelay) resolved_cert_reqs = resolve_cert_reqs(self.cert_reqs) resolved_ssl_version = resolve_ssl_version(self.ssl_version) @@ -107,4 +173,6 @@ class VerifiedHTTPSConnection(HTTPSConnection): if ssl: + # Make a copy for testing. + UnverifiedHTTPSConnection = HTTPSConnection HTTPSConnection = VerifiedHTTPSConnection diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index 44ecffd0..243d700e 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -31,6 +31,7 @@ from .exceptions import ( from .packages.ssl_match_hostname import CertificateError from .packages import six from .connection import ( + port_by_scheme, DummyConnection, HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection, HTTPException, BaseSSLError, @@ -51,12 +52,6 @@ log = logging.getLogger(__name__) _Default = object() -port_by_scheme = { - 'http': 80, - 'https': 443, -} - - ## Pool objects class ConnectionPool(object): @@ -169,7 +164,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): def _new_conn(self): """ - Return a fresh :class:`httplib.HTTPConnection`. + Return a fresh :class:`HTTPConnection`. """ self.num_connections += 1 log.info("Starting new HTTP connection (%d): %s" % @@ -179,9 +174,14 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): if not six.PY3: # Python 2 extra_params['strict'] = self.strict - return self.ConnectionCls(host=self.host, port=self.port, + conn = self.ConnectionCls(host=self.host, port=self.port, timeout=self.timeout.connect_timeout, **extra_params) + if self.proxy is not None: + # Enable Nagle's algorithm for proxies, to avoid packet + # fragmentation. + conn.tcp_nodelay = 0 + return conn def _get_conn(self, timeout=None): """ @@ -260,7 +260,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): def _make_request(self, conn, method, url, timeout=_Default, **httplib_request_kw): """ - Perform a request on a given httplib connection object taken from our + Perform a request on a given urllib connection object taken from our pool. :param conn: @@ -517,17 +517,17 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): raise except (HTTPException, SocketError) as e: - if isinstance(e, SocketError) and self.proxy is not None: - raise ProxyError('Cannot connect to proxy. ' - 'Socket error: %s.' % e) - # Connection broken, discard. It will be replaced next _get_conn(). conn = None # This is necessary so we can access e below err = e if retries == 0: - raise MaxRetryError(self, url, e) + if isinstance(e, SocketError) and self.proxy is not None: + raise ProxyError('Cannot connect to proxy. ' + 'Socket error: %s.' % e) + else: + raise MaxRetryError(self, url, e) finally: if release_conn: @@ -565,7 +565,7 @@ class HTTPSConnectionPool(HTTPConnectionPool): When Python is compiled with the :mod:`ssl` module, then :class:`.VerifiedHTTPSConnection` is used, which *can* verify certificates, - instead of :class:`httplib.HTTPSConnection`. + instead of :class:`.HTTPSConnection`. :class:`.VerifiedHTTPSConnection` uses one of ``assert_fingerprint``, ``assert_hostname`` and ``host`` in this order to verify connections. @@ -652,6 +652,10 @@ class HTTPSConnectionPool(HTTPConnectionPool): conn = self.ConnectionCls(host=actual_host, port=actual_port, timeout=self.timeout.connect_timeout, **extra_params) + if self.proxy is not None: + # Enable Nagle's algorithm for proxies, to avoid packet + # fragmentation. + conn.tcp_nodelay = 0 return self._prepare_conn(conn) diff --git a/requests/packages/urllib3/contrib/pyopenssl.py b/requests/packages/urllib3/contrib/pyopenssl.py index f78e7170..d9bda15a 100644 --- a/requests/packages/urllib3/contrib/pyopenssl.py +++ b/requests/packages/urllib3/contrib/pyopenssl.py @@ -1,4 +1,4 @@ -'''SSL with SNI-support for Python 2. +'''SSL with SNI_-support for Python 2. This needs the following packages installed: @@ -18,12 +18,31 @@ your application begins using ``urllib3``, like this:: Now you can use :mod:`urllib3` as you normally would, and it will support SNI when the required modules are installed. + +Activating this module also has the positive side effect of disabling SSL/TLS +encryption in Python 2 (see `CRIME attack`_). + +If you want to configure the default list of supported cipher suites, you can +set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable. + +Module Variables +---------------- + +:var DEFAULT_SSL_CIPHER_LIST: The list of supported SSL/TLS cipher suites. + Default: ``EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA256 + EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EDH+aRSA EECDH RC4 !aNULL !eNULL !LOW !3DES + !MD5 !EXP !PSK !SRP !DSS'`` + +.. _sni: https://en.wikipedia.org/wiki/Server_Name_Indication +.. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) + ''' from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT -from ndg.httpsclient.subj_alt_name import SubjectAltName +from ndg.httpsclient.subj_alt_name import SubjectAltName as BaseSubjectAltName import OpenSSL.SSL from pyasn1.codec.der import decoder as der_decoder +from pyasn1.type import univ, constraint from socket import _fileobject import ssl import select @@ -50,6 +69,13 @@ _openssl_verify = { + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, } +# Default SSL/TLS cipher list. +# Recommendation by https://community.qualys.com/blogs/securitylabs/2013/08/05/ +# configuring-apache-nginx-and-openssl-for-forward-secrecy +DEFAULT_SSL_CIPHER_LIST = 'EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM ' + \ + 'EECDH+ECDSA+SHA256 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EDH+aRSA ' + \ + 'EECDH RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS' + orig_util_HAS_SNI = util.HAS_SNI orig_connection_ssl_wrap_socket = connection.ssl_wrap_socket @@ -69,6 +95,17 @@ def extract_from_urllib3(): util.HAS_SNI = orig_util_HAS_SNI +### Note: This is a slightly bug-fixed version of same from ndg-httpsclient. +class SubjectAltName(BaseSubjectAltName): + '''ASN.1 implementation for subjectAltNames support''' + + # There is no limit to how many SAN certificates a certificate may have, + # however this needs to have some limit so we'll set an arbitrarily high + # limit. + sizeSpec = univ.SequenceOf.sizeSpec + \ + constraint.ValueSizeConstraint(1, 1024) + + ### Note: This is a slightly bug-fixed version of same from ndg-httpsclient. def get_subj_alt_name(peer_cert): # Search through extensions @@ -330,6 +367,13 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, except OpenSSL.SSL.Error as e: raise ssl.SSLError('bad ca_certs: %r' % ca_certs, e) + # Disable TLS compression to migitate CRIME attack (issue #309) + OP_NO_COMPRESSION = 0x20000 + ctx.set_options(OP_NO_COMPRESSION) + + # Set list of supported ciphersuites. + ctx.set_cipher_list(DEFAULT_SSL_CIPHER_LIST) + cnx = OpenSSL.SSL.Connection(ctx, sock) cnx.set_tlsext_host_name(server_hostname) cnx.set_connect_state() diff --git a/requests/packages/urllib3/filepost.py b/requests/packages/urllib3/filepost.py index 4575582e..e8b30bdd 100644 --- a/requests/packages/urllib3/filepost.py +++ b/requests/packages/urllib3/filepost.py @@ -46,16 +46,15 @@ def iter_field_objects(fields): def iter_fields(fields): """ + .. deprecated:: 1.6 + Iterate over fields. - .. deprecated :: - - The addition of `~urllib3.fields.RequestField` makes this function - obsolete. Instead, use :func:`iter_field_objects`, which returns - `~urllib3.fields.RequestField` objects, instead. + The addition of :class:`~urllib3.fields.RequestField` makes this function + obsolete. Instead, use :func:`iter_field_objects`, which returns + :class:`~urllib3.fields.RequestField` objects. Supports list of (k, v) tuples and dicts. - """ if isinstance(fields, dict): return ((k, v) for k, v in six.iteritems(fields)) diff --git a/requests/packages/urllib3/poolmanager.py b/requests/packages/urllib3/poolmanager.py index c16519f8..f18ff2bb 100644 --- a/requests/packages/urllib3/poolmanager.py +++ b/requests/packages/urllib3/poolmanager.py @@ -1,5 +1,5 @@ # urllib3/poolmanager.py -# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt) +# Copyright 2008-2014 Andrey Petrov and contributors (see CONTRIBUTORS.txt) # # This module is part of urllib3 and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php @@ -176,7 +176,7 @@ class ProxyManager(PoolManager): Behaves just like :class:`PoolManager`, but sends all requests through the defined proxy, using the CONNECT method for HTTPS URLs. - :param poxy_url: + :param proxy_url: The URL of the proxy to be used. :param proxy_headers: diff --git a/requests/packages/urllib3/util.py b/requests/packages/urllib3/util.py index 46a0c48d..bd266317 100644 --- a/requests/packages/urllib3/util.py +++ b/requests/packages/urllib3/util.py @@ -620,6 +620,11 @@ if SSLContext is not None: # Python 3.2+ """ context = SSLContext(ssl_version) context.verify_mode = cert_reqs + + # Disable TLS compression to migitate CRIME attack (issue #309) + OP_NO_COMPRESSION = 0x20000 + context.options |= OP_NO_COMPRESSION + if ca_certs: try: context.load_verify_locations(ca_certs) From 33735480f77891754304e7f13e3cdf83aaaa76aa Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 23 Jan 2014 13:25:40 -0500 Subject: [PATCH 015/545] v2.2.1 --- HISTORY.rst | 2 +- requests/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index e160494f..8bdb6ab1 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,7 +3,7 @@ Release History --------------- -2.2.1 (XXXX-XX-XX) +2.2.1 (2014-01-23) ++++++++++++++++++ **Bugfixes** diff --git a/requests/__init__.py b/requests/__init__.py index 90fcb033..2e9f3a0b 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.2.0' -__build__ = 0x020200 +__version__ = '2.2.1' +__build__ = 0x020201 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2014 Kenneth Reitz' From 06ecae43bd02513d5249224b697bf6d891064932 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 24 Jan 2014 15:39:32 -0500 Subject: [PATCH 016/545] NOTICE update --- NOTICE | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/NOTICE b/NOTICE index 4f837dcd..4d69475c 100644 --- a/NOTICE +++ b/NOTICE @@ -43,9 +43,8 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - -Bundle of CA Root Certificates -============================== +CA Bundle License +================= This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public From 4c11fb7f04e1f3bc1fcc0901a81c6af99778e904 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Sat, 25 Jan 2014 00:20:10 -0800 Subject: [PATCH 017/545] s/soley/solely --- requests/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index ae46a831..72b0fec8 100644 --- a/requests/models.py +++ b/requests/models.py @@ -693,7 +693,7 @@ class Response(object): If Response.encoding is None, encoding will be guessed using ``chardet``. - The encoding of the response content is determined based soley on HTTP + The encoding of the response content is determined based solely on HTTP headers, following RFC 2616 to the letter. If you can take advantage of non-HTTP knowledge to make a better guess at the encoding, you should set ``r.encoding`` appropriately before accessing this property. From 1e3eb4ef9e21dded404909142b92fd7506b701f2 Mon Sep 17 00:00:00 2001 From: Alexander Date: Tue, 28 Jan 2014 12:03:59 +0300 Subject: [PATCH 018/545] Added info about posted files headers --- docs/user/quickstart.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 3e802e19..f4273610 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -234,10 +234,10 @@ Requests makes it simple to upload Multipart-encoded files:: ... } -You can set the filename explicitly:: +You can set the filename, content_type and headers explicitly: >>> url = 'http://httpbin.org/post' - >>> files = {'file': ('report.xls', open('report.xls', 'rb'))} + >>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})} >>> r = requests.post(url, files=files) >>> r.text From b6e16799d0225af6a389eb040d940c9f97de63d2 Mon Sep 17 00:00:00 2001 From: Konstantinos Koukopoulos Date: Tue, 28 Jan 2014 09:08:16 -0800 Subject: [PATCH 019/545] test pyopenssl redirect (unit test for #1887) --- test_requests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test_requests.py b/test_requests.py index 63897bb5..ee9c7b78 100755 --- a/test_requests.py +++ b/test_requests.py @@ -412,6 +412,9 @@ class RequestsTestCase(unittest.TestCase): def test_unicode_header_name(self): requests.put(httpbin('put'), headers={str('Content-Type'): 'application/octet-stream'}, data='\xff') # compat.str is unicode. + def test_pyopenssl_redirect(self): + requests.get('https://httpbin.org/status/301') + def test_urlencoded_get_query_multivalued_param(self): r = requests.get(httpbin('get'), params=dict(test=['foo', 'baz'])) From 8b792be81bfaa79fde1ee8de3b72e4e07ef4f6c1 Mon Sep 17 00:00:00 2001 From: Konstantinos Koukopoulos Date: Tue, 28 Jan 2014 09:11:23 -0800 Subject: [PATCH 020/545] on redirect pass URL as bytes. Fixes: #1887 --- requests/sessions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index db227ca3..e262aa32 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -17,7 +17,7 @@ from .cookies import ( cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies) from .models import Request, PreparedRequest from .hooks import default_hooks, dispatch_hook -from .utils import to_key_val_list, default_headers +from .utils import to_key_val_list, default_headers, to_native_string from .exceptions import TooManyRedirects, InvalidSchema from .structures import CaseInsensitiveDict @@ -121,7 +121,7 @@ class SessionRedirectMixin(object): else: url = requote_uri(url) - prepared_request.url = url + prepared_request.url = to_native_string(url) # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4 if (resp.status_code == codes.see_other and From 27eb54a20a6f21cfe7138d4a26a8f41f5b2ced4b Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 28 Jan 2014 20:13:57 -0600 Subject: [PATCH 021/545] Move creation of attributes to RequestException Pass request objects in HTTPAdapter --- requests/adapters.py | 8 ++++---- requests/exceptions.py | 15 ++++++++++----- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index dd10e959..ca462232 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -372,19 +372,19 @@ class HTTPAdapter(BaseAdapter): conn._put_conn(low_conn) except socket.error as sockerr: - raise ConnectionError(sockerr) + raise ConnectionError(sockerr, request=request) except MaxRetryError as e: - raise ConnectionError(e) + raise ConnectionError(e, request=request) except _ProxyError as e: raise ProxyError(e) except (_SSLError, _HTTPError) as e: if isinstance(e, _SSLError): - raise SSLError(e) + raise SSLError(e, request=request) elif isinstance(e, TimeoutError): - raise Timeout(e) + raise Timeout(e, request=request) else: raise diff --git a/requests/exceptions.py b/requests/exceptions.py index cd3c7600..7c291f2b 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -14,15 +14,20 @@ class RequestException(IOError): """There was an ambiguous exception that occurred while handling your request.""" + def __init__(self, *args, **kwargs): + """ + Initialize RequestException with `request` and `response` objects. + """ + self.response = kwargs.pop('response', None) + self.request = kwargs.pop('request', None) + if self.response and not self.request: + self.request = self.response.request + super(RequestException, self).__init__(*args, **kwargs) + class HTTPError(RequestException): """An HTTP error occurred.""" - def __init__(self, *args, **kwargs): - """ Initializes HTTPError with optional `response` object. """ - self.response = kwargs.pop('response', None) - super(HTTPError, self).__init__(*args, **kwargs) - class ConnectionError(RequestException): """A Connection error occurred.""" From c2fab5b4ca2f8dcdc76fabb1851bd268f0d41889 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 28 Jan 2014 20:24:41 -0600 Subject: [PATCH 022/545] Avoid having to fix tests --- requests/exceptions.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/requests/exceptions.py b/requests/exceptions.py index 7c291f2b..c8ec089f 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -18,9 +18,10 @@ class RequestException(IOError): """ Initialize RequestException with `request` and `response` objects. """ - self.response = kwargs.pop('response', None) + response = kwargs.pop('response', None) + self.response = response self.request = kwargs.pop('request', None) - if self.response and not self.request: + if response and not self.request and hasattr(response, 'request'): self.request = self.response.request super(RequestException, self).__init__(*args, **kwargs) From 07e0a6198d132fee147806105a6ff05e541d3ba2 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 28 Jan 2014 20:39:09 -0600 Subject: [PATCH 023/545] Explicitly check for None --- requests/exceptions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requests/exceptions.py b/requests/exceptions.py index c8ec089f..a4ee9d63 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -21,7 +21,8 @@ class RequestException(IOError): response = kwargs.pop('response', None) self.response = response self.request = kwargs.pop('request', None) - if response and not self.request and hasattr(response, 'request'): + if (response is not None and not self.request and + hasattr(response, 'request')): self.request = self.response.request super(RequestException, self).__init__(*args, **kwargs) From 7ba5a534ae9fc24e40b3ae6c480c9075d684727e Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 29 Jan 2014 19:13:46 +0000 Subject: [PATCH 024/545] Repopulate ~/.netrc auth. --- requests/sessions.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/requests/sessions.py b/requests/sessions.py index e262aa32..22fe22f8 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -158,6 +158,23 @@ class SessionRedirectMixin(object): prepared_request._cookies.update(self.cookies) prepared_request.prepare_cookies(prepared_request._cookies) + # If we get redirected to a new host, we should strip out any + # authentication headers. + original_parsed = urlparse(resp.request.url) + redirect_parsed = urlparse(url) + + if original_parsed.hostname != redirect_parsed.hostname: + try: + del headers['Authorization'] + except KeyError: + pass + + # However, .netrc might have more auth for us. Let's get it if it + # does. + new_auth = get_netrc_auth(url) + if new_auth is not None: + prepared_request.prepare_auth(new_auth) + resp = self.send( prepared_request, stream=stream, From 0163a836a104bfd03ceb27055e88d726b53be1ff Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 29 Jan 2014 19:21:04 +0000 Subject: [PATCH 025/545] Avoid breaking crappy distribution methods. --- setup.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/setup.py b/setup.py index 16ba717b..879d94a6 100755 --- a/setup.py +++ b/setup.py @@ -30,8 +30,6 @@ with open('README.rst') as f: readme = f.read() with open('HISTORY.rst') as f: history = f.read() -with open('LICENSE') as f: - license = f.read() setup( name='requests', @@ -46,7 +44,7 @@ setup( package_dir={'requests': 'requests'}, include_package_data=True, install_requires=requires, - license=license, + license='Apache 2.0', zip_safe=False, classifiers=( 'Development Status :: 5 - Production/Stable', From 326a22e8880c1dba52698a479eb7b6038d5b2e87 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 30 Jan 2014 15:11:24 +0000 Subject: [PATCH 026/545] Better layout for checking. --- requests/sessions.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index 22fe22f8..531fd667 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -163,11 +163,9 @@ class SessionRedirectMixin(object): original_parsed = urlparse(resp.request.url) redirect_parsed = urlparse(url) - if original_parsed.hostname != redirect_parsed.hostname: - try: - del headers['Authorization'] - except KeyError: - pass + if (original_parsed.hostname != redirect_parsed.hostname and + 'Authorization' in headers): + del headers['Authorization'] # However, .netrc might have more auth for us. Let's get it if it # does. From d9f34c6848b9b313beefa7d3ce05f52fdea28c27 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Fri, 31 Jan 2014 07:36:44 +0000 Subject: [PATCH 027/545] Respect trust_env on redirect. --- requests/sessions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/sessions.py b/requests/sessions.py index 531fd667..ae7390c5 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -169,7 +169,7 @@ class SessionRedirectMixin(object): # However, .netrc might have more auth for us. Let's get it if it # does. - new_auth = get_netrc_auth(url) + new_auth = get_netrc_auth(url) if self.trust_env else None if new_auth is not None: prepared_request.prepare_auth(new_auth) From f74f5e3ebf0943163cd21b93fb682f790277aa19 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 31 Jan 2014 12:27:05 -0500 Subject: [PATCH 028/545] cleanup shitty code --- requests/sessions.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index ae7390c5..de92d502 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -153,22 +153,20 @@ class SessionRedirectMixin(object): except KeyError: pass - extract_cookies_to_jar(prepared_request._cookies, - prepared_request, resp.raw) + extract_cookies_to_jar(prepared_request._cookies, prepared_request, resp.raw) prepared_request._cookies.update(self.cookies) prepared_request.prepare_cookies(prepared_request._cookies) - # If we get redirected to a new host, we should strip out any - # authentication headers. - original_parsed = urlparse(resp.request.url) - redirect_parsed = urlparse(url) + if 'Authorization' in headers: + # If we get redirected to a new host, we should strip out any + # authentication headers. + original_parsed = urlparse(resp.request.url) + redirect_parsed = urlparse(url) - if (original_parsed.hostname != redirect_parsed.hostname and - 'Authorization' in headers): - del headers['Authorization'] + if (original_parsed.hostname != redirect_parsed.hostname): + del headers['Authorization'] - # However, .netrc might have more auth for us. Let's get it if it - # does. + # .netrc might have more auth for us. new_auth = get_netrc_auth(url) if self.trust_env else None if new_auth is not None: prepared_request.prepare_auth(new_auth) From c042c08179cae74e7b2c520b33ea19881c0ec842 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 31 Jan 2014 12:29:56 -0500 Subject: [PATCH 029/545] who did this --- requests/sessions.py | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index de92d502..2236e83f 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -27,10 +27,10 @@ from .utils import requote_uri, get_environ_proxies, get_netrc_auth from .status_codes import codes REDIRECT_STATI = ( - codes.moved, # 301 - codes.found, # 302 - codes.other, # 303 - codes.temporary_moved, # 307 + codes.moved, # 301 + codes.found, # 302 + codes.other, # 303 + codes.temporary_moved, # 307 ) DEFAULT_REDIRECT_LIMIT = 30 @@ -480,8 +480,7 @@ class Session(SessionRedirectMixin): if not isinstance(request, PreparedRequest): raise ValueError('You can only send PreparedRequests.') - # 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) stream = kwargs.get('stream') timeout = kwargs.get('timeout') @@ -495,8 +494,10 @@ class Session(SessionRedirectMixin): # Start time (approximately) of the request start = datetime.utcnow() + # Send the request r = adapter.send(request, **kwargs) + # Total elapsed time of the request (approximately) r.elapsed = datetime.utcnow() - start @@ -505,15 +506,20 @@ class Session(SessionRedirectMixin): # Persist cookies if r.history: + # If the hooks create history then we want those cookies too for resp in r.history: extract_cookies_to_jar(self.cookies, resp.request, resp.raw) + extract_cookies_to_jar(self.cookies, request, r.raw) # Redirect resolving generator. - gen = self.resolve_redirects(r, request, stream=stream, - timeout=timeout, verify=verify, cert=cert, - proxies=proxies) + gen = self.resolve_redirects(r, request, + stream=stream, + timeout=timeout, + verify=verify, + cert=cert, + proxies=proxies) # Resolve redirects if allowed. history = [resp for resp in gen] if allow_redirects else [] @@ -547,8 +553,10 @@ class Session(SessionRedirectMixin): """Registers a connection adapter to a prefix. Adapters are sorted in descending order by key length.""" + self.adapters[prefix] = adapter keys_to_move = [k for k in self.adapters if len(k) < len(prefix)] + for key in keys_to_move: self.adapters[key] = self.adapters.pop(key) From 2b23d6a9f7aa02561fe6d38be31cde4d609d0e2f Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 31 Jan 2014 12:30:52 -0500 Subject: [PATCH 030/545] omg never do this again ever please --- requests/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index 72b0fec8..e2429b1a 100644 --- a/requests/models.py +++ b/requests/models.py @@ -644,8 +644,7 @@ class Response(object): pending = None - for chunk in self.iter_content(chunk_size=chunk_size, - decode_unicode=decode_unicode): + for chunk in self.iter_content(chunk_size=chunk_size, decode_unicode=decode_unicode): if pending is not None: chunk = pending + chunk From c6084704ccb5610ea093b6b47fb45d2149570174 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 31 Jan 2014 12:31:11 -0500 Subject: [PATCH 031/545] what is wrong with you people? :) --- requests/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index e2429b1a..014ca789 100644 --- a/requests/models.py +++ b/requests/models.py @@ -612,8 +612,7 @@ class Response(object): try: # Special case for urllib3. 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 except IncompleteRead as e: raise ChunkedEncodingError(e) From b5b8198fd1e872471f4523e0b450dfee33072242 Mon Sep 17 00:00:00 2001 From: Aaron Iles Date: Sun, 2 Feb 2014 20:14:05 +1100 Subject: [PATCH 032/545] Document requirements for SNI support on Python2 A section for Request's advanced usage guide on what Server Name Indication is, its purpose, and how to enable it on Python2. --- docs/user/advanced.rst | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index f5e8e59f..9df9f9b1 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -179,6 +179,31 @@ If you specify a wrong path or an invalid cert:: >>> requests.get('https://kennethreitz.com', cert='/wrong_path/server.pem') SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib +Server Name Indication +---------------------- + +`Server Name Indication`_, or SNI, is an official extension to SSL where the +client tells the server what hostname it is contacting. This enables `virtual +hosting`_ on SSL protected sites. + +Python3's SSL module includes native support for SNI. This support has not been +back ported to Python2. However, Requests will enable SNI support on Python2 if +the following packages are installed: + +* `pyOpenSSL`_, a Python wrapper module around the OpenSSL library. +* `ndg-httpsclient`_, enhanced HTTPS support for httplib and urllib2. +* `pyasn1`_, ASN.1 types and codecs. + +When these packages are installed, Requests will automatically indicate to the +server what hostname is being contacted. This allows the server to return the +correct server certificate for SSL certificate verification. + +.. _`Server Name Indication`: https://en.wikipedia.org/wiki/Server_Name_Indication +.. _`virtual hosting`: https://en.wikipedia.org/wiki/Virtual_hosting +.. _`pyOpenSSL`: https://pypi.python.org/pypi/pyOpenSSL +.. _`ndg-httpsclient`: https://pypi.python.org/pypi/ndg-httpsclient +.. _`pyasn1`: https://pypi.python.org/pypi/pyasn1 + Body Content Workflow --------------------- From 8a0bae45c2efa8706c76d8af925d0dcd727cfc32 Mon Sep 17 00:00:00 2001 From: Aaron Iles Date: Sun, 2 Feb 2014 22:19:10 +1100 Subject: [PATCH 033/545] Move SNI documentation to FAQ Relocate documentation on Server-Name-Indication from the advanced section to the frequently asked questions. This is minus details on enabling SNI on Python2, which is instead captured by linking to Stack Overflow. --- docs/community/faq.rst | 22 ++++++++++++++++++++++ docs/user/advanced.rst | 27 ++------------------------- 2 files changed, 24 insertions(+), 25 deletions(-) diff --git a/docs/community/faq.rst b/docs/community/faq.rst index edbf9b70..84c6b80a 100644 --- a/docs/community/faq.rst +++ b/docs/community/faq.rst @@ -60,3 +60,25 @@ supported: * Python 3.2 * Python 3.3 * PyPy 1.9 + +What are "hostname doesn't match" errors? +----------------------------------------- + +These errors occur when :ref:`SSL certificate verification ` +fails to match the certificate the server responds with to the hostname +Requests thinks it's contacting. If you're certain the server's SSL setup is +correct (for example, because you can visit the site with your browser) a +possible explanation is Request's is lacking Server-Name-Indication. + +`Server-Name-Indication`_, or SNI, is an official extension to SSL where the +client tells the server what hostname it is contacting. This enables `virtual +hosting`_ on SSL protected sites, the server being to able to respond with a +certificate appropriate for the hostname the client is contacting. + +Python3's SSL module includes native support for SNI. This support has not been +back ported to Python2. For information on using SNI with Requests on Python2 +refer to this `Stack Overflow answer`_. + +.. _`Server-Name-Indication`: https://en.wikipedia.org/wiki/Server_Name_Indication +.. _`virtual hosting`: https://en.wikipedia.org/wiki/Virtual_hosting +.. _`Stack Overflow answer`: https://stackoverflow.com/questions/18578439/using-requests-with-tls-doesnt-give-sni-support/18579484#18579484 diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 9df9f9b1..80c1e6ae 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -145,6 +145,8 @@ applied, replace the call to :meth:`Request.prepare() print(resp.status_code) +.. _verification: + SSL Cert Verification --------------------- @@ -179,31 +181,6 @@ If you specify a wrong path or an invalid cert:: >>> requests.get('https://kennethreitz.com', cert='/wrong_path/server.pem') SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib -Server Name Indication ----------------------- - -`Server Name Indication`_, or SNI, is an official extension to SSL where the -client tells the server what hostname it is contacting. This enables `virtual -hosting`_ on SSL protected sites. - -Python3's SSL module includes native support for SNI. This support has not been -back ported to Python2. However, Requests will enable SNI support on Python2 if -the following packages are installed: - -* `pyOpenSSL`_, a Python wrapper module around the OpenSSL library. -* `ndg-httpsclient`_, enhanced HTTPS support for httplib and urllib2. -* `pyasn1`_, ASN.1 types and codecs. - -When these packages are installed, Requests will automatically indicate to the -server what hostname is being contacted. This allows the server to return the -correct server certificate for SSL certificate verification. - -.. _`Server Name Indication`: https://en.wikipedia.org/wiki/Server_Name_Indication -.. _`virtual hosting`: https://en.wikipedia.org/wiki/Virtual_hosting -.. _`pyOpenSSL`: https://pypi.python.org/pypi/pyOpenSSL -.. _`ndg-httpsclient`: https://pypi.python.org/pypi/ndg-httpsclient -.. _`pyasn1`: https://pypi.python.org/pypi/pyasn1 - Body Content Workflow --------------------- From 5ee8b348ebab9a7c427a87355dd089c83ee74be9 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Mon, 3 Feb 2014 12:00:14 +0000 Subject: [PATCH 034/545] Reinstate falling back to self.text for JSON responses A JSON response that has no encoding specified will be decoded with a detected UTF codec (compliant with the JSON RFC), but if that fails, we guessed wrong and need to fall back to charade character detection (via `self.text`). Kenneth removed this functionality (by accident?) in 1451ba0c6d395c41f86da35036fa361c3a41bc90, this reinstates it again and adds a log warning. Fixes #1674 --- requests/models.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index 34dce181..2adc5492 100644 --- a/requests/models.py +++ b/requests/models.py @@ -725,11 +725,20 @@ class Response(object): if not self.encoding and len(self.content) > 3: # No encoding set. JSON RFC 4627 section 3 states we should expect # UTF-8, -16 or -32. Detect which one to use; If the detection or - # decoding fails, fall back to `self.text` (using chardet to make + # decoding fails, fall back to `self.text` (using charade to make # a best guess). encoding = guess_json_utf(self.content) if encoding is not None: - return json.loads(self.content.decode(encoding), **kwargs) + try: + return json.loads(self.content.decode(encoding), **kwargs) + except UnicodeDecodeError: + # Wrong UTF codec detected; usually because it's not UTF-8 + # but some other 8-bit codec. This is an RFC violation, + # and the server didn't bother to tell us what codec *was* + # used. + pass + log.warn('No encoding specified for JSON response, and no ' + 'UTF codec detected. Falling back to charade best guess.') return json.loads(self.text, **kwargs) @property From f35838beb6a84b1ece6e775570f6dd668dcd8da0 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Mon, 3 Feb 2014 13:37:57 +0000 Subject: [PATCH 035/545] Remove logging and charade mention. Logging is Not Allowed, so out it goes. --- requests/models.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/requests/models.py b/requests/models.py index 2adc5492..27b73899 100644 --- a/requests/models.py +++ b/requests/models.py @@ -725,7 +725,7 @@ class Response(object): if not self.encoding and len(self.content) > 3: # No encoding set. JSON RFC 4627 section 3 states we should expect # UTF-8, -16 or -32. Detect which one to use; If the detection or - # decoding fails, fall back to `self.text` (using charade to make + # decoding fails, fall back to `self.text` (using chardet to make # a best guess). encoding = guess_json_utf(self.content) if encoding is not None: @@ -737,8 +737,6 @@ class Response(object): # and the server didn't bother to tell us what codec *was* # used. pass - log.warn('No encoding specified for JSON response, and no ' - 'UTF codec detected. Falling back to charade best guess.') return json.loads(self.text, **kwargs) @property From c5b6a107eb9af0fd6a1a48c7bf0276fbf71e9157 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Mon, 3 Feb 2014 13:39:21 +0000 Subject: [PATCH 036/545] One last Charade reference to remove here. --- requests/models.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index 014ca789..6270a691 100644 --- a/requests/models.py +++ b/requests/models.py @@ -593,8 +593,7 @@ class Response(object): @property def apparent_encoding(self): - """The apparent encoding, provided by the lovely Charade library - (Thanks, Ian!).""" + """The apparent encoding, provided by the chardet library""" return chardet.detect(self.content)['encoding'] def iter_content(self, chunk_size=1, decode_unicode=False): From 488b90f5acfafe65b428765aea70ebebd4afe58a Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Mon, 3 Feb 2014 13:43:59 +0000 Subject: [PATCH 037/545] Remove unused loggers. --- requests/auth.py | 3 --- requests/models.py | 3 --- 2 files changed, 6 deletions(-) diff --git a/requests/auth.py b/requests/auth.py index 6664cd80..9f831b7a 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -11,7 +11,6 @@ import os import re import time import hashlib -import logging from base64 import b64encode @@ -19,8 +18,6 @@ from .compat import urlparse, str from .cookies import extract_cookies_to_jar from .utils import parse_dict_header -log = logging.getLogger(__name__) - CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' CONTENT_TYPE_MULTI_PART = 'multipart/form-data' diff --git a/requests/models.py b/requests/models.py index 014ca789..49797a7f 100644 --- a/requests/models.py +++ b/requests/models.py @@ -8,7 +8,6 @@ This module contains the primary objects that power Requests. """ import collections -import logging import datetime from io import BytesIO, UnsupportedOperation @@ -35,8 +34,6 @@ from .compat import ( CONTENT_CHUNK_SIZE = 10 * 1024 ITER_CHUNK_SIZE = 512 -log = logging.getLogger(__name__) - class RequestEncodingMixin(object): @property From 98b76f4b988145a98be7d865da407e150aadc165 Mon Sep 17 00:00:00 2001 From: Aaron Iles Date: Tue, 4 Feb 2014 21:08:11 +1100 Subject: [PATCH 038/545] State early in SNI discussion Python2 limitation Reference at the earliest opportunity that Server-Name-Indication is a limitation of Python 2.6 and 2.7. Avoid describing it as a Requests issue. --- docs/community/faq.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/community/faq.rst b/docs/community/faq.rst index 84c6b80a..4e792eca 100644 --- a/docs/community/faq.rst +++ b/docs/community/faq.rst @@ -67,8 +67,9 @@ What are "hostname doesn't match" errors? These errors occur when :ref:`SSL certificate verification ` fails to match the certificate the server responds with to the hostname Requests thinks it's contacting. If you're certain the server's SSL setup is -correct (for example, because you can visit the site with your browser) a -possible explanation is Request's is lacking Server-Name-Indication. +correct (for example, because you can visit the site with your browser) and +you're using Python 2.6 or 2.7, a possible explanation is that you need +Server-Name-Indication. `Server-Name-Indication`_, or SNI, is an official extension to SSL where the client tells the server what hostname it is contacting. This enables `virtual From 3443c177a3b8279b6c5b5ce9c1643689d8983521 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Wed, 5 Feb 2014 17:29:09 +0000 Subject: [PATCH 039/545] Document the `Response.reason` attribute. Made `.status_code` and `.reason` consistent with one another, adding some examples. Addresses #1225. --- requests/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 6270a691..78832054 100644 --- a/requests/models.py +++ b/requests/models.py @@ -517,7 +517,7 @@ class Response(object): self._content = False self._content_consumed = False - #: Integer Code of responded HTTP Status. + #: Integer Code of responded HTTP Status, e.g. 404 or 200. self.status_code = None #: Case-insensitive Dictionary of Response Headers. @@ -541,6 +541,7 @@ class Response(object): #: up here. The list is sorted from the oldest to the most recent request. self.history = [] + #: Textual reason of responded HTTP Status, e.g. "Not Found" or "OK". self.reason = None #: A CookieJar of Cookies the server sent back. From 969195ad55f9e44ee2e1605e8561123bfed62c4e Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Tue, 11 Feb 2014 07:31:47 +0000 Subject: [PATCH 040/545] Clarify our 1.2.1 behaviour change. --- HISTORY.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 8bdb6ab1..90ea6146 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -128,6 +128,8 @@ Release History 1.2.1 (2013-05-20) ++++++++++++++++++ +- 301 and 302 redirects now change the verb to GET for all verbs, not just + POST, improving browser compatibility. - Python 3.3.2 compatibility - Always percent-encode location headers - Fix connection adapter matching to be most-specific first From 0caa2432123bab2d991e635ce558226d019d7bc7 Mon Sep 17 00:00:00 2001 From: Zack Weinberg Date: Thu, 13 Feb 2014 14:27:42 -0500 Subject: [PATCH 041/545] New Response property, .is_redirect. --- HISTORY.rst | 9 +++++++++ requests/__init__.py | 4 ++-- requests/models.py | 17 +++++++++++++++++ requests/sessions.py | 15 +++++---------- test_requests.py | 2 ++ 5 files changed, 35 insertions(+), 12 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 90ea6146..9625b95c 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,15 @@ Release History --------------- +2.3.0 (YYYY-MM-DD) +++++++++++++++++++ + +**API Changes** + +- New ``Response`` property ``is_redirect``, which is true when the + library could have processed this response as a redirection (whether + or not it actually did). + 2.2.1 (2014-01-23) ++++++++++++++++++ diff --git a/requests/__init__.py b/requests/__init__.py index 2e9f3a0b..bba19002 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.2.1' -__build__ = 0x020201 +__version__ = '2.3.0' +__build__ = 0x020300 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2014 Kenneth Reitz' diff --git a/requests/models.py b/requests/models.py index 25956be5..cd232e68 100644 --- a/requests/models.py +++ b/requests/models.py @@ -30,7 +30,17 @@ from .utils import ( from .compat import ( cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO, is_py2, chardet, json, builtin_str, basestring, IncompleteRead) +from .status_codes import codes +#: The set of HTTP status codes that indicate an automatically +#: processable redirect. +REDIRECT_STATI = ( + codes.moved, # 301 + codes.found, # 302 + codes.other, # 303 + codes.temporary_moved, # 307 +) +DEFAULT_REDIRECT_LIMIT = 30 CONTENT_CHUNK_SIZE = 10 * 1024 ITER_CHUNK_SIZE = 512 @@ -589,6 +599,13 @@ class Response(object): return False return True + @property + def is_redirect(self): + """True if this Response is a well-formed HTTP redirect that could have + been processed automatically (by :meth:`Session.resolve_redirects`). + """ + return ('location' in self.headers and self.status_code in REDIRECT_STATI) + @property def apparent_encoding(self): """The apparent encoding, provided by the chardet library""" diff --git a/requests/sessions.py b/requests/sessions.py index 2236e83f..a023e4ec 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -15,7 +15,7 @@ from datetime import datetime from .compat import cookielib, OrderedDict, urljoin, urlparse, builtin_str from .cookies import ( cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies) -from .models import Request, PreparedRequest +from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT from .hooks import default_hooks, dispatch_hook from .utils import to_key_val_list, default_headers, to_native_string from .exceptions import TooManyRedirects, InvalidSchema @@ -26,13 +26,9 @@ from .adapters import HTTPAdapter from .utils import requote_uri, get_environ_proxies, get_netrc_auth from .status_codes import codes -REDIRECT_STATI = ( - codes.moved, # 301 - codes.found, # 302 - codes.other, # 303 - codes.temporary_moved, # 307 -) -DEFAULT_REDIRECT_LIMIT = 30 + +# formerly defined here, reexposed here for backward compatibility +from .models import REDIRECT_STATI def merge_setting(request_setting, session_setting, dict_class=OrderedDict): @@ -89,8 +85,7 @@ class SessionRedirectMixin(object): i = 0 - # ((resp.status_code is codes.see_other)) - while ('location' in resp.headers and resp.status_code in REDIRECT_STATI): + while resp.is_redirect: prepared_request = req.copy() resp.content # Consume socket so it can be released diff --git a/test_requests.py b/test_requests.py index ee9c7b78..3d7cdaaf 100755 --- a/test_requests.py +++ b/test_requests.py @@ -115,6 +115,8 @@ class RequestsTestCase(unittest.TestCase): def test_HTTP_302_ALLOW_REDIRECT_GET(self): r = requests.get(httpbin('redirect', '1')) assert r.status_code == 200 + assert r.history[0].status_code == 302 + assert r.history[0].is_redirect # def test_HTTP_302_ALLOW_REDIRECT_POST(self): # r = requests.post(httpbin('status', '302'), data={'some': 'data'}) From d2f647cee45fd05cc1977cc3faf4b095b5047b29 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 14 Feb 2014 16:15:21 -0600 Subject: [PATCH 042/545] Do not set headers with None value - Regardless of whether they are on the session or not - Fixes #1920 --- requests/sessions.py | 2 ++ test_requests.py | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/requests/sessions.py b/requests/sessions.py index a023e4ec..c06fbcbe 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -59,6 +59,8 @@ def merge_setting(request_setting, session_setting, dict_class=OrderedDict): if v is None: del merged_setting[k] + merged_setting = dict((k, v) for (k, v) in merged_setting.items() if v is not None) + return merged_setting diff --git a/test_requests.py b/test_requests.py index 3d7cdaaf..998f17f3 100755 --- a/test_requests.py +++ b/test_requests.py @@ -211,6 +211,14 @@ class RequestsTestCase(unittest.TestCase): req_urls = [r.request.url for r in resp.history] assert urls == req_urls + def test_headers_on_session_with_None_are_not_sent(self): + """Do not send headers in Session.headers with None values.""" + ses = requests.Session() + ses.headers['Accept-Encoding'] = None + req = requests.Request('GET', 'http://httpbin.org/get') + prep = ses.prepare_request(req) + assert 'Accept-Encoding' not in prep.headers + def test_user_agent_transfers(self): heads = { From 81e88b70bd757f5ca5e9bc418a17369c13c6abf2 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sun, 16 Feb 2014 19:00:22 +0000 Subject: [PATCH 043/545] The timeout is in seconds. --- requests/api.py | 2 +- requests/sessions.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/api.py b/requests/api.py index baf43dd6..01d853d5 100644 --- a/requests/api.py +++ b/requests/api.py @@ -26,7 +26,7 @@ def request(method, url, **kwargs): :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 auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. - :param timeout: (optional) Float describing the timeout of the request. + :param timeout: (optional) Float describing the timeout of the request in seconds. :param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. :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. diff --git a/requests/sessions.py b/requests/sessions.py index a023e4ec..b2dc1a99 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -328,7 +328,7 @@ class Session(SessionRedirectMixin): :param auth: (optional) Auth tuple or callable to enable Basic/Digest/Custom HTTP Auth. :param timeout: (optional) Float describing the timeout of the - request. + request in seconds. :param allow_redirects: (optional) Boolean. Set to True by default. :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. From 780ce3902eccc7f7087e195211daf4c592cb28fb Mon Sep 17 00:00:00 2001 From: schlamar Date: Tue, 18 Feb 2014 15:15:36 +0100 Subject: [PATCH 044/545] Revert "Proxy urls should have explicit schemes." This reverts commit 840540b6b1f07ef87faab73392c03fbef0dcc9fe. Conflicts: requests/adapters.py requests/utils.py --- requests/adapters.py | 4 ++-- requests/utils.py | 21 +++++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index ca462232..d51a5fe3 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -16,7 +16,7 @@ from .packages.urllib3.response import HTTPResponse from .packages.urllib3.util import Timeout as TimeoutSauce from .compat import urlparse, basestring, urldefrag, unquote from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers, - except_on_missing_scheme, get_auth_from_url) + prepend_scheme_if_needed, get_auth_from_url) from .structures import CaseInsensitiveDict from .packages.urllib3.exceptions import MaxRetryError from .packages.urllib3.exceptions import TimeoutError @@ -203,7 +203,7 @@ class HTTPAdapter(BaseAdapter): proxy = proxies.get(urlparse(url.lower()).scheme) if proxy: - except_on_missing_scheme(proxy) + proxy = prepend_scheme_if_needed(proxy, urlparse(url.lower()).scheme) proxy_headers = self.proxy_headers(proxy) if not proxy in self.proxy_manager: diff --git a/requests/utils.py b/requests/utils.py index 7b7ff0a7..5a3df4f7 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -24,10 +24,10 @@ from . import __version__ from . import certs from .compat import parse_http_list as _parse_list_header from .compat import (quote, urlparse, bytes, str, OrderedDict, unquote, is_py2, - builtin_str, getproxies, proxy_bypass) + builtin_str, getproxies, proxy_bypass, urlunparse) from .cookies import RequestsCookieJar, cookiejar_from_dict from .structures import CaseInsensitiveDict -from .exceptions import MissingSchema, InvalidURL +from .exceptions import InvalidURL _hush_pyflakes = (RequestsCookieJar,) @@ -622,13 +622,18 @@ def guess_json_utf(data): return None -def except_on_missing_scheme(url): - """Given a URL, raise a MissingSchema exception if the scheme is missing. - """ - scheme, netloc, path, params, query, fragment = urlparse(url) +def prepend_scheme_if_needed(url, new_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.''' + scheme, netloc, path, params, query, fragment = urlparse(url, new_scheme) - if not scheme: - raise MissingSchema('Proxy URLs must have explicit schemes.') + # urlparse is a finicky beast, and sometimes decides that there isn't a + # netloc present. Assume that it's being over-cautious, and switch netloc + # and path if urlparse decided there was no netloc. + if not netloc: + netloc, path = path, netloc + + return urlunparse((scheme, netloc, path, params, query, fragment)) def get_auth_from_url(url): From 4404e7e328114868a272b99ffc81b4c74ecd06ed Mon Sep 17 00:00:00 2001 From: schlamar Date: Tue, 18 Feb 2014 15:24:24 +0100 Subject: [PATCH 045/545] Default proxy scheme to http. --- requests/adapters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/adapters.py b/requests/adapters.py index d51a5fe3..e4d5e7dd 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -203,7 +203,7 @@ class HTTPAdapter(BaseAdapter): proxy = proxies.get(urlparse(url.lower()).scheme) if proxy: - proxy = prepend_scheme_if_needed(proxy, urlparse(url.lower()).scheme) + proxy = prepend_scheme_if_needed(proxy, 'http') proxy_headers = self.proxy_headers(proxy) if not proxy in self.proxy_manager: From 9b20cadaeb8103ab37c4f02f5cfcd6ca7ca7ec22 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 28 Feb 2014 08:27:50 -0600 Subject: [PATCH 046/545] Charade -> Chardet and Add cacert.pem license - Charade is gone, long live Chardet. - cacert.pem is now taken wholesale from Mozilla so we need to display that itis licensed under the MPL2.0 --- NOTICE | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/NOTICE b/NOTICE index 4d69475c..76641d57 100644 --- a/NOTICE +++ b/NOTICE @@ -24,8 +24,8 @@ FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TOR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Charade License -================ +Chardet License +=============== This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -46,18 +46,14 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA CA Bundle License ================= -This library is free software; you can redistribute it and/or -modify it under the terms of the GNU Lesser General Public -License as published by the Free Software Foundation; either -version 2.1 of the License, or (at your option) any later version. - -This library is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -Lesser General Public License for more details. - -You should have received a copy of the GNU Lesser General Public -License along with this library; if not, write to the Free Software -Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA -02110-1301 +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. +Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. +Subject: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. +Label: "GTE CyberTrust Global Root" +Serial: 421 +MD5 Fingerprint: ca:3d:d3:68:f1:03:5c:d0:32:fa:b8:2b:59:e8:5a:db +SHA1 Fingerprint: 97:81:79:50:d8:1c:96:70:cc:34:d8:09:cf:79:44:31:36:7e:f4:74 +SHA256 Fingerprint: a5:31:25:18:8d:21:10:aa:96:4b:02:c7:b7:c6:da:32:03:17:08:94:e5:fb:71:ff:fb:66:67:d5:e6:81:0a:36 From 930f03c8649613da9efb7915e17428dd1e8526af Mon Sep 17 00:00:00 2001 From: cjstapleton Date: Fri, 28 Feb 2014 10:08:57 -0600 Subject: [PATCH 047/545] Add timeout to stream with testing Fixes Issue #1803 --- requests/adapters.py | 5 +---- test_requests.py | 9 +++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index ca462232..28bea07c 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -310,10 +310,7 @@ class HTTPAdapter(BaseAdapter): chunked = not (request.body is None or 'Content-Length' in request.headers) - if stream: - timeout = TimeoutSauce(connect=timeout) - else: - timeout = TimeoutSauce(connect=timeout, read=timeout) + timeout = TimeoutSauce(connect=timeout, read=timeout) try: if not chunked: diff --git a/test_requests.py b/test_requests.py index 3d7cdaaf..0fe849d4 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1170,6 +1170,15 @@ class TestMorselToCookieMaxAge(unittest.TestCase): with pytest.raises(TypeError): morsel_to_cookie(morsel) +class TestTimeout: + def test_stream_timeout(self): + try: + r = requests.get('https://httpbin.org/delay/10', timeout=5.0) + except requests.exceptions.Timeout as e: + assert 'Read timed out' in e.args[0].args[0] + if __name__ == '__main__': unittest.main() + + From 5f404a0592bba18c5eae7b2aead033f3d0cb27ff Mon Sep 17 00:00:00 2001 From: cjstapleton Date: Fri, 28 Feb 2014 19:06:59 -0600 Subject: [PATCH 048/545] Fix styling issues with add timeout to stream with testing --- test_requests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test_requests.py b/test_requests.py index 0fe849d4..a6b6f89c 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1170,6 +1170,7 @@ class TestMorselToCookieMaxAge(unittest.TestCase): with pytest.raises(TypeError): morsel_to_cookie(morsel) + class TestTimeout: def test_stream_timeout(self): try: @@ -1180,5 +1181,3 @@ class TestTimeout: if __name__ == '__main__': unittest.main() - - From 64f0b3c81ecbe6b4dff1393ad0409da64e1e8cee Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 1 Mar 2014 11:15:14 -0600 Subject: [PATCH 049/545] Remove unnecessary bits from cacert notice section --- NOTICE | 8 -------- 1 file changed, 8 deletions(-) diff --git a/NOTICE b/NOTICE index 76641d57..223101a0 100644 --- a/NOTICE +++ b/NOTICE @@ -49,11 +49,3 @@ CA Bundle License This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. - -Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. -Subject: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. -Label: "GTE CyberTrust Global Root" -Serial: 421 -MD5 Fingerprint: ca:3d:d3:68:f1:03:5c:d0:32:fa:b8:2b:59:e8:5a:db -SHA1 Fingerprint: 97:81:79:50:d8:1c:96:70:cc:34:d8:09:cf:79:44:31:36:7e:f4:74 -SHA256 Fingerprint: a5:31:25:18:8d:21:10:aa:96:4b:02:c7:b7:c6:da:32:03:17:08:94:e5:fb:71:ff:fb:66:67:d5:e6:81:0a:36 From 03f444e601e6608f68f3a09d708af93e4512560a Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 3 Mar 2014 20:23:59 +0000 Subject: [PATCH 050/545] Timeout documentation changes. --- HISTORY.rst | 2 ++ docs/api.rst | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 9625b95c..19c2ff1b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -11,6 +11,8 @@ Release History - New ``Response`` property ``is_redirect``, which is true when the library could have processed this response as a redirection (whether or not it actually did). +- The ``timeout`` parameter now affects requests with both ``stream=True`` and + ``stream=False`` equally. 2.2.1 (2014-01-23) ++++++++++++++++++ diff --git a/docs/api.rst b/docs/api.rst index 77f1f02d..86061be9 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -249,7 +249,9 @@ Behavioral Changes * Timeouts behave slightly differently. On streaming requests, the timeout only applies to the connection attempt. On regular requests, the timeout - is applied to the connection process and downloading the full body. + is applied to the connection process and on to all attempts to read data from + the underlying socket. It does *not* apply to the total download time for the + request. :: From c8226f69e13b9a076f725965e2ed462216c2f4c3 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Mar 2014 16:27:13 -0500 Subject: [PATCH 051/545] Add documentation about decode_unicode. --- requests/models.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requests/models.py b/requests/models.py index cd232e68..682cd9e1 100644 --- a/requests/models.py +++ b/requests/models.py @@ -617,6 +617,9 @@ class Response(object): large responses. The chunk size is the number of bytes it should read into memory. This is not necessarily the length of each item returned as decoding can take place. + + If decode_unicode is True, content will be decoded using the best + available encoding based on the response. """ if self._content_consumed: # simulate reading small chunks of the content From d289eb22f164e0bc71c81b191c1f00bdd08fe669 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Mar 2014 16:33:40 -0500 Subject: [PATCH 052/545] Always honor decode_unicode, even when _content is present. --HG-- extra : amend_source : 25977a1227b163d49bf2e1aec6aa448e5cd3be8a --- requests/models.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/requests/models.py b/requests/models.py index 682cd9e1..c55e6ea7 100644 --- a/requests/models.py +++ b/requests/models.py @@ -621,10 +621,6 @@ class Response(object): If decode_unicode is True, content will be decoded using the best available encoding based on the response. """ - if self._content_consumed: - # simulate reading small chunks of the content - return iter_slices(self._content, chunk_size) - def generate(): try: # Special case for urllib3. @@ -645,12 +641,17 @@ class Response(object): self._content_consumed = True - gen = generate() + # simulate reading small chunks of the content + reused_chunks = iter_slices(self._content, chunk_size) + + stream_chunks = generate() + + chunks = reused_chunks if self._content_consumed else stream_chunks if decode_unicode: - gen = stream_decode_response_unicode(gen, self) + chunks = stream_decode_response_unicode(chunks, self) - return gen + return chunks def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None): """Iterates over the response data, one line at a time. When From 7b378cb74266ccea308a83c5b0f7129cb4962567 Mon Sep 17 00:00:00 2001 From: "Jason R. Coombs" Date: Tue, 4 Mar 2014 17:46:58 -0500 Subject: [PATCH 053/545] Add tests for decode_unicode --- test_requests.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test_requests.py b/test_requests.py index 17de8491..65eb571c 100755 --- a/test_requests.py +++ b/test_requests.py @@ -9,6 +9,7 @@ import os import pickle import unittest +import io import requests import pytest from requests.adapters import HTTPAdapter @@ -690,6 +691,26 @@ class RequestsTestCase(unittest.TestCase): assert next(iter(r)) io.close() + def test_response_decode_unicode(self): + """ + When called with decode_unicode, Response.iter_content should always + return unicode. + """ + r = requests.Response() + r._content_consumed = True + r._content = b'the content' + r.encoding = 'ascii' + + chunks = r.iter_content(decode_unicode=True) + assert all(isinstance(chunk, str) for chunk in chunks) + + # also for streaming + r = requests.Response() + r.raw = io.BytesIO(b'the content') + r.encoding = 'ascii' + chunks = r.iter_content(decode_unicode=True) + assert all(isinstance(chunk, str) for chunk in chunks) + def test_request_and_response_are_pickleable(self): r = requests.get(httpbin('get')) From a59fac5e4ba290164cd0e108769bcab523f1bfb2 Mon Sep 17 00:00:00 2001 From: schlamar Date: Wed, 12 Mar 2014 12:10:53 +0100 Subject: [PATCH 054/545] Removed compress from accepted encodings. --- requests/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/utils.py b/requests/utils.py index 7b7ff0a7..4d648bc5 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -548,7 +548,7 @@ def default_user_agent(name="python-requests"): def default_headers(): return CaseInsensitiveDict({ 'User-Agent': default_user_agent(), - 'Accept-Encoding': ', '.join(('gzip', 'deflate', 'compress')), + 'Accept-Encoding': ', '.join(('gzip', 'deflate')), 'Accept': '*/*' }) From ee7fe02953d864021298ed8d3e3e5f6aff1f6731 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 12 Mar 2014 18:34:43 +0000 Subject: [PATCH 055/545] Ensure that .raw() is present after unpickling. Some people will assume that .raw() is present, and they shouldn't get AttributeErrors when they make that assumption on a pickled Response. However, @kennethreitz has asked that we not be too dependent on urllib3. For that reason, set to None. --- requests/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/requests/models.py b/requests/models.py index cd232e68..e2fa09f8 100644 --- a/requests/models.py +++ b/requests/models.py @@ -575,6 +575,7 @@ class Response(object): # pickled objects do not have .raw setattr(self, '_content_consumed', True) + setattr(self, 'raw', None) def __repr__(self): return '' % (self.status_code) From 6d7e8a97bbefa287366bc5d0b0b8f789532e853a Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 12 Mar 2014 19:20:12 +0000 Subject: [PATCH 056/545] Split get_environ_proxies into two methods. This makes it possible to get at the no_proxy logic separately. --- requests/utils.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/requests/utils.py b/requests/utils.py index 7b7ff0a7..1095b3e7 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -466,9 +466,10 @@ def is_valid_cidr(string_network): return True -def get_environ_proxies(url): - """Return a dict of environment proxies.""" - +def should_bypass_proxies(url): + """ + Returns whether we should bypass proxies or not. + """ get_proxy = lambda k: os.environ.get(k) or os.environ.get(k.upper()) # First check whether no_proxy is defined. If it is, check that the URL @@ -486,13 +487,13 @@ def get_environ_proxies(url): for proxy_ip in no_proxy: if is_valid_cidr(proxy_ip): if address_in_network(ip, proxy_ip): - return {} + return True else: for host in no_proxy: if netloc.endswith(host) or netloc.split(':')[0].endswith(host): # The URL does match something in no_proxy, so we don't want # to apply the proxies on this URL. - return {} + return True # If the system proxy settings indicate that this URL should be bypassed, # don't proxy. @@ -506,12 +507,16 @@ def get_environ_proxies(url): bypass = False if bypass: - return {} + return True - # If we get here, we either didn't have no_proxy set or we're not going - # anywhere that no_proxy applies to, and the system settings don't require - # bypassing the proxy for the current URL. - return getproxies() + return False + +def get_environ_proxies(url): + """Return a dict of environment proxies.""" + if should_bypass_proxies(url): + return {} + else: + return getproxies() def default_user_agent(name="python-requests"): From 97cf16e958a948ecf30c3019ae94f2e7ec7dcb7f Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 12 Mar 2014 19:22:11 +0000 Subject: [PATCH 057/545] Move auth rebuild to its own method. --- requests/sessions.py | 39 ++++++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 13 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index 425db22c..4c24984e 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -154,19 +154,7 @@ class SessionRedirectMixin(object): prepared_request._cookies.update(self.cookies) prepared_request.prepare_cookies(prepared_request._cookies) - if 'Authorization' in headers: - # If we get redirected to a new host, we should strip out any - # authentication headers. - original_parsed = urlparse(resp.request.url) - redirect_parsed = urlparse(url) - - if (original_parsed.hostname != redirect_parsed.hostname): - del headers['Authorization'] - - # .netrc might have more auth for us. - new_auth = get_netrc_auth(url) if self.trust_env else None - if new_auth is not None: - prepared_request.prepare_auth(new_auth) + self.rebuild_auth(prepared_request, resp) resp = self.send( prepared_request, @@ -183,6 +171,31 @@ class SessionRedirectMixin(object): i += 1 yield resp + def rebuild_auth(self, prepared_request, response): + """ + When being redirected we may want to strip authentication from the + request to avoid leaking credentials. This method intelligently removes + and reapplies authentication where possible to avoid credential loss. + """ + headers = prepared_request.headers + url = prepared_request.url + + if 'Authorization' in headers: + # If we get redirected to a new host, we should strip out any + # authentication headers. + original_parsed = urlparse(response.request.url) + redirect_parsed = urlparse(url) + + if (original_parsed.hostname != redirect_parsed.hostname): + del headers['Authorization'] + + # .netrc might have more auth for us on our new host. + new_auth = get_netrc_auth(url) if self.trust_env else None + if new_auth is not None: + prepared_request.prepare_auth(new_auth) + + return + class Session(SessionRedirectMixin): """A Requests session. From 4d8cb3244e8e4f84b250c10a48e025f9a8bf6137 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 12 Mar 2014 19:22:40 +0000 Subject: [PATCH 058/545] Add method for rebuilding proxy configuration. This includes auth. --- requests/sessions.py | 52 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/requests/sessions.py b/requests/sessions.py index 4c24984e..db2fca39 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -12,6 +12,7 @@ import os from collections import Mapping from datetime import datetime +from .auth import _basic_auth_str from .compat import cookielib, OrderedDict, urljoin, urlparse, builtin_str from .cookies import ( cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies) @@ -23,7 +24,10 @@ from .structures import CaseInsensitiveDict from .adapters import HTTPAdapter -from .utils import requote_uri, get_environ_proxies, get_netrc_auth +from .utils import ( + requote_uri, get_environ_proxies, get_netrc_auth, should_bypass_proxies, + get_auth_from_url +) from .status_codes import codes @@ -154,6 +158,8 @@ class SessionRedirectMixin(object): prepared_request._cookies.update(self.cookies) prepared_request.prepare_cookies(prepared_request._cookies) + # Rebuild auth and proxy information. + proxies = self.rebuild_proxies(prepared_request, proxies) self.rebuild_auth(prepared_request, resp) resp = self.send( @@ -196,6 +202,50 @@ class SessionRedirectMixin(object): return + def rebuild_proxies(self, prepared_request, proxies): + """ + This method re-evaluates the proxy configuration by considering the + environment variables. If we are redirected to a URL covered by + NO_PROXY, we strip the proxy configuration. Otherwise, we set missing + proxy keys for this URL (in case they were stripped by a previous + redirect). + + This method also replaces the Proxy-Authorization header where + necessary. + """ + headers = prepared_request.headers + url = prepared_request.url + new_proxies = {} + + # Consider proxies. First evaluate the new proxy config. If we are + # being redirected to a host on the NO_PROXY list then we want to + # remove the proxy dictionary entirely. Otherwise, if there's a relevant + # environment proxy, set it if we don't already have a proxy to go to. + if not should_bypass_proxies(url): + environ_proxies = get_environ_proxies(url) + scheme = urlparse(url).scheme + + try: + new_proxies.setdefault(scheme, environ_proxies[scheme]) + except KeyError: + pass + + # If there's a proxy-authorization header present, remove it, then add + # a new one (potentially re-adding the one we just removed). + if 'Proxy-Authorization' in headers: + del headers['Proxy-Authorization'] + + try: + username, password = get_auth_from_url(new_proxies[scheme]) + if username and password: + headers['Proxy-Authorization'] = _basic_auth_str( + username, password + ) + except KeyError: + pass + + return new_proxies + class Session(SessionRedirectMixin): """A Requests session. From 4f6dca42ea0fb3d1c4706e63e594e43f7a3237f7 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 12 Mar 2014 21:21:14 +0000 Subject: [PATCH 059/545] Remove some extraneous comments to please KR. --- requests/sessions.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index db2fca39..65902d87 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -217,10 +217,6 @@ class SessionRedirectMixin(object): url = prepared_request.url new_proxies = {} - # Consider proxies. First evaluate the new proxy config. If we are - # being redirected to a host on the NO_PROXY list then we want to - # remove the proxy dictionary entirely. Otherwise, if there's a relevant - # environment proxy, set it if we don't already have a proxy to go to. if not should_bypass_proxies(url): environ_proxies = get_environ_proxies(url) scheme = urlparse(url).scheme @@ -230,8 +226,6 @@ class SessionRedirectMixin(object): except KeyError: pass - # If there's a proxy-authorization header present, remove it, then add - # a new one (potentially re-adding the one we just removed). if 'Proxy-Authorization' in headers: del headers['Proxy-Authorization'] From 724038e4b5e94addb0bf6c767b7f5578b172c659 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 12 Mar 2014 21:53:07 +0000 Subject: [PATCH 060/545] Test stripping standard auth. --- test_requests.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test_requests.py b/test_requests.py index 17de8491..1bebb1ad 100755 --- a/test_requests.py +++ b/test_requests.py @@ -17,6 +17,7 @@ from requests.compat import ( Morsel, cookielib, getproxies, str, urljoin, urlparse) from requests.cookies import cookiejar_from_dict, morsel_to_cookie from requests.exceptions import InvalidURL, MissingSchema +from requests.models import PreparedRequest, Response from requests.structures import CaseInsensitiveDict try: @@ -865,6 +866,22 @@ class RequestsTestCase(unittest.TestCase): preq = req.prepare() assert test_url == preq.url + def test_auth_is_stripped_on_redirect_off_host(self): + r = requests.get( + httpbin('redirect-to'), + params={'url': 'http://www.google.co.uk'}, + auth=('user', 'pass'), + ) + assert r.history[0].request.headers['Authorization'] + assert not r.request.headers.get('Authorization', '') + + def test_auth_is_retained_for_redirect_on_host(self): + r = requests.get(httpbin('redirect/1'), auth=('user', 'pass')) + h1 = r.history[0].request.headers['Authorization'] + h2 = r.request.headers['Authorization'] + + assert h1 == h2 + class TestContentEncodingDetection(unittest.TestCase): From daf56b3f62f91c7b6ae783ef74ee41ff3c7af89f Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 15 Mar 2014 10:33:00 -0500 Subject: [PATCH 061/545] Fix #1960: A Response's history should be a list --- requests/sessions.py | 1 - test_requests.py | 10 ++++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/requests/sessions.py b/requests/sessions.py index 425db22c..1e6de8b2 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -527,7 +527,6 @@ class Session(SessionRedirectMixin): history.insert(0, r) # Get the last request made r = history.pop() - r.history = tuple(history) return r diff --git a/test_requests.py b/test_requests.py index 17de8491..76943f2c 100755 --- a/test_requests.py +++ b/test_requests.py @@ -211,6 +211,16 @@ class RequestsTestCase(unittest.TestCase): req_urls = [r.request.url for r in resp.history] assert urls == req_urls + def test_history_is_always_a_list(self): + """ + Show that even with redirects, Response.history is always a list. + """ + resp = requests.get(httpbin('get')) + assert isinstance(resp.history, list) + resp = requests.get(httpbin('redirect/1')) + assert isinstance(resp.history, list) + assert not isinstance(resp.history, tuple) + def test_headers_on_session_with_None_are_not_sent(self): """Do not send headers in Session.headers with None values.""" ses = requests.Session() From 8d693a2a27d0a073c0d03823cab71a3716001285 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 15 Mar 2014 11:38:13 -0500 Subject: [PATCH 062/545] Fix #1955: Do not use original request in redirect The original request was never being properly overriden in resolve_redirects. As such being having a POST request respond with a 303 would generate a GET request. If the GET request encountered another redirect to something like a 307, then it would use the original request and generate another POST request. There are two parts to this fix: - The fix itself - The test infrastructure to ensure it does not regress because HTTPBin is insufficient --- requests/sessions.py | 5 +++- test_requests.py | 61 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/requests/sessions.py b/requests/sessions.py index 425db22c..fc5b0ffa 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -168,8 +168,11 @@ class SessionRedirectMixin(object): if new_auth is not None: prepared_request.prepare_auth(new_auth) + # Override the original request. + req = prepared_request + resp = self.send( - prepared_request, + req, stream=stream, timeout=timeout, verify=verify, diff --git a/test_requests.py b/test_requests.py index 17de8491..0b3c106b 100755 --- a/test_requests.py +++ b/test_requests.py @@ -8,6 +8,7 @@ import json import os import pickle import unittest +import collections import requests import pytest @@ -18,6 +19,7 @@ from requests.compat import ( from requests.cookies import cookiejar_from_dict, morsel_to_cookie from requests.exceptions import InvalidURL, MissingSchema from requests.structures import CaseInsensitiveDict +from requests.sessions import SessionRedirectMixin try: import StringIO @@ -1187,5 +1189,64 @@ class TestTimeout: assert 'Read timed out' in e.args[0].args[0] +SendCall = collections.namedtuple('SendCall', ('args', 'kwargs')) + + +class RedirectSession(SessionRedirectMixin): + def __init__(self, order_of_redirects): + self.redirects = order_of_redirects + self.calls = [] + self.max_redirects = 30 + self.cookies = {} + self.trust_env = False + + def send(self, *args, **kwargs): + self.calls.append(SendCall(args, kwargs)) + return self.build_response() + + def build_response(self): + request = self.calls[-1].args[0] + r = requests.Response() + + try: + r.status_code = int(self.redirects.pop(0)) + except IndexError: + r.status_code = 200 + + r.headers = CaseInsensitiveDict({'Location': '/'}) + r.raw = self._build_raw() + r.request = request + return r + + def _build_raw(self): + string = StringIO.StringIO('') + setattr(string, 'release_conn', lambda *args: args) + return string + + +class TestRedirects: + default_keyword_args = { + 'stream': False, + 'verify': True, + 'cert': None, + 'timeout': None, + 'allow_redirects': False, + 'proxies': None, + } + + def test_requests_are_updated_each_time(self): + session = RedirectSession([303, 307]) + prep = requests.Request('POST', 'http://httpbin.org/post').prepare() + r0 = session.send(prep) + assert r0.request.method == 'POST' + assert session.calls[-1] == SendCall((r0.request,), {}) + redirect_generator = session.resolve_redirects(r0, prep) + for response in redirect_generator: + assert response.request.method == 'GET' + send_call = SendCall((response.request,), + TestRedirects.default_keyword_args) + assert session.calls[-1] == send_call + + if __name__ == '__main__': unittest.main() From 8fc6a65be892651e6f16597fcaf9e608ab5d958b Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 15 Mar 2014 12:48:29 -0500 Subject: [PATCH 063/545] Actually assign history --- requests/sessions.py | 1 + 1 file changed, 1 insertion(+) diff --git a/requests/sessions.py b/requests/sessions.py index 1e6de8b2..a8d2bb11 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -527,6 +527,7 @@ class Session(SessionRedirectMixin): history.insert(0, r) # Get the last request made r = history.pop() + r.history = history return r From 98ac3df71369a6cf9bde35b8e5c8dc5935b028d0 Mon Sep 17 00:00:00 2001 From: Avi Das Date: Sat, 22 Mar 2014 15:42:23 -0500 Subject: [PATCH 064/545] Add __str__ to case insensitive dict. Logging headers for debugging purposes is often necessary, and the currently logging the headers would be using __repr__ which would log the implementation detail of headers, caseinsensitivedict. Adding str lends a more printing and log friendly implementation for case insentitice dict --- requests/structures.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requests/structures.py b/requests/structures.py index a1759137..2addb938 100644 --- a/requests/structures.py +++ b/requests/structures.py @@ -108,6 +108,9 @@ class CaseInsensitiveDict(collections.MutableMapping): def __repr__(self): return '%s(%r)' % (self.__class__.__name__, dict(self.items())) + def __str__(self): + return '%s' % (dict(self.items()) + class LookupDict(dict): """Dictionary lookup object.""" From 90f73378582e4e2cbc75a189a2cfa7826824f29e Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 22 Mar 2014 21:11:33 +0000 Subject: [PATCH 065/545] Style changes thanks to @sigmavirus24. --- requests/sessions.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index 65902d87..79ea7773 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -221,22 +221,21 @@ class SessionRedirectMixin(object): environ_proxies = get_environ_proxies(url) scheme = urlparse(url).scheme - try: + proxy = environ_proxies.get(scheme) + + if proxy: new_proxies.setdefault(scheme, environ_proxies[scheme]) - except KeyError: - pass if 'Proxy-Authorization' in headers: del headers['Proxy-Authorization'] try: username, password = get_auth_from_url(new_proxies[scheme]) - if username and password: - headers['Proxy-Authorization'] = _basic_auth_str( - username, password - ) except KeyError: - pass + username, password = None, None + + if username and password: + headers['Proxy-Authorization'] = _basic_auth_str(username, password) return new_proxies From a3fb689942e8a215858ef0619d0ec95e22186c77 Mon Sep 17 00:00:00 2001 From: Avi Das Date: Sat, 22 Mar 2014 16:30:22 -0500 Subject: [PATCH 066/545] Fix parenthesis typo --- requests/structures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/structures.py b/requests/structures.py index 2addb938..a710e9bb 100644 --- a/requests/structures.py +++ b/requests/structures.py @@ -109,7 +109,7 @@ class CaseInsensitiveDict(collections.MutableMapping): return '%s(%r)' % (self.__class__.__name__, dict(self.items())) def __str__(self): - return '%s' % (dict(self.items()) + return '%s' % (dict(self.items())) class LookupDict(dict): From c2e6fe4d5dfbb2bf4e31d70bb7c3f3eda2435beb Mon Sep 17 00:00:00 2001 From: Avi Das Date: Sun, 23 Mar 2014 11:38:37 -0500 Subject: [PATCH 067/545] Shorten str for case insensitive dict --- requests/structures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/structures.py b/requests/structures.py index a710e9bb..3aa090e8 100644 --- a/requests/structures.py +++ b/requests/structures.py @@ -109,7 +109,7 @@ class CaseInsensitiveDict(collections.MutableMapping): return '%s(%r)' % (self.__class__.__name__, dict(self.items())) def __str__(self): - return '%s' % (dict(self.items())) + return str(dict(self.items())) class LookupDict(dict): From b2db089eefe7a38789429ad8d02a84d9ea6958a5 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 24 Mar 2014 11:39:15 -0400 Subject: [PATCH 068/545] 3.4 --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 4b0ecfd4..d882e7a3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -73,7 +73,7 @@ Requests is ready for today's web. - Multipart File Uploads - Connection Timeouts - ``.netrc`` support -- Python 2.6—3.3 +- Python 2.6—3.4 - Thread-safe. From b92f4ec6fb8e53c71603dcd91f8e0883539e678d Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 26 Mar 2014 08:12:31 -0500 Subject: [PATCH 069/545] The expected value changes for the proxies keyword It used to be None but a recent PR changed that before my last one was merged --- test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index 13b9d64c..dbb38064 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1258,7 +1258,7 @@ class TestRedirects: 'cert': None, 'timeout': None, 'allow_redirects': False, - 'proxies': None, + 'proxies': {}, } def test_requests_are_updated_each_time(self): From 24819e8aae62170ed2c8439e400cc16423207660 Mon Sep 17 00:00:00 2001 From: Feng Liu Date: Fri, 14 Mar 2014 16:24:25 +0800 Subject: [PATCH 070/545] support request tuple data rewrite the TestModels Ajust the code --- requests/models.py | 4 +--- test_requests.py | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/requests/models.py b/requests/models.py index e2fa09f8..7390d1c6 100644 --- a/requests/models.py +++ b/requests/models.py @@ -408,9 +408,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): is_stream = all([ hasattr(data, '__iter__'), - not isinstance(data, basestring), - not isinstance(data, list), - not isinstance(data, dict) + not isinstance(data, (basestring, list, tuple, dict)) ]) try: diff --git a/test_requests.py b/test_requests.py index dbb38064..55e7a3d1 100755 --- a/test_requests.py +++ b/test_requests.py @@ -21,6 +21,8 @@ from requests.exceptions import InvalidURL, MissingSchema from requests.models import PreparedRequest, Response from requests.structures import CaseInsensitiveDict from requests.sessions import SessionRedirectMixin +from requests.models import PreparedRequest, urlencode +from requests.hooks import default_hooks try: import StringIO @@ -1275,5 +1277,30 @@ class TestRedirects: assert session.calls[-1] == send_call +@pytest.fixture +def list_of_tuples(): + return [ + (('a', 'b'), ('c', 'd')), + (('c', 'd'), ('a', 'b')), + (('a', 'b'), ('c', 'd'), ('e', 'f')), + ] + + +def test_data_argument_accepts_tuples(list_of_tuples): + """ + Ensure that the data argument will accept tuples of strings + and properly encode them. + """ + for data in list_of_tuples: + p = PreparedRequest() + p.prepare( + method='GET', + url='http://www.example.com', + data=data, + hooks=default_hooks() + ) + assert p.body == urlencode(data) + + if __name__ == '__main__': unittest.main() From 6c839985b9520ceeb0e48f204d86c92eb018bf3e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 28 Mar 2014 16:37:54 -0700 Subject: [PATCH 071/545] Update urllib to 1.8 (8a8c601bee) --- requests/packages/urllib3/__init__.py | 2 +- requests/packages/urllib3/_collections.py | 106 +++++++++++++++++- requests/packages/urllib3/connection.py | 47 +++++--- requests/packages/urllib3/connectionpool.py | 82 ++++++++------ .../packages/urllib3/contrib/pyopenssl.py | 45 ++++++-- requests/packages/urllib3/exceptions.py | 5 + .../packages/ssl_match_hostname/__init__.py | 2 +- requests/packages/urllib3/response.py | 18 ++- 8 files changed, 229 insertions(+), 78 deletions(-) diff --git a/requests/packages/urllib3/__init__.py b/requests/packages/urllib3/__init__.py index 73071f70..086387f3 100644 --- a/requests/packages/urllib3/__init__.py +++ b/requests/packages/urllib3/__init__.py @@ -10,7 +10,7 @@ urllib3 - Thread-safe connection pooling and re-using. __author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' __license__ = 'MIT' -__version__ = 'dev' +__version__ = '1.8' from .connectionpool import ( diff --git a/requests/packages/urllib3/_collections.py b/requests/packages/urllib3/_collections.py index 5907b0dc..9cea3a44 100644 --- a/requests/packages/urllib3/_collections.py +++ b/requests/packages/urllib3/_collections.py @@ -4,7 +4,7 @@ # This module is part of urllib3 and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php -from collections import MutableMapping +from collections import Mapping, MutableMapping try: from threading import RLock except ImportError: # Platform-specific: No threads available @@ -20,9 +20,10 @@ try: # Python 2.7+ from collections import OrderedDict except ImportError: from .packages.ordered_dict import OrderedDict +from .packages.six import itervalues -__all__ = ['RecentlyUsedContainer'] +__all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict'] _Null = object() @@ -101,3 +102,104 @@ class RecentlyUsedContainer(MutableMapping): def keys(self): with self.lock: return self._container.keys() + + +class HTTPHeaderDict(MutableMapping): + """ + :param headers: + An iterable of field-value pairs. Must not contain multiple field names + when compared case-insensitively. + + :param kwargs: + Additional field-value pairs to pass in to ``dict.update``. + + A ``dict`` like container for storing HTTP Headers. + + Field names are stored and compared case-insensitively in compliance with + RFC 2616. Iteration provides the first case-sensitive key seen for each + case-insensitive pair. + + Using ``__setitem__`` syntax overwrites fields that compare equal + case-insensitively in order to maintain ``dict``'s api. For fields that + compare equal, instead create a new ``HTTPHeaderDict`` and use ``.add`` + in a loop. + + If multiple fields that are equal case-insensitively are passed to the + constructor or ``.update``, the behavior is undefined and some will be + lost. + + >>> headers = HTTPHeaderDict() + >>> headers.add('Set-Cookie', 'foo=bar') + >>> headers.add('set-cookie', 'baz=quxx') + >>> headers['content-length'] = '7' + >>> headers['SET-cookie'] + 'foo=bar, baz=quxx' + >>> headers['Content-Length'] + '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): + self._data = {} + if headers is None: + headers = {} + self.update(headers, **kwargs) + + def add(self, key, value): + """Adds a (name, value) pair, doesn't overwrite the value if it already + exists. + + >>> headers = HTTPHeaderDict(foo='bar') + >>> headers.add('Foo', 'baz') + >>> headers['foo'] + 'bar, baz' + """ + self._data.setdefault(key.lower(), []).append((key, value)) + + def getlist(self, key): + """Returns a list of all the values for the named field. Returns an + empty list if the key doesn't exist.""" + return self[key].split(', ') if key in self else [] + + def copy(self): + h = HTTPHeaderDict() + for key in self._data: + for rawkey, value in self._data[key]: + 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): + return '%s(%r)' % (self.__class__.__name__, dict(self.items())) diff --git a/requests/packages/urllib3/connection.py b/requests/packages/urllib3/connection.py index 21247745..662bd2e4 100644 --- a/requests/packages/urllib3/connection.py +++ b/requests/packages/urllib3/connection.py @@ -4,6 +4,7 @@ # This module is part of urllib3 and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php +import sys import socket from socket import timeout as SocketTimeout @@ -38,6 +39,7 @@ from .exceptions import ( ConnectTimeoutError, ) from .packages.ssl_match_hostname import match_hostname +from .packages import six from .util import ( assert_fingerprint, resolve_cert_reqs, @@ -53,27 +55,40 @@ port_by_scheme = { class HTTPConnection(_HTTPConnection, object): + """ + Based on httplib.HTTPConnection but provides an extra constructor + backwards-compatibility layer between older and newer Pythons. + """ + default_port = port_by_scheme['http'] # By default, disable Nagle's Algorithm. tcp_nodelay = 1 + def __init__(self, *args, **kw): + if six.PY3: # Python 3 + kw.pop('strict', None) + + if sys.version_info < (2, 7): # Python 2.6 and earlier + kw.pop('source_address', None) + self.source_address = None + + _HTTPConnection.__init__(self, *args, **kw) + def _new_conn(self): """ Establish a socket connection and set nodelay settings on it :return: a new socket connection """ - try: - conn = socket.create_connection( - (self.host, self.port), - self.timeout, - self.source_address, - ) - except AttributeError: # Python 2.6 - conn = socket.create_connection( - (self.host, self.port), - self.timeout, - ) + extra_args = [] + if self.source_address: # Python 2.7+ + extra_args.append(self.source_address) + + conn = socket.create_connection( + (self.host, self.port), + self.timeout, + *extra_args + ) conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, self.tcp_nodelay) return conn @@ -95,10 +110,12 @@ class HTTPSConnection(HTTPConnection): def __init__(self, host, port=None, key_file=None, cert_file=None, strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, source_address=None): - try: - HTTPConnection.__init__(self, host, port, strict, timeout, source_address) - except TypeError: # Python 2.6 - HTTPConnection.__init__(self, host, port, strict, timeout) + + HTTPConnection.__init__(self, host, port, + strict=strict, + timeout=timeout, + source_address=source_address) + self.key_file = key_file self.cert_file = cert_file diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index 243d700e..6d0dbb18 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -19,6 +19,7 @@ except ImportError: from .exceptions import ( ClosedPoolError, + ConnectionError, ConnectTimeoutError, EmptyPoolError, HostChangedError, @@ -170,13 +171,9 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): log.info("Starting new HTTP connection (%d): %s" % (self.num_connections, self.host)) - extra_params = {} - if not six.PY3: # Python 2 - extra_params['strict'] = self.strict - conn = self.ConnectionCls(host=self.host, port=self.port, timeout=self.timeout.connect_timeout, - **extra_params) + strict=self.strict) if self.proxy is not None: # Enable Nagle's algorithm for proxies, to avoid packet # fragmentation. @@ -238,8 +235,9 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): pass except Full: # This should never happen if self.block == True - log.warning("HttpConnectionPool is full, discarding connection: %s" - % self.host) + log.warning( + "Connection pool is full, discarding connection: %s" % + self.host) # Connection never got put back into the pool, close it. if conn: @@ -414,10 +412,13 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): :param retries: Number of retries to allow before raising a MaxRetryError exception. + If `False`, then retries are disabled and any exception is raised + immediately. :param redirect: If True, automatically handle redirects (status codes 301, 302, - 303, 307, 308). Each redirect counts as a retry. + 303, 307, 308). Each redirect counts as a retry. Disabling retries + will disable redirect, too. :param assert_same_host: If ``True``, will make sure that the host of the pool requests is @@ -451,7 +452,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): if headers is None: headers = self.headers - if retries < 0: + if retries < 0 and retries is not False: raise MaxRetryError(self, url) if release_conn is None: @@ -470,6 +471,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): headers = headers.copy() headers.update(self.proxy_headers) + # Must keep the exception bound to a separate variable or else Python 3 + # complains about UnboundLocalError. + err = None + try: # Request a connection from the queue conn = self._get_conn(timeout=pool_timeout) @@ -497,38 +502,41 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # ``response.read()``) except Empty: - # Timed out by queue + # Timed out by queue. raise EmptyPoolError(self, "No pool connections are available.") - except BaseSSLError as e: + except (BaseSSLError, CertificateError) as e: + # Release connection unconditionally because there is no way to + # close it externally in case of exception. + release_conn = True raise SSLError(e) - except CertificateError as e: - # Name mismatch - raise SSLError(e) + except (TimeoutError, HTTPException, SocketError) as e: + if conn: + # Discard the connection for these exceptions. It will be + # be replaced during the next _get_conn() call. + conn.close() + conn = None - except TimeoutError as e: - # Connection broken, discard. - conn = None - # Save the error off for retry logic. + if not retries: + if isinstance(e, TimeoutError): + # TimeoutError is exempt from MaxRetryError-wrapping. + # FIXME: ... Not sure why. Add a reason here. + raise + + # Wrap unexpected exceptions with the most appropriate + # module-level exception and re-raise. + if isinstance(e, SocketError) and self.proxy: + raise ProxyError('Cannot connect to proxy.', e) + + if retries is False: + raise ConnectionError('Connection failed.', e) + + raise MaxRetryError(self, url, e) + + # Keep track of the error for the retry warning. err = e - if retries == 0: - raise - - except (HTTPException, SocketError) as e: - # Connection broken, discard. It will be replaced next _get_conn(). - conn = None - # This is necessary so we can access e below - err = e - - if retries == 0: - if isinstance(e, SocketError) and self.proxy is not None: - raise ProxyError('Cannot connect to proxy. ' - 'Socket error: %s.' % e) - else: - raise MaxRetryError(self, url, e) - finally: if release_conn: # Put the connection back to be reused. If the connection is @@ -538,8 +546,8 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): if not conn: # Try again - log.warn("Retrying (%d attempts remain) after connection " - "broken by '%r': %s" % (retries, err, url)) + log.warning("Retrying (%d attempts remain) after connection " + "broken by '%r': %s" % (retries, err, url)) return self.urlopen(method, url, body, headers, retries - 1, redirect, assert_same_host, timeout=timeout, pool_timeout=pool_timeout, @@ -547,7 +555,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # Handle redirect? redirect_location = redirect and response.get_redirect_location() - if redirect_location: + if redirect_location and retries is not False: if response.status == 303: method = 'GET' log.info("Redirecting %s -> %s" % (url, redirect_location)) diff --git a/requests/packages/urllib3/contrib/pyopenssl.py b/requests/packages/urllib3/contrib/pyopenssl.py index d9bda15a..7c513f3a 100644 --- a/requests/packages/urllib3/contrib/pyopenssl.py +++ b/requests/packages/urllib3/contrib/pyopenssl.py @@ -29,9 +29,8 @@ Module Variables ---------------- :var DEFAULT_SSL_CIPHER_LIST: The list of supported SSL/TLS cipher suites. - Default: ``EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA256 - EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EDH+aRSA EECDH RC4 !aNULL !eNULL !LOW !3DES - !MD5 !EXP !PSK !SRP !DSS'`` + 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 .. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) @@ -43,7 +42,7 @@ from ndg.httpsclient.subj_alt_name import SubjectAltName as BaseSubjectAltName import OpenSSL.SSL from pyasn1.codec.der import decoder as der_decoder from pyasn1.type import univ, constraint -from socket import _fileobject +from socket import _fileobject, timeout import ssl import select from cStringIO import StringIO @@ -69,12 +68,22 @@ _openssl_verify = { + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, } -# Default SSL/TLS cipher list. -# Recommendation by https://community.qualys.com/blogs/securitylabs/2013/08/05/ -# configuring-apache-nginx-and-openssl-for-forward-secrecy -DEFAULT_SSL_CIPHER_LIST = 'EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM ' + \ - 'EECDH+ECDSA+SHA256 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EDH+aRSA ' + \ - 'EECDH RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS' +# A secure default. +# 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" orig_util_HAS_SNI = util.HAS_SNI @@ -139,6 +148,13 @@ def get_subj_alt_name(peer_cert): class fileobject(_fileobject): + def _wait_for_sock(self): + rd, wd, ed = select.select([self._sock], [], [], + self._sock.gettimeout()) + if not rd: + raise timeout() + + def read(self, size=-1): # Use max, disallow tiny reads in a loop as they are very inefficient. # We never leave read() with any leftover data from a new recv() call @@ -156,6 +172,7 @@ class fileobject(_fileobject): try: data = self._sock.recv(rbufsize) except OpenSSL.SSL.WantReadError: + self._wait_for_sock() continue if not data: break @@ -183,6 +200,7 @@ class fileobject(_fileobject): try: data = self._sock.recv(left) except OpenSSL.SSL.WantReadError: + self._wait_for_sock() continue if not data: break @@ -234,6 +252,7 @@ class fileobject(_fileobject): break buffers.append(data) except OpenSSL.SSL.WantReadError: + self._wait_for_sock() continue break return "".join(buffers) @@ -244,6 +263,7 @@ class fileobject(_fileobject): try: data = self._sock.recv(self._rbufsize) except OpenSSL.SSL.WantReadError: + self._wait_for_sock() continue if not data: break @@ -271,7 +291,8 @@ class fileobject(_fileobject): try: data = self._sock.recv(self._rbufsize) except OpenSSL.SSL.WantReadError: - continue + self._wait_for_sock() + continue if not data: break left = size - buf_len @@ -366,6 +387,8 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, ctx.load_verify_locations(ca_certs, None) except OpenSSL.SSL.Error as e: raise ssl.SSLError('bad ca_certs: %r' % ca_certs, e) + else: + ctx.set_default_verify_paths() # Disable TLS compression to migitate CRIME attack (issue #309) OP_NO_COMPRESSION = 0x20000 diff --git a/requests/packages/urllib3/exceptions.py b/requests/packages/urllib3/exceptions.py index 98ef9abc..b4df831f 100644 --- a/requests/packages/urllib3/exceptions.py +++ b/requests/packages/urllib3/exceptions.py @@ -44,6 +44,11 @@ class ProxyError(HTTPError): pass +class ConnectionError(HTTPError): + "Raised when a normal connection fails." + pass + + class DecodeError(HTTPError): "Raised when automatic decoding based on Content-Type fails." pass diff --git a/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py b/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py index 3aa5b2e1..dd59a75f 100644 --- a/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py +++ b/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py @@ -7,7 +7,7 @@ except ImportError: from backports.ssl_match_hostname import CertificateError, match_hostname except ImportError: # Our vendored copy - from _implementation import CertificateError, match_hostname + from ._implementation import CertificateError, match_hostname # Not needed, but documenting what we provide. __all__ = ('CertificateError', 'match_hostname') diff --git a/requests/packages/urllib3/response.py b/requests/packages/urllib3/response.py index 6a1fe1a7..db441828 100644 --- a/requests/packages/urllib3/response.py +++ b/requests/packages/urllib3/response.py @@ -9,6 +9,7 @@ import logging import zlib import io +from ._collections import HTTPHeaderDict from .exceptions import DecodeError from .packages.six import string_types as basestring, binary_type from .util import is_fp_closed @@ -79,7 +80,10 @@ class HTTPResponse(io.IOBase): def __init__(self, body='', headers=None, status=0, version=0, reason=None, strict=0, preload_content=True, decode_content=True, original_response=None, pool=None, connection=None): - self.headers = headers or {} + + self.headers = HTTPHeaderDict() + if headers: + self.headers.update(headers) self.status = status self.version = version self.reason = reason @@ -249,17 +253,9 @@ class HTTPResponse(io.IOBase): with ``original_response=r``. """ - # Normalize headers between different versions of Python - headers = {} + headers = HTTPHeaderDict() for k, v in r.getheaders(): - # Python 3: Header keys are returned capitalised - k = k.lower() - - has_value = headers.get(k) - if has_value: # Python 3: Repeating header keys are unmerged. - v = ', '.join([has_value, v]) - - headers[k] = v + headers.add(k, v) # HTTPResponse objects in Python 3 don't have a .strict attribute strict = getattr(r, 'strict', 0) From b361a4c0562c41f1cbf3cacd72bc8201d5ade3b7 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 2 Apr 2014 19:04:58 -0500 Subject: [PATCH 072/545] Make note of the fact that we fixed some CVEs --- HISTORY.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 19c2ff1b..896dca4c 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -14,6 +14,11 @@ Release History - The ``timeout`` parameter now affects requests with both ``stream=True`` and ``stream=False`` equally. +**Bugfixes** + +- No longer expose Authorization or Proxy-Authorization headers on redirect. + Fix CVE-2014-1829 and CVE-2014-1830 respectively. + 2.2.1 (2014-01-23) ++++++++++++++++++ From 8a23dd94cb27a2127cf962a53829b329cdb460aa Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Fri, 4 Apr 2014 23:58:18 -0400 Subject: [PATCH 073/545] replace reference to crate.io crate.io is gone, so a different mirror should be used as an example. Also, the list of PyPI mirrors should be mentioned. --- docs/user/install.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/user/install.rst b/docs/user/install.rst index 718c9c51..0ad4a8aa 100644 --- a/docs/user/install.rst +++ b/docs/user/install.rst @@ -26,9 +26,11 @@ Cheeseshop (PyPI) Mirror ------------------------ If the Cheeseshop (a.k.a. PyPI) is down, you can also install Requests from one -of the mirrors. `Crate.io `_ is one of them:: +of the mirrors. `pypi.douban.com `_ is one of them:: - $ pip install -i http://simple.crate.io/ requests + $ pip install -i http://pypi.douban.com/simple requests + +A `list of PyPI mirrors `_ is available. Get the Code From 96936b56164aa7973431375402ba6b41262277c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ionel=20Cristian=20M=C4=83rie=C8=99?= Date: Sat, 5 Apr 2014 16:40:13 +0300 Subject: [PATCH 074/545] Make it more clear where install commands are run (so that newbies don't run them in the REPL, I've seen it happen :). --- docs/user/install.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/user/install.rst b/docs/user/install.rst index 718c9c51..e1733d92 100644 --- a/docs/user/install.rst +++ b/docs/user/install.rst @@ -10,13 +10,13 @@ The first step to using any software package is getting it properly installed. Distribute & Pip ---------------- -Installing Requests is simple with `pip `_:: +Installing Requests is simple with `pip `_, just run this in your terminal:: - $ pip install requests + pip install requests or, with `easy_install `_:: - $ easy_install requests + easy_install requests But, you really `shouldn't do that `_. From fb2380c3cc48eac77cc164df3f277a045a9f8848 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Sat, 5 Apr 2014 11:57:43 -0400 Subject: [PATCH 075/545] delete pypi mirror section --- docs/user/install.rst | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/docs/user/install.rst b/docs/user/install.rst index 0ad4a8aa..3ef73290 100644 --- a/docs/user/install.rst +++ b/docs/user/install.rst @@ -21,18 +21,6 @@ or, with `easy_install `_:: But, you really `shouldn't do that `_. - -Cheeseshop (PyPI) Mirror ------------------------- - -If the Cheeseshop (a.k.a. PyPI) is down, you can also install Requests from one -of the mirrors. `pypi.douban.com `_ is one of them:: - - $ pip install -i http://pypi.douban.com/simple requests - -A `list of PyPI mirrors `_ is available. - - Get the Code ------------ From 6f52970a6d359e1a703f59478ef8d6070564318f Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 5 Apr 2014 17:29:00 +0100 Subject: [PATCH 076/545] Reinstate '$'. --- docs/user/install.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/user/install.rst b/docs/user/install.rst index e1733d92..ba480a5c 100644 --- a/docs/user/install.rst +++ b/docs/user/install.rst @@ -12,11 +12,11 @@ Distribute & Pip Installing Requests is simple with `pip `_, just run this in your terminal:: - pip install requests + $ pip install requests or, with `easy_install `_:: - easy_install requests + $ easy_install requests But, you really `shouldn't do that `_. @@ -39,7 +39,7 @@ Requests is actively developed on GitHub, where the code is You can either clone the public repository:: - git clone git://github.com/kennethreitz/requests.git + $ git clone git://github.com/kennethreitz/requests.git Download the `tarball `_:: From 4ca4865170ac98226ca214854975ce02062cdcac Mon Sep 17 00:00:00 2001 From: Pavlo Kapyshin Date: Thu, 10 Apr 2014 06:09:36 +0300 Subject: [PATCH 077/545] Fix typo --- requests/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/utils.py b/requests/utils.py index 6f4eb500..b703ac4c 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -61,7 +61,7 @@ def super_len(o): return os.fstat(fileno).st_size if hasattr(o, 'getvalue'): - # e.g. BytesIO, cStringIO.StringI + # e.g. BytesIO, cStringIO.StringIO return len(o.getvalue()) From af21ec6165c40c0c12f8dccc8379eb97de53da20 Mon Sep 17 00:00:00 2001 From: "ap-Codkelden@github.com" Date: Sat, 12 Apr 2014 21:13:52 +0300 Subject: [PATCH 078/545] Font layer convert to curves --- ext/requests-logo.ai | 1222 ++++++++++++++++++++++++------------------ 1 file changed, 707 insertions(+), 515 deletions(-) diff --git a/ext/requests-logo.ai b/ext/requests-logo.ai index c8bedb8d..8f74083a 100644 --- a/ext/requests-logo.ai +++ b/ext/requests-logo.ai @@ -1,5 +1,5 @@ %PDF-1.5 % -1 0 obj <>/OCGs[6 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream +1 0 obj <>/OCGs[6 0 R 29 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream @@ -17,15 +17,15 @@ xmlns:xmpGImg="http://ns.adobe.com/xap/1.0/g/img/"> Adobe Illustrator CS5.1 2011-06-23T03:13:46-04:00 - 2011-06-23T03:13:46-04:00 - 2011-06-23T03:13:46-04:00 + 2014-04-12T20:48:38+03:00 + 2014-04-12T20:48:38+03:00 256 - 196 + 200 JPEG - /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAxAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYqhNU1fStJsZL/VbyGwsoRWW5uZFijX5u5AxV4N58/5zH8k6Q0lr5Vs5fMF 2tV+tPW2tAfEFlMslD4IAezYq8O8y/8AOVH5va7ZXll9fh0y3vGFDp8RhliQVrHFNyaQBqipry22 I3qq8pSea4vfrV16t2eQluW5H1GUEciZGD0J/mIOKsx0385vP2g6tPc+VtZvtK01pWe00ma5a+gh jJqsfG4Uo/EfDy4VxV6h5d/5zU882phi13SLHUoVZRNPAJLadkqOR+08fKnSigYq+wNJ1Sy1bSrL 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 sVdirsVdirsVdirsVdirsVdirsVf/9k= + /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAyAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYqoX1/Y2FrJeX1xFaWkKlprid1jjRR1LOxCgfPFXhvn3/nL/wDL/QjJa+XI ZPMl8tV9SMmCzVh4zOpZ/wDYIQf5sVeGeYf+ctfzc1aK9t7e4tdKt7tQkf1OErLAoJJ9OV2duTDZ m/4HicVeP3d5f6hdSXN3PLd3UlWlmlZpJGpuSzMSTirL7X82vNuh6tLc+TdU1HQtMJUwaY92byNa KAeSyIsT1ap/u9q0xV6XoP8Azmf+Y9jEIdX07T9WK0HrcHtpie/L02Mf3RjFX1t5I826d5v8p6Z5 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 5pX9lc/mJpENvPHNLbaWq3CxsGKM1xKQrU6NTemKvp78mNSsb/8AKnyk9pMkwh0ixt5eDBiskNuk bq1CaEMhGKszxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2K uxV2KuxVjfnz8vfK3nvSIdI8y2z3VjBcLdxxpK8JEqI8YPKMqfsyttirCoP+cV/yMjQq/l55iTXk 97fA/L4JkGKo7QP+ccvyl8v6/aa9pGkyW2oWMgmtW+tXMiI4FPsySPXr3xV6XirsVdirsVdirsVd irsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdi rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirs VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsV dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVf/Z @@ -38,7 +38,7 @@ proof:pdf uuid:65E6390686CF11DBA6E2D887CEACB407 xmp.did:03801174072068118C14DE8EAF56EBAC - uuid:c5688439-e292-b24c-adf4-afab40481673 + uuid:d7a1c904-8296-4139-89d6-900760967c29 uuid:52d3fbd8-d8d7-2543-8e0c-81d9ea137ecb xmp.did:8CF5709C0E20681188C6A12CE4B46A4D @@ -61,7 +61,7 @@ EmbedByReference - /Users/kreitz/Library/Caches/TemporaryItems/page3f.gif + page3f.gif @@ -75,7 +75,6 @@ 1 False @@ -85,19 +84,6 @@ 300.000000 Pixels - - - - Palatino-Roman - Palatino - Regular - TrueType - 3.8 - False - Palatino - - - Cyan @@ -109,7 +95,7 @@ - Default Swatch Group + Группа образцов по умолчанию 0 @@ -618,19 +604,48 @@ - endstream endobj 3 0 obj <> endobj 8 0 obj <>/Resources<>/ExtGState<>/Font<>/ProcSet[/PDF/Text]/Properties<>/XObject<>>>/Thumb 14 0 R/TrimBox[0.0 0.0 300.0 300.0]/Type/Page>> endobj 9 0 obj <>stream -HDM -@9EpM*R}([+"!!|d{ue 3IxMNXѠP06Q&1*t 83r[xɩf8bzP)z|-#r endstream endobj 14 0 obj <>stream -8;Z\rCD).?#_]nk"h@Aa_=@jgoOlX5-.:uQ+JS=Sl -;kM(ghg@C^)^2L>5mSkncPa`A+Ba\_EQ/$BC2ko2pi29ao4WBJ"o1`?gk]3!%'UIr -<8EVlO?ND?f>%=i<0ZCoL9:OWOkO#@~> endstream endobj 15 0 obj [/Indexed/DeviceRGB 255 16 0 R] endobj 16 0 obj <>stream + +endstream endobj 3 0 obj <> endobj 8 0 obj <>/Resources<>/ExtGState<>/Properties<>/XObject<>>>/Thumb 36 0 R/TrimBox[0.0 0.0 300.0 300.0]/Type/Page>> endobj 31 0 obj <>stream +HlWK% ܿS lQDi;=WaxYT1* + +2%`۟?{yGyz/{oky~~;F>~>=~v&4 5zr|j'kd^sǣ\Я98 w!8uU~yO#bhֳ^ˉ[X["Wk:7~ygb=cv=;,Å+Ch|53ڿ0|{=Fk. ;/6 72Dv9߸G+z=Rk^k_ PuM_>9/?~ LI&Be\F#9}LG} (qƴÙyo\^><#~ Eθჽ>wARwZJ';<% 24 5 +8ױuJ -TochN"6@5o_j'omq ^Dml?*mKR|2e'GmDZmfA^Gkm(Ц-U&BllIu Zr?:E+R˚{PŢhq$bᏫ/@:l q G4Dmo_Q_*IƧT<X7,䑱C21{& &Doz>u]mv *#vR㸁SpHƆ y +gI*y{Vngyiz7Z.ngY8j @LGqF.gY[Y/eN*qTI?R_R} wk@Nn|ٍk9N[#t)d{08]TH6NOle82,T@__yRٝO*a}iWpyQ=dPEC(sҐMs]|VܰBcC#D>-0{N+A@cCgWD A'L/*PI`_YHtJΈ}r1Yf0{ꝗf*_eDQJegKTIʺsiuY(-Em1E"=uH]C"eCxTul5bOi[Jۘ^-h6麐؁~T\Ll bЃޑM&u#+'Qeݖdci8ylWm0G+ k~Qz.^u+(| An+/AŮ@mD? U&_3=B\ND.TۍݫS.|4yFHTMxl~8E2]x{y\RQʢ9r^&SMh?JNH; 0/JG`p?F +Y=!ZW@ +/0%=Oq@'[=Q Ux*XqEa1 +]i܇tVDArfsƩwD@V>l F>Px߯ȗXNqfOe? %/vrNp#;3ECn$N4!:! }cH+nO9#Wlaȶq!RMJs`v<=gdhe(͠Uҵ{3H\7<9D6O5 +Ѽܔ7D%l>>`M;E/)wcv+и=DKUpd4)Z&TK*T7n DAyb ~P#rc`ˇ9w]KI;R6 3}냰ڢ5ESje|pLx6;\^co#%?J+n.ҊΚ7@Rtk   +r.؂@ʸs9's^T6ǜ:77xTPEA*PԳQZ|1XXAc5l†?"eIqlo[u -6>CF9A::ZOZfM&O=]dyyShB=sM_vj!qS1F]cmI_+[\ty/\!/Qb+ "`bBh#vYğ66N%7F/eйW 'f=EdH&P |HVH0cw?$AaF8(?厼aB 8ZRĴP@UE?gTmS'Aj_>_ep3kREV +@ė'GH!f\z*6: dc7w:*jǃQCr!8o]?#*_U8GTMpF3[v&# +[: YxXʻ *e<ۊ@[9r;}J{߯gk j cvi=7緯p0j߫NHsփ(D.t_hN[)H)#lV¶{Ũf:F{(AOߤil}oRϣeU f8Gȯ* +Alx77.CwOvmXrR›*MOn!D 6\URnDi]_ C U dF>ӓSAAFbY 9/  ?fvQW/' 3qZT>":nϭZ7*IY7d8o7&AniP9 Cƌi+nF3{$o}=_ RF=z#PBS$\^ӋmVÊʘВ<[:ik,DŽ.>WB9ij(-#R]=nk :VX:/E!a¬w2k/CF: 8"PhQ԰ҔZzګ*f̨W3":kx;4b>,*qo1t ()8}+Co( +,! VQо>43 'O5o|ޝ J(PAJ(!S }>o Ŕ..԰d%⚮T07Ѷ[0*P?M8}LT t>l/iO S~}cؔʯPr;t1'2i>mД).'e=qܞk'rXV'lvHТ#oO@K}zb9%5%uByING?LixBfHq ɮi6gξ#n0A͡w[Rf'flCqQn`,X2w){$O 7KİgsԢhLҠ龬wm /ń!zvA5YiZIgӃuMA\,vAf1m +_U\Qh#3l;[ZZB;n5;k:0!({S,-DL(cM^޶د5T_XqU{]<6Qd1 +\եa{3r!J'nadc\sP=hsÌb^ įhFXqu1[`MpX̮`m-GeL)L@=g8Oki6H=\tJ+mbR5mPÀָ.Gu}cR-_t]:4UwCm;+UR!q]*;^2OL#vV>stream +8;Z\q;%A+!$q#7=$hcV.l9`ZPS9u6E(^N7\^]E>LSS93^as7EAa(2NL`)*CAX\EK\ +ds1eSk?>tr4rZk>I"K/X2]RcN%\":Nqf"KFD@cScViE`-.gkRM5ZK=ung]?c"kSh8 +JmQ)`EpV,1*!62V\,gm~> +endstream endobj 37 0 obj [/Indexed/DeviceRGB 255 38 0 R] endobj 38 0 obj <>stream 8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn 6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( -l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 13 0 obj <>>>/Subtype/Form>>stream +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> +endstream endobj 35 0 obj <>>>/Subtype/Form>>stream 0 g /GS0 gs q 1 0 0 1 221.2627 230.3779 cm @@ -1718,78 +1733,8 @@ q 1 0 0 1 224.9375 222.293 cm -1.672 1.738 0.033 2.121 0 0 c f* Q - endstream endobj 17 0 obj <> endobj 12 0 obj <> endobj 6 0 obj <> endobj 18 0 obj [/View/Design] endobj 19 0 obj <>>> endobj 5 0 obj <> endobj 20 0 obj <> endobj 21 0 obj <>stream -HtT!a2@@@A -JE @Ԁ+-HS@AқIPBC zk[Vfֽs{Ϸ} /ANI1ɣ?}w|D+ŕ>yu->d{sr˵J"$΢U@8 ǩ؝-{<oas}eKlD|RJ'>nG{tiPk 84)&59g͐PY;=&).kpF^ ϧl:٨PoPtksqcO} O59lqls!x[;;І N<m@d]@< -gs#w)rۑȉǯxu~UQSRVhk>.}3NuSکTuj9uN#39o;6틜**t]\\fF9Q9QF t.tuuwpǕq^ -η'/(R9Zkh>ANjqs -Yŝr΃NMM WN3UUrE[U3:LTq&X8]{{{ oᏽ0cz=<.N;'ɓ1*<6BVgݼ ~R'86جi|5shfjf|3e$3VLdLOɘ1!{03}+ M?_W9s~v.x9uuMoAȾ 1 C0xbbۘI?a &c86!?`*<~/|[ m؁]fOߡ33|w08H@WtC"yD -z|TAoE*@ z p): b63av`(sK0s?<0c~ #Xw~eI_nQt8K$-c)E,ò,A, -|/~Vbe>/APVìQ|5Y6c4|XO7O [[ ۲v` ;0q.8o8$OS,Yȟ3+/7KkMIC)ʡ -Wd -)Bu.br)JnW 4Riݭ2*rG*3UReƳ+ q Gp_ 9kFIeoa_.V]='UOV5T#5VL35W 4۴VU;3uT'*NEuSݼgWz^PkEA!ae.񚠉^d-_zSS5M5C35K5Gsm-"-;zWKTkVjjZ3W}ڤ̹U̞;Cw?ڭ=ګ}گC:#:j ]1y%V`%>{XH.4VjEBJ7deu)WKAf,Ba9FEmQQ;PwUH -7KZ+ҸePpEyT@E܇Q ATCQ5jEmcx@]<'Q*Sx M-mϠb,~bE:Ly9&,sLYTK3L?s.|E3byopFryn&_+f\e LfΩfafe]܀yX`]d|,2HtjZX Fwfl1n30ofUG!3u ґaYwOs pZEAn+ aY$NM$|g69uHȟElꛍgpK" Y󄛓p* '!aUJ77pH^On ave0޸Yz lAONbHf$H(@Cur WI` ƮE !D{Kd2ü̏ B 1"@ H IR,͒>A]5QUW{6d[db nB6%& "cS- NKe*Bi:PqZhA"T25S[I^軻{{=;߹k!"h=TD,&h -M?*n{ hZZMϡHa5 hU&ja'i6͡VK]]cL^\rb;^11nXz;{\w4  א뎞uQY8;5e?'UY'0;5Xن6g#V gG$jֳO|&g6\4mh#GjmVl\Cjuc h܋Ȼ:)(4 +h.~Z&1Na lpr ; oS25S~Fb, ͏ܞ!S~.!:it~GG}; ]#! R^nL~ÖeSmwnz`[W f,6sys"Ԭ*FԞ-~i{`Xg -llyF%zv>׻ -}F}P2C5|11_Ϸ?Q01BE.EqVjld'Suy[aCG- Kbk}Hs.4~\^NT# ixP(i9=B|*8W fOP%<.?cz1VXVʳ'u h?je'|!5TuG6>1>]-$O8k~:,F -x^*)+Ej^&z 6"S~-Sih<Ê' - x5=f(WVȌ4gC1|XJrRb&KꨪFu)W1o6ھ$*DCut5jC5\m&)j9+Ԑ5Z"x_ԚUziKVg:fΠST K:hUf%27>f YF%:<uVն3sfHk^1jj<T5OkיV(3*Vu'[a”9]5K8 --u*ܽWzj*-)RӦ<۽v5Lga -ljXzñu =ѧOҊ5+Ypfl09}&5+Qa*,kZ8):ZQi饐lA5FxŘH-@<NN1BQL1Lx`-K-U#{~+UioNfyb۶KT\rdG{=xk_UhM>K -Ft觝Bi5;d'C_3pLmAgN-"uM5Z^;'RgRoJ]j>֜ik8XЪ.! ȳ̠/-TZ'"_oQ{Q1hr qfa& Wd>rxj&1 L@)pa. DmT$ u֪ɜ֛^G -VcƄHmϋ%U=5Bco`kXRN+/h%J.{'ke[8Ld1RyaNǥjF]w^%r6_B/;~FX~;Nx~11ۑꉚ %J.X/ئS[[iŽr##c:yb2{76E[GO)≠b\y3g, - 92}36hB*R9SlEYPc)Hk,`R~9Md',@Y&4$ ՙ3$M Wu s/=2-AcV =w39Y(aVLО#!*/VO|b/΢Ecܢ'qrB!!B &(< =,Xs*(-fnL,MXY~ -'g]ut -+4=0kRA[[ &!"HD2˦F7Y+НkCWZ(4(S <۫"!h5m'w?Yoث]SȏoW!˙a!"jݨC[oa {;of601Th!FYm*VJ[ - j-,Ҫ)߹Wð+/vr9޽瞏9rs0FbtsAp4;cl>E.S"Ċk3vUq 46 Wb+!o (]r.sԍ'| M~E 0ot|6Ls}> ~ -=KU5X_1 \CdO/wO407j,Z: -Vya2O#~@s~şC֩ &o*!}!F 5X smtcW2ϕT,+5c|t/ -\ _UƂw+۫Q{OGqR=s 1˥/P{otcIUQ^QOܗ7u|#4 cd1Pg7Ac\51?U5zyX1uc]:@w9l8ɾ<6sӹe~<,c W͓GOp-F GToW>T[|c0n^q_1.5۩c H%|,CnhsxgMOg1spUTuC+80߭I9syr]  q\6r?źPK=_wh/O6uV5Wg}k i:pyzno)?P{zgV]?岼/^LoG՜7z@q ahC7_Y)'̿r9POEw4uO9P-黷Jb9@?y`5J?_Lr:.ss6 ^yYfؗHk0\:{|pR6ILkO88|o_?U"-@?0i( :6 -Wj -W Q҂Xy`F0g ]AZz>?U;5Xr\pӂho tOW" S6N_RF}m4*~g0Ms0kt( \EQ45^0n =C5(NvUp@y[phck4L+8J6Ep]}W 􄴤0M2VzzPm`!G;)~i8tIpip KafZeǂ2+=kUpF8}#WEG -Q!x`pZA'&JO`|Z0b\EW*|9s&x\0SA0| MazshWP\08Uf CO F^\qjb8ʞӂ5}tYBpUGx1QEx($Ix*p.k  -G1p`8H62G0FV ӍQtzl[EUF^H*,rxrͭ %okN:u^7iք7iOgk t"ҩLO$t51fƴX<#adTz% }ÎjIuuN)I2q+̘4=ݤ,T?k79lF#J2CiodYdT̰cEf&R#~3تtfٯ99Haw oQN,z5FY6h^` ¢VGDlf -k4l3jl6SP`gYjy"؜i^R%wTܮe$e-lY3繰HM!<f+-lϳt6t%̥# ,bxaas[aI'Le(% nvLoFD&i;mFߘ+zhǽ7xyl1S%[8N{Gg -[7Y7&\;\azӆ~d/2Rlgl8yrbM(XdP)1Č~(E3pRO}%5moJ#H -D[>8}0U49Y+"=}dr6`4AqmM/Dz.1J\u{T_tC3K6EpNrX5U_}4ܛr3S/`y{ϣhQt]g b)-wm"ae`t{yfXٴKgm[̯ߑ?һ+8{e/Ͽڹjò}4ga֢10ߐ3?~3>ٮze>ڹc3[oOcwnn?gڱGuRgηwv;ܭo3zצѻ;}?=t8ލ񓅞W?xc*/ ީ;™Sn{/n>fǓ[wetgeٮ.oiSD"8Q%" -J)PFH4&%MLPTTBe" T6 - (~yz'yϹO{rwݳoי۶1(v]la2bϒK v%H|)f: }*FxU5r# -CU5q526܈ p3nA}4hFhl$]4E34G ܎p'Z.܍6h ܃vhLt@:"9肮Žy聞xÅp‹"08-F F#!FpHG1x1Őix -Oc:x3Jx3٘c˘W*^cޠo-X`XEX%XeXX>Vf2CzlFlGSl|ϱ۰;n^~AWq[w88?Gq -WqgpOq_\4)Li*Uy/g: -^꼊5XW^X׳6Ⱥx3oa}6lFl؄Mٌق%`+ּw 23Ŏf'vf+sy/c7vgy?{'{7þtq,> Y,` 3!aɇ88>ʉ8)i|Os:|3<_Y9|/s._| |os>˅\\¥\\|s?j\!^:zLFnGSn~Ϲ۸;n^~AWy[w<ߕf6 -|Q]WRJ6䋁#;~@_,Tv'vEXnw(AK/+\g"[Պbw(v]1!PȍW#o{` r"oTZ]pZ%ur Yt(PEtr Y݊Rjvr^|qrQTʋ<y"[ 1?d[b$'=G~IǬ-ˡ6^n'8");;tvYjwN$!wX`co1.N;1e;'Rc;0X^X?eFt6ƪV -"`4n{.oc&ڛl\a}Iwڇi} sڇitɔ`ahsP4ߨM;1{Bx x.Gq8 -x -88$C-Ųi%.r cZ۸֮+ɎvH:BHPBG$GJ8mʼn!V޵kCm &Фx@Cq7!!ax 3Nt`PÛ2޻v?x{w9{@ -ʔR%À̩RR:Kʤnɔ^.9$W * -GuarKU1 -ֹ•<%(!۬Bȱ3pAX`u9gEK(pt'xjAabI#˚c;m;*Ҝ4tڌ3]ƝF#-,_;zwܬ iv/jnaFa0r_lrx\Up -6`U -erEE)+yURP0V*X+y[oT=FjhMZŊC*qhaJi¦ItǓfЦHhS$Z4ᄣB3ZCmmwR4rz -HxN -̪a>1WELeLCC3\a: pڌ/O4 LTLR蘤1)#I;_.Hًä}^bHzO{[F8{JMSK(_"GQD'Rjhu{qIE7c5h 4xcvy`y DXY)Yidȇ~z;U -ݛh,| {T>JVuJ.?I*iߖP\PJ^MwOwGT&B^1EM1*|+MES uSq.3 pn&w -έ -_{a evEvaOK= cL8U}¼?e6,R76R PLBRz#wnKۗ.Yb-Z:t37}5Dc)ew0 R 0?46y|m`^2Uv3#2c?~|UʚL -`>WD)⛸]\s9j_"jϣvӨ=ڏvO,xK-[[b t\qq_Džev _Gm 9@ݱo-sˢ,bB89!O@2#?gdW{ҿk*GY+&{wO35d-|Effrz§m VI%k&{¬>Ƀf`r6dÜ➃xCt+4>;FzY+-5Yd3MU %&&Ƃw,- ו{oP)?^ ^◫*M\lV{V΃| ?ߐWsЩ >U$2 -!K >YP"3l݊ -X˼3fƪ'c2A-mm mrǢʥJ)2-~JhZL2ڜV=BW:~0(_Y~"MWG[(nK -a!$A '0Bwb ܺ[z_"vmG|ZXc2;Anakcm8d ܘd>v|xbcs_Ϸ{{v}sě47=nPbVa%} ʃxpGaOԭk|lZѕg7[鵘;VO& }^`&d+Ն9 _| endstream endobj 11 0 obj [/ICCBased 22 0 R] endobj 22 0 obj <>stream + +endstream endobj 39 0 obj <> endobj 34 0 obj <> endobj 29 0 obj <> endobj 40 0 obj [/View/Design] endobj 41 0 obj <>>> endobj 33 0 obj [/ICCBased 42 0 R] endobj 42 0 obj <>stream HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= @@ -1798,420 +1743,667 @@ H N')].uJr  wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! -zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km endstream endobj 10 0 obj <> endobj 23 0 obj <> endobj 24 0 obj <>stream -%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 15.0 %%AI8_CreatorVersion: 15.1.0 %%For: (Kenneth Reitz) () %%Title: (Untitled-2) %%CreationDate: 6/23/11 3:13 AM %%Canvassize: 16383 %%BoundingBox: 2 -270 301 -43 %%HiResBoundingBox: 2.41797 -269.6123 300.3203 -43.2681 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 11.0 %AI12_BuildNumber: 39 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_Cropmarks: 0 -300 300 0 %AI3_TemplateBox: 150.5 -150.5 150.5 -150.5 %AI3_TileBox: -138 -506 438 228 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 6 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI9_OpenToView: -147 8 2.68 1595 837 18 0 0 35 161 0 0 0 1 1 0 1 1 0 1 %AI5_OpenViewLayers: 7 %%PageOrigin:-250 -450 %AI7_GridSettings: 72 8 72 8 1 0 0.8 0.8 0.8 0.9 0.9 0.9 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 25 0 obj <>stream -%%BoundingBox: 2 -270 301 -43 %%HiResBoundingBox: 2.41797 -269.6123 300.3203 -43.2681 %AI7_Thumbnail: 128 100 8 %%BeginData: 10032 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45FD47FF7D27F8F8F82752A8A8FD73FFA8522727FD04F82727F8F8F8 %277D7DFD6EFF7D52FD09F827FD06F827FD6AFFA87DFD0BF827FD08F87DFD %68FF7D27FD08F87D7DF87D27F8F827527D52F8F852A8FD65FFA827FD08F8 %27F82727FD05F8277DA8A8F8F82727A8FD62FFA852FD0AF87DA8F8F8277D %52272727F8F82752F8F8F852FD61FF52FD0DF82727F8527D277D7DF8F8F8 %7DA827F8F827FD5FFFA827FD11F852FFA87D2727F8F87D7D5227F827527D %A8FD04FFA87D7D527D7DA8A8FD4FFF7DFD13F827A852F87D27F8F87D277D %F8F827A827F827275227F8F8F827F8F8F85227527DFD4BFF52FD07F827FD %0CF87D52F8F85227F8277D7D277D275227F8527DF87D27F8F827277D2752 %527D272727A8FD47FFA827FD07F87D27F8F8F827FD06F8277D27F8F8F87D %27F85252F87DFF7DA8A82752FFFFFFA8527DF8F852F8F8277DFFF87DA8FD %46FF7D27FD07F8275227F827F852FD05F8525227F8F852FF5227F8A87DFD %07FFA8FD05FF52F8F87DF827A8FFA827F8A8FD45FF7DFD09F82727F82727 %F8F827F8F8F85252F8F8277DA8527D52F852FD0DFF7DA87D7DFF7DA8FD04 %FFA87D7DFD43FF7DFD0AF827F8F8F827FD04F8275227F82727F87DF82752 %522727FD09FFA8FFFFFFF827FF7D52A8FD06FF7DF8A8FD41FF52FD12F827 %7DA8F8F8F8A87DF8527D7D5227A87DFD09FF52A8A8FF52F82727F87D7D27 %277DFFFFF8F852FD40FF7DFD0FF8FD0427F87DFD04F8A8FF7DFF527D2727 %A87DFD09FF7DFFA852FF5252FFFFA87DF827F8A8A827527D7DFD3EFF7DFD %04F82727FD04F852FD05F87D27F8527DFD05F87DFFFF5227F87D522727FD %09FF27FF52F8FFFFA8FF27FD05F8527D7DFF5227FD3DFF7DFD06F852F8F8 %F82752FD05F827F8527D5227F8272727F8FD0452A8A87D2752FD04FFA8FD %04FF7DA87D27FFFFFFF8F8F87DF8F8F827A8FFFFA8F8FD3CFFA8FD12F852 %5252F82752F827A827525227277DFFFF7D277DFD04FF527DFFA8527DFF7D %A8FFFFA87D527DFFA8272752A8FFFFFF27A8FD3AFFA827FD09F827FD05F8 %275227A87DF8F8527D52FFA8A8FF527D7D5252FFFF5252FD04FF7DA85252 %27FD0AFFA827FFFFFF7D7DFFFF52FD3BFF27FD0AF827FD06F8275252FD04 %F852FD05FF5252272727FD04FF527DFFFFA852F8527DA8A8A8FFFFFFA827 %A8FFF827FFFFFF52F8277D52A8FD39FF7DFD0BF827FD05F8527D272752F8 %F852FD06FFA8A852F8527DFFFFFF27A8FFFFA8F8F87D2752277DFD04FFF8 %7DFD05FF7DF8F8527DF8FD39FF7DFD11F8527D27F82727F8F8FD05FFA8A8 %7DA852A85227A8FFA8FD05FF5227F8272727A87D7DFFFF7DA8A852A8FFFF %2727F87D7D27FD39FFFD0FF827525227F8F8F8272752A8FD05FF7D27277D %7D2727A8FD08FFA82727F8277DFFA827A8FFFFFFF8F8F8527D27F85252A8 %52FD38FF27FD0EF8527D52FD05F87DF87DFD06FFA82752277D27F87DFD06 %FF27A8A85227FD055227A87DFFFF7DA8A87D277D7DFD3BFF7DFD0EF85252 %27F8F827F8F87DFFA8FFA8FD05FFA8527D277D52F87DFD06FF5252FF2727 %7D7DFD06F852A8FFA8277DA8FD3CFF7DFD0DF82727FD04F8275227A8FFFF %5227F8FD04FFA827F8F852F8277DFF7DA8FD04FF527D27FD0AF8A8525227 %52A8FD3CFF52FD0CF8275252FD05F8277DA87DFFFF7DF87DFFFFFF7DF852 %A8A82752A8FFA827A8FFA8A8A8527D27F8F8F82727FD04F827FFFFA8A8FD %3DFF52FD06F827F827F852F852A852F8F82727A87D2752527DFFFFFFA8FD %04FFF87D527DA8F827FFA82752FFFFA827277DFFF827277D7D522727F8F8 %F8FD40FFA8FD06F827F85227527DF82752F8F8277D27A852F8F82727A8FD %06FFA827F8F852A8FFFFFF7D52FD04FF7DF8FFA85252A8FFFF52A82727F8 %27FD40FF27FD05F82727F87D27FD0AF82752F8F8F827A8FD05FFA8A82727 %5227F8A8FFFF277DFFFFA87D277DFFFFFFA8F852FFFFA8FFA852F8FD3FFF %52F8272727FD04F82752FD07F87D5252F82727A8527DFFFFA8FD04FF7D27 %7DFFA827F87DFF52527D7D5227F852FD04FFA8F827FD05FF5227FD3DFFA8 %52FD05F87DF8F8F852F8F8F87D27527D52F8527DF8F82752A8FFFF522752 %FFFFFF5252F87D52FF7DFFA8F827FF27F8F8F852FD05FFA8FD06FFA852FD %3CFF525227FD04F827F8F8F827F8F8F82752527DFF7DFD0452FD07FF5252 %A8FF5227F827F8F8FFFF7D27FD06F827FD0DFFA87DFD3AFFA8F8F8F85227 %27F8F8F87D5227F827F827522752F8F87DFD0AFF7D7D527D52F827527DF8 %52A87DFD04F82727527DFD0EFF7D7DFD39FFA8F8F8F8527D5252277D7D7D %FF5227A8A87D52F87D7DA8FD0BFFA8527D277DF827F87DFFFFF82752A8A8 %FD13FF7DFD39FF52FD07F8FFFFFF522752A87DFFFFFFF82752FF7D5252FF %52F87DA8275227F8275252277D2727F852FF5252FD0EFF7DA8A8A8FD04FF %7DFD39FF7DF82727F8F8277D7D7D52527D277DFD04FF277DFFFFA8A87D52 %A852F8277D27F82727A8FFFF2752F87DF87D2752FD0FFF7D7D52A8FD3EFF %F8F8F82727522727F8F8F827F87D527D527DA8A8FD06FFA8FFFFFFFD05A8 %FFFFFF27F82752A8522752A8FD0FFF27277DFD3EFF7DFD04F82727FD05F8 %27527D5227F827FD13FF5227F87DA8527D7DFD10FFA87D52A8FD3EFF7DFD %07F827F8F87D7D7DFD0427FD13FFA8F87D277DA85252FD0EFF7D7DA85227 %527DFF52A827A8FD39FF7DF8F852F87DF8525252F87DF852F8277DFD14FF %52F8A8A87DA87D7DFD0EFF7D7D2752522727A87D2727FD3AFF7DF82752F8 %27F852F85252F8F8F852A8FD14FF52F827A87DA8FFA8FD0FFF7D7DF87D52 %A8277DFF527DFD3AFFA8F8F827F8F8F87DF827275227A8FD15FF52F82752 %FFA8FD11FF52277D522752F8272752FD3DFF52FD05F82752F8277DFD16FF %52F87D277DFD12FF7DF827A87D2727F87D5252FD40FF7D52277D277DFD17 %FFA8F87D7DA8FD14FF7DA8F8527D5227FFFF52A8FD40FFA8FD1AFFA82752 %52A8FD14FF5227F8F8275227277D7D7DFD5CFF277D52A8FD13FFA8275252 %27277D52F8F87D7DFD26FFA8FD04527DFD07527D7DFD27FF7D2752A8FD14 %FFF8F827527D522727F87D7DFD27FFA85252FD0DF87DFD25FF7D7DA8FD15 %FF52F8F87D7DF8A827A8A8FD2BFF7DF8F8F87DA8FFA8FF7D27F8F8F852FD %23FFA827FD16FFA8272752275252FF7D27FD15FF7D27A8FD14FFA8F8F827 %FD07FF52F8F8F8FD23FFA87DFD16FFA85227F8F87DA8FF52FD15FF52F8F8 %FD15FFA8F8F8F8FD07FFA8F8F8F87DFD3AFFA8F8527D7D7DFD18FF27F827 %A8FD14FFA8F8F827FD08FF27F8F87DFD3BFFA8FD1CFF27F827FD15FFA8F8 %F827FD08FF27F8F852FD58FF27F827FD15FFA8F8F827FD08FFF8F8F8A8FD %09FFA87D7DA8FD0DFFA87D7DA8FFFFFFA8A8FD06FFA8FD09FFA8A8FD09FF %7D7D7DA8FD0AFF7D7D7DA8A8FD07FF27F827FD09FFA87D7D7DA8FD07FFA8 %F8F827FD07FF7DF8F852FD08FF52272752F8F8F8A8FD09FF7DFD05F8277D %52F8FFFFFFA8A85227F8FD05FFA8A87D52F8A8FD07FF52F8522727F8277D %FD06FF7DF8272727F8F827A8FFFFA87D27FD07F8A8FFFFA82727275227F8 %F852FD05FFA8F8F852FD06FFA827F827FD07FFA8F827A8FFFFFF52F8F8A8 %FD06FF7D27F87DA8FF7D27F8F8F852FFFF52FD04F827FD04FF7D27FD04F8 %A8FD05FFA827F8A8FFFFFF7DF8F87DFD04FF52F87DFD04FF52F852FFFFA8 %2752F8F8F852275227FFFFA8F827A8FFFFFFA8F8F8FD05FFA8F8F827FD05 %FF7DF8F852A8FD06FFA8F8F87DFD05FF27F8F8FD05FF52F8F8FD05FFA852 %F8F852FFFFA8A87DF8F827FD05FF7DA8F8F8F8A8FD04FFA8F8F8A8FD05FF %52F8F8A8FFFFA8F827FD06FFF87DFD05FF27F852FD06FF27F87DFD05FF7D %F8A8FD04FFA8F8F852FF52F827F82752FD09FF27F827FD06FFA8F8F87DFF %FFFF7DF8F8FD07FFA8F8F87DFD04FFA827F852FD07FF27F827A8FD04FF52 %F852FD06FFA8F8F87DFFFF52F852FD06FF5252FD05FF27F827FD05FFA8F8 %F8FD06FFA827FD05FFA8F8F827FF7DF8F8F827FD09FFA8F8F87DFD06FFA8 %F8F852FFFFA827F852FD07FFA8F8F87DFD05FFF8F827FD07FF27F8F8FD04 %FFA8F8F852FD07FFF8F827FFFF52F827FD06FFA8FD06FF27F852FD05FFA8 %F8F87DFD06FFA8FD05FFA8F8F827FFFF7DF8F8F852FD08FF7DF8F87DFD07 %FFF8F827FFFF7DF8F87DFD07FFA8F8F87DFD05FF27F852FD07FF52F8F8A8 %FFFFFF7DF8F8A8FD07FF27F827FFFF52F8F852A8FD0BFF27F852FD05FFA8 %F8F8277DFD0BFFA8F8F827FFFFFF52F8F8F87DFD07FF52F8F8FD047DA87D %A87DF8F827A8FF52F8F8A8FD07FFA8F8F852FD05FFF8F827FD07FF27F8F8 %A8FFFFFF52F8F852A87DA8FD047DF8F8F8FFFFA8FD05F85252A8FD07FF27 %F852FD06FF27FD04F827527DA8FD06FFA8F8F827FD04FF27F8F827A8FD06 %FF52F8F8FD0927527DFFFF52F827A8FD07FFA8F8F8A8FD05FF27F852FD07 %FF52F827A8FFFFFF27F8F8FD0927527DFFFFFFA8FD07F827A8FD05FF27F8 %52FD07FF27FD06F82752FD05FFA8F8F827FD05FFF8F8F827FD06FF27F8F8 %A8FD0CFF27F8F8A8FD07FFA8F8F87DFD05FFF8F827FD07FF27F8F8FD04FF %52F8F87DFD0FFF7D5227FD05F8A8FD04FF27F852FD08FFA87D2727FD04F8 %52FD04FFA8F8F827FD05FFA8F8F8F87DFD05FF52F8F87DFD0CFF52F8F87D %FD07FFA8F8F87DFD04FFA827F852FD07FF52F827A8FFFFFF52F8F87DFD12 %FFA87D27F8F87DFD04FF27F852FD0CFFA852F8F827A8FFFFFFA8F8F8F8FD %06FF52F8F8F8A8FD04FF52F8F852FD0CFF52F8F87DFD07FFA8F8F87DFD04 %FFA8F8F827FD07FF27F8F8A8FFFFFF7DF8F827FD0CFF7D7DFD07FFF8F827 %FFFFFFA827F852FD06FF52FD07FF52F8F8A8FFFFFFA8F8F827FD07FF27F8 %F8F8FD05FFF8F8F8FD0CFFA8F8F827FD07FFA8F8F87DFD04FFA827F827FD %06FFA8F8F827A8FFFFFFA8F8F827A8FD0BFF5252FD07FF52F87DFD04FFF8 %F852FD05FFA827FD07FFA8F827A8FFFFFFA8F8F8F8FD07FFA8F8F8F827FD %04FF52F8F827FD07FFA8A8FFFFFF27F8F852FD06FF52F8F87DFD05FFF8F8 %F8A8FD04FF7D27F8F8F8A8FD04FF27F8F852FD08FFA8FFFF5227FD07FF27 %F87DFFFFFFA827F827FD06FFF8A8FD06FF7DF827FD04FF7DF8F8F8A8FD07 %FF52F8F8F852FFFFFFA827F8F827FD05FF7D27A8FFFFFF7DF8F8F87DFFFF %FFA87D27F8F87DFD05FF52F8F8F8A8A8FF52277D52F8F8A8FD05FF27F8F8 %52A8FD04FF7DF8A8FFFF27F8A8FD05FF7DF852FD05FF27F8F87DA8FF7DFF %A8F852FD06FF27F8A8FF7D5227FD05F82752FD06FF27FD04F8A8FFFFA827 %F8F8F8275252F8F87DFD05FF7DFD04F8522752A8A8F8F87DFD05FFA827FD %06F87DFFFD04F8277DFD04FF27F8F8F827522727F87DFFFFFF27F8F87DA8 %A8A852F827FD06FFA8FD05F827FFA8F8F827A8A8A87D27F8A8FFFFFF52FD %057D527D7DFD07FFFD047DA8FD04FFA82727F8F8277DFD08FFA852F827F8 %7DFFFFA8F8F8A8FD06FFA87D2727F852A8FFFFA87D7D527D7DFD05FF7D52 %27F8F8277DFD05FFA87D5227F82727527DFD08FFA82727F852FFFFFFA852 %52F827F82752FD20FFA8FD0FFFA8FD04FFA8F8F87DFD2BFFA8FD17FFA8FD %38FF7DF8F87DFD7CFFA8F8F852FD7CFFA8F8F87DFD7CFFA8F8F87DFD7CFF %7DF8F87DFD7CFF7DF8F852FD7AFF527DF8F8F827527DFD77FFA827275252 %52272727FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFC %FFFD6CFFFF %%EndData endstream endobj 26 0 obj <>stream -%AI12_CompressedDataxis70 x?*R dVqd^g8{rmsBJ߳`"Eѭ%-XF8gOëE_v1m_ju{b՗?Փ o̍,7񒤋3|X?}\e[-گ~ꫯ| /鯥 ?Lun]_-G^_eQOG׏/^ܔo2+21-TZE4dsy{X~n.77j74_+]V?{f[`,M|\aZ\k]f\-`绞y~'՛ru̩.V~zg|w 5;h=Ap,PIx$¼W|;>"#tzA)h'b}}^?^ ş_~ج< 5:?_v^XU<|Z~l5SbYosWO qeǾ b0":ɜ0 tnVl'֏J`$b8~]^y0f -J y[_.|[M{  ƛk\Gk@=_s -<~QCo7 ZvǢ'U~1Z.pbr_L痷;@ŏ6q{bη̩r nbm|AO?]GY,=kj^\l ߻ ݸ{7ܕ[o[w -:X\\|_fqwvZ.>η8?\@ޮ`DO*vdH/ufwxw1^x|#Pd~Z/oբv^[@S?_o.oW0z] 6/]ky9_wz7[-7*7 ˏ8Ő14fL/6#+"S I|=W^-X"Issݧw9CtzPOi~7jN\[€ z/>{6W kon}a1c:7 ;ww|ӏ|ӏA{v?ʗR/-;|m~I 4hqv~^ow'~q9/S8g rޛ2kr\Tn$zh]BHY= W%X} -N 4o"d.q-竫w0kK Khq4ϋ=!,Gx/Ӌ",V (Eo^:yfF𴾁i `DQ.}~nn#d99z, }cuWڃZ憷7zl6+׸\rjާ{Sn}<:KiyM|/{}I% -S ?Є^.w D]W^\5?!\-^)pk;POM)NVQ%^j9.hSP{4*hD &D$-y\CZ .(Z>#Z-)k4< yw Da1v89( a&iOa5Ly> Ni1 ,+̬T )&<1 o6E"ʃI!4aH=eOT/3S{ʤS*=ea|OxS$XPЄ%AO"@#:I0`:J1܈?nƐ:iGSu"rF,`!h&ljUADXyD"94$4 ?),mXcdF3/S%hh]Jd; -d|D}h<9:LQ/@l -D(& &f,XЄd$.g%Ŀ28=H9U(6JPpL)9 C c ᔡp Ao rȂŠJCi :4 ́$v(,(t@HFvk7rpmDI ԎݬJ7q;h)OE&}!1PV[r -sBͤTFq!2 -ϘĔ)̜$+6%PIeǍql2e} Y S"&FL , ETAԄT#N03b4r?0"l!2" -46,+ߔɍ#:,YTȦJ,au2Rp^ˉ-olqIX"*ZIg1VYe 'lAY.0AgJkYdclllMuV:KdR6|䩌95BИ@,YsfJ&YL)Ҋ^v$ɑFA,2VŲ^ -$*q06XqZrg;ҡH8#Yvd s) {7}b87[C4֓3OIEhYԟh9vfeY3c -c'0vxh|\kg?%q~2~aUk9IcE&\H8FRrK7ҫc"F 򊃼9lnvWRcRq7g1JwJw{\S*:rRlbOdFΜXHɒQZ"p ' N9oH{EIsıW0 p|֯8qνp⢒{n$5 $+/2wZ>š# q!r2͍ Y^teFBlĪH.X"$a% mLDU6$lSD1IflFA6%&"[n84،<8 -3c"C y&>Cj[ M@B\ȦRC7fIYq塌=vh% ' gba|cPh(, gd&t;;rgnF []JG3Yd2:?EY3e TĨ#ZScfC8'GIC6&cf_23F o,ђ<3CǰLZ:7BXg=Hd}䟡),Oo,l3 4b{i \`HAϱ 697GiXrcS9QrĶ&ٍ1ް(5(J!*8Z0e -#PiaNHG2}G7 4#!Z$b k cAskNzPD>/XӌjǶ'W)%W\bryfDQYk8k|)bcs5M19C/vV6FZpVUF~֬ٔ (Iw UVcUYkt2$?%uYՎ;ޱgU< Vf"l{V*bOQ@Tv)[#ZI[c -=~%L_p\ʋf]tU^4Y?]ͺݪWTʈ Bb͌/VyDaevɈֺ%E&TW RЅI%&LHG.`*w+ۮ!^ʅPvq0U3k^_B?Āq 7kڄXM sAgJfMxX:']fMS(h]#CGڢ@*Hȹ5(y:`UT`m(Xۼ?3vkrE2Ljs)\eMUD`AD}mE -XDyML";٩ny g趔%r3.S^RwJ$NL( -GD}5R-"ISȝǞSKcA"@Ɲo9T=8II\ E"As= -TrK>(ʡ# \pR\_l)g92'߆)Znrș(32X'9Fpڲg]cBb/0ބVli2A$VQ\㔨)G6&"1F$Hz999Ad(E9JGެ Znṉn=w&fڪmK\@BSAx' zq' zq'$ zM՝Tu:K$ -5Ȱ2,Q}&җi <8RK%T2_-r)J8|mA*vf.p3vS7PxTN~p 8Nmةb-niL}FPEѨlK@!Hd#H\tUkLeqTx; )XP($@U;ůN0֊ƆڀV$5#mn-Wic =0j;mѩr`!UFΏJe7Q*n* sWsVˇN vCMrFjdK$M2(9m ͉c7LiK*F)(nה|p|$C$C @b"g$E$16LRc -5uy x~q@igr*¡exR%k*"V iU3 8g%[.$H@ZIoN?,l~µƦ[a2Y Ϊ+X,XƋMkn^:.[ hx2;wݽnOgΎx:n 5>1X8=5Nii!τ.l-R2ւbPS  0%󔍅 FG7U;C=e"!ځGņOaMXûF1_Ar2!Lh# -0ӄ9i#9 ,f;0!)!XdA LKmt'"_b/i.5[8>i{~nImw~nc}m@"'g7)U%i{{ép6-H@S44~&/)-CdCYӀMxOͲBR,&D`, -=V챂}X,Hl˱/KV1cqI*U/)HJ/QaʔB| -57Hb)( *"{CLM{bT^_{-%'F({Ό "?GwgN;# >@+ytkVk90}HL7rѼ#% AH#-j1lr efBbIiq˜>EkqqH6#Kf„)"or"#xA~6/!rnDG8J51 vxGKL0^qཙHB[vLK$ - |F66HjbQ8W - +)Lb %R8P~^RLtȉP>AVEZ -y\d#+)4"4q^,KǤnJBd2Đ?/(xrk1 t"°f邚IZvRyNvְg ;Aa'24IaZR);Ja"|){?e7EYqTpTeȸTn"v&[TʮxDeݙOm%4d31_?el]n 4ވ[/F5wC9*77tTGgbbg.B\؈< (.EټHcA^" -rǸmآXO捜I8̱X]t~OvRf -op[V+YmHaDS`k|5[$xKYV}d?6eFLG>&y3 -iԹ;ecblhhZ_:|SS0yZjCr -7 N lVW;FoKVצa%v;g >1| 6`pTx*gW2**0FJ~?@{&!%O 95r3yU ;LhKa\R,|WjJ8#9ZN}t<2S&w3l}SJ8f30(ߐh&_fMOs…1B3sɧ*slh )e2MG'qa:6PQ%E:|RR>;:&MǸs)&)\Rm0ɪz:̵0}Pf/ߪȮW̌Uro,y]9%R-2wQbwKU`fiI)o --YZ𹁦д rD[ά&=+`|N@f`XlEZEKb*مoZ"CIg,OSc<-J(QIDǘ gH[c"($&C" 3, V&`uJ(̩0lĥZJnGgcʺ吓) -2*LҍԴ]`a5t)&H\JnrMJ*p*fd'O/籘Ҽ⬲sB N\ aX̂K$Ip -T )y"5Þ8!d` #5J@8pĥnh0P+a%r a~U -Ǣ^ƍt)Թca.S˹2,[g^L[1gFfL{EivՔ5f.A O=L6ӳL3=L6cNʒAɤPj:t|V uڮ9; ewQP ,}Eb2Ux`P/C Nә=bdk<@6`f#l)*Έi<8Mc* Y.ksw74Ʃ"]`IP'H^h! -9mLF@D ۱ |AA|LGB ̂Y{hC!5yO3CZDgA$HõkvN쌝M}NrlPfR"=>"9W9ঊ2AVɇ` a4_t'd P 2:'nh Pmqr{g:_}%JߠQe4~x\ fv='.Y@fIqYln\m`&6ybL'҄ңOa3wD"_:s|X,ښ(LsykյѕcvqJAbTbe/e/JG*K`$q$Y$SJ9%ZIpfM^݈ҔR/e7/TqԔF5 T]ԊM"M -$& u&\eJʦu w^ Q;F&8q.^l, -I)bs\ Gd.dm2)Fk>sX#i6G۪)Gu݆213aRGnv;R -(#xj[L&Ȟrv APxeA0E͊oMuSڐƨY-KoO➭ަ62u*En;L8YV>4E v'8G|iqiyFݍM{MUaK뫲1HӮ6mdtZm|ty)\T}8ْ٢>E…0s46]ym C4 ܇u,g~)sdsQ󶊅ޗw]Cqݟ{rX?範e1*^لUA‡2(!my\+ޔaG 'JQrJihzYs*@-V%^E2#+^ϯW'Adn5K&1AD'cƧpM%|1q BiŨDbpP5tul2)ҐntXvgjs4>Gy4U -LcGhf7zJ/h90m2A撐owiX -?3q#@}wy)`&.XA>gkt푺3<;G_$ЎtvcЎ*A.;(է Svk7 éLJamQ:wUO Ga~^828;w}scYmqnM{VGG {ްzQSؑ <JiO G8)s.Afei|ϣcW0-`.![H [QNv"#6Ћ(a[] Y -#zb7YF*?ŕg–[dhX>S{V>,#V`E5ͰXN&4K" DVAV -gXM]|za6B  +ϵek\&wf+F:3bA qjqG6saʵ=sbG88eչaOE[˛k۞9l88[(4uPB95Q6`c L3MJ'놦}d"eK[TPLБ[z\Yg}>m Ȍ#?hf(6vQ6f# -,Gv`R~6diJ>X&~Ǎ)h#ҽǔ󞳼g6Fd.ИNҮp+T8̬&O MBNj2 -JEPŸ̕Q8(S] qئHa$(㠌Loj(NO3߼PfyO_ecoL 0Z$ؔ|`r!℧#M%L< -Z5O6D'%{0A0'S].7?5Ɋ<0G2TK.^́He{Vxw rՄw9u@9[-J"׾f^3:(2еff( &~әvdt,8Yސ G4'N$ }wq[=;xC;5=r$q!; RuZj,/)s#}^"j[%E7p2pl(<q`(tl*JNcRVIU7r[ J` Ƹ[ - <3E Ǖ2)QCjCH!J4BD"D.1Q P2bFU'މ -#&N( 4!rsV6z=!DW,,Z# ãH("2ySr`A!%&[`wϗ/ڠUJɘ9={F+zR!芰iv$M(_LQHzO)I@9E G[|ٽKݔp~bDr4;;TPCBT3T]a&N" -qOb QqilC19>I ҩݝݩݝݩݝݩݝݩݝZ*tС©jZ8uX-d"e`1SN?JޝJ -?ѐ/}<,FXXBp ; ?D)HzJIahmԱB we+4 -ΑY`~}@0ˍ̚!f`B0+l -7zy8 K_Εִa@x<`ߕfP"U;C ,-x6pG0*TA< t)^y -0w 8OMc=(OTvY$hZ>rو[F0{y'Q"ތePaa)\fPat<*@S^Mgt!2y f(:te (K?ǂ-sSUal3NŏyRl>dlbAC\qz ʝ K'ާYii -9O Qs?zŽaͲt!M!M!GS rG/<~`:OYゎWt|lBt 숎ef*tСB -5TӉ dT< koQ ŝNq$G'1i<0fq5fﺚT~ V4k/UȜ2WX/uc`ج9a.S>k5\bd޵a)>j`|n[d(0vɬ\&ORo#l&3Mn`Hx%-Ig!MHqU8tcؔ;-.l@@&Q9)a>zB!0b#[M -|cZTn_ cF(_ /H*׭iSEetl>3{C[86Sg0^qco܋㌽O\Q"pULRJ";0;3cp DCFzNa0q0 CQ;#a*8н*4*a? ŇA)\{>l6AFImj#sG1 G>"(w珸3&86Ğ,Tމ1, JT `HIŔRf_&8Mi90. +ș K9i'D(xf2 Jÿ -xJR y0-L2e/1ۈ73tabʣg(\|T8 ^JN6 ΆA3{ '5)e.z#{b{d34GčGA/,u;Ŏ#@3Pfy⎡yKyx#^cȝp'؄Ǟ`S^x#_؈2ei 8;~((35)S` .TRPx,-)(Ȑ(RlJ ;Ņs5 ȯ)bn@ nTvt;vt;ַC:TPa+T5>?Jco3)v91qUTPQJwG{\s=&B -{ͅ~& % NH=1" ^IQ?>>(aI?'[˒d֕ʥ|5zM)Jڳ?Rs}3AL>@(8k c'Īpqg|ո!<سy -ªyTy?VC 8E:#wG6W|T1itߙWeEM=^4ɼII펠뎠뎠뎠뎠{#j>4.σ[zKx_Zv}x_=-^x/k}Ypv]mpv]mp]e -*tܶXmLL#0YJ85:[ΙO1|Χ+fAcs=hHV h,cՐ x܁R&xi;TA3k1AX ^[9"b^LRڒ@B6M6MMEIN&8|mzpKF&JUKNaV5g08$X'XHVhFx8G4&QHt)_ 5%a:ylNffP,Esv-0 0A;PW>.w+RK)u:Q>?(j,P5"C4TRb*C7Cz[gDx3?[xhA |uE?lC-PG/Yi*&G rg7 5X>j%3z, *Y]w~?+?wvur~Y_Ʒo7_^:HT/O:y/+ 1t:n|㿣34yLWPUyS>V>#;v^)(Ct=Yv9 -/"a9<'͓t\: M!{p-Xx|JuZ rXW aݻ^;Y=J 2 Cg_5gߓ&ZD O81psUzw{|>e 4u=\V۳G"I=Jw ܼ%2o9+!i | a쫆u\!\ ]mϾ.pd ðڬܟj{kǾ^=!|{· aQmm±l –w:s| W@ھBbInKu3 :4[~ǒ{_TzCU2r,?#2c>;3⎢f*t(j۰JBhxKĶ}(cm;Kl1c{_TzCx,hK 9X7޷mb >4W[f!F XTmlvپsu`p$=M/{Auؽ^(D2a%hA%YK4ml!鰄=I]wżC]L+W*WlG2Zֵ)' E# GI+sT^ǩ+ԃڴ$hV\:`.E&kp)ޝpOT!J{{`OuKXJόžO%,eIaq>T5C- _`q8-\%4(akpg8󓰸A.z{Zj!w&@3 ~o8*x!Fi.muW<6V«z4XC/,5SI|NH5@FT=:7\%EM%RV}bZ)")^~YĤ/\ rܬO˾"wz]?ovs޿^Fk4A11sQ31$.8$[\:5*03 祵sh;DFVs)r(PFSHiIk jEDa TTevvTk)mee Gh 95.sq!2Kr͈ :y~\-c{/|.[nW,.w -?u[Tֻݧt fu;ru]}F:4 -P=e2hct.vIb!OS;#:`zt8>8woodY^TwH]1;'n߶1uEjo? )pLr#ZTY^x8F@iclo7v񿷀SS'py[.rukԵ%\w( l>.f{4T'SVe -#{h^ono zU3\,w,5 \Lzt^=:m祈Em/N3czayQ^+88^+ƚNԽNԽN{ꞌ_҈ԁhAm=hcza~}HZovWKq>t]=R:oonAަ,yf@\kkɞ*:]_VcVAă|;ެov`?x [ݾ_.^_WǛ|Kkӿ>n֋@Nsr|yHz~`]>\/vڦ -.y[O ie +|9` ?i:~;|6r"?}ؾ_L`%@+?dCDvQ訤!`im 8G7hX j<֦j]jz!$oa֋vru@>?=Cl?~ج6?_oY{FdvuZ1[bH{;2BNR d#G}#vcek ` ?NV`eQ7ƿyg"r?l=0_׋>ts+SY4 -fÍ[M{A2R+x\ rqX゚lף^Q>3jxA~Iv(E4ċπq]ۤwl}% -p,6@+yZ ]kQI}W'bvAr_C_]-w?/vs}oBmXe;e࣋nr<䳼=SO[:;xc[`5*m+'r=ڢ a6  ‹6.wvP%V2̠ڐZYW 2D>px4jWϝ>HRΝ͝^J7tΝܹ;wrNLN&*5Qs!w.΅ޝ s!>rp|2Z=:$68:s7wK_C' bWvv=cOO~,=tfVx|}\Di%ZwCuv:O6ݦtvnmj1V#o:Mgy/£:#iFFPKرmlo ʡZ~/YEx3 l*<(lrg˝wʝzV̳u ڦzrO Nٸ[{~lV]vOvG#b/.ZL!}XZ9L@@z#8h<tQd}\^#}ڈt;#8k ~Ҙ2ieLNj=/ٝ*yFj42vF`vFΨ5N7j/Ũ@:S؝Q3jPtFc@l7o)*fhmJ|f^ȑVOӌ/z8ʶV׷?Jq#fZyv~m{X_T:irwyP}.,=J%;jin|xjҥnR7%en/ZoNMBե.:_F_nooWr6v JNʖTgbԺoǏE/.~?Έ>X;h%v o {fqSPn|1nVE"u&;uSNWbW)|F)|Oum+|,JރQZZu_u_u#k|ɋZQ)"^HZkzô_׊8w ۓI2^|2^llnt*3꒒L߶or66}oW߿qruKݧ{s-NQ'yaLcejeoGk)\,usһd汹-Bn//wJ4??v}hsX}4G{~,=tfx~j瞑eҭP?׷cOTJ -s$xijɒ+=95ᖥ$'̅,.ܛ g!1f8N\ĥN^:2/2e,|eͻCrzR)CVV‹ lE@` xU?p IC@K8Ixg;l]m/hlPw߉u>;twJ]X4>i `qKyZDfdHH/Hԥ=($K_YȔO Q+~V~ ]9w-v֮\JڣvE'*y{+\H+S|>y)iZȎcTً{Q -YU:Б= "]ST *"x,i9LADi׼PVeZ(r^h"$y/đDWc"d}NI:Ǣ_㫠 5lٯO렻:ꌏ# <,͆ ̂ ,fWڈKoΘ`"lmԍѼ75KVi#+e\/LS=5H889PJԗT],iR h/He@pqDyPх~ںd${tF2z:lt,dS/Q+LyI_dL|I?9HF@5hsc?<򤃧y2TN%W),`)jS:Ft(ȜnHTT2j8s`a:)19h:X%xl L<L+Σm4^x F0">H&qÄ62iR!;F$\W( f v4tWkC0מ4b, _Q,8l,ό&B!EqUFybҚ~$DFQfsnXg+37|%Fo }jkT&z~W`IT XIsE~H,HҞ ǯ',W}v#[>ǕjM#ꮻG̽xۚ rެ{҈"qqPC$4|l?0F[.(`X&(r@O$yRssndW:>`'yξ•J:.ڂVNՍ P@j$+X), :۵{D,i~$ioH?*v$Lk#E`] %վU7%F(điJ@*Ґӹx &=?8IFbe m`r`5-yV5?h`QgafN}F I4 -$_H$[k4^^^ٹ3x8|wfr4oƫrxݷl/~\m. +@o_co!PuO -A#K%'e\ e2~K ki}ܱjQV ңs4IPD9N@0d|*?4;dS0e'kt4E?M"hNtQC&:l;ggοة;ggl9yhOgxFN+~.S-+v>[A/4l_c-g3EES_;:@K̹w], KŻ]'dvNig,{NjLɎr%"W̓7oTaHoNpқ9:S?q0RaXn kt -?x߃%A'H``Nx^/E&.Jg<%ΰ6[;8;o&qI F[oξPrso%& )IS Oe(O5׻-az!s׏ `ws `XxN%~!1K'?ʆ-ҧ jt?aC!ՉQ}Ҍm4*$Fy[%| H "2Qt\2ڣ0P42@)ھ2m= tԃwwg -)v}* E3|8IJ&Rrڊw/}w>LJme,[DMmF7R320 -|l9"5b/YB-Dmi )Q1ǰ89DT TťH)|:U\$zt«#LhUqf3ȜkJҁ$%bL%qiTnh*4}*mRD KWF7?^R8nԩuD/ -g%xJ7hހፃ$ax0\Ig+g$i?cnSXa+H fk [7J%Ya6[)FI[8@ Q;ՙ,l~$\&U -&&6ʸ -$fY32B$ع[ݫj}Ha*?گh̽S -@f;)ar5-4y,_ jU_ߪ7:/ΐ=6 Cm@.yrx1a5P#]Hҁ -_m'*hP1MH-άE` أd($HYxX ÷ o|>HD/B\rhCY&YɕNqi L $L(Ђn!@"(VB B' j -qU!NGOQ*.~++c[Rcl7jEV.F3^[|i=؂IJ&%$Zڤ4L b//ؕB{T$wS2%2 --v4 *ZrAM8WI䪲h6Ke*Q$/K%5Y@l:&c+H25݀&+I -&aݳMj]ȡ) Jȫ-Ɯ&2V1W{BM̎/ V #B|aAKC: f4(꘩0.edqd"TV[rأ\Uņ{׻bfmf|x?|ipD@TF-[SbI,qVV U蔏y(PR PncpIz@ H -h(l F0i(E7< DY(Soyv7wen_¹]/LxIgti0TcCNR&p3-}97 ]ެ@KQ$'Si!&綽 mPU`Yd #P$?U9Ҝ<7ϣb#Є`ܠ1>ߠB (rF^BtuBl͘M9Z]@b*=@&=T"ۃ(O=gOEWlaHsġSJ0[ -F &t"CCun-xN%܊TaК 9#UJS ɥ~44XCYeY0o50e3WxIdQu{hPwv:/)cT*\0P4X*C:+Q -*'F1H苇R ->V;ԍrsݩ=F_=tvd@mC;T-bSAG,#"b*&M]}LzujOւfk>Zՠczk֑lqB(K8EspI['`=з"'UtAk!;ekDL"@+1jc:uFCU3l< 5+:| Ѐ$Jwp*du\ -IJ*5  pa Y3!I .CȑYM - `v@8E)tj># -Ԝa| P5s::3Sx,S*hH2`$,X<%ARh/FOAp䄬8c(-tI٭$K6=$A7t A0CXt?Z4"4Fi,KۯKTVVp +zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km +endstream endobj 32 0 obj <> endobj 43 0 obj <> endobj 44 0 obj <>stream +%!PS-Adobe-3.0 +%%Creator: Adobe Illustrator(R) 15.0 +%%AI8_CreatorVersion: 15.0.0 +%%For: (\720\745\755\740\762 \715\740\761\760\750\744\750\755\756\742) () +%%Title: (requests-logo.ai) +%%CreationDate: 4/12/2014 8:48 PM +%%Canvassize: 16383 +%%BoundingBox: 4 -270 298 -43 +%%HiResBoundingBox: 4.07178 -269.6123 297.8438 -43.2681 +%%DocumentProcessColors: Cyan Magenta Yellow Black +%AI5_FileFormat 11.0 +%AI12_BuildNumber: 399 +%AI3_ColorUsage: Color +%AI7_ImageSettings: 0 +%%RGBProcessColor: 0 0 0 ([Registration]) +%AI3_Cropmarks: 0 -300 300 0 +%AI3_TemplateBox: 150.5 -150.5 150.5 -150.5 +%AI3_TileBox: -138 -506 438 228 +%AI3_DocumentPreview: None +%AI5_ArtSize: 14400 14400 +%AI5_RulerUnits: 6 +%AI9_ColorModel: 1 +%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 +%AI5_TargetResolution: 800 +%AI5_NumLayers: 1 +%AI9_OpenToView: -147 8 2.68 1823 917 18 0 0 48 120 0 0 0 1 1 0 1 1 0 1 +%AI5_OpenViewLayers: 7 +%%PageOrigin:-250 -450 +%AI7_GridSettings: 72 8 72 8 1 0 0.8 0.8 0.8 0.9 0.9 0.9 +%AI9_Flatten: 1 +%AI12_CMSettings: 00.MS +%%EndComments + +endstream endobj 45 0 obj <>stream +%%BoundingBox: 4 -270 298 -43 +%%HiResBoundingBox: 4.07178 -269.6123 297.8438 -43.2681 +%AI7_Thumbnail: 128 100 8 +%%BeginData: 10225 Hex Bytes +%0000330000660000990000CC0033000033330033660033990033CC0033FF +%0066000066330066660066990066CC0066FF009900009933009966009999 +%0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 +%00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 +%3333663333993333CC3333FF3366003366333366663366993366CC3366FF +%3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 +%33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 +%6600666600996600CC6600FF6633006633336633666633996633CC6633FF +%6666006666336666666666996666CC6666FF669900669933669966669999 +%6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 +%66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF +%9933009933339933669933999933CC9933FF996600996633996666996699 +%9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 +%99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF +%CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 +%CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 +%CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF +%CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC +%FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 +%FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 +%FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 +%000011111111220000002200000022222222440000004400000044444444 +%550000005500000055555555770000007700000077777777880000008800 +%000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB +%DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF +%00FF0000FFFFFF0000FF00FFFFFF00FFFFFF +%524C45FD47FFA852F8F8F827277DA8FD74FF522727FD05F852F8F8F82752 +%7DA8FD6DFFA852FD09F827FD07F87DFD6AFFA827FD0AF827FD09F8FD68FF +%7D27FD08F852A8F87D52F8F8F8277D5227F8F87DFD65FFA827FD08F827F8 +%F82727FD05F87D7DA827F8F82727FD62FFA852FD0AF852FF27F8F87D5252 +%F827F8F8F85227F8F8F8A8FD60FF52FD0DF82727F827A82752A827F8F827 +%A852F8F8F87DFD5EFFA827FD11F827FFA8A82727F8F8277D5252F8F8527D +%7DA8A8FFFFFF7D7D527D527D7DA8FD4EFF7DFD14F87D7DF82752F8F85227 +%5227F8F8A85227F8272752FD04F827F8F827522752A8FD4AFF52FD05F827 +%F827FD0CF8527DF8F82752F8F852A827FD0452F8F8A827525227F827277D +%7D52527D5227F852A8FD46FF7D27FD07F87D52F8F8F827FD07F85252F8F8 +%F85252F827522752FFA87DFF7D27A8FFFFFF7D7D27F82752F8F827A87D27 +%7DFD46FF7DFD09F85227F827F85227FD04F8525252F8F827FF7D27F87D7D +%FD0EFF27F8527DF852FFFFA8F827A8FD44FF52FD09F82727F8F827F8F827 +%F8F8F8277D27F8F87D7D52527D27F8FD0DFFA87DA87D7DFF7DFD05FFA87D +%7DFD42FF52FD0AF827F8F8F827FD04F8275227F8F852F852272752272727 +%A8FD09FFA8FFFF7DF87DFF7D52FD07FF27F8A8FD40FF52FD13F852A827F8 +%F852A8F8277D7D7DF87DA8A8FD08FFA852FFA8A827F852F827A87DF8527D +%FFA8F8F87DFD3FFF52FD0AF827FD04F82727F852F87D27F8F8F852FF7DFF +%7D7D52277DA8A8FD08FFA87DFF27FFFF277DFFA8A82727F827FF7D277D52 +%A8FD3DFF52FD04F82752FD04F852FD05F85227F852A8FD05F852A8FF7D27 +%27527D52F8A8FD08FF7D52FF2727FFFFFF7DFD06F87D7DA8FF2727FD3CFF +%52FD06F852F8F8F82752FD07F8277D5227F8F82752F8275252277DA8A827 +%27A8FFFFFFA8A8FFFFFFA87DFF277DFFFF7DF8F8527DF8F8F827FFFFFF7D +%52FD3BFF7DFD10F827F8525227F8277D2727A852527D27277DA8FFA82727 +%FD04FFA827FFA87D27FFFFA8A8FFFFFF7D7DA8FF7D52277DA8FFFFFF27FD +%3AFFA8FD0AF827FD05F82752277D7DF8F8527D27FFA8A8FF7D527D52527D +%FF7D52A8FFFFFFA87DA827527DFD06FFA8FFFFFF2752FFFFFF7D7DFFA852 +%FD39FFA8FD0BF82727FD05F827525227F8F8F852FD05FFFD0452F8FD04FF +%7D27FFFFFF5227F8A852FF52FD04FF7D27FF7DF852FFFFFFF8F8F8A827FD +%39FF27FD0BF827FD05F8527D27F852F8F827FD07FFA87DF85252FFFFFF52 +%7DFFFFFF52F85252272727A8FFFFFFA8F8A8FD05FF52F8F8A85227FD38FF +%52FD11F8527D27F82727F8F8FD05FFA8A87D7D52A87D527DFFA8FD05FF7D +%52F8F8522752FF52A8FFFF7DFF52527DFF7D52F8F87D7D27FD37FFA8FD0F +%F8275252FD04F8272752A8FD05FFA82727527D52F87DFD09FF5252272727 +%FFFF7D52FFFFFF7DF8F827527DF827527D7D7DFD37FFFD0FF8527D27FD05 +%F87D277DFD07FF527D277D7DF827FD06FF7D27FF7D27527D522752277D7D +%7DFFFFA8FFA87D527D7DFD3AFF27FD0EF85252F8F8F827F8F87DFFFFFFA8 +%A8FD05FF277D277D525227FD06FFA827FF7D27277DFD07F852A8FF52527D +%FD3CFF27FD0DF82727FD04F8275227A8FFFF52F8F87DFFFFFFA82727F852 +%F8F87DFFA87DFD04FF7D5227FD0AF87DA85252527DFD3CFF27FD0CF8277D +%52F8F8F827F8277DA852FFFFFFF852FFFFFFA8F852A8FF5227A8FFFF527D +%FFFFA8A87D7DA8F8F8F8522727FD04F852FD40FF27FD06F827F8272752F8 +%52A827F8F82752A87D275252A8FFFFFFA8FD04FF27527D52FF5227FFFF27 +%27FFFFFF7D2727FF52F82752A87D2727F827F852FD3FFF52FD06F827F852 +%275252F82727F8F8277D277D52F8F82727A8FD07FF52F8F8527DA8FFFFA8 +%52FD05FFF87DFF7D527DA8FF7D7D7D52272752FD3EFF7DF8F827F8F8F827 +%F8277D27FD06F827F8F8F8275227F8F852A8FD05FFA87D27525227F852FF +%FF5252A8FF7DA8277DA8FFFFFF27F87DFD05FFF852FD3DFFA827F8522727 +%FD04F85227FD07F852527DF82727A8527DFFA8A8FD04FFA8277DA8FF5227 +%52FF7D27A87D5227F8F8FD05FF7DF8A8FD05FF277DFD3CFF7DFD05F82752 +%F8F8F827F8F8F87D527DA87DF82752F8F85252A8FFFF52F827A8FFFF5252 +%F82727FFA8FFFF27F8A852F8F8F827FD05FFA8FD07FF7DA8FD3AFF7D2752 +%F827FD07F827F8F8F82752527DA8FD057DFD07FF7D527DFF5227F827F8F8 +%7DFFA827FD06F827FD0EFF7DA8FD39FF52F8F827522727F82727A87D27F8 +%27F8FD0452F8F8A8FD0AFFA87D525252F827277D5227A85227F8FD04277D +%7DFD0FFF52FD38FFA852F8F8F85252527D52FF7D52A85227FFFF7D52F87D +%A8FFA8FFFFFFA8FD05FFA8A85252277D27272752FFFF27277DFD15FF7DA8 +%FD37FF7DFD07F827FFFFFF27277DFFA8FFFFA8F85252A87D2727FF27F852 +%A8F827F8F8277D7D2752FD0427FF7D7DFD0EFFA87DA87DA8FFFFFFA8A8FD +%37FFA8F8F82727F8F8527D7D5227527D27A8FD04FF27A8FFFFA8FF7D52A8 +%7D27527D52F82752A8FFFF277DF87DF87D2727FD0FFFA8527D7DFD3DFF7D +%FD04F82752FD05F827F8A8527D2752A8FD0DFFA8FD05FF27F82752A85227 +%7DA8FD0FFF522727FD3EFF27FD04F827FD06F8527D7D2727F852FD13FF52 +%27F87DFF5252A8FD11FF7D7D7DFD04FFA8FD39FF27F8F8F827F8F8F852F8 +%277D7D522727277DFD13FFA8F852527DFF7D52A8FD0DFFA8527D7DF82752 +%FF7D525252FD39FF27F827275252F8525252F852F852F852A8FD14FF27F8 +%A8A87DA87D7DFD0EFFA87D52F8A8525227FF7D27A8FD39FF52F8522727F8 +%2752F85227F8F827A8FD15FF27F827A852A8FFA8FD0FFFA85252F87D527D +%27A8FF52FD3AFF7DF8F827F8F8F87DF8F827527DFD16FF52275252FD13FF +%522727A8F852F8F852277DFD3CFF5227F827F8F85227F87DFD17FF52F8A8 +%27A8FD12FFA85227A8277DF85227FF527DFD3FFFA87D7D7D52FD18FF7DF8 +%7D7DFD15FF7DA827F87D7D277DFFA852FD5BFFA8277D52A8FD14FF7D27F8 +%F8275227F8527D52FD5CFF277D52FD15FFF8277D52277D5227F85252FD26 +%FFFD052752FD07275252A8FD26FF7D5252FD15FF52F827277D7D2752F87D +%7DFD27FFA87D52F8F8F8272752525227FD04F852FD25FF7DA8A8FD15FFA8 +%F8F8527DF852A87DA8FD2BFF27F8F87DFD05FFA827F8F8F87DFD23FFA827 +%FD17FF52522727277D7DFF27A8FD12FFA8FFFFA87DFD14FF27F8F8A8FD07 +%FF52F8F8F8FD24FFA8FD17FF5227F8F87D7DFF7DA8FD14FFA852F827FD14 +%FF27F8F87DFD07FF7DF8F8F87DFD3BFF2752A8A8A8FD18FF7DF8F827FD14 +%FF27F8F87DFD07FFA8F8F8F8A8FD58FF7DF8F852FD14FF27F8F87DFD07FF +%7DF8F8F8A8FD58FFA8F8F852FD14FF27F8F87DFD07FF7DF8F827FD09FFA8 +%7D527D7DFD0CFFA8527D52A8A8FFFF7DA8FD05FFA87DA8FD08FFA87DFD09 +%FFA87D527D7DFD09FF7D7D527D52A8FD06FFA8F8F87DFD08FFA87D525252 +%7DA8FD05FF27F8F87DFD07FFF8F8F8A8FD07FFA827F8F827F8F8F87DFD08 +%FFA827FD06F82727F8A8FFFF7D7D27F8F8A8FD04FFA87D2727F827FD07FF +%A8F8F82727F8F827A8FD05FFA8F8F82752F8F8F827A8FD04FF7DF8F852A8 +%7D7D52FFFFFF52F8F85227F8F8F827FD04FF27F8F87DFD05FFA8F8F8F8A8 +%FD07FF7DF827A8FFFFFF52F8F87DFD06FF52F827A8A8A87D52F8F8F827FF +%FFA827FD04F87DFD04FF52FD04F827FD06FF52F827A8FFFFFF27F8F8A8FF +%FFFF7DF827A8FFFFFFA827F8A8FFFF7D27FD08F8FFFF27F852FD04FF7DF8 +%27FFFFFFA827F8F87DFF7D7D5252F8F852FD08FF7DF8F8A8FD05FFF8F8F8 +%A8FD04FF27F852FD06FF7DF8F827FD05FF27F8F8A8FD04FFA8FF7DF8F852 +%FD05FF27F827FD05FFA8F8F8F8FFFFFFF8F87DFD05FFA8F87DFFFF7D5252 +%F8F827FD047DFF7DF8F8FD06FF52F8FD04FF27F8F8A8A8FD04F87DA8FD08 +%FFA8F8F852FD06FF7DF8F852FFFFFF52F852FD07FF7DF8F87DFD05FF52F8 +%F87DFD07FFF8F827FD04FF7DF8F8A8FD06FF52F8F87DFFA8F8F8A8FD06FF +%27A8FD04FF7DF8F87DFD05FF52F852FD06FF7D52FD04FF27F8F87DFF7DF8 +%F8F852FD09FF52F8F8A8FD06FF7DF8F827FFFF7DF8F8A8FD07FF7DF8F852 +%FD05FF52F8F8A8FD06FFA8F8F852FD04FF27F8F8FD07FF52F8F852FFA8F8 +%F827FD06FFA8A8FD04FF7DF8F852FD05FF52F8F8A8FD06FFA8FD04FF27F8 +%F87DFFFF52F8F8F87DFD08FF27F8F8A8FD06FFA8F8F8F8FFFF52F827FD08 +%FF7DF8F87DFD05FF52F8F8A8FD07FFF8F827FFFFFFA8F8F827A8FD06FF7D +%F8F852FFA8FD04F87D7DFD09FF7DF8F87DFD05FF7DF8F8F8527DA8FD08FF +%27F8F87DFFFFFF27F8F8F87DFD07FFFD05F827F827F827F8F8F827FFFFF8 +%F8F8FD08FF7DF8F852FD05FF52F8F8A8FD06FFA8F8F827FFFFFFA8FD05F8 +%272727F827F8F8F852FFFF52FD06F82752A8FD05FF7DF8F852FD05FFA827 +%FD06F8277DFD05FF27F8F87DFD04FFF8F8F827FD07FF27F82752FD087DA8 +%A8FFA8F8F827FD08FF7DF8F87DFD05FF52F8F8A8FD07FFF8F827FFFFFF7D +%F8F827FD097DFD05FF7D27FD07F87DFD04FF7DF8F87DFD07FF52FD07F827 +%A8FFFFFF27F8F87DFD04FF7DF8F8F827FD06FFF8F8F8FD0DFFF8F8F8FD08 +%FF7DF8F852FD05FF52F8F8A8FD06FFA8F8F827FFFFFFA8F8F8F8FD10FFA8 +%5227FD05F8A8FFFFFF7DF8F852FD09FF7D52FD05F827FFFFFF27F8F87DFD +%05FF52F8F8F87DFD05FF27F8F87DFD0CFF27F8F87DFD07FF7DF8F87DFD05 +%FF52F8F87DFD06FFA8F8F827FFFFFFA8F8F8F8A8FD13FF7D27F8F852FFFF +%FF7DF8F87DFD0CFFA852F8F8F8FFFFA827F8F87DFD06FF27F8F8F8A8FD04 +%FF27F8F852FD0CFF27F8F852FD07FF7DF8F852FD05FF27F8F87DFD06FF7D +%F8F852FD04FFF8F8F87DFD0BFFA827FD07FF7DF8F87DFFFFFF7DF8F852FD +%05FF5252FD07FF52F8F8FFFFFF27F8F87DFD06FFA827F8F8F8FD04FF7DF8 +%F8F8A8FD0BFF7DF8F8F8A8FD06FF27F8F87DFD05FF7DF8F852FD05FFA8F8 +%F8F827FD04FF52F8F8F8FD0BFFA827FD08FFF8F87DFFFFFF7DF8F87DFD05 +%FF5227FD07FF7DF8F8FFFFFF27F8F852FD07FF7DF8F8F827FD04FF27F8F8 +%F8A8FD06FF52A8FFFFFF27F8F8F8A8FD04FF27F8F8F852FD05FF7DF8F8F8 +%7DFFFFFF7DF827F8F852FD04FFA8F8F8F827FD07FF27A8FFA8F8A8FD06FF +%7DF8F8FD04FF7DF8F8F8FFFFA87DFF2727FD07FF27F87DA8A87DF8F8F827 +%7DA8A8FD05FF52F8F8F827A8FFFFA827F8F8F8527DA87D7DF8F8A8FFFFFF +%A8FD04F8275227F87D7DF8F87DFD06FFFD04F82752F8F87DFFF8F8F8A8A8 +%FFFFFF7DFD04F8527DA87D52F827FFFF7DF8F8A8FD04FF7DF8F8A8FD04FF +%A8FD04F827F87DFF52F852A8FFFFFFA852F852FFF8F8F827F827F8F8F852 +%FD06FF27FD04F8A8FFFFA852FD07F852A8FD05FFA852FD05F8A8FF7DF8F8 +%52FD06FF7DFD05F8277DFF7DFD05F87DFFFFFF7D27FD07F852A8FFFF7DFD +%05F827F8F827FD07FF52FD04F852FFFF27FD04F827F8F8F87DFD1CFFA87D +%7D527D7DFD0BFF7DA8A8FFFFFF7DF8F87DFD08FFA8A87DA8FD0FFFA87D52 +%527DA8FD07FF7D7D5252527DA8FD09FFA8A87DA8FD04FFA87D5252527D7D +%FD35FF7DF8F852FD7CFF7DF8F87DFD7CFF7DF8F852FD7CFF7DF8F87DFD7C +%FF7DF8F852FD7CFF7DF8F87DFD79FFA8FFA852F8F8F8A8A8FD77FF7DF827 +%F827F827F827A8FD76FFA8A8A8FFFFFFA8A8A8FDFCFFFDFCFFFDFCFFFDFC +%FFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFD6CFFFF +%%EndData + +endstream endobj 46 0 obj <>stream +%AI12_CompressedDatax0 Yil+Y|u#Nv!ȶ6ɫ$oUngh4eIT7dW͢g_|1.U[nov[z"Dx5_r~ӿ/J%R9d|/R/LR.}6rZ@c.nv7f0_~e +ϞwpBFBU{=]2_>Y.iX9ܮw͟p[/'W-Z܄ LdpLA*d g\t@'zr\܌7|~~kZmV{f}9_֛OڿjqwrzB$ zt\]p{fPԻ_n[CLﮡbͿ'@v j|ʴ|oû{8zן XDos1 . $J{8R|_~ج<Rv^\TW ~sXjN@Ew fuuۧ\ ~ȏ7A] `04|G ٧ +? a3%ru_&0*nW4A +.v`{?xs5ʿ/4q?{-nբW0v8x3Y\X-׺ꍮp_\߽[l ~\nQޮ^|Xlw77ūvxK^-.~$m3L}$]jBb߼X\V4vSzyb+b #]|_tv]/?—vbf+Wr#z^k=0j]b} 7^]\/[0@ۛ[f{{],nqq} H_P%Z\\-W9 \ޮGy?| [ B^ ~ЃPЎ~N/܅)~1w;kLw/~B3?~7ӏnaǛ/f@n_ǟo- k@~܌g=׏F4ť}[_oZ꫖#LZ?p/ߘ>m6|j+d[on{>w4Mg?O5AL7 +<&B3]_n{ +E銋 =ց{?oo?`X=5%LV-vsg_jQ vӿC_-OoG1T1`q ^bo}|盏=ηjo{fߡD8@lA nV7a +$_,t-%ogqKy|ug8u5KpIIN =j{R'𦛇DdF|Ov7%/> J?~ Y73IL 5'DAZ +i;p~_\޼٬>2HNQ3›YU(Dn2CGH5e1PR]9H?_^wңQVwC!wnOKZ)Q +1r0=bCpW<%N2C$EG>"z]⚚犰ʰuƇ,g^î +nl-7Z|Ez4P`ջ6CjAn{+RxA&<)`h)~J4Nr> XIBHBJ|P/^Gn\43Lc=V4zƙ'D#69yxn.9^]{7U`yrج{?wljNe~gI+DQJq!SOFҘ,J##s$Jgtp<O&$>NS9 +J6͡C*lA3%T3xOfw&TR}Rڍݝ x+gOgPk2i%dPR()#H(J4&3*ؠOFR)T2), & +JLERTI4Q߈fK0RIj c(fPu4 PQ%_*^w;K- ~Vom ?GZ-FYdK$S4k8k0aB/5L"|`*ѫ(rzEB^5IѫWJr<:Mq6̊,rx. 8kJNa'4O4Je +1RhqdIdI +$QL'Q"M@6T*TR HE`x1,B$ Kr ThcPLO|g݈18ɱ@3]9fi/yE*c(z@5B6D#*1IEA4e"SU*]lO)ڂs8+e /Т)ٞh1SԞ)@0l~ MAKA1\E*"(GՈSreO`jȉ!u5E%12PP02!bDZ2ՙ7êE:Ļ@LYN 1 4S"#rԁbNfǗa\'s"?oޟpwcWA&~BsF%(R(3 Q7&lZ1N*Cf @%eRCPZz9AndF0XxihM,CHGCgf!,T +""p0b;yw=zƑڹyֳ۹v^fF?^4b!/ŀv 9rdt1N7`y c(U !᠕Lf@& N GN@P4OFW-W-#}3.ZqN`cxG$I-H͇$8 i/JA VP&̱0HA;c)/ "(MId7$.'8%*d ١l7fٮ .c@u2VJH2̓$rġ06댈˘V DK⛸%}lqNSay ލ|-c?& ,׿YAcHb?u}7)=f_u~˺ScC/Yd/&WÈ͈#{p(Xowp(Bwp(QP@Qws(:3g. chb +dɌMhxАfeJm;3+k%DB/L,)RМ.9c4F2mbe@۩nmX lʸhBE[`1t-amj3VI4\iƳSlwf(\Ű& +ih1w@@,MU*-(PCZPM4uj JgiEIs4#a1(H˺GEқb}"aNKzZ(XT!UzJUd :)՗]='渏E-n_wgwrd$*r/{8y/׹G}VX1T)hHJZ_L]D܄k)h1feƴ&Zh_{٦-acֳrOrP+O*VfE؂hQd6-.ZˬOm 묶>Gt"ye#UjK 3cB߾ZޮJ~7{ly8E My8YleHS;7)yF,MSu)dZ>ci Zs'qu>)t1<|z" #-Y1Bۅge~P&ni.9 -5wKSiT`@wswKdHn,(Zt}>ZW>yxh}ǯzϪuBx)DLMLĔ 6FMT3۠r*ؘOCG485:ZJ;*yQ/8<5> +Br (P2BJo c2w iȀ\cL! +TQm32r_N 2X +\Srn-m 2YbaAVO = #OtH?JS\üKkQԔU}\Xvil($[6ln &9gym \9!хx0F"k(a.D PGct!"G ctAϟ5 c;ձzS*ZlXK4($AT 2*+teZ*Eã?e_TҠdpJ.^B(5$vF̙Y@-c9}4}fZ>L&W 2a|`!ɍckHٰsxبh̊HpІcf6Ą#6@)D茏ʆ ucֆHH2dyC8 :ńqDCc3U 36hC1 =#ҍ)֛g-`%n4-׸xvs*F7QAolKޗ|ޗ|ޏ9LxSb˿N7rGFZ)hTz-juyhD`) +Z*H^hFQIP]D q OpNCA(&%\Icbvɛ0Ya0>O95L 1{8Ӟ_y5U&-:2ۿaSo?1oL2|/#onE(޹69|ͯQ` a 8hb6\姄d#ڧ.~E@i_N50-af;ІP( Л52iX-i '{6N:[Rv- hRJ-C̴L27R +M)5d2&ƌ[нŗvqU-,+wK[Wqo}r ݪ +2gݹ +t{~nu ~'v33áS: ԜN b0/="ͼMF;>g֩^u;z\޶Zٲ׭]fU vՌ=*~Pq.P YZ$S4h+%6<߄Vc!݊=P(@a fzK؀,}("pBx ++#C E[e{0lQ 1&η8zҝmփgū~V+۠Uڮyۅ;vLhQ{7'8{A]df=#>9ɲp\cDkXf1tT+I?{Ԇ֌{ N +Pm.ǡUJ3 +*)(ºtFÑUESA("F fR$vL-5PÛ0?CsƵ{Xk#rzbaqb2I\[dL}u,fjg5PKuVY$ƘU9C(&kWήd,! #5I2h1\"qxrِ7ShI/@ rNJ^-8驝 o3Y:HLus\:ǜW#8 +Ĺp>,([$`LJ*:| +P榠P@"8T„4;!ľ .ۥEPi1F7Ai#3$SΊ8 "*3.?Xr"_o[ĸ#\?̈h\anuL0˄wBBB^pRu; ԆYhCCajC,Ă.scXj.R-Q(4\nCmU4YgUrS-PNBX(SOu29LVQO}WOdN=l*>%WSUx沬Ve=G((J/:5,aB%(ܫ>!e{|P&w shJEGåp͜dJ~ 1yln, bDzS&R͜m08gFMa YzH&oO47!^BA#I4V|`i[ &biy7Ќ8(liQȟʁ F^fs.㛿nVsl̩&H_o0\xm.0rhqr>[6 +gg.- +%ke޽#`0'v:'aM +۬\ p.ZM>bl" +rKbY16!٣lk3_rB(P#sNpsQFPsұ)ۘ-LcsScJ)R3K3kWr6n`6GIepQ4nIEVø'(mK07mp%wqXJPȬs^u;*##X΁TBu"UHƉD:GR+i3i;ޡӤ#'83Lk=XꙦz%>2Ki$^@mѓGo|X\jgl:\n{+H͸H>᜹8"HT0jRP +Le CdP"Śa +m"μncf1Y:䑰ӵ*=K-Y*B\BsMJit 7X?6~4}b Lj7=uE)LrvǒS.0[Hs|v=r L>r&bZ< Z?R%S71( +vqb3f?$/GIcs)P!d;QWH$ qG]f:@qxS[jxҘAƖsԴ~~(`Goj3_.׿~;bݳDz[֊hdV =OpiLd Rd0cν#SLdAfc%|yX&(r +ꈥ-+$Y n,'ۜ㝨c=&,K- Ùq M9NhInj$)̤ YMul6PoHy:*pMt#n EY!3ڔB +5@e$'qضd{$Hlzm[r x`|ȘDl>Or7`SR]x9ݔ Ql6Mfc:R?R޳;"(UuVݕwYZe],nTةu**_rZi i֚^jiTU@jT 0-{s8HWmmnjzf]fg]`v >60:1R{>\b!YF8P +7`>GDwahF 눭bW/y ')@~`#D.!PogKC}RS>j[˹_Xk=%ל*kqRkh 7]^4pvJ eD8"F +B TmY׋; kjċRd؞'l5"%W Vؤ-1Σ}S;L.jMgYn4/E`N;ُd;VŽr,N[wKx9R PNad51cӺSN/]Ħ3&>w|hS)}?qII6 Ul(ϯ(p +!yjrj9GќRkdD>-1i6d48Lf'پ(jZ]Cc}% JQh•ל%O)j&'t雜 liϠ) N']Vΐ/sAH@8~GED c>>N{DHe /d[+M6.\c( fX \FHCF t7S~1ٟI'?{"'l.X1UCMéԞc滃|oC- +،ZMeCcMR')确M>?>#m$EnosKekusFN-Sɺb-Z\Z+߈:³:81Д+$W^xڽ1.x%%w|ڼ;s_$Ҁ~Ә@v ># .eG{w&79I8=ǣ:&t G}LǔQ)舎w:Ng:[SF^7SWEh/+~?AwI8ۃ؂c+WbdL|CҒioՆRE 0!wX~2@F2 +ZpY&[X&2!ebRrvR1u{P{%"E+l+c LzyM=X坨ݲCHzB}!Ag 0{b+zaZ{d%9S|9M4>pIrgUб~%tM'hMV%1]U9{^9<;~nL^r4o_5][[vK^ MO^Z<_OnvY* +{ď͇8OMAaZ!O.c*̆b/92g8F Lٴg)A<ʞ#  XZݷ^e\a?O;$9O>01: CGd- Nܚx'l01Fŀ &/*.qspX/&wXX"6asbpz$_Pg<>bd㵬ywy]Jִtʼ;YMޡrơtͺS%)V9稓o.9y~D)%n5(&=9ǵͼig zcN:3A: !!&Ph@e%zC|DԘ8/P@S%ߗ>5Ns ,kh.Ღu8DN܉-"y"ׂV ! Isc + pw #{Ͽo V=?޳NTNTNTNTNDNpÉ'N+ѡNRBV)?-TC{$+{lȼ'Y+MZxBwJ=iNIeRם:0c_*>s4Ix0 u|̆S@]rW g0=fӝc: nPb0T1AY\݆;_tKauGWԺf% okW}f u w Cf|@x }@%V,WAbEs,3\qTIhF6!a9bH Ċ{)D@#F6`H'8cDh2GvNҢZT5]\~I)"(yuLJI LÈ684{YO3,4U̐tIH9;bt>h$P@)Ng鰣N-# p)R:TdLGSRp0R}Ȉ>1!XG </Bԇ1wnZAsڤKwj{oېW*-˛%,Y1tzQ*<;E53]8hhl = :.6ڸ\`Ϯh.hpp葶Ebh,S#Y%$%LhrG8f:pÉ'Dn̍"*F@lxM!=`GOz'8ԗRt xw,C_]XQЮ:;(]WW"p\%iαsԑ3qIVfd49$#& .ob2eS|+ + ҆]1e@q'\ #&9H 3b)̂b%m9{?939k $qK [gIm&>-#L2SX'*ScQҬv΍R~ +[Sn3@>^f->t.#3ˑZԳ'ZXX'i8ahLӾa2>4rkOle°8Hd26I$'ZG<$6 "f# NŪ#tl哱8t:w>ל95ԆypjRɹ䮼mGk⧬ {⾢ĵUHZ,R?ˈq:ʘ7asC_#غcFmL>:&*<]Lkb%5DžU:0`|ȋy)\өY"f"0Z0.qt ;b n0aTrj[#Xi9~S{TcJI>AW@;dL{☦6)uqL4j$"&+P(u]qLk"&&ɳhk=8*h`iUg@yhplTn~XAit4VyG8%cX{ $Ms$I)Jaqqa~wEܓ},H,B%9 +D')N,=I/CY{=g0vMt[+֊nkEZmVt[+.É':8AV>w3|~9+wm;:4o)(Z챮նx<Դ(~=#@`')c>%4B;GUCp-1[.ֹQxn9U՝;>NڎU?kH`fveK p;>Sw6M~ffg.J?̅R@(8sO:*:*W%[U;ɐ1MؓݼZғ3$?KVwR]g/X&{9Ȝd4N_h%?%\M焳L(DTs +S&ԂzQg4EC`fY"IK<[G3uGohUVdN`z\T;CmyDQS>+kBAFRuaAm|34& Ip緹8NxZ(iBۈ+eeL$x)/BjBru'qrҏEQJ$6UOꪞ%4si.ԋ6+퉘X1'^1]rHgA O3B΋\{-nO6!*g sсֱdqO`y\ +O;ZU'wRcݫF)-#^T7^ɢ؈xbÈg-Uٚ'*l9{f9fdI>IR*ɋE3( i2^gAs[l@^Uj/8iI c&z/9CiF5 Ƥ$njP"6&O56d" A]gb4&pcl1?fp|+.%nQP'ٴƷ4%aҙ9ͩ]0^!1 \:M܌(ږ8+j<ɋvdݍ-@8ٴ3mmrqЬa"^j;{v=k68CY~ + +(^;n VښUʨ$Nz%i7 :ʸLxw{cvMMx/gd ǟhhd W6'{筬.jpG7dd9Ǽ .FDgZǎ)thQ׌L>ocI8V-9}ׂy/bjfC +hg@<2{.edI~/\ǁW&lh}9'a%Lr/bfw_#C82{ +K 0CUbo'Ehd&V+G;0Y*T"T~?wySyS5x면 Z @9 Ө&j~G|?H:AM0ZjrQa,TD wbS{rI1.7ec}잖#Sv' NJ>֑Ȧ(/9?%O T_Ե鞖&Amzg6J hȰ 6O3<:T[n=TP.۬>-Xkiq<-XV6O f~LyZrU4mLcdPy2)IrK㜤#)@N;2P\-Kj"ۼ͉^r͉ۜ+9TPsќgbsb-d -*цTTiHT 4!.9.yVќx7d.Vќxג†„ns_h.(+9TpsT+9b*r?K:n6 曧^Yy9 Щ9,yv[c3+kpSOЎ7䳥3~yl\+phmoo۶l<UQM_x? +fz>Gt<MW(UE LKJen7'ԙkJf$̾(.$S YPz?}FF9)i+IƦ3WySj*˂'bMIXB⠝}^;odpk\#:o)tVQޯyfX5mF.p@B惸Y =~}M mFT0%3l;Kru]n݇{&hfj5DiV57ADQB@C !сpYMhU4-h RaAe2Sb5rЇ"B6͹Ib ECKrK7y$-p~"VY W^ \ a}D2K4 Z\˃UHS3,7:'?jd T% 8jfƔ 4P5O UTl3NuRfi,3ɬ`f+F]Y +@(K4)F!WS<$'4rC=K@Ӕp1LgZ0$ji@+R呭L#*@rVkTp`T*y +I,p5f5= `+%Seak8YkpIlk82YF2p8,Wq8MTli0p̋+m"e9]&nl:XCঌE<|*Ñr'.Hq̸ ʬt$Ix8Q0o%*ƕlI5ɽL͊ȨPEnZʕ*+ .%I8(`*3e0ѕKP4.<ǞڰOܻx |;[.gů߿asXޗ^c:]Lv^ru]qw}ieZ3{W|ǻD&]gyŐX^7_~C{X{k>6s7phmE/*jHg*ի5zꙖ1HQrƛzƚYs!fw5Y䈤:׉ōo77z!z~|sh]`W'-FBZ:Ҋ\ ܼ}{؍"JxJ#, +# ܬ6ۯx㪅/uaq%sƲFNwL~o~جyx3Zw癪I}:g6c:M_?'p^n 6xŋK-g)re"Ϟs.Ε.huΣO9MXVpU5g#Rg1 F$AZ-n1ԜF7/΅!-|ܽ^Z`enjSu~}y=KY5yejGs.#:3]3k٬F]ݾH #o[̏ k1#:m>sӄyH3ւl?߬6>#-= |`;x&$kH_!u5p|6 \hDsQlhD *AHs0w]S>k!;1>sv6@o#ٰC:7VSs|^^-VV֌ꝧޣyb~6Y|X/׋djshgJQ>g@_φLpz\Ԩ5ƝRs~T ҝ:jv:j>s&[l=U]j*kuBYdF0}|6- GTWvBwr7߶O`n7-]}QżnU q!7$1sA6g`M~4ox۩]mhmj8v4_/M|yU˧bHgj͂:Z RUsv6IO/"Ő΍YwI"S"gԆe kzI-tfsHjt粎^DR!4(#?vUmf{jsJȞw6A\H'){R{=;[J^ZX,>K #´#<3Žvj:Ϝ.;2ؑN vd%up'L Vuk|&խ=kju˭9(zIQ ӟĈzjmlJe?*ُDuɂɂzDG=x}dCz­]|Xw6r|IeMcȚPR&=IML{ʧ;YgՠmGge9'n} Ѹl$l?ln@ja8c:rٞsql@yԣ k.Vp#'4с`DmՓsI_Ů81tME9_sw)f\sS`tvԸspCssw!lwChǓt_-߾mqbҹЍ:731ެAW[b?V W? n:?.VZ{ ںXLoonoAn 𮆜1Ύ#v'Qom7 -Y6ܬoo6]!;\ꬌ/~?׷17 +[[;wˉ V%/̜4iLrIl_?nܮJFe=-iekIY[DYG_I_t @ks6;_|z>^x +sq4"pҩ*ک*ک@_TG6:U驢O>SF2˹HfO;,s;;w o-o'\Y-Ft <#:#giŧVO5lj{9_RϘ ϷES:f }nj΄3aƭ/x9swўW'YǓOu?3yK?O}n"gIT]ϡYFΉ/^ĥiS{w2đ{2]*IՋATDq/@Yڋ 3 rS1UzUx[nvEaۅ(rhl$Yօ8L+IO!c2V= D=!%"z_- d&)uS%=y/*g&A*.167{*5§#֓ã\`xpPw2n`i)[HKO-ʛ[Ń0A{`fRq)<]x}Qk梏}J<'__ Gc6L<܏kBΊ2!tͼI!| Dk]J9#'TfPtkR#Yquz.nQѓD)ӄ"}1HLfb&ȁ*j]$rGLI Ji$GYq2b3$BiAQ; WGe,%IBJ "O[w|9s b Or\2S㖅(p2 X5ƀdZDg 8[ѝi\:`~EQʁ" @{}üAʛoj6o^fRMx-D(|214K + .)"Yh06&N[hNExn^qU +]V"BQLH/A! ʑ0>)wr2(ИD'D`E*@"B\{ӢXSA(RQG!sJawpQZ@,K +;摦`]J5#mmt+K|'y>.q5C^w.2z_֨\mW +pPTJI뚾ϑ|ts4 ך݀@FXH`_,q" Tj|9Tch$1.z*3zWI;UxwZ@=_^m Gz|1H 7l3O+ϗǺdwk0COΌm@0vQ??M ] ^Kk_>g3;4}r_͏}.&.\0@_כutW=FGCTg  +XMʱq kF{P! 5O`]^#G 0`WcS7ut" +h7,E:BK:B18)jsf@ 2ǯAbKgI=9)F3j^ += Y4P86=OZz.|eL<J]*4X Ft?%O>Iύ~7,4d:0P>AAӷ$(qQG a +e[KC_T)RϾ7 //o\ۻaiq^lX7j<[o7kK6@f6o˯I/l3%'̒qAa?ך=H(<+z`rbr}iQբnڻ[RoVf)0 ׈ⱈN= ĹJ +e)4"-b4bU֌B3ЖT)m2I% OB h7Bp +jh5+{ Ng)8 `2W7`MbgU~A-hj"P% ZAJ 9eAH4yCA~|\dT7|(@8҆u@Hsr P|L}etN=6j4VQHqecn&:l|\%x,z@jLcOQ.l~ P P009PHlO:^`FwiccǾ^ѳn彃gzL3LAQ M xHXZDRId+tLHE! 1r/xDQGuZ<c x^xP;fv2ex[7jU)>r],0 fSS>ȥDl*"ʡa>m/kDR/7 +zb~4wM(w\ υJDj` (;pNa~}ZRQAzihJf9tI@")Gӵ}>07MV.P(01( N +RP+̲%@7$|-gs!,ӅylZO[m/Grt~ﺔT6ا>2Jz5Su58;_d:ѻڠmfv3;~Tr 2D+n(XTک{:> zuԐ7&[4d'PE&&_jt0еBfEZsk㏩К7 Y0uk_nA>Ltl0p Y!8`a&,,<4x'_չ$p@E)KmUI»Nc4_Wswn`+ ֽ^].M3BMhn/?|7ߝFBIV~OP Y'CISD]J"C*U~BӣOk4I]̘< +"nDŽ %$R&wj|O7wO{%m0S K~.tFS>nmL`&r `rifJ7aiv~ }{6]G*Q|M&]^ +8*C*R77˽dk q!x$~ßd7{aSVIdlkK|P#C@iH0@@~*SBҖN:D$U6\ >AB&R#BF ,@&c8MIqХJ9{eky=x[D)ZBݧRҪ"oP5w:{>椪LUe6 +U $x}F'BEJCsʽ0wMzCW%"vF(IKxlJNB b` %@ +yb}jA d21{DBJB[wk<4`;g4 :S;b~4wB04ѯ\_!6kFe9&2fC?GMFC3_a1u6'T:w8YK (LtG6໛>}Ө6]3d _[nRGK,kcLBR-ȴCn4x鞎ӖH{5e:S< >uKiDT#Hi +<;}8qاiFsf z&$qp=8ksY)`5r9&SH+C8j(y&X/їĚ (rLC̘Rh,GӇ,tBL\x_}a*Ckm+l)H ƭD0p&/˼髀|\/c+*d?Npt=H:hsׄ[ZŪ)'>$HZhα +_$xOޛ57$ bzO >x$ fv&$3=ԚݖzggzB֏ЃL YUYJL _* 7 w-d!b8>d߭F6VV֨46WPFјYWAV@Ja 1DotMGʧ* In&=^I$N$CwQ@"M&T*hW#Z_%䔒S EbsS&E d,VkNE$ +'6.0t/REGk@ +fD,tD֨j3LנiH h*=bO*KIZW$hB'cI1jF§`?Ra+QAQPWu Pq9 jb3v>yK(D d.ҙl3̋sӠ8R*|'8aufe랅] +УR.it9(4U1qE"4VMWS c5X=Nh+14ށ7tdGe d0&27$ 5]|ѳ)- [ eUa25/Ah*2ɋ(c=XY[[k_n?K2G4 2.0 0]P%^vBYlyQ3d#z:f"M%|SQ+ra>V YFDB@7F/İ[??᫊bJ eGWd6R(LO"|zm8@@SV|*pydB +R ֨Lqt+?s/N IC$F)#aWYN}ؿUgkzX?)=Kzx霋d9kH$e("dbE؃f/yd兆FیIQJHn‘*AAAPReP\NcKdF`_JLtz2KZϙ%yKنhbF:1#Kk\/:-/P:<3Eg9ruVU!M?[U$֬ [hR~="F~Z'_{.mHB9K  nY%_2/ʐJ/L!4d5L!?zgV@W#ϑ<FќW_xkY0"DX"-DŒjMHV\E!.;EdV3!`c]4K xbqZVL s$ȹe%0z®u7֑*2BqV=.8t8͍N.0¡ fExMt$Uװ darDc7 -*H?xDT@_*ΗγݺUdj3ExxsDK x,-C5`7=^'@`8eFk-HG?0{5٬DM 4IW\u&\i,z*-EfkEZ=Y 49^9}?dѸ"q #d= d8A~}xHx]?^ dtTKp - \D̫jhheMC=̤j*2;#]&-Ŏ<j`wgULle|^\9,(:I0RVA/E4cWnBaG̟<.zD7/^Ch`|e Sģ>{V+v]G͎#Vg(1 *Y|@"yEjGNtW[0Ȯ*ëM:GgNI <]ͮţ C%꯴䔁|'{Y%taʮenib~?#U, -Z)l'd&fI-OvF:Yy9sJohg;+fQ`!EGF<~CPGE I, -vA -s׳SӍL,KsoJwCL#4UƜ !ɮpk)Bv АEGSL|ّ">/N Ϝ@3Z:Itʄ -E#&.9-jwNpD<4&(5, L(68drF-0BIKH$*0o ux+<&B25'JDt3͒=э?!_2JhXA$0Htf;DMS1= 'R &pci%4mvHg .䗀8zqA? K6U9#@pl"VƑ}cHeh)fXA}ԙcS.3O[,cPe'K MYE7m:2l܌ 2X<҉`j!2ICUӛ"y4ɄMU^7AF\$:{cf' -ȣY ;HxS =ObY/#%#3o,(\c:R"IS@&"# -6g*2:,6ci"`*`#*)3UF,"MEf |+QU9ʹnHO1@'GīLIYpr2d?v -Ė.>41fM!hF - -{ -1` :C.śDڈ1Ye'fFfh БLE jc [՝ǤfQV|][3HADoi*P(횾{s^&]T'U*W7 3CIdiI-Zi,ٌH\r#*vŸ&2^`XdfD$=tgY %Ml:چlvÎmBRG -̘sҶuڍt=\%z-<! -KתkGZNL;" d,ϖPVTQfDJb,rS -]3Xu`Ji!# u;xbK2(=eȕLS2c @)$&&.,(ٻUw-fE 4Ҋ5Me*c54+$$XUT^UC:pR-e}M2sKMsFWMi "b(j!%7'N)>ˑ9a],E. 0Ǹ3uG4ivDnfj3;wȬ?Daߜ䛨I$=QeOj9PqI s^5IY֤`s@^D f8δ4mXNpv"FE24 pa^k|}V>\#(U& ?RS-!"Ӳ^yaA$Ff&`*2f4%Yqə -}F3*gj5?/z9Oe^F"bXZIT*!haDY<"ιȐ(kС 'b_Ln8vh/}s;3(ߨObt=UA:ng.{B:ew),c -0toqfrxzj5t6CO;NkvΛ1g)Мs'V@e;ϊQ Q$i p4@2yGY1}ƮS$MǽD`C=OՔ)nHc>zRbNQXi -Vm*zX%\eᠠx^aT=N3gu| [vua֨Yc‹L&;ǹϿ'14 -j$ -)£LӾ[tnFG#1j=%@x"I}d1S;_}ƅ8I #pc8DCC&F˚<\v-\ z$bɞ4'1$$3MO3{+%F7{_Q3 =-<42Wbn"2{!"yFɣF9r7  5#z~$h^2$?JbrW:"oe(np# |'{F` -un 6?XQdžK-)ޔ"¹ '! f?CC`ar+0=e熙O,ɽBկ^uf0Srz~ NgHƤoh>ZѬ$\ qy4\zaD}y= >F&9rZA+Bw_ ؃L݄Ж+ -Y(-B' u97}c}.3NQ|R&٥:]R` Gإf8DQ]Z'Iviώ.KW.]_&M ac_%tbLIW!.9Z%*dx5]uti M`,Y*%fF߱y(bT7޳GxmL -'d::(ژپ7ԉ-Za><5L]*< -sҥӡ-ehV/Ͼ&Bvn!0{kP!hG!⳸B"[Q`B # "K4Uf@c&˓_2fۀvYu垖USڊz=&+އAzƚ23[y]$e 01{igzK"Ut=O{Wc4 t+Ƶpd7ul!Z?[ql"gMvl)AԆ1vS OWQ? +V% H#zy#mg MUӚE;Ӧ鑤Jis>ЦO4 ?tƊ(/ENڝғyw  I$o60&'jz*RfƈU6AT\urrB~|=7u'[ `!*.Ի0[Uo < -JmP;_PKu0yQ Q_gVlr1Es9[`NMwLg깍-3 L$iT<۞%kP;o MfU=4]LYD],Vȟyk`;d\oF;[. ˭EO9YJjf@zM]APcd3Yc8zAN#K"Mi; $|ѦO\~ȔBK9]oEHI_DH}/"+)H}/"\5I)] sdyqZLZZ)_Z}Q/jVV_Z#EVjE!21[ \)G F?$t9 ;ŕ/ŀZ(7y8?™ }|@.~qo/Q3aMRET*&T /mgFb - -k}m3&?m}ɸ(5/It$n}ީ\^̄dm6TUpw|huD[7f(^Y9hv~id -3 )5^dNP -0T#Sl PvbsA=RgLDuGL'nf4e_gx!|رXo&GZ |-@4-)^=Xt-3$k ]Qg2,~n4?vD$1|&&)xW4^Tǧ%`D3=?<~c>ͤz̀:h &ooOE/kn/$杆?Ҵog1ȿ60k7|?b4>$y"NP -~&hTdJCTٰl%_Uj*CYxf RB2`ˬ*JݤHEmB$Ds Zd@xiu,lwuωN8أd@XoYLsd -+$60"װ^eTjlj-Y&#9FRYAͤ3T Dr# Q9ܔ 82k!%]i 0s;؄a#Ǣki1wj_^vl&r9i4M -ϱW?ZN_>Ÿx0p9  aa 9msƨ꺟/tge{EC'x?{|8zz ٠4>) [F{, -K٢'qoO3g1V CW/,$j?pT -u5Fr?d:(5`]Av)ѯ󞁑5;`gB:,T ykΏ fxT>(Y(̨Yo|5촚};1Zr-95Ű]{2,ּfKzU:T]kߌ^m #TQ2Xfӻ. ) mħ(f V7N}i!87VFnQPu^gl!Gt|Q͑ XgC9a#a4 l)*$| }L^W@7ކ|- -6460Y{GYBɰ#U՞4k -" ځ1?sEig =mYYC(ά=;?>X/;#Za奟O#H'3&um!kxt]E"fM=yjC3VIYc$8-7*ͩJ; A\Y[SPfLbfoevlLSih!g0\Q#v6s eQ:g4 b}?fˇB=/X hys`EeSv S -"Bw[F|5TAxN-OVKH0EKH€-E<fDŽ=H=(>Yls=Re&k>8 -&$8ퟴDCcS [*/yd.xUDٴ>C7'xjCߨw9nEzk۶B>ڕǡͣ>N`ha[;`450(OraGTm - ;XZLힽNM*/ϨY퍁:fo*`mKRF%J 1}P `?ZϵUkVc~{MD\{n%݀Y )2[1ڨ;,}W,tÿ-Xo`=mGc~ԞGXnd,گ5>c1NVkQ͟!_Jg5uz(~J^,"l^]ww;9۸ -[6˞=sYQs"а}U8˞G&{&0e+30}s-O~8$P%``T*ۥÜAԞ8閇TBiGsccA(U]2 7zEa^e_?ږn/L -Vu\F~_iNc' -ò'ϝ\ -T7.ͯnUm棊X3MΨT >AtY-]hl: GQ^{3k@EL7 zDN;E؝24Nf þxtKCoyn# K~aK5M]7+o/mzb3Ԧ.a]=5y\5\Fh#⍀2Qd{؋/Gw_;7V=ze w-Km -vV0f/P)~l߽JDe7|!P'+oX>LV"'Ba}5.sU,'+{iKw{|zq6M'n^7%]iDee['lf$ Ol,@3܇t;Оr,<NNm .8X qZM`d%_ -;I. v^wI`?(d k-ÜZHPt91;k_9 ǸX}a2ԍHԔF* L pA -KCZk0@ZKeLny=A*Yԧ-0NKX -'㙻ƴRMW;&%pt:9t༹NBp5s*UҩƕPN?ޤNFmPnśN,_]&NvfLHԇe=ŕ`ƨe YY?T`CC'Oy1"MMW[][3CaЋsn=s]r 0}q8[d@{5}ԯ.q~|^[=9L5PZrü伥@Ǡ핶jgng?hIa;tNoh85 v"U p#)u M1{o6& iIh'c%k 4LGEXy+a6j&zznqFqK}h$d#zrUMԟ_R}28!g3n#xC b - 4Y w~xtx܈ŭDz6Ov2= 6-~ [g\e',@ JH\B.Ic#uV()u0e][u[Tx6: $%QNPMPRNdĔl Ԙ<>7 Ek{\|t{'1ցfAO>&O:t(/ ӀaWɭ)'ћZp}^73Xv'CNg -=Eٰ7ATH_9@l3`{]Q6墮f33uFabf4_W~V^yuSSo/7VG Z0L7 {?8 "r^ s!z6s[Ve:ёxz'[QO+T{R-ajASKy\ܵ́|5֋7фqgS9醛ܕc}螗|6J^J:٧W\EOvqiͫwd cI~bho0ؔ\݀~YnwL_TћV-]y€qJ6^Mb7jwvֈ+:F5ܽ -v*zU!C=1Z /pnkAIUx{{?pw>e'gdoZ_=}K&W3 -u{af`ֳ]yx]3L8G%$v,XgWSzgͱj6UV} yN5UGe:SII9Fy7)#-lHcL@n4& ݋+ѝQoñj2L~<_EPv#ɣ -TNnj.vMmu3yI>IU_pB_ޮjS:^S foVݗszu0{W /.áW^`ld=}T mɻÍiS'|Oy7Գvvʶhm묺]Omvy2駃`cYfVKNkLWng10O/z/a.l xoK>:a؟'~?I63#MVrD}yUĖQg e;~ZV$%osd;h/&GcS,Y_#{c6O|ԲQd%z|t?raޮ"NCKc~IbKrH9Õt"TM'](GUboQ!Fxe9ucTZBv^RB` -輶L7ASo %LL䇮a2|YE.+vXn-8KP{{!@-qJj(g֎)Z/͈4RiFj*xJC 6Z0ˀIHK(ƴbb Dp/ - u荓F2>&Y)>y;NGa1*P8@96.ՍBTiBe1v6R\V5u?q=QZ\ɓex3҅Tt -N" % ๕n)ߌm]N+4jڟ߫a{5ɪ,lF -NdbMld$M|-U;yJrgs1nn˥P87C| 2; kmNQh!܋abHk},|,FyY:oWjށވe`歛%BXTǸ&JHig$6:̅-͂8a*^eOzpFt x.t@{|ީokW@pBwS*Wه7dҏ3N۝TI @.[/WS;nĐkϛX>PO -AG[؅Rj&w+.D!xNkVE-G焋Ǖ$ov\+6e)PƑQp8,ڲdSAYʣ6Tm3_On䱷9ó~y>^*[WE6ED@5ZsUX}Vl<-Z$/iP]܄=J{ZwUJ=Бadh??lWظ$'^rR@-q{l :XnЕvy2pff -?5C<{)?'9:+tRtĊvtBV,;Y.'#>0Tfk|V Pmoϴ큉=TsB-Y3-LBv3=sΙ-~3YpJ՞15ˉo -ij`X5,-]yPpKs?vCYbo:oRnsC@niwӔY|@2scG0:n%/BQ\r%$L,;aݒ6a,  xQu셋0_gwIfT=.x.(g5Xk+Ϡfx`$P]X{h{߰B㖴I/Ia {@t& =&|T`XE]檅z:@gmg&JN] -fL B:LFR wNR( :z3l70-].~-*Y1\~$(bp׆bPoŏ9)?@0ǘ8j1~㸼^oj_w{{b2=Է_o7d3wyפV6:?ݰ^;tO7z7֗^O3]cTmģg{~ctt}|̲>1]]_ƶwb[~v1;X6VZ<5Dy_~>4P9 G{+| ^r\{Hsadrާ?{[Krz'9c/okSQP|&0%?[քx*$Wk?׺׫J>tw/x Ziq*m4Yj@pP!| 0g(nԭ[:xstV%l_5,/cx*"xs0Mun7kUތ.TrZaV:L羅rR í`41Leg:,gq| z eu/|jq/*5u_I6rR Jj끿Q0*rJV-d?&֦oʾ9K|q =g:[ SB BW4MˡG1?=_R] !J.߼8Kx粹VeoݻoӺB si%hܟMU _k嵧-f̻*^ -[wm7+oq^~H:ލ7xn^?_{C7o -x#k_xjxŕW^)^ymثl<{UբWOKޭ+wTݨ޽wzꍶ7&V^ު7}ޣlw{Ƽ{ xה#E1VN%w{-{o;)n zyRGk^/smw[)eNamqݻ xK7p=* Wڲv{Lsˡu;-l/ikɷ,m/kgzag9ڻ{_N.si騹y<.g˧e|5/qc,[seHX_Ar\;^Wr&VJ(❶"UnWVo+ɳaKdn+Jm7rX_yһ+BoŨZ+oےo}b~eu}3Q] >V퓷U{Z_WND__=cz] o>FW [+ӓj?\>pw[=C?+} 휭\%;]|'_N G}w˃[s+빯[)ּ傴(_Z5I]^:֢ɷv\6kݵZKkUC~Xk'okPf}Zu)}?HG\yqo?k.}{^]i*}?ϷskB_>f8z("y@dp=q&{y% _=^{XPRcP\56.:Ql7A!Ty n.WޜZhXiXY+6Zz \oיp0G[1ruD#EpJۗ ixp]W7\B6%ϟ"n.P -)Q':|Zc:pxn7ЪYY\[T_s5~,>Q-Sդ,"DPQפt:վWr1uY>H$|U$K*'onF|_C%eE)6M][S7%E4[zOh^gC헑foîvKV?+٨ ɹ_ڎj~v66w[[e/Ь;ݪwKՓfg[Jv>.o -_wytS;]bwo=+J/׫'cY<8&O{g'G{/{zwm?J4zOn|L=D1U\>ojtcFL,3Wbv?nv|p8^|Ӄ`w'yqx[K6գQ,x;z(%q`(+\t\6Nه hc: zw!ci^U]8}o{ςjvSRg7Yk1 gN>,7P+?_σ Sex;Y[]TJ9.7Z_ήsc*w,枳n޻k?*ūUd>.}Z;n仉U+W7oWK*Oo٠8-_WջUAz~ٿonu[@息7NC-yc} --϶OWS'TWK*r՗m-6x}~ -G~p=Gu(^QlV%Wn]? 阓Ll_VR%{s;4*Q* -Q5[{6oMho#Zեvj>ˀr[zךu26oBs'u5Zm;VYHz;?KnCS+t.=Ͻv;A]{ݓo -=6zueC=|}uP9(;_9dQo7A_Uzmf\snxkWm7^dM 1ͫntzwT<'bO7ibK_\ؿŶēqr[@G=ܽTj @n^x7ko9ЉiIuq? ^Ͼq8890<$O9mC5Z&n\F#O׶y-r j`߈mٛ\~bjK[q` $Eڃ;sj]3n` >n,\G&NNTz1}- ~nPu{W5Y{϶yaq/B' $H! I~xkjws]'4HfݧJunF'>P%͓77l[a"Uwes\J5%F|MZyw}SFQ@vmEc_N9{r\FhchoZ'bwʫRIVw GZ}t$:l{Rͻ36r^ n -zWBN ΦDwZ^ Q"7Ow Q1ޔ'D&^m)^Ɉ6nnGnnneF;б+=D/8Y%D!NeFޏM?ܕX9 ˈZ+/dDá#N`ZzJNtup=)>!-/o+,遑ۡqQsuN4m-Ü~FN{`m^DóH~$y= vz# -T( hsee_Jź*K־unN[U?^.~S};nԯz۴^?N5&y[{?}5k܌iǞY'M`Tvz,/w\< -߫U}Z~W'nKcy88o膯x{2lJ>(J7^ 4|)7DpSX\ɉzj+'Dq#gBh #q!,3.`~/w`%̉Brn2_A4x;9QS |(_I2WzJY~q&1RQ/ VMSjSx4;ckWbӼLB2ʠX.4=#UpNiɟTw~YJ:t6golV!#U[;t5A՝\zL7GצZb|u1iiD}(XK[w{h -LG(RdTskNƠ:j+8!g,NV \uHP'eNDm/ ,<|r, -㪤8s)TɷF֚6=0z9UuL%1cWV5zU}28V)zRLY3,9 og2屺$Q,<˹:/U AQ|$mz5`0+͕xw; ǝ ǰ,QzٚVlI IVF4cw5QJPv8v` 1_;6?tß}NrzwĒ4mHIs@NOD!Fuj=}=q8Nؙ@8Ky32:2k24z2)ݝnr(N펙;~F$ucFYo\~FYu2#Í/u<#y*N >xF1KŹjgdx>Owp1魦aj5u$|^DŽO A5DsdF`ݕDB1!* itkvo*'T!O~~HIݣ`Oq \NuG|ۏι|t]9=oBYDqQ:Tq[!y|ogT>s]R5Qc5T3JT-}[7S|S|vIy\Y߄}GקDl__{\"Ou[e#Rk[[)Rk8s>mz.=\@W>[4]0N,\Nҩi:ٜ_:;M';KGi:\z}dgk4 UBQi:: Wv4,2i:< ].͵3.7LoN{G;?M'LoWOXli:YeXNOqT$ew -W?M=3ҵt4i: *s;XtVBi:YOVw4 tɝFVFpNvN_:>M'SF1i:_y4,>Sd(/֝b%9 >itd,飋vGfz*ٶ`--~QwL ?>,eJRN{rŅ,;JcIHma4z -*5^8"ro3~D$nVnR]tW̚l+5w&6(I.t]zJ[iҋ:hFkF}ѝY%5w7EwƋG/<\;W]uN,]Ut۪t=/D{ -/^B |G71+wܵ w[m~Ý.G}w熻FnX~]:8OT)c7Tp'9dp> wuÝWW.ݙk큒ޒΝ|<|xwYoDFX9wᘭ5&'5vveǘ5xXjO߁kc2mJa~ZxY(M}\u.Z9xIʝI<9g7֖hhp9R£ xxS]$:t>~fM7u7DonfDObwvyJ+bdK|G?dY )\upI.]Տ_:O/ʶt܍@{)vVOu?occNwt$½~c[*Jq~]^?1]Е̻~&u~V—Z;OpVv"{fz{$[}JOye|QomH6oN}_/;WmW`o+_t__܃V6Ns)=t>s';mW۽ׯĐ#ݽ~faAuZr'ot[s_X ~ⓜHm^?~cKr_{Ҫ9jv6]O ->NN^?m>p%p_[#ߘV?W*%ںOI{W]83O7a{ټeQc{*+ۼO_G{ѫVV{aٽ~Lѽ~*I6Ai ~1LGq5-5O9΁깰H1Z/;>J}ڛBv!Uܱ̚h;vͭ@,n_@vdb,9>_ݽ~Ԏ^tfH&]\&G1Y؋-}=k#OC?_Vnؚ׭WO7^~oݰǭZln-7Wi}՞1{d_|iћdz!UTأT+m~dm`#{hjje"sDC$ &W-\s(rwq'2s}"Zfdsrߞ#qwR>=+L->{p髧?^C'; oGzXVſrǖְ^>{9U Fvq<63BN'(ym8 o5 Pa0NҪ=c&,NPkyӧřo'N)ߛm^}Z^d9uV0/7K'ӏ>[^2W^O:Tٜf$t>-{6F>$Y8[ \}_Ը}-܁tzdZȗAc~ң0z]|{0~jxg #ovo'o6̮~?xt-?%\ -֕r(mӺIG]iQ?>6F#A2DQmcM: >鍐 <!umyw 7c;0Ky4^w}4Gnisgr6}vY>#$ʐoJI.\>[§.h6i=,`:7|ǖ; -ot=>6~,\ 'x3>iXěXSg -{` $mgI:y1L<~b|j8sESo&/@>=9еlmZec'8r_<܎Mn.&# -D>Q'VAa^C@ {ad#S׽J;aiQ0% -G۴Mߟ1ln;M hcGVg66eS~6rc-ӘB(4Q`?Imث]ƔТ6n^L@q6V?{IU5jQ Ov$ -TcbP®? NN5﮹7 8=jHM5qylM>;3p>iv`3?[cmϳ?_=m=,to0cc̬8mg?Z- ߻3 0i$4XkBhkDt+F3ֳo=2[Hp]o2Xϧq:c3 6JɌdKlInkd0Gc{On٬+6]=_\`$ؿZQ(¨m}zvp:#uݝqmRX7zeg/'aq:u""}̽] -8vc\wxj=\&m347@e:gv9aC2 ?CAӫo00kZiwLs m=')?1u2>ݚ[Ez16\YǐohuEoۆzF 3y켺8μ`Wכ֣wct]{xkkIC|8]dY!|DG  dٞ _ ~tŜ.u'v>5tp3_ТRi6~6ߩ 1SkQ9|dxAd!GE/hue~Ǚ2WN^Giyx1:D&6R{~ G##N#g6^wNOUEZv':z=Z(ۑ,mζ?̋~z?[Ϸ@![I,P;ѳT,hn=}HWOǿ_}O <_!ՓI;n-O~bwxbx0&w_"+.;#$gKC|{YlƉ,;d3].Ƨ-4dH@%w G98OYq"1ambek.B`>ܑ'mC:>"s [tΊ VN~ā*oaf{#˳ȟ77/~>|n{?>^ܜ:wKWg?&\\_󳫏}ԯskO1 ]0FW KokvgN>Y|>t'Ű?~mxvr|!&裥盯3?ZӓkOl,Y9?yg].7}<<9n\q>mÕsyC]$VΙ<vO2q@(JP= -Q@o^ -HϴVGq@(3P5vVGգ^'֮<'2_=M=r&aj,@Hڤl]IË=.f6 h!=]ƶ|kouۢ(o 4}#h@k/y7vÿag;QaO>[8qr}St|so}o=Orë\p<{34VJ|_!lyVG`!\|?j ށ< non'ݜ_7ٗWW~w~y;y}|i鯢(K|wu` ~Sɓ5=;&\a>ױŰkkP "TCF`Ijjjjjjjjjjjjjjjjj05njhUjOS~5߂6ZU[p 5<4^ֆj -& PÃAUoҜ5PCй^ kPoUL 5ОWVМh 5Ԑ[j7[VDP!:WiuSGY5І3 "DEYC ;t&b!\҂5s T*12,+zjYJ]@D*165'U4w\6鹆 lhm{Gm#bb)j U UEHd*}f=k+P̡R~}f짹 5G{'L,ZU~ endstream endobj 27 0 obj <>stream -UI>kBZRC JM]4{ѤM -VǓ]VĠ=f?$"j-ІH_K1/D4xeH9.<( 7V* &h%/*):HZOw*5c`n6L!H]϶5o+P_&T D%cWiRYDl+=B4E[U覡APr*wBD( , 㪉HKs&B .P0[7L?JCАP1WǼ3q\5\^SżJcU+~T:g#KOD:>KA,A"!ͯOG49IQ&z -(W%i%Hcj** @{8cku7U~mR4`}fWlyTEzd5QhR⃊g~=H .*n0Q EyEٳF =fɯ*T&pAczl{US* $1szlU{Ųt DMťH4않`*&y:'_#T[R}Dw=JP)]*W5erH#sۖ2։\c"o5 ^(bzlly9"D*N4ğDMU6,Ljï/M4WlE`f(UQu^j0#_t\:'n8R:DI=fR0l^ VOCrW* -9W=ERsDtUfúHd,SOL(ehܳJ#b93{mP&g3*Cȱg$O W8uh'",AVʧm fOU~1ǡU)ۜ&ϬO7Q(*kPZ!.1@O,̝ENDTD̥9҂REI3K^m ]MtMQ%&n{%RWo*Y4z@)' )⃉D5<4VUafķz)؟8 z6DbN8*0٣u|4ii 3\CsHMZlDPXT"r*4PcR1LʊU SL4WieU HWC`2z$2Uٌ4_4 ( -J +U:P^Y8[U+1gPjqCp9rP6R"`"o"s>E楙EzB"RJpiYUhj:Ft÷RR bpZD"cN)bR̫$DUU了ʠ2uQ *UDb6):JçM)URU`*/SC)hտme 2`^DX[$ꕊ% I{(G)4KySI%bsPX9HHc+֫)XUK&lX5&U5]QT'j0QqjaJ e $TKKP_D#'F&HJubL mƜ -TJxг* P:k$:2gFä oz$zjhxRRD0SP%2O쑹JK<c&׿5#委sld:4g)9IOёEaٷ\?M3ܲZlzL4YC%۹ 1bK*FWl"W,(BEWM%HBpB !}]<:rY =E䲙k&9`bzPRnMH%J2a"Uk hJ(>A % - |TE^%FCLpTRDҟRDSLh*HB79rRHhC*{TUJ |E;WEĶ%2GfU"x 7PDMCh)*DaTlU2K_i(Wr2RҪAl, -7Uq샊U)碢DEf,"&:TJ:ȕ? &ٞlђܰG_\AD5P)"s((TjȆ4/P~ l\~iY.'[{XM$XIv}JiAQgKɩޯl:WSj(U֋ʊ8y&5DD9oGʤ*'LBgzMF ֩aҔSUz"_'h[O1QMSPʏFRޤKi>(J/dRf=GA "RF̣ RJvR[+F.P 9"-s%+8z5J`-1d_Rc`JAolRo*$&ETkU Sσȿ T s5t'Z*)D(A2*~z`VQ/AD*N44 oNMc2Z8Eck!*z8{饐I$VXwTOو5RGӛưşR;qr4=I3K_Ii^d~T*Jᠴ -ZZY5u%LV*h3h@/liHA6Ѯ U&mD HN+JˊVB+r,}6K%ʬ[ 4zNA[!(Ag1Fm"a)nđȤ&ꫬG8UL&J'jjvQj)'=EW-̵(jPL+OGJ=NV([#e:y\UT՟&{WT >4y2QGSVj/ĔTp.+ȕXŚI5 ^R9Y<<^ieSxÊ#MqP 5rʤm_íl*!yKA̙OdMhΫDΚJʳ̽US<׻*[D'Ajo=fPd|hP${1U /߅.wOiUR 7q>HA) χ?RJ]==7 Ae[49 и7EWA%.6 ۂI;߿,!)F&k_Z]W*gJkE j".?5 gv{IIU+۵T_ 3KݳvdP9?b+$D}U #p,MKA倉*[ß~euZ;o 2H{Os:Q(v@`6Ku4~ǥKYY;o p{.HN#= D Hm7]&+%P `O1*-s-[ ɺa+aSj(.Pt4I-_TT #GlR~\C yP"r 5z%ЌT4'Bg?bUyIJ 5k@?ejᗁ4fjHZC #j{pkjjjjjjjjjjjjj~/4NNzkt?ݓۻH؂&-]]\8߾)Hɗw7?> /,<=;ܽ;#} G^Ӟ$OW7'g@dWw~N^ߜߞQVw> 8AYY9N;>%Esq=y汝H2}_9Ą"'R -=~us WIO߿jy BLM'_q -]|%]듻/wH7'?> a$S$?~O󳻫Lڼ7״puw}7_\Лzswu?'wg_\LLl <'kt[i#`=?m3gY ?WH>}o[}{^DZN}A WecG8tH}4=۶!%hz)v3CL^jVIa -])0)~3Qaմ-xxt#V3T1o؋Z.˜byPnr))~32y)SZ="n9T-҇4W䦘0`˹8e}(o{.BJXԅیm3֏ -s< ՌB6~ P'b;)P[.C6}-PshA&!s0H C P)QYu<5+ R,D`1>Q)\՗ h`t< D +M',r( g7)sES"t5LPi(a{ht@&HBUGM7Do6=&`ێ}O.ۃqr y^L"?z)x\i>=b9 $S˥1A`H͏Q P iȡV遝Ktb'|h |[q(].8NT~ Cku-]x>.SD!Z@dALF`S#D}!)cǥ>Mch!€/( '6xfm\"PL_D;H&sm,-.'A=ҡxx@PY`ƤPn'r `C䦥}svUbM0y - ]'OY$娞@l1 &JeYzRY(LVi4RgliM* D߁ȳ\á>[c:zG0`r aw 2/( S "qwL -<A'0) qTx,^2QP.ȏ=L"km@p70Xx1{؞CwE%2A.x 7I ڤcl,d -LIBnQ:!dFe_ $C;aEBE) #ZKx\I^dcB9=Ã864@j+b̶| Dt<Ŏd1jI@g[68j|Q)RuhݠO5 (R9 urY>v1LwѦh M]Q&Eb#&qT>"B+,"H/zɀΈ )BHT$Ɩ\! O -J"Z `gikbiC;I?( n$Ȣ:m'a܍C}cuvEj4}BR@ &-Yǎ}M#&xoLh=?>tH<I@QB," ]]; E=p瀔-?n!Ph#ҳD@ӪF\vETR6IEXvH -.Dj4oD0V!J# #LtISa z)poZ+87h.)%)'jρSVX -S9tDicjyI$t,Øf@q,Rl^)`Ct<6=@)q a1(b8|.v}ZX^}кGsa'b>p~C Aif@'Ss 촉@qL<dXP 2XBC:=T Y@0ABa1*ioCD8DQA [ Ov_bPņT8JS|``+GxO $ͅSB9mmZ6KAt$TDM -#Hq::)0\I@?.w$4~,q)R;>i$@6IM\1 vNSq$ӒCjS2 a~@G?N:#*1)p<$)N6; C d u:E IJUEz{'>ĀV@8IqF # 8p`Dkh+Ȍyt6$٤9$lN%(I d$fbz2R2s2%RFm {OЮZSt1ˋ6:Cmm`q hbt560$hC -(BMHzv"lA! Z'0']D -nLR(aa`H`pbC:G (DKI- -ә(B Fn+-:SU ! -#',/S@?ACƻTY ; -tEZL}Ѓ!S4GrbIm9?e9H/Sc ]xzUDJ)zDbN;47Y\))5!BJDM'q,fSc6"&\`꽅CyT%h BT!C*."1&8 d.h)"QHq1|LQ0a90,Lš4 FioZ=) M!&0(X3 T\AFBJ9\Tsf;0fi h)c\LF jQ:˷S/#hp.s]!L+ȂsnJ%0Zɼe4` n:4df-'s$B1Jsb%B4G)Ȃ tEia S>}|MբB+pz&ص iH'hܗŪ8(]^gRuP/p[}xh(e 2rnw1wAyBo4* SQ@D.K.1$ͧ );6iAت(H9H(2L8n&mq6J jhm,Zkq {y؊5E,whIs B&;V Q$NVN,Kaal,:XBB 28f#@f#CI羘b8' +*BAE!CL2z -0>/T% -r1m{@Bp.'{=צȌht6#Jc[&`KF'9yRq&} 8W@ Mj&v,\%] i jːqm܇15ǎG{(/Km%8Za:Y4pO:fԪ:H"324Dp6dG(d"B1;t˦vE2OԄkC|x<_2IDK5 -iz!K..Yr%q͔s;d~ )l΢,kԃxnkL)hm_Oc7qV.w"qFԧ]:lAڈ`/K[@.鵚Z"K:G.\\СA᥌CcNK}2o )>ʙ$K[i[ n:wH"\菊fb* D8Ep,b$CgW}2{b1X> 29Yy0쀦u'@Dj "syߊ)VڵB]:i11=rK@3I,idr!6y0,`b.bH FX@:4biBOII6[sLCiΡ3):JcF:uG(!Ӂxy\(gv]ǁ-Zd4 -CtnTمAE! h*q MRb>0*3ǡVBwXwY'VwEq.+F - JAD BdAlSM]झO}D -XeyL='+Ng2AGGG6Y{}5y@6$VIEV {.4}ʃumHh#ofiMќ1P|2aDb}G&m҇@JD/dl'3qq:08V 206-q5T瀝qLR'|vX>IqH4 $r,;\s3%ӡԏMo%P>tbpF1)a+C DxtR&[ӠXȪ#\ai -N,t \^&3.) %3i,. -^&p'sfx6 1-J l!XNB pQl -ql)n(ApbY_i)d&+o4[t7WHWٲm(_7 &PskLەUt eμH.,Gбr8x[ErS ,:ɂp̉=[ݨ \fSȮ5ܾfY)=/]!vLz|F46@\I#<\K&#IMHAR9p݀ NT,]\'0cM:ssLKBtNdҪe -9pEAH=<{bMpB(EVQڏS=r1Q2pB#t"y̷@i/NӬvm\ qk .TBҴ9(Ħ룏alPaObV 0H S݀"i&46-ޘOvҩx(ȟQIˉ*l!DIB>BVE9znU~t Y%-Y`钔fs \Owp|7i- .*jBl_!"=/&XȄ3 -v~R0NV - -Yّ秫G .@6c ]eI`SY)Ơ -ɎRVM a:d^HH-F- oobPч.!T݀Njc"ar'lMa%С%̆|(?ҺvY1~%;-+hYd5ZM2dGq֙Ri]L! "|Kdy5"{'BfŴ K&'k;6.V'Ab芙Ȝ%B Dz·:i1\_#fLMG&w~l_fb%hEe8L2Bzt`"Q*#zd6db'[S0%$sYeZ͖S9@|'SϾ`BLGIdDwݪ+6[8?$Q YRDg3/ -BytAg I)oץ۞qdX[~+d&NuFLG@vZ`-RJMq\H] Oàg@µļׁ -ݰ; [xdA"xB9̂ApK"3Qvf=kC0tLC"5Elxۯp A1&ц,+dD]D\dON$*%3]0qOxq6N_F$qjwaJR_6cVFg_:}\ǀVpVQL' p[GOгlw*%_d;9ctFl)3z ['*'gA3a Fbη"=6f^ȥN;>qMK'rr]blᜣ3}R8nL -(UMxi.\ɇls~r29#|;b MS1NV0q~\l#:gr.s[?["i$ ٳLܬDYnO΄ίoύiW=GhD'Ah#d]ۮ%U2w/4,F YMϑȧi{*"Vf:#|<.\ 9x' uPDW(sιeKu[_-r,vvV;b -Ow6*hwǃt8gZbe[0LW~[M􄋷!_eF kV"H9DY$ FK.u;hpo2`82YOVaTd–`?v3wKC^gذ[Tæa߄[6&SHFZ+) ~u\Nl|ʎČ ܄b>pj'% mMA"ȇ`!20u+5R/5XWژm.u˅64#cXЍc?L{Y܆O+ňj PY&R74z Żk1ؘZ!O\i~oXcB{ |abUJCc`hb j&H,Ѭ"$W#'aDUCmg""]=Ϥ@4D.G*O씈"v SNlؘ} Ḿ]&sT7M:")KYHrAr dwaV"gnV>J$'n9 [#4emKa_&ؘb`ûٻw9 $wic*pN-T@F6:;S_ɬ1F"#%) -?-o:D}0jxa۔ -R7F{@BD -1auM^GkeAt`B06 ? nɖqҋzyeCu&7Qз^)fRqL yAEK{mb(2'3u`ɥ\Fz~x^'DoOsjI*= -R^nH dY*0G&5<3!X(HOxIG_/ [@5M!sp!!$0@r4좏.<ty %qNe{p 4cgûNm(~N ?-jI^Q$y/ZT -Y9׹(嫵 -#DTMTu'Ͷ 唿(¸$%NI;d ʠFT.X]M+tA|)R6 E2+h> -MH)b戙sydmx㰸".~OB=i Q-SRY6׷)5\E1j0XXGSTQr=e.N8:|LD,"1'OVh {\k4l鱗Δ^ 5Q/@@Uv;Cwi@ !12BjkO;]IA_ɻdKբ Y\C0" ]fqC|.ą{ƤUipPECx޲]<lY^}Ũ_(Q3G5<);jZU. \3O.I!%I-+z`Yt_n?/cYFiA1_ D4F#*w(Iz*~ܢ~)3/ ?U* -dh@ en#[ZqcNO -'[v9% {fzTYMP[gq*uhy^UyF̳VKEIydiAt^ -K4P*4C /'<90Fΐe53Jz2E`aOB7ZSqO9Y)KTw`2.i*jB}, vRySځTO_GoӒ{U -|G;TJ2cǘ8mtyȰϑYM1!Fָ]?Jgr!2"3|S ̡=QI{yC=n>Chy8M'IFSڴLZ>>tD+ɍ -"$0hnj@ -HjV&ڹn',׎>:]j?M4mRG۵":AK-@o%[uRT jjI[]6 P&R&;Q (ADJCs<]P_uQk8( {7¼n&񲸞k' X=C xZZqGKWoI / ScmʛCjD?n tި%/rQz L1 \GZ)І>X4n vwV0d}7ϊBf`BM b1< bx ^Y`Q;J](-2 z}ugU!CX /,Y '+S}Ճ=23Qh(?ԏ2+$ѴHcY,JpF -ChEB<*kDg1r -U6( 8hɫNt6>—!{U%ks#j 0<hwVە -vgÀ?yN旺Q'!" :@h`H +2)7}:YN"ء%x3$̤D$ug# TXq4s u,=WE>'t9YAO1Z$Myc5aNЯfJT<{JQM/OZ7+Ac~qW.!}|^ט"%H`@n)%q}@cq$BӭJ DC'^PQi9taSl9[PBRh!䔵9'*#>'->, '3q3GRb'>uk;,+:1QW>PN,s["n(Oα*@pTĉ3z9N3gٟ֩6lϯ8SІ.;FnѦyJ6dKvmCHaPB\Ǖ:8v$D`LԺlMm'!(ɆΨz:q;v&e8Sq=3N5j6@rAm Ɲ-Y]KِdkSmJi{K^'ѩ(ʅ(I$X*n*2<?#I[~xE64kIt& Zl {HƮ&矙֩.P,w -+3)/ʝbj7M)+:-ߞ 'Lb dVpL!KUFŶdH.c@9֠~cT˰JK&Ȁv)z`@?+"r˖I|3PM9LOm'7B@AɄxJ;X@ᮘG`2֢\RS4n1lRj`tZCnJGXNӦ"0ې]Z͛!qȮ >>\@U˛CEW;ɼkg&xl - ~ݺ[$$" >މzĕMTX9n07?j̷dK3Ξl&<^tT3/I'tԝY&t\a!7Yi0v9VƬNsVARC;E6ѶX)rC -`K@8y9ְʙeX#}woG扳wCEU.$.)뚨dN(ѳh*$N#S\H4y0:- ax]Zxal!" .BpbHAT"yGQlKM!,f gmgB*M裦-t%.^njx3aԫ~&9H*cxUdj,Xy3a%Piu ~#Tm.L)6VS\B-ZhVWwS-,p܉lИq6{y6xvQlc\| U \dے(Ĺe",=k-xs(VsK*+ڔ@l9$*d1"閶;X(Q\ Qu@|-{F;kDJ7[hܩ[{5[.Sr؜U,\kxHxRtAEZrP>]rDNH[ c17Ï,^|Cx/VB#oά ^o ݕl[yOb `(e+nM+ &([NswҬ^# V|h9glC|Яw`ҟ!59$ʒ /=R+ui"N)@,;y&`&d[IdoRo4@ -Ҕ/t7hL"u%JKϱsc;N8O_\29mֽ1f+p -i=G-! 4AH ƁPkUǩV A%Za5;HIl8d6]0%QRj8I#lNJS+E6+ǚ`_e&sA-,MI1QEU_gOːl>ގܯї!Ġ= lZE0p D"{[˒.>0~WI f-sI 1H?oD Cu(j@sAHN/Ԝ!z`5é< -amRXfmּ}߇QwաSpwMo;Jp_ڍ-CB,oWB -*"n!|Di -]dm=VԃLJ a( ?UM| ,.eU ת`%q|s:8EtJe%˕@R!Lc\\oՓ -K'J`l[#>RːH]NWL-*|9/۬p>n`'gfCNLT3j~a\RYVBbSSi=t9j5%ڕr+D6tGV- !ؤ}f-g8ARn)S"|VDY -FF\ >6V`̿peRN[ -9RkyvSW4-r }šH7%<9Vҡke6V uT:uEN)*Nr{$ѭ8$4h!`K.j|-jrwDOW$E RyS9噜%w(V޶)ȺAΑd S)ε0+LA M]\l/DڎӾKum'ZnPD,b.2μ>("L`:]7h ܐo/'O{le0-qLr~yq&\'_4QTI1Ѻ)p]MʃMQ#؎J40+hDŽ9(蹂qQRB:nTe46uUX ۏPrTр!Omq#W=*ώĭP*Mb#Ѵ>Et{wQegeO(doJ Ű:?q=% -*0ô/a(nol٨R^$hAP -zOR_jGq}0lvYʁޛ(Kĵ㢑~Jm;HXh`0"˭n[ҎW -ҀriˈV"_%bkeӇ⼩k:OnRJ DFCA2uV7l$li-঒˰> :l$OwW!՝[)MBTbWN[} ~8RaIn/0>sk& ѩ-n ΄ &έA~Hi~1Xg5! i'A6e8 -Ohoj*~g5lL$vRwYfq7`n\pSif+CJyByleL]KШjōd%*gBa0 Nf2݇ON1-_hfuxnaϩ2*bq"0|ѦgWɲ# j=ZUvOq:K>Qu_E9ZjB)U9I7I{)M J*&Z%- uj{VambmRČl?;FW0)⎘$AO*Jm|sqE: s2d8`{VgE+Ee*fFp7ɮsJ,#Q rJP{NB &Cm0ЈNa#Y՞^[7,.l%^% J% DTXۘ5 ="!0Uin T0W.$ R;euS^9/xvrKBwxc'As~%U$F ȕ(ٺ흉6{b*XlS ,TI&q ؀Gm&"p&PuS>%R9K?cIƷ2TSgcY6Z Y,P4}75O^1k4 -sW  -ٍ=-JbBu\Y&pCj FgD1`jUXJlmAZkߺj[fWl xIK\㡅TE7ցPk;jYg%PA, jXyk΀Sw7"~! -;C`QF@Odwm6j6+Q=>!k8W?#9^|l)ly1)8GvϤ=lObyO]+Iv -NHVj,7Z*<.4w.~"U(#ےьqܹ]8ȍ<5KjvC8g!"s6Ds@lp: ?Eoۼg MV{^㑔sdh2Cv7nf9"F:3m~p1 N9R#Ϛg!R+53 '2U% ' ӅR oD A kwcffNvP;HS=hAPj.۫7vlۖ84tQU{DT=X.*L1fG1VrTknB r> ܣ#` I`$Q=>k*S7 e6շG#S3NسuDEfo0i?z.nИj֌+ס[ a*)X~m +([ 0#n&3[Uze -j}+b99#4#iVɣx](]TNIbrC'Sc2J-딂p&3 - 9<%jqS_.% 00hH|sI|e;wѺB&:a* -ӼRb[3M[i\dp;A( -,8iaG^:{&:Y,t X*ͧcn. -jNq.1k7P>J_eÐA2#=ky5ZW!@w0TX:D_^wґ'p n nIjrwv=v[ҋ>BA{FT2p]VqsRV$۟~/w?||O_^??oӯy?߿iO?_ۏ?}{z?/߽/|ˇ?û/{gx>}v?wO]0ß}w\_>rF{;_ۧ_<%y->>\/O w/?__|k:|wֿ~[ߟOyw?û?=7|~|?~ӻ?]C/O~r]~7?z' endstream endobj 7 0 obj [6 0 R] endobj 28 0 obj <> endobj xref 0 29 0000000000 65535 f +]JmXxRwʙ}]wF<@ xEF7LI6ZQP _"%^F$ +9N BLj~PUgC4Q PSi|wʬIX]Xt0˛A@%@fAi]JEPjn x `@#}0M|T#{P$iW's.O g!^^N3d^Q t80A 2Tguʦn> ,LU]Ugbwfj-a ,tE6Ccv{X^4w"2@>%8S-f{gXeqC<Tm#iK/Y+s(F`߽7lT @,rmsX4 2D*9fWǒ؅p͛;v۪jZU(pW% RTnk"`D44~5"hv S؜jF7%o|RN00e vdic~~lh6?Ao2څ +S:wR$6W.4Ms +W-|CuudኌB;CݓiY{x7Yٵ= ˀA)0MXUBaC MW"HIaY͉.245N6TPLU 'D /*{,S"̞bw#EsXV3Mȩħt3/d4)hC [_hl!M|} }1B{r9PK Di:2QP# αì;rI.TBdǡГbhe*s»^ۗAV녭/)  H[Ԅ3 + *21Df>2X@c:p2 iucVEhjٽ(e*s/^ۀNbQn ZGM+YR|€D9ZB)]d2AQS8j&]MD +vWv5"QDGЂN*a9|bv5{vW#kRfkK|>)&O2ΚH6r +trDb˚j ^ +!G#VqR>{"s 3|8Lb!4]P{l]f2/GDg̭DId7JÄS+$*ajd+ + /lL&mdpAz +^Bd8n>( EE5H4LpKz^SKqǠ%?!1JHEI/0]64_䐐Y`L(BVnG.mu@d]Z^h+*7 + bBSSL4wgj< Y`!MFaT6N +t(?; ҈*%;[SThʘ@eL^ J pX.3`S珑_1-A`(̭ǧ3 ? +L:,U ɟ 66!ߴѭ8xQ +Ơa]Vb0 I7dx4"K g 2d\%/~7+~G"-' +4(1bdUc |$o@ #&҂!a\ěm%$opDZ &i DQƔ_%SEڌsi L=_AQ +7c@ Q`o*2|ΓL:qquO(4VS$UשdF |Q|6t hf])K&bªeSU3@:9 qm1jL{71A`=3?ī_ta'D])?gom" R%(`r;hA@svCK2b"9P2fjG&/wKD $Q3$sXE[IPP6 :ҒC1)VcFM_$ʐAGi;3<;.q:ik2~QO։A'PE+ U +mDg(l,tA$ F+h+cߕ` TG[Kdh,g K\ƴ3r9PVq s;`O9e &!S)3z#?o^\$xNGmD0yh1fmx$O2> Ե(,PD%iWhPjvuNGB̍ 3uq;/aH,dG/\=`TQ -:j<(R(RFv#O&Kؠ3аT#k8\bU`'幉Bbʮ )aD$l%34X |1(ɋ\7 (aɡ"xt +SKrk@FeW11(DOcW2YFr#%$gYwpMK)nl"n]TMorӍw\*.) h4mV +ifL誂Κ|3_DByV~y +c Z,90ʏ[UFTU*-d%(9?cI:%fGuΪhe01ry*ݜIhZe^& azH;Փ0,´@Gh&&cZ3 +\"LxY;R|{=MOF([`eM$T,ENT>(HҺ@sɞ& ,# [+^c)R! +%BR %hKazޓ2{ + Ӎu]0AE_ka&ClR|:@gӽM$Ab;%,'#;d8@DkxsQѭ$2~HN0YΨ"UQ\V$ M@:0.)e))YP&&A>&` ъ6Vj B +THD Tp1xk7|y ( #D khKUkMc9аz$ +s'.Kw%%.h8:sC'f՛<vY5F,Lk$P;9X$/rۏ,dQ]=ϜZ 020D灨.a4R8 ,9M1jITIF/eBs8L\Jk։}@φr{`tB.ϻ f96! 4(u|zT}VLI.7LJBY z.) dE!A4Ef3V0*(ѽ@(}YLX'8@Vh6v%kv/kb<Ȝ&z =}k3& ~^0Io쀁L% A.uZ.0ScyLY]gq&@&'^Pca8 k40FbzaNEɭRLĢӃIջX&LzS5ǡ*˔fG%oY9di|t@#"I9)%Fgy#!,@2Qq+Xe.|(\9-ƧɫnE_Ԁ$@Ӳ@wF[HcFãX 2ܘ@wcYFz$ÇAfXEڊ!Dih[Q%f"ӡDL*ZC2؞ P3)t ; 69cMaS[1MMw~V}HȽ†_6"$Fm$#A# ?'=i$uVƽO3r8W~XJ->%ut{{WG8TlYVJ=h,as.bݝОܨ`/&CG@չLWMsjAi(+^#0T5BD5‡,M:ށH8ĘF|s{q{NA!X \.Ūf Y))Qix,X#S/m"VՇމ +Y#3˾3`*<};g}#ՑS#oMz9赃0o]L"2ޞtՠh$UVe^/i|N)^DK+%7w|p)h:biΑqFjHMȑR0GK=(xPNNМY_9̍Qtf߇kpHYp*2fûyN~wjJP5[̰כc1')'M8%"Mby/(`cd)Ef~*,FwĈ.ÔVІW-$!a1k5ci!EZnL0gbκŜDC_Ud>4X70b}<#G>ˆ 3bwÈaFl6#Da/#V0W0b=#f8̈].Fj=<2 #nɈy[7Ǽ.Qk[2"%B+EґᑞXl.400Y +}'*9(Ă&G'ew~)wiK覤Ú{G+9Mr(+#ߔ>zcI\c~t`C§hOݿbhAk5{ΞiCy7Pج {6T Ef97U5lCja#đA p?+2&J7%#EoUpnu>&$v7v$|PŻ^wo/-~_FgSySĊOu_<| +g p7P'6Sno6 ^{x>ƴ_k1ZETGf[5O|UZuJ[ͽ2-c:u}$(lW3 WC +: zab}w~L 3DOV]+ۍqo5}1J`7@4y֟9t% -~ZWA+Hj)b1rYQQ[ů4> sݧ_#o8=F,I9Bf!#\}!oRX]kTMwm}2])[R}E-v#WCf8~7+[ 2R:C8J9+1ھ+ۿ$OBSD-_oXV,.+2+^,tL'~N^!Bm?}JW6$N"h ٷݍr +24([܂$+k +_Ϫ%̭,PP +|y*TaF8M)&v[:zϾT:?aIMTH(U^/)`be=q"7<$ߚw Dp*km6$DU!pޅzv9Sߕ[Rm l̻':0Ke譤nz-ρk쉽7ʭҗ +jj(t1qQ&rqa?ROg>ho6$GWiA G'5~D%GMB*%A%?TrT?,+&#!pXQXA.?\j\~rA.! \~iȥA.? mrIFrEfAs8?vO_0|cldx>IW87e߈~o|ßN~pB7'o~0o `C؟/J?,ZCȎg~t`0e4#C4 +x [ ^ |C ŮVrՈeܕпoY+|/{FB>_r3[Ȱn'R>௑ c^552bpH^dނ(қG 3҃)z5߂ˑzIL>I>>H(G4WIej%VB 3 ^ ˜uVe7-K zWa w毪2uPß5%2=zphQTM0Ɏh?jhYVSF[#)&o~#7Ԓ*..ti*oUdU]D|tKE^6Ut:~_߈8qaWyy.[hn*  S_Z~ Q__/Gڦy#f3v(9F\?هWI\#u|&D&)n7oݭn m@Wn&/z8i7U旺6W5qZdX*gU2͡ ᾵|D4ۇA3}lWy8r^1(g-^_SC:_峟aǣr~Uz]煂{s{v<=Seeo_v?/?$n p|k܊v+rF*Wbjl_|cm7ڿg"? 3 n#z5n!HKȌp9(`50,tW%j0=9a +b#E +^-4Z S^?g?V˾xr }^XYڗSuneU xݻ8;>n88͓8/GDgK)Bg\lԛ! +Y9 C6MnKStr>X #hm9S$OU_ +rI= 2ˋjsRL6Q ++wN}u3dhn(lO~ϲ1󫵻?PMk$~!xB[+-`;j6ɈD"vZwN[f(yeY$ȸu-Ԛhi@ltKu^ +~~-<xyWˈ܀#8ZUmؗD CGK;p|zٖ~;wy\72ʽ  #v^).4dj:deEYT`pw c{LY +ԬԹ_K$5.B>MA@dUQ +] * +&TggZbDbFct>Xۗ" eJӲd(&"ʵ +;g@g/'E}cX23 -7P4woy员)xLJkii,`ꊦ[27uO_|?O??#˝V){Tbau2GĎp V~Qܜ@$^ /@8{ E/Mei<:h +3haz,V˓\rښ0➧z'j|Un_^StU=C]ynwN]$IUPltosi>6A7(7*u1p E5U y, +(b^iAW5f#w2 hʾ 7uNjnJL8J*#*jdu,loHt,k0laxzf$̖dM3mo>2gU F7zXH+Q3YkakYS5:UtwRN .]8i\E|C55U=[¡v %%La|h,K _ ,xzV%e`g[ a5gλe@x>vm!@λViT7@z672Q7SqDOur(i +at)PL]UbHa22"2G:Nq[,ηТݕ++Eճ Zv4mt%.=eITMKI=:RRxTg $9'JCqVQ|!$zf+8[p贮@. h Adm`5Y zV 5WFS4B+ҿi0tɬktB2& +/8xӧq&#j6< PeDܜjXbv2F ;G`˄FFyWp+ \)ds4dN<X IĀT7Ddmz%Y 5ǐoSV'Qt̓7c F zL{%XeBWKFDD+O&Ζ bh([`ºLG +7S+(9:>ցD9[@!#üHvpd|1]Hړ &1K Db]6Z"It- +3&m4=@I9ƐTyI*QH`&1$dA_7ֆ>X]댃T<VҬE >L.LJ1-ߎ W5 T4vɭYjS.UQh&mQvEKotn9am@r˘ W(^/{BŒtKƐ61 }T{wD}.>%ms"E'0cg9c+f;!*6X][-z  +h5Q\g ȓyPMܬv1@S]Mv8 )(&@br ,d:u)[z,B@0LN|GSR+{2ZY(JQvUՋG?Jq0QJD.iZ;Ρ-~}7aigeQd cjL4;2੫LLt.*fi(URQI&sW5뺦>Ϝqﵺ4큽bM(>U]gTܑ,F2Q:_,;&UG`~]IͮkX݉b#qnQΗi%Yr2AOScUzkXqo+OB$Ԣ ;r"FzX2Ѣ+BSR&,Fm M& + J^͎`9Vۑ| [OodAJu .՝x:DEuڸrDG򨙬2o9iz4Z +Pź)q*d'bS6 [Ob c 3l +}tL0laY¥h_ Wt<!d+ +]';hF'z-\Ajczhn.M^Biw+,>\{{wO:U*za eXm>Gzv> +Umw&̻GJ  ֋4*:IsEZjeDV41T!I %6C{<ܲ̎E!ЉxqJh0{oM( & gڿqA ZUϷZExيIM7U Er  .GAwIA?UiC@);cKA@EL+Ɍ 7,VDB-dlvoFњ~^ݳ u8."I?eվMTS\*f۬VX'0zcERWܷV +rbTӍ+{zD.^bc2p&4]j+vKzΎSޚԬѵ3~ӕj~70l;SH-$9u)Oct\[;M#ؾ>&ǝ g5!rQ#V+ћ ^$ ;17阢C=oUP|74)j;3{ST|8YWNt0t3yӭ[IxW*IH'uGV"l(1_.`@ʄ/&TqY_h\rU-6h><:iBu.(7J8mzns>/2Hֽb}nهqſ5CIjdz1V+mH{in_UOq8=аGw%.|m?m7c[tj) \ S4A'2zXڭo4-J{*Y4LU?gO>O7?B?? Yku.ٙҭMй1}ቋNƙ_mjŊv!/w{ST~a*6#'uRCF Npci?Ί]NMx>|mԑtV'2\Q?ݏ>?\ >˫Y^mc]Ctw[˧A!(Ԃ˧$Ͼ_~7DS3(_? _d0`wJ?5??@@W@}_|/4 +^?[2߲HlҒ>#.OwX0>N?SO+Z|OB?'ϟaLo&O4$A0ǿRT-GL2HaIo0sDH֢تaޝ`K#n~>Wnd +)v7! |~Լ:S ^o_\ +gF$)5gśPqw NQ7/X<<Z&M[o+4\uW,βĺ,Cթ˝yZy,~KB퍈YZek.{@ޝlYLZN(8|8dGdqy-fFŋDf[%L:3:6S4.w:7vzd[Pg$lRu?^\m%^L\bwPe lenBk=y!GjKz>қvRjl{@Z.DNIƼ(K>%\t>]_}ie]^?^9Dוr.Q,&#X 'v?tW˱f;ӋЦ6;H'ScIVtNGu[?H3\ۥ + CJ?$+wzm\V~_<.$b%x:I6|ZxIv4˅g[l1b5am=6//ˏhb,(6fkkb?CJƆޭ=Jb2\@0Wam.kyQ^EubyU,&z9}_BiX&r9Џl Whbw}"lArFyfU*QyXҦ4r]h-f992x7{\ɯ/. Ua̮B_Bui"'S7J Be)U]li7Cٸj {)UVLǓn8[v=cɧPZ<jrC\ƻ[10ۂL׿ٴ@|eHCCNL-A{ +zLkƽ._FE?0ŖK Z5V WBת9  />qVMϨrg˼[]qGLl4^p4K8ٻ4b籚,dm f@3 ak]ʄKV:X>="kfҏWiRٚP.y&=&Te6^WwܲHּM5Y LxuGf3Q\'_%ʱV2w (6M;LTC~L] ހzIOWڲvbPMR;Et`å[#ܒ诨tĶ.hnpb(@,^e +ͅLX⠍a\S=Hw3.$:r)o[mr?-B8z?듨,mPUMm?}P@-vF4=cpԙk:馦-`;Ъ70z0:r6<_sv"|Zowf +ZSf^a9َ4œ=\=]*7lv_?̵éǘZvgvKeH(@˥t!Zz8-B:pCEGL;^wc(S}=Mw%-@,l6odٱSN>Ʈ1m]I{u*R/.=3,n'TeU4HWGXb{XJ沅P緄dbg1pۿafmPWˏ ex#r"B"0 ̸K=K>`}@y<*\׽ R9 TQ/:9/?gUu9UNg  !SŐpS1 ||y6umoe$vtTxnX`6V7%@N +ܸ sU 7^  ǡ$^T3̙\6fTI/Z*n~yG}{ lsկ3iT +i$m/l>lKv݃Jex|r,Y8O=5ìݵon':_k١*k3VkrZ2nޓMlFpiě-r;q8B~߀Hr;}q+!d>DkFeg=Ȩ7~֟ۉ̜ +dc8DA˶"ƲZn ab>!OSbZv E[aZ[.TqXb>R1גۛe;LYn=ߵt`t +-RdyAk"y)4KOAzovf݇ҒSiH|m)?F!jTX&5δREC#1Z鹃4Q^oG*z.:{Bi+&H[c@G!UNŪFAOcش}m%r6\6to947Ij޵Uxy]bTAX_tKkx1=B줝'/%Kzf!U@w)dgXy9ɐ:Vi.,v9"w*BL4.Rb`xTo G7A>[g%q26{צC(t6+`W곫\ֵj+L/Ez8 'ijfoЄUCo4I$ +U,{I f:U {}!-U0!n/%W\-;frf\:X#͘ ZЅBA,.<̠ !V9>c;7C9-_HE@"ՏCR l҉4 I_F{˙ ?ѵ~0P5ථm,@MZzI)mZe)4NJe%laa|t 9=DO?ߟG{榜\@\`6# DWѾYO`dv?Ǥ߿8fP P30_vwE0ůUa16B + |X\~]qX¯YV_5fcS>7)RHl,aC>-bJSUACX_Ӊrpq%ƥZK_jL6 +@6~ةIf@="rfkIb/=o% KVo7f>? jOhBYMJRrfUx<O~pմzU; _]K)8Rkn=m4nYo5+t: CT͡|u*nQ~=q&ďs[|{W'B<2Ќ}(Zh_.moji9rU06=@PZx~]Xմ"zw¹ψ(T&}s?avِ uM=;: 4*1F·r2~p9|G d?ٕb?Sy + o7oq3|ÂKw3i\"dg U 7mCA7B U )H_,ܭg9)Tp6*Uaj\ۿgs6AJG~l/4זgo޿V,67.t^ֿuQG[b_]J7BwΟ>zw;lgֿp+-hB(3>x_S Tgq^$j'2{ۘdgeqܚ9럟\ m13?0temWALagL;7lfl8̊1.ȡYͮ?Uf'O׳ýNNHf.7fnӧlpqr5ל}Qjm$jXun&U[\NTB۵9amiN5ۇ.vXx tΝ,6."s+Gsٛ~m7#S *y ͯ.lDžJn~v\?Htj9x~/=յǣN\sx!,(x0¶vppZ8]XX>.,䅺P-7 Eda^,..‹r~Y\;^]﷕lxtxQtŊvZlfv-@b^4S[ l<@*v88mbqA 3x\Iz&ܺE/^(\ +^%.m~y_4| -Enf֎#ϕv~.=n퇛P0X IF!3S%Jp9]\vC|3': x/Kpf9Kחݑ\|SExuoj8\v\ÏI-<^ P -hD\$i+3{J䤰bfՕ`~%ڗWWՕlYRia4+WBXV ,%q#y@hJN;==hFW ҭfwS~)=n{Id/+ڡQߏ>٨dJ#3p1Su<'X_^]_g@Wʵjt5%^K&{kɻZboq.օM<׏jq\\?nKaVx8~ws#Y<_|>@; [pyk{dV7+hL?Zj>wb瘜RjzQ=-k>?A3l-.ɉLD}zF"ɻZ,y<ȥV6멸LcR*xKa,]{ƶ6w8|`%~b'y}޹6wKn,|=)&v^p mgC{^7Oyg ݫƃbl)S;y:zgnCj {ZW +gKٳu.z|=]{>W7ye!tU"V]\3fR۹{̆kUqNJޝn$ڟWWku3܈;\)6oEmb~}{[l"ޭΎsiCW}]Kgn@cP<&ZM-淢Q.?=:.KGRhab'\}*7-n^guJٷJB(RRV-ts'[>oٻ|y:ʗ_oz.|ݜ{?仳˗~kn~Zŷ/+SO^Ӌ鋙/.hra~iš wWOkÏW͏QM8i|ؽ9 n?G-vk= +}k{{Mwc_3eA-ZDK:x_<^<7Ӈl|>: FOX/F}s=}5j}"϶f?GKˏ@oj~P؋={ǟ۽c'۽Zoa[t{>,=8m-\}ԅf~aĭ}^Bm헧6_Do}}_xqy\sï^K0G΋r'pwZï sSsOՋͣÔtl^_ Onmg( Q0 :/Rɣu;DZI_yrÓ k{=u~}]첲W;&zLrvuYY:^~\{F|x!Hޏ^tzTnH{kq{׾[WZztupu{O*: +qAh] C;x:uspZo;XNz:^hPɗ i/_znnnNؽ 7a-MXvpzVt]o'[^G?/dZkfegr!{ץS_w. +wId<0J{]xovpfHxDu1rmAuk:uZ+/?$qj[yYkU u>\g\Ǵ׍^?^znhk^i+g+^_hnT:oZ䲇G'#OL*{:=z%$ތ?U0f&vl@뷾Y}ߴ>}r|嘴׃CWM鳡/^_X.}{wVdܼǫI0g<ȂK{=[Oͽ;gacBSC?fש+.kG'ǒ^ϣ+bZxn {Wٗxd:ㅼA{}60͋'>[OSw鵛Dqojyz]zuf7&Njp~Z8>Y_K,߾^9swL^+'0ϴJY@pL|Zx@ +)k}ulg3k}Uh=}n5UOoBSr[vʖ¡zNݘzQiڞy1 +MRvz4͟Z7AT+ݝ)en윶j~Uh=]<4TOɭ +߬{oGOևoTO#`Xa:lF7vcdW<= ЎٳǯW}Ι yrӻfKZ?}XN&Lާ7ڜ<bhC'ۣW/TDwpNO}dK4fp[~Ͽ]ߞel(qiL׏A[$3vV]6N/{w0W~~>8:[[ _Mn&>gpjs)ilW +]{Խ-L({%( +1>+x:RQxNeAW2 .}Io{8כva7+(zm>½{y>3;8' G+({ŽWzzJva~ 8d4W>K'u4iUf7i=:o8MfL`/Ö|\!gRf?cc?==uP׹`%b"vӛWH g1;y("M:П9c$ƖZ0T~7o/|]f"PHBg z8!+ى򙵒F"qi[ڲ+7Oи٭ВHXnٽ|3b2a5{gs}K`鼯S湌(keÊ>~NڰPVu:lTdӃnL`WQ:PRb +#J$ IOYwӷd4<\'dep +G =(8}F@T3P(}2ٔ h{T~*`5-MVTP*.ntP/ul 9fG #;W'#`gغÎh-%t ]-jmP^%='dHZ48KjXx( :\j@$̦`Jq Ae +]!r>~aI>~XV+r!B0f_e%A"dP' +A"e (b;؎$ Igw> d.SI_Ue~kK΋o[sT٥z-۰g;SmY sϊal{ %ҍNQv5M~&n psQ@p]JO/~AP>AE1R{> q{n$J+ 2UZnFUY!%Ia1'hGf2K-Y~[e9eL|aq۹ݱ=wf$Lqj=)TJd(嘒ZglmanvɫR:ӉmW<6ش[>.P4^Uyz{= >?)UĄ"rQd:)&сS\(oAWSD +("aA yh20蚃g+ʃlCmoW62rn7n +ѪQM7J\>aJ="2pXBnV"v e(bk7ȝ-ԳYǣu=O{ Y E4ző[ֶ|pڲv3 2kDrAd#ۨ"D_og}ɡlq̺*P$%8fW\jVڡ05'MjGx䖜lm9Gg5MG3:[&]bޣ] cIBR9{2%$cġ+վn'Ov9#C}klL5ٔ9Alƺl 2~*qo*q SaǷL 20 KMVH_fP8{{_?:_X,nOo.pw\\ZtO:rOu>]nb||MGu W+^_u>IΧGu>]ƣ|-._^'@k.I2h +2՗~I r_\]:V;ʉ1OS`uI|@k>b*[nSf?)`+*0qn .s(SPnӤ ]{IH=i`!6&;j׸6}2$T)& 9ip m̱< eBB㵻6"ͭkwҐdUtJ~ ?ul4)&p{-_6űsŝp|joع7^.vݨ4`i\ ֈNX>Y^'KNFwh]QN} /y] w +Gܩ͖t[ܝQdޛ),1Rw +FBc+S0ģ-l?cP죊М mShx1\՗̑V](3rsW9'M٫{¼D=ꋘB;7gv2g ,︤ 0*`E)R^+L$bPE18C<%Kij~B04pN/u dBa1-R'ײDm3ç3ZTv*&$'Uw +Ӄe?=('R6zR˚FTFћxy_7x-ћ ǓCPXm'z.Vێsސ&e>\M} ǜsQrwͣ73+ d~;Z BGzBt׈#]nHwߐ|_4CҰl'l;tE>p4W +4|̍|eS7<7 +Nq;u3Ovym&Ú+Lͼd.v"Gʘn + oW'ZNhGou~Kuf^3/76l`goIwksݫN\%Jq% NK UKyK_j<6 oKiQ?@M\~uvDž#=.ː1nTge1vS-H9)ST*sl cHc[\=氼a#R+~ +Lؽ+&NϹ1%?= L + KXVTmwwQߖMedtN\%ցKvxg.\EDƈ#d]3蔹h(-D=oڱɥ<͜KydN+ʳY~w^C4y)޳<>x N_ wpǷ9*߇ݾ\{O~8O!gvW{6O(9ȸɮ~y' n )uĽFlHU\dY$m=Z_{G֞M֛ޱ݃#ϛiy8SW,i|*n16',Vҹ p ?:W~~߭׷x0Zmn(zm>͒\;]ս.-zoA7 pO{?h^+jf{ޓ;/^μV]E]b){Ņ`ò_#MKd6~Y}Jc~}+Т鎓ɧ'F6r|jTʛsgkE NQ|l[cu1^ѹ8lEׅyBENoQ ]j +_QKazz݊_)E)_W>l9י˙.uެW;XB4oSr(=whŔC* c3M-& X׽Fb-Lg .{z̦>. ubVCwb,(.nusS~O$W'՗?)>3){O }sٙ}[}Jd!n:pOg'syq.n Y_쓭4@ы}2nnbl ecdcHjbU:pOccJ +> *BsZ}-Xvbl醴;yO I>a$݋}[}}klJ.nMd\N\/nSFd^PE5m+y#u9:?M.Gf*|pNUS9W^JR$4ޫu˟m)MAJ&17C?hD,5}OLILVr˚.R0<CtMv#)VN❾NDjG%KM~YFt/K'`x$㺕g|Gsw<:u6њQ~h)N>DKaٝL$}q} tfIj(L 7FW^+WnկZ)0_~jzQeT+#Xm5 dnh1;pB(~q i >7wZ=VxWz!ӂӨ,z] dF&6f1SJ%'MذMxqb.s׈em (/t; {+;+fݫW(m>} EwH|g(+'ʴW(̃m ?_WiO몣;޿4mq~_kYS<_@.fcz;{ ߋ[?߾GGiok_y0{4Q4AMDU[+Fm;&e'Qt^),quJ+[; M6]*ËT{YݥoD7&w$w9lipjⷵ<1HzcCKw<[c@qg7({vafZg~U+Z.VlҐ2٭o>) u݆ױM_1wRa2J77jCīgwScΑ{ w=zSͭjz} s&#7n?6G:_^ghtzouٲ^nE/_إW?z=|9:ߑ}Tu3/\+w3oXz&&> _dT|x9PwY?-S4qWP뵻. Ae7;ӽ^{g>oǠ(%|JcxɅIKYnr{5|`ao5Ѿd+:sh薼kFfV>̡܉һW]lsp;*FҼ,0)|!D򬌚7LOY[| n^>6ܝ#THd~H2os>no.L6:vDFP$.ɏ{,oX0]}vNܚPh wnCKsuJ:#xQ"DiE+;XJ}\2l+ $JWM#rnK\IT-5쇊Ws"Gr9*]IlUf+}%QCS>"ؑ +K +KBy/+drne<< +ɒ/,Byخ/+T(U +M +s$Ti3^1}a'~j o;+{a*/<^Zq/<ԇտjUQ*U/<Կ0rt|](Po;dכ^xgNCAyC=Kd/LM"P1l}?wRɜvme@.D6zɮ~BQ\"ULoL=]}no^;}Z<?:ЋQ寽oGLF>>;MX 'ŵWٺ*«CkkW 7Wvۯ]{0uoގ [6{g}t/^,Lܾzrso_C _zG6Oo^ZzMzS lۛ:^J^~$B/=_PܼN'ؐ+xuw|vc^aqrV!Iq>>=y9>*c7}XkOˋGcLۛt8cat*|ΚX<!?&6N8ʛQU.(-\S/] W}/.K|w{;bow#G=(N9ф|!73O{o-.8/Be> k⼯A;jL8C%@aBσ?F(c8'ֽw Dքb=Z}kxeopݚ=f]~L\_Z^Y[/<۱OZ/SiLyMd49 h:LfFIe Og-D$'4aaxobiA#0E  .xzԂn|= ~ݣxgۗtǍd W(w6t'ß67fB&.@fr6!@5!TGce(;Ssw|]?~~ș}7rq?흥.-|dx}5hG`# (j0tԷ;N>_Ox:]FeOWȧd|X i50r yȟOGrΏ9ǀ~W=}K}.Gx|;>?qt`uqiUDUʻwH%i(P~%eGNx]a=ڞ`L_#1YosoOw䇍7/Vglr\]Oޮ_N/}{|{Lf/z0+NeQ;MdAgȳ́ yi ;C $ ~vy5Ůe$csd쥃.O(<-Wb郵!O M'~v>??! <̟f3S& xzؿ"V҆1}`=H(vΟ %g5% \DC9B׹'d~pVac=m=>?^pWdW7!R%܁$r;Ԛ?͸/~;:q=>|>~e~9Pr‰u9s3)dtLэ5m{GN /RV˹?PxOt>Ye<6R;;@dWw9zݕwGk3|15BNʎ~$e}1YOi[۹@GOC}g64eL\l_@w\J/{8'G:dz&|3.Â-9`}xT"_n >x$y7yG*BޏЋ#mmXy54FΛ&{iÒ +^!gmbk-9,h}1P=nqY9|xR$8-A~!H4MAAJYF#li2F2Bб ѹ6SK:Dh#2ĎH fs9aqJs(&WB'8M_8F8iefr2.gXIX㓁ۣ7oyYF嘣}+OW@Θ]sqtze}ur1=; W$*Pb,EoE&{.{o-/w~l^Q B>մ$"'8!˃t\w/z9Ï,?M}LmqidDDr$솨yD&^l{O54H٘=:'ѫ> ÷3iΫ{idX22Q١j=vpO$QcuF>k,G''Mb_o9I[Ae''ykm~|0,њq$HP:'qmLȦ*gYO&Gڛvr1 ~2LOcĹ%g{wv,cdYcaU5F4fc&3,ѴѬ^X9r1?YIwIC +2qG'}2G$'xb|IOI̎N3iɎ7ëKqhyv3ufeX?uL<_˓śtqzy33Jϟ~:9I"3K2Qe6})oivsthkh?`j +*`Eɻ1gu~ݧaTLB#OofF__[}͝eF7[3_N,?뙉;늬I^SAR Qϵ{ld5H8c/SO,&,cy€5w$3/s^h$ɂ<|.rn6 do0v7pl 8&o1Jh'{;})KqG; ;v74g~|G;$, >stream +6>I#MObpHBCܷierm鋇B{H"vg҈҆E*fWk-0b#.\gPEOIpOo{vዏ =p:Jp٧ˣL;+==!~34YP C9~JǶz^`t=Lr<$,}?l}#m!s&ܞNߜ <1wܝ^N\}9u?7}FWstkigzFP=h6=c8ai|K {\2,(:~dAMjjjjjjjjjjjjjjjjK j!kï&`n_G 5RZ|k*a\p 5<ֲ+YC 5 [h Lܠ:RP lZ[ BNkaVRSUo 5,L(:PolRS-Vj1ɲݕ%엉~6E3)TZjQ|܇BT"V*JjfYb!B4]@$Y5P+i,[UTi"֬?4Fc1llQajи*a/a | 5`h,&VXJ_j\MjFYװU89PCgP 5uY.a6 + @!6KV(B,3Ug : JWIm}5gDZ?U{ 5V`l^`B,;qfM{_.& ]`>ڶJP/ +#}dh/Gָ]Al"k;#$ju+0(Vl~9TqިŮtv饆 P>׶ł#6:"X+bҬ +wDj\JjUZVXml#=BDE[Y褢A}(9Ȅ) +,1cb庖֌ A캐AMjP~(ԇ6AӅjTrdrTc5_Uī$VvpI !hƷSPKdIJQUG;'Xf,EH:2e[Fߪ +I_bR_X(>QS^_I[j+@,,("Xm* &|8AłKjj*EZn> +`95P8&Be޹楘2!UcVEC7X>WGڰB~5hո +XΕi0Ĝ)i})y]K?K*J3(0 Y/竆 +, ;E9S\M]y[lG/[`"#RR\B=Wa5iMUa)zbFo {&fơ5AjwMw&"7B= IU5iyYʥőQ(= ǂI"Tq$1'R/$5d(s黨H:IM( 6K1t zJei%DԄ* a5QuC+W],A9=&M8=$V p+KiKejx80RT_UTł"TxB/(Us +tӜ=Rp`bRcїp]XGSj~Pᑖpj2_S͈hH5U 5bᗀT? AlAE$CZ__XXf"EYjإ_Te{p Tk)[Tŧq^UT "cQ5fDQIT4BTQ rCUssbo z.bn}HRU5UMT,0~>ht^0nQ5ZĶ"QfZWJq8K?r]HB/igHjx(P)o"ԣ*Pi[GA; %b.a"sH1 0Sy~@% + &2l=*5GR ҷ*F~V9GJ5vڶ*6(,#bZ9\p uJG%U*qT]J~4XmbFQd'MRl1cbj + F-%B:5 #&$PTXGqI[3Ը*N,(3FCOi뫕e\CGp Gټ\c!1v+EWKW3FĶ2 #&eR/֤SÚ5f8)~}zbI +py7W(SMs=B)šR!pդL:(/۪)H +1TïAW!mLrkrO:"z"ņŚ1Ȱ Aj#$UMc_+o"6FMDL>rDM~; L0։{LZOjl[6X#Ii.TxbapoY0shAoAC.b34դQ}KK)NeIғ'VkTbV[ *P)!`hմ9U AZ"JٔbVuGKDpK5,\_,8( blG- Ѳl뿪tp +;(a?hY +rçPCY+9Qvk1Vk b.)纓Ҭ }}iWR/sDb +[DŽ8(|0#VJkȈȸ|TI%)~(N$IώBi kvl;z֌ +i;A#3GTH^!I/R2(yǦjPhkЀFeN,hYC /:R! 9YK07M5S)G%b_i)*)&IQgZJL *+|g!VUc"MD*lJJCt$o@w@H+k8U#=5pC@,Ӵ%ҿS\Q0NPJ\F&*JT8MXW a_5P•ARWʋ +9U]y."Z8UʆclYJX01m)q^* GH`CjRX B!G:5h_bծ]JDl]iN=үb5B)R9Lh(K-\*@)xe[H#}p +/QYS)fkDUAZ_ʂ= =L:DY֤_MLdLDRqQtWĄkz׃I pM Ϋne VM>7MGѪu Ka}A *0T57M_MeM[=*=zʻ6KmqacgI,\CViؼXzT,NJ+ۅUXkGzDb@H˂YDAPV(1DUX"M$.IʍS%ľTTu``>XcZj(/5SJz&DrUd;@!=lBje阖 tbL-fօRl"#rSX +8HB.+UC!Tk b;jDK+l}Vq}Cbcafӎ_H*<+8"bRdĂ6 e/+hf>4&55dm%-$5W 8"çqG:j)L@T",KTR<5jXkvLưC̛=6 +zWE8o]yc%3X ̅ + 1>Jšj +e+7WӰL*HphLj;"v0`*Į~~6l.& J~(WĦ_Z EF4qkQѯ!RP&&P4/琈U^=RpU,I#sb4DЦG'A}bi)E(%LD <Xf]2PIIU#Du`VU +5R#>,d+~-Dҕ7dY)D#=W< +"M_jzL$YC)빉 1bKzGl!Wl(v!udP* +U*l*.T7ȐfZkQUS)אЕMdbf1%_#wþL֤(ԠRi +EeהvѴ,,X4bܣX1i~J\PmYUJu]rMĢB%v-v \wV0!15P񳪤M0/6in_+ce̖XV>oΠQ WQ +KTh +E멹:!TlT0 {)ՐNeK H^s]}8}S50~P*\(UGb)YŋH UNrWpsRX0vk(RˬJՏe~r! ¯!W/mdp5Z'bXw}IaC.f S A#*rn 1P0qxb8mi5hQJqBT O¹EP}eVz +&UXD9.2V)܇_R: k2%qL [Ԡcr8\w"ըeIi~ E4 h(M{a_ +zCAc:)|c_*-lPCHTD0D]Pz!(J#ds? TJQH}M חs)c)}0 ŦUĬ⽔~H F ^ 5W^]UHL T@KfW= ,*P}=QT ǂhXRQѣKBҋe欢DSK@C@!]T$_*STf rҬ'@SlΑaPY ) BMaYu +e} + 54i[*C'בs`}GRlG&hK(X!"HpP8a-L@nykyS5k@ϋTmilAPӎ հƲE -ڝWTVZِ/USUeRsfK0_&rA j-W} +mdƲh"T;RTuib7%BiX6SMr-䁠+ouo%t*HũJKbur]4<p* +DwKm#k *Ma RZ:-5R%Ob쟮*j~U-jA}j)5Xy[ * @(t+5ON%( 0Vebxk}@1VX[ ҕA0\nXmR: ʥ,֬ +8]tv_ ,P ,H] jjAo+KQ6ndͰxؒ& +s-1r\5[Ow7 +?%:7^r_EC_Bf(-xb&LAhv*G ]C f/QH媬Ҥj9t 5@W\C 5$mokk? l 5P/5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC .O?[ƿWC(:;g٧ˣLbِհ L934;ӧ#;35 +ׄӞ훣O٧ӴӉ;x_'oNoOά٫|rZN_l hή'$l'Zw_ǯWOCEL@ww)Mꋣt6BqoI:?mUȠ% szk*%mlM| +b<=ћ{-?+D=`{oz_g?ܓY= {,*|e遡}smmu8vi:Ax3j4[a# +& ǎqmPlx%v#,^ʂNbj:`rJw|( i;FZ ۂ@>rnCh.4 nRJ#/rh˃stvn3!oA-j5txV# Gm"hMPyAmBІ@o BAR-$] h%ۈl8)4BZ# jęjUD |R-CZcdPv^#pH],; gA@pRP6$#?+h:]v=drrsqk7@t.p烔\ PPl/đEM +8l6 =QMó^Ӎ(EKBDDSA<hZ (QԤE%!)ai;hA@ۑOظ 0t )`zK"phP(BAJ-q"X4s; kڠ؟K(^@Nt NdӍt+9z& %& +dC!m۩X~<+$ 8BTf`1zI?hӆ !r\#R7&I^0 96mq̡Cа#2 5Ո3?cA O$talR#夆aE^1 &|;̑0L$Nv:kH&q /dMж|JRRp%/#GBq~dC0E.|6| $\tΣEWOtGDaiҦbS0ȷ){QnC:HK.Rd +ig{05# î$m=0&i[~b[B g0'C`>? +la쀈 +HEcd%@9j-8La7DG1,YX꒙&;4a &dp)!97λa`]!?a+) i>Em5#jYA">'ٽZ%恐4%^eu,tV +ɲc +,zs +;[(]6N"jbE:у+Z}V5Ŋ0;8o+K,UG|Y* )tGI(9#c@%Aa$6GB (Bb~[> <ƒ:%&@[ t%[ERYF2:ȇy3+v3s Τ`p; 0<6r0)1\ ɐz@ِBvգ ]2ݐ :^_.n( xqK4řl[mFxA 0|%~"`$#'€KayB+$ R`c0՟mFG;etpѦCFH#6u*o d$),m>"0ȣU EFS M:=a[9(j^ "dk r%{2ψL=x#RP|t510c]G!d_\3YLvMH`=#E6/9iS=)VHz 6SIfO0Π#Ox]zt"#,I-$I)I؂"d^t6̞017-~æ`0!- aZ[SԳ%E~ q=<blt@feI[P?J68!ǚp#bKP&P R%΀zNp.NMdM̂t6VA &%;{M "(G\ t<-Q"X}(qJ 0-9&˜&,yH1`"?"0>nJ~RrGfH%#Sc1m%ݡAQ&?V0ȡpQj$J^&6qE#Z;t8 "+ XӷtqW*1v !pDEPrzb-rq'HM`v]  +,D&[%2ՙ=Szd$'%'Z`zj2 dMH…\V#8xd MX&n2x`Bqy +a& hg- p3xɁ,.; +<% -t,i%xT9p7|b:c>YVj9hhD +&-$dD&%V :I;p=:8IK왜".7L +ɹ"T =2o'`Ѓvj Mp!m@o&Ȏ' s?9$#+9e(m 2#1%.3%vNX-hvR\C/ a ldriNXN>?9R YƜ-\10擈\E:XXjaHr;,jYC&X'0.$-=ED +l˒A q"Zh\)ȨՌeBzIL3,&<۠ 8%5lJ|Y~ XcjK&ـ3_2iZ4hCW&.VK^x+4=׳G/9~"Q;=ڸ9}mˑRAУ0:Ȭ귋~!^N@C ul0 6n}q,<{Z3"ߙc=ǹ|zҮI8E`.ʐ*Npf%CG5$* S˜D(FHÈfF$!L"ܠ6S fb^&G L{QRhkrx / o. $mdv>əL9.j(K>EO`Y RIवP0 39MTAUqk4OHa`ep%N22&-M +ir9&G +a꒒} 6O2I4@Gs + al(#Ip"([F ڢ(IVOٟ{JdHdD4.IzڹGJRwL$KC"HMuKia@pH*'Qah!L⎘bCر$;:`;\`Id9dXi&)Y;$C$NB+1aڣKIO%05,q!fi1I`/٢>-˰g%DsZa* ɔvoӼGJ YR!L$ Yj'O\KI9c+< )Li_E `P:I'q6 0}g4= Pʌja2+_ 14dEWHp C>sɪg d1o̺ 0$ӨF^R#4Jf.٭&'N 2\;r Czh:5dGRɣo|I(Ġ`F(c$qZa.'ڮDA2ktdqLS].yRML/qa;& !C$DzIV!BQItG"MzB:ogmyjM+Q%D ȕjUa$n4=TIX K) Xi%KAuEaeEbm/f5\0zaݱh{m:b[Ut 3ά]ǭ $("'UsHץ_8y LDȋABVo %7Q )5}tk𵧪 Z>]X l*5,\r\!Y{>3WSÊ`n xdE,=a ϚߘM('/"DV啴odCJ^0n] ExnLGsog +{g~ė:ūwoȍ4Zcf575OaFgUi۴8n>^7M$,9 +BwC#{#>ll`E9&n%@xՄ`7$&fI})r\¼v"Q6{̧xzwkFIV`ډcq; jF/4!FFJ5I{`Đn9'Hq-|߾b{Ő` z AnlgkXL nLR#\d|M˴կ<<<951 ,&pApQ &:朹RgWK*` r1K%cbRAqFqYdwF]F#X5VN*eaA/Mn7{'m<&gh zv]|D/LM%jH `W?G>sZ,GNfAقIӐL;^MP{Hi D@h A|M\%Os˘v'YKlxfCQ;$!Bv UI(Zdrۛ8\9E&#7N1E'@v<Vhoo, ,{aLMqqVԄM,aŠi) +3!duK?>btPKԈˬ26hTwA[ƒn^%)x9\豺he`r[9Q ӳ7QAPP'"Z~@h(b$𫑭pxI C1UBbWUɳ!8S=+69>$!jQʜ/9Fx-2( +C&q1Zᒉ&S +.xO$% g(y knT2sGY_E"B2f +HMUHE?׋w12gNUgyzߢcOh,Y+mEJ<ğ%' 󅏏_)e[Ճ2V!2g+׋I}u(I4>h8BBKݢV2߄yR=[=׋qɮ_Er k#UeR0(:M8+x ,0`%!rɗ^XOcQN32h-Zyhֆ38..H&7uVCzc ǤHU,I ˊ5:ypa` ~dũZ3ƶ8+9sYNrwLpz{kt=z !^cyPE(+K5W_q7P,BR~#B (,@fүTp~͝Aݛe:mBx'-ҪKj$Rs٠m2ϩ wV1bvYLZHKod$4m5>daSua&ڂ"VA&x 23iSX/\!:Z +ȧ >A*TjuNDXoUijO#E_7f97_d/Y8S!zUk%vѝ[~SciHTXF6%nɈUŋlV@zk|"1rf͛[!40R] +*岈g` xR2jJqA*R dCZ%NmCq?[¬/7I|W. eɐ&!>{teCI[T8w6 ,ov_%b9Bɴ|Q`|UK#,`ʣb0ݥ!كNjjvЃZLq0b*1Uvr0|F. :"R…ZCqXO_L4P{`XwS^Hlր Z*I!N}Q`qA Tc +(h kYI RGyVqMy +ce*Lcպ`mhFl ^H|i>GlOCf%>DV|xTC- ~fҊb xYQ7.tg)&1؀n߾¯1Ћ=4/bJ$2lXOqH#~$*VP^p`uU[.8_j:ԴX@Ybǡ NT*djۘ n&8i44kgyR&GI5aPn4t"rTm3L|sabmUd/2x`wj +I9)8!TU [[h\G7/د%܊G!HopjAwk\90dSE-c @@2fp_/c#Zט?YU|Sb}E ORTv<(Q^7^wwy>cp,2W\wOѿ(KPH,Gm"=ll!ƻ&(HGalrd,me" XL "an/`i(tĩ& k*xC-ͤuOg]0`ƚb+W]*k0U wzdAt%0LMf@>"g @.߃]kJIh*:$ȃ&-)X䨐}rE ٵ~ T}Δ:d%,#[nnX2}tzR/YE(<|ϋM);X3 +dv˳3ϡёK[ K¨}G2vPmnPtßO@mj x|W̑܊56gbQjq?QHy+h~4?'/CjwVNzkLkgZS^^poxzٔ)IRiAI| +W&j?p3 +wC"a;ոH,%Tr&j27$)B#5]]u[^wEN''e"amE)}ѕ4E>,i)VI 2Biu\Y}vkpQMwBEnA`0wfjDZ0*rmN>ް D2SZv +$n‘ōF-ŦBv"){=i"i\-ď$Np8tW8C +'`'omУa9tg/Pq*YŰ)tŋo՜%2kTis&n:fifz/v 55YtS/r5U_B2L~J}bǻ{XslZ &:Q'h.n ʊƉl n5!Ozqbp,^V$µmkE,g&A1]q]b `(ƄEֽbIY4.t<u"pɦŅt7kdz-~CwwVi,[8;il,Wy4YF`gZ7؉;|L,ݜdi6_yIS,tTPbbpny}N..a +b52wjX@1@͈ +ٔ);ToRUo&~tSK"hC(vTޠ!̙gܦ1=@7?oȽwRҜCӘ7fY=gL"ѡ6]%IC'wi Ff)@Y(̾!1_dW’ 6;1ⶆڬ.cCd9E0@ml/e,T'5?b9uX[=a`݋#`o?I/*ڔFYR'ZHCQ$rs1e?`hv8jiQޚ|eZZzpttY+V0cZ/fp(]BW먿spe:TҗU4 I@H.IxByQvWVxi\V4=\$ߐ`LVqZ_̚AQn:_2BP]+bYw !'BxE,Zv9޺--CvG "W X&OU'P(@Sߏ8468jS2CDHrn sf !eS8iTs^~vsIie\ED+=bԬ3.kUQ0hr)D +0ouT@+zkQ`Ռ:]i)`sw1)!o6MoYXDÜ)\Æy{ xRTm?!/?'xq5d3+K*K UHp[RV0^6<y!%,\̢SsX<*q)JeVH Қ)/ŋs,KXmf>ܑJE~c)') Od->KC')f55veJEoV2$HSf)e$Ku>DVs3'eMR2Tݸm橒 +_l8ΡLjc9[w[x}jljzw) 2(j)%Ml^k؈f^ +xCQtjM 1WdXۚ,Ln!Dټ[a]\P;(r۫U|\uzq2Kr =Ut_Tojj.حZr0ݍ7)ST+\' 桓5B l$`Vg(6Ew? +YDME꜁}佸bBAAE tdNUT:o&B 'Y㲝gvNc//8~pu[JQT ܎6n LES.=(gדV]Z~|PnhTNMiIaTk坍⺑g6L^ca?FϠ滨DC+ߩ3,:ho~(\ +XO?խc^ggž?j*M^=J0kRdK$zv@{uUHO 0VVUca+iY$ stN@q!v/7M2FP?WT&8&j1V>mRdmcBJwsVq){QZ=wn( wSgT6\6>,}$QVj1}l<@ ҏ~peS7VZEޚ_̘ Z|.p~QkE12bU>E][F:9Őx{1`\'lBx־tW6;KU}dB2"[+HMVL3Nq(!eIOnbȗkqdSE,8M$U &YruUG.k! sj Dg^.H/*U=8 C5*ա4x!0waTYK~"1ԕbAj./pEэaljuxl[vq_rSK6y7U'd/.)/8?3υSmhmE7Z --avM%s_ݘVXxe!"2H!t=@a2yFw>XL^Wyh +=FX,+nWRP_h%FTg[$!v~Vk[>k o{f%~8G2IiMDf>e\^23j(]2$Hfa`*1==6^Z/ G(AlqRmb`ɚn"7OwWZhM}I2!L zpYj=mNÃ)G-ul4~ +qv{gSH=Ѫ).P ȁA/@OSּDZɲ*G(4lkfv7YY/c*UFyfqn8jݦHGْ([}q(kA6u2;0u.8<skPܦ%ǢfRy^Vzp&7%3N(3KGk;}4#fp̄Kl4݂<2b2Me + +(ܧPuO5Gh'5bh#V,U< a L TTFpg^$JB G†YFd y5ⱷjlW3Zr#NO_-a%& ?{0Qɒ2HPT3uRS,.>n`H4t˰ u϶6Ofka2vuJ ;[Xۊ㞇i2@t-ѸВ5J]e0;lī"$)]9Q'{bJ /˭ | `B5?(P%Tf}fdaB7J8]fl@߽͇ſiô)|B!R;(3?VfDOvckcOC%3͸!)+Q.;~OKJ +FMX$-e1UL[uT+ m5&+"Yݦ2PA"۰|-S\vajΧ֟Of._aYfflFJbN_3_l{#kjwv CEng1zaL&5p+uz*bY9HhKjm >.>Kph*FF`BϤj*ަ`TA YJUE*Ljv7iV, +IC~F`ROM5!8CU׵J=h +rϨOn̠0Y96el]5Vu +`P|)}5P*J趭` +hkz>-6WL@͍Sww__Oc$Y##ljP_w_3ДΔwuӂ9t AV x`=J@Ɖ!3j]*ۡaUE!az'P|f(&j9x.U6ui֤_OHۿEx MrquB3*^Պlrov jTIS#DkԊhn/GrK& ^g 8#TQsTZ+SH=j**fTWq}U3%Qp'K$+XSb w[FR_!M#bDʼnUe\|{< ̝rǜM`hDk?'NbBe0^ ùgmXX@L|Pn +jn o6yebvaLi6\, +\p'3T$$Ihϑq,ş&ĢxM+UԋYKE!G+`̓XCmW{%$ /jWr%x(p'Ff A4f0_Yg0ep??n¦G4 uMIe"bIDJko{:J"5a. +)v0f@ 6Y+O{LU|C83]$dA!;bR !<)mޚYh:!,}sL`/Qg7 [pkWqq&BHʓ(D%V(%E2Bgk_I\Nn9|y囟ſ}^?/u/|/_f>7~_oO7Tyo߿o~O?}ǟ{O?~ۙϟ^?و x?G/v_fO#_/߿ÿϯ_K/|/?'Ex}>*;;I'#.dϟtM#Ѧ.%#D-9| T˙50M0>cA=N2Ҁf>$k)on߃ZB e{ngay00yQ@1Ze286,Ɯ 45MIz%X0ZcjT襜FM(Zfev4" x= <J3 xt#HjQ 9WDi}8`ܓ`?ƁUq`RB}E}. :Ճzr=2LkՅzFZ7>A~Ư#YTXln~hp/5ЌLך ƪZB$mڷoL`nL);4XfX/,R}*8Ѱ0z m1X-K9\X.I!x͍HL +X'pP˩Hu I5,.eL\zd(? y,Y-#nf1ߕWjy-$3܈C*4?fXgn 4Jsx7g ڐ,R*䠛_'4;<= t$O1-8ԟ'f 'VRd5n5Dr#% OHKŒDl1gUM6D7l"10`YV :Ri*,Iqi@ȝ%ycj)% NF STHbpc`^f{?”|ݢb|_8YNU3ӏ4{8BJr_ɶyL@9$i`+@%k^^BV?nvOYأ\co'>K +t8"xq l:Dy۠PZp7{40uֱH.5}Wmq"`m7lR ʖWT)O?xUΟ=CGE(zk ~wOr'riM~DxY__e^2@.+o4R;+}7i2TźhB,#E ,ɖ-*} TxM,n GnQVQ4LUL ݯq:x +ӂ9zf9L䲁hiA3Z/S?(sàvGRP?̟t{i Q0 e@'V)*&t +"$-*I!DRHPE**%àPh!I+-;qj`=] !!O;`sD`kU8_홿#HTh".%&*#B.aCdls""q\H֙u`+*3ح^ 0*UB8l|mN'Mh{q{ABfO 3]c 5ՠ\/h2L=AMSȺemq΁jv&%{{ BIkx3:8eP_&%-Ὀ' j.du,7HGj(~Lļ[:cn`fna];RGz̧yľem]Dha1 +lfG :a!Z@pM<[.`6Hq)|2ti@)j̧z϶"%OSB(ګY(bqFe9 ~~ _4}T@vR0#YA?1Kٲn/cمWv*LMQ> +vA쥇/(Yd_> Ѫ ;xnhI/`zY}.}@*76k{2p{02}i(Z32˟*Cg?uhd}VsÑI L*s. ѻ'Ñ89K >nҝulrߏ@DW<-;셑O6rz#m[:NjF֏uӵ7%MiYZɪե W*69hM>=zҟTQ!}鑔0b7!1nQ;\,b;Ӊxb3#62Π*x5ة#a%3<JAC6[l@nڐo9ٴo7hun٦W6eww;3߅@mio` :QKiYnbgI1{0go󠅚 hW4W(ZQޝϳ@|.pCGc1i@¶|΅5aPK*!AoB~!9.-]/۳d.!-/'Nn /Iml!2aAb^=U9! tb-X4OE6`80θuW-IN;E~Nhy9+'P4FS*P4WH20Q&('4FFvϙ]ڞ:~Ltch"L*eG64閑ZvW/т#75^ܔh?n[{;P%z0ZRkK4i@;+d&xz8 +VIŽzV:^hzI"hŎ1|ضf@~4v/[l&I}]ar ؼPxXM< _Cu]vYϸ \ P~,eӫlvO.6|h +XURbe)\ߘzO\ޘh'xi7C(o{*E!b9׫jG dIAm(u<UtO=;#֡]5Vyf^[S#f_6Lp]!o!e|JG!6"Y;*]Cx\d36Lc `0Uu1Jj=%@JAɾ2~7xVu@8th02"Ibf/Jvm0QِediBh2>Cu +n2k +܊)6C Whe֓ŚQ<[b6,&8%͸MM,({+@&D@mM*.ïm92k[7iV},?YJph3]-f,GV\m $#:ӫ}垅dŗ~J~\ RIq em {*~5nC(Qd@=ueRx?gSE%s+v1 ,^Li9_qBUW޾nu~Z =أ%TǵD:Uvޮ] +yJy88eZ@}ZwyuГ5 Gksɡ˼s" TG0?Ecm&O@^I&=ȹD<ű*JۄLBY@0Ű`c}46xІ%qJ +˸ 6$E7i>MsTwT].,| R[6-f "`3) <̷53fly&J' nqqm֐ihFVsGC˂LYg3C$.,`¶$ +)k1Ե-Cii|sOa&k'OX/Ȏ\v(`j\-kwAXUCєO&s۞ $W[XSw֚Ѳ8"n 9"A<u˰[ʲ¡fbK篷3S!Km}80Iby`8i'{WF{$wՃ_& 鎷adoC̯jRLC7cu<~4:Zw/]S68PewCBH8y(&N֋囸]{.T{pEer'kYU&v|7V'`rJv3a]ڤ鷬~Xdګ'#d|feڕWt50! _գrwԇL9wyMQ59j3Lg0ձD>,Ԛ_bvXvʃo!2fU!mG/,I\4MzH'}38LS1#X Di57MU.`)A4#ˆz)fHo99]]"h1LVSW'00>< &B~&15zC:J~<̮n},s-/tp_isjMxyuML3tBX?Lwj=8:c.EQd2Sv7_ ǎaQ-c KP‡_@(\$!gjzr,@V-iRHԤ%/m),*B&s"HStČHU)xXͼś?`bVm֘N,=O@22 ~4]MKN8:;aG`6{o{i\C)D'j:ޣo="X}E,Gm=7X}czA]"j`%}"5|%E=[:-"X`l [usM"`);4!Kh#%n?kC%E@(^k@fn͋w_{č +cmjr49+7n\-kia$3TQ* F\:MĝiN+Q12#8 +QttLTPl0ʙ +&śSHbf'-g1p1XQHQIdLx&ILLٹLԞRicI)"XRj|&&U3R"O>UUsJl+zWf aVj6֔ok" il$ߝZpS/XIdv4MdVnLҵXԼN])N!';rDZ̛唽}Esbac<'d89!L'ҒM'Չzr:qH3݉I?;ezGY-Mm2Ä"IYHnfFS{R9OT΍"^W.Fa:j$BKZq@8D‘4-~,M3?WBƀ cBg H/pI~ +&&kv ƃ"fY;@#Ng/ xxEVd(H%AXyoJ%Pl&M)'DI%XuB55( DQޤM?֘4f&ЙiQQ}pN$K״\7F\>(vKK>}NOGV49I[;L҂ Et-I-c윳D-)8ʼn*( 5RӜϝt^*f7 28=;vh5ْ5E5i;?MvԱ7~u}F$elQ/S1fnJ KƊPR+#8Lz/ YMU#X䠵?Q$j1Q& AjP)4 !u *bB bk)Vn1ke6WX7Z+uKl#k$և V8Zp$җ\0Y֥ӸC:;?N_:;jʥ=m4+mʴ$WJLUkVOsD0nA89CtY%C!$'VQT8 g)IJUjyvX>;%*`& Ƃ+ZM$=>K-NVJ+{EηUΏit~gEm|P!hܸoA$Ek}YᮌߡvWHJ0=ڽ.+U52J55r*\OqFuGR &']aEcdTOy؆t[Vs݃+ +KiN,x V-3Y97Պ{vjıCxmâo wyspXkτs%$#fm-kߦf,VnarWӝ-Ciݰ0۬%6E{o|ӊT:} +P +©]2ښbCn `(VD56[ʵrr+>-Cbd +.5eТcI)d~9žJJZb&L :M. p\MV 2aN,̈́Jfq$G|re-m BәByU+v]g.^B#=zaqT:'{.#^J. +4iSr" F?Ã?X6F2;5{%0k vlqw:wiPƁŲۄfeIt6a蝗@aQQWݑQ4@Ny*J2m昬$?L@׻BSVV%$ḓ?wAy &'C])tIt V%'*OMaR:IGN<5LF|?yNjN+vGi',, tL)WP0^t2Y]} P薭n&`TMIoV~ՃIJ7`3ymIcJ辸IE7eI5?1 ਸ਼`5߀r"(‹0 uB$Pm\'$ࡖ:"w%8n7&A竾$G:0` +IJ.LP&d&$211}_+0 +(1nO-P}M^*؇׆XA8ePA jkpPOeNP2 nA(l0$p%`1xd0 ]x>$z8ca0m]s~ZJtѹLlЀ +ҀJ*1*zpI Z[H!O ;BwH]$;L XQ7zqb%,M'^6Hb" \B$F"s[?d=n<@mגr`:"sh"p)HGӐ16)?8δ(J^e@$.wD:]PGJ7߉H "e!^ V^"o"d% 8, VG$t c"эý':L1L@b[zXn<ֵ @%D-LX֍GDj\xx.- &3#w?d{9n߀G@ްޮqN|f?,C~cɴa~̻ߞۿ?>c~o~?~ߟ_>}?}z폟? o9Ȧ +endstream endobj 6 0 obj <> endobj 18 0 obj [/View/Design] endobj 19 0 obj <>>> endobj 30 0 obj [29 0 R] endobj 48 0 obj <> endobj xref +0 49 +0000000004 65535 f 0000000016 00000 n -0000000144 00000 n -0000053959 00000 n +0000000159 00000 n +0000053548 00000 n 0000000000 00000 f -0000092071 00000 n -0000091885 00000 n -0000211933 00000 n -0000054010 00000 n -0000054430 00000 n -0000109010 00000 n -0000106327 00000 n -0000091772 00000 n -0000055498 00000 n -0000054632 00000 n -0000054937 00000 n -0000054985 00000 n -0000091709 00000 n -0000091955 00000 n -0000091986 00000 n -0000092314 00000 n -0000092571 00000 n -0000106362 00000 n -0000109084 00000 n -0000109280 00000 n -0000110282 00000 n -0000120517 00000 n -0000186105 00000 n -0000211956 00000 n -trailer <<299B326F1F494A60851087D01B26F9C5>]>> startxref 212135 %%EOF \ No newline at end of file +0000000000 00000 f +0000212530 00000 n +0000000000 00000 f +0000053599 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000212600 00000 n +0000212631 00000 n +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000000000 00000 f +0000097203 00000 n +0000212716 00000 n +0000053985 00000 n +0000100074 00000 n +0000097390 00000 n +0000097090 00000 n +0000060815 00000 n +0000059958 00000 n +0000060253 00000 n +0000060301 00000 n +0000097027 00000 n +0000097274 00000 n +0000097305 00000 n +0000097425 00000 n +0000100148 00000 n +0000100344 00000 n +0000101437 00000 n +0000111871 00000 n +0000177460 00000 n +0000212741 00000 n +trailer +<]>> +startxref +212920 +%%EOF From f409d65d1536848769d2e01aecd17593b90c1ed9 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 15 Apr 2014 19:01:41 -0400 Subject: [PATCH 079/545] 12,000,000 --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index d882e7a3..52168a63 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -38,7 +38,7 @@ Requests takes all of the work out of Python HTTP/1.1 — making your integrati Testimonials ------------ -Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku, PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington Post, Twitter, SoundCloud, Kippt, Readability, and Federal US Institutions use Requests internally. It has been downloaded over 8,000,000 times from PyPI. +Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku, PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington Post, Twitter, SoundCloud, Kippt, Readability, and Federal US Institutions use Requests internally. It has been downloaded over 12,000,000 times from PyPI. **Armin Ronacher** Requests is the perfect example how beautiful an API can be with the From 21010ee25a7803f2283487cbc8e4abc87602f259 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 15 Apr 2014 19:07:41 -0400 Subject: [PATCH 080/545] core contributors --- AUTHORS.rst | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index b6cf9d0c..7fa0ef17 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -4,7 +4,14 @@ various contributors: Development Lead ```````````````` -- Kenneth Reitz +- Kenneth Reitz @kennethreitz + +Core Contributors +````````````````` + +- Cory Benfield @lukasa +- Ian Cordasco @sigmavirus42 + Urllib3 From b8ba5c2bcb861b07db6806da60fa96646743cfbc Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 15 Apr 2014 19:11:58 -0400 Subject: [PATCH 081/545] github urls --- AUTHORS.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 7fa0ef17..1d69ee5d 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -4,13 +4,13 @@ various contributors: Development Lead ```````````````` -- Kenneth Reitz @kennethreitz +- Kenneth Reitz `@kennethreitz `_ Core Contributors ````````````````` -- Cory Benfield @lukasa -- Ian Cordasco @sigmavirus42 +- Cory Benfield `@lukasa `_ +- Ian Cordasco `@sigmavirus42 `_ From df34ed7a003e0029b24258c3eed5ee5f954d1e4b Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 15 Apr 2014 19:15:39 -0400 Subject: [PATCH 082/545] @sigmavirus42 --- AUTHORS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 1d69ee5d..900fb4d1 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -10,7 +10,7 @@ Core Contributors ````````````````` - Cory Benfield `@lukasa `_ -- Ian Cordasco `@sigmavirus42 `_ +- Ian Cordasco `@sigmavirus42 `_ From b4354fc66f1ed3b39b1752c3d1a0ccf8066509bf Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 16 Apr 2014 10:58:30 -0400 Subject: [PATCH 083/545] Revert "Font layer convert to curves" This reverts commit af21ec6165c40c0c12f8dccc8379eb97de53da20. --- ext/requests-logo.ai | 1222 ++++++++++++++++++------------------------ 1 file changed, 515 insertions(+), 707 deletions(-) diff --git a/ext/requests-logo.ai b/ext/requests-logo.ai index 8f74083a..c8bedb8d 100644 --- a/ext/requests-logo.ai +++ b/ext/requests-logo.ai @@ -1,5 +1,5 @@ %PDF-1.5 % -1 0 obj <>/OCGs[6 0 R 29 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream +1 0 obj <>/OCGs[6 0 R]>>/Pages 3 0 R/Type/Catalog>> endobj 2 0 obj <>stream @@ -17,15 +17,15 @@ xmlns:xmpGImg="http://ns.adobe.com/xap/1.0/g/img/"> Adobe Illustrator CS5.1 2011-06-23T03:13:46-04:00 - 2014-04-12T20:48:38+03:00 - 2014-04-12T20:48:38+03:00 + 2011-06-23T03:13:46-04:00 + 2011-06-23T03:13:46-04:00 256 - 200 + 196 JPEG - /9j/4AAQSkZJRgABAgEASABIAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABAASAAAAAEA AQBIAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAyAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYqoX1/Y2FrJeX1xFaWkKlprid1jjRR1LOxCgfPFXhvn3/nL/wDL/QjJa+XI ZPMl8tV9SMmCzVh4zOpZ/wDYIQf5sVeGeYf+ctfzc1aK9t7e4tdKt7tQkf1OErLAoJJ9OV2duTDZ m/4HicVeP3d5f6hdSXN3PLd3UlWlmlZpJGpuSzMSTirL7X82vNuh6tLc+TdU1HQtMJUwaY92byNa KAeSyIsT1ap/u9q0xV6XoP8Azmf+Y9jEIdX07T9WK0HrcHtpie/L02Mf3RjFX1t5I826d5v8p6Z5 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 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYqhNU1fStJsZL/VbyGwsoRWW5uZFijX5u5AxV4N58/5zH8k6Q0lr5Vs5fMF 2tV+tPW2tAfEFlMslD4IAezYq8O8y/8AOVH5va7ZXll9fh0y3vGFDp8RhliQVrHFNyaQBqipry22 I3qq8pSea4vfrV16t2eQluW5H1GUEciZGD0J/mIOKsx0385vP2g6tPc+VtZvtK01pWe00ma5a+gh jJqsfG4Uo/EfDy4VxV6h5d/5zU882phi13SLHUoVZRNPAJLadkqOR+08fKnSigYq+wNJ1Sy1bSrL VbF/Vsr+CK6tZKU5RTIJEanurDFUVirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir sVdirsVdirTukaNJIwREBZ3Y0AA3JJOKvnz81f8AnLny1oDTaX5Mjj17VVqjX7E/UIm/yWUhp/8A YEL/AJR6Yq+UfOn5g+cfOmoG+8yanNfSAkwwseMEQPaKJaIg+Q+eKsdxVm/lyDy5p/l2HzPpfmga Z550i4acaVdW7tFLENo/q0ipMjuejJKApB3oBUqp7odt5i1/ynrfmKbWjodlrOs2un6jDYW0qRXD fVZzwW10+MKxIl48W4qxbc1OKpZN5e1H8s7yDUvMGjxXl5fJcJpWmaiPhSKnpNcXcEb1R+L/ALuI vVW+I9F5Kp55q02f8wvJ3+ONP0DSvLFlos9tpLLZPHFHcfWH4q0oDKkTQMRyZkXksgPRcVekf84q fmxrD+bpvJGqyGLTGsok021mcn0LqyjigdI+e6icKzsnQP0G5xV9X4q7FXYq7FXYq7FXYq7FXYq7 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 4+TNW8s3EvoJqcBiWcDl6cikPG/HvxkVTTFXx95N0/8A5yO/J7Xr+00Xy1d38NxQXMKWc9/YT8CR HKsltShpX9tTQ/EMVfTn5d3X5x6v5Z1W/wDO9tYaZqV7EU0bSLZHjMBVHXncO0k5/eMVPGp4ge9A q8s/KS4/Of8AKLTZ/L3mLyVe655dWVri1udHaK7niMjASBIkesisfiCtxYGvY7Ks+uvzp836lbSW /lT8tPMb6swpCdctk0y0Vm2DPK8rcgOpUU+Y64q78mvyYvPK2p6n5x813MWpeeNdeSS7miH7m2WZ ubxxEgVLH7TUAp8I23Kr1rFXYq7FXYq7FXYq7FXYq7FXYq7FXYq+Qf8AnN8j/FHlkd/qU+3/AD1G Kvpz8t5DJ+XflaRur6RYMadKm2jOKsixV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2KuxV2Ku xV2KuxV2KuxV2KuxV2KuxV2KuxVJvN3nHy55Q0WTW/MV39R0yJ0je49OWajSHio4QrI+59sVfFX/ ADlB+ZnlTz35v0y48s3LXmn6fZei920ckIeV5GdgqSqj0UEblRvir6K/I387fy81ry35S8nwamw8 0x6dDZvpzwXFTJY2370+t6fo0KQM4+Ppt12xV7JirsVdirsVdirsVdirsVdirsVdirsVdirsVdir sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirs VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsV dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVd irsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdi rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir sVdirsVdirsVdirsVdirsVdirsVf/9k= @@ -38,7 +38,7 @@ proof:pdf uuid:65E6390686CF11DBA6E2D887CEACB407 xmp.did:03801174072068118C14DE8EAF56EBAC - uuid:d7a1c904-8296-4139-89d6-900760967c29 + uuid:c5688439-e292-b24c-adf4-afab40481673 uuid:52d3fbd8-d8d7-2543-8e0c-81d9ea137ecb xmp.did:8CF5709C0E20681188C6A12CE4B46A4D @@ -61,7 +61,7 @@ EmbedByReference - page3f.gif + /Users/kreitz/Library/Caches/TemporaryItems/page3f.gif @@ -75,6 +75,7 @@ 1 False @@ -84,6 +85,19 @@ 300.000000 Pixels + + + + Palatino-Roman + Palatino + Regular + TrueType + 3.8 + False + Palatino + + + Cyan @@ -95,7 +109,7 @@ - Группа образцов по умолчанию + Default Swatch Group 0 @@ -604,48 +618,19 @@ - -endstream endobj 3 0 obj <> endobj 8 0 obj <>/Resources<>/ExtGState<>/Properties<>/XObject<>>>/Thumb 36 0 R/TrimBox[0.0 0.0 300.0 300.0]/Type/Page>> endobj 31 0 obj <>stream -HlWK% ܿS lQDi;=WaxYT1* - -2%`۟?{yGyz/{oky~~;F>~>=~v&4 5zr|j'kd^sǣ\Я98 w!8uU~yO#bhֳ^ˉ[X["Wk:7~ygb=cv=;,Å+Ch|53ڿ0|{=Fk. ;/6 72Dv9߸G+z=Rk^k_ PuM_>9/?~ LI&Be\F#9}LG} (qƴÙyo\^><#~ Eθჽ>wARwZJ';<% 24 5 -8ױuJ -TochN"6@5o_j'omq ^Dml?*mKR|2e'GmDZmfA^Gkm(Ц-U&BllIu Zr?:E+R˚{PŢhq$bᏫ/@:l q G4Dmo_Q_*IƧT<X7,䑱C21{& &Doz>u]mv *#vR㸁SpHƆ y -gI*y{Vngyiz7Z.ngY8j @LGqF.gY[Y/eN*qTI?R_R} wk@Nn|ٍk9N[#t)d{08]TH6NOle82,T@__yRٝO*a}iWpyQ=dPEC(sҐMs]|VܰBcC#D>-0{N+A@cCgWD A'L/*PI`_YHtJΈ}r1Yf0{ꝗf*_eDQJegKTIʺsiuY(-Em1E"=uH]C"eCxTul5bOi[Jۘ^-h6麐؁~T\Ll bЃޑM&u#+'Qeݖdci8ylWm0G+ k~Qz.^u+(| An+/AŮ@mD? U&_3=B\ND.TۍݫS.|4yFHTMxl~8E2]x{y\RQʢ9r^&SMh?JNH; 0/JG`p?F -Y=!ZW@ -/0%=Oq@'[=Q Ux*XqEa1 -]i܇tVDArfsƩwD@V>l F>Px߯ȗXNqfOe? %/vrNp#;3ECn$N4!:! }cH+nO9#Wlaȶq!RMJs`v<=gdhe(͠Uҵ{3H\7<9D6O5 -Ѽܔ7D%l>>`M;E/)wcv+и=DKUpd4)Z&TK*T7n DAyb ~P#rc`ˇ9w]KI;R6 3}냰ڢ5ESje|pLx6;\^co#%?J+n.ҊΚ7@Rtk   -r.؂@ʸs9's^T6ǜ:77xTPEA*PԳQZ|1XXAc5l†?"eIqlo[u -6>CF9A::ZOZfM&O=]dyyShB=sM_vj!qS1F]cmI_+[\ty/\!/Qb+ "`bBh#vYğ66N%7F/eйW 'f=EdH&P |HVH0cw?$AaF8(?厼aB 8ZRĴP@UE?gTmS'Aj_>_ep3kREV -@ė'GH!f\z*6: dc7w:*jǃQCr!8o]?#*_U8GTMpF3[v&# -[: YxXʻ *e<ۊ@[9r;}J{߯gk j cvi=7緯p0j߫NHsփ(D.t_hN[)H)#lV¶{Ũf:F{(AOߤil}oRϣeU f8Gȯ* -Alx77.CwOvmXrR›*MOn!D 6\URnDi]_ C U dF>ӓSAAFbY 9/  ?fvQW/' 3qZT>":nϭZ7*IY7d8o7&AniP9 Cƌi+nF3{$o}=_ RF=z#PBS$\^ӋmVÊʘВ<[:ik,DŽ.>WB9ij(-#R]=nk :VX:/E!a¬w2k/CF: 8"PhQ԰ҔZzګ*f̨W3":kx;4b>,*qo1t ()8}+Co( -,! VQо>43 'O5o|ޝ J(PAJ(!S }>o Ŕ..԰d%⚮T07Ѷ[0*P?M8}LT t>l/iO S~}cؔʯPr;t1'2i>mД).'e=qܞk'rXV'lvHТ#oO@K}zb9%5%uByING?LixBfHq ɮi6gξ#n0A͡w[Rf'flCqQn`,X2w){$O 7KİgsԢhLҠ龬wm /ń!zvA5YiZIgӃuMA\,vAf1m -_U\Qh#3l;[ZZB;n5;k:0!({S,-DL(cM^޶د5T_XqU{]<6Qd1 -\եa{3r!J'nadc\sP=hsÌb^ įhFXqu1[`MpX̮`m-GeL)L@=g8Oki6H=\tJ+mbR5mPÀָ.Gu}cR-_t]:4UwCm;+UR!q]*;^2OL#vV>stream -8;Z\q;%A+!$q#7=$hcV.l9`ZPS9u6E(^N7\^]E>LSS93^as7EAa(2NL`)*CAX\EK\ -ds1eSk?>tr4rZk>I"K/X2]RcN%\":Nqf"KFD@cScViE`-.gkRM5ZK=ung]?c"kSh8 -JmQ)`EpV,1*!62V\,gm~> -endstream endobj 37 0 obj [/Indexed/DeviceRGB 255 38 0 R] endobj 38 0 obj <>stream + endstream endobj 3 0 obj <> endobj 8 0 obj <>/Resources<>/ExtGState<>/Font<>/ProcSet[/PDF/Text]/Properties<>/XObject<>>>/Thumb 14 0 R/TrimBox[0.0 0.0 300.0 300.0]/Type/Page>> endobj 9 0 obj <>stream +HDM +@9EpM*R}([+"!!|d{ue 3IxMNXѠP06Q&1*t 83r[xɩf8bzP)z|-#r endstream endobj 14 0 obj <>stream +8;Z\rCD).?#_]nk"h@Aa_=@jgoOlX5-.:uQ+JS=Sl +;kM(ghg@C^)^2L>5mSkncPa`A+Ba\_EQ/$BC2ko2pi29ao4WBJ"o1`?gk]3!%'UIr +<8EVlO?ND?f>%=i<0ZCoL9:OWOkO#@~> endstream endobj 15 0 obj [/Indexed/DeviceRGB 255 16 0 R] endobj 16 0 obj <>stream 8;X]O>EqN@%''O_@%e@?J;%+8(9e>X=MR6S?i^YgA3=].HDXF.R$lIL@"pJ+EP(%0 b]6ajmNZn*!='OQZeQ^Y*,=]?C.B+\Ulg9dhD*"iC[;*=3`oP1[!S^)?1)IZ4dup` E1r!/,*0[*9.aFIR2&b-C#soRZ7Dl%MLY\.?d>Mn 6%Q2oYfNRF$$+ON<+]RUJmC0InDZ4OTs0S!saG>GGKUlQ*Q?45:CI&4J'_2j$XKrcYp0n+Xl_nU*O( -l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> -endstream endobj 35 0 obj <>>>/Subtype/Form>>stream +l[$6Nn+Z_Nq0]s7hs]`XX1nZ8&94a\~> endstream endobj 13 0 obj <>>>/Subtype/Form>>stream 0 g /GS0 gs q 1 0 0 1 221.2627 230.3779 cm @@ -1733,8 +1718,78 @@ q 1 0 0 1 224.9375 222.293 cm -1.672 1.738 0.033 2.121 0 0 c f* Q - -endstream endobj 39 0 obj <> endobj 34 0 obj <> endobj 29 0 obj <> endobj 40 0 obj [/View/Design] endobj 41 0 obj <>>> endobj 33 0 obj [/ICCBased 42 0 R] endobj 42 0 obj <>stream + endstream endobj 17 0 obj <> endobj 12 0 obj <> endobj 6 0 obj <> endobj 18 0 obj [/View/Design] endobj 19 0 obj <>>> endobj 5 0 obj <> endobj 20 0 obj <> endobj 21 0 obj <>stream +HtT!a2@@@A +JE @Ԁ+-HS@AқIPBC zk[Vfֽs{Ϸ} /ANI1ɣ?}w|D+ŕ>yu->d{sr˵J"$΢U@8 ǩ؝-{<oas}eKlD|RJ'>nG{tiPk 84)&59g͐PY;=&).kpF^ ϧl:٨PoPtksqcO} O59lqls!x[;;І N<m@d]@< +gs#w)rۑȉǯxu~UQSRVhk>.}3NuSکTuj9uN#39o;6틜**t]\\fF9Q9QF t.tuuwpǕq^ -η'/(R9Zkh>ANjqs +Yŝr΃NMM WN3UUrE[U3:LTq&X8]{{{ oᏽ0cz=<.N;'ɓ1*<6BVgݼ ~R'86جi|5shfjf|3e$3VLdLOɘ1!{03}+ M?_W9s~v.x9uuMoAȾ 1 C0xbbۘI?a &c86!?`*<~/|[ m؁]fOߡ33|w08H@WtC"yD +z|TAoE*@ z p): b63av`(sK0s?<0c~ #Xw~eI_nQt8K$-c)E,ò,A, +|/~Vbe>/APVìQ|5Y6c4|XO7O [[ ۲v` ;0q.8o8$OS,Yȟ3+/7KkMIC)ʡ +Wd +)Bu.br)JnW 4Riݭ2*rG*3UReƳ+ q Gp_ 9kFIeoa_.V]='UOV5T#5VL35W 4۴VU;3uT'*NEuSݼgWz^PkEA!ae.񚠉^d-_zSS5M5C35K5Gsm-"-;zWKTkVjjZ3W}ڤ̹U̞;Cw?ڭ=ګ}گC:#:j ]1y%V`%>{XH.4VjEBJ7deu)WKAf,Ba9FEmQQ;PwUH +7KZ+ҸePpEyT@E܇Q ATCQ5jEmcx@]<'Q*Sx M-mϠb,~bE:Ly9&,sLYTK3L?s.|E3byopFryn&_+f\e LfΩfafe]܀yX`]d|,2HtjZX Fwfl1n30ofUG!3u ґaYwOs pZEAn+ aY$NM$|g69uHȟElꛍgpK" Y󄛓p* '!aUJ77pH^On ave0޸Yz lAONbHf$H(@Cur WI` ƮE !D{Kd2ü̏ B 1"@ H IR,͒>A]5QUW{6d[db nB6%& "cS- NKe*Bi:PqZhA"T25S[I^軻{{=;߹k!"h=TD,&h +M?*n{ hZZMϡHa5 hU&ja'i6͡VK]]cL^\rb;^11nXz;{\w4  א뎞uQY8;5e?'UY'0;5Xن6g#V gG$jֳO|&g6\4mh#GjmVl\Cjuc h܋Ȼ:)(4 +h.~Z&1Na lpr ; oS25S~Fb, ͏ܞ!S~.!:it~GG}; ]#! R^nL~ÖeSmwnz`[W f,6sys"Ԭ*FԞ-~i{`Xg +llyF%zv>׻ +}F}P2C5|11_Ϸ?Q01BE.EqVjld'Suy[aCG- Kbk}Hs.4~\^NT# ixP(i9=B|*8W fOP%<.?cz1VXVʳ'u h?je'|!5TuG6>1>]-$O8k~:,F +x^*)+Ej^&z 6"S~-Sih<Ê' + x5=f(WVȌ4gC1|XJrRb&KꨪFu)W1o6ھ$*DCut5jC5\m&)j9+Ԑ5Z"x_ԚUziKVg:fΠST K:hUf%27>f YF%:<uVն3sfHk^1jj<T5OkיV(3*Vu'[a”9]5K8 +-u*ܽWzj*-)RӦ<۽v5Lga +ljXzñu =ѧOҊ5+Ypfl09}&5+Qa*,kZ8):ZQi饐lA5FxŘH-@<NN1BQL1Lx`-K-U#{~+UioNfyb۶KT\rdG{=xk_UhM>K +Ft觝Bi5;d'C_3pLmAgN-"uM5Z^;'RgRoJ]j>֜ik8XЪ.! ȳ̠/-TZ'"_oQ{Q1hr qfa& Wd>rxj&1 L@)pa. DmT$ u֪ɜ֛^G +VcƄHmϋ%U=5Bco`kXRN+/h%J.{'ke[8Ld1RyaNǥjF]w^%r6_B/;~FX~;Nx~11ۑꉚ %J.X/ئS[[iŽr##c:yb2{76E[GO)≠b\y3g, + 92}36hB*R9SlEYPc)Hk,`R~9Md',@Y&4$ ՙ3$M Wu s/=2-AcV =w39Y(aVLО#!*/VO|b/΢Ecܢ'qrB!!B &(< =,Xs*(-fnL,MXY~ +'g]ut ++4=0kRA[[ &!"HD2˦F7Y+НkCWZ(4(S <۫"!h5m'w?Yoث]SȏoW!˙a!"jݨC[oa {;of601Th!FYm*VJ[ + j-,Ҫ)߹Wð+/vr9޽瞏9rs0FbtsAp4;cl>E.S"Ċk3vUq 46 Wb+!o (]r.sԍ'| M~E 0ot|6Ls}> ~ +=KU5X_1 \CdO/wO407j,Z: +Vya2O#~@s~şC֩ &o*!}!F 5X smtcW2ϕT,+5c|t/ +\ _UƂw+۫Q{OGqR=s 1˥/P{otcIUQ^QOܗ7u|#4 cd1Pg7Ac\51?U5zyX1uc]:@w9l8ɾ<6sӹe~<,c W͓GOp-F GToW>T[|c0n^q_1.5۩c H%|,CnhsxgMOg1spUTuC+80߭I9syr]  q\6r?źPK=_wh/O6uV5Wg}k i:pyzno)?P{zgV]?岼/^LoG՜7z@q ahC7_Y)'̿r9POEw4uO9P-黷Jb9@?y`5J?_Lr:.ss6 ^yYfؗHk0\:{|pR6ILkO88|o_?U"-@?0i( :6 +Wj +W Q҂Xy`F0g ]AZz>?U;5Xr\pӂho tOW" S6N_RF}m4*~g0Ms0kt( \EQ45^0n =C5(NvUp@y[phck4L+8J6Ep]}W 􄴤0M2VzzPm`!G;)~i8tIpip KafZeǂ2+=kUpF8}#WEG +Q!x`pZA'&JO`|Z0b\EW*|9s&x\0SA0| MazshWP\08Uf CO F^\qjb8ʞӂ5}tYBpUGx1QEx($Ix*p.k  +G1p`8H62G0FV ӍQtzl[EUF^H*,rxrͭ %okN:u^7iք7iOgk t"ҩLO$t51fƴX<#adTz% }ÎjIuuN)I2q+̘4=ݤ,T?k79lF#J2CiodYdT̰cEf&R#~3تtfٯ99Haw oQN,z5FY6h^` ¢VGDlf +k4l3jl6SP`gYjy"؜i^R%wTܮe$e-lY3繰HM!<f+-lϳt6t%̥# ,bxaas[aI'Le(% nvLoFD&i;mFߘ+zhǽ7xyl1S%[8N{Gg +[7Y7&\;\azӆ~d/2Rlgl8yrbM(XdP)1Č~(E3pRO}%5moJ#H +D[>8}0U49Y+"=}dr6`4AqmM/Dz.1J\u{T_tC3K6EpNrX5U_}4ܛr3S/`y{ϣhQt]g b)-wm"ae`t{yfXٴKgm[̯ߑ?һ+8{e/Ͽڹjò}4ga֢10ߐ3?~3>ٮze>ڹc3[oOcwnn?gڱGuRgηwv;ܭo3zצѻ;}?=t8ލ񓅞W?xc*/ ީ;™Sn{/n>fǓ[wetgeٮ.oiSD"8Q%" +J)PFH4&%MLPTTBe" T6 + (~yz'yϹO{rwݳoי۶1(v]la2bϒK v%H|)f: }*FxU5r# +CU5q526܈ p3nA}4hFhl$]4E34G ܎p'Z.܍6h ܃vhLt@:"9肮Žy聞xÅp‹"08-F F#!FpHG1x1Őix +Oc:x3Jx3٘c˘W*^cޠo-X`XEX%XeXX>Vf2CzlFlGSl|ϱ۰;n^~AWq[w88?Gq +WqgpOq_\4)Li*Uy/g: +^꼊5XW^X׳6Ⱥx3oa}6lFl؄Mٌق%`+ּw 23Ŏf'vf+sy/c7vgy?{'{7þtq,> Y,` 3!aɇ88>ʉ8)i|Os:|3<_Y9|/s._| |os>˅\\¥\\|s?j\!^:zLFnGSn~Ϲ۸;n^~AWy[w<ߕf6 -|Q]WRJ6䋁#;~@_,Tv'vEXnw(AK/+\g"[Պbw(v]1!PȍW#o{` r"oTZ]pZ%ur Yt(PEtr Y݊Rjvr^|qrQTʋ<y"[ 1?d[b$'=G~IǬ-ˡ6^n'8");;tvYjwN$!wX`co1.N;1e;'Rc;0X^X?eFt6ƪV +"`4n{.oc&ڛl\a}Iwڇi} sڇitɔ`ahsP4ߨM;1{Bx x.Gq8 +x +88$C-Ųi%.r cZ۸֮+ɎvH:BHPBG$GJ8mʼn!V޵kCm &Фx@Cq7!!ax 3Nt`PÛ2޻v?x{w9{@ +ʔR%À̩RR:Kʤnɔ^.9$W * +GuarKU1 +ֹ•<%(!۬Bȱ3pAX`u9gEK(pt'xjAabI#˚c;m;*Ҝ4tڌ3]ƝF#-,_;zwܬ iv/jnaFa0r_lrx\Up +6`U +erEE)+yURP0V*X+y[oT=FjhMZŊC*qhaJi¦ItǓfЦHhS$Z4ᄣB3ZCmmwR4rz +HxN +̪a>1WELeLCC3\a: pڌ/O4 LTLR蘤1)#I;_.Hًä}^bHzO{[F8{JMSK(_"GQD'Rjhu{qIE7c5h 4xcvy`y DXY)Yidȇ~z;U +ݛh,| {T>JVuJ.?I*iߖP\PJ^MwOwGT&B^1EM1*|+MES uSq.3 pn&w +έ +_{a evEvaOK= cL8U}¼?e6,R76R PLBRz#wnKۗ.Yb-Z:t37}5Dc)ew0 R 0?46y|m`^2Uv3#2c?~|UʚL +`>WD)⛸]\s9j_"jϣvӨ=ڏvO,xK-[[b t\qq_Džev _Gm 9@ݱo-sˢ,bB89!O@2#?gdW{ҿk*GY+&{wO35d-|Effrz§m VI%k&{¬>Ƀf`r6dÜ➃xCt+4>;FzY+-5Yd3MU %&&Ƃw,- ו{oP)?^ ^◫*M\lV{V΃| ?ߐWsЩ >U$2 +!K >YP"3l݊ +X˼3fƪ'c2A-mm mrǢʥJ)2-~JhZL2ڜV=BW:~0(_Y~"MWG[(nK +a!$A '0Bwb ܺ[z_"vmG|ZXc2;Anakcm8d ܘd>v|xbcs_Ϸ{{v}sě47=nPbVa%} ʃxpGaOԭk|lZѕg7[鵘;VO& }^`&d+Ն9 _| endstream endobj 11 0 obj [/ICCBased 22 0 R] endobj 22 0 obj <>stream HyTSwoɞc [5laQIBHADED2mtFOE.c}08׎8GNg9w߽'0 ֠Jb  2y.-;!KZ ^i"L0- @8(r;q7Ly&Qq4j|9 V)gB0iW8#8wթ8_٥ʨQQj@&A)/g>'Kt;\ ӥ$պFZUn(4T%)뫔0C&Zi8bxEB;Pӓ̹A om?W= @@ -1743,667 +1798,420 @@ H N')].uJr  wG xR^[oƜchg`>b$*~ :Eb~,m,-ݖ,Y¬*6X[ݱF=3뭷Y~dó ti zf6~`{v.Ng#{}}jc1X6fm;'_9 r:8q:˜O:ϸ8uJqnv=MmR 4 n3ܣkGݯz=[==<=GTB(/S,]6*-W:#7*e^YDY}UjAyT`#D="b{ų+ʯ:!kJ4Gmt}uC%K7YVfFY .=b?SƕƩȺy چ k5%4m7lqlioZlG+Zz͹mzy]?uuw|"űNwW&e֥ﺱ*|j5kyݭǯg^ykEklD_p߶7Dmo꿻1ml{Mś nLl<9O[$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! -zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km -endstream endobj 32 0 obj <> endobj 43 0 obj <> endobj 44 0 obj <>stream -%!PS-Adobe-3.0 -%%Creator: Adobe Illustrator(R) 15.0 -%%AI8_CreatorVersion: 15.0.0 -%%For: (\720\745\755\740\762 \715\740\761\760\750\744\750\755\756\742) () -%%Title: (requests-logo.ai) -%%CreationDate: 4/12/2014 8:48 PM -%%Canvassize: 16383 -%%BoundingBox: 4 -270 298 -43 -%%HiResBoundingBox: 4.07178 -269.6123 297.8438 -43.2681 -%%DocumentProcessColors: Cyan Magenta Yellow Black -%AI5_FileFormat 11.0 -%AI12_BuildNumber: 399 -%AI3_ColorUsage: Color -%AI7_ImageSettings: 0 -%%RGBProcessColor: 0 0 0 ([Registration]) -%AI3_Cropmarks: 0 -300 300 0 -%AI3_TemplateBox: 150.5 -150.5 150.5 -150.5 -%AI3_TileBox: -138 -506 438 228 -%AI3_DocumentPreview: None -%AI5_ArtSize: 14400 14400 -%AI5_RulerUnits: 6 -%AI9_ColorModel: 1 -%AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 -%AI5_TargetResolution: 800 -%AI5_NumLayers: 1 -%AI9_OpenToView: -147 8 2.68 1823 917 18 0 0 48 120 0 0 0 1 1 0 1 1 0 1 -%AI5_OpenViewLayers: 7 -%%PageOrigin:-250 -450 -%AI7_GridSettings: 72 8 72 8 1 0 0.8 0.8 0.8 0.9 0.9 0.9 -%AI9_Flatten: 1 -%AI12_CMSettings: 00.MS -%%EndComments - -endstream endobj 45 0 obj <>stream -%%BoundingBox: 4 -270 298 -43 -%%HiResBoundingBox: 4.07178 -269.6123 297.8438 -43.2681 -%AI7_Thumbnail: 128 100 8 -%%BeginData: 10225 Hex Bytes -%0000330000660000990000CC0033000033330033660033990033CC0033FF -%0066000066330066660066990066CC0066FF009900009933009966009999 -%0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 -%00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 -%3333663333993333CC3333FF3366003366333366663366993366CC3366FF -%3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 -%33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 -%6600666600996600CC6600FF6633006633336633666633996633CC6633FF -%6666006666336666666666996666CC6666FF669900669933669966669999 -%6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 -%66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF -%9933009933339933669933999933CC9933FF996600996633996666996699 -%9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 -%99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF -%CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 -%CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 -%CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF -%CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC -%FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 -%FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 -%FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 -%000011111111220000002200000022222222440000004400000044444444 -%550000005500000055555555770000007700000077777777880000008800 -%000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB -%DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF -%00FF0000FFFFFF0000FF00FFFFFF00FFFFFF -%524C45FD47FFA852F8F8F827277DA8FD74FF522727FD05F852F8F8F82752 -%7DA8FD6DFFA852FD09F827FD07F87DFD6AFFA827FD0AF827FD09F8FD68FF -%7D27FD08F852A8F87D52F8F8F8277D5227F8F87DFD65FFA827FD08F827F8 -%F82727FD05F87D7DA827F8F82727FD62FFA852FD0AF852FF27F8F87D5252 -%F827F8F8F85227F8F8F8A8FD60FF52FD0DF82727F827A82752A827F8F827 -%A852F8F8F87DFD5EFFA827FD11F827FFA8A82727F8F8277D5252F8F8527D -%7DA8A8FFFFFF7D7D527D527D7DA8FD4EFF7DFD14F87D7DF82752F8F85227 -%5227F8F8A85227F8272752FD04F827F8F827522752A8FD4AFF52FD05F827 -%F827FD0CF8527DF8F82752F8F852A827FD0452F8F8A827525227F827277D -%7D52527D5227F852A8FD46FF7D27FD07F87D52F8F8F827FD07F85252F8F8 -%F85252F827522752FFA87DFF7D27A8FFFFFF7D7D27F82752F8F827A87D27 -%7DFD46FF7DFD09F85227F827F85227FD04F8525252F8F827FF7D27F87D7D -%FD0EFF27F8527DF852FFFFA8F827A8FD44FF52FD09F82727F8F827F8F827 -%F8F8F8277D27F8F87D7D52527D27F8FD0DFFA87DA87D7DFF7DFD05FFA87D -%7DFD42FF52FD0AF827F8F8F827FD04F8275227F8F852F852272752272727 -%A8FD09FFA8FFFF7DF87DFF7D52FD07FF27F8A8FD40FF52FD13F852A827F8 -%F852A8F8277D7D7DF87DA8A8FD08FFA852FFA8A827F852F827A87DF8527D -%FFA8F8F87DFD3FFF52FD0AF827FD04F82727F852F87D27F8F8F852FF7DFF -%7D7D52277DA8A8FD08FFA87DFF27FFFF277DFFA8A82727F827FF7D277D52 -%A8FD3DFF52FD04F82752FD04F852FD05F85227F852A8FD05F852A8FF7D27 -%27527D52F8A8FD08FF7D52FF2727FFFFFF7DFD06F87D7DA8FF2727FD3CFF -%52FD06F852F8F8F82752FD07F8277D5227F8F82752F8275252277DA8A827 -%27A8FFFFFFA8A8FFFFFFA87DFF277DFFFF7DF8F8527DF8F8F827FFFFFF7D -%52FD3BFF7DFD10F827F8525227F8277D2727A852527D27277DA8FFA82727 -%FD04FFA827FFA87D27FFFFA8A8FFFFFF7D7DA8FF7D52277DA8FFFFFF27FD -%3AFFA8FD0AF827FD05F82752277D7DF8F8527D27FFA8A8FF7D527D52527D -%FF7D52A8FFFFFFA87DA827527DFD06FFA8FFFFFF2752FFFFFF7D7DFFA852 -%FD39FFA8FD0BF82727FD05F827525227F8F8F852FD05FFFD0452F8FD04FF -%7D27FFFFFF5227F8A852FF52FD04FF7D27FF7DF852FFFFFFF8F8F8A827FD -%39FF27FD0BF827FD05F8527D27F852F8F827FD07FFA87DF85252FFFFFF52 -%7DFFFFFF52F85252272727A8FFFFFFA8F8A8FD05FF52F8F8A85227FD38FF -%52FD11F8527D27F82727F8F8FD05FFA8A87D7D52A87D527DFFA8FD05FF7D -%52F8F8522752FF52A8FFFF7DFF52527DFF7D52F8F87D7D27FD37FFA8FD0F -%F8275252FD04F8272752A8FD05FFA82727527D52F87DFD09FF5252272727 -%FFFF7D52FFFFFF7DF8F827527DF827527D7D7DFD37FFFD0FF8527D27FD05 -%F87D277DFD07FF527D277D7DF827FD06FF7D27FF7D27527D522752277D7D -%7DFFFFA8FFA87D527D7DFD3AFF27FD0EF85252F8F8F827F8F87DFFFFFFA8 -%A8FD05FF277D277D525227FD06FFA827FF7D27277DFD07F852A8FF52527D -%FD3CFF27FD0DF82727FD04F8275227A8FFFF52F8F87DFFFFFFA82727F852 -%F8F87DFFA87DFD04FF7D5227FD0AF87DA85252527DFD3CFF27FD0CF8277D -%52F8F8F827F8277DA852FFFFFFF852FFFFFFA8F852A8FF5227A8FFFF527D -%FFFFA8A87D7DA8F8F8F8522727FD04F852FD40FF27FD06F827F8272752F8 -%52A827F8F82752A87D275252A8FFFFFFA8FD04FF27527D52FF5227FFFF27 -%27FFFFFF7D2727FF52F82752A87D2727F827F852FD3FFF52FD06F827F852 -%275252F82727F8F8277D277D52F8F82727A8FD07FF52F8F8527DA8FFFFA8 -%52FD05FFF87DFF7D527DA8FF7D7D7D52272752FD3EFF7DF8F827F8F8F827 -%F8277D27FD06F827F8F8F8275227F8F852A8FD05FFA87D27525227F852FF -%FF5252A8FF7DA8277DA8FFFFFF27F87DFD05FFF852FD3DFFA827F8522727 -%FD04F85227FD07F852527DF82727A8527DFFA8A8FD04FFA8277DA8FF5227 -%52FF7D27A87D5227F8F8FD05FF7DF8A8FD05FF277DFD3CFF7DFD05F82752 -%F8F8F827F8F8F87D527DA87DF82752F8F85252A8FFFF52F827A8FFFF5252 -%F82727FFA8FFFF27F8A852F8F8F827FD05FFA8FD07FF7DA8FD3AFF7D2752 -%F827FD07F827F8F8F82752527DA8FD057DFD07FF7D527DFF5227F827F8F8 -%7DFFA827FD06F827FD0EFF7DA8FD39FF52F8F827522727F82727A87D27F8 -%27F8FD0452F8F8A8FD0AFFA87D525252F827277D5227A85227F8FD04277D -%7DFD0FFF52FD38FFA852F8F8F85252527D52FF7D52A85227FFFF7D52F87D -%A8FFA8FFFFFFA8FD05FFA8A85252277D27272752FFFF27277DFD15FF7DA8 -%FD37FF7DFD07F827FFFFFF27277DFFA8FFFFA8F85252A87D2727FF27F852 -%A8F827F8F8277D7D2752FD0427FF7D7DFD0EFFA87DA87DA8FFFFFFA8A8FD -%37FFA8F8F82727F8F8527D7D5227527D27A8FD04FF27A8FFFFA8FF7D52A8 -%7D27527D52F82752A8FFFF277DF87DF87D2727FD0FFFA8527D7DFD3DFF7D -%FD04F82752FD05F827F8A8527D2752A8FD0DFFA8FD05FF27F82752A85227 -%7DA8FD0FFF522727FD3EFF27FD04F827FD06F8527D7D2727F852FD13FF52 -%27F87DFF5252A8FD11FF7D7D7DFD04FFA8FD39FF27F8F8F827F8F8F852F8 -%277D7D522727277DFD13FFA8F852527DFF7D52A8FD0DFFA8527D7DF82752 -%FF7D525252FD39FF27F827275252F8525252F852F852F852A8FD14FF27F8 -%A8A87DA87D7DFD0EFFA87D52F8A8525227FF7D27A8FD39FF52F8522727F8 -%2752F85227F8F827A8FD15FF27F827A852A8FFA8FD0FFFA85252F87D527D -%27A8FF52FD3AFF7DF8F827F8F8F87DF8F827527DFD16FF52275252FD13FF -%522727A8F852F8F852277DFD3CFF5227F827F8F85227F87DFD17FF52F8A8 -%27A8FD12FFA85227A8277DF85227FF527DFD3FFFA87D7D7D52FD18FF7DF8 -%7D7DFD15FF7DA827F87D7D277DFFA852FD5BFFA8277D52A8FD14FF7D27F8 -%F8275227F8527D52FD5CFF277D52FD15FFF8277D52277D5227F85252FD26 -%FFFD052752FD07275252A8FD26FF7D5252FD15FF52F827277D7D2752F87D -%7DFD27FFA87D52F8F8F8272752525227FD04F852FD25FF7DA8A8FD15FFA8 -%F8F8527DF852A87DA8FD2BFF27F8F87DFD05FFA827F8F8F87DFD23FFA827 -%FD17FF52522727277D7DFF27A8FD12FFA8FFFFA87DFD14FF27F8F8A8FD07 -%FF52F8F8F8FD24FFA8FD17FF5227F8F87D7DFF7DA8FD14FFA852F827FD14 -%FF27F8F87DFD07FF7DF8F8F87DFD3BFF2752A8A8A8FD18FF7DF8F827FD14 -%FF27F8F87DFD07FFA8F8F8F8A8FD58FF7DF8F852FD14FF27F8F87DFD07FF -%7DF8F8F8A8FD58FFA8F8F852FD14FF27F8F87DFD07FF7DF8F827FD09FFA8 -%7D527D7DFD0CFFA8527D52A8A8FFFF7DA8FD05FFA87DA8FD08FFA87DFD09 -%FFA87D527D7DFD09FF7D7D527D52A8FD06FFA8F8F87DFD08FFA87D525252 -%7DA8FD05FF27F8F87DFD07FFF8F8F8A8FD07FFA827F8F827F8F8F87DFD08 -%FFA827FD06F82727F8A8FFFF7D7D27F8F8A8FD04FFA87D2727F827FD07FF -%A8F8F82727F8F827A8FD05FFA8F8F82752F8F8F827A8FD04FF7DF8F852A8 -%7D7D52FFFFFF52F8F85227F8F8F827FD04FF27F8F87DFD05FFA8F8F8F8A8 -%FD07FF7DF827A8FFFFFF52F8F87DFD06FF52F827A8A8A87D52F8F8F827FF -%FFA827FD04F87DFD04FF52FD04F827FD06FF52F827A8FFFFFF27F8F8A8FF -%FFFF7DF827A8FFFFFFA827F8A8FFFF7D27FD08F8FFFF27F852FD04FF7DF8 -%27FFFFFFA827F8F87DFF7D7D5252F8F852FD08FF7DF8F8A8FD05FFF8F8F8 -%A8FD04FF27F852FD06FF7DF8F827FD05FF27F8F8A8FD04FFA8FF7DF8F852 -%FD05FF27F827FD05FFA8F8F8F8FFFFFFF8F87DFD05FFA8F87DFFFF7D5252 -%F8F827FD047DFF7DF8F8FD06FF52F8FD04FF27F8F8A8A8FD04F87DA8FD08 -%FFA8F8F852FD06FF7DF8F852FFFFFF52F852FD07FF7DF8F87DFD05FF52F8 -%F87DFD07FFF8F827FD04FF7DF8F8A8FD06FF52F8F87DFFA8F8F8A8FD06FF -%27A8FD04FF7DF8F87DFD05FF52F852FD06FF7D52FD04FF27F8F87DFF7DF8 -%F8F852FD09FF52F8F8A8FD06FF7DF8F827FFFF7DF8F8A8FD07FF7DF8F852 -%FD05FF52F8F8A8FD06FFA8F8F852FD04FF27F8F8FD07FF52F8F852FFA8F8 -%F827FD06FFA8A8FD04FF7DF8F852FD05FF52F8F8A8FD06FFA8FD04FF27F8 -%F87DFFFF52F8F8F87DFD08FF27F8F8A8FD06FFA8F8F8F8FFFF52F827FD08 -%FF7DF8F87DFD05FF52F8F8A8FD07FFF8F827FFFFFFA8F8F827A8FD06FF7D -%F8F852FFA8FD04F87D7DFD09FF7DF8F87DFD05FF7DF8F8F8527DA8FD08FF -%27F8F87DFFFFFF27F8F8F87DFD07FFFD05F827F827F827F8F8F827FFFFF8 -%F8F8FD08FF7DF8F852FD05FF52F8F8A8FD06FFA8F8F827FFFFFFA8FD05F8 -%272727F827F8F8F852FFFF52FD06F82752A8FD05FF7DF8F852FD05FFA827 -%FD06F8277DFD05FF27F8F87DFD04FFF8F8F827FD07FF27F82752FD087DA8 -%A8FFA8F8F827FD08FF7DF8F87DFD05FF52F8F8A8FD07FFF8F827FFFFFF7D -%F8F827FD097DFD05FF7D27FD07F87DFD04FF7DF8F87DFD07FF52FD07F827 -%A8FFFFFF27F8F87DFD04FF7DF8F8F827FD06FFF8F8F8FD0DFFF8F8F8FD08 -%FF7DF8F852FD05FF52F8F8A8FD06FFA8F8F827FFFFFFA8F8F8F8FD10FFA8 -%5227FD05F8A8FFFFFF7DF8F852FD09FF7D52FD05F827FFFFFF27F8F87DFD -%05FF52F8F8F87DFD05FF27F8F87DFD0CFF27F8F87DFD07FF7DF8F87DFD05 -%FF52F8F87DFD06FFA8F8F827FFFFFFA8F8F8F8A8FD13FF7D27F8F852FFFF -%FF7DF8F87DFD0CFFA852F8F8F8FFFFA827F8F87DFD06FF27F8F8F8A8FD04 -%FF27F8F852FD0CFF27F8F852FD07FF7DF8F852FD05FF27F8F87DFD06FF7D -%F8F852FD04FFF8F8F87DFD0BFFA827FD07FF7DF8F87DFFFFFF7DF8F852FD -%05FF5252FD07FF52F8F8FFFFFF27F8F87DFD06FFA827F8F8F8FD04FF7DF8 -%F8F8A8FD0BFF7DF8F8F8A8FD06FF27F8F87DFD05FF7DF8F852FD05FFA8F8 -%F8F827FD04FF52F8F8F8FD0BFFA827FD08FFF8F87DFFFFFF7DF8F87DFD05 -%FF5227FD07FF7DF8F8FFFFFF27F8F852FD07FF7DF8F8F827FD04FF27F8F8 -%F8A8FD06FF52A8FFFFFF27F8F8F8A8FD04FF27F8F8F852FD05FF7DF8F8F8 -%7DFFFFFF7DF827F8F852FD04FFA8F8F8F827FD07FF27A8FFA8F8A8FD06FF -%7DF8F8FD04FF7DF8F8F8FFFFA87DFF2727FD07FF27F87DA8A87DF8F8F827 -%7DA8A8FD05FF52F8F8F827A8FFFFA827F8F8F8527DA87D7DF8F8A8FFFFFF -%A8FD04F8275227F87D7DF8F87DFD06FFFD04F82752F8F87DFFF8F8F8A8A8 -%FFFFFF7DFD04F8527DA87D52F827FFFF7DF8F8A8FD04FF7DF8F8A8FD04FF -%A8FD04F827F87DFF52F852A8FFFFFFA852F852FFF8F8F827F827F8F8F852 -%FD06FF27FD04F8A8FFFFA852FD07F852A8FD05FFA852FD05F8A8FF7DF8F8 -%52FD06FF7DFD05F8277DFF7DFD05F87DFFFFFF7D27FD07F852A8FFFF7DFD -%05F827F8F827FD07FF52FD04F852FFFF27FD04F827F8F8F87DFD1CFFA87D -%7D527D7DFD0BFF7DA8A8FFFFFF7DF8F87DFD08FFA8A87DA8FD0FFFA87D52 -%527DA8FD07FF7D7D5252527DA8FD09FFA8A87DA8FD04FFA87D5252527D7D -%FD35FF7DF8F852FD7CFF7DF8F87DFD7CFF7DF8F852FD7CFF7DF8F87DFD7C -%FF7DF8F852FD7CFF7DF8F87DFD79FFA8FFA852F8F8F8A8A8FD77FF7DF827 -%F827F827F827A8FD76FFA8A8A8FFFFFFA8A8A8FDFCFFFDFCFFFDFCFFFDFC -%FFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFD6CFFFF -%%EndData - -endstream endobj 46 0 obj <>stream -%AI12_CompressedDatax0 Yil+Y|u#Nv!ȶ6ɫ$oUngh4eIT7dW͢g_|1.U[nov[z"Dx5_r~ӿ/J%R9d|/R/LR.}6rZ@c.nv7f0_~e -ϞwpBFBU{=]2_>Y.iX9ܮw͟p[/'W-Z܄ LdpLA*d g\t@'zr\܌7|~~kZmV{f}9_֛OڿjqwrzB$ zt\]p{fPԻ_n[CLﮡbͿ'@v j|ʴ|oû{8zן XDos1 . $J{8R|_~ج<Rv^\TW ~sXjN@Ew fuuۧ\ ~ȏ7A] `04|G ٧ -? a3%ru_&0*nW4A -.v`{?xs5ʿ/4q?{-nբW0v8x3Y\X-׺ꍮp_\߽[l ~\nQޮ^|Xlw77ūvxK^-.~$m3L}$]jBb߼X\V4vSzyb+b #]|_tv]/?—vbf+Wr#z^k=0j]b} 7^]\/[0@ۛ[f{{],nqq} H_P%Z\\-W9 \ޮGy?| [ B^ ~ЃPЎ~N/܅)~1w;kLw/~B3?~7ӏnaǛ/f@n_ǟo- k@~܌g=׏F4ť}[_oZ꫖#LZ?p/ߘ>m6|j+d[on{>w4Mg?O5AL7 -<&B3]_n{ -E銋 =ց{?oo?`X=5%LV-vsg_jQ vӿC_-OoG1T1`q ^bo}|盏=ηjo{fߡD8@lA nV7a -$_,t-%ogqKy|ug8u5KpIIN =j{R'𦛇DdF|Ov7%/> J?~ Y73IL 5'DAZ -i;p~_\޼٬>2HNQ3›YU(Dn2CGH5e1PR]9H?_^wңQVwC!wnOKZ)Q -1r0=bCpW<%N2C$EG>"z]⚚犰ʰuƇ,g^î -nl-7Z|Ez4P`ջ6CjAn{+RxA&<)`h)~J4Nr> XIBHBJ|P/^Gn\43Lc=V4zƙ'D#69yxn.9^]{7U`yrج{?wljNe~gI+DQJq!SOFҘ,J##s$Jgtp<O&$>NS9 -J6͡C*lA3%T3xOfw&TR}Rڍݝ x+gOgPk2i%dPR()#H(J4&3*ؠOFR)T2), & -JLERTI4Q߈fK0RIj c(fPu4 PQ%_*^w;K- ~Vom ?GZ-FYdK$S4k8k0aB/5L"|`*ѫ(rzEB^5IѫWJr<:Mq6̊,rx. 8kJNa'4O4Je -1RhqdIdI -$QL'Q"M@6T*TR HE`x1,B$ Kr ThcPLO|g݈18ɱ@3]9fi/yE*c(z@5B6D#*1IEA4e"SU*]lO)ڂs8+e /Т)ٞh1SԞ)@0l~ MAKA1\E*"(GՈSreO`jȉ!u5E%12PP02!bDZ2ՙ7êE:Ļ@LYN 1 4S"#rԁbNfǗa\'s"?oޟpwcWA&~BsF%(R(3 Q7&lZ1N*Cf @%eRCPZz9AndF0XxihM,CHGCgf!,T -""p0b;yw=zƑڹyֳ۹v^fF?^4b!/ŀv 9rdt1N7`y c(U !᠕Lf@& N GN@P4OFW-W-#}3.ZqN`cxG$I-H͇$8 i/JA VP&̱0HA;c)/ "(MId7$.'8%*d ١l7fٮ .c@u2VJH2̓$rġ06댈˘V DK⛸%}lqNSay ލ|-c?& ,׿YAcHb?u}7)=f_u~˺ScC/Yd/&WÈ͈#{p(Xowp(Bwp(QP@Qws(:3g. chb -dɌMhxАfeJm;3+k%DB/L,)RМ.9c4F2mbe@۩nmX lʸhBE[`1t-amj3VI4\iƳSlwf(\Ű& -ih1w@@,MU*-(PCZPM4uj JgiEIs4#a1(H˺GEқb}"aNKzZ(XT!UzJUd :)՗]='渏E-n_wgwrd$*r/{8y/׹G}VX1T)hHJZ_L]D܄k)h1feƴ&Zh_{٦-acֳrOrP+O*VfE؂hQd6-.ZˬOm 묶>Gt"ye#UjK 3cB߾ZޮJ~7{ly8E My8YleHS;7)yF,MSu)dZ>ci Zs'qu>)t1<|z" #-Y1Bۅge~P&ni.9 -5wKSiT`@wswKdHn,(Zt}>ZW>yxh}ǯzϪuBx)DLMLĔ 6FMT3۠r*ؘOCG485:ZJ;*yQ/8<5> -Br (P2BJo c2w iȀ\cL! -TQm32r_N 2X -\Srn-m 2YbaAVO = #OtH?JS\üKkQԔU}\Xvil($[6ln &9gym \9!хx0F"k(a.D PGct!"G ctAϟ5 c;ձzS*ZlXK4($AT 2*+teZ*Eã?e_TҠdpJ.^B(5$vF̙Y@-c9}4}fZ>L&W 2a|`!ɍckHٰsxبh̊HpІcf6Ą#6@)D茏ʆ ucֆHH2dyC8 :ńqDCc3U 36hC1 =#ҍ)֛g-`%n4-׸xvs*F7QAolKޗ|ޗ|ޏ9LxSb˿N7rGFZ)hTz-juyhD`) -Z*H^hFQIP]D q OpNCA(&%\Icbvɛ0Ya0>O95L 1{8Ӟ_y5U&-:2ۿaSo?1oL2|/#onE(޹69|ͯQ` a 8hb6\姄d#ڧ.~E@i_N50-af;ІP( Л52iX-i '{6N:[Rv- hRJ-C̴L27R -M)5d2&ƌ[нŗvqU-,+wK[Wqo}r ݪ -2gݹ -t{~nu ~'v33áS: ԜN b0/="ͼMF;>g֩^u;z\޶Zٲ׭]fU vՌ=*~Pq.P YZ$S4h+%6<߄Vc!݊=P(@a fzK؀,}("pBx -+#C E[e{0lQ 1&η8zҝmփgū~V+۠Uڮyۅ;vLhQ{7'8{A]df=#>9ɲp\cDkXf1tT+I?{Ԇ֌{ N -Pm.ǡUJ3 -*)(ºtFÑUESA("F fR$vL-5PÛ0?CsƵ{Xk#rzbaqb2I\[dL}u,fjg5PKuVY$ƘU9C(&kWήd,! #5I2h1\"qxrِ7ShI/@ rNJ^-8驝 o3Y:HLus\:ǜW#8 -Ĺp>,([$`LJ*:| +P榠P@"8T„4;!ľ .ۥEPi1F7Ai#3$SΊ8 "*3.?Xr"_o[ĸ#\?̈h\anuL0˄wBBB^pRu; ԆYhCCajC,Ă.scXj.R-Q(4\nCmU4YgUrS-PNBX(SOu29LVQO}WOdN=l*>%WSUx沬Ve=G((J/:5,aB%(ܫ>!e{|P&w shJEGåp͜dJ~ 1yln, bDzS&R͜m08gFMa YzH&oO47!^BA#I4V|`i[ &biy7Ќ8(liQȟʁ F^fs.㛿nVsl̩&H_o0\xm.0rhqr>[6 -gg.- -%ke޽#`0'v:'aM -۬\ p.ZM>bl" -rKbY16!٣lk3_rB(P#sNpsQFPsұ)ۘ-LcsScJ)R3K3kWr6n`6GIepQ4nIEVø'(mK07mp%wqXJPȬs^u;*##X΁TBu"UHƉD:GR+i3i;ޡӤ#'83Lk=XꙦz%>2Ki$^@mѓGo|X\jgl:\n{+H͸H>᜹8"HT0jRP -Le CdP"Śa -m"μncf1Y:䑰ӵ*=K-Y*B\BsMJit 7X?6~4}b Lj7=uE)LrvǒS.0[Hs|v=r L>r&bZ< Z?R%S71( -vqb3f?$/GIcs)P!d;QWH$ qG]f:@qxS[jxҘAƖsԴ~~(`Goj3_.׿~;bݳDz[֊hdV =OpiLd Rd0cν#SLdAfc%|yX&(r -ꈥ-+$Y n,'ۜ㝨c=&,K- Ùq M9NhInj$)̤ YMul6PoHy:*pMt#n EY!3ڔB -5@e$'qضd{$Hlzm[r x`|ȘDl>Or7`SR]x9ݔ Ql6Mfc:R?R޳;"(UuVݕwYZe],nTةu**_rZi i֚^jiTU@jT 0-{s8HWmmnjzf]fg]`v >60:1R{>\b!YF8P -7`>GDwahF 눭bW/y ')@~`#D.!PogKC}RS>j[˹_Xk=%ל*kqRkh 7]^4pvJ eD8"F -B TmY׋; kjċRd؞'l5"%W Vؤ-1Σ}S;L.jMgYn4/E`N;ُd;VŽr,N[wKx9R PNad51cӺSN/]Ħ3&>w|hS)}?qII6 Ul(ϯ(p -!yjrj9GќRkdD>-1i6d48Lf'پ(jZ]Cc}% JQh•ל%O)j&'t雜 liϠ) N']Vΐ/sAH@8~GED c>>N{DHe /d[+M6.\c( fX \FHCF t7S~1ٟI'?{"'l.X1UCMéԞc滃|oC- +،ZMeCcMR')确M>?>#m$EnosKekusFN-Sɺb-Z\Z+߈:³:81Д+$W^xڽ1.x%%w|ڼ;s_$Ҁ~Ә@v ># .eG{w&79I8=ǣ:&t G}LǔQ)舎w:Ng:[SF^7SWEh/+~?AwI8ۃ؂c+WbdL|CҒioՆRE 0!wX~2@F2 -ZpY&[X&2!ebRrvR1u{P{%"E+l+c LzyM=X坨ݲCHzB}!Ag 0{b+zaZ{d%9S|9M4>pIrgUб~%tM'hMV%1]U9{^9<;~nL^r4o_5][[vK^ MO^Z<_OnvY* -{ď͇8OMAaZ!O.c*̆b/92g8F Lٴg)A<ʞ#  XZݷ^e\a?O;$9O>01: CGd- Nܚx'l01Fŀ &/*.qspX/&wXX"6asbpz$_Pg<>bd㵬ywy]Jִtʼ;YMޡrơtͺS%)V9稓o.9y~D)%n5(&=9ǵͼig zcN:3A: !!&Ph@e%zC|DԘ8/P@S%ߗ>5Ns ,kh.Ღu8DN܉-"y"ׂV ! Isc - pw #{Ͽo V=?޳NTNTNTNTNDNpÉ'N+ѡNRBV)?-TC{$+{lȼ'Y+MZxBwJ=iNIeRם:0c_*>s4Ix0 u|̆S@]rW g0=fӝc: nPb0T1AY\݆;_tKauGWԺf% okW}f u w Cf|@x }@%V,WAbEs,3\qTIhF6!a9bH Ċ{)D@#F6`H'8cDh2GvNҢZT5]\~I)"(yuLJI LÈ684{YO3,4U̐tIH9;bt>h$P@)Ng鰣N-# p)R:TdLGSRp0R}Ȉ>1!XG </Bԇ1wnZAsڤKwj{oېW*-˛%,Y1tzQ*<;E53]8hhl = :.6ڸ\`Ϯh.hpp葶Ebh,S#Y%$%LhrG8f:pÉ'Dn̍"*F@lxM!=`GOz'8ԗRt xw,C_]XQЮ:;(]WW"p\%iαsԑ3qIVfd49$#& .ob2eS|+ - ҆]1e@q'\ #&9H 3b)̂b%m9{?939k $qK [gIm&>-#L2SX'*ScQҬv΍R~ -[Sn3@>^f->t.#3ˑZԳ'ZXX'i8ahLӾa2>4rkOle°8Hd26I$'ZG<$6 "f# NŪ#tl哱8t:w>ל95ԆypjRɹ䮼mGk⧬ {⾢ĵUHZ,R?ˈq:ʘ7asC_#غcFmL>:&*<]Lkb%5DžU:0`|ȋy)\өY"f"0Z0.qt ;b n0aTrj[#Xi9~S{TcJI>AW@;dL{☦6)uqL4j$"&+P(u]qLk"&&ɳhk=8*h`iUg@yhplTn~XAit4VyG8%cX{ $Ms$I)Jaqqa~wEܓ},H,B%9 -D')N,=I/CY{=g0vMt[+֊nkEZmVt[+.É':8AV>w3|~9+wm;:4o)(Z챮նx<Դ(~=#@`')c>%4B;GUCp-1[.ֹQxn9U՝;>NڎU?kH`fveK p;>Sw6M~ffg.J?̅R@(8sO:*:*W%[U;ɐ1MؓݼZғ3$?KVwR]g/X&{9Ȝd4N_h%?%\M焳L(DTs -S&ԂzQg4EC`fY"IK<[G3uGohUVdN`z\T;CmyDQS>+kBAFRuaAm|34& Ip緹8NxZ(iBۈ+eeL$x)/BjBru'qrҏEQJ$6UOꪞ%4si.ԋ6+퉘X1'^1]rHgA O3B΋\{-nO6!*g sсֱdqO`y\ -O;ZU'wRcݫF)-#^T7^ɢ؈xbÈg-Uٚ'*l9{f9fdI>IR*ɋE3( i2^gAs[l@^Uj/8iI c&z/9CiF5 Ƥ$njP"6&O56d" A]gb4&pcl1?fp|+.%nQP'ٴƷ4%aҙ9ͩ]0^!1 \:M܌(ږ8+j<ɋvdݍ-@8ٴ3mmrqЬa"^j;{v=k68CY~ - -(^;n VښUʨ$Nz%i7 :ʸLxw{cvMMx/gd ǟhhd W6'{筬.jpG7dd9Ǽ .FDgZǎ)thQ׌L>ocI8V-9}ׂy/bjfC -hg@<2{.edI~/\ǁW&lh}9'a%Lr/bfw_#C82{ -K 0CUbo'Ehd&V+G;0Y*T"T~?wySyS5x면 Z @9 Ө&j~G|?H:AM0ZjrQa,TD wbS{rI1.7ec}잖#Sv' NJ>֑Ȧ(/9?%O T_Ե鞖&Amzg6J hȰ 6O3<:T[n=TP.۬>-Xkiq<-XV6O f~LyZrU4mLcdPy2)IrK㜤#)@N;2P\-Kj"ۼ͉^r͉ۜ+9TPsќgbsb-d -*цTTiHT 4!.9.yVќx7d.Vќxג†„ns_h.(+9TpsT+9b*r?K:n6 曧^Yy9 Щ9,yv[c3+kpSOЎ7䳥3~yl\+phmoo۶l<UQM_x? -fz>Gt<MW(UE LKJen7'ԙkJf$̾(.$S YPz?}FF9)i+IƦ3WySj*˂'bMIXB⠝}^;odpk\#:o)tVQޯyfX5mF.p@B惸Y =~}M mFT0%3l;Kru]n݇{&hfj5DiV57ADQB@C !сpYMhU4-h RaAe2Sb5rЇ"B6͹Ib ECKrK7y$-p~"VY W^ \ a}D2K4 Z\˃UHS3,7:'?jd T% 8jfƔ 4P5O UTl3NuRfi,3ɬ`f+F]Y -@(K4)F!WS<$'4rC=K@Ӕp1LgZ0$ji@+R呭L#*@rVkTp`T*y -I,p5f5= `+%Seak8YkpIlk82YF2p8,Wq8MTli0p̋+m"e9]&nl:XCঌE<|*Ñr'.Hq̸ ʬt$Ix8Q0o%*ƕlI5ɽL͊ȨPEnZʕ*+ .%I8(`*3e0ѕKP4.<ǞڰOܻx |;[.gů߿asXޗ^c:]Lv^ru]qw}ieZ3{W|ǻD&]gyŐX^7_~C{X{k>6s7phmE/*jHg*ի5zꙖ1HQrƛzƚYs!fw5Y䈤:׉ōo77z!z~|sh]`W'-FBZ:Ҋ\ ܼ}{؍"JxJ#, -# ܬ6ۯx㪅/uaq%sƲFNwL~o~جyx3Zw癪I}:g6c:M_?'p^n 6xŋK-g)re"Ϟs.Ε.huΣO9MXVpU5g#Rg1 F$AZ-n1ԜF7/΅!-|ܽ^Z`enjSu~}y=KY5yejGs.#:3]3k٬F]ݾH #o[̏ k1#:m>sӄyH3ւl?߬6>#-= |`;x&$kH_!u5p|6 \hDsQlhD *AHs0w]S>k!;1>sv6@o#ٰC:7VSs|^^-VV֌ꝧޣyb~6Y|X/׋djshgJQ>g@_φLpz\Ԩ5ƝRs~T ҝ:jv:j>s&[l=U]j*kuBYdF0}|6- GTWvBwr7߶O`n7-]}QżnU q!7$1sA6g`M~4ox۩]mhmj8v4_/M|yU˧bHgj͂:Z RUsv6IO/"Ő΍YwI"S"gԆe kzI-tfsHjt粎^DR!4(#?vUmf{jsJȞw6A\H'){R{=;[J^ZX,>K #´#<3Žvj:Ϝ.;2ؑN vd%up'L Vuk|&խ=kju˭9(zIQ ӟĈzjmlJe?*ُDuɂɂzDG=x}dCz­]|Xw6r|IeMcȚPR&=IML{ʧ;YgՠmGge9'n} Ѹl$l?ln@ja8c:rٞsql@yԣ k.Vp#'4с`DmՓsI_Ů81tME9_sw)f\sS`tvԸspCssw!lwChǓt_-߾mqbҹЍ:731ެAW[b?V W? n:?.VZ{ ںXLoonoAn 𮆜1Ύ#v'Qom7 -Y6ܬoo6]!;\ꬌ/~?׷17 -[[;wˉ V%/̜4iLrIl_?nܮJFe=-iekIY[DYG_I_t @ks6;_|z>^x -sq4"pҩ*ک*ک@_TG6:U驢O>SF2˹HfO;,s;;w o-o'\Y-Ft <#:#giŧVO5lj{9_RϘ ϷES:f }nj΄3aƭ/x9swўW'YǓOu?3yK?O}n"gIT]ϡYFΉ/^ĥiS{w2đ{2]*IՋATDq/@Yڋ 3 rS1UzUx[nvEaۅ(rhl$Yօ8L+IO!c2V= D=!%"z_- d&)uS%=y/*g&A*.167{*5§#֓ã\`xpPw2n`i)[HKO-ʛ[Ń0A{`fRq)<]x}Qk梏}J<'__ Gc6L<܏kBΊ2!tͼI!| Dk]J9#'TfPtkR#Yquz.nQѓD)ӄ"}1HLfb&ȁ*j]$rGLI Ji$GYq2b3$BiAQ; WGe,%IBJ "O[w|9s b Or\2S㖅(p2 X5ƀdZDg 8[ѝi\:`~EQʁ" @{}üAʛoj6o^fRMx-D(|214K - .)"Yh06&N[hNExn^qU -]V"BQLH/A! ʑ0>)wr2(ИD'D`E*@"B\{ӢXSA(RQG!sJawpQZ@,K -;摦`]J5#mmt+K|'y>.q5C^w.2z_֨\mW -pPTJI뚾ϑ|ts4 ך݀@FXH`_,q" Tj|9Tch$1.z*3zWI;UxwZ@=_^m Gz|1H 7l3O+ϗǺdwk0COΌm@0vQ??M ] ^Kk_>g3;4}r_͏}.&.\0@_כutW=FGCTg  -XMʱq kF{P! 5O`]^#G 0`WcS7ut" -h7,E:BK:B18)jsf@ 2ǯAbKgI=9)F3j^ -= Y4P86=OZz.|eL<J]*4X Ft?%O>Iύ~7,4d:0P>AAӷ$(qQG a -e[KC_T)RϾ7 //o\ۻaiq^lX7j<[o7kK6@f6o˯I/l3%'̒qAa?ך=H(<+z`rbr}iQբnڻ[RoVf)0 ׈ⱈN= ĹJ -e)4"-b4bU֌B3ЖT)m2I% OB h7Bp -jh5+{ Ng)8 `2W7`MbgU~A-hj"P% ZAJ 9eAH4yCA~|\dT7|(@8҆u@Hsr P|L}etN=6j4VQHqecn&:l|\%x,z@jLcOQ.l~ P P009PHlO:^`FwiccǾ^ѳn彃gzL3LAQ M xHXZDRId+tLHE! 1r/xDQGuZ<c x^xP;fv2ex[7jU)>r],0 fSS>ȥDl*"ʡa>m/kDR/7 -zb~4wM(w\ υJDj` (;pNa~}ZRQAzihJf9tI@")Gӵ}>07MV.P(01( N -RP+̲%@7$|-gs!,ӅylZO[m/Grt~ﺔT6ا>2Jz5Su58;_d:ѻڠmfv3;~Tr 2D+n(XTک{:> zuԐ7&[4d'PE&&_jt0еBfEZsk㏩К7 Y0uk_nA>Ltl0p Y!8`a&,,<4x'_չ$p@E)KmUI»Nc4_Wswn`+ ֽ^].M3BMhn/?|7ߝFBIV~OP Y'CISD]J"C*U~BӣOk4I]̘< -"nDŽ %$R&wj|O7wO{%m0S K~.tFS>nmL`&r `rifJ7aiv~ }{6]G*Q|M&]^ -8*C*R77˽dk q!x$~ßd7{aSVIdlkK|P#C@iH0@@~*SBҖN:D$U6\ >AB&R#BF ,@&c8MIqХJ9{eky=x[D)ZBݧRҪ"oP5w:{>椪LUe6 -U $x}F'BEJCsʽ0wMzCW%"vF(IKxlJNB b` %@ +yb}jA d21{DBJB[wk<4`;g4 :S;b~4wB04ѯ\_!6kFe9&2fC?GMFC3_a1u6'T:w8YK (LtG6໛>}Ө6]3d _[nRGK,kcLBR-ȴCn4x鞎ӖH{5e:S< >uKiDT#Hi -<;}8qاiFsf z&$qp=8ksY)`5r9&SH+C8j(y&X/їĚ (rLC̘Rh,GӇ,tBL\x_}a*Ckm+l)H ƭD0p&/˼髀|\/c+*d?Npt=H:hsׄ[ZŪ)'>$HZhα -_$xOޛ57$ bzO >x$ fv&$3=ԚݖzggzB֏ЃL YUYJL _* 7 w-d!b8>d߭F6VV֨46WPFјYWAV@Ja 1DotMGʧ* In&=^I$N$CwQ@"M&T*hW#Z_%䔒S EbsS&E d,VkNE$ -'6.0t/REGk@ -fD,tD֨j3LנiH h*=bO*KIZW$hB'cI1jF§`?Ra+QAQPWu Pq9 jb3v>yK(D d.ҙl3̋sӠ8R*|'8aufe랅] -УR.it9(4U1qE"4VMWS c5X=Nh+14ށ7tdGe d0&27$ 5]|ѳ)- [ eUa25/Ah*2ɋ(c=XY[[k_n?K2G4 2.0 0]P%^vBYlyQ3d#z:f"M%|SQ+ra>V YFDB@7F/İ[??᫊bJ eGWd6R(LO"|zm8@@SV|*pydB -R ֨Lqt+?s/N IC$F)#aWYN}ؿUgkzX?)=Kzx霋d9kH$e("d> endobj 23 0 obj <> endobj 24 0 obj <>stream +%!PS-Adobe-3.0 %%Creator: Adobe Illustrator(R) 15.0 %%AI8_CreatorVersion: 15.1.0 %%For: (Kenneth Reitz) () %%Title: (Untitled-2) %%CreationDate: 6/23/11 3:13 AM %%Canvassize: 16383 %%BoundingBox: 2 -270 301 -43 %%HiResBoundingBox: 2.41797 -269.6123 300.3203 -43.2681 %%DocumentProcessColors: Cyan Magenta Yellow Black %AI5_FileFormat 11.0 %AI12_BuildNumber: 39 %AI3_ColorUsage: Color %AI7_ImageSettings: 0 %%RGBProcessColor: 0 0 0 ([Registration]) %AI3_Cropmarks: 0 -300 300 0 %AI3_TemplateBox: 150.5 -150.5 150.5 -150.5 %AI3_TileBox: -138 -506 438 228 %AI3_DocumentPreview: None %AI5_ArtSize: 14400 14400 %AI5_RulerUnits: 6 %AI9_ColorModel: 1 %AI5_ArtFlags: 0 0 0 1 0 0 1 0 0 %AI5_TargetResolution: 800 %AI5_NumLayers: 1 %AI9_OpenToView: -147 8 2.68 1595 837 18 0 0 35 161 0 0 0 1 1 0 1 1 0 1 %AI5_OpenViewLayers: 7 %%PageOrigin:-250 -450 %AI7_GridSettings: 72 8 72 8 1 0 0.8 0.8 0.8 0.9 0.9 0.9 %AI9_Flatten: 1 %AI12_CMSettings: 00.MS %%EndComments endstream endobj 25 0 obj <>stream +%%BoundingBox: 2 -270 301 -43 %%HiResBoundingBox: 2.41797 -269.6123 300.3203 -43.2681 %AI7_Thumbnail: 128 100 8 %%BeginData: 10032 Hex Bytes %0000330000660000990000CC0033000033330033660033990033CC0033FF %0066000066330066660066990066CC0066FF009900009933009966009999 %0099CC0099FF00CC0000CC3300CC6600CC9900CCCC00CCFF00FF3300FF66 %00FF9900FFCC3300003300333300663300993300CC3300FF333300333333 %3333663333993333CC3333FF3366003366333366663366993366CC3366FF %3399003399333399663399993399CC3399FF33CC0033CC3333CC6633CC99 %33CCCC33CCFF33FF0033FF3333FF6633FF9933FFCC33FFFF660000660033 %6600666600996600CC6600FF6633006633336633666633996633CC6633FF %6666006666336666666666996666CC6666FF669900669933669966669999 %6699CC6699FF66CC0066CC3366CC6666CC9966CCCC66CCFF66FF0066FF33 %66FF6666FF9966FFCC66FFFF9900009900339900669900999900CC9900FF %9933009933339933669933999933CC9933FF996600996633996666996699 %9966CC9966FF9999009999339999669999999999CC9999FF99CC0099CC33 %99CC6699CC9999CCCC99CCFF99FF0099FF3399FF6699FF9999FFCC99FFFF %CC0000CC0033CC0066CC0099CC00CCCC00FFCC3300CC3333CC3366CC3399 %CC33CCCC33FFCC6600CC6633CC6666CC6699CC66CCCC66FFCC9900CC9933 %CC9966CC9999CC99CCCC99FFCCCC00CCCC33CCCC66CCCC99CCCCCCCCCCFF %CCFF00CCFF33CCFF66CCFF99CCFFCCCCFFFFFF0033FF0066FF0099FF00CC %FF3300FF3333FF3366FF3399FF33CCFF33FFFF6600FF6633FF6666FF6699 %FF66CCFF66FFFF9900FF9933FF9966FF9999FF99CCFF99FFFFCC00FFCC33 %FFCC66FFCC99FFCCCCFFCCFFFFFF33FFFF66FFFF99FFFFCC110000001100 %000011111111220000002200000022222222440000004400000044444444 %550000005500000055555555770000007700000077777777880000008800 %000088888888AA000000AA000000AAAAAAAABB000000BB000000BBBBBBBB %DD000000DD000000DDDDDDDDEE000000EE000000EEEEEEEE0000000000FF %00FF0000FFFFFF0000FF00FFFFFF00FFFFFF %524C45FD47FF7D27F8F8F82752A8A8FD73FFA8522727FD04F82727F8F8F8 %277D7DFD6EFF7D52FD09F827FD06F827FD6AFFA87DFD0BF827FD08F87DFD %68FF7D27FD08F87D7DF87D27F8F827527D52F8F852A8FD65FFA827FD08F8 %27F82727FD05F8277DA8A8F8F82727A8FD62FFA852FD0AF87DA8F8F8277D %52272727F8F82752F8F8F852FD61FF52FD0DF82727F8527D277D7DF8F8F8 %7DA827F8F827FD5FFFA827FD11F852FFA87D2727F8F87D7D5227F827527D %A8FD04FFA87D7D527D7DA8A8FD4FFF7DFD13F827A852F87D27F8F87D277D %F8F827A827F827275227F8F8F827F8F8F85227527DFD4BFF52FD07F827FD %0CF87D52F8F85227F8277D7D277D275227F8527DF87D27F8F827277D2752 %527D272727A8FD47FFA827FD07F87D27F8F8F827FD06F8277D27F8F8F87D %27F85252F87DFF7DA8A82752FFFFFFA8527DF8F852F8F8277DFFF87DA8FD %46FF7D27FD07F8275227F827F852FD05F8525227F8F852FF5227F8A87DFD %07FFA8FD05FF52F8F87DF827A8FFA827F8A8FD45FF7DFD09F82727F82727 %F8F827F8F8F85252F8F8277DA8527D52F852FD0DFF7DA87D7DFF7DA8FD04 %FFA87D7DFD43FF7DFD0AF827F8F8F827FD04F8275227F82727F87DF82752 %522727FD09FFA8FFFFFFF827FF7D52A8FD06FF7DF8A8FD41FF52FD12F827 %7DA8F8F8F8A87DF8527D7D5227A87DFD09FF52A8A8FF52F82727F87D7D27 %277DFFFFF8F852FD40FF7DFD0FF8FD0427F87DFD04F8A8FF7DFF527D2727 %A87DFD09FF7DFFA852FF5252FFFFA87DF827F8A8A827527D7DFD3EFF7DFD %04F82727FD04F852FD05F87D27F8527DFD05F87DFFFF5227F87D522727FD %09FF27FF52F8FFFFA8FF27FD05F8527D7DFF5227FD3DFF7DFD06F852F8F8 %F82752FD05F827F8527D5227F8272727F8FD0452A8A87D2752FD04FFA8FD %04FF7DA87D27FFFFFFF8F8F87DF8F8F827A8FFFFA8F8FD3CFFA8FD12F852 %5252F82752F827A827525227277DFFFF7D277DFD04FF527DFFA8527DFF7D %A8FFFFA87D527DFFA8272752A8FFFFFF27A8FD3AFFA827FD09F827FD05F8 %275227A87DF8F8527D52FFA8A8FF527D7D5252FFFF5252FD04FF7DA85252 %27FD0AFFA827FFFFFF7D7DFFFF52FD3BFF27FD0AF827FD06F8275252FD04 %F852FD05FF5252272727FD04FF527DFFFFA852F8527DA8A8A8FFFFFFA827 %A8FFF827FFFFFF52F8277D52A8FD39FF7DFD0BF827FD05F8527D272752F8 %F852FD06FFA8A852F8527DFFFFFF27A8FFFFA8F8F87D2752277DFD04FFF8 %7DFD05FF7DF8F8527DF8FD39FF7DFD11F8527D27F82727F8F8FD05FFA8A8 %7DA852A85227A8FFA8FD05FF5227F8272727A87D7DFFFF7DA8A852A8FFFF %2727F87D7D27FD39FFFD0FF827525227F8F8F8272752A8FD05FF7D27277D %7D2727A8FD08FFA82727F8277DFFA827A8FFFFFFF8F8F8527D27F85252A8 %52FD38FF27FD0EF8527D52FD05F87DF87DFD06FFA82752277D27F87DFD06 %FF27A8A85227FD055227A87DFFFF7DA8A87D277D7DFD3BFF7DFD0EF85252 %27F8F827F8F87DFFA8FFA8FD05FFA8527D277D52F87DFD06FF5252FF2727 %7D7DFD06F852A8FFA8277DA8FD3CFF7DFD0DF82727FD04F8275227A8FFFF %5227F8FD04FFA827F8F852F8277DFF7DA8FD04FF527D27FD0AF8A8525227 %52A8FD3CFF52FD0CF8275252FD05F8277DA87DFFFF7DF87DFFFFFF7DF852 %A8A82752A8FFA827A8FFA8A8A8527D27F8F8F82727FD04F827FFFFA8A8FD %3DFF52FD06F827F827F852F852A852F8F82727A87D2752527DFFFFFFA8FD %04FFF87D527DA8F827FFA82752FFFFA827277DFFF827277D7D522727F8F8 %F8FD40FFA8FD06F827F85227527DF82752F8F8277D27A852F8F82727A8FD %06FFA827F8F852A8FFFFFF7D52FD04FF7DF8FFA85252A8FFFF52A82727F8 %27FD40FF27FD05F82727F87D27FD0AF82752F8F8F827A8FD05FFA8A82727 %5227F8A8FFFF277DFFFFA87D277DFFFFFFA8F852FFFFA8FFA852F8FD3FFF %52F8272727FD04F82752FD07F87D5252F82727A8527DFFFFA8FD04FF7D27 %7DFFA827F87DFF52527D7D5227F852FD04FFA8F827FD05FF5227FD3DFFA8 %52FD05F87DF8F8F852F8F8F87D27527D52F8527DF8F82752A8FFFF522752 %FFFFFF5252F87D52FF7DFFA8F827FF27F8F8F852FD05FFA8FD06FFA852FD %3CFF525227FD04F827F8F8F827F8F8F82752527DFF7DFD0452FD07FF5252 %A8FF5227F827F8F8FFFF7D27FD06F827FD0DFFA87DFD3AFFA8F8F8F85227 %27F8F8F87D5227F827F827522752F8F87DFD0AFF7D7D527D52F827527DF8 %52A87DFD04F82727527DFD0EFF7D7DFD39FFA8F8F8F8527D5252277D7D7D %FF5227A8A87D52F87D7DA8FD0BFFA8527D277DF827F87DFFFFF82752A8A8 %FD13FF7DFD39FF52FD07F8FFFFFF522752A87DFFFFFFF82752FF7D5252FF %52F87DA8275227F8275252277D2727F852FF5252FD0EFF7DA8A8A8FD04FF %7DFD39FF7DF82727F8F8277D7D7D52527D277DFD04FF277DFFFFA8A87D52 %A852F8277D27F82727A8FFFF2752F87DF87D2752FD0FFF7D7D52A8FD3EFF %F8F8F82727522727F8F8F827F87D527D527DA8A8FD06FFA8FFFFFFFD05A8 %FFFFFF27F82752A8522752A8FD0FFF27277DFD3EFF7DFD04F82727FD05F8 %27527D5227F827FD13FF5227F87DA8527D7DFD10FFA87D52A8FD3EFF7DFD %07F827F8F87D7D7DFD0427FD13FFA8F87D277DA85252FD0EFF7D7DA85227 %527DFF52A827A8FD39FF7DF8F852F87DF8525252F87DF852F8277DFD14FF %52F8A8A87DA87D7DFD0EFF7D7D2752522727A87D2727FD3AFF7DF82752F8 %27F852F85252F8F8F852A8FD14FF52F827A87DA8FFA8FD0FFF7D7DF87D52 %A8277DFF527DFD3AFFA8F8F827F8F8F87DF827275227A8FD15FF52F82752 %FFA8FD11FF52277D522752F8272752FD3DFF52FD05F82752F8277DFD16FF %52F87D277DFD12FF7DF827A87D2727F87D5252FD40FF7D52277D277DFD17 %FFA8F87D7DA8FD14FF7DA8F8527D5227FFFF52A8FD40FFA8FD1AFFA82752 %52A8FD14FF5227F8F8275227277D7D7DFD5CFF277D52A8FD13FFA8275252 %27277D52F8F87D7DFD26FFA8FD04527DFD07527D7DFD27FF7D2752A8FD14 %FFF8F827527D522727F87D7DFD27FFA85252FD0DF87DFD25FF7D7DA8FD15 %FF52F8F87D7DF8A827A8A8FD2BFF7DF8F8F87DA8FFA8FF7D27F8F8F852FD %23FFA827FD16FFA8272752275252FF7D27FD15FF7D27A8FD14FFA8F8F827 %FD07FF52F8F8F8FD23FFA87DFD16FFA85227F8F87DA8FF52FD15FF52F8F8 %FD15FFA8F8F8F8FD07FFA8F8F8F87DFD3AFFA8F8527D7D7DFD18FF27F827 %A8FD14FFA8F8F827FD08FF27F8F87DFD3BFFA8FD1CFF27F827FD15FFA8F8 %F827FD08FF27F8F852FD58FF27F827FD15FFA8F8F827FD08FFF8F8F8A8FD %09FFA87D7DA8FD0DFFA87D7DA8FFFFFFA8A8FD06FFA8FD09FFA8A8FD09FF %7D7D7DA8FD0AFF7D7D7DA8A8FD07FF27F827FD09FFA87D7D7DA8FD07FFA8 %F8F827FD07FF7DF8F852FD08FF52272752F8F8F8A8FD09FF7DFD05F8277D %52F8FFFFFFA8A85227F8FD05FFA8A87D52F8A8FD07FF52F8522727F8277D %FD06FF7DF8272727F8F827A8FFFFA87D27FD07F8A8FFFFA82727275227F8 %F852FD05FFA8F8F852FD06FFA827F827FD07FFA8F827A8FFFFFF52F8F8A8 %FD06FF7D27F87DA8FF7D27F8F8F852FFFF52FD04F827FD04FF7D27FD04F8 %A8FD05FFA827F8A8FFFFFF7DF8F87DFD04FF52F87DFD04FF52F852FFFFA8 %2752F8F8F852275227FFFFA8F827A8FFFFFFA8F8F8FD05FFA8F8F827FD05 %FF7DF8F852A8FD06FFA8F8F87DFD05FF27F8F8FD05FF52F8F8FD05FFA852 %F8F852FFFFA8A87DF8F827FD05FF7DA8F8F8F8A8FD04FFA8F8F8A8FD05FF %52F8F8A8FFFFA8F827FD06FFF87DFD05FF27F852FD06FF27F87DFD05FF7D %F8A8FD04FFA8F8F852FF52F827F82752FD09FF27F827FD06FFA8F8F87DFF %FFFF7DF8F8FD07FFA8F8F87DFD04FFA827F852FD07FF27F827A8FD04FF52 %F852FD06FFA8F8F87DFFFF52F852FD06FF5252FD05FF27F827FD05FFA8F8 %F8FD06FFA827FD05FFA8F8F827FF7DF8F8F827FD09FFA8F8F87DFD06FFA8 %F8F852FFFFA827F852FD07FFA8F8F87DFD05FFF8F827FD07FF27F8F8FD04 %FFA8F8F852FD07FFF8F827FFFF52F827FD06FFA8FD06FF27F852FD05FFA8 %F8F87DFD06FFA8FD05FFA8F8F827FFFF7DF8F8F852FD08FF7DF8F87DFD07 %FFF8F827FFFF7DF8F87DFD07FFA8F8F87DFD05FF27F852FD07FF52F8F8A8 %FFFFFF7DF8F8A8FD07FF27F827FFFF52F8F852A8FD0BFF27F852FD05FFA8 %F8F8277DFD0BFFA8F8F827FFFFFF52F8F8F87DFD07FF52F8F8FD047DA87D %A87DF8F827A8FF52F8F8A8FD07FFA8F8F852FD05FFF8F827FD07FF27F8F8 %A8FFFFFF52F8F852A87DA8FD047DF8F8F8FFFFA8FD05F85252A8FD07FF27 %F852FD06FF27FD04F827527DA8FD06FFA8F8F827FD04FF27F8F827A8FD06 %FF52F8F8FD0927527DFFFF52F827A8FD07FFA8F8F8A8FD05FF27F852FD07 %FF52F827A8FFFFFF27F8F8FD0927527DFFFFFFA8FD07F827A8FD05FF27F8 %52FD07FF27FD06F82752FD05FFA8F8F827FD05FFF8F8F827FD06FF27F8F8 %A8FD0CFF27F8F8A8FD07FFA8F8F87DFD05FFF8F827FD07FF27F8F8FD04FF %52F8F87DFD0FFF7D5227FD05F8A8FD04FF27F852FD08FFA87D2727FD04F8 %52FD04FFA8F8F827FD05FFA8F8F8F87DFD05FF52F8F87DFD0CFF52F8F87D %FD07FFA8F8F87DFD04FFA827F852FD07FF52F827A8FFFFFF52F8F87DFD12 %FFA87D27F8F87DFD04FF27F852FD0CFFA852F8F827A8FFFFFFA8F8F8F8FD %06FF52F8F8F8A8FD04FF52F8F852FD0CFF52F8F87DFD07FFA8F8F87DFD04 %FFA8F8F827FD07FF27F8F8A8FFFFFF7DF8F827FD0CFF7D7DFD07FFF8F827 %FFFFFFA827F852FD06FF52FD07FF52F8F8A8FFFFFFA8F8F827FD07FF27F8 %F8F8FD05FFF8F8F8FD0CFFA8F8F827FD07FFA8F8F87DFD04FFA827F827FD %06FFA8F8F827A8FFFFFFA8F8F827A8FD0BFF5252FD07FF52F87DFD04FFF8 %F852FD05FFA827FD07FFA8F827A8FFFFFFA8F8F8F8FD07FFA8F8F8F827FD %04FF52F8F827FD07FFA8A8FFFFFF27F8F852FD06FF52F8F87DFD05FFF8F8 %F8A8FD04FF7D27F8F8F8A8FD04FF27F8F852FD08FFA8FFFF5227FD07FF27 %F87DFFFFFFA827F827FD06FFF8A8FD06FF7DF827FD04FF7DF8F8F8A8FD07 %FF52F8F8F852FFFFFFA827F8F827FD05FF7D27A8FFFFFF7DF8F8F87DFFFF %FFA87D27F8F87DFD05FF52F8F8F8A8A8FF52277D52F8F8A8FD05FF27F8F8 %52A8FD04FF7DF8A8FFFF27F8A8FD05FF7DF852FD05FF27F8F87DA8FF7DFF %A8F852FD06FF27F8A8FF7D5227FD05F82752FD06FF27FD04F8A8FFFFA827 %F8F8F8275252F8F87DFD05FF7DFD04F8522752A8A8F8F87DFD05FFA827FD %06F87DFFFD04F8277DFD04FF27F8F8F827522727F87DFFFFFF27F8F87DA8 %A8A852F827FD06FFA8FD05F827FFA8F8F827A8A8A87D27F8A8FFFFFF52FD %057D527D7DFD07FFFD047DA8FD04FFA82727F8F8277DFD08FFA852F827F8 %7DFFFFA8F8F8A8FD06FFA87D2727F852A8FFFFA87D7D527D7DFD05FF7D52 %27F8F8277DFD05FFA87D5227F82727527DFD08FFA82727F852FFFFFFA852 %52F827F82752FD20FFA8FD0FFFA8FD04FFA8F8F87DFD2BFFA8FD17FFA8FD %38FF7DF8F87DFD7CFFA8F8F852FD7CFFA8F8F87DFD7CFFA8F8F87DFD7CFF %7DF8F87DFD7CFF7DF8F852FD7AFF527DF8F8F827527DFD77FFA827275252 %52272727FDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFCFFFDFC %FFFD6CFFFF %%EndData endstream endobj 26 0 obj <>stream +%AI12_CompressedDataxis70 x?*R dVqd^g8{rmsBJ߳`"Eѭ%-XF8gOëE_v1m_ju{b՗?Փ o̍,7񒤋3|X?}\e[-گ~ꫯ| /鯥 ?Lun]_-G^_eQOG׏/^ܔo2+21-TZE4dsy{X~n.77j74_+]V?{f[`,M|\aZ\k]f\-`绞y~'՛ru̩.V~zg|w 5;h=Ap,PIx$¼W|;>"#tzA)h'b}}^?^ ş_~ج< 5:?_v^XU<|Z~l5SbYosWO qeǾ b0":ɜ0 tnVl'֏J`$b8~]^y0f +J y[_.|[M{  ƛk\Gk@=_s +<~QCo7 ZvǢ'U~1Z.pbr_L痷;@ŏ6q{bη̩r nbm|AO?]GY,=kj^\l ߻ ݸ{7ܕ[o[w +:X\\|_fqwvZ.>η8?\@ޮ`DO*vdH/ufwxw1^x|#Pd~Z/oբv^[@S?_o.oW0z] 6/]ky9_wz7[-7*7 ˏ8Ő14fL/6#+"S I|=W^-X"Issݧw9CtzPOi~7jN\[€ z/>{6W kon}a1c:7 ;ww|ӏ|ӏA{v?ʗR/-;|m~I 4hqv~^ow'~q9/S8g rޛ2kr\Tn$zh]BHY= W%X} +N 4o"d.q-竫w0kK Khq4ϋ=!,Gx/Ӌ",V (Eo^:yfF𴾁i `DQ.}~nn#d99z, }cuWڃZ憷7zl6+׸\rjާ{Sn}<:KiyM|/{}I% +S ?Є^.w D]W^\5?!\-^)pk;POM)NVQ%^j9.hSP{4*hD &D$-y\CZ .(Z>#Z-)k4< yw Da1v89( a&iOa5Ly> Ni1 ,+̬T )&<1 o6E"ʃI!4aH=eOT/3S{ʤS*=ea|OxS$XPЄ%AO"@#:I0`:J1܈?nƐ:iGSu"rF,`!h&ljUADXyD"94$4 ?),mXcdF3/S%hh]Jd; +d|D}h<9:LQ/@l -D(& &f,XЄd$.g%Ŀ28=H9U(6JPpL)9 C c ᔡp Ao rȂŠJCi :4 ́$v(,(t@HFvk7rpmDI ԎݬJ7q;h)OE&}!1PV[r +sBͤTFq!2 +ϘĔ)̜$+6%PIeǍql2e} Y S"&FL , ETAԄT#N03b4r?0"l!2" +46,+ߔɍ#:,YTȦJ,au2Rp^ˉ-olqIX"*ZIg1VYe 'lAY.0AgJkYdclllMuV:KdR6|䩌95BИ@,YsfJ&YL)Ҋ^v$ɑFA,2VŲ^ +$*q06XqZrg;ҡH8#Yvd s) {7}b87[C4֓3OIEhYԟh9vfeY3c +c'0vxh|\kg?%q~2~aUk9IcE&\H8FRrK7ҫc"F 򊃼9lnvWRcRq7g1JwJw{\S*:rRlbOdFΜXHɒQZ"p ' N9oH{EIsıW0 p|֯8qνp⢒{n$5 $+/2wZ>š# q!r2͍ Y^teFBlĪH.X"$a% mLDU6$lSD1IflFA6%&"[n84،<8 +3c"C y&>Cj[ M@B\ȦRC7fIYq塌=vh% ' gba|cPh(, gd&t;;rgnF []JG3Yd2:?EY3e TĨ#ZScfC8'GIC6&cf_23F o,ђ<3CǰLZ:7BXg=Hd}䟡),Oo,l3 4b{i \`HAϱ 697GiXrcS9QrĶ&ٍ1ް(5(J!*8Z0e +#PiaNHG2}G7 4#!Z$b k cAskNzPD>/XӌjǶ'W)%W\bryfDQYk8k|)bcs5M19C/vV6FZpVUF~֬ٔ (Iw UVcUYkt2$?%uYՎ;ޱgU< Vf"l{V*bOQ@Tv)[#ZI[c +=~%L_p\ʋf]tU^4Y?]ͺݪWTʈ Bb͌/VyDaevɈֺ%E&TW RЅI%&LHG.`*w+ۮ!^ʅPvq0U3k^_B?Āq 7kڄXM sAgJfMxX:']fMS(h]#CGڢ@*Hȹ5(y:`UT`m(Xۼ?3vkrE2Ljs)\eMUD`AD}mE +XDyML";٩ny g趔%r3.S^RwJ$NL( +GD}5R-"ISȝǞSKcA"@Ɲo9T=8II\ E"As= +TrK>(ʡ# \pR\_l)g92'߆)Znrș(32X'9Fpڲg]cBb/0ބVli2A$VQ\㔨)G6&"1F$Hz999Ad(E9JGެ Znṉn=w&fڪmK\@BSAx' zq' zq'$ zM՝Tu:K$ +5Ȱ2,Q}&җi <8RK%T2_-r)J8|mA*vf.p3vS7PxTN~p 8Nmةb-niL}FPEѨlK@!Hd#H\tUkLeqTx; )XP($@U;ůN0֊ƆڀV$5#mn-Wic =0j;mѩr`!UFΏJe7Q*n* sWsVˇN vCMrFjdK$M2(9m ͉c7LiK*F)(nה|p|$C$C @b"g$E$16LRc +5uy x~q@igr*¡exR%k*"V iU3 8g%[.$H@ZIoN?,l~µƦ[a2Y Ϊ+X,XƋMkn^:.[ hx2;wݽnOgΎx:n 5>1X8=5Nii!τ.l-R2ւbPS  0%󔍅 FG7U;C=e"!ځGņOaMXûF1_Ar2!Lh# +0ӄ9i#9 ,f;0!)!XdA LKmt'"_b/i.5[8>i{~nImw~nc}m@"'g7)U%i{{ép6-H@S44~&/)-CdCYӀMxOͲBR,&D`, +=V챂}X,Hl˱/KV1cqI*U/)HJ/QaʔB| +57Hb)( *"{CLM{bT^_{-%'F({Ό "?GwgN;# >@+ytkVk90}HL7rѼ#% AH#-j1lr efBbIiq˜>EkqqH6#Kf„)"or"#xA~6/!rnDG8J51 vxGKL0^qཙHB[vLK$ + |F66HjbQ8W + +)Lb %R8P~^RLtȉP>AVEZ +y\d#+)4"4q^,KǤnJBd2Đ?/(xrk1 t"°f邚IZvRyNvְg ;Aa'24IaZR);Ja"|){?e7EYqTpTeȸTn"v&[TʮxDeݙOm%4d31_?el]n 4ވ[/F5wC9*77tTGgbbg.B\؈< (.EټHcA^" +rǸmآXO捜I8̱X]t~OvRf +op[V+YmHaDS`k|5[$xKYV}d?6eFLG>&y3 +iԹ;ecblhhZ_:|SS0yZjCr +7 N lVW;FoKVצa%v;g >1| 6`pTx*gW2**0FJ~?@{&!%O 95r3yU ;LhKa\R,|WjJ8#9ZN}t<2S&w3l}SJ8f30(ߐh&_fMOs…1B3sɧ*slh )e2MG'qa:6PQ%E:|RR>;:&MǸs)&)\Rm0ɪz:̵0}Pf/ߪȮW̌Uro,y]9%R-2wQbwKU`fiI)o +-YZ𹁦д rD[ά&=+`|N@f`XlEZEKb*مoZ"CIg,OSc<-J(QIDǘ gH[c"($&C" 3, V&`uJ(̩0lĥZJnGgcʺ吓) +2*LҍԴ]`a5t)&H\JnrMJ*p*fd'O/籘Ҽ⬲sB N\ aX̂K$Ip +T )y"5Þ8!d` #5J@8pĥnh0P+a%r a~U +Ǣ^ƍt)Թca.S˹2,[g^L[1gFfL{EivՔ5f.A O=L6ӳL3=L6cNʒAɤPj:t|V uڮ9; ewQP ,}Eb2Ux`P/C Nә=bdk<@6`f#l)*Έi<8Mc* Y.ksw74Ʃ"]`IP'H^h! +9mLF@D ۱ |AA|LGB ̂Y{hC!5yO3CZDgA$HõkvN쌝M}NrlPfR"=>"9W9ঊ2AVɇ` a4_t'd P 2:'nh Pmqr{g:_}%JߠQe4~x\ fv='.Y@fIqYln\m`&6ybL'҄ңOa3wD"_:s|X,ښ(LsykյѕcvqJAbTbe/e/JG*K`$q$Y$SJ9%ZIpfM^݈ҔR/e7/TqԔF5 T]ԊM"M +$& u&\eJʦu w^ Q;F&8q.^l, +I)bs\ Gd.dm2)Fk>sX#i6G۪)Gu݆213aRGnv;R +(#xj[L&Ȟrv APxeA0E͊oMuSڐƨY-KoO➭ަ62u*En;L8YV>4E v'8G|iqiyFݍM{MUaK뫲1HӮ6mdtZm|ty)\T}8ْ٢>E…0s46]ym C4 ܇u,g~)sdsQ󶊅ޗw]Cqݟ{rX?範e1*^لUA‡2(!my\+ޔaG 'JQrJihzYs*@-V%^E2#+^ϯW'Adn5K&1AD'cƧpM%|1q BiŨDbpP5tul2)ҐntXvgjs4>Gy4U +LcGhf7zJ/h90m2A撐owiX +?3q#@}wy)`&.XA>gkt푺3<;G_$ЎtvcЎ*A.;(է Svk7 éLJamQ:wUO Ga~^828;w}scYmqnM{VGG {ްzQSؑ <JiO G8)s.Afei|ϣcW0-`.![H [QNv"#6Ћ(a[] Y +#zb7YF*?ŕg–[dhX>S{V>,#V`E5ͰXN&4K" DVAV +gXM]|za6B  +ϵek\&wf+F:3bA qjqG6saʵ=sbG88eչaOE[˛k۞9l88[(4uPB95Q6`c L3MJ'놦}d"eK[TPLБ[z\Yg}>m Ȍ#?hf(6vQ6f# +,Gv`R~6diJ>X&~Ǎ)h#ҽǔ󞳼g6Fd.ИNҮp+T8̬&O MBNj2 +JEPŸ̕Q8(S] qئHa$(㠌Loj(NO3߼PfyO_ecoL 0Z$ؔ|`r!℧#M%L< +Z5O6D'%{0A0'S].7?5Ɋ<0G2TK.^́He{Vxw rՄw9u@9[-J"׾f^3:(2еff( &~әvdt,8Yސ G4'N$ }wq[=;xC;5=r$q!; RuZj,/)s#}^"j[%E7p2pl(<q`(tl*JNcRVIU7r[ J` Ƹ[ + <3E Ǖ2)QCjCH!J4BD"D.1Q P2bFU'މ +#&N( 4!rsV6z=!DW,,Z# ãH("2ySr`A!%&[`wϗ/ڠUJɘ9={F+zR!芰iv$M(_LQHzO)I@9E G[|ٽKݔp~bDr4;;TPCBT3T]a&N" +qOb QqilC19>I ҩݝݩݝݩݝݩݝݩݝZ*tС©jZ8uX-d"e`1SN?JޝJ +?ѐ/}<,FXXBp ; ?D)HzJIahmԱB we+4 +ΑY`~}@0ˍ̚!f`B0+l +7zy8 K_Εִa@x<`ߕfP"U;C ,-x6pG0*TA< t)^y +0w 8OMc=(OTvY$hZ>rو[F0{y'Q"ތePaa)\fPat<*@S^Mgt!2y f(:te (K?ǂ-sSUal3NŏyRl>dlbAC\qz ʝ K'ާYii +9O Qs?zŽaͲt!M!M!GS rG/<~`:OYゎWt|lBt 숎ef*tСB +5TӉ dT< koQ ŝNq$G'1i<0fq5fﺚT~ V4k/UȜ2WX/uc`ج9a.S>k5\bd޵a)>j`|n[d(0vɬ\&ORo#l&3Mn`Hx%-Ig!MHqU8tcؔ;-.l@@&Q9)a>zB!0b#[M +|cZTn_ cF(_ /H*׭iSEetl>3{C[86Sg0^qco܋㌽O\Q"pULRJ";0;3cp DCFzNa0q0 CQ;#a*8н*4*a? ŇA)\{>l6AFImj#sG1 G>"(w珸3&86Ğ,Tމ1, JT `HIŔRf_&8Mi90. +ș K9i'D(xf2 Jÿ +xJR y0-L2e/1ۈ73tabʣg(\|T8 ^JN6 ΆA3{ '5)e.z#{b{d34GčGA/,u;Ŏ#@3Pfy⎡yKyx#^cȝp'؄Ǟ`S^x#_؈2ei 8;~((35)S` .TRPx,-)(Ȑ(RlJ ;Ņs5 ȯ)bn@ nTvt;vt;ַC:TPa+T5>?Jco3)v91qUTPQJwG{\s=&B +{ͅ~& % NH=1" ^IQ?>>(aI?'[˒d֕ʥ|5zM)Jڳ?Rs}3AL>@(8k c'Īpqg|ո!<سy +ªyTy?VC 8E:#wG6W|T1itߙWeEM=^4ɼII펠뎠뎠뎠뎠{#j>4.σ[zKx_Zv}x_=-^x/k}Ypv]mpv]mp]e +*tܶXmLL#0YJ85:[ΙO1|Χ+fAcs=hHV h,cՐ x܁R&xi;TA3k1AX ^[9"b^LRڒ@B6M6MMEIN&8|mzpKF&JUKNaV5g08$X'XHVhFx8G4&QHt)_ 5%a:ylNffP,Esv-0 0A;PW>.w+RK)u:Q>?(j,P5"C4TRb*C7Cz[gDx3?[xhA |uE?lC-PG/Yi*&G rg7 5X>j%3z, *Y]w~?+?wvur~Y_Ʒo7_^:HT/O:y/+ 1t:n|㿣34yLWPUyS>V>#;v^)(Ct=Yv9 +/"a9<'͓t\: M!{p-Xx|JuZ rXW aݻ^;Y=J 2 Cg_5gߓ&ZD O81psUzw{|>e 4u=\V۳G"I=Jw ܼ%2o9+!i | a쫆u\!\ ]mϾ.pd ðڬܟj{kǾ^=!|{· aQmm±l –w:s| W@ھBbInKu3 :4[~ǒ{_TzCU2r,?#2c>;3⎢f*t(j۰JBhxKĶ}(cm;Kl1c{_TzCx,hK 9X7޷mb >4W[f!F XTmlvپsu`p$=M/{Auؽ^(D2a%hA%YK4ml!鰄=I]wżC]L+W*WlG2Zֵ)' E# GI+sT^ǩ+ԃڴ$hV\:`.E&kp)ޝpOT!J{{`OuKXJόžO%,eIaq>T5C- _`q8-\%4(akpg8󓰸A.z{Zj!w&@3 ~o8*x!Fi.muW<6V«z4XC/,5SI|NH5@FT=:7\%EM%RV}bZ)")^~YĤ/\ rܬO˾"wz]?ovs޿^Fk4A11sQ31$.8$[\:5*03 祵sh;DFVs)r(PFSHiIk jEDa TTevvTk)mee Gh 95.sq!2Kr͈ :y~\-c{/|.[nW,.w +?u[Tֻݧt fu;ru]}F:4 +P=e2hct.vIb!OS;#:`zt8>8woodY^TwH]1;'n߶1uEjo? )pLr#ZTY^x8F@iclo7v񿷀SS'py[.rukԵ%\w( l>.f{4T'SVe +#{h^ono zU3\,w,5 \Lzt^=:m祈Em/N3czayQ^+88^+ƚNԽNԽN{ꞌ_҈ԁhAm=hcza~}HZovWKq>t]=R:oonAަ,yf@\kkɞ*:]_VcVAă|;ެov`?x [ݾ_.^_WǛ|Kkӿ>n֋@Nsr|yHz~`]>\/vڦ +.y[O ie +|9` ?i:~;|6r"?}ؾ_L`%@+?dCDvQ訤!`im 8G7hX j<֦j]jz!$oa֋vru@>?=Cl?~ج6?_oY{FdvuZ1[bH{;2BNR d#G}#vcek ` ?NV`eQ7ƿyg"r?l=0_׋>ts+SY4 +fÍ[M{A2R+x\ rqX゚lף^Q>3jxA~Iv(E4ċπq]ۤwl}% +p,6@+yZ ]kQI}W'bvAr_C_]-w?/vs}oBmXe;e࣋nr<䳼=SO[:;xc[`5*m+'r=ڢ a6  ‹6.wvP%V2̠ڐZYW 2D>px4jWϝ>HRΝ͝^J7tΝܹ;wrNLN&*5Qs!w.΅ޝ s!>rp|2Z=:$68:s7wK_C' bWvv=cOO~,=tfVx|}\Di%ZwCuv:O6ݦtvnmj1V#o:Mgy/£:#iFFPKرmlo ʡZ~/YEx3 l*<(lrg˝wʝzV̳u ڦzrO Nٸ[{~lV]vOvG#b/.ZL!}XZ9L@@z#8h<tQd}\^#}ڈt;#8k ~Ҙ2ieLNj=/ٝ*yFj42vF`vFΨ5N7j/Ũ@:S؝Q3jPtFc@l7o)*fhmJ|f^ȑVOӌ/z8ʶV׷?Jq#fZyv~m{X_T:irwyP}.,=J%;jin|xjҥnR7%en/ZoNMBե.:_F_nooWr6v JNʖTgbԺoǏE/.~?Έ>X;h%v o {fqSPn|1nVE"u&;uSNWbW)|F)|Oum+|,JރQZZu_u_u#k|ɋZQ)"^HZkzô_׊8w ۓI2^|2^llnt*3꒒L߶or66}oW߿qruKݧ{s-NQ'yaLcejeoGk)\,usһd汹-Bn//wJ4??v}hsX}4G{~,=tfx~j瞑eҭP?׷cOTJ +s$xijɒ+=95ᖥ$'̅,.ܛ g!1f8N\ĥN^:2/2e,|eͻCrzR)CVV‹ lE@` xU?p IC@K8Ixg;l]m/hlPw߉u>;twJ]X4>i `qKyZDfdHH/Hԥ=($K_YȔO Q+~V~ ]9w-v֮\JڣvE'*y{+\H+S|>y)iZȎcTً{Q +YU:Б= "]ST *"x,i9LADi׼PVeZ(r^h"$y/đDWc"d}NI:Ǣ_㫠 5lٯO렻:ꌏ# <,͆ ̂ ,fWڈKoΘ`"lmԍѼ75KVi#+e\/LS=5H889PJԗT],iR h/He@pqDyPх~ںd${tF2z:lt,dS/Q+LyI_dL|I?9HF@5hsc?<򤃧y2TN%W),`)jS:Ft(ȜnHTT2j8s`a:)19h:X%xl L<L+Σm4^x F0">H&qÄ62iR!;F$\W( f v4tWkC0מ4b, _Q,8l,ό&B!EqUFybҚ~$DFQfsnXg+37|%Fo }jkT&z~W`IT XIsE~H,HҞ ǯ',W}v#[>ǕjM#ꮻG̽xۚ rެ{҈"qqPC$4|l?0F[.(`X&(r@O$yRssndW:>`'yξ•J:.ڂVNՍ P@j$+X), :۵{D,i~$ioH?*v$Lk#E`] %վU7%F(điJ@*Ґӹx &=?8IFbe m`r`5-yV5?h`QgafN}F I4 +$_H$[k4^^^ٹ3x8|wfr4oƫrxݷl/~\m. +@o_co!PuO -A#K%'e\ e2~K ki}ܱjQV ңs4IPD9N@0d|*?4;dS0e'kt4E?M"hNtQC&:l;ggοة;ggl9yhOgxFN+~.S-+v>[A/4l_c-g3EES_;:@K̹w], KŻ]'dvNig,{NjLɎr%"W̓7oTaHoNpқ9:S?q0RaXn kt +?x߃%A'H``Nx^/E&.Jg<%ΰ6[;8;o&qI F[oξPrso%& )IS Oe(O5׻-az!s׏ `ws `XxN%~!1K'?ʆ-ҧ jt?aC!ՉQ}Ҍm4*$Fy[%| H "2Qt\2ڣ0P42@)ھ2m= tԃwwg +)v}* E3|8IJ&Rrڊw/}w>LJme,[DMmF7R320 +|l9"5b/YB-Dmi )Q1ǰ89DT TťH)|:U\$zt«#LhUqf3ȜkJҁ$%bL%qiTnh*4}*mRD KWF7?^R8nԩuD/ +g%xJ7hހፃ$ax0\Ig+g$i?cnSXa+H fk [7J%Ya6[)FI[8@ Q;ՙ,l~$\&U +&&6ʸ +$fY32B$ع[ݫj}Ha*?گh̽S +@f;)ar5-4y,_ jU_ߪ7:/ΐ=6 Cm@.yrx1a5P#]Hҁ +_m'*hP1MH-άE` أd($HYxX ÷ o|>HD/B\rhCY&YɕNqi L $L(Ђn!@"(VB B' j +qU!NGOQ*.~++c[Rcl7jEV.F3^[|i=؂IJ&%$Zڤ4L b//ؕB{T$wS2%2 +-v4 *ZrAM8WI䪲h6Ke*Q$/K%5Y@l:&c+H25݀&+I +&aݳMj]ȡ) Jȫ-Ɯ&2V1W{BM̎/ V #B|aAKC: f4(꘩0.edqd"TV[rأ\Uņ{׻bfmf|x?|ipD@TF-[SbI,qVV U蔏y(PR PncpIz@ H +h(l F0i(E7< DY(Soyv7wen_¹]/LxIgti0TcCNR&p3-}97 ]ެ@KQ$'Si!&綽 mPU`Yd #P$?U9Ҝ<7ϣb#Є`ܠ1>ߠB (rF^BtuBl͘M9Z]@b*=@&=T"ۃ(O=gOEWlaHsġSJ0[ +F &t"CCun-xN%܊TaК 9#UJS ɥ~44XCYeY0o50e3WxIdQu{hPwv:/)cT*\0P4X*C:+Q +*'F1H苇R +>V;ԍrsݩ=F_=tvd@mC;T-bSAG,#"b*&M]}LzujOւfk>Zՠczk֑lqB(K8EspI['`=з"'UtAk!;ekDL"@+1jc:uFCU3l< 5+:| Ѐ$Jwp*du\ +IJ*5  pa Y3!I .CȑYM + `v@8E)tj># +Ԝa| P5s::3Sx,S*hH2`$,X<%ARh/FOAp䄬8c(-tI٭$K6=$A7t A0CXt?Z4"4Fi,KۯKTVVp -]JmXxRwʙ}]wF<@ xEF7LI6ZQP _"%^F$ -9N BLj~PUgC4Q PSi|wʬIX]Xt0˛A@%@fAi]JEPjn x `@#}0M|T#{P$iW's.O g!^^N3d^Q t80A 2Tguʦn> ,LU]Ugbwfj-a ,tE6Ccv{X^4w"2@>%8S-f{gXeqC<Tm#iK/Y+s(F`߽7lT @,rmsX4 2D*9fWǒ؅p͛;v۪jZU(pW% RTnk"`D44~5"hv S؜jF7%o|RN00e vdic~~lh6?Ao2څ -S:wR$6W.4Ms -W-|CuudኌB;CݓiY{x7Yٵ= ˀA)0MXUBaC MW"HIaY͉.245N6TPLU 'D /*{,S"̞bw#EsXV3Mȩħt3/d4)hC [_hl!M|} }1B{r9PK Di:2QP# αì;rI.TBdǡГbhe*s»^ۗAV녭/)  H[Ԅ3 - *21Df>2X@c:p2 iucVEhjٽ(e*s/^ۀNbQn ZGM+YR|€D9ZB)]d2AQS8j&]MD -vWv5"QDGЂN*a9|bv5{vW#kRfkK|>)&O2ΚH6r -trDb˚j ^ +!G#VqR>{"s 3|8Lb!4]P{l]f2/GDg̭DId7JÄS+$*ajd+ - /lL&mdpAz -^Bd8n>( EE5H4LpKz^SKqǠ%?!1JHEI/0]64_䐐Y`L(BVnG.mu@d]Z^h+*7 - bBSSL4wgj< Y`!MFaT6N -t(?; ҈*%;[SThʘ@eL^ J pX.3`S珑_1-A`(̭ǧ3 ? -L:,U ɟ 66!ߴѭ8xQ +Ơa]Vb0 I7dx4"K g 2d\%/~7+~G"-' -4(1bdUc |$o@ #&҂!a\ěm%$opDZ &i DQƔ_%SEڌsi L=_AQ -7c@ Q`o*2|ΓL:qquO(4VS$UשdF |Q|6t hf])K&bªeSU3@:9 qm1jL{71A`=3?ī_ta'D])?gom" R%(`r;hA@svCK2b"9P2fjG&/wKD $Q3$sXE[IPP6 :ҒC1)VcFM_$ʐAGi;3<;.q:ik2~QO։A'PE+ U +mDg(l,tA$ F+h+cߕ` TG[Kdh,g K\ƴ3r9PVq s;`O9e &!S)3z#?o^\$xNGmD0yh1fmx$O2> Ե(,PD%iWhPjvuNGB̍ 3uq;/aH,dG/\=`TQ -:j<(R(RFv#O&Kؠ3аT#k8\bU`'幉Bbʮ )aD$l%34X |1(ɋ\7 (aɡ"xt -SKrk@FeW11(DOcW2YFr#%$gYwpMK)nl"n]TMorӍw\*.) h4mV -ifL誂Κ|3_DByV~y -c Z,90ʏ[UFTU*-d%(9?cI:%fGuΪhe01ry*ݜIhZe^& azH;Փ0,´@Gh&&cZ3 -\"LxY;R|{=MOF([`eM$T,ENT>(HҺ@sɞ& ,# [+^c)R! +%BR %hKazޓ2{ - Ӎu]0AE_ka&ClR|:@gӽM$Ab;%,'#;d8@DkxsQѭ$2~HN0YΨ"UQ\V$ M@:0.)e))YP&&A>&` ъ6Vj B +THD Tp1xk7|y ( #D khKUkMc9аz$ -s'.Kw%%.h8:sC'f՛<vY5F,Lk$P;9X$/rۏ,dQ]=ϜZ 020D灨.a4R8 ,9M1jITIF/eBs8L\Jk։}@φr{`tB.ϻ f96! 4(u|zT}VLI.7LJBY z.) dE!A4Ef3V0*(ѽ@(}YLX'8@Vh6v%kv/kb<Ȝ&z =}k3& ~^0Io쀁L% A.uZ.0ScyLY]gq&@&'^Pca8 k40FbzaNEɭRLĢӃIջX&LzS5ǡ*˔fG%oY9di|t@#"I9)%Fgy#!,@2Qq+Xe.|(\9-ƧɫnE_Ԁ$@Ӳ@wF[HcFãX 2ܘ@wcYFz$ÇAfXEڊ!Dih[Q%f"ӡDL*ZC2؞ P3)t ; 69cMaS[1MMw~V}HȽ†_6"$Fm$#A# ?'=i$uVƽO3r8W~XJ->%ut{{WG8TlYVJ=h,as.bݝОܨ`/&CG@չLWMsjAi(+^#0T5BD5‡,M:ށH8ĘF|s{q{NA!X \.Ūf Y))Qix,X#S/m"VՇމ -Y#3˾3`*<};g}#ՑS#oMz9赃0o]L"2ޞtՠh$UVe^/i|N)^DK+%7w|p)h:biΑqFjHMȑR0GK=(xPNNМY_9̍Qtf߇kpHYp*2fûyN~wjJP5[̰כc1')'M8%"Mby/(`cd)Ef~*,FwĈ.ÔVІW-$!a1k5ci!EZnL0gbκŜDC_Ud>4X70b}<#G>ˆ 3bwÈaFl6#Da/#V0W0b=#f8̈].Fj=<2 #nɈy[7Ǽ.Qk[2"%B+EґᑞXl.400Y +}'*9(Ă&G'ew~)wiK覤Ú{G+9Mr(+#ߔ>zcI\c~t`C§hOݿbhAk5{ΞiCy7Pج {6T Ef97U5lCja#đA p?+2&J7%#EoUpnu>&$v7v$|PŻ^wo/-~_FgSySĊOu_<| -g p7P'6Sno6 ^{x>ƴ_k1ZETGf[5O|UZuJ[ͽ2-c:u}$(lW3 WC -: zab}w~L 3DOV]+ۍqo5}1J`7@4y֟9t% -~ZWA+Hj)b1rYQQ[ů4> sݧ_#o8=F,I9Bf!#\}!oRX]kTMwm}2])[R}E-v#WCf8~7+[ 2R:C8J9+1ھ+ۿ$OBSD-_oXV,.+2+^,tL'~N^!Bm?}JW6$N"h ٷݍr -24([܂$+k -_Ϫ%̭,PP -|y*TaF8M)&v[:zϾT:?aIMTH(U^/)`be=q"7<$ߚw Dp*km6$DU!pޅzv9Sߕ[Rm l̻':0Ke譤nz-ρk쉽7ʭҗ -jj(t1qQ&rqa?ROg>ho6$GWiA G'5~D%GMB*%A%?TrT?,+&#!pXQXA.?\j\~rA.! \~iȥA.? mrIFrEfAs8?vO_0|cldx>IW87e߈~o|ßN~pB7'o~0o `C؟/J?,ZCȎg~t`0e4#C4 +x [ ^ |C ŮVrՈeܕпoY+|/{FB>_r3[Ȱn'R>௑ c^552bpH^dނ(қG 3҃)z5߂ˑzIL>I>>H(G4WIej%VB 3 ^ ˜uVe7-K zWa w毪2uPß5%2=zphQTM0Ɏh?jhYVSF[#)&o~#7Ԓ*..ti*oUdU]D|tKE^6Ut:~_߈8qaWyy.[hn*  S_Z~ Q__/Gڦy#f3v(9F\?هWI\#u|&D&)n7oݭn m@Wn&/z8i7U旺6W5qZdX*gU2͡ ᾵|D4ۇA3}lWy8r^1(g-^_SC:_峟aǣr~Uz]煂{s{v<=Seeo_v?/?$n p|k܊v+rF*Wbjl_|cm7ڿg"? 3 n#z5n!HKȌp9(`50,tW%j0=9a -b#E +^-4Z S^?g?V˾xr }^XYڗSuneU xݻ8;>n88͓8/GDgK)Bg\lԛ! -Y9 C6MnKStr>X #hm9S$OU_ -rI= 2ˋjsRL6Q -+wN}u3dhn(lO~ϲ1󫵻?PMk$~!xB[+-`;j6ɈD"vZwN[f(yeY$ȸu-Ԛhi@ltKu^ -~~-<xyWˈ܀#8ZUmؗD CGK;p|zٖ~;wy\72ʽ  #v^).4dj:deEYT`pw c{LY -ԬԹ_K$5.B>MA@dUQ -] * -&TggZbDbFct>Xۗ" eJӲd(&"ʵ -;g@g/'E}cX23 -7P4woy员)xLJkii,`ꊦ[27uO_|?O??#˝V){Tbau2GĎp V~Qܜ@$^ /@8{ E/Mei<:h -3haz,V˓\rښ0➧z'j|Un_^StU=C]ynwN]$IUPltosi>6A7(7*u1p E5U y, -(b^iAW5f#w2 hʾ 7uNjnJL8J*#*jdu,loHt,k0laxzf$̖dM3mo>2gU F7zXH+Q3YkakYS5:UtwRN .]8i\E|C55U=[¡v %%La|h,K _ ,xzV%e`g[ a5gλe@x>vm!@λViT7@z672Q7SqDOur(i -at)PL]UbHa22"2G:Nq[,ηТݕ++Eճ Zv4mt%.=eITMKI=:RRxTg $9'JCqVQ|!$zf+8[p贮@. h Adm`5Y zV 5WFS4B+ҿi0tɬktB2& -/8xӧq&#j6< PeDܜjXbv2F ;G`˄FFyWp+ \)ds4dN<X IĀT7Ddmz%Y 5ǐoSV'Qt̓7c F zL{%XeBWKFDD+O&Ζ bh([`ºLG -7S+(9:>ցD9[@!#üHvpd|1]Hړ &1K Db]6Z"It- -3&m4=@I9ƐTyI*QH`&1$dA_7ֆ>X]댃T<VҬE >L.LJ1-ߎ W5 T4vɭYjS.UQh&mQvEKotn9am@r˘ W(^/{BŒtKƐ61 }T{wD}.>%ms"E'0cg9c+f;!*6X][-z  -h5Q\g ȓyPMܬv1@S]Mv8 )(&@br ,d:u)[z,B@0LN|GSR+{2ZY(JQvUՋG?Jq0QJD.iZ;Ρ-~}7aigeQd cjL4;2੫LLt.*fi(URQI&sW5뺦>Ϝqﵺ4큽bM(>U]gTܑ,F2Q:_,;&UG`~]IͮkX݉b#qnQΗi%Yr2AOScUzkXqo+OB$Ԣ ;r"FzX2Ѣ+BSR&,Fm M& - J^͎`9Vۑ| [OodAJu .՝x:DEuڸrDG򨙬2o9iz4Z -Pź)q*d'bS6 [Ob c 3l -}tL0laY¥h_ Wt<!d+ -]';hF'z-\Ajczhn.M^Biw+,>\{{wO:U*za eXm>Gzv> -Umw&̻GJ  ֋4*:IsEZjeDV41T!I %6C{<ܲ̎E!ЉxqJh0{oM( & gڿqA ZUϷZExيIM7U Er  .GAwIA?UiC@);cKA@EL+Ɍ 7,VDB-dlvoFњ~^ݳ u8."I?eվMTS\*f۬VX'0zcERWܷV -rbTӍ+{zD.^bc2p&4]j+vKzΎSޚԬѵ3~ӕj~70l;SH-$9u)Oct\[;M#ؾ>&ǝ g5!rQ#V+ћ ^$ ;17阢C=oUP|74)j;3{ST|8YWNt0t3yӭ[IxW*IH'uGV"l(1_.`@ʄ/&TqY_h\rU-6h><:iBu.(7J8mzns>/2Hֽb}nهqſ5CIjdz1V+mH{in_UOq8=аGw%.|m?m7c[tj) \ S4A'2zXڭo4-J{*Y4LU?gO>O7?B?? Yku.ٙҭMй1}ቋNƙ_mjŊv!/w{ST~a*6#'uRCF Npci?Ί]NMx>|mԑtV'2\Q?ݏ>?\ >˫Y^mc]Ctw[˧A!(Ԃ˧$Ͼ_~7DS3(_? _d0`wJ?5??@@W@}_|/4 -^?[2߲HlҒ>#.OwX0>N?SO+Z|OB?'ϟaLo&O4$A0ǿRT-GL2HaIo0sDH֢تaޝ`K#n~>Wnd -)v7! |~Լ:S ^o_\ -gF$)5gśPqw NQ7/X<<Z&M[o+4\uW,βĺ,Cթ˝yZy,~KB퍈YZek.{@ޝlYLZN(8|8dGdqy-fFŋDf[%L:3:6S4.w:7vzd[Pg$lRu?^\m%^L\bwPe lenBk=y!GjKz>қvRjl{@Z.DNIƼ(K>%\t>]_}ie]^?^9Dוr.Q,&#X 'v?tW˱f;ӋЦ6;H'ScIVtNGu[?H3\ۥ - CJ?$+wzm\V~_<.$b%x:I6|ZxIv4˅g[l1b5am=6//ˏhb,(6fkkb?CJƆޭ=Jb2\@0Wam.kyQ^EubyU,&z9}_BiX&r9Џl Whbw}"lArFyfU*QyXҦ4r]h-f992x7{\ɯ/. Ua̮B_Bui"'S7J Be)U]li7Cٸj {)UVLǓn8[v=cɧPZ<jrC\ƻ[10ۂL׿ٴ@|eHCCNL-A{ -zLkƽ._FE?0ŖK Z5V WBת9  />qVMϨrg˼[]qGLl4^p4K8ٻ4b籚,dm f@3 ak]ʄKV:X>="kfҏWiRٚP.y&=&Te6^WwܲHּM5Y LxuGf3Q\'_%ʱV2w (6M;LTC~L] ހzIOWڲvbPMR;Et`å[#ܒ诨tĶ.hnpb(@,^e -ͅLX⠍a\S=Hw3.$:r)o[mr?-B8z?듨,mPUMm?}P@-vF4=cpԙk:馦-`;Ъ70z0:r6<_sv"|Zowf -ZSf^a9َ4œ=\=]*7lv_?̵éǘZvgvKeH(@˥t!Zz8-B:pCEGL;^wc(S}=Mw%-@,l6odٱSN>Ʈ1m]I{u*R/.=3,n'TeU4HWGXb{XJ沅P緄dbg1pۿafmPWˏ ex#r"B"0 ̸K=K>`}@y<*\׽ R9 TQ/:9/?gUu9UNg  !SŐpS1 ||y6umoe$vtTxnX`6V7%@N -ܸ sU 7^  ǡ$^T3̙\6fTI/Z*n~yG}{ lsկ3iT -i$m/l>lKv݃Jex|r,Y8O=5ìݵon':_k١*k3VkrZ2nޓMlFpiě-r;q8B~߀Hr;}q+!d>DkFeg=Ȩ7~֟ۉ̜ -dc8DA˶"ƲZn ab>!OSbZv E[aZ[.TqXb>R1גۛe;LYn=ߵt`t --RdyAk"y)4KOAzovf݇ҒSiH|m)?F!jTX&5δREC#1Z鹃4Q^oG*z.:{Bi+&H[c@G!UNŪFAOcش}m%r6\6to947Ij޵Uxy]bTAX_tKkx1=B줝'/%Kzf!U@w)dgXy9ɐ:Vi.,v9"w*BL4.Rb`xTo G7A>[g%q26{צC(t6+`W곫\ֵj+L/Ez8 'ijfoЄUCo4I$ -U,{I f:U {}!-U0!n/%W\-;frf\:X#͘ ZЅBA,.<̠ !V9>c;7C9-_HE@"ՏCR l҉4 I_F{˙ ?ѵ~0P5ථm,@MZzI)mZe)4NJe%laa|t 9=DO?ߟG{榜\@\`6# DWѾYO`dv?Ǥ߿8fP P30_vwE0ůUa16B + |X\~]qX¯YV_5fcS>7)RHl,aC>-bJSUACX_Ӊrpq%ƥZK_jL6 -@6~ةIf@="rfkIb/=o% KVo7f>? jOhBYMJRrfUx<O~pմzU; _]K)8Rkn=m4nYo5+t: CT͡|u*nQ~=q&ďs[|{W'B<2Ќ}(Zh_.moji9rU06=@PZx~]Xմ"zw¹ψ(T&}s?avِ uM=;: 4*1F·r2~p9|G d?ٕb?Sy - o7oq3|ÂKw3i\"dg U 7mCA7B U )H_,ܭg9)Tp6*Uaj\ۿgs6AJG~l/4זgo޿V,67.t^ֿuQG[b_]J7BwΟ>zw;lgֿp+-hB(3>x_S Tgq^$j'2{ۘdgeqܚ9럟\ m13?0temWALagL;7lfl8̊1.ȡYͮ?Uf'O׳ýNNHf.7fnӧlpqr5ל}Qjm$jXun&U[\NTB۵9amiN5ۇ.vXx tΝ,6."s+Gsٛ~m7#S *y ͯ.lDžJn~v\?Htj9x~/=յǣN\sx!,(x0¶vppZ8]XX>.,䅺P-7 Eda^,..‹r~Y\;^]﷕lxtxQtŊvZlfv-@b^4S[ l<@*v88mbqA 3x\Iz&ܺE/^(\ -^%.m~y_4| -Enf֎#ϕv~.=n퇛P0X IF!3S%Jp9]\vC|3': x/Kpf9Kחݑ\|SExuoj8\v\ÏI-<^ P -hD\$i+3{J䤰bfՕ`~%ڗWWՕlYRia4+WBXV ,%q#y@hJN;==hFW ҭfwS~)=n{Id/+ڡQߏ>٨dJ#3p1Su<'X_^]_g@Wʵjt5%^K&{kɻZboq.օM<׏jq\\?nKaVx8~ws#Y<_|>@; [pyk{dV7+hL?Zj>wb瘜RjzQ=-k>?A3l-.ɉLD}zF"ɻZ,y<ȥV6멸LcR*xKa,]{ƶ6w8|`%~b'y}޹6wKn,|=)&v^p mgC{^7Oyg ݫƃbl)S;y:zgnCj {ZW -gKٳu.z|=]{>W7ye!tU"V]\3fR۹{̆kUqNJޝn$ڟWWku3܈;\)6oEmb~}{[l"ޭΎsiCW}]Kgn@cP<&ZM-淢Q.?=:.KGRhab'\}*7-n^guJٷJB(RRV-ts'[>oٻ|y:ʗ_oz.|ݜ{?仳˗~kn~Zŷ/+SO^Ӌ鋙/.hra~iš wWOkÏW͏QM8i|ؽ9 n?G-vk= -}k{{Mwc_3eA-ZDK:x_<^<7Ӈl|>: FOX/F}s=}5j}"϶f?GKˏ@oj~P؋={ǟ۽c'۽Zoa[t{>,=8m-\}ԅf~aĭ}^Bm헧6_Do}}_xqy\sï^K0G΋r'pwZï sSsOՋͣÔtl^_ Onmg( Q0 :/Rɣu;DZI_yrÓ k{=u~}]첲W;&zLrvuYY:^~\{F|x!Hޏ^tzTnH{kq{׾[WZztupu{O*: -qAh] C;x:uspZo;XNz:^hPɗ i/_znnnNؽ 7a-MXvpzVt]o'[^G?/dZkfegr!{ץS_w. -wId<0J{]xovpfHxDu1rmAuk:uZ+/?$qj[yYkU u>\g\Ǵ׍^?^znhk^i+g+^_hnT:oZ䲇G'#OL*{:=z%$ތ?U0f&vl@뷾Y}ߴ>}r|嘴׃CWM鳡/^_X.}{wVdܼǫI0g<ȂK{=[Oͽ;gacBSC?fש+.kG'ǒ^ϣ+bZxn {Wٗxd:ㅼA{}60͋'>[OSw鵛Dqojyz]zuf7&Njp~Z8>Y_K,߾^9swL^+'0ϴJY@pL|Zx@ -)k}ulg3k}Uh=}n5UOoBSr[vʖ¡zNݘzQiڞy1 -MRvz4͟Z7AT+ݝ)en윶j~Uh=]<4TOɭ -߬{oGOևoTO#`Xa:lF7vcdW<= ЎٳǯW}Ι yrӻfKZ?}XN&Lާ7ڜ<bhC'ۣW/TDwpNO}dK4fp[~Ͽ]ߞel(qiL׏A[$3vV]6N/{w0W~~>8:[[ _Mn&>gpjs)ilW -]{Խ-L({%( -1>+x:RQxNeAW2 .}Io{8כva7+(zm>½{y>3;8' G+({ŽWzzJva~ 8d4W>K'u4iUf7i=:o8MfL`/Ö|\!gRf?cc?==uP׹`%b"vӛWH g1;y("M:П9c$ƖZ0T~7o/|]f"PHBg z8!+ى򙵒F"qi[ڲ+7Oи٭ВHXnٽ|3b2a5{gs}K`鼯S湌(keÊ>~NڰPVu:lTdӃnL`WQ:PRb -#J$ IOYwӷd4<\'dep -G =(8}F@T3P(}2ٔ h{T~*`5-MVTP*.ntP/ul 9fG #;W'#`gغÎh-%t ]-jmP^%='dHZ48KjXx( :\j@$̦`Jq Ae -]!r>~aI>~XV+r!B0f_e%A"dP' -A"e (b;؎$ Igw> d.SI_Ue~kK΋o[sT٥z-۰g;SmY sϊal{ %ҍNQv5M~&n psQ@p]JO/~AP>AE1R{> q{n$J+ 2UZnFUY!%Ia1'hGf2K-Y~[e9eL|aq۹ݱ=wf$Lqj=)TJd(嘒ZglmanvɫR:ӉmW<6ش[>.P4^Uyz{= >?)UĄ"rQd:)&сS\(oAWSD -("aA yh20蚃g+ʃlCmoW62rn7n -ѪQM7J\>aJ="2pXBnV"v e(bk7ȝ-ԳYǣu=O{ Y E4ző[ֶ|pڲv3 2kDrAd#ۨ"D_og}ɡlq̺*P$%8fW\jVڡ05'MjGx䖜lm9Gg5MG3:[&]bޣ] cIBR9{2%$cġ+վn'Ov9#C}klL5ٔ9Alƺl 2~*qo*q SaǷL 20 KMVH_fP8{{_?:_X,nOo.pw\\ZtO:rOu>]nb||MGu W+^_u>IΧGu>]ƣ|-._^'@k.I2h -2՗~I r_\]:V;ʉ1OS`uI|@k>b*[nSf?)`+*0qn .s(SPnӤ ]{IH=i`!6&;j׸6}2$T)& 9ip m̱< eBB㵻6"ͭkwҐdUtJ~ ?ul4)&p{-_6űsŝp|joع7^.vݨ4`i\ ֈNX>Y^'KNFwh]QN} /y] w -Gܩ͖t[ܝQdޛ),1Rw -FBc+S0ģ-l?cP죊М mShx1\՗̑V](3rsW9'M٫{¼D=ꋘB;7gv2g ,︤ 0*`E)R^+L$bPE18C<%Kij~B04pN/u dBa1-R'ײDm3ç3ZTv*&$'Uw -Ӄe?=('R6zR˚FTFћxy_7x-ћ ǓCPXm'z.Vێsސ&e>\M} ǜsQrwͣ73+ d~;Z BGzBt׈#]nHwߐ|_4CҰl'l;tE>p4W -4|̍|eS7<7 -Nq;u3Ovym&Ú+Lͼd.v"Gʘn - oW'ZNhGou~Kuf^3/76l`goIwksݫN\%Jq% NK UKyK_j<6 oKiQ?@M\~uvDž#=.ː1nTge1vS-H9)ST*sl cHc[\=氼a#R+~ -Lؽ+&NϹ1%?= L - KXVTmwwQߖMedtN\%ցKvxg.\EDƈ#d]3蔹h(-D=oڱɥ<͜KydN+ʳY~w^C4y)޳<>x N_ wpǷ9*߇ݾ\{O~8O!gvW{6O(9ȸɮ~y' n )uĽFlHU\dY$m=Z_{G֞M֛ޱ݃#ϛiy8SW,i|*n16',Vҹ p ?:W~~߭׷x0Zmn(zm>͒\;]ս.-zoA7 pO{?h^+jf{ޓ;/^μV]E]b){Ņ`ò_#MKd6~Y}Jc~}+Т鎓ɧ'F6r|jTʛsgkE NQ|l[cu1^ѹ8lEׅyBENoQ ]j -_QKazz݊_)E)_W>l9י˙.uެW;XB4oSr(=whŔC* c3M-& X׽Fb-Lg .{z̦>. ubVCwb,(.nusS~O$W'՗?)>3){O }sٙ}[}Jd!n:pOg'syq.n Y_쓭4@ы}2nnbl ecdcHjbU:pOccJ -> *BsZ}-Xvbl醴;yO I>a$݋}[}}klJ.nMd\N\/nSFd^PE5m+y#u9:?M.Gf*|pNUS9W^JR$4ޫu˟m)MAJ&17C?hD,5}OLILVr˚.R0<CtMv#)VN❾NDjG%KM~YFt/K'`x$㺕g|Gsw<:u6њQ~h)N>DKaٝL$}q} tfIj(L 7FW^+WnկZ)0_~jzQeT+#Xm5 dnh1;pB(~q i >7wZ=VxWz!ӂӨ,z] dF&6f1SJ%'MذMxqb.s׈em (/t; {+;+fݫW(m>} EwH|g(+'ʴW(̃m ?_WiO몣;޿4mq~_kYS<_@.fcz;{ ߋ[?߾GGiok_y0{4Q4AMDU[+Fm;&e'Qt^),quJ+[; M6]*ËT{YݥoD7&w$w9lipjⷵ<1HzcCKw<[c@qg7({vafZg~U+Z.VlҐ2٭o>) u݆ױM_1wRa2J77jCīgwScΑ{ w=zSͭjz} s&#7n?6G:_^ghtzouٲ^nE/_إW?z=|9:ߑ}Tu3/\+w3oXz&&> _dT|x9PwY?-S4qWP뵻. Ae7;ӽ^{g>oǠ(%|JcxɅIKYnr{5|`ao5Ѿd+:sh薼kFfV>̡܉һW]lsp;*FҼ,0)|!D򬌚7LOY[| n^>6ܝ#THd~H2os>no.L6:vDFP$.ɏ{,oX0]}vNܚPh wnCKsuJ:#xQ"DiE+;XJ}\2l+ $JWM#rnK\IT-5쇊Ws"Gr9*]IlUf+}%QCS>"ؑ -K -KBy/+drne<< -ɒ/,Byخ/+T(U -M -s$Ti3^1}a'~j o;+{a*/<^Zq/<ԇտjUQ*U/<Կ0rt|](Po;dכ^xgNCAyC=Kd/LM"P1l}?wRɜvme@.D6zɮ~BQ\"ULoL=]}no^;}Z<?:ЋQ寽oGLF>>;MX 'ŵWٺ*«CkkW 7Wvۯ]{0uoގ [6{g}t/^,Lܾzrso_C _zG6Oo^ZzMzS lۛ:^J^~$B/=_PܼN'ؐ+xuw|vc^aqrV!Iq>>=y9>*c7}XkOˋGcLۛt8cat*|ΚX<!?&6N8ʛQU.(-\S/] W}/.K|w{;bow#G=(N9ф|!73O{o-.8/Be> k⼯A;jL8C%@aBσ?F(c8'ֽw Dքb=Z}kxeopݚ=f]~L\_Z^Y[/<۱OZ/SiLyMd49 h:LfFIe Og-D$'4aaxobiA#0E  .xzԂn|= ~ݣxgۗtǍd W(w6t'ß67fB&.@fr6!@5!TGce(;Ssw|]?~~ș}7rq?흥.-|dx}5hG`# (j0tԷ;N>_Ox:]FeOWȧd|X i50r yȟOGrΏ9ǀ~W=}K}.Gx|;>?qt`uqiUDUʻwH%i(P~%eGNx]a=ڞ`L_#1YosoOw䇍7/Vglr\]Oޮ_N/}{|{Lf/z0+NeQ;MdAgȳ́ yi ;C $ ~vy5Ůe$csd쥃.O(<-Wb郵!O M'~v>??! <̟f3S& xzؿ"V҆1}`=H(vΟ %g5% \DC9B׹'d~pVac=m=>?^pWdW7!R%܁$r;Ԛ?͸/~;:q=>|>~e~9Pr‰u9s3)dtLэ5m{GN /RV˹?PxOt>Ye<6R;;@dWw9zݕwGk3|15BNʎ~$e}1YOi[۹@GOC}g64eL\l_@w\J/{8'G:dz&|3.Â-9`}xT"_n >x$y7yG*BޏЋ#mmXy54FΛ&{iÒ -^!gmbk-9,h}1P=nqY9|xR$8-A~!H4MAAJYF#li2F2Bб ѹ6SK:Dh#2ĎH fs9aqJs(&WB'8M_8F8iefr2.gXIX㓁ۣ7oyYF嘣}+OW@Θ]sqtze}ur1=; W$*Pb,EoE&{.{o-/w~l^Q B>մ$"'8!˃t\w/z9Ï,?M}LmqidDDr$솨yD&^l{O54H٘=:'ѫ> ÷3iΫ{idX22Q١j=vpO$QcuF>k,G''Mb_o9I[Ae''ykm~|0,њq$HP:'qmLȦ*gYO&Gڛvr1 ~2LOcĹ%g{wv,cdYcaU5F4fc&3,ѴѬ^X9r1?YIwIC -2qG'}2G$'xb|IOI̎N3iɎ7ëKqhyv3ufeX?uL<_˓śtqzy33Jϟ~:9I"3K2Qe6})oivsthkh?`j -*`Eɻ1gu~ݧaTLB#OofF__[}͝eF7[3_N,?뙉;늬I^SAR Qϵ{ld5H8c/SO,&,cy€5w$3/s^h$ɂ<|.rn6 do0v7pl 8&o1Jh'{;})KqG; ;v74g~|G;$, >stream -6>I#MObpHBCܷierm鋇B{H"vg҈҆E*fWk-0b#.\gPEOIpOo{vዏ =p:Jp٧ˣL;+==!~34YP C9~JǶz^`t=Lr<$,}?l}#m!s&ܞNߜ <1wܝ^N\}9u?7}FWstkigzFP=h6=c8ai|K {\2,(:~dAMjjjjjjjjjjjjjjjjK j!kï&`n_G 5RZ|k*a\p 5<ֲ+YC 5 [h Lܠ:RP lZ[ BNkaVRSUo 5,L(:PolRS-Vj1ɲݕ%엉~6E3)TZjQ|܇BT"V*JjfYb!B4]@$Y5P+i,[UTi"֬?4Fc1llQajи*a/a | 5`h,&VXJ_j\MjFYװU89PCgP 5uY.a6 - @!6KV(B,3Ug : JWIm}5gDZ?U{ 5V`l^`B,;qfM{_.& ]`>ڶJP/ -#}dh/Gָ]Al"k;#$ju+0(Vl~9TqިŮtv饆 P>׶ł#6:"X+bҬ +wDj\JjUZVXml#=BDE[Y褢A}(9Ȅ) -,1cb庖֌ A캐AMjP~(ԇ6AӅjTrdrTc5_Uī$VvpI !hƷSPKdIJQUG;'Xf,EH:2e[Fߪ -I_bR_X(>QS^_I[j+@,,("Xm* &|8AłKjj*EZn> -`95P8&Be޹楘2!UcVEC7X>WGڰB~5hո -XΕi0Ĝ)i})y]K?K*J3(0 Y/竆 -, ;E9S\M]y[lG/[`"#RR\B=Wa5iMUa)zbFo {&fơ5AjwMw&"7B= IU5iyYʥőQ(= ǂI"Tq$1'R/$5d(s黨H:IM( 6K1t zJei%DԄ* a5QuC+W],A9=&M8=$V p+KiKejx80RT_UTł"TxB/(Us +tӜ=Rp`bRcїp]XGSj~Pᑖpj2_S͈hH5U 5bᗀT? AlAE$CZ__XXf"EYjإ_Te{p Tk)[Tŧq^UT "cQ5fDQIT4BTQ rCUssbo z.bn}HRU5UMT,0~>ht^0nQ5ZĶ"QfZWJq8K?r]HB/igHjx(P)o"ԣ*Pi[GA; %b.a"sH1 0Sy~@% - &2l=*5GR ҷ*F~V9GJ5vڶ*6(,#bZ9\p uJG%U*qT]J~4XmbFQd'MRl1cbj - F-%B:5 #&$PTXGqI[3Ը*N,(3FCOi뫕e\CGp Gټ\c!1v+EWKW3FĶ2 #&eR/֤SÚ5f8)~}zbI -py7W(SMs=B)šR!pդL:(/۪)H -1TïAW!mLrkrO:"z"ņŚ1Ȱ Aj#$UMc_+o"6FMDL>rDM~; L0։{LZOjl[6X#Ii.TxbapoY0shAoAC.b34դQ}KK)NeIғ'VkTbV[ *P)!`hմ9U AZ"JٔbVuGKDpK5,\_,8( blG- Ѳl뿪tp -;(a?hY -rçPCY+9Qvk1Vk b.)纓Ҭ }}iWR/sDb -[DŽ8(|0#VJkȈȸ|TI%)~(N$IώBi kvl;z֌ -i;A#3GTH^!I/R2(yǦjPhkЀFeN,hYC /:R! 9YK07M5S)G%b_i)*)&IQgZJL *+|g!VUc"MD*lJJCt$o@w@H+k8U#=5pC@,Ӵ%ҿS\Q0NPJ\F&*JT8MXW a_5P•ARWʋ -9U]y."Z8UʆclYJX01m)q^* GH`CjRX B!G:5h_bծ]JDl]iN=үb5B)R9Lh(K-\*@)xe[H#}p -/QYS)fkDUAZ_ʂ= =L:DY֤_MLdLDRqQtWĄkz׃I pM Ϋne VM>7MGѪu Ka}A *0T57M_MeM[=*=zʻ6KmqacgI,\CViؼXzT,NJ+ۅUXkGzDb@H˂YDAPV(1DUX"M$.IʍS%ľTTu``>XcZj(/5SJz&DrUd;@!=lBje阖 tbL-fօRl"#rSX -8HB.+UC!Tk b;jDK+l}Vq}Cbcafӎ_H*<+8"bRdĂ6 e/+hf>4&55dm%-$5W 8"çqG:j)L@T",KTR<5jXkvLưC̛=6 -zWE8o]yc%3X ̅ - 1>Jšj -e+7WӰL*HphLj;"v0`*Į~~6l.& J~(WĦ_Z EF4qkQѯ!RP&&P4/琈U^=RpU,I#sb4DЦG'A}bi)E(%LD <Xf]2PIIU#Du`VU -5R#>,d+~-Dҕ7dY)D#=W< -"M_jzL$YC)빉 1bKzGl!Wl(v!udP* -U*l*.T7ȐfZkQUS)אЕMdbf1%_#wþL֤(ԠRi -EeהvѴ,,X4bܣX1i~J\PmYUJu]rMĢB%v-v \wV0!15P񳪤M0/6in_+ce̖XV>oΠQ WQ -KTh +E멹:!TlT0 {)ՐNeK H^s]}8}S50~P*\(UGb)YŋH UNrWpsRX0vk(RˬJՏe~r! ¯!W/mdp5Z'bXw}IaC.f S A#*rn 1P0qxb8mi5hQJqBT O¹EP}eVz -&UXD9.2V)܇_R: k2%qL [Ԡcr8\w"ըeIi~ E4 h(M{a_ -zCAc:)|c_*-lPCHTD0D]Pz!(J#ds? TJQH}M חs)c)}0 ŦUĬ⽔~H F ^ 5W^]UHL T@KfW= ,*P}=QT ǂhXRQѣKBҋe欢DSK@C@!]T$_*STf rҬ'@SlΑaPY ) BMaYu -e} - 54i[*C'בs`}GRlG&hK(X!"HpP8a-L@nykyS5k@ϋTmilAPӎ հƲE -ڝWTVZِ/USUeRsfK0_&rA j-W} -mdƲh"T;RTuib7%BiX6SMr-䁠+ouo%t*HũJKbur]4<p* -DwKm#k *Ma RZ:-5R%Ob쟮*j~U-jA}j)5Xy[ * @(t+5ON%( 0Vebxk}@1VX[ ҕA0\nXmR: ʥ,֬ -8]tv_ ,P ,H] jjAo+KQ6ndͰxؒ& -s-1r\5[Ow7 -?%:7^r_EC_Bf(-xb&LAhv*G ]C f/QH媬Ҥj9t 5@W\C 5$mokk? l 5P/5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC 5PC .O?[ƿWC(:;g٧ˣLbِհ L934;ӧ#;35 -ׄӞ훣O٧ӴӉ;x_'oNoOά٫|rZN_l hή'$l'Zw_ǯWOCEL@ww)Mꋣt6BqoI:?mUȠ% szk*%mlM| -b<=ћ{-?+D=`{oz_g?ܓY= {,*|e遡}smmu8vi:Ax3j4[a# -& ǎqmPlx%v#,^ʂNbj:`rJw|( i;FZ ۂ@>rnCh.4 nRJ#/rh˃stvn3!oA-j5txV# Gm"hMPyAmBІ@o BAR-$] h%ۈl8)4BZ# jęjUD |R-CZcdPv^#pH],; gA@pRP6$#?+h:]v=drrsqk7@t.p烔\ PPl/đEM -8l6 =QMó^Ӎ(EKBDDSA<hZ (QԤE%!)ai;hA@ۑOظ 0t )`zK"phP(BAJ-q"X4s; kڠ؟K(^@Nt NdӍt+9z& %& -dC!m۩X~<+$ 8BTf`1zI?hӆ !r\#R7&I^0 96mq̡Cа#2 5Ո3?cA O$talR#夆aE^1 &|;̑0L$Nv:kH&q /dMж|JRRp%/#GBq~dC0E.|6| $\tΣEWOtGDaiҦbS0ȷ){QnC:HK.Rd -ig{05# î$m=0&i[~b[B g0'C`>? -la쀈 -HEcd%@9j-8La7DG1,YX꒙&;4a &dp)!97λa`]!?a+) i>Em5#jYA">'ٽZ%恐4%^eu,tV -ɲc -,zs -;[(]6N"jbE:у+Z}V5Ŋ0;8o+K,UG|Y* )tGI(9#c@%Aa$6GB (Bb~[> <ƒ:%&@[ t%[ERYF2:ȇy3+v3s Τ`p; 0<6r0)1\ ɐz@ِBvգ ]2ݐ :^_.n( xqK4řl[mFxA 0|%~"`$#'€KayB+$ R`c0՟mFG;etpѦCFH#6u*o d$),m>"0ȣU EFS M:=a[9(j^ "dk r%{2ψL=x#RP|t510c]G!d_\3YLvMH`=#E6/9iS=)VHz 6SIfO0Π#Ox]zt"#,I-$I)I؂"d^t6̞017-~æ`0!- aZ[SԳ%E~ q=<blt@feI[P?J68!ǚp#bKP&P R%΀zNp.NMdM̂t6VA &%;{M "(G\ t<-Q"X}(qJ 0-9&˜&,yH1`"?"0>nJ~RrGfH%#Sc1m%ݡAQ&?V0ȡpQj$J^&6qE#Z;t8 "+ XӷtqW*1v !pDEPrzb-rq'HM`v]  -,D&[%2ՙ=Szd$'%'Z`zj2 dMH…\V#8xd MX&n2x`Bqy -a& hg- p3xɁ,.; -<% -t,i%xT9p7|b:c>YVj9hhD -&-$dD&%V :I;p=:8IK왜".7L -ɹ"T =2o'`Ѓvj Mp!m@o&Ȏ' s?9$#+9e(m 2#1%.3%vNX-hvR\C/ a ldriNXN>?9R YƜ-\10擈\E:XXjaHr;,jYC&X'0.$-=ED -l˒A q"Zh\)ȨՌeBzIL3,&<۠ 8%5lJ|Y~ XcjK&ـ3_2iZ4hCW&.VK^x+4=׳G/9~"Q;=ڸ9}mˑRAУ0:Ȭ귋~!^N@C ul0 6n}q,<{Z3"ߙc=ǹ|zҮI8E`.ʐ*Npf%CG5$* S˜D(FHÈfF$!L"ܠ6S fb^&G L{QRhkrx / o. $mdv>əL9.j(K>EO`Y RIवP0 39MTAUqk4OHa`ep%N22&-M -ir9&G -a꒒} 6O2I4@Gs - al(#Ip"([F ڢ(IVOٟ{JdHdD4.IzڹGJRwL$KC"HMuKia@pH*'Qah!L⎘bCر$;:`;\`Id9dXi&)Y;$C$NB+1aڣKIO%05,q!fi1I`/٢>-˰g%DsZa* ɔvoӼGJ YR!L$ Yj'O\KI9c+< )Li_E `P:I'q6 0}g4= Pʌja2+_ 14dEWHp C>sɪg d1o̺ 0$ӨF^R#4Jf.٭&'N 2\;r Czh:5dGRɣo|I(Ġ`F(c$qZa.'ڮDA2ktdqLS].yRML/qa;& !C$DzIV!BQItG"MzB:ogmyjM+Q%D ȕjUa$n4=TIX K) Xi%KAuEaeEbm/f5\0zaݱh{m:b[Ut 3ά]ǭ $("'UsHץ_8y LDȋABVo %7Q )5}tk𵧪 Z>]X l*5,\r\!Y{>3WSÊ`n xdE,=a ϚߘM('/"DV啴odCJ^0n] ExnLGsog -{g~ė:ūwoȍ4Zcf575OaFgUi۴8n>^7M$,9 -BwC#{#>ll`E9&n%@xՄ`7$&fI})r\¼v"Q6{̧xzwkFIV`ډcq; jF/4!FFJ5I{`Đn9'Hq-|߾b{Ő` z AnlgkXL nLR#\d|M˴կ<<<951 ,&pApQ &:朹RgWK*` r1K%cbRAqFqYdwF]F#X5VN*eaA/Mn7{'m<&gh zv]|D/LM%jH `W?G>sZ,GNfAقIӐL;^MP{Hi D@h A|M\%Os˘v'YKlxfCQ;$!Bv UI(Zdrۛ8\9E&#7N1E'@v<Vhoo, ,{aLMqqVԄM,aŠi) -3!duK?>btPKԈˬ26hTwA[ƒn^%)x9\豺he`r[9Q ӳ7QAPP'"Z~@h(b$𫑭pxI C1UBbWUɳ!8S=+69>$!jQʜ/9Fx-2( -C&q1Zᒉ&S -.xO$% g(y knT2sGY_E"B2f -HMUHE?׋w12gNUgyzߢcOh,Y+mEJ<ğ%' 󅏏_)e[Ճ2V!2g+׋I}u(I4>h8BBKݢV2߄yR=[=׋qɮ_Er k#UeR0(:M8+x ,0`%!rɗ^XOcQN32h-Zyhֆ38..H&7uVCzc ǤHU,I ˊ5:ypa` ~dũZ3ƶ8+9sYNrwLpz{kt=z !^cyPE(+K5W_q7P,BR~#B (,@fүTp~͝Aݛe:mBx'-ҪKj$Rs٠m2ϩ wV1bvYLZHKod$4m5>daSua&ڂ"VA&x 23iSX/\!:Z -ȧ >A*TjuNDXoUijO#E_7f97_d/Y8S!zUk%vѝ[~SciHTXF6%nɈUŋlV@zk|"1rf͛[!40R] -*岈g` xR2jJqA*R dCZ%NmCq?[¬/7I|W. eɐ&!>{teCI[T8w6 ,ov_%b9Bɴ|Q`|UK#,`ʣb0ݥ!كNjjvЃZLq0b*1Uvr0|F. :"R…ZCqXO_L4P{`XwS^Hlր Z*I!N}Q`qA Tc -(h kYI RGyVqMy -ce*Lcպ`mhFl ^H|i>GlOCf%>DV|xTC- ~fҊb xYQ7.tg)&1؀n߾¯1Ћ=4/bJ$2lXOqH#~$*VP^p`uU[.8_j:ԴX@Ybǡ NT*djۘ n&8i44kgyR&GI5aPn4t"rTm3L|sabmUd/2x`wj -I9)8!TU [[h\G7/د%܊G!HopjAwk\90dSE-c @@2fp_/c#Zט?YU|Sb}E ORTv<(Q^7^wwy>cp,2W\wOѿ(KPH,Gm"=ll!ƻ&(HGalrd,me" XL "an/`i(tĩ& k*xC-ͤuOg]0`ƚb+W]*k0U wzdAt%0LMf@>"g @.߃]kJIh*:$ȃ&-)X䨐}rE ٵ~ T}Δ:d%,#[nnX2}tzR/YE(<|ϋM);X3 -dv˳3ϡёK[ K¨}G2vPmnPtßO@mj x|W̑܊56gbQjq?QHy+h~4?'/CjwVNzkLkgZS^^poxzٔ)IRiAI| -W&j?p3 -wC"a;ոH,%Tr&j27$)B#5]]u[^wEN''e"amE)}ѕ4E>,i)VI 2Biu\Y}vkpQMwBEnA`0wfjDZ0*rmN>ް D2SZv -$n‘ōF-ŦBv"){=i"i\-ď$Np8tW8C -'`'omУa9tg/Pq*YŰ)tŋo՜%2kTis&n:fifz/v 55YtS/r5U_B2L~J}bǻ{XslZ &:Q'h.n ʊƉl n5!Ozqbp,^V$µmkE,g&A1]q]b `(ƄEֽbIY4.t<u"pɦŅt7kdz-~CwwVi,[8;il,Wy4YF`gZ7؉;|L,ݜdi6_yIS,tTPbbpny}N..a -b52wjX@1@͈ -ٔ);ToRUo&~tSK"hC(vTޠ!̙gܦ1=@7?oȽwRҜCӘ7fY=gL"ѡ6]%IC'wi Ff)@Y(̾!1_dW’ 6;1ⶆڬ.cCd9E0@ml/e,T'5?b9uX[=a`݋#`o?I/*ڔFYR'ZHCQ$rs1e?`hv8jiQޚ|eZZzpttY+V0cZ/fp(]BW먿spe:TҗU4 I@H.IxByQvWVxi\V4=\$ߐ`LVqZ_̚AQn:_2BP]+bYw !'BxE,Zv9޺--CvG "W X&OU'P(@Sߏ8468jS2CDHrn sf !eS8iTs^~vsIie\ED+=bԬ3.kUQ0hr)D -0ouT@+zkQ`Ռ:]i)`sw1)!o6MoYXDÜ)\Æy{ xRTm?!/?'xq5d3+K*K UHp[RV0^6<y!%,\̢SsX<*q)JeVH Қ)/ŋs,KXmf>ܑJE~c)') Od->KC')f55veJEoV2$HSf)e$Ku>DVs3'eMR2Tݸm橒 -_l8ΡLjc9[w[x}jljzw) 2(j)%Ml^k؈f^ +xCQtjM 1WdXۚ,Ln!Dټ[a]\P;(r۫U|\uzq2Kr =Ut_Tojj.حZr0ݍ7)ST+\' 桓5B l$`Vg(6Ew? -YDME꜁}佸bBAAE tdNUT:o&B 'Y㲝gvNc//8~pu[JQT ܎6n LES.=(gדV]Z~|PnhTNMiIaTk坍⺑g6L^ca?FϠ滨DC+ߩ3,:ho~(\ -XO?խc^ggž?j*M^=J0kRdK$zv@{uUHO 0VVUca+iY$ stN@q!v/7M2FP?WT&8&j1V>mRdmcBJwsVq){QZ=wn( wSgT6\6>,}$QVj1}l<@ ҏ~peS7VZEޚ_̘ Z|.p~QkE12bU>E][F:9Őx{1`\'lBx־tW6;KU}dB2"[+HMVL3Nq(!eIOnbȗkqdSE,8M$U &YruUG.k! sj Dg^.H/*U=8 C5*ա4x!0waTYK~"1ԕbAj./pEэaljuxl[vq_rSK6y7U'd/.)/8?3υSmhmE7Z --avM%s_ݘVXxe!"2H!t=@a2yFw>XL^Wyh -=FX,+nWRP_h%FTg[$!v~Vk[>k o{f%~8G2IiMDf>e\^23j(]2$Hfa`*1==6^Z/ G(AlqRmb`ɚn"7OwWZhM}I2!L zpYj=mNÃ)G-ul4~ -qv{gSH=Ѫ).P ȁA/@OSּDZɲ*G(4lkfv7YY/c*UFyfqn8jݦHGْ([}q(kA6u2;0u.8<skPܦ%ǢfRy^Vzp&7%3N(3KGk;}4#fp̄Kl4݂<2b2Me - -(ܧPuO5Gh'5bh#V,U< a L TTFpg^$JB G†YFd y5ⱷjlW3Zr#NO_-a%& ?{0Qɒ2HPT3uRS,.>n`H4t˰ u϶6Ofka2vuJ ;[Xۊ㞇i2@t-ѸВ5J]e0;lī"$)]9Q'{bJ /˭ | `B5?(P%Tf}fdaB7J8]fl@߽͇ſiô)|B!R;(3?VfDOvckcOC%3͸!)+Q.;~OKJ -FMX$-e1UL[uT+ m5&+"Yݦ2PA"۰|-S\vajΧ֟Of._aYfflFJbN_3_l{#kjwv CEng1zaL&5p+uz*bY9HhKjm >.>Kph*FF`BϤj*ަ`TA YJUE*Ljv7iV, -IC~F`ROM5!8CU׵J=h -rϨOn̠0Y96el]5Vu +`P|)}5P*J趭` -hkz>-6WL@͍Sww__Oc$Y##ljP_w_3ДΔwuӂ9t AV x`=J@Ɖ!3j]*ۡaUE!az'P|f(&j9x.U6ui֤_OHۿEx MrquB3*^Պlrov jTIS#DkԊhn/GrK& ^g 8#TQsTZ+SH=j**fTWq}U3%Qp'K$+XSb w[FR_!M#bDʼnUe\|{< ̝rǜM`hDk?'NbBe0^ ùgmXX@L|Pn -jn o6yebvaLi6\, -\p'3T$$Ihϑq,ş&ĢxM+UԋYKE!G+`̓XCmW{%$ /jWr%x(p'Ff A4f0_Yg0ep??n¦G4 uMIe"bIDJko{:J"5a. -)v0f@ 6Y+O{LU|C83]$dA!;bR !<)mޚYh:!,}sL`/Qg7 [pkWqq&BHʓ(D%V(%E2Bgk_I\Nn9|y囟ſ}^?/u/|/_f>7~_oO7Tyo߿o~O?}ǟ{O?~ۙϟ^?و x?G/v_fO#_/߿ÿϯ_K/|/?'Ex}>*;;I'#.dϟtM#Ѧ.%#D-9| T˙50M0>cA=N2Ҁf>$k)on߃ZB e{ngay00yQ@1Ze286,Ɯ 45MIz%X0ZcjT襜FM(Zfev4" x= <J3 xt#HjQ 9WDi}8`ܓ`?ƁUq`RB}E}. :Ճzr=2LkՅzFZ7>A~Ư#YTXln~hp/5ЌLך ƪZB$mڷoL`nL);4XfX/,R}*8Ѱ0z m1X-K9\X.I!x͍HL -X'pP˩Hu I5,.eL\zd(? y,Y-#nf1ߕWjy-$3܈C*4?fXgn 4Jsx7g ڐ,R*䠛_'4;<= t$O1-8ԟ'f 'VRd5n5Dr#% OHKŒDl1gUM6D7l"10`YV :Ri*,Iqi@ȝ%ycj)% NF STHbpc`^f{?”|ݢb|_8YNU3ӏ4{8BJr_ɶyL@9$i`+@%k^^BV?nvOYأ\co'>K -t8"xq l:Dy۠PZp7{40uֱH.5}Wmq"`m7lR ʖWT)O?xUΟ=CGE(zk ~wOr'riM~DxY__e^2@.+o4R;+}7i2TźhB,#E ,ɖ-*} TxM,n GnQVQ4LUL ݯq:x -ӂ9zf9L䲁hiA3Z/S?(sàvGRP?̟t{i Q0 e@'V)*&t -"$-*I!DRHPE**%àPh!I+-;qj`=] !!O;`sD`kU8_홿#HTh".%&*#B.aCdls""q\H֙u`+*3ح^ 0*UB8l|mN'Mh{q{ABfO 3]c 5ՠ\/h2L=AMSȺemq΁jv&%{{ BIkx3:8eP_&%-Ὀ' j.du,7HGj(~Lļ[:cn`fna];RGz̧yľem]Dha1 -lfG :a!Z@pM<[.`6Hq)|2ti@)j̧z϶"%OSB(ګY(bqFe9 ~~ _4}T@vR0#YA?1Kٲn/cمWv*LMQ> -vA쥇/(Yd_> Ѫ ;xnhI/`zY}.}@*76k{2p{02}i(Z32˟*Cg?uhd}VsÑI L*s. ѻ'Ñ89K >nҝulrߏ@DW<-;셑O6rz#m[:NjF֏uӵ7%MiYZɪե W*69hM>=zҟTQ!}鑔0b7!1nQ;\,b;Ӊxb3#62Π*x5ة#a%3<JAC6[l@nڐo9ٴo7hun٦W6eww;3߅@mio` :QKiYnbgI1{0go󠅚 hW4W(ZQޝϳ@|.pCGc1i@¶|΅5aPK*!AoB~!9.-]/۳d.!-/'Nn /Iml!2aAb^=U9! tb-X4OE6`80θuW-IN;E~Nhy9+'P4FS*P4WH20Q&('4FFvϙ]ڞ:~Ltch"L*eG64閑ZvW/т#75^ܔh?n[{;P%z0ZRkK4i@;+d&xz8 -VIŽzV:^hzI"hŎ1|ضf@~4v/[l&I}]ar ؼPxXM< _Cu]vYϸ \ P~,eӫlvO.6|h -XURbe)\ߘzO\ޘh'xi7C(o{*E!b9׫jG dIAm(u<UtO=;#֡]5Vyf^[S#f_6Lp]!o!e|JG!6"Y;*]Cx\d36Lc `0Uu1Jj=%@JAɾ2~7xVu@8th02"Ibf/Jvm0QِediBh2>Cu -n2k -܊)6C Whe֓ŚQ<[b6,&8%͸MM,({+@&D@mM*.ïm92k[7iV},?YJph3]-f,GV\m $#:ӫ}垅dŗ~J~\ RIq em {*~5nC(Qd@=ueRx?gSE%s+v1 ,^Li9_qBUW޾nu~Z =أ%TǵD:Uvޮ] -yJy88eZ@}ZwyuГ5 Gksɡ˼s" TG0?Ecm&O@^I&=ȹD<ű*JۄLBY@0Ű`c}46xІ%qJ -˸ 6$E7i>MsTwT].,| R[6-f "`3) <̷53fly&J' nqqm֐ihFVsGC˂LYg3C$.,`¶$ -)k1Ե-Cii|sOa&k'OX/Ȏ\v(`j\-kwAXUCєO&s۞ $W[XSw֚Ѳ8"n 9"A<u˰[ʲ¡fbK篷3S!Km}80Iby`8i'{WF{$wՃ_& 鎷adoC̯jRLC7cu<~4:Zw/]S68PewCBH8y(&N֋囸]{.T{pEer'kYU&v|7V'`rJv3a]ڤ鷬~Xdګ'#d|feڕWt50! _գrwԇL9wyMQ59j3Lg0ձD>,Ԛ_bvXvʃo!2fU!mG/,I\4MzH'}38LS1#X Di57MU.`)A4#ˆz)fHo99]]"h1LVSW'00>< &B~&15zC:J~<̮n},s-/tp_isjMxyuML3tBX?Lwj=8:c.EQd2Sv7_ ǎaQ-c KP‡_@(\$!gjzr,@V-iRHԤ%/m),*B&s"HStČHU)xXͼś?`bVm֘N,=O@22 ~4]MKN8:;aG`6{o{i\C)D'j:ޣo="X}E,Gm=7X}czA]"j`%}"5|%E=[:-"X`l [usM"`);4!Kh#%n?kC%E@(^k@fn͋w_{č -cmjr49+7n\-kia$3TQ* F\:MĝiN+Q12#8 -QttLTPl0ʙ -&śSHbf'-g1p1XQHQIdLx&ILLٹLԞRicI)"XRj|&&U3R"O>UUsJl+zWf aVj6֔ok" il$ߝZpS/XIdv4MdVnLҵXԼN])N!';rDZ̛唽}Esbac<'d89!L'ҒM'Չzr:qH3݉I?;ezGY-Mm2Ä"IYHnfFS{R9OT΍"^W.Fa:j$BKZq@8D‘4-~,M3?WBƀ cBg H/pI~ -&&kv ƃ"fY;@#Ng/ xxEVd(H%AXyoJ%Pl&M)'DI%XuB55( DQޤM?֘4f&ЙiQQ}pN$K״\7F\>(vKK>}NOGV49I[;L҂ Et-I-c윳D-)8ʼn*( 5RӜϝt^*f7 28=;vh5ْ5E5i;?MvԱ7~u}F$elQ/S1fnJ KƊPR+#8Lz/ YMU#X䠵?Q$j1Q& AjP)4 !u *bB bk)Vn1ke6WX7Z+uKl#k$և V8Zp$җ\0Y֥ӸC:;?N_:;jʥ=m4+mʴ$WJLUkVOsD0nA89CtY%C!$'VQT8 g)IJUjyvX>;%*`& Ƃ+ZM$=>K-NVJ+{EηUΏit~gEm|P!hܸoA$Ek}YᮌߡvWHJ0=ڽ.+U52J55r*\OqFuGR &']aEcdTOy؆t[Vs݃+ -KiN,x V-3Y97Պ{vjıCxmâo wyspXkτs%$#fm-kߦf,VnarWӝ-Ciݰ0۬%6E{o|ӊT:} -P -©]2ښbCn `(VD56[ʵrr+>-Cbd -.5eТcI)d~9žJJZb&L :M. p\MV 2aN,̈́Jfq$G|re-m BәByU+v]g.^B#=zaqT:'{.#^J. -4iSr" F?Ã?X6F2;5{%0k vlqw:wiPƁŲۄfeIt6a蝗@aQQWݑQ4@Ny*J2m昬$?L@׻BSVV%$ḓ?wAy &'C])tIt V%'*OMaR:IGN<5LF|?yNjN+vGi',, tL)WP0^t2Y]} P薭n&`TMIoV~ՃIJ7`3ymIcJ辸IE7eI5?1 ਸ਼`5߀r"(‹0 uB$Pm\'$ࡖ:"w%8n7&A竾$G:0` -IJ.LP&d&$211}_+0 -(1nO-P}M^*؇׆XA8ePA jkpPOeNP2 nA(l0$p%`1xd0 ]x>$z8ca0m]s~ZJtѹLlЀ -ҀJ*1*zpI Z[H!O ;BwH]$;L XQ7zqb%,M'^6Hb" \B$F"s[?d=n<@mגr`:"sh"p)HGӐ16)?8δ(J^e@$.wD:]PGJ7߉H "e!^ V^"o"d% 8, VG$t c"эý':L1L@b[zXn<ֵ @%D-LX֍GDj\xx.- &3#w?d{9n߀G@ްޮqN|f?,C~cɴa~̻ߞۿ?>c~o~?~ߟ_>}?}z폟? o9Ȧ -endstream endobj 6 0 obj <> endobj 18 0 obj [/View/Design] endobj 19 0 obj <>>> endobj 30 0 obj [29 0 R] endobj 48 0 obj <> endobj xref -0 49 -0000000004 65535 f +I:B2fB0DtvRTPPT!VVf>bE؃f/yd兆FیIQJHn‘*AAAPReP\NcKdF`_JLtz2KZϙ%yKنhbF:1#Kk\/:-/P:<3Eg9ruVU!M?[U$֬ [hR~="F~Z'_{.mHB9K  nY%_2/ʐJ/L!4d5L!?zgV@W#ϑ<FќW_xkY0"DX"-DŒjMHV\E!.;EdV3!`c]4K xbqZVL s$ȹe%0z®u7֑*2BqV=.8t8͍N.0¡ fExMt$Uװ darDc7 +*H?xDT@_*ΗγݺUdj3ExxsDK x,-C5`7=^'@`8eFk-HG?0{5٬DM 4IW\u&\i,z*-EfkEZ=Y 49^9}?dѸ"q #d= d8A~}xHx]?^ dtTKp + \D̫jhheMC=̤j*2;#]&-Ŏ<j`wgULle|^\9,(:I0RVA/E4cWnBaG̟<.zD7/^Ch`|e Sģ>{V+v]G͎#Vg(1 *Y|@"yEjGNtW[0Ȯ*ëM:GgNI <]ͮţ C%꯴䔁|'{Y%taʮenib~?#U, +Z)l'd&fI-OvF:Yy9sJohg;+fQ`!EGF<~CPGE I, +vA +s׳SӍL,KsoJwCL#4UƜ !ɮpk)Bv АEGSL|ّ">/N Ϝ@3Z:Itʄ +E#&.9-jwNpD<4&(5, L(68drF-0BIKH$*0o ux+<&B25'JDt3͒=э?!_2JhXA$0Htf;DMS1= 'R &pci%4mvHg .䗀8zqA? K6U9#@pl"VƑ}cHeh)fXA}ԙcS.3O[,cPe'K MYE7m:2l܌ 2X<҉`j!2ICUӛ"y4ɄMU^7AF\$:{cf' +ȣY ;HxS =ObY/#%#3o,(\c:R"IS@&"# +6g*2:,6ci"`*`#*)3UF,"MEf |+QU9ʹnHO1@'GīLIYpr2d?v +Ė.>41fM!hF + +{ +1` :C.śDڈ1Ye'fFfh БLE jc [՝ǤfQV|][3HADoi*P(횾{s^&]T'U*W7 3CIdiI-Zi,ٌH\r#*vŸ&2^`XdfD$=tgY %Ml:چlvÎmBRG +̘sҶuڍt=\%z-<! +KתkGZNL;" d,ϖPVTQfDJb,rS +]3Xu`Ji!# u;xbK2(=eȕLS2c @)$&&.,(ٻUw-fE 4Ҋ5Me*c54+$$XUT^UC:pR-e}M2sKMsFWMi "b(j!%7'N)>ˑ9a],E. 0Ǹ3uG4ivDnfj3;wȬ?Daߜ䛨I$=QeOj9PqI s^5IY֤`s@^D f8δ4mXNpv"FE24 pa^k|}V>\#(U& ?RS-!"Ӳ^yaA$Ff&`*2f4%Yqə +}F3*gj5?/z9Oe^F"bXZIT*!haDY<"ιȐ(kС 'b_Ln8vh/}s;3(ߨObt=UA:ng.{B:ew),c +0toqfrxzj5t6CO;NkvΛ1g)Мs'V@e;ϊQ Q$i p4@2yGY1}ƮS$MǽD`C=OՔ)nHc>zRbNQXi +Vm*zX%\eᠠx^aT=N3gu| [vua֨Yc‹L&;ǹϿ'14 +j$ +)£LӾ[tnFG#1j=%@x"I}d1S;_}ƅ8I #pc8DCC&F˚<\v-\ z$bɞ4'1$$3MO3{+%F7{_Q3 =-<42Wbn"2{!"yFɣF9r7  5#z~$h^2$?JbrW:"oe(np# |'{F` +un 6?XQdžK-)ޔ"¹ '! f?CC`ar+0=e熙O,ɽBկ^uf0Srz~ NgHƤoh>ZѬ$\ qy4\zaD}y= >F&9rZA+Bw_ ؃L݄Ж+ +Y(-B' u97}c}.3NQ|R&٥:]R` Gإf8DQ]Z'Iviώ.KW.]_&M ac_%tbLIW!.9Z%*dx5]uti M`,Y*%fF߱y(bT7޳GxmL +'d::(ژپ7ԉ-Za><5L]*< +sҥӡ-ehV/Ͼ&Bvn!0{kP!hG!⳸B"[Q`B # "K4Uf@c&˓_2fۀvYu垖USڊz=&+އAzƚ23[y]$e 01{igzK"Ut=O{Wc4 t+Ƶpd7ul!Z?[ql"gMvl)AԆ1vS OWQ? +V% H#zy#mg MUӚE;Ӧ鑤Jis>ЦO4 ?tƊ(/ENڝғyw  I$o60&'jz*RfƈU6AT\urrB~|=7u'[ `!*.Ի0[Uo < +JmP;_PKu0yQ Q_gVlr1Es9[`NMwLg깍-3 L$iT<۞%kP;o MfU=4]LYD],Vȟyk`;d\oF;[. ˭EO9YJjf@zM]APcd3Yc8zAN#K"Mi; $|ѦO\~ȔBK9]oEHI_DH}/"+)H}/"\5I)] sdyqZLZZ)_Z}Q/jVV_Z#EVjE!21[ \)G F?$t9 ;ŕ/ŀZ(7y8?™ }|@.~qo/Q3aMRET*&T /mgFb + +k}m3&?m}ɸ(5/It$n}ީ\^̄dm6TUpw|huD[7f(^Y9hv~id +3 )5^dNP +0T#Sl PvbsA=RgLDuGL'nf4e_gx!|رXo&GZ |-@4-)^=Xt-3$k ]Qg2,~n4?vD$1|&&)xW4^Tǧ%`D3=?<~c>ͤz̀:h &ooOE/kn/$杆?Ҵog1ȿ60k7|?b4>$y"NP +~&hTdJCTٰl%_Uj*CYxf RB2`ˬ*JݤHEmB$Ds Zd@xiu,lwuωN8أd@XoYLsd ++$60"װ^eTjlj-Y&#9FRYAͤ3T Dr# Q9ܔ 82k!%]i 0s;؄a#Ǣki1wj_^vl&r9i4M +ϱW?ZN_>Ÿx0p9  aa 9msƨ꺟/tge{EC'x?{|8zz ٠4>) [F{, +K٢'qoO3g1V CW/,$j?pT +u5Fr?d:(5`]Av)ѯ󞁑5;`gB:,T ykΏ fxT>(Y(̨Yo|5촚};1Zr-95Ű]{2,ּfKzU:T]kߌ^m #TQ2Xfӻ. ) mħ(f V7N}i!87VFnQPu^gl!Gt|Q͑ XgC9a#a4 l)*$| }L^W@7ކ|- +6460Y{GYBɰ#U՞4k +" ځ1?sEig =mYYC(ά=;?>X/;#Za奟O#H'3&um!kxt]E"fM=yjC3VIYc$8-7*ͩJ; A\Y[SPfLbfoevlLSih!g0\Q#v6s eQ:g4 b}?fˇB=/X hys`EeSv S +"Bw[F|5TAxN-OVKH0EKH€-E<fDŽ=H=(>Yls=Re&k>8 +&$8ퟴDCcS [*/yd.xUDٴ>C7'xjCߨw9nEzk۶B>ڕǡͣ>N`ha[;`450(OraGTm + ;XZLힽNM*/ϨY퍁:fo*`mKRF%J 1}P `?ZϵUkVc~{MD\{n%݀Y )2[1ڨ;,}W,tÿ-Xo`=mGc~ԞGXnd,گ5>c1NVkQ͟!_Jg5uz(~J^,"l^]ww;9۸ +[6˞=sYQs"а}U8˞G&{&0e+30}s-O~8$P%``T*ۥÜAԞ8閇TBiGsccA(U]2 7zEa^e_?ږn/L +Vu\F~_iNc' +ò'ϝ\ -T7.ͯnUm棊X3MΨT >AtY-]hl: GQ^{3k@EL7 zDN;E؝24Nf þxtKCoyn# K~aK5M]7+o/mzb3Ԧ.a]=5y\5\Fh#⍀2Qd{؋/Gw_;7V=ze w-Km +vV0f/P)~l߽JDe7|!P'+oX>LV"'Ba}5.sU,'+{iKw{|zq6M'n^7%]iDee['lf$ Ol,@3܇t;Оr,<NNm .8X qZM`d%_ +;I. v^wI`?(d k-ÜZHPt91;k_9 ǸX}a2ԍHԔF* L pA +KCZk0@ZKeLny=A*Yԧ-0NKX +'㙻ƴRMW;&%pt:9t༹NBp5s*UҩƕPN?ޤNFmPnśN,_]&NvfLHԇe=ŕ`ƨe YY?T`CC'Oy1"MMW[][3CaЋsn=s]r 0}q8[d@{5}ԯ.q~|^[=9L5PZrü伥@Ǡ핶jgng?hIa;tNoh85 v"U p#)u M1{o6& iIh'c%k 4LGEXy+a6j&zznqFqK}h$d#zrUMԟ_R}28!g3n#xC b + 4Y w~xtx܈ŭDz6Ov2= 6-~ [g\e',@ JH\B.Ic#uV()u0e][u[Tx6: $%QNPMPRNdĔl Ԙ<>7 Ek{\|t{'1ցfAO>&O:t(/ ӀaWɭ)'ћZp}^73Xv'CNg +=Eٰ7ATH_9@l3`{]Q6墮f33uFabf4_W~V^yuSSo/7VG Z0L7 {?8 "r^ s!z6s[Ve:ёxz'[QO+T{R-ajASKy\ܵ́|5֋7фqgS9醛ܕc}螗|6J^J:٧W\EOvqiͫwd cI~bho0ؔ\݀~YnwL_TћV-]y€qJ6^Mb7jwvֈ+:F5ܽ +v*zU!C=1Z /pnkAIUx{{?pw>e'gdoZ_=}K&W3 +u{af`ֳ]yx]3L8G%$v,XgWSzgͱj6UV} yN5UGe:SII9Fy7)#-lHcL@n4& ݋+ѝQoñj2L~<_EPv#ɣ +TNnj.vMmu3yI>IU_pB_ޮjS:^S foVݗszu0{W /.áW^`ld=}T mɻÍiS'|Oy7Գvvʶhm묺]Omvy2駃`cYfVKNkLWng10O/z/a.l xoK>:a؟'~?I63#MVrD}yUĖQg e;~ZV$%osd;h/&GcS,Y_#{c6O|ԲQd%z|t?raޮ"NCKc~IbKrH9Õt"TM'](GUboQ!Fxe9ucTZBv^RB` +輶L7ASo %LL䇮a2|YE.+vXn-8KP{{!@-qJj(g֎)Z/͈4RiFj*xJC 6Z0ˀIHK(ƴbb Dp/ + u荓F2>&Y)>y;NGa1*P8@96.ՍBTiBe1v6R\V5u?q=QZ\ɓex3҅Tt +N" % ๕n)ߌm]N+4jڟ߫a{5ɪ,lF +NdbMld$M|-U;yJrgs1nn˥P87C| 2; kmNQh!܋abHk},|,FyY:oWjށވe`歛%BXTǸ&JHig$6:̅-͂8a*^eOzpFt x.t@{|ީokW@pBwS*Wه7dҏ3N۝TI @.[/WS;nĐkϛX>PO +AG[؅Rj&w+.D!xNkVE-G焋Ǖ$ov\+6e)PƑQp8,ڲdSAYʣ6Tm3_On䱷9ó~y>^*[WE6ED@5ZsUX}Vl<-Z$/iP]܄=J{ZwUJ=Бadh??lWظ$'^rR@-q{l :XnЕvy2pff +?5C<{)?'9:+tRtĊvtBV,;Y.'#>0Tfk|V Pmoϴ큉=TsB-Y3-LBv3=sΙ-~3YpJ՞15ˉo -ij`X5,-]yPpKs?vCYbo:oRnsC@niwӔY|@2scG0:n%/BQ\r%$L,;aݒ6a,  xQu셋0_gwIfT=.x.(g5Xk+Ϡfx`$P]X{h{߰B㖴I/Ia {@t& =&|T`XE]檅z:@gmg&JN] +fL B:LFR wNR( :z3l70-].~-*Y1\~$(bp׆bPoŏ9)?@0ǘ8j1~㸼^oj_w{{b2=Է_o7d3wyפV6:?ݰ^;tO7z7֗^O3]cTmģg{~ctt}|̲>1]]_ƶwb[~v1;X6VZ<5Dy_~>4P9 G{+| ^r\{Hsadrާ?{[Krz'9c/okSQP|&0%?[քx*$Wk?׺׫J>tw/x Ziq*m4Yj@pP!| 0g(nԭ[:xstV%l_5,/cx*"xs0Mun7kUތ.TrZaV:L羅rR í`41Leg:,gq| z eu/|jq/*5u_I6rR Jj끿Q0*rJV-d?&֦oʾ9K|q =g:[ SB BW4MˡG1?=_R] !J.߼8Kx粹VeoݻoӺB si%hܟMU _k嵧-f̻*^ +[wm7+oq^~H:ލ7xn^?_{C7o +x#k_xjxŕW^)^ymثl<{UբWOKޭ+wTݨ޽wzꍶ7&V^ު7}ޣlw{Ƽ{ xה#E1VN%w{-{o;)n zyRGk^/smw[)eNamqݻ xK7p=* Wڲv{Lsˡu;-l/ikɷ,m/kgzag9ڻ{_N.si騹y<.g˧e|5/qc,[seHX_Ar\;^Wr&VJ(❶"UnWVo+ɳaKdn+Jm7rX_yһ+BoŨZ+oےo}b~eu}3Q] >V퓷U{Z_WND__=cz] o>FW [+ӓj?\>pw[=C?+} 휭\%;]|'_N G}w˃[s+빯[)ּ傴(_Z5I]^:֢ɷv\6kݵZKkUC~Xk'okPf}Zu)}?HG\yqo?k.}{^]i*}?ϷskB_>f8z("y@dp=q&{y% _=^{XPRcP\56.:Ql7A!Ty n.WޜZhXiXY+6Zz \oיp0G[1ruD#EpJۗ ixp]W7\B6%ϟ"n.P +)Q':|Zc:pxn7ЪYY\[T_s5~,>Q-Sդ,"DPQפt:վWr1uY>H$|U$K*'onF|_C%eE)6M][S7%E4[zOh^gC헑foîvKV?+٨ ɹ_ڎj~v66w[[e/Ь;ݪwKՓfg[Jv>.o +_wytS;]bwo=+J/׫'cY<8&O{g'G{/{zwm?J4zOn|L=D1U\>ojtcFL,3Wbv?nv|p8^|Ӄ`w'yqx[K6գQ,x;z(%q`(+\t\6Nه hc: zw!ci^U]8}o{ςjvSRg7Yk1 gN>,7P+?_σ Sex;Y[]TJ9.7Z_ήsc*w,枳n޻k?*ūUd>.}Z;n仉U+W7oWK*Oo٠8-_WջUAz~ٿonu[@息7NC-yc} +-϶OWS'TWK*r՗m-6x}~ +G~p=Gu(^QlV%Wn]? 阓Ll_VR%{s;4*Q* +Q5[{6oMho#Zեvj>ˀr[zךu26oBs'u5Zm;VYHz;?KnCS+t.=Ͻv;A]{ݓo +=6zueC=|}uP9(;_9dQo7A_Uzmf\snxkWm7^dM 1ͫntzwT<'bO7ibK_\ؿŶēqr[@G=ܽTj @n^x7ko9ЉiIuq? ^Ͼq8890<$O9mC5Z&n\F#O׶y-r j`߈mٛ\~bjK[q` $Eڃ;sj]3n` >n,\G&NNTz1}- ~nPu{W5Y{϶yaq/B' $H! I~xkjws]'4HfݧJunF'>P%͓77l[a"Uwes\J5%F|MZyw}SFQ@vmEc_N9{r\FhchoZ'bwʫRIVw GZ}t$:l{Rͻ36r^ n +zWBN ΦDwZ^ Q"7Ow Q1ޔ'D&^m)^Ɉ6nnGnnneF;б+=D/8Y%D!NeFޏM?ܕX9 ˈZ+/dDá#N`ZzJNtup=)>!-/o+,遑ۡqQsuN4m-Ü~FN{`m^DóH~$y= vz# +T( hsee_Jź*K־unN[U?^.~S};nԯz۴^?N5&y[{?}5k܌iǞY'M`Tvz,/w\< +߫U}Z~W'nKcy88o膯x{2lJ>(J7^ 4|)7DpSX\ɉzj+'Dq#gBh #q!,3.`~/w`%̉Brn2_A4x;9QS |(_I2WzJY~q&1RQ/ VMSjSx4;ckWbӼLB2ʠX.4=#UpNiɟTw~YJ:t6golV!#U[;t5A՝\zL7GצZb|u1iiD}(XK[w{h +LG(RdTskNƠ:j+8!g,NV \uHP'eNDm/ ,<|r, +㪤8s)TɷF֚6=0z9UuL%1cWV5zU}28V)zRLY3,9 og2屺$Q,<˹:/U AQ|$mz5`0+͕xw; ǝ ǰ,QzٚVlI IVF4cw5QJPv8v` 1_;6?tß}NrzwĒ4mHIs@NOD!Fuj=}=q8Nؙ@8Ky32:2k24z2)ݝnr(N펙;~F$ucFYo\~FYu2#Í/u<#y*N >xF1KŹjgdx>Owp1魦aj5u$|^DŽO A5DsdF`ݕDB1!* itkvo*'T!O~~HIݣ`Oq \NuG|ۏι|t]9=oBYDqQ:Tq[!y|ogT>s]R5Qc5T3JT-}[7S|S|vIy\Y߄}GקDl__{\"Ou[e#Rk[[)Rk8s>mz.=\@W>[4]0N,\Nҩi:ٜ_:;M';KGi:\z}dgk4 UBQi:: Wv4,2i:< ].͵3.7LoN{G;?M'LoWOXli:YeXNOqT$ew +W?M=3ҵt4i: *s;XtVBi:YOVw4 tɝFVFpNvN_:>M'SF1i:_y4,>Sd(/֝b%9 >itd,飋vGfz*ٶ`--~QwL ?>,eJRN{rŅ,;JcIHma4z +*5^8"ro3~D$nVnR]tW̚l+5w&6(I.t]zJ[iҋ:hFkF}ѝY%5w7EwƋG/<\;W]uN,]Ut۪t=/D{ +/^B |G71+wܵ w[m~Ý.G}w熻FnX~]:8OT)c7Tp'9dp> wuÝWW.ݙk큒ޒΝ|<|xwYoDFX9wᘭ5&'5vveǘ5xXjO߁kc2mJa~ZxY(M}\u.Z9xIʝI<9g7֖hhp9R£ xxS]$:t>~fM7u7DonfDObwvyJ+bdK|G?dY )\upI.]Տ_:O/ʶt܍@{)vVOu?occNwt$½~c[*Jq~]^?1]Е̻~&u~V—Z;OpVv"{fz{$[}JOye|QomH6oN}_/;WmW`o+_t__܃V6Ns)=t>s';mW۽ׯĐ#ݽ~faAuZr'ot[s_X ~ⓜHm^?~cKr_{Ҫ9jv6]O +>NN^?m>p%p_[#ߘV?W*%ںOI{W]83O7a{ټeQc{*+ۼO_G{ѫVV{aٽ~Lѽ~*I6Ai ~1LGq5-5O9΁깰H1Z/;>J}ڛBv!Uܱ̚h;vͭ@,n_@vdb,9>_ݽ~Ԏ^tfH&]\&G1Y؋-}=k#OC?_Vnؚ׭WO7^~oݰǭZln-7Wi}՞1{d_|iћdz!UTأT+m~dm`#{hjje"sDC$ &W-\s(rwq'2s}"Zfdsrߞ#qwR>=+L->{p髧?^C'; oGzXVſrǖְ^>{9U Fvq<63BN'(ym8 o5 Pa0NҪ=c&,NPkyӧřo'N)ߛm^}Z^d9uV0/7K'ӏ>[^2W^O:Tٜf$t>-{6F>$Y8[ \}_Ը}-܁tzdZȗAc~ң0z]|{0~jxg #ovo'o6̮~?xt-?%\ -֕r(mӺIG]iQ?>6F#A2DQmcM: >鍐 <!umyw 7c;0Ky4^w}4Gnisgr6}vY>#$ʐoJI.\>[§.h6i=,`:7|ǖ; +ot=>6~,\ 'x3>iXěXSg +{` $mgI:y1L<~b|j8sESo&/@>=9еlmZec'8r_<܎Mn.&# +D>Q'VAa^C@ {ad#S׽J;aiQ0% +G۴Mߟ1ln;M hcGVg66eS~6rc-ӘB(4Q`?Imث]ƔТ6n^L@q6V?{IU5jQ Ov$ +TcbP®? NN5﮹7 8=jHM5qylM>;3p>iv`3?[cmϳ?_=m=,to0cc̬8mg?Z- ߻3 0i$4XkBhkDt+F3ֳo=2[Hp]o2Xϧq:c3 6JɌdKlInkd0Gc{On٬+6]=_\`$ؿZQ(¨m}zvp:#uݝqmRX7zeg/'aq:u""}̽] +8vc\wxj=\&m347@e:gv9aC2 ?CAӫo00kZiwLs m=')?1u2>ݚ[Ez16\YǐohuEoۆzF 3y켺8μ`Wכ֣wct]{xkkIC|8]dY!|DG  dٞ _ ~tŜ.u'v>5tp3_ТRi6~6ߩ 1SkQ9|dxAd!GE/hue~Ǚ2WN^Giyx1:D&6R{~ G##N#g6^wNOUEZv':z=Z(ۑ,mζ?̋~z?[Ϸ@![I,P;ѳT,hn=}HWOǿ_}O <_!ՓI;n-O~bwxbx0&w_"+.;#$gKC|{YlƉ,;d3].Ƨ-4dH@%w G98OYq"1ambek.B`>ܑ'mC:>"s [tΊ VN~ā*oaf{#˳ȟ77/~>|n{?>^ܜ:wKWg?&\\_󳫏}ԯskO1 ]0FW KokvgN>Y|>t'Ű?~mxvr|!&裥盯3?ZӓkOl,Y9?yg].7}<<9n\q>mÕsyC]$VΙ<vO2q@(JP= +Q@o^ +HϴVGq@(3P5vVGգ^'֮<'2_=M=r&aj,@Hڤl]IË=.f6 h!=]ƶ|kouۢ(o 4}#h@k/y7vÿag;QaO>[8qr}St|so}o=Orë\p<{34VJ|_!lyVG`!\|?j ށ< non'ݜ_7ٗWW~w~y;y}|i鯢(K|wu` ~Sɓ5=;&\a>ױŰkkP "TCF`Ijjjjjjjjjjjjjjjjj05njhUjOS~5߂6ZU[p 5<4^ֆj +& PÃAUoҜ5PCй^ kPoUL 5ОWVМh 5Ԑ[j7[VDP!:WiuSGY5І3 "DEYC ;t&b!\҂5s T*12,+zjYJ]@D*165'U4w\6鹆 lhm{Gm#bb)j U UEHd*}f=k+P̡R~}f짹 5G{'L,ZU~ endstream endobj 27 0 obj <>stream +UI>kBZRC JM]4{ѤM +VǓ]VĠ=f?$"j-ІH_K1/D4xeH9.<( 7V* &h%/*):HZOw*5c`n6L!H]϶5o+P_&T D%cWiRYDl+=B4E[U覡APr*wBD( , 㪉HKs&B .P0[7L?JCАP1WǼ3q\5\^SżJcU+~T:g#KOD:>KA,A"!ͯOG49IQ&z +(W%i%Hcj** @{8cku7U~mR4`}fWlyTEzd5QhR⃊g~=H .*n0Q EyEٳF =fɯ*T&pAczl{US* $1szlU{Ųt DMťH4않`*&y:'_#T[R}Dw=JP)]*W5erH#sۖ2։\c"o5 ^(bzlly9"D*N4ğDMU6,Ljï/M4WlE`f(UQu^j0#_t\:'n8R:DI=fR0l^ VOCrW* +9W=ERsDtUfúHd,SOL(ehܳJ#b93{mP&g3*Cȱg$O W8uh'",AVʧm fOU~1ǡU)ۜ&ϬO7Q(*kPZ!.1@O,̝ENDTD̥9҂REI3K^m ]MtMQ%&n{%RWo*Y4z@)' )⃉D5<4VUafķz)؟8 z6DbN8*0٣u|4ii 3\CsHMZlDPXT"r*4PcR1LʊU SL4WieU HWC`2z$2Uٌ4_4 ( +J +U:P^Y8[U+1gPjqCp9rP6R"`"o"s>E楙EzB"RJpiYUhj:Ft÷RR bpZD"cN)bR̫$DUU了ʠ2uQ *UDb6):JçM)URU`*/SC)hտme 2`^DX[$ꕊ% I{(G)4KySI%bsPX9HHc+֫)XUK&lX5&U5]QT'j0QqjaJ e $TKKP_D#'F&HJubL mƜ +TJxг* P:k$:2gFä oz$zjhxRRD0SP%2O쑹JK<c&׿5#委sld:4g)9IOёEaٷ\?M3ܲZlzL4YC%۹ 1bK*FWl"W,(BEWM%HBpB !}]<:rY =E䲙k&9`bzPRnMH%J2a"Uk hJ(>A % + |TE^%FCLpTRDҟRDSLh*HB79rRHhC*{TUJ |E;WEĶ%2GfU"x 7PDMCh)*DaTlU2K_i(Wr2RҪAl, +7Uq샊U)碢DEf,"&:TJ:ȕ? &ٞlђܰG_\AD5P)"s((TjȆ4/P~ l\~iY.'[{XM$XIv}JiAQgKɩޯl:WSj(U֋ʊ8y&5DD9oGʤ*'LBgzMF ֩aҔSUz"_'h[O1QMSPʏFRޤKi>(J/dRf=GA "RF̣ RJvR[+F.P 9"-s%+8z5J`-1d_Rc`JAolRo*$&ETkU Sσȿ T s5t'Z*)D(A2*~z`VQ/AD*N44 oNMc2Z8Eck!*z8{饐I$VXwTOو5RGӛưşR;qr4=I3K_Ii^d~T*Jᠴ +ZZY5u%LV*h3h@/liHA6Ѯ U&mD HN+JˊVB+r,}6K%ʬ[ 4zNA[!(Ag1Fm"a)nđȤ&ꫬG8UL&J'jjvQj)'=EW-̵(jPL+OGJ=NV([#e:y\UT՟&{WT >4y2QGSVj/ĔTp.+ȕXŚI5 ^R9Y<<^ieSxÊ#MqP 5rʤm_íl*!yKA̙OdMhΫDΚJʳ̽US<׻*[D'Ajo=fPd|hP${1U /߅.wOiUR 7q>HA) χ?RJ]==7 Ae[49 и7EWA%.6 ۂI;߿,!)F&k_Z]W*gJkE j".?5 gv{IIU+۵T_ 3KݳvdP9?b+$D}U #p,MKA倉*[ß~euZ;o 2H{Os:Q(v@`6Ku4~ǥKYY;o p{.HN#= D Hm7]&+%P `O1*-s-[ ɺa+aSj(.Pt4I-_TT #GlR~\C yP"r 5z%ЌT4'Bg?bUyIJ 5k@?ejᗁ4fjHZC #j{pkjjjjjjjjjjjjj~/4NNzkt?ݓۻH؂&-]]\8߾)Hɗw7?> /,<=;ܽ;#} G^Ӟ$OW7'g@dWw~N^ߜߞQVw> 8AYY9N;>%Esq=y汝H2}_9Ą"'R +=~us WIO߿jy BLM'_q +]|%]듻/wH7'?> a$S$?~O󳻫Lڼ7״puw}7_\Лzswu?'wg_\LLl <'kt[i#`=?m3gY ?WH>}o[}{^DZN}A WecG8tH}4=۶!%hz)v3CL^jVIa +])0)~3Qaմ-xxt#V3T1o؋Z.˜byPnr))~32y)SZ="n9T-҇4W䦘0`˹8e}(o{.BJXԅیm3֏ +s< ՌB6~ P'b;)P[.C6}-PshA&!s0H C P)QYu<5+ R,D`1>Q)\՗ h`t< D +M',r( g7)sES"t5LPi(a{ht@&HBUGM7Do6=&`ێ}O.ۃqr y^L"?z)x\i>=b9 $S˥1A`H͏Q P iȡV遝Ktb'|h |[q(].8NT~ Cku-]x>.SD!Z@dALF`S#D}!)cǥ>Mch!€/( '6xfm\"PL_D;H&sm,-.'A=ҡxx@PY`ƤPn'r `C䦥}svUbM0y + ]'OY$娞@l1 &JeYzRY(LVi4RgliM* D߁ȳ\á>[c:zG0`r aw 2/( S "qwL +<A'0) qTx,^2QP.ȏ=L"km@p70Xx1{؞CwE%2A.x 7I ڤcl,d +LIBnQ:!dFe_ $C;aEBE) #ZKx\I^dcB9=Ã864@j+b̶| Dt<Ŏd1jI@g[68j|Q)RuhݠO5 (R9 urY>v1LwѦh M]Q&Eb#&qT>"B+,"H/zɀΈ )BHT$Ɩ\! O +J"Z `gikbiC;I?( n$Ȣ:m'a܍C}cuvEj4}BR@ &-Yǎ}M#&xoLh=?>tH<I@QB," ]]; E=p瀔-?n!Ph#ҳD@ӪF\vETR6IEXvH +.Dj4oD0V!J# #LtISa z)poZ+87h.)%)'jρSVX +S9tDicjyI$t,Øf@q,Rl^)`Ct<6=@)q a1(b8|.v}ZX^}кGsa'b>p~C Aif@'Ss 촉@qL<dXP 2XBC:=T Y@0ABa1*ioCD8DQA [ Ov_bPņT8JS|``+GxO $ͅSB9mmZ6KAt$TDM -#Hq::)0\I@?.w$4~,q)R;>i$@6IM\1 vNSq$ӒCjS2 a~@G?N:#*1)p<$)N6; C d u:E IJUEz{'>ĀV@8IqF # 8p`Dkh+Ȍyt6$٤9$lN%(I d$fbz2R2s2%RFm {OЮZSt1ˋ6:Cmm`q hbt560$hC +(BMHzv"lA! Z'0']D +nLR(aa`H`pbC:G (DKI- +ә(B Fn+-:SU ! +#',/S@?ACƻTY ; +tEZL}Ѓ!S4GrbIm9?e9H/Sc ]xzUDJ)zDbN;47Y\))5!BJDM'q,fSc6"&\`꽅CyT%h BT!C*."1&8 d.h)"QHq1|LQ0a90,Lš4 FioZ=) M!&0(X3 T\AFBJ9\Tsf;0fi h)c\LF jQ:˷S/#hp.s]!L+ȂsnJ%0Zɼe4` n:4df-'s$B1Jsb%B4G)Ȃ tEia S>}|MբB+pz&ص iH'hܗŪ8(]^gRuP/p[}xh(e 2rnw1wAyBo4* SQ@D.K.1$ͧ );6iAت(H9H(2L8n&mq6J jhm,Zkq {y؊5E,whIs B&;V Q$NVN,Kaal,:XBB 28f#@f#CI羘b8' +*BAE!CL2z +0>/T% +r1m{@Bp.'{=צȌht6#Jc[&`KF'9yRq&} 8W@ Mj&v,\%] i jːqm܇15ǎG{(/Km%8Za:Y4pO:fԪ:H"324Dp6dG(d"B1;t˦vE2OԄkC|x<_2IDK5 +iz!K..Yr%q͔s;d~ )l΢,kԃxnkL)hm_Oc7qV.w"qFԧ]:lAڈ`/K[@.鵚Z"K:G.\\СA᥌CcNK}2o )>ʙ$K[i[ n:wH"\菊fb* D8Ep,b$CgW}2{b1X> 29Yy0쀦u'@Dj "syߊ)VڵB]:i11=rK@3I,idr!6y0,`b.bH FX@:4biBOII6[sLCiΡ3):JcF:uG(!Ӂxy\(gv]ǁ-Zd4 +CtnTمAE! h*q MRb>0*3ǡVBwXwY'VwEq.+F + JAD BdAlSM]झO}D +XeyL='+Ng2AGGG6Y{}5y@6$VIEV {.4}ʃumHh#ofiMќ1P|2aDb}G&m҇@JD/dl'3qq:08V 206-q5T瀝qLR'|vX>IqH4 $r,;\s3%ӡԏMo%P>tbpF1)a+C DxtR&[ӠXȪ#\ai +N,t \^&3.) %3i,. +^&p'sfx6 1-J l!XNB pQl +ql)n(ApbY_i)d&+o4[t7WHWٲm(_7 &PskLەUt eμH.,Gбr8x[ErS ,:ɂp̉=[ݨ \fSȮ5ܾfY)=/]!vLz|F46@\I#<\K&#IMHAR9p݀ NT,]\'0cM:ssLKBtNdҪe +9pEAH=<{bMpB(EVQڏS=r1Q2pB#t"y̷@i/NӬvm\ qk .TBҴ9(Ħ룏alPaObV 0H S݀"i&46-ޘOvҩx(ȟQIˉ*l!DIB>BVE9znU~t Y%-Y`钔fs \Owp|7i- .*jBl_!"=/&XȄ3 +v~R0NV + +Yّ秫G .@6c ]eI`SY)Ơ +ɎRVM a:d^HH-F- oobPч.!T݀Njc"ar'lMa%С%̆|(?ҺvY1~%;-+hYd5ZM2dGq֙Ri]L! "|Kdy5"{'BfŴ K&'k;6.V'Ab芙Ȝ%B Dz·:i1\_#fLMG&w~l_fb%hEe8L2Bzt`"Q*#zd6db'[S0%$sYeZ͖S9@|'SϾ`BLGIdDwݪ+6[8?$Q YRDg3/ +BytAg I)oץ۞qdX[~+d&NuFLG@vZ`-RJMq\H] Oàg@µļׁ +ݰ; [xdA"xB9̂ApK"3Qvf=kC0tLC"5Elxۯp A1&ц,+dD]D\dON$*%3]0qOxq6N_F$qjwaJR_6cVFg_:}\ǀVpVQL' p[GOгlw*%_d;9ctFl)3z ['*'gA3a Fbη"=6f^ȥN;>qMK'rr]blᜣ3}R8nL +(UMxi.\ɇls~r29#|;b MS1NV0q~\l#:gr.s[?["i$ ٳLܬDYnO΄ίoύiW=GhD'Ah#d]ۮ%U2w/4,F YMϑȧi{*"Vf:#|<.\ 9x' uPDW(sιeKu[_-r,vvV;b +Ow6*hwǃt8gZbe[0LW~[M􄋷!_eF kV"H9DY$ FK.u;hpo2`82YOVaTd–`?v3wKC^gذ[Tæa߄[6&SHFZ+) ~u\Nl|ʎČ ܄b>pj'% mMA"ȇ`!20u+5R/5XWژm.u˅64#cXЍc?L{Y܆O+ňj PY&R74z Żk1ؘZ!O\i~oXcB{ |abUJCc`hb j&H,Ѭ"$W#'aDUCmg""]=Ϥ@4D.G*O씈"v SNlؘ} Ḿ]&sT7M:")KYHrAr dwaV"gnV>J$'n9 [#4emKa_&ؘb`ûٻw9 $wic*pN-T@F6:;S_ɬ1F"#%) +?-o:D}0jxa۔ +R7F{@BD +1auM^GkeAt`B06 ? nɖqҋzyeCu&7Qз^)fRqL yAEK{mb(2'3u`ɥ\Fz~x^'DoOsjI*= +R^nH dY*0G&5<3!X(HOxIG_/ [@5M!sp!!$0@r4좏.<ty %qNe{p 4cgûNm(~N ?-jI^Q$y/ZT +Y9׹(嫵 +#DTMTu'Ͷ 唿(¸$%NI;d ʠFT.X]M+tA|)R6 E2+h> +MH)b戙sydmx㰸".~OB=i Q-SRY6׷)5\E1j0XXGSTQr=e.N8:|LD,"1'OVh {\k4l鱗Δ^ 5Q/@@Uv;Cwi@ !12BjkO;]IA_ɻdKբ Y\C0" ]fqC|.ą{ƤUipPECx޲]<lY^}Ũ_(Q3G5<);jZU. \3O.I!%I-+z`Yt_n?/cYFiA1_ D4F#*w(Iz*~ܢ~)3/ ?U* +dh@ en#[ZqcNO +'[v9% {fzTYMP[gq*uhy^UyF̳VKEIydiAt^ +K4P*4C /'<90Fΐe53Jz2E`aOB7ZSqO9Y)KTw`2.i*jB}, vRySځTO_GoӒ{U +|G;TJ2cǘ8mtyȰϑYM1!Fָ]?Jgr!2"3|S ̡=QI{yC=n>Chy8M'IFSڴLZ>>tD+ɍ +"$0hnj@ +HjV&ڹn',׎>:]j?M4mRG۵":AK-@o%[uRT jjI[]6 P&R&;Q (ADJCs<]P_uQk8( {7¼n&񲸞k' X=C xZZqGKWoI / ScmʛCjD?n tި%/rQz L1 \GZ)І>X4n vwV0d}7ϊBf`BM b1< bx ^Y`Q;J](-2 z}ugU!CX /,Y '+S}Ճ=23Qh(?ԏ2+$ѴHcY,JpF +ChEB<*kDg1r +U6( 8hɫNt6>—!{U%ks#j 0<hwVە +vgÀ?yN旺Q'!" :@h`H +2)7}:YN"ء%x3$̤D$ug# TXq4s u,=WE>'t9YAO1Z$Myc5aNЯfJT<{JQM/OZ7+Ac~qW.!}|^ט"%H`@n)%q}@cq$BӭJ DC'^PQi9taSl9[PBRh!䔵9'*#>'->, '3q3GRb'>uk;,+:1QW>PN,s["n(Oα*@pTĉ3z9N3gٟ֩6lϯ8SІ.;FnѦyJ6dKvmCHaPB\Ǖ:8v$D`LԺlMm'!(ɆΨz:q;v&e8Sq=3N5j6@rAm Ɲ-Y]KِdkSmJi{K^'ѩ(ʅ(I$X*n*2<?#I[~xE64kIt& Zl {HƮ&矙֩.P,w ++3)/ʝbj7M)+:-ߞ 'Lb dVpL!KUFŶdH.c@9֠~cT˰JK&Ȁv)z`@?+"r˖I|3PM9LOm'7B@AɄxJ;X@ᮘG`2֢\RS4n1lRj`tZCnJGXNӦ"0ې]Z͛!qȮ >>\@U˛CEW;ɼkg&xl + ~ݺ[$$" >މzĕMTX9n07?j̷dK3Ξl&<^tT3/I'tԝY&t\a!7Yi0v9VƬNsVARC;E6ѶX)rC +`K@8y9ְʙeX#}woG扳wCEU.$.)뚨dN(ѳh*$N#S\H4y0:- ax]Zxal!" .BpbHAT"yGQlKM!,f gmgB*M裦-t%.^njx3aԫ~&9H*cxUdj,Xy3a%Piu ~#Tm.L)6VS\B-ZhVWwS-,p܉lИq6{y6xvQlc\| U \dے(Ĺe",=k-xs(VsK*+ڔ@l9$*d1"閶;X(Q\ Qu@|-{F;kDJ7[hܩ[{5[.Sr؜U,\kxHxRtAEZrP>]rDNH[ c17Ï,^|Cx/VB#oά ^o ݕl[yOb `(e+nM+ &([NswҬ^# V|h9glC|Яw`ҟ!59$ʒ /=R+ui"N)@,;y&`&d[IdoRo4@ +Ҕ/t7hL"u%JKϱsc;N8O_\29mֽ1f+p +i=G-! 4AH ƁPkUǩV A%Za5;HIl8d6]0%QRj8I#lNJS+E6+ǚ`_e&sA-,MI1QEU_gOːl>ގܯї!Ġ= lZE0p D"{[˒.>0~WI f-sI 1H?oD Cu(j@sAHN/Ԝ!z`5é< +amRXfmּ}߇QwաSpwMo;Jp_ڍ-CB,oWB +*"n!|Di +]dm=VԃLJ a( ?UM| ,.eU ת`%q|s:8EtJe%˕@R!Lc\\oՓ +K'J`l[#>RːH]NWL-*|9/۬p>n`'gfCNLT3j~a\RYVBbSSi=t9j5%ڕr+D6tGV- !ؤ}f-g8ARn)S"|VDY +FF\ >6V`̿peRN[ +9RkyvSW4-r }šH7%<9Vҡke6V uT:uEN)*Nr{$ѭ8$4h!`K.j|-jrwDOW$E RyS9噜%w(V޶)ȺAΑd S)ε0+LA M]\l/DڎӾKum'ZnPD,b.2μ>("L`:]7h ܐo/'O{le0-qLr~yq&\'_4QTI1Ѻ)p]MʃMQ#؎J40+hDŽ9(蹂qQRB:nTe46uUX ۏPrTр!Omq#W=*ώĭP*Mb#Ѵ>Et{wQegeO(doJ Ű:?q=% +*0ô/a(nol٨R^$hAP +zOR_jGq}0lvYʁޛ(Kĵ㢑~Jm;HXh`0"˭n[ҎW +ҀriˈV"_%bkeӇ⼩k:OnRJ DFCA2uV7l$li-঒˰> :l$OwW!՝[)MBTbWN[} ~8RaIn/0>sk& ѩ-n ΄ &έA~Hi~1Xg5! i'A6e8 +Ohoj*~g5lL$vRwYfq7`n\pSif+CJyByleL]KШjōd%*gBa0 Nf2݇ON1-_hfuxnaϩ2*bq"0|ѦgWɲ# j=ZUvOq:K>Qu_E9ZjB)U9I7I{)M J*&Z%- uj{VambmRČl?;FW0)⎘$AO*Jm|sqE: s2d8`{VgE+Ee*fFp7ɮsJ,#Q rJP{NB &Cm0ЈNa#Y՞^[7,.l%^% J% DTXۘ5 ="!0Uin T0W.$ R;euS^9/xvrKBwxc'As~%U$F ȕ(ٺ흉6{b*XlS ,TI&q ؀Gm&"p&PuS>%R9K?cIƷ2TSgcY6Z Y,P4}75O^1k4 +sW  +ٍ=-JbBu\Y&pCj FgD1`jUXJlmAZkߺj[fWl xIK\㡅TE7ցPk;jYg%PA, jXyk΀Sw7"~! +;C`QF@Odwm6j6+Q=>!k8W?#9^|l)ly1)8GvϤ=lObyO]+Iv +NHVj,7Z*<.4w.~"U(#ےьqܹ]8ȍ<5KjvC8g!"s6Ds@lp: ?Eoۼg MV{^㑔sdh2Cv7nf9"F:3m~p1 N9R#Ϛg!R+53 '2U% ' ӅR oD A kwcffNvP;HS=hAPj.۫7vlۖ84tQU{DT=X.*L1fG1VrTknB r> ܣ#` I`$Q=>k*S7 e6շG#S3NسuDEfo0i?z.nИj֌+ס[ a*)X~m +([ 0#n&3[Uze +j}+b99#4#iVɣx](]TNIbrC'Sc2J-딂p&3 + 9<%jqS_.% 00hH|sI|e;wѺB&:a* +ӼRb[3M[i\dp;A( +,8iaG^:{&:Y,t X*ͧcn. +jNq.1k7P>J_eÐA2#=ky5ZW!@w0TX:D_^wґ'p n nIjrwv=v[ҋ>BA{FT2p]VqsRV$۟~/w?||O_^??oӯy?߿iO?_ۏ?}{z?/߽/|ˇ?û/{gx>}v?wO]0ß}w\_>rF{;_ۧ_<%y->>\/O w/?__|k:|wֿ~[ߟOyw?û?=7|~|?~ӻ?]C/O~r]~7?z' endstream endobj 7 0 obj [6 0 R] endobj 28 0 obj <> endobj xref 0 29 0000000000 65535 f 0000000016 00000 n -0000000159 00000 n -0000053548 00000 n +0000000144 00000 n +0000053959 00000 n 0000000000 00000 f -0000000000 00000 f -0000212530 00000 n -0000000000 00000 f -0000053599 00000 n -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000212600 00000 n -0000212631 00000 n -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000000000 00000 f -0000097203 00000 n -0000212716 00000 n -0000053985 00000 n -0000100074 00000 n -0000097390 00000 n -0000097090 00000 n -0000060815 00000 n -0000059958 00000 n -0000060253 00000 n -0000060301 00000 n -0000097027 00000 n -0000097274 00000 n -0000097305 00000 n -0000097425 00000 n -0000100148 00000 n -0000100344 00000 n -0000101437 00000 n -0000111871 00000 n -0000177460 00000 n -0000212741 00000 n -trailer -<]>> -startxref -212920 -%%EOF +0000092071 00000 n +0000091885 00000 n +0000211933 00000 n +0000054010 00000 n +0000054430 00000 n +0000109010 00000 n +0000106327 00000 n +0000091772 00000 n +0000055498 00000 n +0000054632 00000 n +0000054937 00000 n +0000054985 00000 n +0000091709 00000 n +0000091955 00000 n +0000091986 00000 n +0000092314 00000 n +0000092571 00000 n +0000106362 00000 n +0000109084 00000 n +0000109280 00000 n +0000110282 00000 n +0000120517 00000 n +0000186105 00000 n +0000211956 00000 n +trailer <<299B326F1F494A60851087D01B26F9C5>]>> startxref 212135 %%EOF \ No newline at end of file From b1d0f9743e9de8a832d377fb7bd14729530cbe52 Mon Sep 17 00:00:00 2001 From: "ap-Codkelden@github.com" Date: Wed, 16 Apr 2014 18:11:35 +0300 Subject: [PATCH 084/545] SVG logo version (with fonts converted to curves) --- ext/requests-logo.svg | 487 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 487 insertions(+) create mode 100644 ext/requests-logo.svg diff --git a/ext/requests-logo.svg b/ext/requests-logo.svg new file mode 100644 index 00000000..bdcd2179 --- /dev/null +++ b/ext/requests-logo.svg @@ -0,0 +1,487 @@ + + + + + + + + + + +]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From e56cc117575ae31937f9fe19ebcf9d1a35eccae8 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 18 Apr 2014 10:35:44 -0500 Subject: [PATCH 085/545] Fix my name --- AUTHORS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 900fb4d1..268a0cba 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -10,7 +10,7 @@ Core Contributors ````````````````` - Cory Benfield `@lukasa `_ -- Ian Cordasco `@sigmavirus42 `_ +- Ian Cordasco `@sigmavirus24 `_ From 082b877d81d64ad81d2845933436e210b47a3e3e Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Fri, 18 Apr 2014 17:15:45 +0100 Subject: [PATCH 086/545] We're awesome, but don't need to be here twice. --- AUTHORS.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 268a0cba..ab82930b 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -87,7 +87,6 @@ Patches and Suggestions - David Kemp - Brendon Crawford - Denis (Telofy) -- Cory Benfield (Lukasa) - Matt Giuca - Adam Tauber - Honza Javorek @@ -118,7 +117,6 @@ Patches and Suggestions - Leila Muhtasib - Matthias Rahlf - Jakub Roztocil -- Ian Cordasco @sigmavirus24 - Rhys Elsmore - André Graf (dergraf) - Stephen Zhuang (everbird) From 6c1ddeedfbb12abfb6778620836a181f63387b4a Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Fri, 18 Apr 2014 17:28:12 +0100 Subject: [PATCH 087/545] First pass at update to philosophy section. --- docs/dev/philosophy.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/dev/philosophy.rst b/docs/dev/philosophy.rst index 2f8ca855..8c9490ab 100644 --- a/docs/dev/philosophy.rst +++ b/docs/dev/philosophy.rst @@ -4,10 +4,12 @@ Development Philosophy Requests is an open but opinionated library, created by an open but opinionated developer. -Benevolent Dictator -~~~~~~~~~~~~~~~~~~~ +Management Style +~~~~~~~~~~~~~~~~ -`Kenneth Reitz `_ is the BDFL. He has final say in any decision related to Requests. +`Kenneth Reitz `_ is the BDFL. He has final say in any decision related to the Requests project. Kenneth is responsible for the direction and form of the library. In addition to making decisions based on technical merit, he is responsible for making decisions based on the development philosophy of Requests. Only Kenneth may merge code into Requests. + +`Ian Cordasco `_ and `Cory Benfield `_ are the core contributors. They are responsible for triaging bug reports, reviewing pull requests and ensuring that Kenneth is kept up to speed with developments around the library. The day-to-day managing of the project is done by the core contributors. They are responsible for making judgements about whether or not a feature request is likely to be accepted by Kenneth. They do not have the authority to change code or merge code changes, though they may change documentation. Their word is not final. Values ~~~~~~ From 36b0481f24aa45a0229791630b467971dd58c92f Mon Sep 17 00:00:00 2001 From: apr Date: Thu, 24 Apr 2014 14:43:26 -0400 Subject: [PATCH 088/545] Update urllib to 1.8.2 --- requests/packages/urllib3/__init__.py | 2 +- requests/packages/urllib3/connection.py | 42 +- requests/packages/urllib3/connectionpool.py | 25 +- .../packages/urllib3/contrib/pyopenssl.py | 17 +- requests/packages/urllib3/util.py | 648 ------------------ requests/packages/urllib3/util/__init__.py | 27 + requests/packages/urllib3/util/connection.py | 45 ++ requests/packages/urllib3/util/request.py | 68 ++ requests/packages/urllib3/util/response.py | 13 + requests/packages/urllib3/util/ssl_.py | 133 ++++ requests/packages/urllib3/util/timeout.py | 234 +++++++ requests/packages/urllib3/util/url.py | 162 +++++ 12 files changed, 738 insertions(+), 678 deletions(-) delete mode 100644 requests/packages/urllib3/util.py create mode 100644 requests/packages/urllib3/util/__init__.py create mode 100644 requests/packages/urllib3/util/connection.py create mode 100644 requests/packages/urllib3/util/request.py create mode 100644 requests/packages/urllib3/util/response.py create mode 100644 requests/packages/urllib3/util/ssl_.py create mode 100644 requests/packages/urllib3/util/timeout.py create mode 100644 requests/packages/urllib3/util/url.py diff --git a/requests/packages/urllib3/__init__.py b/requests/packages/urllib3/__init__.py index 086387f3..bd237a66 100644 --- a/requests/packages/urllib3/__init__.py +++ b/requests/packages/urllib3/__init__.py @@ -10,7 +10,7 @@ urllib3 - Thread-safe connection pooling and re-using. __author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' __license__ = 'MIT' -__version__ = '1.8' +__version__ = '1.8.2' from .connectionpool import ( diff --git a/requests/packages/urllib3/connection.py b/requests/packages/urllib3/connection.py index 662bd2e4..de7b925e 100644 --- a/requests/packages/urllib3/connection.py +++ b/requests/packages/urllib3/connection.py @@ -68,15 +68,17 @@ class HTTPConnection(_HTTPConnection, object): def __init__(self, *args, **kw): if six.PY3: # Python 3 kw.pop('strict', None) - - if sys.version_info < (2, 7): # Python 2.6 and earlier + if sys.version_info < (2, 7): # Python 2.6 and older kw.pop('source_address', None) - self.source_address = None - _HTTPConnection.__init__(self, *args, **kw) + # Pre-set source_address in case we have an older Python like 2.6. + self.source_address = kw.get('source_address') + + # Superclass also sets self.source_address in Python 2.7+. + _HTTPConnection.__init__(self, *args, **kw) def _new_conn(self): - """ Establish a socket connection and set nodelay settings on it + """ Establish a socket connection and set nodelay settings on it. :return: a new socket connection """ @@ -85,12 +87,10 @@ class HTTPConnection(_HTTPConnection, object): extra_args.append(self.source_address) conn = socket.create_connection( - (self.host, self.port), - self.timeout, - *extra_args - ) - conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, - self.tcp_nodelay) + (self.host, self.port), self.timeout, *extra_args) + conn.setsockopt( + socket.IPPROTO_TCP, socket.TCP_NODELAY, self.tcp_nodelay) + return conn def _prepare_conn(self, conn): @@ -108,17 +108,18 @@ class HTTPSConnection(HTTPConnection): default_port = port_by_scheme['https'] def __init__(self, host, port=None, key_file=None, cert_file=None, - strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, - source_address=None): + strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, **kw): - HTTPConnection.__init__(self, host, port, - strict=strict, - timeout=timeout, - source_address=source_address) + HTTPConnection.__init__(self, host, port, strict=strict, + timeout=timeout, **kw) self.key_file = key_file self.cert_file = cert_file + # Required property for Google AppEngine 1.9.0 which otherwise causes + # HTTPS requests to go out as HTTP. (See Issue #356) + self._protocol = 'https' + def connect(self): conn = self._new_conn() self._prepare_conn(conn) @@ -133,6 +134,7 @@ class VerifiedHTTPSConnection(HTTPSConnection): cert_reqs = None ca_certs = None ssl_version = None + conn_kw = {} def set_cert(self, key_file=None, cert_file=None, cert_reqs=None, ca_certs=None, @@ -147,11 +149,11 @@ class VerifiedHTTPSConnection(HTTPSConnection): def connect(self): # Add certificate verification + try: sock = socket.create_connection( - address=(self.host, self.port), - timeout=self.timeout, - ) + address=(self.host, self.port), timeout=self.timeout, + **self.conn_kw) except SocketTimeout: raise ConnectTimeoutError( self, "Connection to %s timed out. (connect timeout=%s)" % diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index 6d0dbb18..95a53a7d 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -4,6 +4,7 @@ # This module is part of urllib3 and is released under # the MIT License: http://www.opensource.org/licenses/mit-license.php +import sys import errno import logging @@ -23,6 +24,7 @@ from .exceptions import ( ConnectTimeoutError, EmptyPoolError, HostChangedError, + LocationParseError, MaxRetryError, SSLError, TimeoutError, @@ -40,7 +42,6 @@ from .connection import ( from .request import RequestMethods from .response import HTTPResponse from .util import ( - assert_fingerprint, get_host, is_connection_dropped, Timeout, @@ -65,6 +66,9 @@ class ConnectionPool(object): QueueCls = LifoQueue def __init__(self, host, port=None): + if host is None: + raise LocationParseError(host) + # httplib doesn't like it when we include brackets in ipv6 addresses host = host.strip('[]') @@ -136,7 +140,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): def __init__(self, host, port=None, strict=False, timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, block=False, - headers=None, _proxy=None, _proxy_headers=None): + headers=None, _proxy=None, _proxy_headers=None, **conn_kw): ConnectionPool.__init__(self, host, port) RequestMethods.__init__(self, headers) @@ -163,6 +167,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): self.num_connections = 0 self.num_requests = 0 + if sys.version_info < (2, 7): # Python 2.6 and older + conn_kw.pop('source_address', None) + self.conn_kw = conn_kw + def _new_conn(self): """ Return a fresh :class:`HTTPConnection`. @@ -173,7 +181,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): conn = self.ConnectionCls(host=self.host, port=self.port, timeout=self.timeout.connect_timeout, - strict=self.strict) + strict=self.strict, **self.conn_kw) if self.proxy is not None: # Enable Nagle's algorithm for proxies, to avoid packet # fragmentation. @@ -594,10 +602,14 @@ class HTTPSConnectionPool(HTTPConnectionPool): _proxy=None, _proxy_headers=None, key_file=None, cert_file=None, cert_reqs=None, ca_certs=None, ssl_version=None, - assert_hostname=None, assert_fingerprint=None): + assert_hostname=None, assert_fingerprint=None, + **conn_kw): + + if sys.version_info < (2, 7): # Python 2.6 or older + conn_kw.pop('source_address', None) HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize, - block, headers, _proxy, _proxy_headers) + block, headers, _proxy, _proxy_headers, **conn_kw) self.key_file = key_file self.cert_file = cert_file self.cert_reqs = cert_reqs @@ -605,6 +617,7 @@ class HTTPSConnectionPool(HTTPConnectionPool): self.ssl_version = ssl_version self.assert_hostname = assert_hostname self.assert_fingerprint = assert_fingerprint + self.conn_kw = conn_kw def _prepare_conn(self, conn): """ @@ -620,6 +633,7 @@ class HTTPSConnectionPool(HTTPConnectionPool): assert_hostname=self.assert_hostname, assert_fingerprint=self.assert_fingerprint) conn.ssl_version = self.ssl_version + conn.conn_kw = self.conn_kw if self.proxy is not None: # Python 2.7+ @@ -656,6 +670,7 @@ class HTTPSConnectionPool(HTTPConnectionPool): extra_params = {} if not six.PY3: # Python 2 extra_params['strict'] = self.strict + extra_params.update(self.conn_kw) conn = self.ConnectionCls(host=actual_host, port=actual_port, timeout=self.timeout.connect_timeout, diff --git a/requests/packages/urllib3/contrib/pyopenssl.py b/requests/packages/urllib3/contrib/pyopenssl.py index 7c513f3a..21a12c68 100644 --- a/requests/packages/urllib3/contrib/pyopenssl.py +++ b/requests/packages/urllib3/contrib/pyopenssl.py @@ -1,4 +1,7 @@ -'''SSL with SNI_-support for Python 2. +'''SSL with SNI_-support for Python 2. Follow these instructions if you would +like to verify SSL certificates in Python 2. Note, the default libraries do +*not* do certificate checking; you need to do additional work to validate +certificates yourself. This needs the following packages installed: @@ -6,9 +9,15 @@ This needs the following packages installed: * ndg-httpsclient (tested with 0.3.2) * pyasn1 (tested with 0.1.6) -To activate it call :func:`~urllib3.contrib.pyopenssl.inject_into_urllib3`. -This can be done in a ``sitecustomize`` module, or at any other time before -your application begins using ``urllib3``, like this:: +You can install them with the following command: + + pip install pyopenssl ndg-httpsclient pyasn1 + +To activate certificate checking, call +:func:`~urllib3.contrib.pyopenssl.inject_into_urllib3` from your Python code +before you begin making HTTP requests. This can be done in a ``sitecustomize`` +module, or at any other time before your application begins using ``urllib3``, +like this:: try: import urllib3.contrib.pyopenssl diff --git a/requests/packages/urllib3/util.py b/requests/packages/urllib3/util.py deleted file mode 100644 index bd266317..00000000 --- a/requests/packages/urllib3/util.py +++ /dev/null @@ -1,648 +0,0 @@ -# urllib3/util.py -# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt) -# -# This module is part of urllib3 and is released under -# the MIT License: http://www.opensource.org/licenses/mit-license.php - - -from base64 import b64encode -from binascii import hexlify, unhexlify -from collections import namedtuple -from hashlib import md5, sha1 -from socket import error as SocketError, _GLOBAL_DEFAULT_TIMEOUT -import time - -try: - from select import poll, POLLIN -except ImportError: # `poll` doesn't exist on OSX and other platforms - poll = False - try: - from select import select - except ImportError: # `select` doesn't exist on AppEngine. - select = False - -try: # Test for SSL features - SSLContext = None - HAS_SNI = False - - import ssl - from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 - from ssl import SSLContext # Modern SSL? - from ssl import HAS_SNI # Has SNI? -except ImportError: - pass - -from .packages import six -from .exceptions import LocationParseError, SSLError, TimeoutStateError - - -_Default = object() -# The default timeout to use for socket connections. This is the attribute used -# by httplib to define the default timeout - - -def current_time(): - """ - Retrieve the current time, this function is mocked out in unit testing. - """ - return time.time() - - -class Timeout(object): - """ - Utility object for storing timeout values. - - Example usage: - - .. code-block:: python - - timeout = urllib3.util.Timeout(connect=2.0, read=7.0) - pool = HTTPConnectionPool('www.google.com', 80, timeout=timeout) - pool.request(...) # Etc, etc - - :param connect: - The maximum amount of time to wait for a connection attempt to a server - to succeed. Omitting the parameter will default the connect timeout to - the system default, probably `the global default timeout in socket.py - `_. - None will set an infinite timeout for connection attempts. - - :type connect: integer, float, or None - - :param read: - The maximum amount of time to wait between consecutive - read operations for a response from the server. Omitting - the parameter will default the read timeout to the system - default, probably `the global default timeout in socket.py - `_. - None will set an infinite timeout. - - :type read: integer, float, or None - - :param total: - This combines the connect and read timeouts into one; the read timeout - will be set to the time leftover from the connect attempt. In the - event that both a connect timeout and a total are specified, or a read - timeout and a total are specified, the shorter timeout will be applied. - - Defaults to None. - - :type total: integer, float, or None - - .. note:: - - Many factors can affect the total amount of time for urllib3 to return - an HTTP response. Specifically, Python's DNS resolver does not obey the - timeout specified on the socket. Other factors that can affect total - request time include high CPU load, high swap, the program running at a - low priority level, or other behaviors. The observed running time for - urllib3 to return a response may be greater than the value passed to - `total`. - - In addition, the read and total timeouts only measure the time between - read operations on the socket connecting the client and the server, - not the total amount of time for the request to return a complete - response. For most requests, the timeout is raised because the server - has not sent the first byte in the specified time. This is not always - the case; if a server streams one byte every fifteen seconds, a timeout - of 20 seconds will not ever trigger, even though the request will - take several minutes to complete. - - If your goal is to cut off any request after a set amount of wall clock - time, consider having a second "watcher" thread to cut off a slow - request. - """ - - #: A sentinel object representing the default timeout value - DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT - - def __init__(self, total=None, connect=_Default, read=_Default): - self._connect = self._validate_timeout(connect, 'connect') - self._read = self._validate_timeout(read, 'read') - self.total = self._validate_timeout(total, 'total') - self._start_connect = None - - def __str__(self): - return '%s(connect=%r, read=%r, total=%r)' % ( - type(self).__name__, self._connect, self._read, self.total) - - - @classmethod - def _validate_timeout(cls, value, name): - """ Check that a timeout attribute is valid - - :param value: The timeout value to validate - :param name: The name of the timeout attribute to validate. This is used - for clear error messages - :return: the value - :raises ValueError: if the type is not an integer or a float, or if it - is a numeric value less than zero - """ - if value is _Default: - return cls.DEFAULT_TIMEOUT - - if value is None or value is cls.DEFAULT_TIMEOUT: - return value - - try: - float(value) - except (TypeError, ValueError): - raise ValueError("Timeout value %s was %s, but it must be an " - "int or float." % (name, value)) - - try: - if value < 0: - raise ValueError("Attempted to set %s timeout to %s, but the " - "timeout cannot be set to a value less " - "than 0." % (name, value)) - except TypeError: # Python 3 - raise ValueError("Timeout value %s was %s, but it must be an " - "int or float." % (name, value)) - - return value - - @classmethod - def from_float(cls, timeout): - """ Create a new Timeout from a legacy timeout value. - - The timeout value used by httplib.py sets the same timeout on the - connect(), and recv() socket requests. This creates a :class:`Timeout` - object that sets the individual timeouts to the ``timeout`` value passed - to this function. - - :param timeout: The legacy timeout value - :type timeout: integer, float, sentinel default object, or None - :return: a Timeout object - :rtype: :class:`Timeout` - """ - return Timeout(read=timeout, connect=timeout) - - def clone(self): - """ Create a copy of the timeout object - - Timeout properties are stored per-pool but each request needs a fresh - Timeout object to ensure each one has its own start/stop configured. - - :return: a copy of the timeout object - :rtype: :class:`Timeout` - """ - # We can't use copy.deepcopy because that will also create a new object - # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to - # detect the user default. - return Timeout(connect=self._connect, read=self._read, - total=self.total) - - def start_connect(self): - """ Start the timeout clock, used during a connect() attempt - - :raises urllib3.exceptions.TimeoutStateError: if you attempt - to start a timer that has been started already. - """ - if self._start_connect is not None: - raise TimeoutStateError("Timeout timer has already been started.") - self._start_connect = current_time() - return self._start_connect - - def get_connect_duration(self): - """ Gets the time elapsed since the call to :meth:`start_connect`. - - :return: the elapsed time - :rtype: float - :raises urllib3.exceptions.TimeoutStateError: if you attempt - to get duration for a timer that hasn't been started. - """ - if self._start_connect is None: - raise TimeoutStateError("Can't get connect duration for timer " - "that has not started.") - return current_time() - self._start_connect - - @property - def connect_timeout(self): - """ Get the value to use when setting a connection timeout. - - This will be a positive float or integer, the value None - (never timeout), or the default system timeout. - - :return: the connect timeout - :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None - """ - if self.total is None: - return self._connect - - if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: - return self.total - - return min(self._connect, self.total) - - @property - def read_timeout(self): - """ Get the value for the read timeout. - - This assumes some time has elapsed in the connection timeout and - computes the read timeout appropriately. - - If self.total is set, the read timeout is dependent on the amount of - time taken by the connect timeout. If the connection time has not been - established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be - raised. - - :return: the value to use for the read timeout - :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None - :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` - has not yet been called on this object. - """ - if (self.total is not None and - self.total is not self.DEFAULT_TIMEOUT and - self._read is not None and - self._read is not self.DEFAULT_TIMEOUT): - # in case the connect timeout has not yet been established. - if self._start_connect is None: - return self._read - return max(0, min(self.total - self.get_connect_duration(), - self._read)) - elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: - return max(0, self.total - self.get_connect_duration()) - else: - return self._read - - -class Url(namedtuple('Url', ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'])): - """ - Datastructure for representing an HTTP URL. Used as a return value for - :func:`parse_url`. - """ - slots = () - - def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None, query=None, fragment=None): - return super(Url, cls).__new__(cls, scheme, auth, host, port, path, query, fragment) - - @property - def hostname(self): - """For backwards-compatibility with urlparse. We're nice like that.""" - return self.host - - @property - def request_uri(self): - """Absolute path including the query string.""" - uri = self.path or '/' - - if self.query is not None: - uri += '?' + self.query - - return uri - - @property - def netloc(self): - """Network location including host and port""" - if self.port: - return '%s:%d' % (self.host, self.port) - return self.host - - -def split_first(s, delims): - """ - Given a string and an iterable of delimiters, split on the first found - delimiter. Return two split parts and the matched delimiter. - - If not found, then the first part is the full input string. - - Example: :: - - >>> split_first('foo/bar?baz', '?/=') - ('foo', 'bar?baz', '/') - >>> split_first('foo/bar?baz', '123') - ('foo/bar?baz', '', None) - - Scales linearly with number of delims. Not ideal for large number of delims. - """ - min_idx = None - min_delim = None - for d in delims: - idx = s.find(d) - if idx < 0: - continue - - if min_idx is None or idx < min_idx: - min_idx = idx - min_delim = d - - if min_idx is None or min_idx < 0: - return s, '', None - - return s[:min_idx], s[min_idx+1:], min_delim - - -def parse_url(url): - """ - Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is - performed to parse incomplete urls. Fields not provided will be None. - - Partly backwards-compatible with :mod:`urlparse`. - - Example: :: - - >>> parse_url('http://google.com/mail/') - Url(scheme='http', host='google.com', port=None, path='/', ...) - >>> parse_url('google.com:80') - Url(scheme=None, host='google.com', port=80, path=None, ...) - >>> parse_url('/foo?bar') - Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) - """ - - # While this code has overlap with stdlib's urlparse, it is much - # simplified for our needs and less annoying. - # Additionally, this implementations does silly things to be optimal - # on CPython. - - scheme = None - auth = None - host = None - port = None - path = None - fragment = None - query = None - - # Scheme - if '://' in url: - scheme, url = url.split('://', 1) - - # Find the earliest Authority Terminator - # (http://tools.ietf.org/html/rfc3986#section-3.2) - url, path_, delim = split_first(url, ['/', '?', '#']) - - if delim: - # Reassemble the path - path = delim + path_ - - # Auth - if '@' in url: - # Last '@' denotes end of auth part - auth, url = url.rsplit('@', 1) - - # IPv6 - if url and url[0] == '[': - host, url = url.split(']', 1) - host += ']' - - # Port - if ':' in url: - _host, port = url.split(':', 1) - - if not host: - host = _host - - if port: - # If given, ports must be integers. - if not port.isdigit(): - raise LocationParseError("Failed to parse: %s" % url) - port = int(port) - else: - # Blank ports are cool, too. (rfc3986#section-3.2.3) - port = None - - elif not host and url: - host = url - - if not path: - return Url(scheme, auth, host, port, path, query, fragment) - - # Fragment - if '#' in path: - path, fragment = path.split('#', 1) - - # Query - if '?' in path: - path, query = path.split('?', 1) - - return Url(scheme, auth, host, port, path, query, fragment) - - -def get_host(url): - """ - Deprecated. Use :func:`.parse_url` instead. - """ - p = parse_url(url) - return p.scheme or 'http', p.hostname, p.port - - -def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, - basic_auth=None, proxy_basic_auth=None): - """ - Shortcuts for generating request headers. - - :param keep_alive: - If ``True``, adds 'connection: keep-alive' header. - - :param accept_encoding: - Can be a boolean, list, or string. - ``True`` translates to 'gzip,deflate'. - List will get joined by comma. - String will be used as provided. - - :param user_agent: - String representing the user-agent you want, such as - "python-urllib3/0.6" - - :param basic_auth: - Colon-separated username:password string for 'authorization: basic ...' - auth header. - - :param proxy_basic_auth: - Colon-separated username:password string for 'proxy-authorization: basic ...' - auth header. - - Example: :: - - >>> make_headers(keep_alive=True, user_agent="Batman/1.0") - {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} - >>> make_headers(accept_encoding=True) - {'accept-encoding': 'gzip,deflate'} - """ - headers = {} - if accept_encoding: - if isinstance(accept_encoding, str): - pass - elif isinstance(accept_encoding, list): - accept_encoding = ','.join(accept_encoding) - else: - accept_encoding = 'gzip,deflate' - headers['accept-encoding'] = accept_encoding - - if user_agent: - headers['user-agent'] = user_agent - - if keep_alive: - headers['connection'] = 'keep-alive' - - if basic_auth: - headers['authorization'] = 'Basic ' + \ - b64encode(six.b(basic_auth)).decode('utf-8') - - if proxy_basic_auth: - headers['proxy-authorization'] = 'Basic ' + \ - b64encode(six.b(proxy_basic_auth)).decode('utf-8') - - return headers - - -def is_connection_dropped(conn): # Platform-specific - """ - Returns True if the connection is dropped and should be closed. - - :param conn: - :class:`httplib.HTTPConnection` object. - - Note: For platforms like AppEngine, this will always return ``False`` to - let the platform handle connection recycling transparently for us. - """ - sock = getattr(conn, 'sock', False) - if not sock: # Platform-specific: AppEngine - return False - - if not poll: - if not select: # Platform-specific: AppEngine - return False - - try: - return select([sock], [], [], 0.0)[0] - except SocketError: - return True - - # This version is better on platforms that support it. - p = poll() - p.register(sock, POLLIN) - for (fno, ev) in p.poll(0.0): - if fno == sock.fileno(): - # Either data is buffered (bad), or the connection is dropped. - return True - - -def resolve_cert_reqs(candidate): - """ - Resolves the argument to a numeric constant, which can be passed to - the wrap_socket function/method from the ssl module. - Defaults to :data:`ssl.CERT_NONE`. - If given a string it is assumed to be the name of the constant in the - :mod:`ssl` module or its abbrevation. - (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. - If it's neither `None` nor a string we assume it is already the numeric - constant which can directly be passed to wrap_socket. - """ - if candidate is None: - return CERT_NONE - - if isinstance(candidate, str): - res = getattr(ssl, candidate, None) - if res is None: - res = getattr(ssl, 'CERT_' + candidate) - return res - - return candidate - - -def resolve_ssl_version(candidate): - """ - like resolve_cert_reqs - """ - if candidate is None: - return PROTOCOL_SSLv23 - - if isinstance(candidate, str): - res = getattr(ssl, candidate, None) - if res is None: - res = getattr(ssl, 'PROTOCOL_' + candidate) - return res - - return candidate - - -def assert_fingerprint(cert, fingerprint): - """ - Checks if given fingerprint matches the supplied certificate. - - :param cert: - Certificate as bytes object. - :param fingerprint: - 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() - - digest_length, rest = divmod(len(fingerprint), 2) - - if rest or digest_length not in hashfunc_map: - raise SSLError('Fingerprint is of invalid length.') - - # We need encode() here for py32; works on py2 and p33. - fingerprint_bytes = unhexlify(fingerprint.encode()) - - hashfunc = hashfunc_map[digest_length] - - cert_digest = hashfunc(cert).digest() - - if not cert_digest == fingerprint_bytes: - raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".' - .format(hexlify(fingerprint_bytes), - hexlify(cert_digest))) - -def is_fp_closed(obj): - """ - Checks whether a given file-like object is closed. - - :param obj: - The file-like object to check. - """ - if hasattr(obj, 'fp'): - # Object is a container for another file-like object that gets released - # on exhaustion (e.g. HTTPResponse) - return obj.fp is None - - return obj.closed - - -if SSLContext is not None: # Python 3.2+ - def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, - ca_certs=None, server_hostname=None, - ssl_version=None): - """ - All arguments except `server_hostname` have the same meaning as for - :func:`ssl.wrap_socket` - - :param server_hostname: - Hostname of the expected certificate - """ - context = SSLContext(ssl_version) - context.verify_mode = cert_reqs - - # Disable TLS compression to migitate CRIME attack (issue #309) - OP_NO_COMPRESSION = 0x20000 - context.options |= OP_NO_COMPRESSION - - if ca_certs: - try: - context.load_verify_locations(ca_certs) - # Py32 raises IOError - # Py33 raises FileNotFoundError - except Exception as e: # Reraise as SSLError - raise SSLError(e) - if certfile: - # FIXME: This block needs a test. - context.load_cert_chain(certfile, keyfile) - if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI - return context.wrap_socket(sock, server_hostname=server_hostname) - return context.wrap_socket(sock) - -else: # Python 3.1 and earlier - def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, - ca_certs=None, server_hostname=None, - ssl_version=None): - return wrap_socket(sock, keyfile=keyfile, certfile=certfile, - ca_certs=ca_certs, cert_reqs=cert_reqs, - ssl_version=ssl_version) diff --git a/requests/packages/urllib3/util/__init__.py b/requests/packages/urllib3/util/__init__.py new file mode 100644 index 00000000..a40185ee --- /dev/null +++ b/requests/packages/urllib3/util/__init__.py @@ -0,0 +1,27 @@ +# urllib3/util/__init__.py +# Copyright 2008-2014 Andrey Petrov and contributors (see CONTRIBUTORS.txt) +# +# This module is part of urllib3 and is released under +# the MIT License: http://www.opensource.org/licenses/mit-license.php + +from .connection import is_connection_dropped +from .request import make_headers +from .response import is_fp_closed +from .ssl_ import ( + SSLContext, + HAS_SNI, + assert_fingerprint, + resolve_cert_reqs, + resolve_ssl_version, + ssl_wrap_socket, +) +from .timeout import ( + current_time, + Timeout, +) +from .url import ( + get_host, + parse_url, + split_first, + Url, +) diff --git a/requests/packages/urllib3/util/connection.py b/requests/packages/urllib3/util/connection.py new file mode 100644 index 00000000..8deeab5c --- /dev/null +++ b/requests/packages/urllib3/util/connection.py @@ -0,0 +1,45 @@ +from socket import error as SocketError +try: + from select import poll, POLLIN +except ImportError: # `poll` doesn't exist on OSX and other platforms + poll = False + try: + from select import select + except ImportError: # `select` doesn't exist on AppEngine. + select = False + +def is_connection_dropped(conn): # Platform-specific + """ + Returns True if the connection is dropped and should be closed. + + :param conn: + :class:`httplib.HTTPConnection` object. + + Note: For platforms like AppEngine, this will always return ``False`` to + let the platform handle connection recycling transparently for us. + """ + sock = getattr(conn, 'sock', False) + if sock is False: # Platform-specific: AppEngine + return False + if sock is None: # Connection already closed (such as by httplib). + return False + + if not poll: + if not select: # Platform-specific: AppEngine + return False + + try: + return select([sock], [], [], 0.0)[0] + except SocketError: + return True + + # This version is better on platforms that support it. + p = poll() + p.register(sock, POLLIN) + for (fno, ev) in p.poll(0.0): + if fno == sock.fileno(): + # Either data is buffered (bad), or the connection is dropped. + return True + + + diff --git a/requests/packages/urllib3/util/request.py b/requests/packages/urllib3/util/request.py new file mode 100644 index 00000000..d48d6513 --- /dev/null +++ b/requests/packages/urllib3/util/request.py @@ -0,0 +1,68 @@ +from base64 import b64encode + +from ..packages import six + + +ACCEPT_ENCODING = 'gzip,deflate' + + +def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, + basic_auth=None, proxy_basic_auth=None): + """ + Shortcuts for generating request headers. + + :param keep_alive: + If ``True``, adds 'connection: keep-alive' header. + + :param accept_encoding: + Can be a boolean, list, or string. + ``True`` translates to 'gzip,deflate'. + List will get joined by comma. + String will be used as provided. + + :param user_agent: + String representing the user-agent you want, such as + "python-urllib3/0.6" + + :param basic_auth: + Colon-separated username:password string for 'authorization: basic ...' + auth header. + + :param proxy_basic_auth: + Colon-separated username:password string for 'proxy-authorization: basic ...' + auth header. + + Example: :: + + >>> make_headers(keep_alive=True, user_agent="Batman/1.0") + {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} + >>> make_headers(accept_encoding=True) + {'accept-encoding': 'gzip,deflate'} + """ + headers = {} + if accept_encoding: + if isinstance(accept_encoding, str): + pass + elif isinstance(accept_encoding, list): + accept_encoding = ','.join(accept_encoding) + else: + accept_encoding = ACCEPT_ENCODING + headers['accept-encoding'] = accept_encoding + + if user_agent: + headers['user-agent'] = user_agent + + if keep_alive: + headers['connection'] = 'keep-alive' + + if basic_auth: + headers['authorization'] = 'Basic ' + \ + b64encode(six.b(basic_auth)).decode('utf-8') + + if proxy_basic_auth: + headers['proxy-authorization'] = 'Basic ' + \ + b64encode(six.b(proxy_basic_auth)).decode('utf-8') + + return headers + + diff --git a/requests/packages/urllib3/util/response.py b/requests/packages/urllib3/util/response.py new file mode 100644 index 00000000..d0325bc6 --- /dev/null +++ b/requests/packages/urllib3/util/response.py @@ -0,0 +1,13 @@ +def is_fp_closed(obj): + """ + Checks whether a given file-like object is closed. + + :param obj: + The file-like object to check. + """ + if hasattr(obj, 'fp'): + # Object is a container for another file-like object that gets released + # on exhaustion (e.g. HTTPResponse) + return obj.fp is None + + return obj.closed diff --git a/requests/packages/urllib3/util/ssl_.py b/requests/packages/urllib3/util/ssl_.py new file mode 100644 index 00000000..dee4b876 --- /dev/null +++ b/requests/packages/urllib3/util/ssl_.py @@ -0,0 +1,133 @@ +from binascii import hexlify, unhexlify +from hashlib import md5, sha1 + +from ..exceptions import SSLError + + +try: # Test for SSL features + SSLContext = None + HAS_SNI = False + + import ssl + from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 + from ssl import SSLContext # Modern SSL? + from ssl import HAS_SNI # Has SNI? +except ImportError: + pass + + +def assert_fingerprint(cert, fingerprint): + """ + Checks if given fingerprint matches the supplied certificate. + + :param cert: + Certificate as bytes object. + :param fingerprint: + 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() + + digest_length, rest = divmod(len(fingerprint), 2) + + if rest or digest_length not in hashfunc_map: + raise SSLError('Fingerprint is of invalid length.') + + # We need encode() here for py32; works on py2 and p33. + fingerprint_bytes = unhexlify(fingerprint.encode()) + + hashfunc = hashfunc_map[digest_length] + + cert_digest = hashfunc(cert).digest() + + if not cert_digest == fingerprint_bytes: + raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".' + .format(hexlify(fingerprint_bytes), + hexlify(cert_digest))) + + +def resolve_cert_reqs(candidate): + """ + Resolves the argument to a numeric constant, which can be passed to + the wrap_socket function/method from the ssl module. + Defaults to :data:`ssl.CERT_NONE`. + If given a string it is assumed to be the name of the constant in the + :mod:`ssl` module or its abbrevation. + (So you can specify `REQUIRED` instead of `CERT_REQUIRED`. + If it's neither `None` nor a string we assume it is already the numeric + constant which can directly be passed to wrap_socket. + """ + if candidate is None: + return CERT_NONE + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, 'CERT_' + candidate) + return res + + return candidate + + +def resolve_ssl_version(candidate): + """ + like resolve_cert_reqs + """ + if candidate is None: + return PROTOCOL_SSLv23 + + if isinstance(candidate, str): + res = getattr(ssl, candidate, None) + if res is None: + res = getattr(ssl, 'PROTOCOL_' + candidate) + return res + + return candidate + + +if SSLContext is not None: # Python 3.2+ + def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, + ca_certs=None, server_hostname=None, + ssl_version=None): + """ + All arguments except `server_hostname` have the same meaning as for + :func:`ssl.wrap_socket` + + :param server_hostname: + Hostname of the expected certificate + """ + context = SSLContext(ssl_version) + context.verify_mode = cert_reqs + + # Disable TLS compression to migitate CRIME attack (issue #309) + OP_NO_COMPRESSION = 0x20000 + context.options |= OP_NO_COMPRESSION + + if ca_certs: + try: + context.load_verify_locations(ca_certs) + # Py32 raises IOError + # Py33 raises FileNotFoundError + except Exception as e: # Reraise as SSLError + raise SSLError(e) + if certfile: + # FIXME: This block needs a test. + context.load_cert_chain(certfile, keyfile) + if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI + return context.wrap_socket(sock, server_hostname=server_hostname) + return context.wrap_socket(sock) + +else: # Python 3.1 and earlier + def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, + ca_certs=None, server_hostname=None, + ssl_version=None): + return wrap_socket(sock, keyfile=keyfile, certfile=certfile, + ca_certs=ca_certs, cert_reqs=cert_reqs, + ssl_version=ssl_version) diff --git a/requests/packages/urllib3/util/timeout.py b/requests/packages/urllib3/util/timeout.py new file mode 100644 index 00000000..4f947cb2 --- /dev/null +++ b/requests/packages/urllib3/util/timeout.py @@ -0,0 +1,234 @@ +from socket import _GLOBAL_DEFAULT_TIMEOUT +import time + +from ..exceptions import TimeoutStateError + + +def current_time(): + """ + Retrieve the current time, this function is mocked out in unit testing. + """ + return time.time() + + +_Default = object() +# The default timeout to use for socket connections. This is the attribute used +# by httplib to define the default timeout + + +class Timeout(object): + """ + Utility object for storing timeout values. + + Example usage: + + .. code-block:: python + + timeout = urllib3.util.Timeout(connect=2.0, read=7.0) + pool = HTTPConnectionPool('www.google.com', 80, timeout=timeout) + pool.request(...) # Etc, etc + + :param connect: + The maximum amount of time to wait for a connection attempt to a server + to succeed. Omitting the parameter will default the connect timeout to + the system default, probably `the global default timeout in socket.py + `_. + None will set an infinite timeout for connection attempts. + + :type connect: integer, float, or None + + :param read: + The maximum amount of time to wait between consecutive + read operations for a response from the server. Omitting + the parameter will default the read timeout to the system + default, probably `the global default timeout in socket.py + `_. + None will set an infinite timeout. + + :type read: integer, float, or None + + :param total: + This combines the connect and read timeouts into one; the read timeout + will be set to the time leftover from the connect attempt. In the + event that both a connect timeout and a total are specified, or a read + timeout and a total are specified, the shorter timeout will be applied. + + Defaults to None. + + :type total: integer, float, or None + + .. note:: + + Many factors can affect the total amount of time for urllib3 to return + an HTTP response. Specifically, Python's DNS resolver does not obey the + timeout specified on the socket. Other factors that can affect total + request time include high CPU load, high swap, the program running at a + low priority level, or other behaviors. The observed running time for + urllib3 to return a response may be greater than the value passed to + `total`. + + In addition, the read and total timeouts only measure the time between + read operations on the socket connecting the client and the server, + not the total amount of time for the request to return a complete + response. For most requests, the timeout is raised because the server + has not sent the first byte in the specified time. This is not always + the case; if a server streams one byte every fifteen seconds, a timeout + of 20 seconds will not ever trigger, even though the request will + take several minutes to complete. + + If your goal is to cut off any request after a set amount of wall clock + time, consider having a second "watcher" thread to cut off a slow + request. + """ + + #: A sentinel object representing the default timeout value + DEFAULT_TIMEOUT = _GLOBAL_DEFAULT_TIMEOUT + + def __init__(self, total=None, connect=_Default, read=_Default): + self._connect = self._validate_timeout(connect, 'connect') + self._read = self._validate_timeout(read, 'read') + self.total = self._validate_timeout(total, 'total') + self._start_connect = None + + def __str__(self): + return '%s(connect=%r, read=%r, total=%r)' % ( + type(self).__name__, self._connect, self._read, self.total) + + + @classmethod + def _validate_timeout(cls, value, name): + """ Check that a timeout attribute is valid + + :param value: The timeout value to validate + :param name: The name of the timeout attribute to validate. This is used + for clear error messages + :return: the value + :raises ValueError: if the type is not an integer or a float, or if it + is a numeric value less than zero + """ + if value is _Default: + return cls.DEFAULT_TIMEOUT + + if value is None or value is cls.DEFAULT_TIMEOUT: + return value + + try: + float(value) + except (TypeError, ValueError): + raise ValueError("Timeout value %s was %s, but it must be an " + "int or float." % (name, value)) + + try: + if value < 0: + raise ValueError("Attempted to set %s timeout to %s, but the " + "timeout cannot be set to a value less " + "than 0." % (name, value)) + except TypeError: # Python 3 + raise ValueError("Timeout value %s was %s, but it must be an " + "int or float." % (name, value)) + + return value + + @classmethod + def from_float(cls, timeout): + """ Create a new Timeout from a legacy timeout value. + + The timeout value used by httplib.py sets the same timeout on the + connect(), and recv() socket requests. This creates a :class:`Timeout` + object that sets the individual timeouts to the ``timeout`` value passed + to this function. + + :param timeout: The legacy timeout value + :type timeout: integer, float, sentinel default object, or None + :return: a Timeout object + :rtype: :class:`Timeout` + """ + return Timeout(read=timeout, connect=timeout) + + def clone(self): + """ Create a copy of the timeout object + + Timeout properties are stored per-pool but each request needs a fresh + Timeout object to ensure each one has its own start/stop configured. + + :return: a copy of the timeout object + :rtype: :class:`Timeout` + """ + # We can't use copy.deepcopy because that will also create a new object + # for _GLOBAL_DEFAULT_TIMEOUT, which socket.py uses as a sentinel to + # detect the user default. + return Timeout(connect=self._connect, read=self._read, + total=self.total) + + def start_connect(self): + """ Start the timeout clock, used during a connect() attempt + + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to start a timer that has been started already. + """ + if self._start_connect is not None: + raise TimeoutStateError("Timeout timer has already been started.") + self._start_connect = current_time() + return self._start_connect + + def get_connect_duration(self): + """ Gets the time elapsed since the call to :meth:`start_connect`. + + :return: the elapsed time + :rtype: float + :raises urllib3.exceptions.TimeoutStateError: if you attempt + to get duration for a timer that hasn't been started. + """ + if self._start_connect is None: + raise TimeoutStateError("Can't get connect duration for timer " + "that has not started.") + return current_time() - self._start_connect + + @property + def connect_timeout(self): + """ Get the value to use when setting a connection timeout. + + This will be a positive float or integer, the value None + (never timeout), or the default system timeout. + + :return: the connect timeout + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + """ + if self.total is None: + return self._connect + + if self._connect is None or self._connect is self.DEFAULT_TIMEOUT: + return self.total + + return min(self._connect, self.total) + + @property + def read_timeout(self): + """ Get the value for the read timeout. + + This assumes some time has elapsed in the connection timeout and + computes the read timeout appropriately. + + If self.total is set, the read timeout is dependent on the amount of + time taken by the connect timeout. If the connection time has not been + established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be + raised. + + :return: the value to use for the read timeout + :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None + :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` + has not yet been called on this object. + """ + if (self.total is not None and + self.total is not self.DEFAULT_TIMEOUT and + self._read is not None and + self._read is not self.DEFAULT_TIMEOUT): + # in case the connect timeout has not yet been established. + if self._start_connect is None: + return self._read + return max(0, min(self.total - self.get_connect_duration(), + self._read)) + elif self.total is not None and self.total is not self.DEFAULT_TIMEOUT: + return max(0, self.total - self.get_connect_duration()) + else: + return self._read diff --git a/requests/packages/urllib3/util/url.py b/requests/packages/urllib3/util/url.py new file mode 100644 index 00000000..362d2160 --- /dev/null +++ b/requests/packages/urllib3/util/url.py @@ -0,0 +1,162 @@ +from collections import namedtuple + +from ..exceptions import LocationParseError + + +class Url(namedtuple('Url', ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'])): + """ + Datastructure for representing an HTTP URL. Used as a return value for + :func:`parse_url`. + """ + slots = () + + def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None, query=None, fragment=None): + return super(Url, cls).__new__(cls, scheme, auth, host, port, path, query, fragment) + + @property + def hostname(self): + """For backwards-compatibility with urlparse. We're nice like that.""" + return self.host + + @property + def request_uri(self): + """Absolute path including the query string.""" + uri = self.path or '/' + + if self.query is not None: + uri += '?' + self.query + + return uri + + @property + def netloc(self): + """Network location including host and port""" + if self.port: + return '%s:%d' % (self.host, self.port) + return self.host + + +def split_first(s, delims): + """ + Given a string and an iterable of delimiters, split on the first found + delimiter. Return two split parts and the matched delimiter. + + If not found, then the first part is the full input string. + + Example: :: + + >>> split_first('foo/bar?baz', '?/=') + ('foo', 'bar?baz', '/') + >>> split_first('foo/bar?baz', '123') + ('foo/bar?baz', '', None) + + Scales linearly with number of delims. Not ideal for large number of delims. + """ + min_idx = None + min_delim = None + for d in delims: + idx = s.find(d) + if idx < 0: + continue + + if min_idx is None or idx < min_idx: + min_idx = idx + min_delim = d + + if min_idx is None or min_idx < 0: + return s, '', None + + return s[:min_idx], s[min_idx+1:], min_delim + + +def parse_url(url): + """ + Given a url, return a parsed :class:`.Url` namedtuple. Best-effort is + performed to parse incomplete urls. Fields not provided will be None. + + Partly backwards-compatible with :mod:`urlparse`. + + Example: :: + + >>> parse_url('http://google.com/mail/') + Url(scheme='http', host='google.com', port=None, path='/', ...) + >>> parse_url('google.com:80') + Url(scheme=None, host='google.com', port=80, path=None, ...) + >>> parse_url('/foo?bar') + Url(scheme=None, host=None, port=None, path='/foo', query='bar', ...) + """ + + # While this code has overlap with stdlib's urlparse, it is much + # simplified for our needs and less annoying. + # Additionally, this implementations does silly things to be optimal + # on CPython. + + scheme = None + auth = None + host = None + port = None + path = None + fragment = None + query = None + + # Scheme + if '://' in url: + scheme, url = url.split('://', 1) + + # Find the earliest Authority Terminator + # (http://tools.ietf.org/html/rfc3986#section-3.2) + url, path_, delim = split_first(url, ['/', '?', '#']) + + if delim: + # Reassemble the path + path = delim + path_ + + # Auth + if '@' in url: + # Last '@' denotes end of auth part + auth, url = url.rsplit('@', 1) + + # IPv6 + if url and url[0] == '[': + host, url = url.split(']', 1) + host += ']' + + # Port + if ':' in url: + _host, port = url.split(':', 1) + + if not host: + host = _host + + if port: + # If given, ports must be integers. + if not port.isdigit(): + raise LocationParseError(url) + port = int(port) + else: + # Blank ports are cool, too. (rfc3986#section-3.2.3) + port = None + + elif not host and url: + host = url + + if not path: + return Url(scheme, auth, host, port, path, query, fragment) + + # Fragment + if '#' in path: + path, fragment = path.split('#', 1) + + # Query + if '?' in path: + path, query = path.split('?', 1) + + return Url(scheme, auth, host, port, path, query, fragment) + + +def get_host(url): + """ + Deprecated. Use :func:`.parse_url` instead. + """ + p = parse_url(url) + return p.scheme or 'http', p.hostname, p.port From 459f8dfccb7ee2e1dc9e041b7aebc2392219c35e Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 26 Apr 2014 13:05:01 +0100 Subject: [PATCH 089/545] Don't repopulate proxies if we don't trust the env. --- requests/sessions.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index 28b5da9b..6b21b5df 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -218,11 +218,11 @@ class SessionRedirectMixin(object): """ headers = prepared_request.headers url = prepared_request.url - new_proxies = {} + scheme = urlparse(url).scheme + new_proxies = proxies.copy() if proxies is not None else {} - if not should_bypass_proxies(url): + if self.trust_env and not should_bypass_proxies(url): environ_proxies = get_environ_proxies(url) - scheme = urlparse(url).scheme proxy = environ_proxies.get(scheme) From 3e568eb3b0c3f2dc2cc11ba873db8593918bad87 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sat, 26 Apr 2014 09:27:06 -0400 Subject: [PATCH 090/545] sidebar update --- docs/_templates/sidebarintro.html | 17 +++++++++-------- docs/_templates/sidebarlogo.html | 17 +++++++++-------- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 8219e0a7..de68eae7 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -22,14 +22,15 @@ -

Get Support

- -

- If your organization uses Requests, consider financial support: -

-

- Requests Pro -

+

Donate

+

+ If you love Requests, consider supporting the author on Gittip: +

+

+ +

Translations

diff --git a/docs/_templates/sidebarlogo.html b/docs/_templates/sidebarlogo.html index 4c14e4fa..306c7e3d 100644 --- a/docs/_templates/sidebarlogo.html +++ b/docs/_templates/sidebarlogo.html @@ -20,11 +20,12 @@

Subscribe to Newsletter

-

Get Support

- -

- If your organization uses Requests, consider financial support: -

-

- Requests Pro -

\ No newline at end of file +

Donate

+

+ If you love Requests, consider supporting the author on Gittip: +

+

+ +

\ No newline at end of file From 7fa9799e28d11484f8d4c88235d2419e5e1856e4 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sat, 26 Apr 2014 09:28:05 -0400 Subject: [PATCH 091/545] sidebar update --- docs/_templates/sidebarintro.html | 13 ++++++------- docs/_templates/sidebarlogo.html | 14 ++++++++------ 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index de68eae7..09fe51a4 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -15,13 +15,6 @@

-

Get Updates

-

Receive updates on new releases and upcoming projects.

- -

Subscribe to Newsletter

- - -

Donate

If you love Requests, consider supporting the author on Gittip: @@ -33,6 +26,12 @@

+

Get Updates

+

Receive updates on new releases and upcoming projects.

+ +

Subscribe to Newsletter

+ +

Translations

  • English
  • diff --git a/docs/_templates/sidebarlogo.html b/docs/_templates/sidebarlogo.html index 306c7e3d..6aca76eb 100644 --- a/docs/_templates/sidebarlogo.html +++ b/docs/_templates/sidebarlogo.html @@ -14,11 +14,6 @@ development release.

    -

    Get Updates

    -

    Receive updates on new releases and upcoming projects.

    - -

    Subscribe to Newsletter

    -

    Donate

    @@ -28,4 +23,11 @@ -

    \ No newline at end of file +

    + +

    Get Updates

    +

    Receive updates on new releases and upcoming projects.

    + +

    Subscribe to Newsletter

    + + From 142b26ea7b4a4badd5ba98ed0e8490e3c7134592 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 28 Apr 2014 19:42:30 +0100 Subject: [PATCH 092/545] Clearer description of Response.close(). --- requests/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index 7390d1c6..f5afebc8 100644 --- a/requests/models.py +++ b/requests/models.py @@ -791,8 +791,8 @@ class Response(object): raise HTTPError(http_error_msg, response=self) def close(self): - """Closes the underlying file descriptor and releases the connection - back to the pool. + """Releases the connection back to the pool. Once this method has been + called the underlying ``raw`` object must not be accessed again. *Note: Should not normally need to be called explicitly.* """ From b8049ba2012fe251c7351de6c2c4ad3b274df1f1 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Tue, 29 Apr 2014 18:50:38 +0100 Subject: [PATCH 093/545] Add urllib3 util package to setup.py. --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 879d94a6..c25b3680 100755 --- a/setup.py +++ b/setup.py @@ -21,7 +21,8 @@ packages = [ 'requests.packages.urllib3', 'requests.packages.urllib3.packages', 'requests.packages.urllib3.contrib', - 'requests.packages.urllib3.packages.ssl_match_hostname' + 'requests.packages.urllib3.util', + 'requests.packages.urllib3.packages.ssl_match_hostname', ] requires = [] From 31762899a013276343c02c72960c0e48b02ed3d9 Mon Sep 17 00:00:00 2001 From: Donald Stufft Date: Fri, 2 May 2014 12:51:50 -0400 Subject: [PATCH 094/545] Remove the easy_install section There's not a lot of good reason to actually call out easy_install at all. Anyone who prefers it already knows it exists and everyone else should be directed unambiguously towards pip. --- README.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.rst b/README.rst index 734773aa..521ab6f2 100644 --- a/README.rst +++ b/README.rst @@ -64,14 +64,6 @@ To install Requests, simply: $ pip install requests -Or, if you absolutely must: - -.. code-block:: bash - - $ easy_install requests - -But, you really shouldn't do that. - Documentation ------------- From 07f9a7ea0201b5e8d9763c41153862f211de12bd Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 3 May 2014 10:59:14 +0100 Subject: [PATCH 095/545] Remove references to not-yet-made 2.3.0 release. --- HISTORY.rst | 2 +- requests/__init__.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 896dca4c..0df8facf 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,7 +3,7 @@ Release History --------------- -2.3.0 (YYYY-MM-DD) +X.X.X (YYYY-MM-DD) ++++++++++++++++++ **API Changes** diff --git a/requests/__init__.py b/requests/__init__.py index bba19002..2e9f3a0b 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.3.0' -__build__ = 0x020300 +__version__ = '2.2.1' +__build__ = 0x020201 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2014 Kenneth Reitz' From 626c48e9471d8075039aeb5daefa61756951e03f Mon Sep 17 00:00:00 2001 From: Donald Stufft Date: Mon, 12 May 2014 14:43:50 -0400 Subject: [PATCH 096/545] Update urllib3 to 4fb351cd2 --- requests/packages/urllib3/__init__.py | 2 +- requests/packages/urllib3/connection.py | 19 +++++++++++++------ requests/packages/urllib3/fields.py | 2 +- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/requests/packages/urllib3/__init__.py b/requests/packages/urllib3/__init__.py index bd237a66..73071f70 100644 --- a/requests/packages/urllib3/__init__.py +++ b/requests/packages/urllib3/__init__.py @@ -10,7 +10,7 @@ urllib3 - Thread-safe connection pooling and re-using. __author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' __license__ = 'MIT' -__version__ = '1.8.2' +__version__ = 'dev' from .connectionpool import ( diff --git a/requests/packages/urllib3/connection.py b/requests/packages/urllib3/connection.py index de7b925e..5feb3322 100644 --- a/requests/packages/urllib3/connection.py +++ b/requests/packages/urllib3/connection.py @@ -95,7 +95,10 @@ class HTTPConnection(_HTTPConnection, object): def _prepare_conn(self, conn): self.sock = conn - if self._tunnel_host: + # the _tunnel_host attribute was added in python 2.6.3 (via + # http://hg.python.org/cpython/rev/0f57b30a152f) so pythons 2.6(0-2) do + # not have them. + if getattr(self, '_tunnel_host', None): # TODO: Fix tunnel so it doesn't depend on self.sock state. self._tunnel() @@ -165,21 +168,25 @@ class VerifiedHTTPSConnection(HTTPSConnection): resolved_cert_reqs = resolve_cert_reqs(self.cert_reqs) resolved_ssl_version = resolve_ssl_version(self.ssl_version) - # the _tunnel_host attribute was added in python 2.6.3 (via - # http://hg.python.org/cpython/rev/0f57b30a152f) so pythons 2.6(0-2) do - # not have them. + hostname = self.host if getattr(self, '_tunnel_host', None): + # _tunnel_host was added in Python 2.6.3 + # (See: http://hg.python.org/cpython/rev/0f57b30a152f) + self.sock = sock # Calls self._set_hostport(), so self.host is # self._tunnel_host below. self._tunnel() + # Override the host with the one we're requesting data from. + hostname = self._tunnel_host + # Wrap socket using verification with the root certs in # trusted_root_certs self.sock = ssl_wrap_socket(sock, self.key_file, self.cert_file, cert_reqs=resolved_cert_reqs, ca_certs=self.ca_certs, - server_hostname=self.host, + server_hostname=hostname, ssl_version=resolved_ssl_version) if resolved_cert_reqs != ssl.CERT_NONE: @@ -188,7 +195,7 @@ class VerifiedHTTPSConnection(HTTPSConnection): self.assert_fingerprint) elif self.assert_hostname is not False: match_hostname(self.sock.getpeercert(), - self.assert_hostname or self.host) + self.assert_hostname or hostname) if ssl: diff --git a/requests/packages/urllib3/fields.py b/requests/packages/urllib3/fields.py index ed017657..da79e929 100644 --- a/requests/packages/urllib3/fields.py +++ b/requests/packages/urllib3/fields.py @@ -15,7 +15,7 @@ def guess_content_type(filename, default='application/octet-stream'): Guess the "Content-Type" of a file. :param filename: - The filename to guess the "Content-Type" of using :mod:`mimetimes`. + The filename to guess the "Content-Type" of using :mod:`mimetypes`. :param default: If no "Content-Type" can be guessed, default to `default`. """ From c15a8f8a3d8fbe943dcae2603c4ceca7f99ff2bf Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 12 May 2014 15:10:15 -0400 Subject: [PATCH 097/545] CaseInsensitiveDict now looks like a normal dict --- requests/structures.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/requests/structures.py b/requests/structures.py index 3aa090e8..9fd78187 100644 --- a/requests/structures.py +++ b/requests/structures.py @@ -106,12 +106,8 @@ class CaseInsensitiveDict(collections.MutableMapping): return CaseInsensitiveDict(self._store.values()) def __repr__(self): - return '%s(%r)' % (self.__class__.__name__, dict(self.items())) - - def __str__(self): return str(dict(self.items())) - class LookupDict(dict): """Dictionary lookup object.""" From 679ab5bc20d0d8569540b94178f232791bdb157d Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 12 May 2014 20:27:10 +0100 Subject: [PATCH 098/545] Initial 2.3.0 changelog. --- HISTORY.rst | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 0df8facf..05c7ddd5 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,7 +3,7 @@ Release History --------------- -X.X.X (YYYY-MM-DD) +2.3.0 (2014-05-12) ++++++++++++++++++ **API Changes** @@ -13,11 +13,24 @@ X.X.X (YYYY-MM-DD) or not it actually did). - The ``timeout`` parameter now affects requests with both ``stream=True`` and ``stream=False`` equally. +- The change in v2.0.0 to mandate explicit proxy schemes has been reverted. + Proxy schemes now default to ``http://``. +- The ``CaseInsensitiveDict`` used for HTTP headers now behaves like a normal + dictionary when printed as a string or in the interpreter. **Bugfixes** - No longer expose Authorization or Proxy-Authorization headers on redirect. Fix CVE-2014-1829 and CVE-2014-1830 respectively. +- Authorization is re-evaluated each redirect. +- On redirect, pass url as native strings. +- Fall-back to autodetected encoding for JSON when Unicode detection fails. +- Headers set to ``None`` on the ``Session`` are now correctly not sent. +- Correctly honor ``decode_unicode`` even if it wasn't used earlier in the same + response. +- Stop advertising ``compress`` as a supported Content-Encoding. +- The ``Response.history`` parameter is now always a list. +- Many, many ``urllib3`` bugfixes. 2.2.1 (2014-01-23) ++++++++++++++++++ From c2a1f28a2ebd9448be7b4c8f9207d16c894717c3 Mon Sep 17 00:00:00 2001 From: schlamar Date: Mon, 10 Mar 2014 07:54:22 +0100 Subject: [PATCH 099/545] Catch possible exceptions while consuming content of redirect responses. --- requests/sessions.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index 6b21b5df..fe8d7e2a 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -19,7 +19,8 @@ from .cookies import ( from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT from .hooks import default_hooks, dispatch_hook from .utils import to_key_val_list, default_headers, to_native_string -from .exceptions import TooManyRedirects, InvalidSchema +from .exceptions import ( + TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError) from .structures import CaseInsensitiveDict from .adapters import HTTPAdapter @@ -94,7 +95,10 @@ class SessionRedirectMixin(object): while resp.is_redirect: prepared_request = req.copy() - resp.content # Consume socket so it can be released + try: + resp.content # Consume socket so it can be released + except (ChunkedEncodingError, ContentDecodingError, RuntimeError): + resp.raw.read(decode_content=False) if i >= self.max_redirects: raise TooManyRedirects('Exceeded %s redirects.' % self.max_redirects) From 59c8d813818110aac29fd104c2fa012387c2004c Mon Sep 17 00:00:00 2001 From: schlamar Date: Mon, 10 Mar 2014 08:07:10 +0100 Subject: [PATCH 100/545] Read content in Session.send instead of Adapter.send. --- requests/adapters.py | 7 +------ requests/sessions.py | 3 +++ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index 12d23b01..eb7a2d28 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -385,9 +385,4 @@ class HTTPAdapter(BaseAdapter): else: raise - r = self.build_response(request, resp) - - if not stream: - r.content - - return r + return self.build_response(request, resp) diff --git a/requests/sessions.py b/requests/sessions.py index fe8d7e2a..df85a25c 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -592,6 +592,9 @@ class Session(SessionRedirectMixin): r = history.pop() r.history = history + if not stream: + r.content + return r def get_adapter(self, url): From 5e860c08d8b3f14bcdf0382b0535da39a4d5a02e Mon Sep 17 00:00:00 2001 From: schlamar Date: Thu, 13 Mar 2014 18:27:12 +0100 Subject: [PATCH 101/545] Added test case to handle response of streamed redirects. Credits go to @zackw. --- test_requests.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index a1df1f19..e3eacd6f 100755 --- a/test_requests.py +++ b/test_requests.py @@ -917,6 +917,25 @@ class RequestsTestCase(unittest.TestCase): assert h1 == h2 + def test_manual_redirect_with_partial_body_read(self): + s = requests.Session() + r1 = s.get(httpbin('redirect/2'), allow_redirects=False, stream=True) + assert r1.is_redirect + rg = s.resolve_redirects(r1, r1.request, stream=True) + + # read only the first eight bytes of the response body, + # then follow the redirect + r1.iter_content(8) + r2 = next(rg) + assert r2.is_redirect + + # read all of the response via iter_content, + # then follow the redirect + for _ in r2.iter_content(): + pass + r3 = next(rg) + assert not r3.is_redirect + class TestContentEncodingDetection(unittest.TestCase): @@ -1321,7 +1340,7 @@ def test_data_argument_accepts_tuples(list_of_tuples): hooks=default_hooks() ) assert p.body == urlencode(data) - + if __name__ == '__main__': unittest.main() From 16459910a963c108529585fe8feb78f862d6d9ba Mon Sep 17 00:00:00 2001 From: schlamar Date: Sat, 15 Mar 2014 21:29:14 +0100 Subject: [PATCH 102/545] Added test for redirect with wrong gzipped header. --- test_requests.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test_requests.py b/test_requests.py index e3eacd6f..a92b22c6 100755 --- a/test_requests.py +++ b/test_requests.py @@ -936,6 +936,26 @@ class RequestsTestCase(unittest.TestCase): r3 = next(rg) assert not r3.is_redirect + def _patch_adapter_gzipped_redirect(self, session, url): + adapter = session.get_adapter(url=url) + org_build_response = adapter.build_response + self._patched_response = False + + def build_response(*args, **kwargs): + resp = org_build_response(*args, **kwargs) + if not self._patched_response: + resp.raw.headers['content-encoding'] = 'gzip' + self._patched_response = True + return resp + + adapter.build_response = build_response + + def test_redirect_with_wrong_gzipped_header(self): + s = requests.Session() + url = httpbin('redirect/1') + self._patch_adapter_gzipped_redirect(s, url) + s.get(url) + class TestContentEncodingDetection(unittest.TestCase): From a7e724161665895cd6328a90b65c4acdec2fe236 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Romanowski?= Date: Tue, 13 May 2014 10:11:40 +0200 Subject: [PATCH 103/545] Fix typo in advanced.rst docs `Sesssion` -> `Session` --- docs/user/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 80c1e6ae..eead69e7 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -111,7 +111,7 @@ request. The simple recipe for this is the following:: Since you are not doing anything special with the ``Request`` object, you prepare it immediately and modify the ``PreparedRequest`` object. You then send that with the other parameters you would have sent to ``requests.*`` or -``Sesssion.*``. +``Session.*``. However, the above code will lose some of the advantages of having a Requests :class:`Session ` object. In particular, From b26c85f081b083f71345fce13005f95ec242d2f6 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 16 May 2014 13:55:06 -0400 Subject: [PATCH 104/545] history update --- HISTORY.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 05c7ddd5..2e39b864 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,7 +3,7 @@ Release History --------------- -2.3.0 (2014-05-12) +2.3.0 (2014-05-16) ++++++++++++++++++ **API Changes** @@ -16,7 +16,7 @@ Release History - The change in v2.0.0 to mandate explicit proxy schemes has been reverted. Proxy schemes now default to ``http://``. - The ``CaseInsensitiveDict`` used for HTTP headers now behaves like a normal - dictionary when printed as a string or in the interpreter. + dictionary when references as string or viewd in the interpreter. **Bugfixes** From 6366d3dd190a9e58ca582955cddf7e2ac5f32dcc Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 16 May 2014 13:55:47 -0400 Subject: [PATCH 105/545] v2.3.0 --- requests/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/__init__.py b/requests/__init__.py index 2e9f3a0b..bba19002 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.2.1' -__build__ = 0x020201 +__version__ = '2.3.0' +__build__ = 0x020300 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2014 Kenneth Reitz' From 53d3519a3503c59ad7de05941064f472720ae948 Mon Sep 17 00:00:00 2001 From: Alex Gaynor Date: Fri, 16 May 2014 14:11:05 -0400 Subject: [PATCH 106/545] Fixed a typo --- HISTORY.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 2e39b864..81d120aa 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -16,7 +16,7 @@ Release History - The change in v2.0.0 to mandate explicit proxy schemes has been reverted. Proxy schemes now default to ``http://``. - The ``CaseInsensitiveDict`` used for HTTP headers now behaves like a normal - dictionary when references as string or viewd in the interpreter. + dictionary when references as string or viewed in the interpreter. **Bugfixes** From 76c09e7076f156c85f339185845ac9b2bf65dc49 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 16 May 2014 15:30:48 -0400 Subject: [PATCH 107/545] ignot dist --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 60eb6c67..9fcc6c3d 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ env/ t.py t2.py +dist From eebae9c63bd336550210a4e4039298d23e6ad99f Mon Sep 17 00:00:00 2001 From: Ben McGinnes Date: Sun, 18 May 2014 01:16:27 +1000 Subject: [PATCH 108/545] Fixed paragraph formatting in Urllib3/MIT License. --- NOTICE | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/NOTICE b/NOTICE index 223101a0..f583e47a 100644 --- a/NOTICE +++ b/NOTICE @@ -8,21 +8,24 @@ This is the MIT license: http://www.opensource.org/licenses/mit-license.php Copyright 2008-2011 Andrey Petrov and contributors (see CONTRIBUTORS.txt), Modifications copyright 2012 Kenneth Reitz. -Permission is hereby granted, free of charge, to any person obtaining a copy of this -software and associated documentation files (the "Software"), to deal in the Software -without restriction, including without limitation the rights to use, copy, modify, merge, -publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons -to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: -The above copyright notice and this permission notice shall be included in all copies or -substantial portions of the Software. +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, -INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR -PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE -FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Chardet License =============== From f97e78726e7efe64b9a3db27707ec579e859ef06 Mon Sep 17 00:00:00 2001 From: Ben Bass Date: Sat, 17 May 2014 23:15:51 +0100 Subject: [PATCH 109/545] Factor out HTTPAdapter.proxy_manager_for for #2048 --- requests/adapters.py | 31 ++++++++++++++++++++----------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index eb7a2d28..6b8f4d3b 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -118,6 +118,24 @@ class HTTPAdapter(BaseAdapter): self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize, block=block) + def proxy_manager_for(self, proxy): + """Return urllib3 ProxyManager for the given proxy. This method should + not be called from user code, and is only exposed for use when + subclassing the :class:`HTTPAdapter `. + + :param proxy: The proxy to return a urllib3 ProxyManager for. + """ + if not proxy in self.proxy_manager: + proxy_headers = self.proxy_headers(proxy) + self.proxy_manager[proxy] = proxy_from_url( + proxy, + proxy_headers=proxy_headers, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block) + + return self.proxy_manager[proxy] + def cert_verify(self, conn, url, verify, cert): """Verify a SSL certificate. This method should not be called from user code, and is only exposed for use when subclassing the @@ -204,17 +222,8 @@ class HTTPAdapter(BaseAdapter): if proxy: proxy = prepend_scheme_if_needed(proxy, 'http') - proxy_headers = self.proxy_headers(proxy) - - if not proxy in self.proxy_manager: - self.proxy_manager[proxy] = proxy_from_url( - proxy, - proxy_headers=proxy_headers, - num_pools=self._pool_connections, - maxsize=self._pool_maxsize, - block=self._pool_block) - - conn = self.proxy_manager[proxy].connection_from_url(url) + proxy_manager = self.proxy_manager_for(proxy) + conn = proxy_manager.connection_from_url(url) else: # Only scheme should be lower case parsed = urlparse(url) From e3d0ac7c9dae7468dc98ebe19052148256cc0757 Mon Sep 17 00:00:00 2001 From: Ben Bass Date: Sat, 17 May 2014 23:22:15 +0100 Subject: [PATCH 110/545] update AUTHORS.rst --- AUTHORS.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index ab82930b..52bfc350 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -151,4 +151,5 @@ Patches and Suggestions - Kamil Madac - Michael Becker @beckerfuffle - Erik Wickstrom @erikwickstrom -- Константин Подшумок @podshumok \ No newline at end of file +- Константин Подшумок @podshumok +- Ben Bass @codedstructure From 02618c8df5071d608a8c8341e4a44e26f58215d5 Mon Sep 17 00:00:00 2001 From: Ben Bass Date: Sun, 18 May 2014 20:10:35 +0100 Subject: [PATCH 111/545] improve `proxy_manager_for` re-usability by subclass --- requests/adapters.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index 6b8f4d3b..995d8f80 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -118,12 +118,16 @@ class HTTPAdapter(BaseAdapter): self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize, block=block) - def proxy_manager_for(self, proxy): - """Return urllib3 ProxyManager for the given proxy. This method should - not be called from user code, and is only exposed for use when - subclassing the :class:`HTTPAdapter `. + def proxy_manager_for(self, proxy, **proxy_kwargs): + """Return urllib3 ProxyManager for the given proxy. + + This method should not be called from user code, and is only + exposed for use when subclassing the + :class:`HTTPAdapter `. :param proxy: The proxy to return a urllib3 ProxyManager for. + :param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager. + :returns: ProxyManager """ if not proxy in self.proxy_manager: proxy_headers = self.proxy_headers(proxy) @@ -132,7 +136,8 @@ class HTTPAdapter(BaseAdapter): proxy_headers=proxy_headers, num_pools=self._pool_connections, maxsize=self._pool_maxsize, - block=self._pool_block) + block=self._pool_block, + **proxy_kwargs) return self.proxy_manager[proxy] From 54e96b40b7c36deb547339e5d3d4ccac75b53b51 Mon Sep 17 00:00:00 2001 From: Ben Bass Date: Sun, 18 May 2014 20:24:35 +0100 Subject: [PATCH 112/545] allow pool_kwargs to be specified in init_poolmanager --- requests/adapters.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index 995d8f80..da7d7b11 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -101,14 +101,17 @@ class HTTPAdapter(BaseAdapter): self.init_poolmanager(self._pool_connections, self._pool_maxsize, block=self._pool_block) - def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK): - """Initializes a urllib3 PoolManager. This method should not be called - from user code, and is only exposed for use when subclassing the + def init_poolmanager(self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs): + """Initializes a urllib3 PoolManager. + + This method should not be called from user code, and is only + exposed for use when subclassing the :class:`HTTPAdapter `. :param connections: The number of urllib3 connection pools to cache. :param maxsize: The maximum number of connections to save in the pool. :param block: Block when no free connections are available. + :param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager. """ # save these values for pickling self._pool_connections = connections @@ -116,7 +119,7 @@ class HTTPAdapter(BaseAdapter): self._pool_block = block self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize, - block=block) + block=block, **pool_kwargs) def proxy_manager_for(self, proxy, **proxy_kwargs): """Return urllib3 ProxyManager for the given proxy. From 7815953cb41450992e487aa300080064999c4e99 Mon Sep 17 00:00:00 2001 From: Josh Schneier Date: Fri, 23 May 2014 11:52:17 -0400 Subject: [PATCH 113/545] remove unused IteratorProxy --- requests/structures.py | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/requests/structures.py b/requests/structures.py index 9fd78187..66cdad86 100644 --- a/requests/structures.py +++ b/requests/structures.py @@ -8,30 +8,7 @@ Data structures that power Requests. """ -import os import collections -from itertools import islice - - -class IteratorProxy(object): - """docstring for IteratorProxy""" - def __init__(self, i): - self.i = i - # self.i = chain.from_iterable(i) - - def __iter__(self): - return self.i - - def __len__(self): - if hasattr(self.i, '__len__'): - return len(self.i) - if hasattr(self.i, 'len'): - return self.i.len - if hasattr(self.i, 'fileno'): - return os.fstat(self.i.fileno()).st_size - - def read(self, n): - return "".join(islice(self.i, None, n)) class CaseInsensitiveDict(collections.MutableMapping): From 3b0fcb620dd196acc20d03e259ba6fdb20b1a2c9 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Mon, 26 May 2014 00:44:09 +0200 Subject: [PATCH 114/545] compat: handle SyntaxError when importing simplejson We officially support Python 2.6 to 3.3, but simplejson does not support Python 3.1 or 3.2: https://github.com/simplejson/simplejson/issues/66 Importing simplejson on Python 3.2 results in a SyntaxError because simplejson uses the u'...' syntax (the syntax was not supported in Python 3.0 to 3.2). Support for loading simplejson instead of the stdlib json module was added by #710: https://github.com/kennethreitz/requests/pull/710 No mention was made of the lack of support for Python 3.2, but it was mentioned that simplejson can be faster than the stdlib json module. --- requests/compat.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requests/compat.py b/requests/compat.py index bdf10d6a..84d703b6 100644 --- a/requests/compat.py +++ b/requests/compat.py @@ -75,7 +75,9 @@ is_solaris = ('solar==' in str(sys.platform).lower()) # Complete guess. try: import simplejson as json -except ImportError: +except (ImportError, SyntaxError): + # simplejson does not support Python 3.2, it thows a SyntaxError + # because of u'...' Unicode literals. import json # --------- From 5eea4a3767797af6a0b994723a4280382328ce03 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Mon, 26 May 2014 00:18:14 +0200 Subject: [PATCH 115/545] faq: mention that PyPy 2.2 works too The FAQ says that PyPy 1.9 is supported. On my Debian Unstable, I can install PyPy 2.2.1 and the tests work with that version too. --- docs/community/faq.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/community/faq.rst b/docs/community/faq.rst index 4e792eca..ed98fa85 100644 --- a/docs/community/faq.rst +++ b/docs/community/faq.rst @@ -60,6 +60,7 @@ supported: * Python 3.2 * Python 3.3 * PyPy 1.9 +* PyPy 2.2 What are "hostname doesn't match" errors? ----------------------------------------- From f4d1bbb7c549e1e276e61fa6390f9b5ce761c0ae Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Mon, 26 May 2014 00:23:21 +0200 Subject: [PATCH 116/545] faq: add Python 3.4 to list of supported Python versions No code changes were necessary for this. --- docs/community/faq.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/community/faq.rst b/docs/community/faq.rst index ed98fa85..5b3ecb81 100644 --- a/docs/community/faq.rst +++ b/docs/community/faq.rst @@ -59,6 +59,7 @@ supported: * Python 3.1 * Python 3.2 * Python 3.3 +* Python 3.4 * PyPy 1.9 * PyPy 2.2 From c8916a08deb8be25d2be38c646fab9b2ab1fb8f7 Mon Sep 17 00:00:00 2001 From: Martin Geisler Date: Mon, 26 May 2014 00:31:10 +0200 Subject: [PATCH 117/545] test: restore Python 3.2 compatibility The tests were broken because they used the u'...' Unicode literal syntax which disappeared in Python 3.0 to 3.2. We can work around this by conditionally defining a "u" function which will produce a Unicode literal on Python 2.x. This is basically the same approach as taken by the six library often used for writing cross-version compatible code. --- test_requests.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/test_requests.py b/test_requests.py index a92b22c6..4871dc51 100755 --- a/test_requests.py +++ b/test_requests.py @@ -16,7 +16,7 @@ import pytest from requests.adapters import HTTPAdapter from requests.auth import HTTPDigestAuth from requests.compat import ( - Morsel, cookielib, getproxies, str, urljoin, urlparse) + Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3) from requests.cookies import cookiejar_from_dict, morsel_to_cookie from requests.exceptions import InvalidURL, MissingSchema from requests.models import PreparedRequest, Response @@ -30,6 +30,14 @@ try: except ImportError: import io as StringIO +if is_py3: + def u(s): + return s +else: + def u(s): + return s.decode('unicode-escape') + + HTTPBIN = os.environ.get('HTTPBIN_URL', 'http://httpbin.org/') # Issue #1483: Make sure the URL always has a trailing slash HTTPBIN = HTTPBIN.rstrip('/') + '/' @@ -409,7 +417,7 @@ class RequestsTestCase(unittest.TestCase): url = httpbin('post') with open('requirements.txt') as f: pytest.raises(ValueError, "requests.post(url, data='[{\"some\": \"data\"}]', files={'some': f})") - pytest.raises(ValueError, "requests.post(url, data=u'[{\"some\": \"data\"}]', files={'some': f})") + pytest.raises(ValueError, "requests.post(url, data=u('[{\"some\": \"data\"}]'), files={'some': f})") def test_request_ok_set(self): r = requests.get(httpbin('status', '404')) @@ -456,12 +464,12 @@ class RequestsTestCase(unittest.TestCase): def test_unicode_multipart_post(self): r = requests.post(httpbin('post'), - data={'stuff': u'ëlïxr'}, + data={'stuff': u('ëlïxr')}, files={'file': ('test_requests.py', open(__file__, 'rb'))}) assert r.status_code == 200 r = requests.post(httpbin('post'), - data={'stuff': u'ëlïxr'.encode('utf-8')}, + data={'stuff': u('ëlïxr').encode('utf-8')}, files={'file': ('test_requests.py', open(__file__, 'rb'))}) assert r.status_code == 200 @@ -488,7 +496,7 @@ class RequestsTestCase(unittest.TestCase): def test_unicode_method_name(self): files = {'file': open('test_requests.py', 'rb')} - r = requests.request(method=u'POST', url=httpbin('post'), files=files) + r = requests.request(method=u('POST'), url=httpbin('post'), files=files) assert r.status_code == 200 def test_custom_content_type(self): @@ -865,7 +873,7 @@ class RequestsTestCase(unittest.TestCase): assert r.url == url def test_header_keys_are_native(self): - headers = {u'unicode': 'blah', 'byte'.encode('ascii'): 'blah'} + headers = {u('unicode'): 'blah', 'byte'.encode('ascii'): 'blah'} r = requests.Request('GET', httpbin('get'), headers=headers) p = r.prepare() From 3346ad1134bbf50e9d9ad36ddd396536c18fe6bc Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 26 May 2014 16:40:45 +0100 Subject: [PATCH 118/545] Document and initialise Response.request --- requests/models.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/requests/models.py b/requests/models.py index 120968ff..5475c14c 100644 --- a/requests/models.py +++ b/requests/models.py @@ -556,6 +556,10 @@ class Response(object): #: and the arrival of the response (as a timedelta) self.elapsed = datetime.timedelta(0) + #: The :class:`PreparedRequest ` object to which this + #: is a response. + self.request = None + def __getstate__(self): # Consume everything; accessing the content attribute makes # sure the content has been fully read. From 3faff0b8ea2c3c7d87272971893234e26a01d9fc Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 27 May 2014 20:29:44 -0500 Subject: [PATCH 119/545] Check for basestring, not just str Fixes #2071 --- requests/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 5475c14c..0d7132a4 100644 --- a/requests/models.py +++ b/requests/models.py @@ -433,7 +433,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): else: if data: body = self._encode_params(data) - if isinstance(data, str) or isinstance(data, builtin_str) or hasattr(data, 'read'): + if isinstance(data, basestring) or isinstance(data, builtin_str) or hasattr(data, 'read'): content_type = None else: content_type = 'application/x-www-form-urlencoded' From 5ab79e2514b663ac8e0a82981efe0a5e69efe01c Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 28 May 2014 08:17:18 -0500 Subject: [PATCH 120/545] Remove unnecessary check for builtin_str --- requests/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 0d7132a4..9b16b9d8 100644 --- a/requests/models.py +++ b/requests/models.py @@ -433,7 +433,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): else: if data: body = self._encode_params(data) - if isinstance(data, basestring) or isinstance(data, builtin_str) or hasattr(data, 'read'): + if isinstance(data, basestring) or hasattr(data, 'read'): content_type = None else: content_type = 'application/x-www-form-urlencoded' From a2413e010f4d2dd5fa4cfcc85427a47a67fc8b01 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 28 May 2014 16:59:57 +0100 Subject: [PATCH 121/545] Ensure that we open files in binary mode. --- docs/user/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index eead69e7..f384f158 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -229,7 +229,7 @@ Streaming Uploads Requests supports streaming uploads, which allow you to send large streams or files without reading them into memory. To stream and upload, simply provide a file-like object for your body:: - with open('massive-body') as f: + with open('massive-body', 'rb') as f: requests.post('http://some.url/streamed', data=f) From 32f600b0836a86596aa3edc98aaa6827f6fe4644 Mon Sep 17 00:00:00 2001 From: David Gouldin Date: Fri, 30 May 2014 10:11:53 -0700 Subject: [PATCH 122/545] Allow copying of PreparedRequests without headers/cookies --- requests/models.py | 4 ++-- test_requests.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index 9b16b9d8..5aad8ce4 100644 --- a/requests/models.py +++ b/requests/models.py @@ -309,8 +309,8 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): p = PreparedRequest() p.method = self.method p.url = self.url - p.headers = self.headers.copy() - p._cookies = self._cookies.copy() + 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.body = self.body p.hooks = self.hooks return p diff --git a/test_requests.py b/test_requests.py index 4871dc51..f3c93723 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1369,6 +1369,34 @@ def test_data_argument_accepts_tuples(list_of_tuples): ) assert p.body == urlencode(data) +def assert_copy(p, p_copy): + for attr in ('method', 'url', 'headers', '_cookies', 'body', 'hooks'): + assert getattr(p, attr) == getattr(p_copy, attr) + +def test_prepared_request_empty_copy(): + p = PreparedRequest() + assert_copy(p, p.copy()) + +def test_prepared_request_no_cookies_copy(): + p = PreparedRequest() + p.prepare( + method='GET', + url='http://www.example.com', + data='foo=bar', + hooks=default_hooks() + ) + assert_copy(p, p.copy()) + +def test_prepared_request_complete_copy(): + p = PreparedRequest() + p.prepare( + method='GET', + url='http://www.example.com', + data='foo=bar', + hooks=default_hooks(), + cookies={'foo': 'bar'} + ) + assert_copy(p, p.copy()) if __name__ == '__main__': unittest.main() From 702283093a9a39325cc1825b2376d88b0c5cdfd2 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 30 May 2014 13:36:18 -0400 Subject: [PATCH 123/545] v2.3.1 changelog --- HISTORY.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 81d120aa..aeff9468 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,11 @@ Release History --------------- +2.3.1 (?) ++++++++++ + +- Allow copying of PreparedRequests without headers/cookies + 2.3.0 (2014-05-16) ++++++++++++++++++ From 22433163b35bb451e4804b8d757ef25d58e1171a Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 7 Jun 2014 09:53:12 +0100 Subject: [PATCH 124/545] Update trivial mentions to RFC 2616. --- docs/user/quickstart.rst | 4 ++-- requests/sessions.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index f4273610..ddf72da8 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -325,8 +325,8 @@ We can view the server's response headers using a Python dictionary:: } The dictionary is special, though: it's made just for HTTP headers. According to -`RFC 2616 `_, HTTP -Headers are case-insensitive. +`RFC 7230 `_, HTTP Header names +are case-insensitive. So, we can access the headers using any capitalization we want:: diff --git a/requests/sessions.py b/requests/sessions.py index df85a25c..4ec3d14f 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -118,7 +118,7 @@ class SessionRedirectMixin(object): parsed = urlparse(url) url = parsed.geturl() - # Facilitate non-RFC2616-compliant 'location' headers + # Facilitate relative 'location' headers, as allowed by RFC 7231. # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') # Compliant with RFC3986, we percent encode the url. if not urlparse(url).netloc: @@ -128,7 +128,7 @@ class SessionRedirectMixin(object): prepared_request.url = to_native_string(url) - # http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4 + # http://tools.ietf.org/html/rfc7231#section-6.4.4 if (resp.status_code == codes.see_other and method != 'HEAD'): method = 'GET' From d977ee8a2494903178f5b63756b65fc4dedb3715 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sun, 8 Jun 2014 10:20:32 +0100 Subject: [PATCH 125/545] Force basic auth strings to native string type --- requests/auth.py | 8 ++++++-- test_requests.py | 9 +++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/requests/auth.py b/requests/auth.py index 9f831b7a..84cd9c8c 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -16,7 +16,7 @@ from base64 import b64encode from .compat import urlparse, str from .cookies import extract_cookies_to_jar -from .utils import parse_dict_header +from .utils import parse_dict_header, to_native_string CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' CONTENT_TYPE_MULTI_PART = 'multipart/form-data' @@ -25,7 +25,11 @@ CONTENT_TYPE_MULTI_PART = 'multipart/form-data' def _basic_auth_str(username, password): """Returns a Basic Auth string.""" - return 'Basic ' + b64encode(('%s:%s' % (username, password)).encode('latin1')).strip().decode('latin1') + authstr = 'Basic ' + b64encode( + ('%s:%s' % (username, password)).encode('latin1') + ).strip() + + return to_native_string(authstr, encoding='latin1') class AuthBase(object): diff --git a/test_requests.py b/test_requests.py index f3c93723..0866fccb 100755 --- a/test_requests.py +++ b/test_requests.py @@ -14,9 +14,9 @@ import io import requests import pytest from requests.adapters import HTTPAdapter -from requests.auth import HTTPDigestAuth +from requests.auth import HTTPDigestAuth, _basic_auth_str from requests.compat import ( - Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3) + Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3, builtin_str) from requests.cookies import cookiejar_from_dict, morsel_to_cookie from requests.exceptions import InvalidURL, MissingSchema from requests.models import PreparedRequest, Response @@ -964,6 +964,11 @@ class RequestsTestCase(unittest.TestCase): self._patch_adapter_gzipped_redirect(s, url) s.get(url) + def test_basic_auth_str_is_always_native(self): + s = _basic_auth_str("test", "test") + assert isinstance(s, builtin_str) + assert s == "Basic dGVzdDp0ZXN0" + class TestContentEncodingDetection(unittest.TestCase): From 2b8353d3b3e533945bb82d6510ed2dfb15663045 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sun, 8 Jun 2014 10:28:26 +0100 Subject: [PATCH 126/545] Avoid unnecessary encode/decode cycles. --- requests/auth.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requests/auth.py b/requests/auth.py index 84cd9c8c..9b6426dc 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -25,11 +25,11 @@ CONTENT_TYPE_MULTI_PART = 'multipart/form-data' def _basic_auth_str(username, password): """Returns a Basic Auth string.""" - authstr = 'Basic ' + b64encode( - ('%s:%s' % (username, password)).encode('latin1') - ).strip() + authstr = 'Basic ' + to_native_string( + b64encode(('%s:%s' % (username, password)).encode('latin1')).strip() + ) - return to_native_string(authstr, encoding='latin1') + return authstr class AuthBase(object): From 046302c0cbe94588cdaf6460dfc49219b49db389 Mon Sep 17 00:00:00 2001 From: Ben Bass Date: Sun, 8 Jun 2014 15:23:37 +0100 Subject: [PATCH 127/545] indentation change for code review --- requests/adapters.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index da7d7b11..0e20bd1c 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -135,12 +135,12 @@ class HTTPAdapter(BaseAdapter): if not proxy in self.proxy_manager: proxy_headers = self.proxy_headers(proxy) self.proxy_manager[proxy] = proxy_from_url( - proxy, - proxy_headers=proxy_headers, - num_pools=self._pool_connections, - maxsize=self._pool_maxsize, - block=self._pool_block, - **proxy_kwargs) + proxy, + proxy_headers=proxy_headers, + num_pools=self._pool_connections, + maxsize=self._pool_maxsize, + block=self._pool_block, + **proxy_kwargs) return self.proxy_manager[proxy] From e2c4da7dd2f4a50bf8b386724c7b6f016b10504e Mon Sep 17 00:00:00 2001 From: Jeff Knupp Date: Tue, 10 Jun 2014 09:06:22 -0400 Subject: [PATCH 128/545] various pep-8 cleanups; remove unsused imports; remove unused variables --- test_requests.py | 119 ++++++++++++++++++++++++++++------------------- 1 file changed, 71 insertions(+), 48 deletions(-) diff --git a/test_requests.py b/test_requests.py index 0866fccb..ffb88907 100755 --- a/test_requests.py +++ b/test_requests.py @@ -19,10 +19,10 @@ from requests.compat import ( Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3, builtin_str) from requests.cookies import cookiejar_from_dict, morsel_to_cookie from requests.exceptions import InvalidURL, MissingSchema -from requests.models import PreparedRequest, Response +from requests.models import PreparedRequest from requests.structures import CaseInsensitiveDict from requests.sessions import SessionRedirectMixin -from requests.models import PreparedRequest, urlencode +from requests.models import urlencode from requests.hooks import default_hooks try: @@ -153,7 +153,7 @@ class RequestsTestCase(unittest.TestCase): def test_set_cookie_on_301(self): s = requests.session() url = httpbin('cookies/set?foo=bar') - r = s.get(url) + s.get(url) assert s.cookies['foo'] == 'bar' def test_cookie_sent_on_redirect(self): @@ -213,7 +213,7 @@ class RequestsTestCase(unittest.TestCase): def test_param_cookiejar_works(self): cj = cookielib.CookieJar() - cookiejar_from_dict({'foo' : 'bar'}, cj) + cookiejar_from_dict({'foo': 'bar'}, cj) s = requests.session() r = s.get(httpbin('cookies'), cookies=cj) # Make sure the cookie was sent @@ -393,7 +393,7 @@ class RequestsTestCase(unittest.TestCase): assert post4.status_code == 200 with pytest.raises(ValueError): - requests.post(url, files = ['bad file data']) + requests.post(url, files=['bad file data']) def test_POSTBIN_GET_POST_FILES_WITH_DATA(self): @@ -404,14 +404,15 @@ class RequestsTestCase(unittest.TestCase): assert post1.status_code == 200 with open('requirements.txt') as f: - post2 = requests.post(url, data={'some': 'data'}, files={'some': f}) + post2 = requests.post( + url, data={'some': 'data'}, files={'some': f}) assert post2.status_code == 200 post4 = requests.post(url, data='[{"some": "json"}]') assert post4.status_code == 200 with pytest.raises(ValueError): - requests.post(url, files = ['bad file data']) + requests.post(url, files=['bad file data']) def test_conflicting_post_params(self): url = httpbin('post') @@ -444,7 +445,10 @@ class RequestsTestCase(unittest.TestCase): requests.get(httpbin('ø'), params={'foo': 'foo'}) def test_unicode_header_name(self): - requests.put(httpbin('put'), headers={str('Content-Type'): 'application/octet-stream'}, data='\xff') # compat.str is unicode. + requests.put( + httpbin('put'), + headers={str('Content-Type'): 'application/octet-stream'}, + data='\xff') # compat.str is unicode. def test_pyopenssl_redirect(self): requests.get('https://httpbin.org/status/301') @@ -456,31 +460,36 @@ class RequestsTestCase(unittest.TestCase): assert r.url == httpbin('get?test=foo&test=baz') def test_different_encodings_dont_break_post(self): - r = requests.post(httpbin('post'), - data={'stuff': json.dumps({'a': 123})}, - params={'blah': 'asdf1234'}, - files={'file': ('test_requests.py', open(__file__, 'rb'))}) + r = requests.post( + httpbin('post'), + data={'stuff': json.dumps({'a': 123})}, + params={'blah': 'asdf1234'}, + files={'file': ('test_requests.py', open(__file__, 'rb'))}) assert r.status_code == 200 def test_unicode_multipart_post(self): - r = requests.post(httpbin('post'), - data={'stuff': u('ëlïxr')}, - files={'file': ('test_requests.py', open(__file__, 'rb'))}) + r = requests.post( + httpbin('post'), + data={'stuff': u('ëlïxr')}, + files={'file': ('test_requests.py', open(__file__, 'rb'))}) assert r.status_code == 200 - r = requests.post(httpbin('post'), - data={'stuff': u('ëlïxr').encode('utf-8')}, - files={'file': ('test_requests.py', open(__file__, 'rb'))}) + r = requests.post( + httpbin('post'), + data={'stuff': u('ëlïxr').encode('utf-8')}, + files={'file': ('test_requests.py', open(__file__, 'rb'))}) assert r.status_code == 200 - r = requests.post(httpbin('post'), - data={'stuff': 'elixr'}, - files={'file': ('test_requests.py', open(__file__, 'rb'))}) + r = requests.post( + httpbin('post'), + data={'stuff': 'elixr'}, + files={'file': ('test_requests.py', open(__file__, 'rb'))}) assert r.status_code == 200 - r = requests.post(httpbin('post'), - data={'stuff': 'elixr'.encode('utf-8')}, - files={'file': ('test_requests.py', open(__file__, 'rb'))}) + r = requests.post( + httpbin('post'), + data={'stuff': 'elixr'.encode('utf-8')}, + files={'file': ('test_requests.py', open(__file__, 'rb'))}) assert r.status_code == 200 def test_unicode_multipart_post_fieldnames(self): @@ -496,15 +505,17 @@ class RequestsTestCase(unittest.TestCase): def test_unicode_method_name(self): files = {'file': open('test_requests.py', 'rb')} - r = requests.request(method=u('POST'), url=httpbin('post'), files=files) + r = requests.request( + method=u('POST'), url=httpbin('post'), files=files) assert r.status_code == 200 def test_custom_content_type(self): - r = requests.post(httpbin('post'), - data={'stuff': json.dumps({'a': 123})}, - files={'file1': ('test_requests.py', open(__file__, 'rb')), - 'file2': ('test_requests', open(__file__, 'rb'), - 'text/py-content-type')}) + r = requests.post( + httpbin('post'), + data={'stuff': json.dumps({'a': 123})}, + files={'file1': ('test_requests.py', open(__file__, 'rb')), + 'file2': ('test_requests', open(__file__, 'rb'), + 'text/py-content-type')}) assert r.status_code == 200 assert b"text/py-content-type" in r.request.body @@ -563,7 +574,8 @@ class RequestsTestCase(unittest.TestCase): prep = s.prepare_request(req) resp = s.send(prep) - assert resp.json()['headers']['Dummy-Auth-Test'] == 'dummy-auth-test-ok' + assert resp.json()['headers'][ + 'Dummy-Auth-Test'] == 'dummy-auth-test-ok' def test_links(self): r = requests.Response() @@ -694,7 +706,6 @@ class RequestsTestCase(unittest.TestCase): # make sure one can use items multiple times assert list(items) == list(items) - def test_time_elapsed_blank(self): r = requests.get(httpbin('get')) td = r.elapsed @@ -862,7 +873,6 @@ class RequestsTestCase(unittest.TestCase): r = s.get(httpbin('get'), params={'FOO': 'bar'}) assert r.json()['args'] == {'foo': 'bar', 'FOO': 'bar'} - def test_long_authinfo_in_url(self): url = 'http://{0}:{1}@{2}:9000/path?query#frag'.format( 'E8A3BE87-9E3F-4620-8858-95478E385B5B', @@ -1017,7 +1027,7 @@ class TestContentEncodingDetection(unittest.TestCase): class TestCaseInsensitiveDict(unittest.TestCase): def test_mapping_init(self): - cid = CaseInsensitiveDict({'Foo': 'foo','BAr': 'bar'}) + cid = CaseInsensitiveDict({'Foo': 'foo', 'BAr': 'bar'}) assert len(cid) == 2 assert 'foo' in cid assert 'bar' in cid @@ -1091,7 +1101,7 @@ class TestCaseInsensitiveDict(unittest.TestCase): cid['spam'] = 'blueval' cid.update({'sPam': 'notblueval'}) assert cid['spam'] == 'notblueval' - cid = CaseInsensitiveDict({'Foo': 'foo','BAr': 'bar'}) + cid = CaseInsensitiveDict({'Foo': 'foo', 'BAr': 'bar'}) cid.update({'fOO': 'anotherfoo', 'bAR': 'anotherbar'}) assert len(cid) == 2 assert cid['foo'] == 'anotherfoo' @@ -1161,20 +1171,24 @@ class UtilsTestCase(unittest.TestCase): from requests.utils import super_len assert super_len(StringIO.StringIO()) == 0 - assert super_len(StringIO.StringIO('with so much drama in the LBC')) == 29 + assert super_len( + StringIO.StringIO('with so much drama in the LBC')) == 29 assert super_len(BytesIO()) == 0 - assert super_len(BytesIO(b"it's kinda hard bein' snoop d-o-double-g")) == 40 + assert super_len( + BytesIO(b"it's kinda hard bein' snoop d-o-double-g")) == 40 try: import cStringIO except ImportError: pass else: - assert super_len(cStringIO.StringIO('but some how, some way...')) == 25 + assert super_len( + cStringIO.StringIO('but some how, some way...')) == 25 def test_get_environ_proxies_ip_ranges(self): - """ Ensures that IP addresses are correctly matches with ranges in no_proxy variable """ + """Ensures that IP addresses are correctly matches with ranges + in no_proxy variable.""" from requests.utils import get_environ_proxies os.environ['no_proxy'] = "192.168.0.0/24,127.0.0.1,localhost.localdomain,172.16.1.1" assert get_environ_proxies('http://192.168.0.1:5000/') == {} @@ -1185,10 +1199,12 @@ class UtilsTestCase(unittest.TestCase): assert get_environ_proxies('http://192.168.1.1/') != {} def test_get_environ_proxies(self): - """ Ensures that IP addresses are correctly matches with ranges in no_proxy variable """ + """Ensures that IP addresses are correctly matches with ranges + in no_proxy variable.""" from requests.utils import get_environ_proxies os.environ['no_proxy'] = "127.0.0.1,localhost.localdomain,192.168.0.0/24,172.16.1.1" - assert get_environ_proxies('http://localhost.localdomain:5000/v1.0/') == {} + assert get_environ_proxies( + 'http://localhost.localdomain:5000/v1.0/') == {} assert get_environ_proxies('http://www.requests.com/') != {} def test_is_ipv4_address(self): @@ -1214,12 +1230,15 @@ class UtilsTestCase(unittest.TestCase): assert not address_in_network('172.16.0.1', '192.168.1.0/24') def test_get_auth_from_url(self): - """ Ensures that username and password in well-encoded URI as per RFC 3986 are correclty extracted """ + """Ensures that username and password in well-encoded URI as per + RFC 3986 are correclty extracted.""" from requests.utils import get_auth_from_url from requests.compat import quote percent_encoding_test_chars = "%!*'();:@&=+$,/?#[] " url_address = "request.com/url.html#test" - url = "http://" + quote(percent_encoding_test_chars, '') + ':' + quote(percent_encoding_test_chars, '') + '@' + url_address + url = "http://" + quote( + percent_encoding_test_chars, '') + ':' + quote( + percent_encoding_test_chars, '') + '@' + url_address (username, password) = get_auth_from_url(url) assert username == percent_encoding_test_chars assert password == percent_encoding_test_chars @@ -1286,7 +1305,7 @@ class TestMorselToCookieMaxAge(unittest.TestCase): class TestTimeout: def test_stream_timeout(self): try: - r = requests.get('https://httpbin.org/delay/10', timeout=5.0) + requests.get('https://httpbin.org/delay/10', timeout=5.0) except requests.exceptions.Timeout as e: assert 'Read timed out' in e.args[0].args[0] @@ -1353,10 +1372,10 @@ class TestRedirects: @pytest.fixture def list_of_tuples(): return [ - (('a', 'b'), ('c', 'd')), - (('c', 'd'), ('a', 'b')), - (('a', 'b'), ('c', 'd'), ('e', 'f')), - ] + (('a', 'b'), ('c', 'd')), + (('c', 'd'), ('a', 'b')), + (('a', 'b'), ('c', 'd'), ('e', 'f')), + ] def test_data_argument_accepts_tuples(list_of_tuples): @@ -1374,14 +1393,17 @@ def test_data_argument_accepts_tuples(list_of_tuples): ) assert p.body == urlencode(data) + def assert_copy(p, p_copy): for attr in ('method', 'url', 'headers', '_cookies', 'body', 'hooks'): assert getattr(p, attr) == getattr(p_copy, attr) + def test_prepared_request_empty_copy(): p = PreparedRequest() assert_copy(p, p.copy()) + def test_prepared_request_no_cookies_copy(): p = PreparedRequest() p.prepare( @@ -1392,6 +1414,7 @@ def test_prepared_request_no_cookies_copy(): ) assert_copy(p, p.copy()) + def test_prepared_request_complete_copy(): p = PreparedRequest() p.prepare( From bff65f42eebe58f5f76c7b021369698bee4642f7 Mon Sep 17 00:00:00 2001 From: Jeff Knupp Date: Tue, 10 Jun 2014 11:16:24 -0400 Subject: [PATCH 129/545] Update with style considerations by @kennethreitz --- test_requests.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/test_requests.py b/test_requests.py index ffb88907..283353b9 100755 --- a/test_requests.py +++ b/test_requests.py @@ -404,8 +404,8 @@ class RequestsTestCase(unittest.TestCase): assert post1.status_code == 200 with open('requirements.txt') as f: - post2 = requests.post( - url, data={'some': 'data'}, files={'some': f}) + post2 = requests.post(url, + data={'some': 'data'}, files={'some': f}) assert post2.status_code == 200 post4 = requests.post(url, data='[{"some": "json"}]') @@ -460,34 +460,29 @@ class RequestsTestCase(unittest.TestCase): assert r.url == httpbin('get?test=foo&test=baz') def test_different_encodings_dont_break_post(self): - r = requests.post( - httpbin('post'), + r = requests.post(httpbin('post'), data={'stuff': json.dumps({'a': 123})}, params={'blah': 'asdf1234'}, files={'file': ('test_requests.py', open(__file__, 'rb'))}) assert r.status_code == 200 def test_unicode_multipart_post(self): - r = requests.post( - httpbin('post'), + r = requests.post(httpbin('post'), data={'stuff': u('ëlïxr')}, files={'file': ('test_requests.py', open(__file__, 'rb'))}) assert r.status_code == 200 - r = requests.post( - httpbin('post'), + r = requests.post(httpbin('post'), data={'stuff': u('ëlïxr').encode('utf-8')}, files={'file': ('test_requests.py', open(__file__, 'rb'))}) assert r.status_code == 200 - r = requests.post( - httpbin('post'), + r = requests.post(httpbin('post'), data={'stuff': 'elixr'}, files={'file': ('test_requests.py', open(__file__, 'rb'))}) assert r.status_code == 200 - r = requests.post( - httpbin('post'), + r = requests.post(httpbin('post'), data={'stuff': 'elixr'.encode('utf-8')}, files={'file': ('test_requests.py', open(__file__, 'rb'))}) assert r.status_code == 200 From 07d9b730b712e8d77106c5c886be4e8bf3bef4f6 Mon Sep 17 00:00:00 2001 From: Eric L Frederich Date: Tue, 10 Jun 2014 16:30:17 -0400 Subject: [PATCH 130/545] handle 308 redirection the same as 301 and 302 --- requests/models.py | 1 + 1 file changed, 1 insertion(+) diff --git a/requests/models.py b/requests/models.py index 5aad8ce4..7bcebfdc 100644 --- a/requests/models.py +++ b/requests/models.py @@ -39,6 +39,7 @@ REDIRECT_STATI = ( codes.found, # 302 codes.other, # 303 codes.temporary_moved, # 307 + codes.resume, # 308 ) DEFAULT_REDIRECT_LIMIT = 30 CONTENT_CHUNK_SIZE = 10 * 1024 From ec3a2e6f04a6e268cbae747e9f1ff6ea4217f8b2 Mon Sep 17 00:00:00 2001 From: Eric L Frederich Date: Wed, 11 Jun 2014 11:08:10 -0400 Subject: [PATCH 131/545] rename resume/resume_incomplete to permanent_redirect according to rfc7238 Use temporary_redirect intead of temporary_moved to make the words line up nicely ;-) --- requests/models.py | 10 +++++----- requests/sessions.py | 2 +- requests/status_codes.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/requests/models.py b/requests/models.py index 7bcebfdc..b43901b7 100644 --- a/requests/models.py +++ b/requests/models.py @@ -35,11 +35,11 @@ from .status_codes import codes #: The set of HTTP status codes that indicate an automatically #: processable redirect. REDIRECT_STATI = ( - codes.moved, # 301 - codes.found, # 302 - codes.other, # 303 - codes.temporary_moved, # 307 - codes.resume, # 308 + codes.moved, # 301 + codes.found, # 302 + codes.other, # 303 + codes.temporary_redirect, # 307 + codes.permanent_redirect, # 308 ) DEFAULT_REDIRECT_LIMIT = 30 CONTENT_CHUNK_SIZE = 10 * 1024 diff --git a/requests/sessions.py b/requests/sessions.py index 4ec3d14f..5827d917 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -146,7 +146,7 @@ class SessionRedirectMixin(object): prepared_request.method = method # https://github.com/kennethreitz/requests/issues/1084 - if resp.status_code not in (codes.temporary, codes.resume): + if resp.status_code not in (codes.temporary_redirect, codes.permanent_redirect): if 'Content-Length' in prepared_request.headers: del prepared_request.headers['Content-Length'] diff --git a/requests/status_codes.py b/requests/status_codes.py index ed7a8660..9a431dd6 100644 --- a/requests/status_codes.py +++ b/requests/status_codes.py @@ -30,7 +30,7 @@ _codes = { 305: ('use_proxy',), 306: ('switch_proxy',), 307: ('temporary_redirect', 'temporary_moved', 'temporary'), - 308: ('resume_incomplete', 'resume'), + 308: ('permanent_redirect',), # Client Error. 400: ('bad_request', 'bad'), From da3178f377b34230792c70a48cc992500578316f Mon Sep 17 00:00:00 2001 From: Eric L Frederich Date: Wed, 11 Jun 2014 13:27:36 -0400 Subject: [PATCH 132/545] Re-add resume to not break existing apps as requested by Cory Benfield --- requests/status_codes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requests/status_codes.py b/requests/status_codes.py index 9a431dd6..e0887f21 100644 --- a/requests/status_codes.py +++ b/requests/status_codes.py @@ -30,7 +30,8 @@ _codes = { 305: ('use_proxy',), 306: ('switch_proxy',), 307: ('temporary_redirect', 'temporary_moved', 'temporary'), - 308: ('permanent_redirect',), + 308: ('permanent_redirect', + 'resume_incomplete', 'resume',), # These 2 to be removed in 3.0 # Client Error. 400: ('bad_request', 'bad'), From b80ed77cddc2cb9d75c9e6d4f14b1eed9e5fb227 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 11 Jun 2014 16:40:23 -0400 Subject: [PATCH 133/545] Update index.rst --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 52168a63..654c3dbd 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -38,7 +38,7 @@ Requests takes all of the work out of Python HTTP/1.1 — making your integrati Testimonials ------------ -Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku, PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington Post, Twitter, SoundCloud, Kippt, Readability, and Federal US Institutions use Requests internally. It has been downloaded over 12,000,000 times from PyPI. +Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku, PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington Post, Twitter, SoundCloud, Kippt, Readability, and Federal US Institutions use Requests internally. It has been downloaded over 16,000,000 times from PyPI. **Armin Ronacher** Requests is the perfect example how beautiful an API can be with the From c3444f8c7abdda2642f59109d6961bdf8511f38c Mon Sep 17 00:00:00 2001 From: Eric L Frederich Date: Thu, 12 Jun 2014 08:25:21 -0400 Subject: [PATCH 134/545] don't make duplicate requests for permanent redirects --- requests/models.py | 5 +++++ requests/sessions.py | 10 +++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index b43901b7..0dc55568 100644 --- a/requests/models.py +++ b/requests/models.py @@ -610,6 +610,11 @@ class Response(object): """ return ('location' in self.headers and self.status_code in REDIRECT_STATI) + @property + def is_permanent_redirect(self): + """True if this Response one of the permanant versions of redirect""" + return ('location' in self.headers and self.status_code in (codes.moved_permanently, codes.permanent_redirect)) + @property def apparent_encoding(self): """The apparent encoding, provided by the chardet library""" diff --git a/requests/sessions.py b/requests/sessions.py index 5827d917..a263138a 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -127,6 +127,9 @@ class SessionRedirectMixin(object): url = requote_uri(url) prepared_request.url = to_native_string(url) + # cache the url + if resp.is_permanent_redirect: + self.redirect_cache[req.url] = prepared_request.url # http://tools.ietf.org/html/rfc7231#section-6.4.4 if (resp.status_code == codes.see_other and @@ -263,7 +266,7 @@ class Session(SessionRedirectMixin): __attrs__ = [ 'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks', 'params', 'verify', 'cert', 'prefetch', 'adapters', 'stream', - 'trust_env', 'max_redirects'] + 'trust_env', 'max_redirects', 'redirect_cache'] def __init__(self): @@ -316,6 +319,8 @@ class Session(SessionRedirectMixin): self.mount('https://', HTTPAdapter()) self.mount('http://', HTTPAdapter()) + self.redirect_cache = {} + def __enter__(self): return self @@ -540,6 +545,9 @@ class Session(SessionRedirectMixin): if not isinstance(request, PreparedRequest): raise ValueError('You can only send PreparedRequests.') + while request.url in self.redirect_cache: + request.url = self.redirect_cache.get(request.url) + # Set up variables needed for resolve_redirects and dispatching of hooks allow_redirects = kwargs.pop('allow_redirects', True) stream = kwargs.get('stream') From c451dd2c773402e5bdc1b41c3c23d31b5092bd86 Mon Sep 17 00:00:00 2001 From: Benjamin Kerensa Date: Sun, 15 Jun 2014 00:10:09 -0700 Subject: [PATCH 135/545] Update out-there.rst Remove french blog post which is now a deadlink. --- docs/community/out-there.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/community/out-there.rst b/docs/community/out-there.rst index fe821692..4b265cb0 100644 --- a/docs/community/out-there.rst +++ b/docs/community/out-there.rst @@ -30,4 +30,3 @@ Articles & Talks - `Issac Kelly's 'Consuming Web APIs' talk `_ - `Blog post about Requests via Yum `_ - `Russian blog post introducing Requests `_ -- `French blog post introducing Requests `_ From 1bf4af63952227b74a583b5ec5445f130af9d163 Mon Sep 17 00:00:00 2001 From: np-csu Date: Sun, 29 Jun 2014 21:05:04 +0800 Subject: [PATCH 136/545] Update quickstart.rst line 394: correct an input error. --- docs/user/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index ddf72da8..53ee6f6e 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -391,7 +391,7 @@ redirection handling with the ``allow_redirects`` parameter:: If you're using HEAD, you can enable redirection as well:: - >>> r = requests.post('http://github.com', allow_redirects=True) + >>> r = requests.head('http://github.com', allow_redirects=True) >>> r.url 'https://github.com/' >>> r.history From 84f67379c8844ff109a200caa3f3e815fdf0beee Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 2 Jul 2014 10:26:15 -0500 Subject: [PATCH 137/545] Update how we check verify when inspecting env variables --- requests/sessions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index a263138a..96fff637 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -436,11 +436,11 @@ class Session(SessionRedirectMixin): proxies.setdefault(k, v) # Look for configuration. - if not verify and verify is not False: + if verify is True or (verify is None and verify is not False): verify = os.environ.get('REQUESTS_CA_BUNDLE') # Curl compatibility. - if not verify and verify is not False: + if verify is True or (verify is None and verify is not False): verify = os.environ.get('CURL_CA_BUNDLE') # Merge all the kwargs. From d25280dc20d93446f1f66b069932c0c7adaf6515 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 2 Jul 2014 10:34:25 -0500 Subject: [PATCH 138/545] Replace dead link with StackOverflow answer --- docs/user/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/install.rst b/docs/user/install.rst index d890fe81..be821e3c 100644 --- a/docs/user/install.rst +++ b/docs/user/install.rst @@ -18,7 +18,7 @@ or, with `easy_install `_:: $ easy_install requests -But, you really `shouldn't do that `_. +But, you really `shouldn't do that `_. Get the Code From 3957b0befb063ecea1c9cb3dc67b2c385ddad58e Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 2 Jul 2014 10:42:00 -0500 Subject: [PATCH 139/545] Fix silly check --- requests/sessions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index 96fff637..4c6fa2f2 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -436,11 +436,11 @@ class Session(SessionRedirectMixin): proxies.setdefault(k, v) # Look for configuration. - if verify is True or (verify is None and verify is not False): + if verify is True or verify is None: verify = os.environ.get('REQUESTS_CA_BUNDLE') # Curl compatibility. - if verify is True or (verify is None and verify is not False): + if verify is True or verify is None: verify = os.environ.get('CURL_CA_BUNDLE') # Merge all the kwargs. From 291bc9ea079bb554d57802e79d792e8a21d5dff5 Mon Sep 17 00:00:00 2001 From: Espartaco Palma Date: Fri, 4 Jul 2014 11:42:56 -0500 Subject: [PATCH 140/545] Update link to pip site on docs/user/install.rst --- docs/user/install.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/user/install.rst b/docs/user/install.rst index be821e3c..5f0ef9c4 100644 --- a/docs/user/install.rst +++ b/docs/user/install.rst @@ -10,7 +10,8 @@ The first step to using any software package is getting it properly installed. Distribute & Pip ---------------- -Installing Requests is simple with `pip `_, just run this in your terminal:: +Installing Requests is simple with `pip `_, just run +this in your terminal:: $ pip install requests From 7a51337ce0c939a2b9232c86e434c4478dbd1949 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 10 Jul 2014 21:49:16 +0100 Subject: [PATCH 141/545] Preferentially use certifi to requests. --- requests/certs.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/requests/certs.py b/requests/certs.py index bc008261..07e64750 100644 --- a/requests/certs.py +++ b/requests/certs.py @@ -11,14 +11,15 @@ If you are packaging Requests, e.g., for a Linux distribution or a managed environment, you can change the definition of where() to return a separately packaged CA bundle. """ - import os.path - -def where(): - """Return the preferred certificate bundle.""" - # vendored bundle inside Requests - return os.path.join(os.path.dirname(__file__), 'cacert.pem') +try: + from certifi import where +except ImportError: + def where(): + """Return the preferred certificate bundle.""" + # vendored bundle inside Requests + return os.path.join(os.path.dirname(__file__), 'cacert.pem') if __name__ == '__main__': print(where()) From 52facc7984bb2cdd3e985c429d423f8cc6da632e Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 10 Jul 2014 21:49:31 +0100 Subject: [PATCH 142/545] Install certifi with requests. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index c25b3680..316cb9b1 100755 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ packages = [ 'requests.packages.urllib3.packages.ssl_match_hostname', ] -requires = [] +requires = ['certifi'] with open('README.rst') as f: readme = f.read() From 36b6c3749694d0d76282a12d69a36cdaf562b792 Mon Sep 17 00:00:00 2001 From: Tshepang Lekhonkhobe Date: Mon, 14 Jul 2014 16:13:23 +0200 Subject: [PATCH 143/545] Fix broken link --- docs/user/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index f384f158..6a390635 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -661,7 +661,7 @@ SSLv3: block=block, ssl_version=ssl.PROTOCOL_SSLv3) -.. _`described here`: http://kennethreitz.org/exposures/the-future-of-python-http +.. _`described here`: http://www.kennethreitz.org/essays/the-future-of-python-http .. _`urllib3`: https://github.com/shazow/urllib3 Blocking Or Non-Blocking? From d9ea1aaeb0854ff48f3421153ec71fd445226755 Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Tue, 15 Jul 2014 17:45:09 +0900 Subject: [PATCH 144/545] Documentation: Wrap lines in index.rst Wrap lines at 80 characters where possible. Change-Id: Ib254734a7a18b62f965577c368929058258b489f --- docs/index.rst | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 654c3dbd..5f412854 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -8,11 +8,14 @@ Requests: HTTP for Humans Release v\ |version|. (:ref:`Installation `) -Requests is an :ref:`Apache2 Licensed ` HTTP library, written in Python, for human beings. +Requests is an :ref:`Apache2 Licensed ` HTTP library, written in +Python, for human beings. Python's standard **urllib2** module provides most of the HTTP capabilities you need, but the API is thoroughly **broken**. -It was built for a different time — and a different web. It requires an *enormous* amount of work (even method overrides) to perform the simplest of tasks. +It was built for a different time — and a different web. It requires an +*enormous* amount of work (even method overrides) to perform the simplest of +tasks. Things shouldn’t be this way. Not in Python. @@ -32,13 +35,20 @@ Things shouldn’t be this way. Not in Python. See `similar code, without Requests `_. -Requests takes all of the work out of Python HTTP/1.1 — making your integration with web services seamless. There's no need to manually add query strings to your URLs, or to form-encode your POST data. Keep-alive and HTTP connection pooling are 100% automatic, powered by `urllib3 `_, which is embedded within Requests. +Requests takes all of the work out of Python HTTP/1.1 — making your integration +with web services seamless. There's no need to manually add query strings to +your URLs, or to form-encode your POST data. Keep-alive and HTTP connection +pooling are 100% automatic, powered by `urllib3 `_, +which is embedded within Requests. Testimonials ------------ -Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku, PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington Post, Twitter, SoundCloud, Kippt, Readability, and Federal US Institutions use Requests internally. It has been downloaded over 16,000,000 times from PyPI. +Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku, +PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington +Post, Twitter, SoundCloud, Kippt, Readability, and Federal US Institutions use +Requests internally. It has been downloaded over 16,000,000 times from PyPI. **Armin Ronacher** Requests is the perfect example how beautiful an API can be with the From 504b960fb4b890a09d056ae70509aa8ecddb4ec1 Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Tue, 15 Jul 2014 17:48:23 +0900 Subject: [PATCH 145/545] Documentation: Add Sony to list of users Change-Id: I0568f27562e5d12f7e5e528b216ca1b1e9cd8256 --- docs/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 5f412854..df48ab5e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -47,8 +47,8 @@ Testimonials Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku, PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington -Post, Twitter, SoundCloud, Kippt, Readability, and Federal US Institutions use -Requests internally. It has been downloaded over 16,000,000 times from PyPI. +Post, Twitter, SoundCloud, Kippt, Readability, Sony, and Federal US Institutions +use Requests internally. It has been downloaded over 16,000,000 times from PyPI. **Armin Ronacher** Requests is the perfect example how beautiful an API can be with the From cde5a4a5d50ba967813e1bb35041eaa5f7dd2264 Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Tue, 15 Jul 2014 18:23:52 +0900 Subject: [PATCH 146/545] Add Timeout exception in API documentation The Timeout exception is referenced from quickstart.rst but does not get hyperlinked. Change-Id: I69ad881bf9d2475c6239ac7b17f68ddbce0d03cd --- docs/api.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/api.rst b/docs/api.rst index 86061be9..5e22814a 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -53,6 +53,7 @@ Exceptions .. autoexception:: requests.exceptions.HTTPError .. autoexception:: requests.exceptions.URLRequired .. autoexception:: requests.exceptions.TooManyRedirects +.. autoexception:: requests.exceptions.Timeout Status Code Lookup From f5a2138e8ce658d7bf3883c14805589218b06a5f Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Tue, 15 Jul 2014 18:26:30 +0900 Subject: [PATCH 147/545] Minor tidy-ups in quickstart documentation Change-Id: Ia862c17707a2a76182c549db88821636e8402729 --- docs/user/quickstart.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 53ee6f6e..28e44977 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -340,7 +340,7 @@ So, we can access the headers using any capitalization we want:: Cookies ------- -If a response contains some Cookies, you can get quick access to them:: +If a response contains some Cookies, you can quickly access them:: >>> url = 'http://example.com/some/cookie/setting/url' >>> r = requests.get(url) @@ -424,7 +424,7 @@ Errors and Exceptions In the event of a network problem (e.g. DNS failure, refused connection, etc), Requests will raise a :class:`~requests.exceptions.ConnectionError` exception. -In the event of the rare invalid HTTP response, Requests will raise an +In the rare event of an invalid HTTP response, Requests will raise an :class:`~requests.exceptions.HTTPError` exception. If a request times out, a :class:`~requests.exceptions.Timeout` exception is From a8a4c9828aea6f5159cddc2786d4a74426f9dd4f Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Tue, 15 Jul 2014 18:26:51 +0900 Subject: [PATCH 148/545] Restructure "Redirection and History" section in quickstart documentation Change-Id: Ida38d551e8cd0edaeb92d09de411a6f4a3d0578b --- docs/user/quickstart.rst | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 28e44977..128c5780 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -362,11 +362,16 @@ parameter:: Redirection and History ----------------------- -Requests will automatically perform location redirection for all verbs except +By default Requests will perform location redirection for all verbs except HEAD. -GitHub redirects all HTTP requests to HTTPS. We can use the ``history`` method -of the Response object to track redirection. Let's see what GitHub does:: +We can use the ``history`` property of the Response object to track redirection. + +The :meth:`Response.history` list contains the :class:`Request` objects that +were created in order to complete the request. The list is sorted from the +oldest to the most recent request. + +For example, GitHub redirects all HTTP requests to HTTPS:: >>> r = requests.get('http://github.com') >>> r.url @@ -376,9 +381,6 @@ of the Response object to track redirection. Let's see what GitHub does:: >>> r.history [] -The :class:`Response.history` list contains the :class:`Request` objects that -were created in order to complete the request. The list is sorted from the -oldest to the most recent request. If you're using GET, OPTIONS, POST, PUT, PATCH or DELETE, you can disable redirection handling with the ``allow_redirects`` parameter:: From 6dc13f2194b7d367f591954707c05d27ef9c79a2 Mon Sep 17 00:00:00 2001 From: Udi Oron Date: Tue, 15 Jul 2014 19:56:47 +0300 Subject: [PATCH 149/545] removed unneeded parentheses from if clause --- docs/user/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 6a390635..6343e48f 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -423,7 +423,7 @@ like so:: We should confirm that GitHub responded correctly. If it has, we want to work out what type of content it is. Do this like so:: - >>> if (r.status_code == requests.codes.ok): + >>> if r.status_code == requests.codes.ok: ... print r.headers['content-type'] ... application/json; charset=utf-8 From 2a6303e018ab2d0e5925c0afc44d529419bcc9d5 Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Wed, 16 Jul 2014 10:40:16 +0900 Subject: [PATCH 150/545] Docs: Fix :class: and :meth: directives A few instances of the directives were malformed and did not result in hyperlinks in the generated HTML. Change-Id: I94d93de928ee4ff24a48797baf2ac77598a20704 --- docs/user/advanced.rst | 4 ++-- docs/user/quickstart.rst | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 6343e48f..95dd6be6 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -80,7 +80,7 @@ request, and then the request's headers:: Prepared Requests ----------------- -Whenever you receive a :class:`Response ` object +Whenever you receive a :class:`Response ` object from an API call or a Session call, the ``request`` attribute is actually the ``PreparedRequest`` that was used. In some cases you may wish to do some extra work to the body or headers (or anything else really) before sending a @@ -117,7 +117,7 @@ However, the above code will lose some of the advantages of having a Requests :class:`Session ` object. In particular, :class:`Session `-level state such as cookies will not get applied to your request. To get a -:class:`PreparedRequest ` with that state +:class:`PreparedRequest ` with that state applied, replace the call to :meth:`Request.prepare() ` with a call to :meth:`Session.prepare_request() `, like this:: diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 128c5780..6bd82f7d 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -367,9 +367,10 @@ HEAD. We can use the ``history`` property of the Response object to track redirection. -The :meth:`Response.history` list contains the :class:`Request` objects that -were created in order to complete the request. The list is sorted from the -oldest to the most recent request. +The :meth:`Response.history ` list contains the +:class:`Request ` objects that were created in order to +complete the request. The list is sorted from the oldest to the most recent +request. For example, GitHub redirects all HTTP requests to HTTPS:: From f0fb20cd83f770d6f71b8d5e73a89cb13c08f348 Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Wed, 16 Jul 2014 15:13:11 +0900 Subject: [PATCH 151/545] Fix name misspelling Change-Id: Ied3e600e5673eff48558b634b3c2616d13a3337b --- docs/community/out-there.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/community/out-there.rst b/docs/community/out-there.rst index 4b265cb0..235efd69 100644 --- a/docs/community/out-there.rst +++ b/docs/community/out-there.rst @@ -25,7 +25,7 @@ To give it a try, simply:: Articles & Talks ================ - `Python for the Web `_ teaches how to use Python to interact with the web, using Requests. -- `Daniel Greenfield's Review of Requests `_ +- `Daniel Greenfeld's Review of Requests `_ - `My 'Python for Humans' talk `_ ( `audio `_ ) - `Issac Kelly's 'Consuming Web APIs' talk `_ - `Blog post about Requests via Yum `_ From 49ecffb86ee1fd957980581fb990d2d27eb366f6 Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Fri, 18 Jul 2014 15:15:41 +0900 Subject: [PATCH 152/545] Add authentication classes to the API document Change-Id: Ic1464f246602b809f3c29fbafd6f36b747d5d5df --- docs/api.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 5e22814a..42f7c5a0 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -44,6 +44,13 @@ Request Sessions .. autoclass:: requests.adapters.HTTPAdapter :inherited-members: +Authentication +-------------- + +.. autoclass:: requests.auth.AuthBase +.. autoclass:: requests.auth.HTTPBasicAuth +.. autoclass:: requests.auth.HTTPProxyAuth +.. autoclass:: requests.auth.HTTPDigestAuth Exceptions ~~~~~~~~~~ From c6fa5bb1cd06ab6d8206ddb4c07572c77f26b9a3 Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Fri, 18 Jul 2014 15:03:29 +0900 Subject: [PATCH 153/545] Fix a couple more malformed :meth: and :class: links Change-Id: Ie38844a40ec7a483e6ce5e56077be344242bcd99 --- docs/user/authentication.rst | 2 +- docs/user/quickstart.rst | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/user/authentication.rst b/docs/user/authentication.rst index af43bd29..dd0bf2b8 100644 --- a/docs/user/authentication.rst +++ b/docs/user/authentication.rst @@ -99,7 +99,7 @@ If you can't find a good implementation of the form of authentication you want, you can implement it yourself. Requests makes it easy to add your own forms of authentication. -To do so, subclass :class:`requests.auth.AuthBase` and implement the +To do so, subclass :class:`AuthBase ` and implement the ``__call__()`` method:: >>> import requests diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 6bd82f7d..73439edc 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -32,8 +32,8 @@ timeline :: >>> r = requests.get('https://github.com/timeline.json') -Now, we have a :class:`Response` object called ``r``. We can get all the -information we need from this object. +Now, we have a :class:`Request ` object called ``r``. We can +get all the information we need from this object. Requests' simple API means that all forms of HTTP request are as obvious. For example, this is how you make an HTTP POST request:: @@ -286,8 +286,9 @@ reference:: >>> r.status_code == requests.codes.ok True -If we made a bad request (a 4XX client error or 5XX server error response), we can raise it with -:class:`Response.raise_for_status()`:: +If we made a bad request (a 4XX client error or 5XX server error response), we +can raise it with +:meth:`Response.raise_for_status() `:: >>> bad_r = requests.get('http://httpbin.org/status/404') >>> bad_r.status_code From 17852784e8026e779274366dd7b20609f0d397f7 Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Fri, 18 Jul 2014 15:18:20 +0900 Subject: [PATCH 154/545] Remove redundant sentence from quickstart introduction Change-Id: Ic03aa394941367745f3148d299b5313849f77051 --- docs/user/quickstart.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 73439edc..b1fe9344 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -6,8 +6,7 @@ Quickstart .. module:: requests.models Eager to get started? This page gives a good introduction in how to get started -with Requests. This assumes you already have Requests installed. If you do not, -head over to the :ref:`Installation ` section. +with Requests. First, make sure that: From be453ba3c34466abe93f996b7de25a3b88b6fea5 Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Fri, 18 Jul 2014 15:19:32 +0900 Subject: [PATCH 155/545] Remove empty 'internals' page The file is empty and not linked from anywhere, resulting in a warning when building the docs. Change-Id: Ib995d094ef525496fe8b2a369e0a0c5b4c7bd2dd --- docs/dev/internals.rst | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/dev/internals.rst diff --git a/docs/dev/internals.rst b/docs/dev/internals.rst deleted file mode 100644 index e69de29b..00000000 From 44e426e4ff7c8f4113d3754becc5678db1c0db6f Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Fri, 18 Jul 2014 16:35:54 +0900 Subject: [PATCH 156/545] More line wrapping Change-Id: I950c3da727fb97b58d96a872b0d2ed718cc60ba8 --- docs/user/advanced.rst | 49 +++++++++++++++++++++++++++++----------- docs/user/quickstart.rst | 3 ++- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 95dd6be6..7b315433 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -37,13 +37,18 @@ is done by providing data to the properties on a Session object:: s.get('http://httpbin.org/headers', headers={'x-test2': 'true'}) -Any dictionaries that you pass to a request method will be merged with the session-level values that are set. The method-level parameters override session parameters. +Any dictionaries that you pass to a request method will be merged with the +session-level values that are set. The method-level parameters override session +parameters. .. admonition:: Remove a Value From a Dict Parameter - Sometimes you'll want to omit session-level keys from a dict parameter. To do this, you simply set that key's value to ``None`` in the method-level parameter. It will automatically be omitted. + Sometimes you'll want to omit session-level keys from a dict parameter. To + do this, you simply set that key's value to ``None`` in the method-level + parameter. It will automatically be omitted. -All values that are contained within a session are directly available to you. See the :ref:`Session API Docs ` to learn more. +All values that are contained within a session are directly available to you. +See the :ref:`Session API Docs ` to learn more. Request and Response Objects ---------------------------- @@ -150,7 +155,8 @@ applied, replace the call to :meth:`Request.prepare() SSL Cert Verification --------------------- -Requests can verify SSL certificates for HTTPS requests, just like a web browser. To check a host's SSL certificate, you can use the ``verify`` argument:: +Requests can verify SSL certificates for HTTPS requests, just like a web browser. +To check a host's SSL certificate, you can use the ``verify`` argument:: >>> requests.get('https://kennethreitz.com', verify=True) requests.exceptions.SSLError: hostname 'kennethreitz.com' doesn't match either of '*.herokuapp.com', 'herokuapp.com' @@ -171,7 +177,9 @@ Requests can also ignore verifying the SSL certificate if you set ``verify`` to By default, ``verify`` is set to True. Option ``verify`` only applies to host certs. -You can also specify a local cert to use as client side certificate, as a single file (containing the private key and the certificate) or as a tuple of both file's path:: +You can also specify a local cert to use as client side certificate, as a single +file (containing the private key and the certificate) or as a tuple of both +file's path:: >>> requests.get('https://kennethreitz.com', cert=('/path/server.crt', '/path/key')) @@ -193,13 +201,18 @@ attribute with the ``stream`` parameter:: tarball_url = 'https://github.com/kennethreitz/requests/tarball/master' r = requests.get(tarball_url, stream=True) -At this point only the response headers have been downloaded and the connection remains open, hence allowing us to make content retrieval conditional:: +At this point only the response headers have been downloaded and the connection +remains open, hence allowing us to make content retrieval conditional:: if int(r.headers['content-length']) < TOO_LONG: content = r.content ... -You can further control the workflow by use of the :class:`Response.iter_content ` and :class:`Response.iter_lines ` methods. Alternatively, you can read the undecoded body from the underlying urllib3 :class:`urllib3.HTTPResponse ` at :class:`Response.raw `. +You can further control the workflow by use of the :class:`Response.iter_content ` +and :class:`Response.iter_lines ` methods. +Alternatively, you can read the undecoded body from the underlying +urllib3 :class:`urllib3.HTTPResponse ` at +:class:`Response.raw `. If you set ``stream`` to ``True`` when making a request, Requests cannot release the connection back to the pool unless you consume all the data or call @@ -219,15 +232,21 @@ consider using ``contextlib.closing`` (`documented here`_), like this:: Keep-Alive ---------- -Excellent news — thanks to urllib3, keep-alive is 100% automatic within a session! Any requests that you make within a session will automatically reuse the appropriate connection! +Excellent news — thanks to urllib3, keep-alive is 100% automatic within a session! +Any requests that you make within a session will automatically reuse the appropriate +connection! -Note that connections are only released back to the pool for reuse once all body data has been read; be sure to either set ``stream`` to ``False`` or read the ``content`` property of the ``Response`` object. +Note that connections are only released back to the pool for reuse once all body +data has been read; be sure to either set ``stream`` to ``False`` or read the +``content`` property of the ``Response`` object. Streaming Uploads ----------------- -Requests supports streaming uploads, which allow you to send large streams or files without reading them into memory. To stream and upload, simply provide a file-like object for your body:: +Requests supports streaming uploads, which allow you to send large streams or +files without reading them into memory. To stream and upload, simply provide a +file-like object for your body:: with open('massive-body', 'rb') as f: requests.post('http://some.url/streamed', data=f) @@ -236,7 +255,9 @@ Requests supports streaming uploads, which allow you to send large streams or fi Chunk-Encoded Requests ---------------------- -Requests also supports Chunked transfer encoding for outgoing and incoming requests. To send a chunk-encoded request, simply provide a generator (or any iterator without a length) for your body:: +Requests also supports Chunked transfer encoding for outgoing and incoming requests. +To send a chunk-encoded request, simply provide a generator (or any iterator without +a length) for your body:: def gen(): @@ -579,9 +600,11 @@ kinds of exciting ways, 4995 more times. Link Headers ------------ -Many HTTP APIs feature Link headers. They make APIs more self describing and discoverable. +Many HTTP APIs feature Link headers. They make APIs more self describing and +discoverable. -GitHub uses these for `pagination `_ in their API, for example:: +GitHub uses these for `pagination `_ +in their API, for example:: >>> url = 'https://api.github.com/users/kennethreitz/repos?page=1&per_page=10' >>> r = requests.head(url=url) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index b1fe9344..6217ceb2 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -204,7 +204,8 @@ dictionary of data will automatically be form-encoded when the request is made:: ... } -There are many times that you want to send data that is not form-encoded. If you pass in a ``string`` instead of a ``dict``, that data will be posted directly. +There are many times that you want to send data that is not form-encoded. If +you pass in a ``string`` instead of a ``dict``, that data will be posted directly. For example, the GitHub API v3 accepts JSON-Encoded POST/PATCH data:: From 743f38209dad9aa8a70b48249b48534dc0e699ed Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Fri, 18 Jul 2014 16:48:22 +0900 Subject: [PATCH 157/545] Linkify Github usernames in authors list - Linkify github usernames - Remove usernames that do not exist Change-Id: Ib88b70a3010e915b3570ae5062c8cb416c9a6462 --- AUTHORS.rst | 60 ++++++++++++++++++++++++++--------------------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 52bfc350..2f3dd96b 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -46,7 +46,7 @@ Patches and Suggestions - Peter Manser - Jeremy Selier - Jens Diemer -- Alex <@alopatin> +- Alex (`@alopatin `_) - Tom Hogans - Armin Ronacher - Shrikant Sharat Kandula @@ -83,10 +83,10 @@ Patches and Suggestions - Chase Sterling - Marty Alchin - takluyver -- Ben Toews (mastahyeti) +- Ben Toews (`@mastahyeti `_) - David Kemp - Brendon Crawford -- Denis (Telofy) +- Denis (`@Telofy `_) - Matt Giuca - Adam Tauber - Honza Javorek @@ -103,14 +103,14 @@ Patches and Suggestions - Jonty Wareing - Shivaram Lingamneni - Miguel Turner -- Rohan Jain (crodjer) +- Rohan Jain (`@crodjer `_) - Justin Barber -- Roman Haritonov <@reclosedev> +- Roman Haritonov (`@reclosedev `_) - Josh Imhoff - Arup Malakar -- Danilo Bargen (dbrgn) +- Danilo Bargen (`@dbrgn `_) - Torsten Landschoff -- Michael Holler (apotheos) +- Michael Holler (`@apotheos `_) - Timnit Gebru - Sarah Gonzalez - Victoria Mo @@ -118,38 +118,38 @@ Patches and Suggestions - Matthias Rahlf - Jakub Roztocil - Rhys Elsmore -- André Graf (dergraf) -- Stephen Zhuang (everbird) +- André Graf (`@dergraf `_) +- Stephen Zhuang (`@everbird `_) - Martijn Pieters - Jonatan Heyman -- David Bonner @rascalking +- David Bonner (`@rascalking `_) - Vinod Chandru - Johnny Goodnow - Denis Ryzhkov -- Wilfred Hughes @dontYetKnow +- Wilfred Hughes - Dmitry Medvinsky -- Bryce Boe @bboe -- Colin Dunklau @cdunklau -- Bob Carroll @rcarz -- Hugo Osvaldo Barrera @hobarrera -- Łukasz Langa @llanga +- Bryce Boe (`@bboe `_) +- Colin Dunklau (`@cdunklau `_) +- Bob Carroll (`@rcarz `_) +- Hugo Osvaldo Barrera (`@hobarrera `_) +- Łukasz Langa - Dave Shawley -- James Clarke (jam) +- James Clarke (`@jam `_) - Kevin Burke - Flavio Curella -- David Pursehouse @dpursehouse +- David Pursehouse (`@dpursehouse `_) - Jon Parise -- Alexander Karpinsky @homm86 -- Marc Schlaich @schlamar -- Park Ilsu @daftshady -- Matt Spitz @mattspitz -- Vikram Oberoi @voberoi -- Can Ibanoglu @canibanoglu -- Thomas Weißschuh @t-8ch -- Jayson Vantuyl @kagato +- Alexander Karpinsky +- Marc Schlaich (`@schlamar `_) +- Park Ilsu (`@daftshady `_) +- Matt Spitz (`@mattspitz `_) +- Vikram Oberoi (`@voberoi `_) +- Can Ibanoglu (`@canibanoglu `_) +- Thomas Weißschuh (`@t-8ch `_) +- Jayson Vantuyl - Pengfei.X - Kamil Madac -- Michael Becker @beckerfuffle -- Erik Wickstrom @erikwickstrom -- Константин Подшумок @podshumok -- Ben Bass @codedstructure +- Michael Becker +- Erik Wickstrom (`@erikwickstrom `_) +- Константин Подшумок (`@podshumok `_) +- Ben Bass (`@codedstructure `_) From eef4e0836cacd5fba902fb44850ac134fc1b992c Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Fri, 18 Jul 2014 22:46:19 +0900 Subject: [PATCH 158/545] Add links to Twitter accounts Change-Id: Ic88eb128384b3cc9108151c219cd759c7524a4f1 --- AUTHORS.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 2f3dd96b..b4412b9b 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -139,7 +139,7 @@ Patches and Suggestions - Flavio Curella - David Pursehouse (`@dpursehouse `_) - Jon Parise -- Alexander Karpinsky +- Alexander Karpinsky (`@homm86 `_) - Marc Schlaich (`@schlamar `_) - Park Ilsu (`@daftshady `_) - Matt Spitz (`@mattspitz `_) @@ -149,7 +149,7 @@ Patches and Suggestions - Jayson Vantuyl - Pengfei.X - Kamil Madac -- Michael Becker +- Michael Becker (`@beckerfuffle `_) - Erik Wickstrom (`@erikwickstrom `_) - Константин Подшумок (`@podshumok `_) - Ben Bass (`@codedstructure `_) From 698b93b86dd88ed68ee6dc02c60f5defcc4b5995 Mon Sep 17 00:00:00 2001 From: Espartaco Palma Date: Fri, 18 Jul 2014 11:44:57 -0500 Subject: [PATCH 159/545] Docs: Using print() function instead print command on advanced & quickstart user docs --- docs/user/advanced.rst | 28 ++++++++++++++-------------- docs/user/quickstart.rst | 10 +++++----- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 7b315433..503b3f86 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -361,7 +361,7 @@ set ``stream`` to ``True`` and iterate over the response with # filter out keep-alive new lines if line: - print json.loads(line) + print(json.loads(line)) Proxies @@ -445,7 +445,7 @@ We should confirm that GitHub responded correctly. If it has, we want to work out what type of content it is. Do this like so:: >>> if r.status_code == requests.codes.ok: - ... print r.headers['content-type'] + ... print(r.headers['content-type']) ... application/json; charset=utf-8 @@ -455,11 +455,11 @@ So, GitHub returns JSON. That's great, we can use the :meth:`r.json :: >>> commit_data = r.json() - >>> print commit_data.keys() + >>> print(commit_data.keys()) [u'committer', u'author', u'url', u'tree', u'sha', u'parents', u'message'] - >>> print commit_data[u'committer'] + >>> print(commit_data[u'committer']) {u'date': u'2012-05-10T11:10:50-07:00', u'email': u'me@kennethreitz.com', u'name': u'Kenneth Reitz'} - >>> print commit_data[u'message'] + >>> print(commit_data[u'message']) makin' history So far, so simple. Well, let's investigate the GitHub API a little bit. Now, @@ -482,7 +482,7 @@ headers, e.g. :: >>> verbs = requests.options('http://a-good-website.com/api/cats') - >>> print verbs.headers['allow'] + >>> print(verbs.headers['allow']) GET,HEAD,POST,OPTIONS Turning to the documentation, we see that the only other method allowed for @@ -499,9 +499,9 @@ already exists, we will use it as an example. Let's start by getting it. >>> r.status_code 200 >>> issue = json.loads(r.text) - >>> print issue[u'title'] + >>> print(issue[u'title']) Feature any http verb in docs - >>> print issue[u'comments'] + >>> print(issue[u'comments']) 3 Cool, we have three comments. Let's take a look at the last of them. @@ -512,9 +512,9 @@ Cool, we have three comments. Let's take a look at the last of them. >>> r.status_code 200 >>> comments = r.json() - >>> print comments[0].keys() + >>> print(comments[0].keys()) [u'body', u'url', u'created_at', u'updated_at', u'user', u'id'] - >>> print comments[2][u'body'] + >>> print(comments[2][u'body']) Probably in the "advanced" section Well, that seems like a silly place. Let's post a comment telling the poster @@ -522,7 +522,7 @@ that he's silly. Who is the poster, anyway? :: - >>> print comments[2][u'user'][u'login'] + >>> print(comments[2][u'user'][u'login']) kennethreitz OK, so let's tell this Kenneth guy that we think this example should go in the @@ -549,7 +549,7 @@ the very common Basic Auth. >>> r.status_code 201 >>> content = r.json() - >>> print content[u'body'] + >>> print(content[u'body']) Sounds great! I'll get right on it. Brilliant. Oh, wait, no! I meant to add that it would take me a while, because @@ -559,7 +559,7 @@ that. :: - >>> print content[u"id"] + >>> print(content[u"id"]) 5804413 >>> body = json.dumps({u"body": u"Sounds great! I'll get right on it once I feed my cat."}) >>> url = u"https://api.github.com/repos/kennethreitz/requests/issues/comments/5804413" @@ -588,7 +588,7 @@ headers. :: >>> r = requests.head(url=url, auth=auth) - >>> print r.headers + >>> print(r.headers) ... 'x-ratelimit-remaining': '4995' 'x-ratelimit-limit': '5000' diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 6217ceb2..76d4e513 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -194,7 +194,7 @@ dictionary of data will automatically be form-encoded when the request is made:: >>> payload = {'key1': 'value1', 'key2': 'value2'} >>> r = requests.post("http://httpbin.org/post", data=payload) - >>> print r.text + >>> print(r.text) { ... "form": { @@ -264,10 +264,10 @@ If you want, you can send strings to be received as files:: ... } -In the event you are posting a very large file as a ``multipart/form-data`` -request, you may want to stream the request. By default, ``requests`` does not -support this, but there is a separate package which does - -``requests-toolbelt``. You should read `the toolbelt's documentation +In the event you are posting a very large file as a ``multipart/form-data`` +request, you may want to stream the request. By default, ``requests`` does not +support this, but there is a separate package which does - +``requests-toolbelt``. You should read `the toolbelt's documentation `_ for more details about how to use it. From 811ee4eb5d9edba50a62b906420dec8e079532ae Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 21 Jul 2014 13:37:10 -0500 Subject: [PATCH 160/545] Upgrade urllib3 to 1.9.x --- requests/packages/urllib3/__init__.py | 24 +- requests/packages/urllib3/_collections.py | 8 +- requests/packages/urllib3/connection.py | 125 +++++--- requests/packages/urllib3/connectionpool.py | 259 +++++++++------- requests/packages/urllib3/contrib/ntlmpool.py | 6 - .../packages/urllib3/contrib/pyopenssl.py | 202 ++----------- requests/packages/urllib3/exceptions.py | 39 ++- requests/packages/urllib3/fields.py | 26 +- requests/packages/urllib3/filepost.py | 15 +- .../packages/urllib3/packages/ordered_dict.py | 1 - requests/packages/urllib3/poolmanager.py | 43 +-- requests/packages/urllib3/request.py | 44 ++- requests/packages/urllib3/response.py | 82 ++--- requests/packages/urllib3/util/__init__.py | 9 +- requests/packages/urllib3/util/connection.py | 58 +++- requests/packages/urllib3/util/request.py | 19 +- requests/packages/urllib3/util/retry.py | 279 ++++++++++++++++++ requests/packages/urllib3/util/ssl_.py | 5 +- requests/packages/urllib3/util/timeout.py | 100 ++++--- requests/packages/urllib3/util/url.py | 19 +- 20 files changed, 818 insertions(+), 545 deletions(-) create mode 100644 requests/packages/urllib3/util/retry.py diff --git a/requests/packages/urllib3/__init__.py b/requests/packages/urllib3/__init__.py index 73071f70..cdee528c 100644 --- a/requests/packages/urllib3/__init__.py +++ b/requests/packages/urllib3/__init__.py @@ -1,9 +1,3 @@ -# urllib3/__init__.py -# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt) -# -# This module is part of urllib3 and is released under -# the MIT License: http://www.opensource.org/licenses/mit-license.php - """ urllib3 - Thread-safe connection pooling and re-using. """ @@ -23,7 +17,10 @@ from . import exceptions from .filepost import encode_multipart_formdata from .poolmanager import PoolManager, ProxyManager, proxy_from_url from .response import HTTPResponse -from .util import make_headers, get_host, Timeout +from .util.request import make_headers +from .util.url import get_host +from .util.timeout import Timeout +from .util.retry import Retry # Set default logging handler to avoid "No handler found" warnings. @@ -51,8 +48,19 @@ def add_stderr_logger(level=logging.DEBUG): handler.setFormatter(logging.Formatter('%(asctime)s %(levelname)s %(message)s')) logger.addHandler(handler) logger.setLevel(level) - logger.debug('Added an stderr logging handler to logger: %s' % __name__) + logger.debug('Added a stderr logging handler to logger: %s' % __name__) return handler # ... Clean up. del NullHandler + + +# Set security warning to only go off once by default. +import warnings +warnings.simplefilter('module', exceptions.InsecureRequestWarning) + +def disable_warnings(category=exceptions.HTTPWarning): + """ + Helper for quickly disabling all urllib3 warnings. + """ + warnings.simplefilter('ignore', category) diff --git a/requests/packages/urllib3/_collections.py b/requests/packages/urllib3/_collections.py index 9cea3a44..d77ebb8d 100644 --- a/requests/packages/urllib3/_collections.py +++ b/requests/packages/urllib3/_collections.py @@ -1,9 +1,3 @@ -# urllib3/_collections.py -# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt) -# -# This module is part of urllib3 and is released under -# the MIT License: http://www.opensource.org/licenses/mit-license.php - from collections import Mapping, MutableMapping try: from threading import RLock @@ -116,7 +110,7 @@ class HTTPHeaderDict(MutableMapping): A ``dict`` like container for storing HTTP Headers. Field names are stored and compared case-insensitively in compliance with - RFC 2616. Iteration provides the first case-sensitive key seen for each + RFC 7230. Iteration provides the first case-sensitive key seen for each case-insensitive pair. Using ``__setitem__`` syntax overwrites fields that compare equal diff --git a/requests/packages/urllib3/connection.py b/requests/packages/urllib3/connection.py index 5feb3322..0d578d77 100644 --- a/requests/packages/urllib3/connection.py +++ b/requests/packages/urllib3/connection.py @@ -1,52 +1,44 @@ -# urllib3/connection.py -# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt) -# -# This module is part of urllib3 and is released under -# the MIT License: http://www.opensource.org/licenses/mit-license.php - import sys import socket from socket import timeout as SocketTimeout -try: # Python 3 +try: # Python 3 from http.client import HTTPConnection as _HTTPConnection, HTTPException except ImportError: from httplib import HTTPConnection as _HTTPConnection, HTTPException + class DummyConnection(object): "Used to detect a failed ConnectionCls import." pass -try: # Compiled with SSL? - ssl = None + +try: # Compiled with SSL? HTTPSConnection = DummyConnection + import ssl + BaseSSLError = ssl.SSLError +except (ImportError, AttributeError): # Platform-specific: No SSL. + ssl = None class BaseSSLError(BaseException): pass - try: # Python 3 - from http.client import HTTPSConnection as _HTTPSConnection - except ImportError: - from httplib import HTTPSConnection as _HTTPSConnection - - import ssl - BaseSSLError = ssl.SSLError - -except (ImportError, AttributeError): # Platform-specific: No SSL. - pass from .exceptions import ( ConnectTimeoutError, ) from .packages.ssl_match_hostname import match_hostname from .packages import six -from .util import ( - assert_fingerprint, + +from .util.ssl_ import ( resolve_cert_reqs, resolve_ssl_version, ssl_wrap_socket, + assert_fingerprint, ) +from .util import connection + port_by_scheme = { 'http': 80, @@ -58,38 +50,79 @@ class HTTPConnection(_HTTPConnection, object): """ Based on httplib.HTTPConnection but provides an extra constructor backwards-compatibility layer between older and newer Pythons. + + Additional keyword parameters are used to configure attributes of the connection. + Accepted parameters include: + + - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool` + - ``source_address``: Set the source address for the current connection. + + .. note:: This is ignored for Python 2.6. It is only applied for 2.7 and 3.x + + - ``socket_options``: Set specific options on the underlying socket. If not specified, then + defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling + Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy. + + For example, if you wish to enable TCP Keep Alive in addition to the defaults, + you might pass:: + + HTTPConnection.default_socket_options + [ + (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), + ] + + Or you may want to disable the defaults by passing an empty list (e.g., ``[]``). """ default_port = port_by_scheme['http'] - # By default, disable Nagle's Algorithm. - tcp_nodelay = 1 + #: Disable Nagle's algorithm by default. + #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` + default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)] + + #: Whether this connection verifies the host's certificate. + is_verified = False def __init__(self, *args, **kw): if six.PY3: # Python 3 kw.pop('strict', None) - if sys.version_info < (2, 7): # Python 2.6 and older - kw.pop('source_address', None) # Pre-set source_address in case we have an older Python like 2.6. self.source_address = kw.get('source_address') + if sys.version_info < (2, 7): # Python 2.6 + # _HTTPConnection on Python 2.6 will balk at this keyword arg, but + # not newer versions. We can still use it when creating a + # connection though, so we pop it *after* we have saved it as + # self.source_address. + kw.pop('source_address', None) + + #: The socket options provided by the user. If no options are + #: provided, we use the default options. + self.socket_options = kw.pop('socket_options', self.default_socket_options) + # Superclass also sets self.source_address in Python 2.7+. - _HTTPConnection.__init__(self, *args, **kw) + _HTTPConnection.__init__(self, *args, **kw) def _new_conn(self): """ Establish a socket connection and set nodelay settings on it. - :return: a new socket connection + :return: New socket connection. """ - extra_args = [] - if self.source_address: # Python 2.7+ - extra_args.append(self.source_address) + extra_kw = {} + if self.source_address: + extra_kw['source_address'] = self.source_address - conn = socket.create_connection( - (self.host, self.port), self.timeout, *extra_args) - conn.setsockopt( - socket.IPPROTO_TCP, socket.TCP_NODELAY, self.tcp_nodelay) + if self.socket_options: + extra_kw['socket_options'] = self.socket_options + + try: + conn = connection.create_connection( + (self.host, self.port), self.timeout, **extra_kw) + + except SocketTimeout: + raise ConnectTimeoutError( + self, "Connection to %s timed out. (connect timeout=%s)" % + (self.host, self.timeout)) return conn @@ -101,6 +134,8 @@ class HTTPConnection(_HTTPConnection, object): if getattr(self, '_tunnel_host', None): # TODO: Fix tunnel so it doesn't depend on self.sock state. self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 def connect(self): conn = self._new_conn() @@ -137,7 +172,6 @@ class VerifiedHTTPSConnection(HTTPSConnection): cert_reqs = None ca_certs = None ssl_version = None - conn_kw = {} def set_cert(self, key_file=None, cert_file=None, cert_reqs=None, ca_certs=None, @@ -152,18 +186,7 @@ class VerifiedHTTPSConnection(HTTPSConnection): def connect(self): # Add certificate verification - - try: - sock = socket.create_connection( - address=(self.host, self.port), timeout=self.timeout, - **self.conn_kw) - except SocketTimeout: - raise ConnectTimeoutError( - self, "Connection to %s timed out. (connect timeout=%s)" % - (self.host, self.timeout)) - - sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, - self.tcp_nodelay) + conn = self._new_conn() resolved_cert_reqs = resolve_cert_reqs(self.cert_reqs) resolved_ssl_version = resolve_ssl_version(self.ssl_version) @@ -173,17 +196,19 @@ class VerifiedHTTPSConnection(HTTPSConnection): # _tunnel_host was added in Python 2.6.3 # (See: http://hg.python.org/cpython/rev/0f57b30a152f) - self.sock = sock + self.sock = conn # Calls self._set_hostport(), so self.host is # self._tunnel_host below. self._tunnel() + # Mark this connection as not reusable + self.auto_open = 0 # Override the host with the one we're requesting data from. hostname = self._tunnel_host # Wrap socket using verification with the root certs in # trusted_root_certs - self.sock = ssl_wrap_socket(sock, self.key_file, self.cert_file, + self.sock = ssl_wrap_socket(conn, self.key_file, self.cert_file, cert_reqs=resolved_cert_reqs, ca_certs=self.ca_certs, server_hostname=hostname, @@ -197,6 +222,8 @@ class VerifiedHTTPSConnection(HTTPSConnection): match_hostname(self.sock.getpeercert(), self.assert_hostname or hostname) + self.is_verified = resolved_cert_reqs == ssl.CERT_REQUIRED + if ssl: # Make a copy for testing. diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index 95a53a7d..9317fdc3 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -1,17 +1,12 @@ -# urllib3/connectionpool.py -# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt) -# -# This module is part of urllib3 and is released under -# the MIT License: http://www.opensource.org/licenses/mit-license.php - -import sys import errno import logging +import sys +import warnings from socket import error as SocketError, timeout as SocketTimeout import socket -try: # Python 3 +try: # Python 3 from queue import LifoQueue, Empty, Full except ImportError: from Queue import LifoQueue, Empty, Full @@ -20,16 +15,16 @@ except ImportError: from .exceptions import ( ClosedPoolError, - ConnectionError, - ConnectTimeoutError, + ProtocolError, EmptyPoolError, HostChangedError, - LocationParseError, + LocationValueError, MaxRetryError, + ProxyError, + ReadTimeoutError, SSLError, TimeoutError, - ReadTimeoutError, - ProxyError, + InsecureRequestWarning, ) from .packages.ssl_match_hostname import CertificateError from .packages import six @@ -41,11 +36,11 @@ from .connection import ( ) from .request import RequestMethods from .response import HTTPResponse -from .util import ( - get_host, - is_connection_dropped, - Timeout, -) + +from .util.connection import is_connection_dropped +from .util.retry import Retry +from .util.timeout import Timeout +from .util.url import get_host xrange = six.moves.xrange @@ -54,8 +49,8 @@ log = logging.getLogger(__name__) _Default = object() -## Pool objects +## Pool objects class ConnectionPool(object): """ Base class for all connection pools, such as @@ -66,13 +61,11 @@ class ConnectionPool(object): QueueCls = LifoQueue def __init__(self, host, port=None): - if host is None: - raise LocationParseError(host) + if not host: + raise LocationValueError("No host specified.") # httplib doesn't like it when we include brackets in ipv6 addresses - host = host.strip('[]') - - self.host = host + self.host = host.strip('[]') self.port = port def __str__(self): @@ -82,6 +75,7 @@ class ConnectionPool(object): # This is taken from http://hg.python.org/cpython/file/7aaba721ebc0/Lib/socket.py#l252 _blocking_errnos = set([errno.EAGAIN, errno.EWOULDBLOCK]) + class HTTPConnectionPool(ConnectionPool, RequestMethods): """ Thread-safe connection pool for one host. @@ -126,6 +120,9 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): Headers to include with all requests, unless other headers are given explicitly. + :param retries: + Retry configuration to use by default with requests in this pool. + :param _proxy: Parsed proxy URL, should not be used directly, instead, see :class:`urllib3.connectionpool.ProxyManager`" @@ -133,6 +130,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): :param _proxy_headers: A dictionary with proxy headers, should not be used directly, instead, see :class:`urllib3.connectionpool.ProxyManager`" + + :param \**conn_kw: + Additional parameters are used to create fresh :class:`urllib3.connection.HTTPConnection`, + :class:`urllib3.connection.HTTPSConnection` instances. """ scheme = 'http' @@ -140,18 +141,22 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): def __init__(self, host, port=None, strict=False, timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, block=False, - headers=None, _proxy=None, _proxy_headers=None, **conn_kw): + headers=None, retries=None, + _proxy=None, _proxy_headers=None, + **conn_kw): ConnectionPool.__init__(self, host, port) RequestMethods.__init__(self, headers) self.strict = strict - # This is for backwards compatibility and can be removed once a timeout - # can only be set to a Timeout object if not isinstance(timeout, Timeout): timeout = Timeout.from_float(timeout) + if retries is None: + retries = Retry.DEFAULT + self.timeout = timeout + self.retries = retries self.pool = self.QueueCls(maxsize) self.block = block @@ -166,11 +171,14 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # These are mostly for testing and debugging purposes. self.num_connections = 0 self.num_requests = 0 - - if sys.version_info < (2, 7): # Python 2.6 and older - conn_kw.pop('source_address', None) self.conn_kw = conn_kw + if self.proxy: + # Enable Nagle's algorithm for proxies, to avoid packet fragmentation. + # We cannot know if the user has added default socket options, so we cannot replace the + # list. + self.conn_kw.setdefault('socket_options', []) + def _new_conn(self): """ Return a fresh :class:`HTTPConnection`. @@ -182,10 +190,6 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): conn = self.ConnectionCls(host=self.host, port=self.port, timeout=self.timeout.connect_timeout, strict=self.strict, **self.conn_kw) - if self.proxy is not None: - # Enable Nagle's algorithm for proxies, to avoid packet - # fragmentation. - conn.tcp_nodelay = 0 return conn def _get_conn(self, timeout=None): @@ -204,7 +208,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): try: conn = self.pool.get(block=self.block, timeout=timeout) - except AttributeError: # self.pool is None + except AttributeError: # self.pool is None raise ClosedPoolError(self, "Pool is closed.") except Empty: @@ -218,6 +222,11 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): if conn and is_connection_dropped(conn): log.info("Resetting dropped connection: %s" % self.host) conn.close() + if getattr(conn, 'auto_open', 1) == 0: + # This is a proxied connection that has been mutated by + # httplib._tunnel() and cannot be reused (since it would + # attempt to bypass the proxy) + conn = None return conn or self._new_conn() @@ -237,7 +246,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): """ try: self.pool.put(conn, block=False) - return # Everything is dandy, done. + return # Everything is dandy, done. except AttributeError: # self.pool is None. pass @@ -251,6 +260,12 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): if conn: conn.close() + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + pass + def _get_timeout(self, timeout): """ Helper that always returns a :class:`urllib3.util.Timeout` """ if timeout is _Default: @@ -282,23 +297,21 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): self.num_requests += 1 timeout_obj = self._get_timeout(timeout) + timeout_obj.start_connect() + conn.timeout = timeout_obj.connect_timeout - try: - timeout_obj.start_connect() - conn.timeout = timeout_obj.connect_timeout - # conn.request() calls httplib.*.request, not the method in - # urllib3.request. It also calls makefile (recv) on the socket. - conn.request(method, url, **httplib_request_kw) - except SocketTimeout: - raise ConnectTimeoutError( - self, "Connection to %s timed out. (connect timeout=%s)" % - (self.host, timeout_obj.connect_timeout)) + # Trigger any extra validation we need to do. + self._validate_conn(conn) + + # conn.request() calls httplib.*.request, not the method in + # urllib3.request. It also calls makefile (recv) on the socket. + conn.request(method, url, **httplib_request_kw) # Reset the timeout for the recv() on the socket read_timeout = timeout_obj.read_timeout # App Engine doesn't have a sock attr - if hasattr(conn, 'sock'): + if getattr(conn, 'sock', None): # In Python 3 socket.py will catch EAGAIN and return None when you # try and read into the file pointer created by http.client, which # instead raises a BadStatusLine exception. Instead of catching @@ -306,18 +319,17 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # timeouts, check for a zero timeout before making the request. if read_timeout == 0: raise ReadTimeoutError( - self, url, - "Read timed out. (read timeout=%s)" % read_timeout) + self, url, "Read timed out. (read timeout=%s)" % read_timeout) if read_timeout is Timeout.DEFAULT_TIMEOUT: conn.sock.settimeout(socket.getdefaulttimeout()) - else: # None or a value + else: # None or a value conn.sock.settimeout(read_timeout) # Receive the response from the server 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) - except TypeError: # Python 2.6 and older + except TypeError: # Python 2.6 and older httplib_response = conn.getresponse() except SocketTimeout: raise ReadTimeoutError( @@ -329,17 +341,17 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # http://bugs.python.org/issue10272 if 'timed out' in str(e) or \ 'did not complete (read)' in str(e): # Python 2.6 - raise ReadTimeoutError(self, url, "Read timed out.") + raise ReadTimeoutError( + self, url, "Read timed out. (read timeout=%s)" % read_timeout) raise - except SocketError as e: # Platform-specific: Python 2 + except SocketError as e: # Platform-specific: Python 2 # See the above comment about EAGAIN in Python 3. In Python 2 we # have to specifically catch it and throw the timeout error if e.errno in _blocking_errnos: raise ReadTimeoutError( - self, url, - "Read timed out. (read timeout=%s)" % read_timeout) + self, url, "Read timed out. (read timeout=%s)" % read_timeout) raise @@ -364,7 +376,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): conn.close() except Empty: - pass # Done. + pass # Done. def is_same_host(self, url): """ @@ -385,7 +397,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): return (scheme, host, port) == (self.scheme, self.host, self.port) - def urlopen(self, method, url, body=None, headers=None, retries=3, + def urlopen(self, method, url, body=None, headers=None, retries=None, redirect=True, assert_same_host=True, timeout=_Default, pool_timeout=None, release_conn=None, **response_kw): """ @@ -419,9 +431,20 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): these headers completely replace any pool-specific headers. :param retries: - Number of retries to allow before raising a MaxRetryError exception. - If `False`, then retries are disabled and any exception is raised - immediately. + Configure the number of retries to allow before raising a + :class:`~urllib3.exceptions.MaxRetryError` exception. + + Pass ``None`` to retry until you receive a response. Pass a + :class:`~urllib3.util.retry.Retry` object for fine-grained control + over different types of retries. + Pass an integer number to retry connection errors that many times, + but no other types of errors. Pass zero to never retry. + + If ``False``, then retries are disabled and any exception is raised + immediately. Also, instead of raising a MaxRetryError on redirects, + the redirect response will be returned. + + :type retries: :class:`~urllib3.util.retry.Retry`, False, or an int. :param redirect: If True, automatically handle redirects (status codes 301, 302, @@ -460,15 +483,15 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): if headers is None: headers = self.headers - if retries < 0 and retries is not False: - raise MaxRetryError(self, url) + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect, default=self.retries) if release_conn is None: release_conn = response_kw.get('preload_content', True) # Check host if assert_same_host and not self.is_same_host(url): - raise HostChangedError(self, url, retries - 1) + raise HostChangedError(self, url, retries) conn = None @@ -484,10 +507,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): err = None try: - # Request a connection from the queue + # Request a connection from the queue. conn = self._get_conn(timeout=pool_timeout) - # Make the request on the httplib connection object + # Make the request on the httplib connection object. httplib_response = self._make_request(conn, method, url, timeout=timeout, body=body, headers=headers) @@ -526,21 +549,15 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): conn.close() conn = None - if not retries: - if isinstance(e, TimeoutError): - # TimeoutError is exempt from MaxRetryError-wrapping. - # FIXME: ... Not sure why. Add a reason here. - raise + stacktrace = sys.exc_info()[2] + if isinstance(e, SocketError) and self.proxy: + e = ProxyError('Cannot connect to proxy.', e) + elif isinstance(e, (SocketError, HTTPException)): + e = ProtocolError('Connection aborted.', e) - # Wrap unexpected exceptions with the most appropriate - # module-level exception and re-raise. - if isinstance(e, SocketError) and self.proxy: - raise ProxyError('Cannot connect to proxy.', e) - - if retries is False: - raise ConnectionError('Connection failed.', e) - - raise MaxRetryError(self, url, e) + retries = retries.increment(method, url, error=e, + _pool=self, _stacktrace=stacktrace) + retries.sleep() # Keep track of the error for the retry warning. err = e @@ -554,23 +571,43 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): if not conn: # Try again - log.warning("Retrying (%d attempts remain) after connection " + log.warning("Retrying (%r) after connection " "broken by '%r': %s" % (retries, err, url)) - return self.urlopen(method, url, body, headers, retries - 1, + return self.urlopen(method, url, body, headers, retries, redirect, assert_same_host, timeout=timeout, pool_timeout=pool_timeout, release_conn=release_conn, **response_kw) # Handle redirect? redirect_location = redirect and response.get_redirect_location() - if redirect_location and retries is not False: + if redirect_location: if response.status == 303: method = 'GET' + + try: + retries = retries.increment(method, url, response=response, _pool=self) + except MaxRetryError: + if retries.raise_on_redirect: + raise + return response + log.info("Redirecting %s -> %s" % (url, redirect_location)) return self.urlopen(method, redirect_location, body, headers, - retries - 1, redirect, assert_same_host, - timeout=timeout, pool_timeout=pool_timeout, - release_conn=release_conn, **response_kw) + retries=retries, redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, pool_timeout=pool_timeout, + release_conn=release_conn, **response_kw) + + # Check if we should retry the HTTP response. + if retries.is_forced_retry(method, status_code=response.status): + retries = retries.increment(method, url, response=response, _pool=self) + retries.sleep() + log.info("Forced retry: %s" % url) + return self.urlopen(method, url, body, headers, + retries=retries, redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, pool_timeout=pool_timeout, + release_conn=release_conn, **response_kw) return response @@ -597,19 +634,17 @@ class HTTPSConnectionPool(HTTPConnectionPool): ConnectionCls = HTTPSConnection def __init__(self, host, port=None, - strict=False, timeout=None, maxsize=1, - block=False, headers=None, + strict=False, timeout=Timeout.DEFAULT_TIMEOUT, maxsize=1, + block=False, headers=None, retries=None, _proxy=None, _proxy_headers=None, key_file=None, cert_file=None, cert_reqs=None, ca_certs=None, ssl_version=None, assert_hostname=None, assert_fingerprint=None, **conn_kw): - if sys.version_info < (2, 7): # Python 2.6 or older - conn_kw.pop('source_address', None) - HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize, - block, headers, _proxy, _proxy_headers, **conn_kw) + block, headers, retries, _proxy, _proxy_headers, + **conn_kw) self.key_file = key_file self.cert_file = cert_file self.cert_reqs = cert_reqs @@ -617,7 +652,6 @@ class HTTPSConnectionPool(HTTPConnectionPool): self.ssl_version = ssl_version self.assert_hostname = assert_hostname self.assert_fingerprint = assert_fingerprint - self.conn_kw = conn_kw def _prepare_conn(self, conn): """ @@ -633,7 +667,6 @@ class HTTPSConnectionPool(HTTPConnectionPool): assert_hostname=self.assert_hostname, assert_fingerprint=self.assert_fingerprint) conn.ssl_version = self.ssl_version - conn.conn_kw = self.conn_kw if self.proxy is not None: # Python 2.7+ @@ -641,7 +674,12 @@ class HTTPSConnectionPool(HTTPConnectionPool): set_tunnel = conn.set_tunnel except AttributeError: # Platform-specific: Python 2.6 set_tunnel = conn._set_tunnel - set_tunnel(self.host, self.port, self.proxy_headers) + + 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() @@ -667,21 +705,30 @@ class HTTPSConnectionPool(HTTPConnectionPool): actual_host = self.proxy.host actual_port = self.proxy.port - extra_params = {} - if not six.PY3: # Python 2 - extra_params['strict'] = self.strict - extra_params.update(self.conn_kw) - conn = self.ConnectionCls(host=actual_host, port=actual_port, timeout=self.timeout.connect_timeout, - **extra_params) - if self.proxy is not None: - # Enable Nagle's algorithm for proxies, to avoid packet - # fragmentation. - conn.tcp_nodelay = 0 + strict=self.strict, **self.conn_kw) return self._prepare_conn(conn) + def _validate_conn(self, conn): + """ + Called right before a request is made, after the socket is created. + """ + super(HTTPSConnectionPool, self)._validate_conn(conn) + + # Force connect early to allow us to validate the connection. + if not conn.sock: + conn.connect() + + if not conn.is_verified: + warnings.warn(( + 'Unverified HTTPS request is being made. ' + 'Adding certificate verification is strongly advised. See: ' + 'https://urllib3.readthedocs.org/en/latest/security.html ' + '(This warning will only appear once by default.)'), + InsecureRequestWarning) + def connection_from_url(url, **kw): """ @@ -698,7 +745,7 @@ def connection_from_url(url, **kw): :class:`.ConnectionPool`. Useful for specifying things like timeout, maxsize, headers, etc. - Example: :: + Example:: >>> conn = connection_from_url('http://google.com/') >>> r = conn.request('GET', '/') diff --git a/requests/packages/urllib3/contrib/ntlmpool.py b/requests/packages/urllib3/contrib/ntlmpool.py index b8cd9330..c6b266f5 100644 --- a/requests/packages/urllib3/contrib/ntlmpool.py +++ b/requests/packages/urllib3/contrib/ntlmpool.py @@ -1,9 +1,3 @@ -# urllib3/contrib/ntlmpool.py -# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt) -# -# This module is part of urllib3 and is released under -# the MIT License: http://www.opensource.org/licenses/mit-license.php - """ NTLM authenticating pool, contributed by erikcederstran diff --git a/requests/packages/urllib3/contrib/pyopenssl.py b/requests/packages/urllib3/contrib/pyopenssl.py index 21a12c68..7a9ea2e7 100644 --- a/requests/packages/urllib3/contrib/pyopenssl.py +++ b/requests/packages/urllib3/contrib/pyopenssl.py @@ -54,7 +54,6 @@ from pyasn1.type import univ, constraint from socket import _fileobject, timeout import ssl import select -from cStringIO import StringIO from .. import connection from .. import util @@ -155,196 +154,37 @@ def get_subj_alt_name(peer_cert): return dns_name -class fileobject(_fileobject): - - def _wait_for_sock(self): - rd, wd, ed = select.select([self._sock], [], [], - self._sock.gettimeout()) - if not rd: - raise timeout() - - - def read(self, size=-1): - # Use max, disallow tiny reads in a loop as they are very inefficient. - # We never leave read() with any leftover data from a new recv() call - # in our internal buffer. - rbufsize = max(self._rbufsize, self.default_bufsize) - # Our use of StringIO rather than lists of string objects returned by - # recv() minimizes memory usage and fragmentation that occurs when - # rbufsize is large compared to the typical return value of recv(). - buf = self._rbuf - buf.seek(0, 2) # seek end - if size < 0: - # Read until EOF - self._rbuf = StringIO() # reset _rbuf. we consume it via buf. - while True: - try: - data = self._sock.recv(rbufsize) - except OpenSSL.SSL.WantReadError: - self._wait_for_sock() - continue - if not data: - break - buf.write(data) - return buf.getvalue() - else: - # Read until size bytes or EOF seen, whichever comes first - buf_len = buf.tell() - if buf_len >= size: - # Already have size bytes in our buffer? Extract and return. - buf.seek(0) - rv = buf.read(size) - self._rbuf = StringIO() - self._rbuf.write(buf.read()) - return rv - - self._rbuf = StringIO() # reset _rbuf. we consume it via buf. - while True: - left = size - buf_len - # recv() will malloc the amount of memory given as its - # parameter even though it often returns much less data - # than that. The returned data string is short lived - # as we copy it into a StringIO and free it. This avoids - # fragmentation issues on many platforms. - try: - data = self._sock.recv(left) - except OpenSSL.SSL.WantReadError: - self._wait_for_sock() - continue - if not data: - break - n = len(data) - if n == size and not buf_len: - # Shortcut. Avoid buffer data copies when: - # - We have no data in our buffer. - # AND - # - Our call to recv returned exactly the - # number of bytes we were asked to read. - return data - if n == left: - buf.write(data) - del data # explicit free - break - assert n <= left, "recv(%d) returned %d bytes" % (left, n) - buf.write(data) - buf_len += n - del data # explicit free - #assert buf_len == buf.tell() - return buf.getvalue() - - def readline(self, size=-1): - buf = self._rbuf - buf.seek(0, 2) # seek end - if buf.tell() > 0: - # check if we already have it in our buffer - buf.seek(0) - bline = buf.readline(size) - if bline.endswith('\n') or len(bline) == size: - self._rbuf = StringIO() - self._rbuf.write(buf.read()) - return bline - del bline - if size < 0: - # Read until \n or EOF, whichever comes first - if self._rbufsize <= 1: - # Speed up unbuffered case - buf.seek(0) - buffers = [buf.read()] - self._rbuf = StringIO() # reset _rbuf. we consume it via buf. - data = None - recv = self._sock.recv - while True: - try: - while data != "\n": - data = recv(1) - if not data: - break - buffers.append(data) - except OpenSSL.SSL.WantReadError: - self._wait_for_sock() - continue - break - return "".join(buffers) - - buf.seek(0, 2) # seek end - self._rbuf = StringIO() # reset _rbuf. we consume it via buf. - while True: - try: - data = self._sock.recv(self._rbufsize) - except OpenSSL.SSL.WantReadError: - self._wait_for_sock() - continue - if not data: - break - nl = data.find('\n') - if nl >= 0: - nl += 1 - buf.write(data[:nl]) - self._rbuf.write(data[nl:]) - del data - break - buf.write(data) - return buf.getvalue() - else: - # Read until size bytes or \n or EOF seen, whichever comes first - buf.seek(0, 2) # seek end - buf_len = buf.tell() - if buf_len >= size: - buf.seek(0) - rv = buf.read(size) - self._rbuf = StringIO() - self._rbuf.write(buf.read()) - return rv - self._rbuf = StringIO() # reset _rbuf. we consume it via buf. - while True: - try: - data = self._sock.recv(self._rbufsize) - except OpenSSL.SSL.WantReadError: - self._wait_for_sock() - continue - if not data: - break - left = size - buf_len - # did we just receive a newline? - nl = data.find('\n', 0, left) - if nl >= 0: - nl += 1 - # save the excess data to _rbuf - self._rbuf.write(data[nl:]) - if buf_len: - buf.write(data[:nl]) - break - else: - # Shortcut. Avoid data copy through buf when returning - # a substring of our first recv(). - return data[:nl] - n = len(data) - if n == size and not buf_len: - # Shortcut. Avoid data copy through buf when - # returning exactly all of our first recv(). - return data - if n >= left: - buf.write(data[:left]) - self._rbuf.write(data[left:]) - break - buf.write(data) - buf_len += n - #assert buf_len == buf.tell() - return buf.getvalue() - - class WrappedSocket(object): '''API-compatibility wrapper for Python OpenSSL's Connection-class.''' - def __init__(self, connection, socket): + def __init__(self, connection, socket, suppress_ragged_eofs=True): self.connection = connection self.socket = socket + self.suppress_ragged_eofs = suppress_ragged_eofs def fileno(self): return self.socket.fileno() def makefile(self, mode, bufsize=-1): - return fileobject(self.connection, mode, bufsize) + return _fileobject(self, mode, bufsize) + + def recv(self, *args, **kwargs): + try: + data = self.connection.recv(*args, **kwargs) + except OpenSSL.SSL.SysCallError as e: + if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): + return b'' + else: + raise + except OpenSSL.SSL.WantReadError: + rd, wd, ed = select.select( + [self.socket], [], [], self.socket.gettimeout()) + if not rd: + raise timeout() + else: + return self.recv(*args, **kwargs) + else: + return data def settimeout(self, timeout): return self.socket.settimeout(timeout) diff --git a/requests/packages/urllib3/exceptions.py b/requests/packages/urllib3/exceptions.py index b4df831f..fff8bfa5 100644 --- a/requests/packages/urllib3/exceptions.py +++ b/requests/packages/urllib3/exceptions.py @@ -1,9 +1,3 @@ -# urllib3/exceptions.py -# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt) -# -# This module is part of urllib3 and is released under -# the MIT License: http://www.opensource.org/licenses/mit-license.php - ## Base Exceptions @@ -11,6 +5,11 @@ class HTTPError(Exception): "Base exception used by this module." pass +class HTTPWarning(Warning): + "Base warning used by this module." + pass + + class PoolError(HTTPError): "Base exception for errors caused within a pool." @@ -44,16 +43,20 @@ class ProxyError(HTTPError): pass -class ConnectionError(HTTPError): - "Raised when a normal connection fails." - pass - - class DecodeError(HTTPError): "Raised when automatic decoding based on Content-Type fails." pass +class ProtocolError(HTTPError): + "Raised when something unexpected happens mid-request/response." + pass + + +#: Renamed to ProtocolError but aliased for backwards compatibility. +ConnectionError = ProtocolError + + ## Leaf Exceptions class MaxRetryError(RequestError): @@ -64,7 +67,7 @@ class MaxRetryError(RequestError): message = "Max retries exceeded with url: %s" % url if reason: - message += " (Caused by %s: %s)" % (type(reason), reason) + message += " (Caused by %r)" % reason else: message += " (Caused by redirect)" @@ -116,7 +119,12 @@ class ClosedPoolError(PoolError): pass -class LocationParseError(ValueError, HTTPError): +class LocationValueError(ValueError, HTTPError): + "Raised when there is something wrong with a given URL input." + pass + + +class LocationParseError(LocationValueError): "Raised when get_host or similar fails to parse the URL input." def __init__(self, location): @@ -124,3 +132,8 @@ class LocationParseError(ValueError, HTTPError): HTTPError.__init__(self, message) self.location = location + + +class InsecureRequestWarning(HTTPWarning): + "Warned when making an unverified HTTPS request." + pass diff --git a/requests/packages/urllib3/fields.py b/requests/packages/urllib3/fields.py index da79e929..c853f8d5 100644 --- a/requests/packages/urllib3/fields.py +++ b/requests/packages/urllib3/fields.py @@ -1,9 +1,3 @@ -# urllib3/fields.py -# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt) -# -# This module is part of urllib3 and is released under -# the MIT License: http://www.opensource.org/licenses/mit-license.php - import email.utils import mimetypes @@ -78,9 +72,10 @@ class RequestField(object): """ A :class:`~urllib3.fields.RequestField` factory from old-style tuple parameters. - Supports constructing :class:`~urllib3.fields.RequestField` from parameter - of key/value strings AND key/filetuple. A filetuple is a (filename, data, MIME type) - tuple where the MIME type is optional. For example: :: + Supports constructing :class:`~urllib3.fields.RequestField` from + parameter of key/value strings AND key/filetuple. A filetuple is a + (filename, data, MIME type) tuple where the MIME type is optional. + For example:: 'foo': 'bar', 'fakefile': ('foofile.txt', 'contents of foofile'), @@ -125,8 +120,8 @@ class RequestField(object): 'Content-Disposition' fields. :param header_parts: - A sequence of (k, v) typles or a :class:`dict` of (k, v) to format as - `k1="v1"; k2="v2"; ...`. + A sequence of (k, v) typles or a :class:`dict` of (k, v) to format + as `k1="v1"; k2="v2"; ...`. """ parts = [] iterable = header_parts @@ -158,7 +153,8 @@ class RequestField(object): lines.append('\r\n') return '\r\n'.join(lines) - def make_multipart(self, content_disposition=None, content_type=None, content_location=None): + def make_multipart(self, content_disposition=None, content_type=None, + content_location=None): """ Makes this request field into a multipart request field. @@ -172,6 +168,10 @@ class RequestField(object): """ self.headers['Content-Disposition'] = content_disposition or 'form-data' - self.headers['Content-Disposition'] += '; '.join(['', self._render_parts((('name', self._name), ('filename', self._filename)))]) + self.headers['Content-Disposition'] += '; '.join([ + '', self._render_parts( + (('name', self._name), ('filename', self._filename)) + ) + ]) self.headers['Content-Type'] = content_type self.headers['Content-Location'] = content_location diff --git a/requests/packages/urllib3/filepost.py b/requests/packages/urllib3/filepost.py index e8b30bdd..0fbf488d 100644 --- a/requests/packages/urllib3/filepost.py +++ b/requests/packages/urllib3/filepost.py @@ -1,11 +1,4 @@ -# urllib3/filepost.py -# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt) -# -# This module is part of urllib3 and is released under -# the MIT License: http://www.opensource.org/licenses/mit-license.php - import codecs -import mimetypes from uuid import uuid4 from io import BytesIO @@ -38,10 +31,10 @@ def iter_field_objects(fields): i = iter(fields) for field in i: - if isinstance(field, RequestField): - yield field - else: - yield RequestField.from_tuples(*field) + if isinstance(field, RequestField): + yield field + else: + yield RequestField.from_tuples(*field) def iter_fields(fields): diff --git a/requests/packages/urllib3/packages/ordered_dict.py b/requests/packages/urllib3/packages/ordered_dict.py index 7f8ee154..4479363c 100644 --- a/requests/packages/urllib3/packages/ordered_dict.py +++ b/requests/packages/urllib3/packages/ordered_dict.py @@ -2,7 +2,6 @@ # Passes Python2.7's test suite and incorporates all the latest updates. # Copyright 2009 Raymond Hettinger, released under the MIT License. # http://code.activestate.com/recipes/576693/ - try: from thread import get_ident as _get_ident except ImportError: diff --git a/requests/packages/urllib3/poolmanager.py b/requests/packages/urllib3/poolmanager.py index f18ff2bb..515dc962 100644 --- a/requests/packages/urllib3/poolmanager.py +++ b/requests/packages/urllib3/poolmanager.py @@ -1,9 +1,3 @@ -# urllib3/poolmanager.py -# Copyright 2008-2014 Andrey Petrov and contributors (see CONTRIBUTORS.txt) -# -# This module is part of urllib3 and is released under -# the MIT License: http://www.opensource.org/licenses/mit-license.php - import logging try: # Python 3 @@ -14,8 +8,10 @@ except ImportError: from ._collections import RecentlyUsedContainer from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool from .connectionpool import port_by_scheme +from .exceptions import LocationValueError from .request import RequestMethods -from .util import parse_url +from .util.url import parse_url +from .util.retry import Retry __all__ = ['PoolManager', 'ProxyManager', 'proxy_from_url'] @@ -49,7 +45,7 @@ class PoolManager(RequestMethods): Additional parameters are used to create fresh :class:`urllib3.connectionpool.ConnectionPool` instances. - Example: :: + Example:: >>> manager = PoolManager(num_pools=2) >>> r = manager.request('GET', 'http://google.com/') @@ -102,10 +98,11 @@ class PoolManager(RequestMethods): ``urllib3.connectionpool.port_by_scheme``. """ + if not host: + raise LocationValueError("No host specified.") + scheme = scheme or 'http' - port = port or port_by_scheme.get(scheme, 80) - pool_key = (scheme, host, port) with self.pools.lock: @@ -118,6 +115,7 @@ class PoolManager(RequestMethods): # Make a fresh ConnectionPool of the desired type pool = self._new_pool(scheme, host, port) self.pools[pool_key] = pool + return pool def connection_from_url(self, url): @@ -161,13 +159,18 @@ class PoolManager(RequestMethods): # Support relative URLs for redirecting. redirect_location = urljoin(url, redirect_location) - # RFC 2616, Section 10.3.4 + # RFC 7231, Section 6.4.4 if response.status == 303: method = 'GET' - log.info("Redirecting %s -> %s" % (url, redirect_location)) - kw['retries'] = kw.get('retries', 3) - 1 # Persist retries countdown + retries = kw.get('retries') + if not isinstance(retries, Retry): + retries = Retry.from_int(retries, redirect=redirect) + + kw['retries'] = retries.increment(method, redirect_location) kw['redirect'] = redirect + + log.info("Redirecting %s -> %s" % (url, redirect_location)) return self.urlopen(method, redirect_location, **kw) @@ -208,12 +211,16 @@ class ProxyManager(PoolManager): if not proxy.port: port = port_by_scheme.get(proxy.scheme, 80) proxy = proxy._replace(port=port) + + assert proxy.scheme in ("http", "https"), \ + 'Not supported proxy scheme %s' % proxy.scheme + self.proxy = proxy self.proxy_headers = proxy_headers or {} - assert self.proxy.scheme in ("http", "https"), \ - 'Not supported proxy scheme %s' % self.proxy.scheme + connection_pool_kw['_proxy'] = self.proxy connection_pool_kw['_proxy_headers'] = self.proxy_headers + super(ProxyManager, self).__init__( num_pools, headers, **connection_pool_kw) @@ -248,10 +255,10 @@ class ProxyManager(PoolManager): # For proxied HTTPS requests, httplib sets the necessary headers # on the CONNECT to the proxy. For HTTP, we'll definitely # need to set 'Host' at the very least. - kw['headers'] = self._set_proxy_headers(url, kw.get('headers', - self.headers)) + headers = kw.get('headers', self.headers) + kw['headers'] = self._set_proxy_headers(url, headers) - return super(ProxyManager, self).urlopen(method, url, redirect, **kw) + return super(ProxyManager, self).urlopen(method, url, redirect=redirect, **kw) def proxy_from_url(url, **kw): diff --git a/requests/packages/urllib3/request.py b/requests/packages/urllib3/request.py index 2a92cc20..51fe2386 100644 --- a/requests/packages/urllib3/request.py +++ b/requests/packages/urllib3/request.py @@ -1,9 +1,3 @@ -# urllib3/request.py -# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt) -# -# This module is part of urllib3 and is released under -# the MIT License: http://www.opensource.org/licenses/mit-license.php - try: from urllib.parse import urlencode except ImportError: @@ -26,8 +20,8 @@ class RequestMethods(object): Specifically, - :meth:`.request_encode_url` is for sending requests whose fields are encoded - in the URL (such as GET, HEAD, DELETE). + :meth:`.request_encode_url` is for sending requests whose fields are + encoded in the URL (such as GET, HEAD, DELETE). :meth:`.request_encode_body` is for sending requests whose fields are encoded in the *body* of the request using multipart or www-form-urlencoded @@ -51,7 +45,7 @@ class RequestMethods(object): def urlopen(self, method, url, body=None, headers=None, encode_multipart=True, multipart_boundary=None, - **kw): # Abstract + **kw): # Abstract raise NotImplemented("Classes extending RequestMethods must implement " "their own ``urlopen`` method.") @@ -61,8 +55,8 @@ class RequestMethods(object): ``fields`` based on the ``method`` used. This is a convenience method that requires the least amount of manual - effort. It can be used in most situations, while still having the option - to drop down to more specific methods when necessary, such as + effort. It can be used in most situations, while still having the + option to drop down to more specific methods when necessary, such as :meth:`request_encode_url`, :meth:`request_encode_body`, or even the lowest level :meth:`urlopen`. """ @@ -70,12 +64,12 @@ class RequestMethods(object): if method in self._encode_url_methods: return self.request_encode_url(method, url, fields=fields, - headers=headers, - **urlopen_kw) + headers=headers, + **urlopen_kw) else: return self.request_encode_body(method, url, fields=fields, - headers=headers, - **urlopen_kw) + headers=headers, + **urlopen_kw) def request_encode_url(self, method, url, fields=None, **urlopen_kw): """ @@ -94,18 +88,18 @@ class RequestMethods(object): the body. This is useful for request methods like POST, PUT, PATCH, etc. When ``encode_multipart=True`` (default), then - :meth:`urllib3.filepost.encode_multipart_formdata` is used to encode the - payload with the appropriate content type. Otherwise + :meth:`urllib3.filepost.encode_multipart_formdata` is used to encode + the payload with the appropriate content type. Otherwise :meth:`urllib.urlencode` is used with the 'application/x-www-form-urlencoded' content type. Multipart encoding must be used when posting files, and it's reasonably - safe to use it in other times too. However, it may break request signing, - such as with OAuth. + safe to use it in other times too. However, it may break request + signing, such as with OAuth. Supports an optional ``fields`` parameter of key/value strings AND key/filetuple. A filetuple is a (filename, data, MIME type) tuple where - the MIME type is optional. For example: :: + the MIME type is optional. For example:: fields = { 'foo': 'bar', @@ -119,17 +113,17 @@ class RequestMethods(object): When uploading a file, providing a filename (the first parameter of the tuple) is optional but recommended to best mimick behavior of browsers. - Note that if ``headers`` are supplied, the 'Content-Type' header will be - overwritten because it depends on the dynamic random boundary string + Note that if ``headers`` are supplied, the 'Content-Type' header will + be overwritten because it depends on the dynamic random boundary string which is used to compose the body of the request. The random boundary string can be explicitly set with the ``multipart_boundary`` parameter. """ if encode_multipart: - body, content_type = encode_multipart_formdata(fields or {}, - boundary=multipart_boundary) + body, content_type = encode_multipart_formdata( + fields or {}, boundary=multipart_boundary) else: body, content_type = (urlencode(fields or {}), - 'application/x-www-form-urlencoded') + 'application/x-www-form-urlencoded') if headers is None: headers = self.headers diff --git a/requests/packages/urllib3/response.py b/requests/packages/urllib3/response.py index db441828..7e0d47fa 100644 --- a/requests/packages/urllib3/response.py +++ b/requests/packages/urllib3/response.py @@ -1,22 +1,14 @@ -# urllib3/response.py -# Copyright 2008-2013 Andrey Petrov and contributors (see CONTRIBUTORS.txt) -# -# This module is part of urllib3 and is released under -# the MIT License: http://www.opensource.org/licenses/mit-license.php - - -import logging import zlib import io +from socket import timeout as SocketTimeout from ._collections import HTTPHeaderDict -from .exceptions import DecodeError +from .exceptions import ProtocolError, DecodeError, ReadTimeoutError from .packages.six import string_types as basestring, binary_type -from .util import is_fp_closed +from .connection import HTTPException, BaseSSLError +from .util.response import is_fp_closed -log = logging.getLogger(__name__) - class DeflateDecoder(object): @@ -91,11 +83,14 @@ class HTTPResponse(io.IOBase): self.decode_content = decode_content self._decoder = None - self._body = body if body and isinstance(body, basestring) else None + self._body = None self._fp = None self._original_response = original_response self._fp_bytes_read = 0 + if body and isinstance(body, (basestring, binary_type)): + self._body = body + self._pool = pool self._connection = connection @@ -163,8 +158,8 @@ class HTTPResponse(io.IOBase): after having ``.read()`` the file object. (Overridden if ``amt`` is set.) """ - # Note: content-encoding value should be case-insensitive, per RFC 2616 - # Section 3.5 + # 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: if content_encoding in self.CONTENT_DECODERS: @@ -178,23 +173,42 @@ class HTTPResponse(io.IOBase): flush_decoder = False try: - if amt is None: - # cStringIO doesn't like amt=None - 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() + try: + if amt is None: + # cStringIO doesn't like amt=None + 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 + + 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) self._fp_bytes_read += len(data) @@ -204,8 +218,7 @@ class HTTPResponse(io.IOBase): except (IOError, zlib.error) as e: raise DecodeError( "Received response with content-encoding: %s, but " - "failed to decode it." % content_encoding, - e) + "failed to decode it." % content_encoding, e) if flush_decoder and decode_content and self._decoder: buf = self._decoder.decompress(binary_type()) @@ -242,7 +255,6 @@ class HTTPResponse(io.IOBase): if data: yield data - @classmethod def from_httplib(ResponseCls, r, **response_kw): """ @@ -297,7 +309,7 @@ class HTTPResponse(io.IOBase): elif hasattr(self._fp, "fileno"): return self._fp.fileno() else: - raise IOError("The file-like object this HTTPResponse is wrapped " + raise IOError("The file-like object this HTTPResponse is wrapped " "around has no file descriptor") def flush(self): diff --git a/requests/packages/urllib3/util/__init__.py b/requests/packages/urllib3/util/__init__.py index a40185ee..8becc814 100644 --- a/requests/packages/urllib3/util/__init__.py +++ b/requests/packages/urllib3/util/__init__.py @@ -1,9 +1,4 @@ -# urllib3/util/__init__.py -# Copyright 2008-2014 Andrey Petrov and contributors (see CONTRIBUTORS.txt) -# -# This module is part of urllib3 and is released under -# the MIT License: http://www.opensource.org/licenses/mit-license.php - +# For backwards compatibility, provide imports that used to be here. from .connection import is_connection_dropped from .request import make_headers from .response import is_fp_closed @@ -19,6 +14,8 @@ from .timeout import ( current_time, Timeout, ) + +from .retry import Retry from .url import ( get_host, parse_url, diff --git a/requests/packages/urllib3/util/connection.py b/requests/packages/urllib3/util/connection.py index 8deeab5c..2156993a 100644 --- a/requests/packages/urllib3/util/connection.py +++ b/requests/packages/urllib3/util/connection.py @@ -1,4 +1,4 @@ -from socket import error as SocketError +import socket try: from select import poll, POLLIN except ImportError: # `poll` doesn't exist on OSX and other platforms @@ -8,6 +8,7 @@ except ImportError: # `poll` doesn't exist on OSX and other platforms except ImportError: # `select` doesn't exist on AppEngine. select = False + def is_connection_dropped(conn): # Platform-specific """ Returns True if the connection is dropped and should be closed. @@ -22,7 +23,7 @@ def is_connection_dropped(conn): # Platform-specific if sock is False: # Platform-specific: AppEngine return False if sock is None: # Connection already closed (such as by httplib). - return False + return True if not poll: if not select: # Platform-specific: AppEngine @@ -30,7 +31,7 @@ def is_connection_dropped(conn): # Platform-specific try: return select([sock], [], [], 0.0)[0] - except SocketError: + except socket.error: return True # This version is better on platforms that support it. @@ -42,4 +43,55 @@ def is_connection_dropped(conn): # Platform-specific return True +# This function is copied from socket.py in the Python 2.7 standard +# library test suite. Added to its signature is only `socket_options`. +def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, + source_address=None, socket_options=None): + """Connect to *address* and return the socket object. + Convenience function. Connect to *address* (a 2-tuple ``(host, + port)``) and return the socket object. Passing the optional + *timeout* parameter will set the timeout on the socket instance + before attempting to connect. If no *timeout* is supplied, the + global default timeout setting returned by :func:`getdefaulttimeout` + is used. If *source_address* is set it must be a tuple of (host, port) + for the socket to bind as a source address before making the connection. + An host of '' or port 0 tells the OS to use the default. + """ + + host, port = address + err = None + for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): + af, socktype, proto, canonname, sa = res + sock = None + try: + sock = socket.socket(af, socktype, proto) + + # If provided, set socket level options before connecting. + # This is the only addition urllib3 makes to this function. + _set_socket_options(sock, socket_options) + + if timeout is not socket._GLOBAL_DEFAULT_TIMEOUT: + sock.settimeout(timeout) + if source_address: + sock.bind(source_address) + sock.connect(sa) + return sock + + except socket.error as _: + err = _ + if sock is not None: + sock.close() + + if err is not None: + raise err + else: + raise socket.error("getaddrinfo returns an empty list") + + +def _set_socket_options(sock, options): + if options is None: + return + + for opt in options: + sock.setsockopt(*opt) diff --git a/requests/packages/urllib3/util/request.py b/requests/packages/urllib3/util/request.py index d48d6513..bc64f6b1 100644 --- a/requests/packages/urllib3/util/request.py +++ b/requests/packages/urllib3/util/request.py @@ -1,13 +1,12 @@ from base64 import b64encode -from ..packages import six - +from ..packages.six import b ACCEPT_ENCODING = 'gzip,deflate' def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, - basic_auth=None, proxy_basic_auth=None): + basic_auth=None, proxy_basic_auth=None, disable_cache=None): """ Shortcuts for generating request headers. @@ -32,7 +31,10 @@ def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, Colon-separated username:password string for 'proxy-authorization: basic ...' auth header. - Example: :: + :param disable_cache: + If ``True``, adds 'cache-control: no-cache' header. + + Example:: >>> make_headers(keep_alive=True, user_agent="Batman/1.0") {'connection': 'keep-alive', 'user-agent': 'Batman/1.0'} @@ -57,12 +59,13 @@ def make_headers(keep_alive=None, accept_encoding=None, user_agent=None, if basic_auth: headers['authorization'] = 'Basic ' + \ - b64encode(six.b(basic_auth)).decode('utf-8') + b64encode(b(basic_auth)).decode('utf-8') if proxy_basic_auth: headers['proxy-authorization'] = 'Basic ' + \ - b64encode(six.b(proxy_basic_auth)).decode('utf-8') + b64encode(b(proxy_basic_auth)).decode('utf-8') + + if disable_cache: + headers['cache-control'] = 'no-cache' return headers - - diff --git a/requests/packages/urllib3/util/retry.py b/requests/packages/urllib3/util/retry.py new file mode 100644 index 00000000..90131977 --- /dev/null +++ b/requests/packages/urllib3/util/retry.py @@ -0,0 +1,279 @@ +import time +import logging + +from ..exceptions import ( + ProtocolError, + ConnectTimeoutError, + ReadTimeoutError, + MaxRetryError, +) +from ..packages import six + + +log = logging.getLogger(__name__) + + +class Retry(object): + """ Retry configuration. + + Each retry attempt will create a new Retry object with updated values, so + they can be safely reused. + + Retries can be defined as a default for a pool:: + + retries = Retry(connect=5, read=2, redirect=5) + http = PoolManager(retries=retries) + response = http.request('GET', 'http://example.com/') + + Or per-request (which overrides the default for the pool):: + + response = http.request('GET', 'http://example.com/', retries=Retry(10)) + + Retries can be disabled by passing ``False``:: + + response = http.request('GET', 'http://example.com/', retries=False) + + Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless + retries are disabled, in which case the causing exception will be raised. + + + :param int total: + Total number of retries to allow. Takes precedence over other counts. + + Set to ``None`` to remove this constraint and fall back on other + counts. It's a good idea to set this to some sensibly-high value to + account for unexpected edge cases and avoid infinite retry loops. + + Set to ``0`` to fail on the first retry. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param int connect: + How many connection-related errors to retry on. + + These are errors raised before the request is sent to the remote server, + which we assume has not triggered the server to process the request. + + Set to ``0`` to fail on the first retry of this type. + + :param int read: + How many times to retry on read errors. + + These errors are raised after the request was sent to the server, so the + request may have side-effects. + + Set to ``0`` to fail on the first retry of this type. + + :param int redirect: + How many redirects to perform. Limit this to avoid infinite redirect + loops. + + A redirect is a HTTP response with a status code 301, 302, 303, 307 or + 308. + + Set to ``0`` to fail on the first retry of this type. + + Set to ``False`` to disable and imply ``raise_on_redirect=False``. + + :param iterable method_whitelist: + Set of uppercased HTTP method verbs that we should retry on. + + By default, we only retry on methods which are considered to be + indempotent (multiple requests with the same parameters end with the + same state). See :attr:`Retry.DEFAULT_METHOD_WHITELIST`. + + :param iterable status_forcelist: + A set of HTTP status codes that we should force a retry on. + + By default, this is disabled with ``None``. + + :param float backoff_factor: + A backoff factor to apply between attempts. urllib3 will sleep for:: + + {backoff factor} * (2 ^ ({number of total retries} - 1)) + + 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 + than :attr:`Retry.MAX_BACKOFF`. + + By default, backoff is disabled (set to 0). + + :param bool raise_on_redirect: Whether, if the number of redirects is + exhausted, to raise a MaxRetryError, or to return a response with a + response code in the 3xx range. + """ + + DEFAULT_METHOD_WHITELIST = frozenset([ + 'HEAD', 'GET', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']) + + #: Maximum backoff time. + BACKOFF_MAX = 120 + + def __init__(self, total=10, connect=None, read=None, redirect=None, + method_whitelist=DEFAULT_METHOD_WHITELIST, status_forcelist=None, + backoff_factor=0, raise_on_redirect=True, _observed_errors=0): + + self.total = total + self.connect = connect + self.read = read + + if redirect is False or total is False: + redirect = 0 + raise_on_redirect = False + + self.redirect = redirect + self.status_forcelist = status_forcelist or set() + self.method_whitelist = method_whitelist + self.backoff_factor = backoff_factor + self.raise_on_redirect = raise_on_redirect + self._observed_errors = _observed_errors # TODO: use .history instead? + + def new(self, **kw): + params = dict( + total=self.total, + connect=self.connect, read=self.read, redirect=self.redirect, + method_whitelist=self.method_whitelist, + status_forcelist=self.status_forcelist, + backoff_factor=self.backoff_factor, + raise_on_redirect=self.raise_on_redirect, + _observed_errors=self._observed_errors, + ) + params.update(kw) + return type(self)(**params) + + @classmethod + def from_int(cls, retries, redirect=True, default=None): + """ Backwards-compatibility for the old retries format.""" + if retries is None: + retries = default if default is not None else cls.DEFAULT + + if isinstance(retries, Retry): + return retries + + redirect = bool(redirect) and None + new_retries = cls(retries, redirect=redirect) + log.debug("Converted retries value: %r -> %r" % (retries, new_retries)) + return new_retries + + def get_backoff_time(self): + """ Formula for computing the current backoff + + :rtype: float + """ + if self._observed_errors <= 1: + return 0 + + backoff_value = self.backoff_factor * (2 ** (self._observed_errors - 1)) + return min(self.BACKOFF_MAX, backoff_value) + + def sleep(self): + """ Sleep between retry attempts using an exponential backoff. + + By default, the backoff factor is 0 and this method will return + immediately. + """ + backoff = self.get_backoff_time() + if backoff <= 0: + return + time.sleep(backoff) + + def _is_connection_error(self, err): + """ Errors when we're fairly sure that the server did not receive the + request, so it should be safe to retry. + """ + return isinstance(err, ConnectTimeoutError) + + def _is_read_error(self, err): + """ Errors that occur after the request has been started, so we can't + assume that the server did not process any of it. + """ + return isinstance(err, (ReadTimeoutError, ProtocolError)) + + def is_forced_retry(self, method, status_code): + """ Is this method/response retryable? (Based on method/codes whitelists) + """ + if self.method_whitelist and method.upper() not in self.method_whitelist: + return False + + return self.status_forcelist and status_code in self.status_forcelist + + def is_exhausted(self): + """ Are we out of retries? + """ + retry_counts = (self.total, self.connect, self.read, self.redirect) + retry_counts = list(filter(None, retry_counts)) + if not retry_counts: + return False + + return min(retry_counts) < 0 + + def increment(self, method=None, url=None, response=None, error=None, _pool=None, _stacktrace=None): + """ Return a new Retry object with incremented retry counters. + + :param response: A response object, or None, if the server did not + return a response. + :type response: :class:`~urllib3.response.HTTPResponse` + :param Exception error: An error encountered during the request, or + None if the response was received successfully. + + :return: A new ``Retry`` object. + """ + if self.total is False and error: + # Disabled, indicate to re-raise the error. + raise six.reraise(type(error), error, _stacktrace) + + total = self.total + if total is not None: + total -= 1 + + _observed_errors = self._observed_errors + connect = self.connect + read = self.read + redirect = self.redirect + + if error and self._is_connection_error(error): + # Connect retry? + if connect is False: + raise six.reraise(type(error), error, _stacktrace) + elif connect is not None: + connect -= 1 + _observed_errors += 1 + + elif error and self._is_read_error(error): + # Read retry? + if read is False: + raise six.reraise(type(error), error, _stacktrace) + elif read is not None: + read -= 1 + _observed_errors += 1 + + elif response and response.get_redirect_location(): + # Redirect retry? + if redirect is not None: + redirect -= 1 + + else: + # FIXME: Nothing changed, scenario doesn't make sense. + _observed_errors += 1 + + new_retry = self.new( + total=total, + connect=connect, read=read, redirect=redirect, + _observed_errors=_observed_errors) + + if new_retry.is_exhausted(): + raise MaxRetryError(_pool, url, error) + + log.debug("Incremented Retry for (url='%s'): %r" % (url, new_retry)) + + return new_retry + + + def __repr__(self): + return ('{cls.__name__}(total={self.total}, connect={self.connect}, ' + 'read={self.read}, redirect={self.redirect})').format( + cls=type(self), self=self) + + +# For backwards compatibility (equivalent to pre-v1.9): +Retry.DEFAULT = Retry(3) diff --git a/requests/packages/urllib3/util/ssl_.py b/requests/packages/urllib3/util/ssl_.py index dee4b876..9cfe2d2a 100644 --- a/requests/packages/urllib3/util/ssl_.py +++ b/requests/packages/urllib3/util/ssl_.py @@ -34,10 +34,9 @@ def assert_fingerprint(cert, fingerprint): } fingerprint = fingerprint.replace(':', '').lower() + digest_length, odd = divmod(len(fingerprint), 2) - digest_length, rest = divmod(len(fingerprint), 2) - - if rest or digest_length not in hashfunc_map: + if odd or digest_length not in hashfunc_map: raise SSLError('Fingerprint is of invalid length.') # We need encode() here for py32; works on py2 and p33. diff --git a/requests/packages/urllib3/util/timeout.py b/requests/packages/urllib3/util/timeout.py index 4f947cb2..ea7027f3 100644 --- a/requests/packages/urllib3/util/timeout.py +++ b/requests/packages/urllib3/util/timeout.py @@ -1,32 +1,49 @@ +# The default socket timeout, used by httplib to indicate that no timeout was +# specified by the user from socket import _GLOBAL_DEFAULT_TIMEOUT import time from ..exceptions import TimeoutStateError +# A sentinel value to indicate that no timeout was specified by the user in +# urllib3 +_Default = object() 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. """ return time.time() -_Default = object() -# The default timeout to use for socket connections. This is the attribute used -# by httplib to define the default timeout - - class Timeout(object): - """ - Utility object for storing timeout values. + """ Timeout configuration. - Example usage: + Timeouts can be defined as a default for a pool:: - .. code-block:: python + timeout = Timeout(connect=2.0, read=7.0) + http = PoolManager(timeout=timeout) + response = http.request('GET', 'http://example.com/') - timeout = urllib3.util.Timeout(connect=2.0, read=7.0) - pool = HTTPConnectionPool('www.google.com', 80, timeout=timeout) - pool.request(...) # Etc, etc + Or per-request (which overrides the default for the pool):: + + response = http.request('GET', 'http://example.com/', timeout=Timeout(10)) + + Timeouts can be disabled by setting all the parameters to ``None``:: + + no_timeout = Timeout(connect=None, read=None) + response = http.request('GET', 'http://example.com/, timeout=no_timeout) + + + :param total: + This combines the connect and read timeouts into one; the read timeout + will be set to the time leftover from the connect attempt. In the + event that both a connect timeout and a total are specified, or a read + timeout and a total are specified, the shorter timeout will be applied. + + Defaults to None. + + :type total: integer, float, or None :param connect: The maximum amount of time to wait for a connection attempt to a server @@ -47,25 +64,15 @@ class Timeout(object): :type read: integer, float, or None - :param total: - This combines the connect and read timeouts into one; the read timeout - will be set to the time leftover from the connect attempt. In the - event that both a connect timeout and a total are specified, or a read - timeout and a total are specified, the shorter timeout will be applied. - - Defaults to None. - - :type total: integer, float, or None - .. note:: Many factors can affect the total amount of time for urllib3 to return - an HTTP response. Specifically, Python's DNS resolver does not obey the - timeout specified on the socket. Other factors that can affect total - request time include high CPU load, high swap, the program running at a - low priority level, or other behaviors. The observed running time for - urllib3 to return a response may be greater than the value passed to - `total`. + an HTTP response. + + For example, Python's DNS resolver does not obey the timeout specified + on the socket. Other factors that can affect total request time include + high CPU load, high swap, the program running at a low priority level, + or other behaviors. In addition, the read and total timeouts only measure the time between read operations on the socket connecting the client and the server, @@ -73,8 +80,8 @@ class Timeout(object): response. For most requests, the timeout is raised because the server has not sent the first byte in the specified time. This is not always the case; if a server streams one byte every fifteen seconds, a timeout - of 20 seconds will not ever trigger, even though the request will - take several minutes to complete. + of 20 seconds will not trigger, even though the request will take + several minutes to complete. If your goal is to cut off any request after a set amount of wall clock time, consider having a second "watcher" thread to cut off a slow @@ -94,17 +101,16 @@ class Timeout(object): return '%s(connect=%r, read=%r, total=%r)' % ( type(self).__name__, self._connect, self._read, self.total) - @classmethod def _validate_timeout(cls, value, name): - """ Check that a timeout attribute is valid + """ Check that a timeout attribute is valid. :param value: The timeout value to validate - :param name: The name of the timeout attribute to validate. This is used - for clear error messages - :return: the value - :raises ValueError: if the type is not an integer or a float, or if it - is a numeric value less than zero + :param name: The name of the timeout attribute to validate. This is + used to specify in error messages. + :return: The validated and casted version of the given value. + :raises ValueError: If the type is not an integer or a float, or if it + is a numeric value less than zero. """ if value is _Default: return cls.DEFAULT_TIMEOUT @@ -123,7 +129,7 @@ class Timeout(object): raise ValueError("Attempted to set %s timeout to %s, but the " "timeout cannot be set to a value less " "than 0." % (name, value)) - except TypeError: # Python 3 + except TypeError: # Python 3 raise ValueError("Timeout value %s was %s, but it must be an " "int or float." % (name, value)) @@ -135,12 +141,12 @@ class Timeout(object): The timeout value used by httplib.py sets the same timeout on the connect(), and recv() socket requests. This creates a :class:`Timeout` - object that sets the individual timeouts to the ``timeout`` value passed - to this function. + object that sets the individual timeouts to the ``timeout`` value + passed to this function. - :param timeout: The legacy timeout value + :param timeout: The legacy timeout value. :type timeout: integer, float, sentinel default object, or None - :return: a Timeout object + :return: Timeout object :rtype: :class:`Timeout` """ return Timeout(read=timeout, connect=timeout) @@ -174,7 +180,7 @@ class Timeout(object): def get_connect_duration(self): """ Gets the time elapsed since the call to :meth:`start_connect`. - :return: the elapsed time + :return: Elapsed time. :rtype: float :raises urllib3.exceptions.TimeoutStateError: if you attempt to get duration for a timer that hasn't been started. @@ -191,7 +197,7 @@ class Timeout(object): This will be a positive float or integer, the value None (never timeout), or the default system timeout. - :return: the connect timeout + :return: Connect timeout. :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None """ if self.total is None: @@ -214,7 +220,7 @@ class Timeout(object): established, a :exc:`~urllib3.exceptions.TimeoutStateError` will be raised. - :return: the value to use for the read timeout + :return: Value to use for the read timeout. :rtype: int, float, :attr:`Timeout.DEFAULT_TIMEOUT` or None :raises urllib3.exceptions.TimeoutStateError: If :meth:`start_connect` has not yet been called on this object. @@ -223,7 +229,7 @@ class Timeout(object): self.total is not self.DEFAULT_TIMEOUT and self._read is not None and 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: return self._read return max(0, min(self.total - self.get_connect_duration(), diff --git a/requests/packages/urllib3/util/url.py b/requests/packages/urllib3/util/url.py index 362d2160..487d456c 100644 --- a/requests/packages/urllib3/util/url.py +++ b/requests/packages/urllib3/util/url.py @@ -3,15 +3,20 @@ from collections import namedtuple from ..exceptions import LocationParseError -class Url(namedtuple('Url', ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'])): +url_attrs = ['scheme', 'auth', 'host', 'port', 'path', 'query', 'fragment'] + + +class Url(namedtuple('Url', url_attrs)): """ Datastructure for representing an HTTP URL. Used as a return value for :func:`parse_url`. """ slots = () - def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None, query=None, fragment=None): - return super(Url, cls).__new__(cls, scheme, auth, host, port, path, query, fragment) + def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None, + query=None, fragment=None): + return super(Url, cls).__new__(cls, scheme, auth, host, port, path, + query, fragment) @property def hostname(self): @@ -43,7 +48,7 @@ def split_first(s, delims): If not found, then the first part is the full input string. - Example: :: + Example:: >>> split_first('foo/bar?baz', '?/=') ('foo', 'bar?baz', '/') @@ -76,7 +81,7 @@ def parse_url(url): Partly backwards-compatible with :mod:`urlparse`. - Example: :: + Example:: >>> parse_url('http://google.com/mail/') Url(scheme='http', host='google.com', port=None, path='/', ...) @@ -91,6 +96,10 @@ def parse_url(url): # Additionally, this implementations does silly things to be optimal # on CPython. + if not url: + # Empty + return Url() + scheme = None auth = None host = None From 170d6269eb004aed085d2e770700bccf92ab0e40 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 21 Jul 2014 15:00:14 -0500 Subject: [PATCH 161/545] Fix test bug courtesy of @shazow --- requests/adapters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requests/adapters.py b/requests/adapters.py index eb7a2d28..d72cb142 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -11,6 +11,7 @@ and maintain connections. import socket from .models import Response +from .packages.urllib3 import Retry from .packages.urllib3.poolmanager import PoolManager, proxy_from_url from .packages.urllib3.response import HTTPResponse from .packages.urllib3.util import Timeout as TimeoutSauce @@ -323,7 +324,7 @@ class HTTPAdapter(BaseAdapter): assert_same_host=False, preload_content=False, decode_content=False, - retries=self.max_retries, + retries=Retry(self.max_retries, read=False), timeout=timeout ) From 57b861148eda51a0be752d133f6ba19c22936e75 Mon Sep 17 00:00:00 2001 From: Tyler Kennedy Date: Tue, 22 Jul 2014 12:38:08 -0400 Subject: [PATCH 162/545] Update faq.rst Removed an unnecessary "to" in the "What are "hostname doesn't match" errors?" section. --- docs/community/faq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/community/faq.rst b/docs/community/faq.rst index 5b3ecb81..1715b64e 100644 --- a/docs/community/faq.rst +++ b/docs/community/faq.rst @@ -75,7 +75,7 @@ Server-Name-Indication. `Server-Name-Indication`_, or SNI, is an official extension to SSL where the client tells the server what hostname it is contacting. This enables `virtual -hosting`_ on SSL protected sites, the server being to able to respond with a +hosting`_ on SSL protected sites, the server being able to respond with a certificate appropriate for the hostname the client is contacting. Python3's SSL module includes native support for SNI. This support has not been From 35f7bcdaa2734aca43a8462ab717af47342ca0c8 Mon Sep 17 00:00:00 2001 From: Tyler Kennedy Date: Tue, 22 Jul 2014 13:10:40 -0400 Subject: [PATCH 163/545] Update faq.rst Revised wording based on feedback from @sigmavirus24 --- docs/community/faq.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/community/faq.rst b/docs/community/faq.rst index 1715b64e..c5daee46 100644 --- a/docs/community/faq.rst +++ b/docs/community/faq.rst @@ -74,9 +74,10 @@ you're using Python 2.6 or 2.7, a possible explanation is that you need Server-Name-Indication. `Server-Name-Indication`_, or SNI, is an official extension to SSL where the -client tells the server what hostname it is contacting. This enables `virtual -hosting`_ on SSL protected sites, the server being able to respond with a -certificate appropriate for the hostname the client is contacting. +client tells the server what hostname it is contacting. This is important +when servers are using `Virtual Hosting`_. When such servers are hosting +more than one SSL site they need to be able to return the appropriate +certificate based on the hostname the client is connecting to. Python3's SSL module includes native support for SNI. This support has not been back ported to Python2. For information on using SNI with Requests on Python2 From 9eab9f7415e718cefc77f4bbe632abf8e089c555 Mon Sep 17 00:00:00 2001 From: Roman Levin Date: Thu, 24 Jul 2014 23:00:50 +0200 Subject: [PATCH 164/545] Handle socket errors in iter_content --- requests/models.py | 5 ++++- test_requests.py | 14 +++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index 0dc55568..2ababaf8 100644 --- a/requests/models.py +++ b/requests/models.py @@ -9,6 +9,7 @@ This module contains the primary objects that power Requests. import collections import datetime +import socket from io import BytesIO, UnsupportedOperation from .hooks import default_hooks @@ -22,7 +23,7 @@ from .packages.urllib3.util import parse_url from .packages.urllib3.exceptions import DecodeError from .exceptions import ( HTTPError, RequestException, MissingSchema, InvalidURL, - ChunkedEncodingError, ContentDecodingError) + ChunkedEncodingError, ContentDecodingError, ConnectionError) from .utils import ( guess_filename, get_auth_from_url, requote_uri, stream_decode_response_unicode, to_key_val_list, parse_header_links, @@ -640,6 +641,8 @@ class Response(object): raise ChunkedEncodingError(e) except DecodeError as e: raise ContentDecodingError(e) + except socket.error as e: + raise ConnectionError(e) except AttributeError: # Standard file-like object. while True: diff --git a/test_requests.py b/test_requests.py index 283353b9..004d6e8f 100755 --- a/test_requests.py +++ b/test_requests.py @@ -18,7 +18,7 @@ from requests.auth import HTTPDigestAuth, _basic_auth_str from requests.compat import ( Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3, builtin_str) from requests.cookies import cookiejar_from_dict, morsel_to_cookie -from requests.exceptions import InvalidURL, MissingSchema +from requests.exceptions import InvalidURL, MissingSchema, ConnectionError from requests.models import PreparedRequest from requests.structures import CaseInsensitiveDict from requests.sessions import SessionRedirectMixin @@ -720,6 +720,18 @@ class RequestsTestCase(unittest.TestCase): assert next(iter(r)) io.close() + def test_iter_content_handles_socket_error(self): + r = requests.Response() + import socket + + class RawMock(object): + def stream(self, chunk_size, decode_content=None): + raise socket.error() + + setattr(r, 'raw', RawMock()) + with pytest.raises(ConnectionError): + list(r.iter_content()) + def test_response_decode_unicode(self): """ When called with decode_unicode, Response.iter_content should always From cf01a743aff009b18c42c553a53640d44f026f5c Mon Sep 17 00:00:00 2001 From: Roman Levin Date: Thu, 24 Jul 2014 23:30:11 +0200 Subject: [PATCH 165/545] Replace setattr --- test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index 004d6e8f..694b779c 100755 --- a/test_requests.py +++ b/test_requests.py @@ -728,7 +728,7 @@ class RequestsTestCase(unittest.TestCase): def stream(self, chunk_size, decode_content=None): raise socket.error() - setattr(r, 'raw', RawMock()) + r.raw = RawMock() with pytest.raises(ConnectionError): list(r.iter_content()) From 47d0517d66e8cf5832768262221f0357ae134ad1 Mon Sep 17 00:00:00 2001 From: Josh Schneier Date: Tue, 29 Jul 2014 23:27:38 -0400 Subject: [PATCH 166/545] handle urllib3 api changes; closes #2045 --- requests/compat.py | 2 -- requests/models.py | 10 +++++----- test_requests.py | 12 ------------ 3 files changed, 5 insertions(+), 19 deletions(-) diff --git a/requests/compat.py b/requests/compat.py index 84d703b6..be5a1ed6 100644 --- a/requests/compat.py +++ b/requests/compat.py @@ -92,7 +92,6 @@ if is_py2: from Cookie import Morsel from StringIO import StringIO from .packages.urllib3.packages.ordered_dict import OrderedDict - from httplib import IncompleteRead builtin_str = str bytes = str @@ -108,7 +107,6 @@ elif is_py3: from http.cookies import Morsel from io import StringIO from collections import OrderedDict - from http.client import IncompleteRead builtin_str = str str = str diff --git a/requests/models.py b/requests/models.py index 2ababaf8..03ff627a 100644 --- a/requests/models.py +++ b/requests/models.py @@ -9,7 +9,6 @@ This module contains the primary objects that power Requests. import collections import datetime -import socket from io import BytesIO, UnsupportedOperation from .hooks import default_hooks @@ -20,7 +19,8 @@ from .cookies import cookiejar_from_dict, get_cookie_header from .packages.urllib3.fields import RequestField from .packages.urllib3.filepost import encode_multipart_formdata from .packages.urllib3.util import parse_url -from .packages.urllib3.exceptions import DecodeError +from .packages.urllib3.exceptions import ( + DecodeError, ReadTimeoutError, ProtocolError) from .exceptions import ( HTTPError, RequestException, MissingSchema, InvalidURL, ChunkedEncodingError, ContentDecodingError, ConnectionError) @@ -30,7 +30,7 @@ from .utils import ( iter_slices, guess_json_utf, super_len, to_native_string) from .compat import ( cookielib, urlunparse, urlsplit, urlencode, str, bytes, StringIO, - is_py2, chardet, json, builtin_str, basestring, IncompleteRead) + is_py2, chardet, json, builtin_str, basestring) from .status_codes import codes #: The set of HTTP status codes that indicate an automatically @@ -637,11 +637,11 @@ class Response(object): try: for chunk in self.raw.stream(chunk_size, decode_content=True): yield chunk - except IncompleteRead as e: + except ProtocolError as e: raise ChunkedEncodingError(e) except DecodeError as e: raise ContentDecodingError(e) - except socket.error as e: + except ReadTimeoutError as e: raise ConnectionError(e) except AttributeError: # Standard file-like object. diff --git a/test_requests.py b/test_requests.py index 694b779c..c9804517 100755 --- a/test_requests.py +++ b/test_requests.py @@ -720,18 +720,6 @@ class RequestsTestCase(unittest.TestCase): assert next(iter(r)) io.close() - def test_iter_content_handles_socket_error(self): - r = requests.Response() - import socket - - class RawMock(object): - def stream(self, chunk_size, decode_content=None): - raise socket.error() - - r.raw = RawMock() - with pytest.raises(ConnectionError): - list(r.iter_content()) - def test_response_decode_unicode(self): """ When called with decode_unicode, Response.iter_content should always From df641e74ffa294d30f0cc9bf951b7f9614aac18d Mon Sep 17 00:00:00 2001 From: ContinuousFunction Date: Sun, 17 Aug 2014 19:13:26 -0700 Subject: [PATCH 167/545] Tracking Previous Requests Addresses the issue brought up here: https://github.com/kennethreitz/requests/issues/1929 --- requests/sessions.py | 7 +++++++ test_requests.py | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/requests/sessions.py b/requests/sessions.py index 4c6fa2f2..a46957ff 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -91,10 +91,17 @@ class SessionRedirectMixin(object): """Receives a Response. Returns a generator of Responses.""" i = 0 + hist = [] #keep track of history while resp.is_redirect: prepared_request = req.copy() + if i > 0: + #create deep copy of the history and keep track of redirects + hist.append(resp) + new_hist = list(hist) + resp.history = new_hist + try: resp.content # Consume socket so it can be released except (ChunkedEncodingError, ContentDecodingError, RuntimeError): diff --git a/test_requests.py b/test_requests.py index c9804517..aa4a2745 100755 --- a/test_requests.py +++ b/test_requests.py @@ -974,6 +974,13 @@ class RequestsTestCase(unittest.TestCase): assert isinstance(s, builtin_str) assert s == "Basic dGVzdDp0ZXN0" + def test_requests_history_is_saved(self): + r = requests.get('https://httpbin.org/redirect/5') + count = 0 + for item in r.history: + assert len(item.history) == count + count = count + 1 + class TestContentEncodingDetection(unittest.TestCase): @@ -1364,6 +1371,7 @@ class TestRedirects: assert session.calls[-1] == send_call + @pytest.fixture def list_of_tuples(): return [ From 53d3cece000fb37bfe35977d01fcd0e5d04866ec Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 19 Aug 2014 12:53:26 -0400 Subject: [PATCH 168/545] 21m --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index df48ab5e..054118c4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -48,7 +48,7 @@ Testimonials Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku, PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington Post, Twitter, SoundCloud, Kippt, Readability, Sony, and Federal US Institutions -use Requests internally. It has been downloaded over 16,000,000 times from PyPI. +use Requests internally. It has been downloaded over 21,000,000 times from PyPI. **Armin Ronacher** Requests is the perfect example how beautiful an API can be with the From 2fc6e8a894a5378dd16b2653df9ccf875d0bddf8 Mon Sep 17 00:00:00 2001 From: ContinuousFunction Date: Tue, 19 Aug 2014 10:51:26 -0700 Subject: [PATCH 169/545] Update on Tracking Previous Requests Modified the comment in sessions.py and rewrote the test in test_requests.py --- requests/sessions.py | 2 +- test_requests.py | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index a46957ff..758e6eab 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -97,7 +97,7 @@ class SessionRedirectMixin(object): prepared_request = req.copy() if i > 0: - #create deep copy of the history and keep track of redirects + #update history and keep track of redirects hist.append(resp) new_hist = list(hist) resp.history = new_hist diff --git a/test_requests.py b/test_requests.py index aa4a2745..34ebd8ca 100755 --- a/test_requests.py +++ b/test_requests.py @@ -976,10 +976,11 @@ class RequestsTestCase(unittest.TestCase): def test_requests_history_is_saved(self): r = requests.get('https://httpbin.org/redirect/5') - count = 0 + total = r.history[-1].history + i = 0 for item in r.history: - assert len(item.history) == count - count = count + 1 + assert item.history == total[0:i] + i=i+1 class TestContentEncodingDetection(unittest.TestCase): From c7e087cf562d173ec62fa384aa028746798f96c8 Mon Sep 17 00:00:00 2001 From: ContinuousFunction Date: Thu, 21 Aug 2014 11:05:02 -0700 Subject: [PATCH 170/545] Added space and sentence case Added space and sentence case as requested by sigmavirus24. https://github.com/kennethreitz/requests/pull/2168 --- requests/sessions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index 758e6eab..07b6a32c 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -91,13 +91,13 @@ class SessionRedirectMixin(object): """Receives a Response. Returns a generator of Responses.""" i = 0 - hist = [] #keep track of history + hist = [] # keep track of history while resp.is_redirect: prepared_request = req.copy() if i > 0: - #update history and keep track of redirects + # Update history and keep track of redirects. hist.append(resp) new_hist = list(hist) resp.history = new_hist From 8bb8a9a37c694c95b9edb17474015a5193658cbf Mon Sep 17 00:00:00 2001 From: ContinuousFunction Date: Thu, 21 Aug 2014 11:12:36 -0700 Subject: [PATCH 171/545] Adding my name to authors. --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index b4412b9b..ebbae981 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -153,3 +153,4 @@ Patches and Suggestions - Erik Wickstrom (`@erikwickstrom `_) - Константин Подшумок (`@podshumok `_) - Ben Bass (`@codedstructure `_) +- Jonathan Wong From 5aa1881959378b37b13a488dfcbd740063874dd7 Mon Sep 17 00:00:00 2001 From: ContinuousFunction Date: Thu, 21 Aug 2014 11:16:59 -0700 Subject: [PATCH 172/545] Adding myself to authors. --- AUTHORS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index ebbae981..b5f4bf90 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -153,4 +153,4 @@ Patches and Suggestions - Erik Wickstrom (`@erikwickstrom `_) - Константин Подшумок (`@podshumok `_) - Ben Bass (`@codedstructure `_) -- Jonathan Wong +- Jonathan Wong (`@ContinuousFunction https://github.com/ContinuousFunction>`_) From 337b338187af83933a441141892bb32bc1e9837d Mon Sep 17 00:00:00 2001 From: ContinuousFunction Date: Thu, 21 Aug 2014 11:25:29 -0700 Subject: [PATCH 173/545] Adding my name to authors --- AUTHORS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index b5f4bf90..1ab53ef8 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -153,4 +153,4 @@ Patches and Suggestions - Erik Wickstrom (`@erikwickstrom `_) - Константин Подшумок (`@podshumok `_) - Ben Bass (`@codedstructure `_) -- Jonathan Wong (`@ContinuousFunction https://github.com/ContinuousFunction>`_) +- Jonathan Wong (`@ContinuousFunction `_) From 1b2602344fe783c5422602ff5f7d03bf7928c0f3 Mon Sep 17 00:00:00 2001 From: tat3657 Date: Fri, 22 Aug 2014 10:39:05 +0200 Subject: [PATCH 174/545] Documentation update : connection keep alive --- docs/user/advanced.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 503b3f86..bffc7c03 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -240,6 +240,9 @@ Note that connections are only released back to the pool for reuse once all body data has been read; be sure to either set ``stream`` to ``False`` or read the ``content`` property of the ``Response`` object. +Also note that you may have to set manually the ``'Connection': 'keep-alive'`` header to force the server keeping +the connection open. +This header is set by default by Chrome, Firefox, IE and opera but isn't set by urllib. Streaming Uploads ----------------- From 1c20d01f10e13a0c2f9348b062d04d5f222542db Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Fri, 22 Aug 2014 11:50:37 +0100 Subject: [PATCH 175/545] Cleanup grammar from previous commit. --- docs/user/advanced.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index bffc7c03..8eb888b1 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -240,9 +240,10 @@ Note that connections are only released back to the pool for reuse once all body data has been read; be sure to either set ``stream`` to ``False`` or read the ``content`` property of the ``Response`` object. -Also note that you may have to set manually the ``'Connection': 'keep-alive'`` header to force the server keeping -the connection open. -This header is set by default by Chrome, Firefox, IE and opera but isn't set by urllib. +For some older servers it may be necessary to manually set the +``Connection: keep-alive`` header. This will request that the server keep the +connection open. This does not guarantee that the connection will be kept open, +and generally is not necessary. Streaming Uploads ----------------- From e70179a6201ba2149d61356badd908900680821d Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Sat, 23 Aug 2014 10:02:27 -0700 Subject: [PATCH 176/545] Clarify which tests to run in the Makefile If you put your Virtualenv in the same folder as Requests (as I usually do), py.test will pick up any test_* files inside the virtualenv and attempt to run them too, leading to the following error: https://gist.github.com/kevinburke/1aa2b07e01de3a7daa15 Instead specify the test file we would like to run in the Makefile. --- Makefile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 3c838c93..7c858729 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,9 @@ init: pip install -r requirements.txt test: - py.test + # This runs all of the tests. To run an individual test, run py.test with + # the -k flag, like "py.test -k " + py.test test_requests.py coverage: py.test --verbose --cov-report term --cov=requests test_requests.py From 024db279c48edfaafe4b6b97921b292c6687a4e3 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Sat, 23 Aug 2014 10:10:44 -0700 Subject: [PATCH 177/545] clarify comment --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7c858729..687f21a3 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ init: test: # This runs all of the tests. To run an individual test, run py.test with - # the -k flag, like "py.test -k " + # the -k flag, like "py.test -k test_path_is_not_double_encoded" py.test test_requests.py coverage: From c2aeaa3959b5754f5b39a45bceff91b196b6c986 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Sat, 23 Aug 2014 11:52:51 -0700 Subject: [PATCH 178/545] Add support for connect timeouts Modifies the timeout interface to also accept a tuple (connect, read) which would be used to set individual connect and read timeouts for Requests. Adds Advanced documentation explaining the interface and providing guidance for timeout values. --- docs/api.rst | 2 +- docs/user/advanced.rst | 38 ++++++++++++++++++++++++++++++++ requests/adapters.py | 33 +++++++++++++++++++++------- requests/exceptions.py | 17 +++++++++++++- requests/structures.py | 4 ++-- test_requests.py | 50 ++++++++++++++++++++++++++++++++++++++++-- 6 files changed, 130 insertions(+), 14 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 42f7c5a0..69f138a2 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -5,7 +5,7 @@ Developer Interface .. module:: requests -This part of the documentation covers all the interfaces of Requests. For +This part of the documentation covers all the interfaces of Requests. For parts where Requests depends on external libraries, we document the most important right here and provide links to the canonical documentation. diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 8eb888b1..65970daf 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -707,3 +707,41 @@ Two excellent examples are `grequests`_ and `requests-futures`_. .. _`grequests`: https://github.com/kennethreitz/grequests .. _`requests-futures`: https://github.com/ross/requests-futures + +Timeouts +-------- + +Most requests to external servers should have a timeout attached, in case the +server is not responding in a timely manner. Without a timeout, your code may +hang for minutes or more. + +The **connect** timeout is the number of seconds Requests will wait for your +client to establish a connection to a remote machine (corresponding to the +`connect()`_) call on the socket. It's a good practice to set connect timeouts +to slightly larger than a multiple of 3, which is the default `TCP packet +retransmission window `_. + +Once your client has connected to the server and sent the HTTP request, the +**read** timeout is the number of seconds the client will wait for the server +to send a response. (Specifically, it's the number of seconds that the client +will wait *between* bytes sent from the server. In 99.9% of cases, this is the +time before the server sends the first byte). + +If you specify a single value for the timeout, like this:: + + r = requests.get('https://github.com', timeout=5) + +The timeout value will be applied to both the ``connect`` and the ``read`` +timeouts. Specify a tuple if you would like to set the values separately:: + + r = requests.get('https://github.com', timeout=(3.05, 27)) + +If the remote server is very slow, you can tell Requests to wait forever for +a response, by passing None as a timeout value and then retrieving a cup of +coffee. + +.. code-block:: python + + r = requests.get('https://github.com', timeout=None) + +.. _`connect()`: http://linux.die.net/man/2/connect diff --git a/requests/adapters.py b/requests/adapters.py index 1ce54470..3c1e979f 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -15,17 +15,19 @@ from .packages.urllib3 import Retry from .packages.urllib3.poolmanager import PoolManager, proxy_from_url from .packages.urllib3.response import HTTPResponse from .packages.urllib3.util import Timeout as TimeoutSauce -from .compat import urlparse, basestring, urldefrag, unquote +from .compat import urlparse, basestring, urldefrag from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers, prepend_scheme_if_needed, get_auth_from_url) from .structures import CaseInsensitiveDict -from .packages.urllib3.exceptions import MaxRetryError -from .packages.urllib3.exceptions import TimeoutError -from .packages.urllib3.exceptions import SSLError as _SSLError +from .packages.urllib3.exceptions import ConnectTimeoutError from .packages.urllib3.exceptions import HTTPError as _HTTPError +from .packages.urllib3.exceptions import MaxRetryError from .packages.urllib3.exceptions import ProxyError as _ProxyError +from .packages.urllib3.exceptions import ReadTimeoutError +from .packages.urllib3.exceptions import SSLError as _SSLError from .cookies import extract_cookies_to_jar -from .exceptions import ConnectionError, Timeout, SSLError, ProxyError +from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError, + ProxyError) from .auth import _basic_auth_str DEFAULT_POOLBLOCK = False @@ -315,6 +317,7 @@ class HTTPAdapter(BaseAdapter): :param request: The :class:`PreparedRequest ` being sent. :param stream: (optional) Whether to stream the request content. :param timeout: (optional) The timeout on the request. + :type timeout: float or tuple (connect timeout, read timeout), eg (3.1, 20) :param verify: (optional) Whether to verify SSL certificates. :param cert: (optional) Any user-provided SSL certificate to be trusted. :param proxies: (optional) The proxies dictionary to apply to the request. @@ -328,7 +331,18 @@ class HTTPAdapter(BaseAdapter): chunked = not (request.body is None or 'Content-Length' in request.headers) - timeout = TimeoutSauce(connect=timeout, read=timeout) + if isinstance(timeout, tuple): + try: + connect, read = timeout + timeout = TimeoutSauce(connect=connect, read=read) + except ValueError as e: + # this may raise a string formatting error. + err = ("Invalid timeout {0}. Pass a (connect, read) " + "timeout tuple, or a single float to set " + "both timeouts to the same value".format(timeout)) + raise ValueError(err) + else: + timeout = TimeoutSauce(connect=timeout, read=timeout) try: if not chunked: @@ -390,6 +404,9 @@ class HTTPAdapter(BaseAdapter): raise ConnectionError(sockerr, request=request) except MaxRetryError as e: + if isinstance(e.reason, ConnectTimeoutError): + raise ConnectTimeout(e, request=request) + raise ConnectionError(e, request=request) except _ProxyError as e: @@ -398,8 +415,8 @@ class HTTPAdapter(BaseAdapter): except (_SSLError, _HTTPError) as e: if isinstance(e, _SSLError): raise SSLError(e, request=request) - elif isinstance(e, TimeoutError): - raise Timeout(e, request=request) + elif isinstance(e, ReadTimeoutError): + raise ReadTimeout(e, request=request) else: raise diff --git a/requests/exceptions.py b/requests/exceptions.py index a4ee9d63..d59637a3 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -44,7 +44,22 @@ class SSLError(ConnectionError): class Timeout(RequestException): - """The request timed out.""" + """The request timed out. + + Catching this error will catch both :exc:`ConnectTimeout` and + :exc:`ReadTimeout` errors. + """ + + +class ConnectTimeout(Timeout): + """ The request timed out while trying to connect to the server. + + Requests that produce this error are safe to retry + """ + + +class ReadTimeout(Timeout): + """The server did not send any data in the allotted amount of time.""" class URLRequired(RequestException): diff --git a/requests/structures.py b/requests/structures.py index 66cdad86..5563d1f8 100644 --- a/requests/structures.py +++ b/requests/structures.py @@ -23,7 +23,7 @@ class CaseInsensitiveDict(collections.MutableMapping): case of the last key to be set, and ``iter(instance)``, ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()`` will contain case-sensitive keys. However, querying and contains - testing is case insensitive: + testing is case insensitive:: cid = CaseInsensitiveDict() cid['Accept'] = 'application/json' @@ -35,7 +35,7 @@ class CaseInsensitiveDict(collections.MutableMapping): of how the header name was originally stored. If the constructor, ``.update``, or equality comparison - operations are given keys that have equal ``.lower()``s, the + operations are given keys that have equal ``.lower()`` s, the behavior is undefined. """ diff --git a/test_requests.py b/test_requests.py index 34ebd8ca..21efe667 100755 --- a/test_requests.py +++ b/test_requests.py @@ -18,7 +18,8 @@ from requests.auth import HTTPDigestAuth, _basic_auth_str from requests.compat import ( Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3, builtin_str) from requests.cookies import cookiejar_from_dict, morsel_to_cookie -from requests.exceptions import InvalidURL, MissingSchema, ConnectionError +from requests.exceptions import (InvalidURL, MissingSchema, ConnectTimeout, + ReadTimeout) from requests.models import PreparedRequest from requests.structures import CaseInsensitiveDict from requests.sessions import SessionRedirectMixin @@ -38,6 +39,9 @@ else: return s.decode('unicode-escape') +# Requests to this URL should always fail with a connection timeout (nothing +# listening on that port) +TARPIT = "http://10.255.255.1" HTTPBIN = os.environ.get('HTTPBIN_URL', 'http://httpbin.org/') # Issue #1483: Make sure the URL always has a trailing slash HTTPBIN = HTTPBIN.rstrip('/') + '/' @@ -1308,10 +1312,52 @@ class TestMorselToCookieMaxAge(unittest.TestCase): class TestTimeout: def test_stream_timeout(self): try: - requests.get('https://httpbin.org/delay/10', timeout=5.0) + requests.get('https://httpbin.org/delay/10', timeout=2.0) except requests.exceptions.Timeout as e: assert 'Read timed out' in e.args[0].args[0] + def test_invalid_timeout(self): + with pytest.raises(ValueError) as e: + requests.get(httpbin('get'), timeout=(3, 4, 5)) + assert '(connect, read)' in str(e) + + with pytest.raises(ValueError) as e: + requests.get(httpbin('get'), timeout="foo") + assert 'must be an int or float' in str(e) + + def test_none_timeout(self): + """ Check that you can set None as a valid timeout value. + + To actually test this behavior, we'd want to check that setting the + timeout to None actually lets the request block past the system default + timeout. However, this would make the test suite unbearably slow. + Instead we verify that setting the timeout to None does not prevent the + request from succeeding. + """ + r = requests.get(httpbin('get'), timeout=None) + assert r.status_code == 200 + + def test_read_timeout(self): + try: + requests.get(httpbin('delay/10'), timeout=(None, 0.1)) + assert False, "The recv() request should time out." + except ReadTimeout: + pass + + def test_connect_timeout(self): + try: + requests.get(TARPIT, timeout=(0.1, None)) + assert False, "The connect() request should time out." + except ConnectTimeout: + pass + + def test_total_timeout_connect(self): + try: + requests.get(TARPIT, timeout=(0.1, 0.1)) + assert False, "The connect() request should time out." + except ConnectTimeout: + pass + SendCall = collections.namedtuple('SendCall', ('args', 'kwargs')) From f0b9b60f6298ec743f2b470819cd249a041845d3 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Sun, 24 Aug 2014 19:46:46 -0700 Subject: [PATCH 179/545] revert change --- requests/structures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/structures.py b/requests/structures.py index 5563d1f8..3e5f2faa 100644 --- a/requests/structures.py +++ b/requests/structures.py @@ -35,7 +35,7 @@ class CaseInsensitiveDict(collections.MutableMapping): of how the header name was originally stored. If the constructor, ``.update``, or equality comparison - operations are given keys that have equal ``.lower()`` s, the + operations are given keys that have equal ``.lower()``s, the behavior is undefined. """ From 8f9ce13e433013cd9bd4dbe9cf61c5b5f14b65ed Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Sun, 24 Aug 2014 19:56:57 -0700 Subject: [PATCH 180/545] ConnectTimeout multiple inheritance --- requests/exceptions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/exceptions.py b/requests/exceptions.py index d59637a3..6dbd98a9 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -51,8 +51,8 @@ class Timeout(RequestException): """ -class ConnectTimeout(Timeout): - """ The request timed out while trying to connect to the server. +class ConnectTimeout(ConnectionError, Timeout): + """The request timed out while trying to connect to the server. Requests that produce this error are safe to retry """ From 39b3a436d3058975ac8e05735825da03e755a3b0 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Sun, 24 Aug 2014 20:18:05 -0700 Subject: [PATCH 181/545] assert connect timeout inheritance --- test_requests.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test_requests.py b/test_requests.py index 21efe667..2df97368 100755 --- a/test_requests.py +++ b/test_requests.py @@ -19,7 +19,7 @@ from requests.compat import ( Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3, builtin_str) from requests.cookies import cookiejar_from_dict, morsel_to_cookie from requests.exceptions import (InvalidURL, MissingSchema, ConnectTimeout, - ReadTimeout) + ReadTimeout, ConnectionError, Timeout) from requests.models import PreparedRequest from requests.structures import CaseInsensitiveDict from requests.sessions import SessionRedirectMixin @@ -1348,8 +1348,10 @@ class TestTimeout: try: requests.get(TARPIT, timeout=(0.1, None)) assert False, "The connect() request should time out." - except ConnectTimeout: + except ConnectTimeout as e: pass + assert isinstance(e, ConnectionError) + assert isinstance(e, Timeout) def test_total_timeout_connect(self): try: From 7f236fcc40ae3597bfc944c704bff2b8d907cd93 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Sun, 24 Aug 2014 20:22:55 -0700 Subject: [PATCH 182/545] woops --- test_requests.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test_requests.py b/test_requests.py index 2df97368..716c0dcf 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1349,9 +1349,8 @@ class TestTimeout: requests.get(TARPIT, timeout=(0.1, None)) assert False, "The connect() request should time out." except ConnectTimeout as e: - pass - assert isinstance(e, ConnectionError) - assert isinstance(e, Timeout) + assert isinstance(e, ConnectionError) + assert isinstance(e, Timeout) def test_total_timeout_connect(self): try: From 8bbc4fcb5718a2cc321a456422a80305c2351582 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Mon, 25 Aug 2014 13:44:29 -0700 Subject: [PATCH 183/545] Updates urllib3 to 528ad3c This includes fixes for openssl running with Pypy, so SSL connections can be made if ndg-httpsclient, pyopenssl and pyasn1 are installed. see also: - https://github.com/shazow/urllib3/issues/449 - https://github.com/pypa/pip/issues/1988 --- requests/packages/urllib3/__init__.py | 2 +- requests/packages/urllib3/connection.py | 31 ++++++++++++----- requests/packages/urllib3/connectionpool.py | 2 +- .../packages/urllib3/contrib/pyopenssl.py | 34 +++++++++++++++---- requests/packages/urllib3/exceptions.py | 21 ++++++++++-- requests/packages/urllib3/response.py | 15 +++++++- requests/packages/urllib3/util/response.py | 19 ++++++++--- requests/packages/urllib3/util/retry.py | 2 +- 8 files changed, 101 insertions(+), 25 deletions(-) diff --git a/requests/packages/urllib3/__init__.py b/requests/packages/urllib3/__init__.py index cdee528c..4b36b5ae 100644 --- a/requests/packages/urllib3/__init__.py +++ b/requests/packages/urllib3/__init__.py @@ -57,7 +57,7 @@ del NullHandler # Set security warning to only go off once by default. import warnings -warnings.simplefilter('module', exceptions.InsecureRequestWarning) +warnings.simplefilter('module', exceptions.SecurityWarning) def disable_warnings(category=exceptions.HTTPWarning): """ diff --git a/requests/packages/urllib3/connection.py b/requests/packages/urllib3/connection.py index 0d578d77..c6e1959a 100644 --- a/requests/packages/urllib3/connection.py +++ b/requests/packages/urllib3/connection.py @@ -1,6 +1,8 @@ +import datetime import sys import socket from socket import timeout as SocketTimeout +import warnings try: # Python 3 from http.client import HTTPConnection as _HTTPConnection, HTTPException @@ -26,6 +28,7 @@ except (ImportError, AttributeError): # Platform-specific: No SSL. from .exceptions import ( ConnectTimeoutError, + SystemTimeWarning, ) from .packages.ssl_match_hostname import match_hostname from .packages import six @@ -45,6 +48,8 @@ port_by_scheme = { 'https': 443, } +RECENT_DATE = datetime.date(2014, 1, 1) + class HTTPConnection(_HTTPConnection, object): """ @@ -172,6 +177,7 @@ class VerifiedHTTPSConnection(HTTPSConnection): cert_reqs = None ca_certs = None ssl_version = None + assert_fingerprint = None def set_cert(self, key_file=None, cert_file=None, cert_reqs=None, ca_certs=None, @@ -206,6 +212,14 @@ class VerifiedHTTPSConnection(HTTPSConnection): # Override the host with the one we're requesting data from. hostname = self._tunnel_host + is_time_off = datetime.date.today() < RECENT_DATE + if is_time_off: + warnings.warn(( + 'System time is way off (before {0}). This will probably ' + 'lead to SSL verification errors').format(RECENT_DATE), + SystemTimeWarning + ) + # Wrap socket using verification with the root certs in # trusted_root_certs self.sock = ssl_wrap_socket(conn, self.key_file, self.cert_file, @@ -214,15 +228,16 @@ class VerifiedHTTPSConnection(HTTPSConnection): server_hostname=hostname, ssl_version=resolved_ssl_version) - if resolved_cert_reqs != ssl.CERT_NONE: - if self.assert_fingerprint: - assert_fingerprint(self.sock.getpeercert(binary_form=True), - self.assert_fingerprint) - elif self.assert_hostname is not False: - match_hostname(self.sock.getpeercert(), - self.assert_hostname or hostname) + if self.assert_fingerprint: + assert_fingerprint(self.sock.getpeercert(binary_form=True), + self.assert_fingerprint) + elif resolved_cert_reqs != ssl.CERT_NONE \ + and self.assert_hostname is not False: + match_hostname(self.sock.getpeercert(), + self.assert_hostname or hostname) - self.is_verified = resolved_cert_reqs == ssl.CERT_REQUIRED + self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED + or self.assert_fingerprint is not None) if ssl: diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index 9317fdc3..9cc2a955 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -718,7 +718,7 @@ class HTTPSConnectionPool(HTTPConnectionPool): super(HTTPSConnectionPool, self)._validate_conn(conn) # Force connect early to allow us to validate the connection. - if not conn.sock: + if not getattr(conn, 'sock', None): # AppEngine might not have `.sock` conn.connect() if not conn.is_verified: diff --git a/requests/packages/urllib3/contrib/pyopenssl.py b/requests/packages/urllib3/contrib/pyopenssl.py index 7a9ea2e7..24de9e40 100644 --- a/requests/packages/urllib3/contrib/pyopenssl.py +++ b/requests/packages/urllib3/contrib/pyopenssl.py @@ -46,8 +46,12 @@ Module Variables ''' -from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT -from ndg.httpsclient.subj_alt_name import SubjectAltName as BaseSubjectAltName +try: + from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT + from ndg.httpsclient.subj_alt_name import SubjectAltName as BaseSubjectAltName +except SyntaxError as e: + raise ImportError(e) + import OpenSSL.SSL from pyasn1.codec.der import decoder as der_decoder from pyasn1.type import univ, constraint @@ -155,18 +159,24 @@ def get_subj_alt_name(peer_cert): class WrappedSocket(object): - '''API-compatibility wrapper for Python OpenSSL's Connection-class.''' + '''API-compatibility wrapper for Python OpenSSL's Connection-class. + + Note: _makefile_refs, _drop() and _reuse() are needed for the garbage + collector of pypy. + ''' def __init__(self, connection, socket, suppress_ragged_eofs=True): self.connection = connection self.socket = socket self.suppress_ragged_eofs = suppress_ragged_eofs + self._makefile_refs = 0 def fileno(self): return self.socket.fileno() def makefile(self, mode, bufsize=-1): - return _fileobject(self, mode, bufsize) + self._makefile_refs += 1 + return _fileobject(self, mode, bufsize, close=True) def recv(self, *args, **kwargs): try: @@ -180,7 +190,7 @@ class WrappedSocket(object): rd, wd, ed = select.select( [self.socket], [], [], self.socket.gettimeout()) if not rd: - raise timeout() + raise timeout('The read operation timed out') else: return self.recv(*args, **kwargs) else: @@ -193,7 +203,10 @@ class WrappedSocket(object): return self.connection.sendall(data) def close(self): - return self.connection.shutdown() + if self._makefile_refs < 1: + return self.connection.shutdown() + else: + self._makefile_refs -= 1 def getpeercert(self, binary_form=False): x509 = self.connection.get_peer_certificate() @@ -216,6 +229,15 @@ class WrappedSocket(object): ] } + def _reuse(self): + self._makefile_refs += 1 + + def _drop(self): + if self._makefile_refs < 1: + self.close() + else: + self._makefile_refs -= 1 + def _verify_callback(cnx, x509, err_no, err_depth, return_code): return err_no == 0 diff --git a/requests/packages/urllib3/exceptions.py b/requests/packages/urllib3/exceptions.py index fff8bfa5..7519ba98 100644 --- a/requests/packages/urllib3/exceptions.py +++ b/requests/packages/urllib3/exceptions.py @@ -60,7 +60,14 @@ ConnectionError = ProtocolError ## Leaf Exceptions class MaxRetryError(RequestError): - "Raised when the maximum number of retries is exceeded." + """Raised when the maximum number of retries is exceeded. + + :param pool: The connection pool + :type pool: :class:`~urllib3.connectionpool.HTTPConnectionPool` + :param string url: The requested Url + :param exceptions.Exception reason: The underlying error + + """ def __init__(self, pool, url, reason=None): self.reason = reason @@ -134,6 +141,16 @@ class LocationParseError(LocationValueError): self.location = location -class InsecureRequestWarning(HTTPWarning): +class SecurityWarning(HTTPWarning): + "Warned when perfoming security reducing actions" + pass + + +class InsecureRequestWarning(SecurityWarning): "Warned when making an unverified HTTPS request." pass + + +class SystemTimeWarning(SecurityWarning): + "Warned when system time is suspected to be wrong" + pass diff --git a/requests/packages/urllib3/response.py b/requests/packages/urllib3/response.py index 7e0d47fa..e69de957 100644 --- a/requests/packages/urllib3/response.py +++ b/requests/packages/urllib3/response.py @@ -48,7 +48,10 @@ class HTTPResponse(io.IOBase): HTTP Response container. Backwards-compatible to httplib's HTTPResponse but the response ``body`` is - loaded and decoded on-demand when the ``data`` property is accessed. + loaded and decoded on-demand when the ``data`` property is accessed. This + class is also compatible with the Python standard library's :mod:`io` + module, and can hence be treated as a readable object in the context of that + framework. Extra parameters for behaviour not present in httplib.HTTPResponse: @@ -317,4 +320,14 @@ class HTTPResponse(io.IOBase): return self._fp.flush() def readable(self): + # This method is required for `io` module compatibility. return True + + def readinto(self, b): + # This method is required for `io` module compatibility. + temp = self.read(len(b)) + if len(temp) == 0: + return 0 + else: + b[:len(temp)] = temp + return len(temp) diff --git a/requests/packages/urllib3/util/response.py b/requests/packages/urllib3/util/response.py index d0325bc6..45fff552 100644 --- a/requests/packages/urllib3/util/response.py +++ b/requests/packages/urllib3/util/response.py @@ -5,9 +5,18 @@ def is_fp_closed(obj): :param obj: The file-like object to check. """ - if hasattr(obj, 'fp'): - # Object is a container for another file-like object that gets released - # on exhaustion (e.g. HTTPResponse) - return obj.fp is None - return obj.closed + try: + # Check via the official file-like-object way. + return obj.closed + except AttributeError: + pass + + try: + # Check if the object is a container for another file-like object that + # gets released on exhaustion (e.g. HTTPResponse). + return obj.fp is None + except AttributeError: + pass + + raise ValueError("Unable to determine whether fp is closed.") diff --git a/requests/packages/urllib3/util/retry.py b/requests/packages/urllib3/util/retry.py index 90131977..eb560dfc 100644 --- a/requests/packages/urllib3/util/retry.py +++ b/requests/packages/urllib3/util/retry.py @@ -83,7 +83,7 @@ class Retry(object): same state). See :attr:`Retry.DEFAULT_METHOD_WHITELIST`. :param iterable status_forcelist: - A set of HTTP status codes that we should force a retry on. + A set of HTTP status codes that we should force a retry on. By default, this is disabled with ``None``. From b4a7906bca61f94a5b7a0e177b0d85b3ee11604f Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 23 Aug 2014 15:50:15 -0500 Subject: [PATCH 184/545] Add Session method to merge environment settings with per-request settings --- requests/sessions.py | 54 ++++++++++++++++++++++++-------------------- 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index 07b6a32c..dafa9567 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -435,36 +435,16 @@ class Session(SessionRedirectMixin): proxies = proxies or {} - # Gather clues from the surrounding environment. - if self.trust_env: - # Set environment's proxies. - env_proxies = get_environ_proxies(url) or {} - for (k, v) in env_proxies.items(): - proxies.setdefault(k, v) - - # Look for configuration. - if verify is True or verify is None: - verify = os.environ.get('REQUESTS_CA_BUNDLE') - - # Curl compatibility. - if verify is True or verify is None: - verify = os.environ.get('CURL_CA_BUNDLE') - - # Merge all the kwargs. - proxies = merge_setting(proxies, self.proxies) - stream = merge_setting(stream, self.stream) - verify = merge_setting(verify, self.verify) - cert = merge_setting(cert, self.cert) + settings = self.merge_environment_settings( + prep.url, proxies, stream, verify, cert + ) # Send the request. send_kwargs = { - 'stream': stream, 'timeout': timeout, - 'verify': verify, - 'cert': cert, - 'proxies': proxies, 'allow_redirects': allow_redirects, } + send_kwargs.update(settings) resp = self.send(prep, **send_kwargs) return resp @@ -612,6 +592,32 @@ class Session(SessionRedirectMixin): return r + def merge_environment_settings(self, url, proxies, stream, verify, cert): + """Checks the environment and merges it with some settings.""" + # Gather clues from the surrounding environment. + if self.trust_env: + # Set environment's proxies. + env_proxies = get_environ_proxies(url) or {} + for (k, v) in env_proxies.items(): + proxies.setdefault(k, v) + + # Look for configuration. + if verify is True or verify is None: + verify = os.environ.get('REQUESTS_CA_BUNDLE') + + # Curl compatibility. + if verify is True or verify is None: + verify = os.environ.get('CURL_CA_BUNDLE') + + # Merge all the kwargs. + proxies = merge_setting(proxies, self.proxies) + stream = merge_setting(stream, self.stream) + verify = merge_setting(verify, self.verify) + cert = merge_setting(cert, self.cert) + + return {'verify': verify, 'proxies': proxies, 'stream': stream, + 'cert': cert} + def get_adapter(self, url): """Returns the appropriate connnection adapter for the given URL.""" for (prefix, adapter) in self.adapters.items(): From 596ca83f0c66460deb82af07b13ce724b9f46474 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 24 Aug 2014 09:54:56 -0500 Subject: [PATCH 185/545] Update verify check and doc-string --- requests/sessions.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index dafa9567..508b0ef2 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -593,7 +593,7 @@ class Session(SessionRedirectMixin): return r def merge_environment_settings(self, url, proxies, stream, verify, cert): - """Checks the environment and merges it with some settings.""" + """Check the environment and merge it with some settings.""" # Gather clues from the surrounding environment. if self.trust_env: # Set environment's proxies. @@ -601,13 +601,11 @@ class Session(SessionRedirectMixin): for (k, v) in env_proxies.items(): proxies.setdefault(k, v) - # Look for configuration. + # Look for requests environment configuration and be compatible + # with cURL. if verify is True or verify is None: - verify = os.environ.get('REQUESTS_CA_BUNDLE') - - # Curl compatibility. - if verify is True or verify is None: - verify = os.environ.get('CURL_CA_BUNDLE') + verify = (os.environ.get('REQUESTS_CA_BUNDLE') or + os.environ.get('CURL_CA_BUNDLE')) # Merge all the kwargs. proxies = merge_setting(proxies, self.proxies) From 08d8876230884eea98306ff60e1778a92fd09057 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Tue, 26 Aug 2014 20:20:34 +0100 Subject: [PATCH 186/545] Remove timeline.json from docs. --- docs/user/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 76d4e513..ff008347 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -29,7 +29,7 @@ Begin by importing the Requests module:: Now, let's try to get a webpage. For this example, let's get GitHub's public timeline :: - >>> r = requests.get('https://github.com/timeline.json') + >>> r = requests.get('https://api.github.com/events') Now, we have a :class:`Request ` object called ``r``. We can get all the information we need from this object. From 89cb408bb06d629f0932758abfafc9a17709cb82 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 28 Aug 2014 13:05:23 -0400 Subject: [PATCH 187/545] v2.4.0 --- requests/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/__init__.py b/requests/__init__.py index bba19002..33a2b27a 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.3.0' -__build__ = 0x020300 +__version__ = '2.4.0' +__build__ = 0x020400 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2014 Kenneth Reitz' From d33ce49aaa46c855458582f338a6e2039d428bee Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 28 Aug 2014 13:05:30 -0400 Subject: [PATCH 188/545] keep-alive --- requests/utils.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requests/utils.py b/requests/utils.py index 68e50cf0..2c6bb090 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -554,7 +554,8 @@ def default_headers(): return CaseInsensitiveDict({ 'User-Agent': default_user_agent(), 'Accept-Encoding': ', '.join(('gzip', 'deflate')), - 'Accept': '*/*' + 'Accept': '*/*', + 'Connection': 'keep-alive' }) From d22b8d8e7e8fcb8d16efba6841977dc81ac2c935 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 28 Aug 2014 13:10:26 -0400 Subject: [PATCH 189/545] 3.4 classifier --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 316cb9b1..9540a47c 100755 --- a/setup.py +++ b/setup.py @@ -57,6 +57,7 @@ setup( 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4' ), ) From 8f17741849edb5e7eba0356f90cb17e43c938a2f Mon Sep 17 00:00:00 2001 From: Carol Willing Date: Thu, 17 Jul 2014 12:34:31 -0700 Subject: [PATCH 190/545] Adds json parameter for POST requests --- requests/api.py | 6 ++++-- requests/models.py | 22 ++++++++++++++++------ requests/sessions.py | 10 ++++++++-- test_requests.py | 8 ++++++++ 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/requests/api.py b/requests/api.py index 01d853d5..88db7dc7 100644 --- a/requests/api.py +++ b/requests/api.py @@ -22,6 +22,7 @@ def request(method, url, **kwargs): :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 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 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 files: (optional) Dictionary of 'name': file-like-objects (or {'name': ('filename', fileobj)}) for multipart encoding upload. @@ -77,15 +78,16 @@ def head(url, **kwargs): return request('head', url, **kwargs) -def post(url, data=None, **kwargs): +def post(url, data=None, json=None, **kwargs): """Sends a POST request. Returns :class:`Response` 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 json: (optional) json data to send in the body of the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. """ - return request('post', url, data=data, **kwargs) + return request('post', url, data=data, json=json, **kwargs) def put(url, data=None, **kwargs): diff --git a/requests/models.py b/requests/models.py index 03ff627a..b6ef9190 100644 --- a/requests/models.py +++ b/requests/models.py @@ -190,6 +190,7 @@ class Request(RequestHooksMixin): :param headers: dictionary of headers to send. :param files: dictionary of {filename: fileobject} files to multipart upload. :param data: the body to attach the request. If a dictionary is provided, form-encoding will take place. + :param json: json for the body to attach the request. :param params: dictionary of URL parameters to append to the URL. :param auth: Auth handler or (user, pass) tuple. :param cookies: dictionary or CookieJar of cookies to attach to this request. @@ -209,6 +210,7 @@ class Request(RequestHooksMixin): headers=None, files=None, data=None, + json=None, params=None, auth=None, cookies=None, @@ -216,6 +218,7 @@ class Request(RequestHooksMixin): # Default empty dicts for dict params. data = [] if data is None else data + json = [] if json is None else json files = [] if files is None else files headers = {} if headers is None else headers params = {} if params is None else params @@ -230,6 +233,7 @@ class Request(RequestHooksMixin): self.headers = headers self.files = files self.data = data + self.json = json self.params = params self.auth = auth self.cookies = cookies @@ -246,6 +250,7 @@ class Request(RequestHooksMixin): headers=self.headers, files=self.files, data=self.data, + json=self.json, params=self.params, auth=self.auth, cookies=self.cookies, @@ -289,7 +294,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): self.hooks = default_hooks() def prepare(self, method=None, url=None, headers=None, files=None, - data=None, params=None, auth=None, cookies=None, hooks=None): + data=None, json=None, params=None, auth=None, cookies=None, hooks=None): """Prepares the entire request with the given parameters.""" self.prepare_method(method) @@ -397,7 +402,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): else: self.headers = CaseInsensitiveDict() - def prepare_body(self, data, files): + def prepare_body(self, data, files, _json=None): """Prepares the given HTTP body data.""" # Check if file, fo, generator, iterator. @@ -408,6 +413,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): content_type = None length = None + if _json is not None: + content_type = 'application/json' + data = json.dumps(_json) + is_stream = all([ hasattr(data, '__iter__'), not isinstance(data, (basestring, list, tuple, dict)) @@ -435,10 +444,11 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): else: if data: body = self._encode_params(data) - if isinstance(data, basestring) or hasattr(data, 'read'): - content_type = None - else: - content_type = 'application/x-www-form-urlencoded' + if not _json: + if isinstance(data, basestring) or hasattr(data, 'read'): + content_type = None + else: + content_type = 'application/x-www-form-urlencoded' self.prepare_content_length(body) diff --git a/requests/sessions.py b/requests/sessions.py index 508b0ef2..7942447f 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -365,6 +365,7 @@ class Session(SessionRedirectMixin): url=request.url, files=request.files, data=request.data, + json=request.json, headers=merge_setting(request.headers, self.headers, dict_class=CaseInsensitiveDict), params=merge_setting(request.params, self.params), auth=merge_setting(auth, self.auth), @@ -376,6 +377,7 @@ class Session(SessionRedirectMixin): def request(self, method, url, params=None, data=None, + json=None, headers=None, cookies=None, files=None, @@ -396,6 +398,8 @@ class Session(SessionRedirectMixin): string for the :class:`Request`. :param data: (optional) Dictionary or bytes to send in the body of the :class:`Request`. + :param json: (optional) json to send in the body of 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 @@ -426,6 +430,7 @@ class Session(SessionRedirectMixin): headers = headers, files = files, data = data or {}, + json = json or {}, params = params or {}, auth = auth, cookies = cookies, @@ -479,15 +484,16 @@ class Session(SessionRedirectMixin): kwargs.setdefault('allow_redirects', False) return self.request('HEAD', url, **kwargs) - def post(self, url, data=None, **kwargs): + def post(self, url, data=None, json=None, **kwargs): """Sends a POST request. Returns :class:`Response` 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 json: (optional) json to send in the body of the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. """ - return self.request('POST', url, data=data, **kwargs) + return self.request('POST', url, data=data, json=json, **kwargs) def put(self, url, data=None, **kwargs): """Sends a PUT request. Returns :class:`Response` object. diff --git a/test_requests.py b/test_requests.py index 716c0dcf..2e98cb91 100755 --- a/test_requests.py +++ b/test_requests.py @@ -986,6 +986,14 @@ class RequestsTestCase(unittest.TestCase): assert item.history == total[0:i] i=i+1 + def test_json_param_post_content_type_works(self): + r = requests.post( + httpbin('post'), + json={'life': 42} + ) + assert r.status_code == 200 + assert 'application/json' in r.headers['Content-Type'] + class TestContentEncodingDetection(unittest.TestCase): From b34a496649667b9324caa634cb17e726e0cce1a5 Mon Sep 17 00:00:00 2001 From: Carol Willing Date: Thu, 28 Aug 2014 16:45:24 -0700 Subject: [PATCH 191/545] Adds review changes --- requests/models.py | 4 ++-- test_requests.py | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/requests/models.py b/requests/models.py index b6ef9190..1b110b49 100644 --- a/requests/models.py +++ b/requests/models.py @@ -301,7 +301,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): self.prepare_url(url, params) self.prepare_headers(headers) self.prepare_cookies(cookies) - self.prepare_body(data, files) + self.prepare_body(data, files, json) self.prepare_auth(auth, url) # Note that prepare_auth must be last to enable authentication schemes # such as OAuth to work on a fully prepared request. @@ -442,7 +442,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if files: (body, content_type) = self._encode_files(files, data) else: - if data: + if data and not _json: body = self._encode_params(data) if not _json: if isinstance(data, basestring) or hasattr(data, 'read'): diff --git a/test_requests.py b/test_requests.py index 2e98cb91..af272355 100755 --- a/test_requests.py +++ b/test_requests.py @@ -992,7 +992,8 @@ class RequestsTestCase(unittest.TestCase): json={'life': 42} ) assert r.status_code == 200 - assert 'application/json' in r.headers['Content-Type'] + assert 'application/json' in r.request.headers['Content-Type'] + #assert {'life': 42} == r.json()['json'] class TestContentEncodingDetection(unittest.TestCase): From 402f3b4993fe8d0626fcffcbfc30caf671b65df3 Mon Sep 17 00:00:00 2001 From: Carol Willing Date: Thu, 28 Aug 2014 19:27:45 -0700 Subject: [PATCH 192/545] Changes check on base and json. Fails on tests. --- requests/models.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/requests/models.py b/requests/models.py index 1b110b49..a96fc4e6 100644 --- a/requests/models.py +++ b/requests/models.py @@ -442,13 +442,12 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if files: (body, content_type) = self._encode_files(files, data) else: - if data and not _json: + if data and _json is None: body = self._encode_params(data) - if not _json: - if isinstance(data, basestring) or hasattr(data, 'read'): - content_type = None - else: - content_type = 'application/x-www-form-urlencoded' + if isinstance(data, basestring) or hasattr(data, 'read'): + content_type = None + else: + content_type = 'application/x-www-form-urlencoded' self.prepare_content_length(body) From 0713e09526d3ed2d2fe2516fa6d35727c557024f Mon Sep 17 00:00:00 2001 From: Carol Willing Date: Thu, 28 Aug 2014 19:42:21 -0700 Subject: [PATCH 193/545] Fixes typo in test --- test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index af272355..294ba684 100755 --- a/test_requests.py +++ b/test_requests.py @@ -993,7 +993,7 @@ class RequestsTestCase(unittest.TestCase): ) assert r.status_code == 200 assert 'application/json' in r.request.headers['Content-Type'] - #assert {'life': 42} == r.json()['json'] + assert {'life': 42} == r.json()['json'] class TestContentEncodingDetection(unittest.TestCase): From 1fcda912dbac57cd4e0f557fb611ea46c200cb4e Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 29 Aug 2014 10:30:49 -0400 Subject: [PATCH 194/545] Revert "Documentation update : connection keep alive" This reverts commit 1b2602344fe783c5422602ff5f7d03bf7928c0f3. Conflicts: docs/user/advanced.rst --- docs/user/advanced.rst | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 65970daf..0de6b1b1 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -240,10 +240,6 @@ Note that connections are only released back to the pool for reuse once all body data has been read; be sure to either set ``stream`` to ``False`` or read the ``content`` property of the ``Response`` object. -For some older servers it may be necessary to manually set the -``Connection: keep-alive`` header. This will request that the server keep the -connection open. This does not guarantee that the connection will be kept open, -and generally is not necessary. Streaming Uploads ----------------- From 95161ed313db11296c3bd473336340dbb19bb347 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 29 Aug 2014 10:32:18 -0400 Subject: [PATCH 195/545] v2.4.0 changelog --- HISTORY.rst | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index aeff9468..26d5c65b 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,10 +3,21 @@ Release History --------------- -2.3.1 (?) -+++++++++ +2.4.0 (2014-08-29) +++++++++++++++++++ + +**Behavioral Changes** + +- ``Connection: keep-alive`` header is now sent automatically. + +**Improvements** + +- Support for connect timeouts! Timeout now accepts a tuple (connect, read) which is used to set individual connect and read timeouts. +- Allow copying of PreparedRequests without headers/cookies. +- Updated bundled urllib3 version. +- Refactored settings loading from environment — new `Session.merge_environment_settings`. +- Handle socket errors in iter_content. -- Allow copying of PreparedRequests without headers/cookies 2.3.0 (2014-05-16) ++++++++++++++++++ From dfa41afd43a15222eb7a2db35feadd36dba4b445 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Fri, 29 Aug 2014 11:33:46 -0700 Subject: [PATCH 196/545] Document Timeout behaviour --- docs/api.rst | 20 ++------------------ requests/adapters.py | 6 ++++-- requests/api.py | 8 ++++++-- requests/exceptions.py | 9 +++++---- requests/sessions.py | 11 +++++++---- 5 files changed, 24 insertions(+), 30 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 69f138a2..f1242ccb 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -60,6 +60,8 @@ Exceptions .. autoexception:: requests.exceptions.HTTPError .. autoexception:: requests.exceptions.URLRequired .. autoexception:: requests.exceptions.TooManyRedirects +.. autoexception:: requests.exceptions.ConnectTimeout +.. autoexception:: requests.exceptions.ReadTimeout .. autoexception:: requests.exceptions.Timeout @@ -255,21 +257,3 @@ Behavioral Changes keys are not native strings (unicode on Python2 or bytestrings on Python 3) they will be converted to the native string type assuming UTF-8 encoding. -* Timeouts behave slightly differently. On streaming requests, the timeout - only applies to the connection attempt. On regular requests, the timeout - is applied to the connection process and on to all attempts to read data from - the underlying socket. It does *not* apply to the total download time for the - request. - - :: - - tarball_url = 'https://github.com/kennethreitz/requests/tarball/master' - - # One second timeout for the connection attempt - # Unlimited time to download the tarball - r = requests.get(tarball_url, stream=True, timeout=1) - - # One second timeout for the connection attempt - # Another full second timeout to download the tarball - r = requests.get(tarball_url, timeout=1) - diff --git a/requests/adapters.py b/requests/adapters.py index 3c1e979f..ed7a9072 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -316,8 +316,10 @@ class HTTPAdapter(BaseAdapter): :param request: The :class:`PreparedRequest ` being sent. :param stream: (optional) Whether to stream the request content. - :param timeout: (optional) The timeout on the request. - :type timeout: float or tuple (connect timeout, read timeout), eg (3.1, 20) + :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 `_) tuple. + :type timeout: float or tuple :param verify: (optional) Whether to verify SSL certificates. :param cert: (optional) Any user-provided SSL certificate to be trusted. :param proxies: (optional) The proxies dictionary to apply to the request. diff --git a/requests/api.py b/requests/api.py index 01d853d5..57a91d55 100644 --- a/requests/api.py +++ b/requests/api.py @@ -24,10 +24,14 @@ def request(method, url, **kwargs): :param data: (optional) Dictionary, bytes, or file-like object to send in the body of 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 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': ('filename', fileobj)}``) for multipart encoding upload. :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. - :param timeout: (optional) Float describing the timeout of the request in seconds. + :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 + `_) tuple. + :type timeout: float or tuple :param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. + :type allow_redirects: bool :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 stream: (optional) if ``False``, the response content will be immediately downloaded. diff --git a/requests/exceptions.py b/requests/exceptions.py index 6dbd98a9..d8f05f08 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -46,15 +46,16 @@ class SSLError(ConnectionError): class Timeout(RequestException): """The request timed out. - Catching this error will catch both :exc:`ConnectTimeout` and - :exc:`ReadTimeout` errors. + Catching this error will catch both + :exc:`~requests.exceptions.ConnectTimeout` and + :exc:`~requests.exceptions.ReadTimeout` errors. """ class ConnectTimeout(ConnectionError, Timeout): - """The request timed out while trying to connect to the server. + """The request timed out while trying to connect to the remote server. - Requests that produce this error are safe to retry + Requests that produced this error are safe to retry. """ diff --git a/requests/sessions.py b/requests/sessions.py index 508b0ef2..334db58e 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -400,13 +400,16 @@ class Session(SessionRedirectMixin): :class:`Request`. :param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`. - :param files: (optional) Dictionary of 'filename': file-like-objects + :param files: (optional) Dictionary of ``'filename': file-like-objects`` for multipart encoding upload. :param auth: (optional) Auth tuple or callable to enable Basic/Digest/Custom HTTP Auth. - :param timeout: (optional) Float describing the timeout of the - request in seconds. - :param allow_redirects: (optional) Boolean. Set to True by default. + :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 `_) tuple. + :type timeout: float or tuple + :param allow_redirects: (optional) Set to True by default. + :type allow_redirects: bool :param proxies: (optional) Dictionary mapping protocol to the URL of the proxy. :param stream: (optional) whether to immediately download the response From 81d6b3e435d41db5d7bb6b57b13977bec6ad526c Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Fri, 29 Aug 2014 11:51:09 -0700 Subject: [PATCH 197/545] s/behavior/behaviour --- docs/api.rst | 2 +- docs/community/support.rst | 2 +- docs/user/advanced.rst | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index f1242ccb..91a2266b 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -249,7 +249,7 @@ API Changes requests.get("http://example.org", proxies=proxies) -Behavioral Changes +Behavioural Changes ~~~~~~~~~~~~~~~~~~ * Keys in the ``headers`` dictionary are now native strings on all Python diff --git a/docs/community/support.rst b/docs/community/support.rst index a187a332..7f8c332d 100644 --- a/docs/community/support.rst +++ b/docs/community/support.rst @@ -15,7 +15,7 @@ If your question is less than 140 characters, feel free to send a tweet to File an Issue ------------- -If you notice some unexpected behavior in Requests, or want to see support +If you notice some unexpected behaviour in Requests, or want to see support for a new feature, `file an issue on GitHub `_. diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 65970daf..0fbf8583 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -194,7 +194,7 @@ Body Content Workflow --------------------- By default, when you make a request, the body of the response is downloaded -immediately. You can override this behavior and defer downloading the response +immediately. You can override this behaviour and defer downloading the response body until you access the :class:`Response.content ` attribute with the ``stream`` parameter:: From 59f5a1089a05bb768e1c86804cc8a6d023c9cb43 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Fri, 29 Aug 2014 12:06:58 -0700 Subject: [PATCH 198/545] add trailing comma --- requests/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/utils.py b/requests/utils.py index 2c6bb090..539a68a0 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -555,7 +555,7 @@ def default_headers(): 'User-Agent': default_user_agent(), 'Accept-Encoding': ', '.join(('gzip', 'deflate')), 'Accept': '*/*', - 'Connection': 'keep-alive' + 'Connection': 'keep-alive', }) From 34ea836d77f342d4c6f74569288fd1288815f596 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Fri, 29 Aug 2014 12:08:03 -0700 Subject: [PATCH 199/545] tildes --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 91a2266b..7c5dae2e 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -250,7 +250,7 @@ API Changes Behavioural Changes -~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~ * Keys in the ``headers`` dictionary are now native strings on all Python versions, i.e. bytestrings on Python 2 and unicode on Python 3. If the From da122231e4dab3cc3f9726b4e8edf70bb649dd5d Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 29 Aug 2014 15:16:30 -0500 Subject: [PATCH 200/545] Capture and re-raise urllib3 ProtocolError --- requests/adapters.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/requests/adapters.py b/requests/adapters.py index 3c1e979f..6c6597da 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -23,6 +23,7 @@ from .packages.urllib3.exceptions import ConnectTimeoutError from .packages.urllib3.exceptions import HTTPError as _HTTPError from .packages.urllib3.exceptions import MaxRetryError from .packages.urllib3.exceptions import ProxyError as _ProxyError +from .packages.urllib3.exceptions import ProtocolError from .packages.urllib3.exceptions import ReadTimeoutError from .packages.urllib3.exceptions import SSLError as _SSLError from .cookies import extract_cookies_to_jar @@ -403,6 +404,9 @@ class HTTPAdapter(BaseAdapter): except socket.error as sockerr: raise ConnectionError(sockerr, request=request) + except ProtocolError as e: + raise ConnectionError(e, request=request) + except MaxRetryError as e: if isinstance(e.reason, ConnectTimeoutError): raise ConnectTimeout(e, request=request) From 2c76122a48c6a4b513930fed56c0aea5362d6c9c Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Fri, 29 Aug 2014 14:07:28 -0700 Subject: [PATCH 201/545] Add test that invalid domain raises a ConnectionError --- test_requests.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test_requests.py b/test_requests.py index 716c0dcf..3868f9dc 100755 --- a/test_requests.py +++ b/test_requests.py @@ -286,6 +286,11 @@ class RequestsTestCase(unittest.TestCase): r = s.get(url) assert r.status_code == 200 + def test_connection_error(self): + """Connecting to an unknown domain should raise a ConnectionError""" + with pytest.raises(ConnectionError): + requests.get("http://fooobarbangbazbing.httpbin.org") + def test_basicauth_with_netrc(self): auth = ('user', 'pass') wrong_auth = ('wronguser', 'wrongpass') From c8098670357f2954ce4db2eb1fa9c7483c5f63d0 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 29 Aug 2014 16:39:22 -0500 Subject: [PATCH 202/545] Add betterssl extra --- setup.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/setup.py b/setup.py index 9540a47c..6a7c8de9 100755 --- a/setup.py +++ b/setup.py @@ -60,4 +60,7 @@ setup( 'Programming Language :: Python :: 3.4' ), + extras_require={ + 'betterssl': ['pyOpenSSL', 'ndg-httpsclient', 'pyasn1'], + }, ) From ce7957c085d912bd1b225460d159b19eaf3e6510 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 29 Aug 2014 16:50:45 -0500 Subject: [PATCH 203/545] Add test for invalid port --- test_requests.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test_requests.py b/test_requests.py index 3868f9dc..2ff61248 100755 --- a/test_requests.py +++ b/test_requests.py @@ -291,6 +291,9 @@ class RequestsTestCase(unittest.TestCase): with pytest.raises(ConnectionError): requests.get("http://fooobarbangbazbing.httpbin.org") + with pytest.raises(ConnectionError): + requests.get("http://httpbin.org:1") + def test_basicauth_with_netrc(self): auth = ('user', 'pass') wrong_auth = ('wronguser', 'wrongpass') From 226b5c7b0aafc3cb744eb79a666d9a1bd0803a47 Mon Sep 17 00:00:00 2001 From: Massimiliano Torromeo Date: Wed, 3 Sep 2014 09:40:27 +0200 Subject: [PATCH 204/545] Open README and HISTORY as utf-8 encoded files (fixes #2196) --- setup.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 9540a47c..e39d13bf 100755 --- a/setup.py +++ b/setup.py @@ -5,6 +5,8 @@ import sys import requests +from codecs import open + try: from setuptools import setup except ImportError: @@ -27,9 +29,9 @@ packages = [ requires = ['certifi'] -with open('README.rst') as f: +with open('README.rst', 'r', 'utf-8') as f: readme = f.read() -with open('HISTORY.rst') as f: +with open('HISTORY.rst', 'r', 'utf-8') as f: history = f.read() setup( From 9354855648cedbec3a91a9391e067f56e8414814 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 3 Sep 2014 16:01:25 -0500 Subject: [PATCH 205/545] Consolidate error handling --- requests/adapters.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index 6c6597da..bf94bbe7 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -401,11 +401,8 @@ class HTTPAdapter(BaseAdapter): # All is well, return the connection to the pool. conn._put_conn(low_conn) - except socket.error as sockerr: - raise ConnectionError(sockerr, request=request) - - except ProtocolError as e: - raise ConnectionError(e, request=request) + except (ProtocolError, socket.error) as err: + raise ConnectionError(err, request=request) except MaxRetryError as e: if isinstance(e.reason, ConnectTimeoutError): From 958845ae35cee1fa15acc14b5691d787e8bed9bb Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 4 Sep 2014 11:37:45 -0700 Subject: [PATCH 206/545] requests[security] --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 6a7c8de9..c38881f8 100755 --- a/setup.py +++ b/setup.py @@ -61,6 +61,6 @@ setup( ), extras_require={ - 'betterssl': ['pyOpenSSL', 'ndg-httpsclient', 'pyasn1'], + 'security': ['pyOpenSSL', 'ndg-httpsclient', 'pyasn1'], }, ) From a77054f90fd3947db03838d3f244ee802c388314 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 4 Sep 2014 19:40:15 +0100 Subject: [PATCH 207/545] Remove hard certifi dependency and document. --- docs/user/advanced.rst | 17 +++++++++++++++++ setup.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 0de6b1b1..d14b3555 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -741,3 +741,20 @@ coffee. r = requests.get('https://github.com', timeout=None) .. _`connect()`: http://linux.die.net/man/2/connect + +CA Certificates +--------------- + +By default Requests bundles a set of root CAs that it trusts, sourced from the +Mozilla trust store. However, these are only updated once for each Requests +version. This means that if you pin a Requests version your certificates can +become extremely out of date. + +From Requests version 2.4.0 onwards, Requests will attempt to use certificates +from `certifi`_ if it is present on the system. This allows for users to update +their trusted certificates without having to change the code that runs on their +system. + +For the sake of security we recommend upgrading certifi frequently! + +.. _certifi: http://certifi.io/ diff --git a/setup.py b/setup.py index 9540a47c..b790ec71 100755 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ packages = [ 'requests.packages.urllib3.packages.ssl_match_hostname', ] -requires = ['certifi'] +requires = [] with open('README.rst') as f: readme = f.read() From 9c6ae1dc0b6ade1dec2e8c3d5dcd8ef98398cb91 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 4 Sep 2014 19:44:37 +0100 Subject: [PATCH 208/545] Link to Mozilla trust store. --- docs/user/advanced.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index d14b3555..df653d08 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -746,7 +746,7 @@ CA Certificates --------------- By default Requests bundles a set of root CAs that it trusts, sourced from the -Mozilla trust store. However, these are only updated once for each Requests +`Mozilla trust store`_. However, these are only updated once for each Requests version. This means that if you pin a Requests version your certificates can become extremely out of date. @@ -758,3 +758,4 @@ system. For the sake of security we recommend upgrading certifi frequently! .. _certifi: http://certifi.io/ +.. _Mozilla trust store: https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt From 27c83f71770a2a64d56b7d53f9cc67f53cf70ccd Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 7 Sep 2014 12:02:23 -0500 Subject: [PATCH 209/545] Fix #2207 @Lukasa wrote the fix in #2207 --- requests/sessions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index 334db58e..023edfbd 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -134,8 +134,8 @@ class SessionRedirectMixin(object): url = requote_uri(url) prepared_request.url = to_native_string(url) - # cache the url - if resp.is_permanent_redirect: + # Cache the url, unless it redirects to itself. + if resp.is_permanent_redirect and req.url != prepared_request.url: self.redirect_cache[req.url] = prepared_request.url # http://tools.ietf.org/html/rfc7231#section-6.4.4 From bf8f791a936dbaadb6be33546f41bca50a652ac0 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Tue, 9 Sep 2014 08:44:40 +0100 Subject: [PATCH 210/545] Request->Response. --- docs/user/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index ff008347..1a4b2714 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -31,7 +31,7 @@ timeline :: >>> r = requests.get('https://api.github.com/events') -Now, we have a :class:`Request ` object called ``r``. We can +Now, we have a :class:`Response ` object called ``r``. We can get all the information we need from this object. Requests' simple API means that all forms of HTTP request are as obvious. For From dff383ce8f7a7f1c348c1cb5ea970a59fbe48527 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 9 Sep 2014 12:34:34 -0400 Subject: [PATCH 211/545] requests v2.4.1 --- HISTORY.rst | 9 +++++++++ requests/__init__.py | 4 ++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 26d5c65b..2e4a8075 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,15 @@ Release History --------------- +2.4.1 (2014-09-09) +++++++++++++++++++ + +- Now has a "security" package extras set, ``$ pip install requests[security]`` +- Requests will now use Certifi if it is available. +- Capture and re-raise urllib3 ProtocolError +- Bugfix for responses that attempt to redirect to themselves forever (wtf?). + + 2.4.0 (2014-08-29) ++++++++++++++++++ diff --git a/requests/__init__.py b/requests/__init__.py index 33a2b27a..7b823743 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.4.0' -__build__ = 0x020400 +__version__ = '2.4.1' +__build__ = 0x020401 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2014 Kenneth Reitz' From 27b6bfc2134454c7296513d85a40d38a839cdd55 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 9 Sep 2014 12:39:36 -0400 Subject: [PATCH 212/545] mils --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 054118c4..264fff42 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -48,7 +48,7 @@ Testimonials Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku, PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington Post, Twitter, SoundCloud, Kippt, Readability, Sony, and Federal US Institutions -use Requests internally. It has been downloaded over 21,000,000 times from PyPI. +use Requests internally. It has been downloaded over 23,000,000 times from PyPI. **Armin Ronacher** Requests is the perfect example how beautiful an API can be with the From 9e8702d3d54a532f01334f47e68eb90e22a6ab7a Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 9 Sep 2014 12:42:00 -0400 Subject: [PATCH 213/545] lawl --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 264fff42..1701e7f2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -47,7 +47,7 @@ Testimonials Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku, PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington -Post, Twitter, SoundCloud, Kippt, Readability, Sony, and Federal US Institutions +Post, Twitter, SoundCloud, Kippt, Readability, Sony, and Federal US Institutions that prefer to be unnamed use Requests internally. It has been downloaded over 23,000,000 times from PyPI. **Armin Ronacher** From d3566ee1b053b6f24c4bf657ca36e6cb4698a44f Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 12 Sep 2014 03:10:54 +0200 Subject: [PATCH 214/545] Add more tests to `test_invalid_url` This adds tests for the behavior introduced in b149be5d, where `PreparedRequest` was made to skip `parse_url` for e.g. `localhost:3128/`. --- test_requests.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/test_requests.py b/test_requests.py index 2ff61248..9f75a9d9 100755 --- a/test_requests.py +++ b/test_requests.py @@ -18,8 +18,9 @@ from requests.auth import HTTPDigestAuth, _basic_auth_str from requests.compat import ( Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3, builtin_str) from requests.cookies import cookiejar_from_dict, morsel_to_cookie -from requests.exceptions import (InvalidURL, MissingSchema, ConnectTimeout, - ReadTimeout, ConnectionError, Timeout) +from requests.exceptions import (ConnectionError, ConnectTimeout, + InvalidSchema, InvalidURL, MissingSchema, + ReadTimeout, Timeout) from requests.models import PreparedRequest from requests.structures import CaseInsensitiveDict from requests.sessions import SessionRedirectMixin @@ -78,6 +79,12 @@ class RequestsTestCase(unittest.TestCase): def test_invalid_url(self): with pytest.raises(MissingSchema): requests.get('hiwpefhipowhefopw') + with pytest.raises(InvalidSchema): + requests.get('localhost:3128') + with pytest.raises(InvalidSchema): + requests.get('localhost.localdomain:3128/') + with pytest.raises(InvalidSchema): + requests.get('10.122.1.1:3128/') with pytest.raises(InvalidURL): requests.get('http://') From bff8b91638586a8269f79f1bac6e82921702e5b3 Mon Sep 17 00:00:00 2001 From: Daniel Hahler Date: Fri, 12 Sep 2014 17:13:43 +0200 Subject: [PATCH 215/545] Document skipping in PreparedRequest; followup to #2222 --- requests/models.py | 4 +++- test_requests.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index 03ff627a..bbf08c81 100644 --- a/requests/models.py +++ b/requests/models.py @@ -334,7 +334,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): except UnicodeDecodeError: pass - # Don't do any URL preparation for oddball schemes + # Don't do any URL preparation for non-HTTP schemes like `mailto`, + # `data` etc to work around exceptions from `url_parse`, which + # handles RFC 3986 only. if ':' in url and not url.lower().startswith('http'): self.url = url return diff --git a/test_requests.py b/test_requests.py index 9f75a9d9..4fccc346 100755 --- a/test_requests.py +++ b/test_requests.py @@ -922,7 +922,7 @@ class RequestsTestCase(unittest.TestCase): assert p.headers['Content-Length'] == length - def test_oddball_schemes_dont_check_URLs(self): + def test_nonhttp_schemes_dont_check_URLs(self): test_urls = ( 'data:image/gif;base64,R0lGODlhAQABAHAAACH5BAUAAAAALAAAAAABAAEAAAICRAEAOw==', 'file:///etc/passwd', From 4707d11004a201a89257fa3ab5365fb3dc9d2f90 Mon Sep 17 00:00:00 2001 From: Martin Jul Date: Tue, 16 Sep 2014 11:38:40 +0200 Subject: [PATCH 216/545] Added example of how to send multiple files in one request. --- AUTHORS.rst | 1 + docs/user/quickstart.rst | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 1ab53ef8..1e084d8f 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -154,3 +154,4 @@ Patches and Suggestions - Константин Подшумок (`@podshumok `_) - Ben Bass (`@codedstructure `_) - Jonathan Wong (`@ContinuousFunction `_) +- Martin Jul (`@mjul `_) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 1a4b2714..4c8a006b 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -271,6 +271,30 @@ support this, but there is a separate package which does - `_ for more details about how to use it. +POST Multiple Multipart-Encoded Files +------------------------------------- + +You can send multiple files in one request. For example, suppose you want to +upload image files to an HTML form with a multiple file field 'images': + + + +To do that, just set files to a list of tuples of (form_field_name, file_info): + + >>> url = 'http://httpbin.org/post' + >>> multiple_files = [('images', ('foo.png', open('foo.png', 'rb'), 'image/png')), + ('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))] + >>> r = requests.post(url, files=multiple_files) + >>> r.text + { + ... + 'files': {'images': 'data:image/png;base64,iVBORw ....'} + 'Content-Type': 'multipart/form-data; boundary=3131623adb2043caaeb5538cc7aa0b3a', + ... + } + + + Response Status Codes --------------------- From 0924069b689c7323f47444b6a19bc037c43b49e2 Mon Sep 17 00:00:00 2001 From: Martin Jul Date: Tue, 16 Sep 2014 11:54:31 +0200 Subject: [PATCH 217/545] Moved multiple files upload example to advanced section. --- docs/user/advanced.rst | 24 ++++++++++++++++++++++++ docs/user/quickstart.rst | 25 ++----------------------- 2 files changed, 26 insertions(+), 23 deletions(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index d285c181..b5758369 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -267,6 +267,30 @@ a length) for your body:: requests.post('http://some.url/chunked', data=gen()) + +POST Multiple Multipart-Encoded Files +------------------------------------- + +You can send multiple files in one request. For example, suppose you want to +upload image files to an HTML form with a multiple file field 'images': + + + +To do that, just set files to a list of tuples of (form_field_name, file_info): + + >>> url = 'http://httpbin.org/post' + >>> multiple_files = [('images', ('foo.png', open('foo.png', 'rb'), 'image/png')), + ('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))] + >>> r = requests.post(url, files=multiple_files) + >>> r.text + { + ... + 'files': {'images': 'data:image/png;base64,iVBORw ....'} + 'Content-Type': 'multipart/form-data; boundary=3131623adb2043caaeb5538cc7aa0b3a', + ... + } + + Event Hooks ----------- diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 4c8a006b..9d4e6904 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -270,29 +270,8 @@ support this, but there is a separate package which does - ``requests-toolbelt``. You should read `the toolbelt's documentation `_ for more details about how to use it. - -POST Multiple Multipart-Encoded Files -------------------------------------- - -You can send multiple files in one request. For example, suppose you want to -upload image files to an HTML form with a multiple file field 'images': - - - -To do that, just set files to a list of tuples of (form_field_name, file_info): - - >>> url = 'http://httpbin.org/post' - >>> multiple_files = [('images', ('foo.png', open('foo.png', 'rb'), 'image/png')), - ('images', ('bar.png', open('bar.png', 'rb'), 'image/png'))] - >>> r = requests.post(url, files=multiple_files) - >>> r.text - { - ... - 'files': {'images': 'data:image/png;base64,iVBORw ....'} - 'Content-Type': 'multipart/form-data; boundary=3131623adb2043caaeb5538cc7aa0b3a', - ... - } - +For sending multiple files in one request refer to the :ref:`advanced ` +section. Response Status Codes From 466a70f7c1ff4a268df3ff2f28a8afb78046b8bd Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 20 Sep 2014 15:00:36 +0100 Subject: [PATCH 218/545] Fixup the remaining references to timeline.json. --- docs/user/quickstart.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 9d4e6904..f3543567 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -81,7 +81,7 @@ We can read the content of the server's response. Consider the GitHub timeline again:: >>> import requests - >>> r = requests.get('https://github.com/timeline.json') + >>> r = requests.get('https://api.github.com/events') >>> r.text u'[{"repository":{"open_issues":0,"url":"https://github.com/... @@ -134,7 +134,7 @@ JSON Response Content There's also a builtin JSON decoder, in case you're dealing with JSON data:: >>> import requests - >>> r = requests.get('https://github.com/timeline.json') + >>> r = requests.get('https://api.github.com/events') >>> r.json() [{u'repository': {u'open_issues': 0, u'url': 'https://github.com/... @@ -150,7 +150,7 @@ In the rare case that you'd like to get the raw socket response from the server, you can access ``r.raw``. If you want to do this, make sure you set ``stream=True`` in your initial request. Once you do, you can do this:: - >>> r = requests.get('https://github.com/timeline.json', stream=True) + >>> r = requests.get('https://api.github.com/events', stream=True) >>> r.raw >>> r.raw.read(10) From d243668d16e5a1b9d886f12f43ab521a1774df65 Mon Sep 17 00:00:00 2001 From: Joe Alcorn Date: Sat, 20 Sep 2014 20:56:00 +0100 Subject: [PATCH 219/545] Remove invoke from requirements.txt, docs --- docs/dev/todo.rst | 3 +-- requirements.txt | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/dev/todo.rst b/docs/dev/todo.rst index 1f2216b5..e59213b4 100644 --- a/docs/dev/todo.rst +++ b/docs/dev/todo.rst @@ -24,8 +24,7 @@ Development Dependencies You'll need to install py.test in order to run the Requests' test suite:: $ pip install -r requirements.txt - $ invoke test - py.test + $ py.test platform darwin -- Python 2.7.3 -- pytest-2.3.4 collected 25 items diff --git a/requirements.txt b/requirements.txt index 5616d2fe..2ac5d3f3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ py==1.4.12 pytest==2.3.4 pytest-cov==1.6 -invoke==0.2.0 wheel From fdf932c61c7ff77d494a4a413cc5fbdb71328ac7 Mon Sep 17 00:00:00 2001 From: Tim Konick Date: Mon, 22 Sep 2014 12:04:29 -0400 Subject: [PATCH 220/545] raise RuntimeError when a single streamed request calls *iter methods than once --- requests/models.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index bbf08c81..a6af6427 100644 --- a/requests/models.py +++ b/requests/models.py @@ -655,8 +655,12 @@ class Response(object): self._content_consumed = True - # simulate reading small chunks of the content - reused_chunks = iter_slices(self._content, chunk_size) + if self._content_consumed and isinstance(self._content, bool): + raise RuntimeError( + 'The content for this response was already consumed') + else: + # simulate reading small chunks of the content + reused_chunks = iter_slices(self._content, chunk_size) stream_chunks = generate() From d6470870d08828c1d332ae446d5288eaab55d1f1 Mon Sep 17 00:00:00 2001 From: Tim Konick Date: Mon, 22 Sep 2014 12:46:07 -0400 Subject: [PATCH 221/545] rm `else` after `if` then `raise` block --- requests/models.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/requests/models.py b/requests/models.py index a6af6427..5974507b 100644 --- a/requests/models.py +++ b/requests/models.py @@ -658,9 +658,8 @@ class Response(object): if self._content_consumed and isinstance(self._content, bool): raise RuntimeError( 'The content for this response was already consumed') - else: - # simulate reading small chunks of the content - reused_chunks = iter_slices(self._content, chunk_size) + # simulate reading small chunks of the content + reused_chunks = iter_slices(self._content, chunk_size) stream_chunks = generate() From 58e0a6f4a020db660119f1c9459ab37ba55b92d4 Mon Sep 17 00:00:00 2001 From: Tim Konick Date: Mon, 22 Sep 2014 15:51:10 -0400 Subject: [PATCH 222/545] add-in StreamConsumedError --- requests/exceptions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requests/exceptions.py b/requests/exceptions.py index d8f05f08..32a18542 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -89,3 +89,6 @@ class ChunkedEncodingError(RequestException): class ContentDecodingError(RequestException, BaseHTTPError): """Failed to decode response content""" + +class StreamConsumedError(RequestException): + """The content for this response was already consumed""" From 6e5f7bb9d397ade498ee22d9785863ef1b8ad946 Mon Sep 17 00:00:00 2001 From: Tim Konick Date: Mon, 22 Sep 2014 15:51:59 -0400 Subject: [PATCH 223/545] using the `StreamConsumedError` --- requests/models.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requests/models.py b/requests/models.py index 5974507b..897cd6fd 100644 --- a/requests/models.py +++ b/requests/models.py @@ -22,8 +22,9 @@ from .packages.urllib3.util import parse_url from .packages.urllib3.exceptions import ( DecodeError, ReadTimeoutError, ProtocolError) from .exceptions import ( - HTTPError, RequestException, MissingSchema, InvalidURL, - ChunkedEncodingError, ContentDecodingError, ConnectionError) + HTTPError, RequestException, MissingSchema, InvalidURL, + ChunkedEncodingError, ContentDecodingError, ConnectionError, + StreamConsumedError) from .utils import ( guess_filename, get_auth_from_url, requote_uri, stream_decode_response_unicode, to_key_val_list, parse_header_links, @@ -656,8 +657,7 @@ class Response(object): self._content_consumed = True if self._content_consumed and isinstance(self._content, bool): - raise RuntimeError( - 'The content for this response was already consumed') + raise StreamConsumedError() # simulate reading small chunks of the content reused_chunks = iter_slices(self._content, chunk_size) From 64ba451049c614eb14d336c2e7989ffaa81b2bb5 Mon Sep 17 00:00:00 2001 From: Tim Konick Date: Mon, 22 Sep 2014 15:58:54 -0400 Subject: [PATCH 224/545] make StreamConsumedError doubly inherit --- requests/exceptions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/exceptions.py b/requests/exceptions.py index 32a18542..34c7a0db 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -90,5 +90,5 @@ class ChunkedEncodingError(RequestException): class ContentDecodingError(RequestException, BaseHTTPError): """Failed to decode response content""" -class StreamConsumedError(RequestException): +class StreamConsumedError(RequestException, TypeError): """The content for this response was already consumed""" From ac44b482971d93cf5e7d4985a0b3fff52b3d2059 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 22 Sep 2014 21:01:36 -0500 Subject: [PATCH 225/545] Avoid getting stuck in a loop This prevents a case where we make a request to URL A, which 301s to B which would then 301 back to A. Alternatively, for less simple schemes, this will also prevent us from getting stuck in a loop, e.g., it will prevent the following from causing an endless loop: A -> B -> C -> D -> E -> F -- ^ \ | / ---<------------<----------<- --- requests/sessions.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/requests/sessions.py b/requests/sessions.py index 023edfbd..f9873064 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -535,8 +535,13 @@ class Session(SessionRedirectMixin): if not isinstance(request, PreparedRequest): raise ValueError('You can only send PreparedRequests.') + checked_urls = set() while request.url in self.redirect_cache: - request.url = self.redirect_cache.get(request.url) + 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 allow_redirects = kwargs.pop('allow_redirects', True) From 671ba85d410febab91ac580eab8282a485c89108 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 22 Sep 2014 21:08:29 -0500 Subject: [PATCH 226/545] Correct redirection introduction The history attribute contains responses, not requests. Thanks to Yang Yang for reporting this to me via email --- docs/user/quickstart.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 1a4b2714..1e852c99 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -369,9 +369,9 @@ HEAD. We can use the ``history`` property of the Response object to track redirection. The :meth:`Response.history ` list contains the -:class:`Request ` objects that were created in order to +:class:`Response ` objects that were created in order to complete the request. The list is sorted from the oldest to the most recent -request. +response. For example, GitHub redirects all HTTP requests to HTTPS:: From 3635b1501d5bfb17678404f199fb6efe2b3249ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Kundr=C3=A1t?= Date: Thu, 25 Sep 2014 20:06:04 +0200 Subject: [PATCH 227/545] docs: Clarify how to pass a custom set of CAs This new wording makes it hopefully easier to find how to override the system-provided list of trusted CAs. I failed to find this through some googling and had to resort to asking on IRC previously. --- docs/user/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index b5758369..b1e334bb 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -166,7 +166,7 @@ I don't have SSL setup on this domain, so it fails. Excellent. GitHub does thoug >>> requests.get('https://github.com', verify=True) -You can also pass ``verify`` the path to a CA_BUNDLE file for private certs. You can also set the ``REQUESTS_CA_BUNDLE`` environment variable. +You can pass ``verify`` the path to a CA_BUNDLE file with certificates of trusted CAs. This list of trusted CAs can also be specified through the ``REQUESTS_CA_BUNDLE`` environment variable. Requests can also ignore verifying the SSL certificate if you set ``verify`` to False. From 6e1db21733214e4d6707b0bc91b80499d4e946db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Thu, 25 Sep 2014 19:47:56 +0000 Subject: [PATCH 228/545] fix #2247 We have to pass urllib3 the url without the authentication information, else it will be parsed by httplib as a netloc and included in the request line and Host header --- requests/adapters.py | 4 ++-- requests/utils.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index d17d9e69..8225d735 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -17,7 +17,7 @@ from .packages.urllib3.response import HTTPResponse from .packages.urllib3.util import Timeout as TimeoutSauce from .compat import urlparse, basestring, urldefrag from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers, - prepend_scheme_if_needed, get_auth_from_url) + prepend_scheme_if_needed, get_auth_from_url, urldefragauth) from .structures import CaseInsensitiveDict from .packages.urllib3.exceptions import ConnectTimeoutError from .packages.urllib3.exceptions import HTTPError as _HTTPError @@ -270,7 +270,7 @@ class HTTPAdapter(BaseAdapter): proxy = proxies.get(scheme) if proxy and scheme != 'https': - url, _ = urldefrag(request.url) + url = urldefragauth(request.url) else: url = request.path_url diff --git a/requests/utils.py b/requests/utils.py index 539a68a0..44ab005d 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -672,3 +672,18 @@ def to_native_string(string, encoding='ascii'): out = string.decode(encoding) return out + + +def urldefragauth(url): + """ + Given a url remove the fragment and the authentication part + """ + scheme, netloc, path, params, query, fragment = urlparse(url) + + # see func:`prepend_scheme_if_needed` + if not netloc: + netloc, path = path, netloc + + netloc = netloc.rsplit('@', 1)[-1] + + return urlunparse((scheme, netloc, path, params, query, '')) From cd5eb629616012fcfa2835a633e04cdc47c33ec9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Fri, 26 Sep 2014 22:28:29 +0000 Subject: [PATCH 229/545] remove unused import --- requests/adapters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/adapters.py b/requests/adapters.py index 8225d735..abb25d11 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -15,7 +15,7 @@ from .packages.urllib3 import Retry from .packages.urllib3.poolmanager import PoolManager, proxy_from_url from .packages.urllib3.response import HTTPResponse from .packages.urllib3.util import Timeout as TimeoutSauce -from .compat import urlparse, basestring, urldefrag +from .compat import urlparse, basestring from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers, prepend_scheme_if_needed, get_auth_from_url, urldefragauth) from .structures import CaseInsensitiveDict From c28da22e9c42e22b303bb07da434ce65e10c0cb2 Mon Sep 17 00:00:00 2001 From: Yossi Gottlieb Date: Sat, 27 Sep 2014 20:42:58 +0300 Subject: [PATCH 230/545] A fix for #1979 repeat HTTP digest authentication after redirect. --- requests/auth.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/requests/auth.py b/requests/auth.py index 9b6426dc..41be9829 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -150,6 +150,14 @@ class HTTPDigestAuth(AuthBase): return 'Digest %s' % (base) + def handle_302(self, r, **kwargs): + """Reset num_401_calls counter on redirects.""" + try: + delattr(self, 'num_401_calls') + except AttributeError: + pass + return r + def handle_401(self, r, **kwargs): """Takes the given response and tries digest-auth, if needed.""" @@ -194,4 +202,5 @@ class HTTPDigestAuth(AuthBase): except AttributeError: pass r.register_hook('response', self.handle_401) + r.register_hook('response', self.handle_302) return r From a68d1b4517395e4845fa8405f043f8aa3110f83c Mon Sep 17 00:00:00 2001 From: Joe Alcorn Date: Mon, 29 Sep 2014 21:11:45 +0100 Subject: [PATCH 231/545] Support bytestring URLs on Python 3.x --- AUTHORS.rst | 1 + requests/models.py | 13 +++++++------ test_requests.py | 6 ++++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 1e084d8f..7c02b4c0 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -155,3 +155,4 @@ Patches and Suggestions - Ben Bass (`@codedstructure `_) - Jonathan Wong (`@ContinuousFunction `_) - Martin Jul (`@mjul `_) +- Joe Alcorn (`@buttscicles `_) diff --git a/requests/models.py b/requests/models.py index bbf08c81..c5cb3f56 100644 --- a/requests/models.py +++ b/requests/models.py @@ -326,13 +326,14 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): def prepare_url(self, url, params): """Prepares the given HTTP URL.""" #: Accept objects that have string representations. + #: We're unable to blindy call unicode/str functions + #: as this will include the bytestring indicator (b'') + #: on python 3.x. + #: https://github.com/kennethreitz/requests/pull/2238 try: - url = unicode(url) - except NameError: - # We're on Python 3. - url = str(url) - except UnicodeDecodeError: - pass + url = url.decode('utf8') + except AttributeError: + url = unicode(url) if is_py2 else str(url) # Don't do any URL preparation for non-HTTP schemes like `mailto`, # `data` etc to work around exceptions from `url_parse`, which diff --git a/test_requests.py b/test_requests.py index 4fccc346..ae879cd1 100755 --- a/test_requests.py +++ b/test_requests.py @@ -591,6 +591,12 @@ class RequestsTestCase(unittest.TestCase): assert resp.json()['headers'][ 'Dummy-Auth-Test'] == 'dummy-auth-test-ok' + def test_prepare_request_with_bytestring_url(self): + req = requests.Request('GET', b'https://httpbin.org/') + s = requests.Session() + prep = s.prepare_request(req) + assert prep.url == "https://httpbin.org/" + def test_links(self): r = requests.Response() r.headers = { From 8ed941fa6991355753e698f6b253096d57f6c7e4 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 30 Sep 2014 16:03:31 -0500 Subject: [PATCH 232/545] Fix a couple of issues I noticed - Don't _ prefix json in prepare_body - Don't initialize json to [] - Don't initialize json to {} - Reorder parameters to PreparedRequest.prepare - Remove extra parentheses - Update docstring --- requests/models.py | 20 +++++++++++--------- requests/sessions.py | 2 +- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/requests/models.py b/requests/models.py index a96fc4e6..c1f7f561 100644 --- a/requests/models.py +++ b/requests/models.py @@ -46,6 +46,8 @@ DEFAULT_REDIRECT_LIMIT = 30 CONTENT_CHUNK_SIZE = 10 * 1024 ITER_CHUNK_SIZE = 512 +json_dumps = json.dumps + class RequestEncodingMixin(object): @property @@ -189,8 +191,8 @@ class Request(RequestHooksMixin): :param url: URL to send. :param headers: dictionary of headers to send. :param files: dictionary of {filename: fileobject} files to multipart upload. - :param data: the body to attach the request. If a dictionary is provided, form-encoding will take place. - :param json: json for the body to attach the request. + :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 params: dictionary of URL parameters to append to the URL. :param auth: Auth handler or (user, pass) tuple. :param cookies: dictionary or CookieJar of cookies to attach to this request. @@ -218,7 +220,6 @@ class Request(RequestHooksMixin): # Default empty dicts for dict params. data = [] if data is None else data - json = [] if json is None else json files = [] if files is None else files headers = {} if headers is None else headers params = {} if params is None else params @@ -294,7 +295,8 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): self.hooks = default_hooks() def prepare(self, method=None, url=None, headers=None, files=None, - data=None, json=None, params=None, auth=None, cookies=None, hooks=None): + data=None, params=None, auth=None, cookies=None, hooks=None, + json=None): """Prepares the entire request with the given parameters.""" self.prepare_method(method) @@ -402,7 +404,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): else: self.headers = CaseInsensitiveDict() - def prepare_body(self, data, files, _json=None): + def prepare_body(self, data, files, json=None): """Prepares the given HTTP body data.""" # Check if file, fo, generator, iterator. @@ -413,9 +415,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): content_type = None length = None - if _json is not None: + if json is not None: content_type = 'application/json' - data = json.dumps(_json) + body = json_dumps(json) is_stream = all([ hasattr(data, '__iter__'), @@ -442,7 +444,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if files: (body, content_type) = self._encode_files(files, data) else: - if data and _json is None: + if data and json is None: body = self._encode_params(data) if isinstance(data, basestring) or hasattr(data, 'read'): content_type = None @@ -452,7 +454,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): self.prepare_content_length(body) # Add content-type if it wasn't explicitly provided. - if (content_type) and (not 'content-type' in self.headers): + if content_type and ('content-type' not in self.headers): self.headers['Content-Type'] = content_type self.body = body diff --git a/requests/sessions.py b/requests/sessions.py index 7942447f..c5ad0060 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -430,7 +430,7 @@ class Session(SessionRedirectMixin): headers = headers, files = files, data = data or {}, - json = json or {}, + json = json, params = params or {}, auth = auth, cookies = cookies, From b1f3a2dd66f1da05976d7f60261ce0665d4b6469 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 4 Oct 2014 23:04:35 -0500 Subject: [PATCH 233/545] Remove timeout from __attrs__ We do not allow the user to set the timeout value on the Session any longer so this is extraneous --- requests/sessions.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index f9873064..29bd8994 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -271,9 +271,10 @@ class Session(SessionRedirectMixin): """ __attrs__ = [ - 'headers', 'cookies', 'auth', 'timeout', 'proxies', 'hooks', - 'params', 'verify', 'cert', 'prefetch', 'adapters', 'stream', - 'trust_env', 'max_redirects', 'redirect_cache'] + 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify', + 'cert', 'prefetch', 'adapters', 'stream', 'trust_env', + 'max_redirects', 'redirect_cache' + ] def __init__(self): From 90dc467fc0b1f4f1b7472acb5708e3017a53fa70 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 5 Oct 2014 13:13:42 -0400 Subject: [PATCH 234/545] v2.4.2 changelog --- HISTORY.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 2e4a8075..f470b816 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,28 @@ Release History --------------- +2.4.2 (2014-10-05) +++++++++++++++++++ + +**Improvements** + +- FINALLY! Add json parameter for uploads! (`#2258 `_) +- Support for bytestring URLs on Python 3.x (`#2238 `_) + +**Bugfixes** + +- Avoid getting stuck in a loop (`#2244 `_) +- Multiple calls to iter* fail with unhelpful error. (`#2240 `_, `#2241 `_) +- Automatically defrag authentication schemes from host/pass URIs. (`#2249 `_) + +**Documentation** + +- Correct redirection introduction (`#2245 `_) +- Added example of how to send multiple files in one request. (`#2227 `_) +- Clarify how to pass a custom set of CAs (`#2248 `_) + + + 2.4.1 (2014-09-09) ++++++++++++++++++ From 79f56e2e7e9cf1828da8287735751a998269a5f1 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 5 Oct 2014 13:14:38 -0400 Subject: [PATCH 235/545] fix documentation for utils.get_unicode_from_response() :) --- requests/utils.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/requests/utils.py b/requests/utils.py index 539a68a0..5d34ab55 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -351,10 +351,7 @@ def get_unicode_from_response(r): Tried: 1. charset from content-type - - 2. every encodings from ```` - - 3. fall back and replace all unicode characters + 2. fall back and replace all unicode characters """ From 779c001e44ed436a805a6fea5b4d2895b381d7b3 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sun, 5 Oct 2014 13:15:33 -0400 Subject: [PATCH 236/545] requests v2.4.2 --- requests/__init__.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requests/__init__.py b/requests/__init__.py index 7b823743..169a6cd0 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -13,7 +13,7 @@ Requests is an HTTP library, written in Python, for human beings. Basic GET usage: >>> import requests - >>> r = requests.get('http://python.org') + >>> r = requests.get('https://www.python.org') >>> r.status_code 200 >>> 'Python is a programming language' in r.content @@ -22,7 +22,7 @@ usage: ... or POST: >>> payload = dict(key1='value1', key2='value2') - >>> r = requests.post("http://httpbin.org/post", data=payload) + >>> r = requests.post('http://httpbin.org/post', data=payload) >>> print(r.text) { ... @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.4.1' -__build__ = 0x020401 +__version__ = '2.4.2' +__build__ = 0x020402 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2014 Kenneth Reitz' From ca2ebc5b682452bc870efb2f7123feda9a80a8ea Mon Sep 17 00:00:00 2001 From: MinRK Date: Sun, 5 Oct 2014 16:30:11 -0700 Subject: [PATCH 237/545] allow unicode URLs on Python 2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit on Python 2 u'é'.decode('utf8') fails with UnicodeEncodeError, but only AttributeError is caught. This only calls decode on known bytes objects. --- requests/models.py | 4 ++-- test_requests.py | 8 ++++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index 245521dc..aaa50632 100644 --- a/requests/models.py +++ b/requests/models.py @@ -338,9 +338,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): #: as this will include the bytestring indicator (b'') #: on python 3.x. #: https://github.com/kennethreitz/requests/pull/2238 - try: + if isinstance(url, bytes): url = url.decode('utf8') - except AttributeError: + else: url = unicode(url) if is_py2 else str(url) # Don't do any URL preparation for non-HTTP schemes like `mailto`, diff --git a/test_requests.py b/test_requests.py index 8864b2d1..0d93893b 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1507,5 +1507,13 @@ def test_prepared_request_complete_copy(): ) assert_copy(p, p.copy()) +def test_prepare_unicode_url(): + p = PreparedRequest() + p.prepare( + method='GET', + url=u('http://www.example.com/üniçø∂é') + ) + assert_copy(p, p.copy()) + if __name__ == '__main__': unittest.main() From 0e4efab270780726fbf4f8b6704f8f22741399d6 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 5 Oct 2014 18:55:59 -0500 Subject: [PATCH 238/545] Re-order params for backwards compat --- requests/models.py | 4 ++-- requests/sessions.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/requests/models.py b/requests/models.py index 245521dc..831ffb6e 100644 --- a/requests/models.py +++ b/requests/models.py @@ -213,11 +213,11 @@ class Request(RequestHooksMixin): headers=None, files=None, data=None, - json=None, params=None, auth=None, cookies=None, - hooks=None): + hooks=None, + json=None): # Default empty dicts for dict params. data = [] if data is None else data diff --git a/requests/sessions.py b/requests/sessions.py index a133e6a3..d701ff2e 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -378,7 +378,6 @@ class Session(SessionRedirectMixin): def request(self, method, url, params=None, data=None, - json=None, headers=None, cookies=None, files=None, @@ -389,7 +388,8 @@ class Session(SessionRedirectMixin): hooks=None, stream=None, verify=None, - cert=None): + cert=None, + json=None): """Constructs a :class:`Request `, prepares it and sends it. Returns :class:`Response ` object. From 78d12bbbe9a1a5ddf0a50aa793dc0579798ff7bd Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 6 Oct 2014 05:42:55 -0400 Subject: [PATCH 239/545] v2.4.3 --- HISTORY.rst | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index f470b816..0d0d216d 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,16 @@ Release History --------------- +2.4.3 (2014-10-06) +++++++++++++++++++ + +**Bugfixes** + +- Unicode URL improvements for Python 2. +- Re-order JSON param for backwards compat. +- Automatically defrag authentication schemes from host/pass URIs. (`#2249 `_) + + 2.4.2 (2014-10-05) ++++++++++++++++++ @@ -15,7 +25,6 @@ Release History - Avoid getting stuck in a loop (`#2244 `_) - Multiple calls to iter* fail with unhelpful error. (`#2240 `_, `#2241 `_) -- Automatically defrag authentication schemes from host/pass URIs. (`#2249 `_) **Documentation** From 46d646064ca0836a7d7b4d50ea2c762d12ff8ce1 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 6 Oct 2014 05:44:25 -0400 Subject: [PATCH 240/545] v2.4.3 --- requests/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/__init__.py b/requests/__init__.py index 169a6cd0..d5e1956e 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.4.2' -__build__ = 0x020402 +__version__ = '2.4.3' +__build__ = 0x020403 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2014 Kenneth Reitz' From 6ff6f756c034ad0a946c711d0e136cfbd077ab62 Mon Sep 17 00:00:00 2001 From: Yossi Gottlieb Date: Wed, 8 Oct 2014 00:06:01 +0300 Subject: [PATCH 241/545] Clean up, support all redirects, fix potential endless 401 loop. --- requests/auth.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/requests/auth.py b/requests/auth.py index 41be9829..b2b341f5 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -17,6 +17,7 @@ from base64 import b64encode from .compat import urlparse, str from .cookies import extract_cookies_to_jar from .utils import parse_dict_header, to_native_string +from .status_codes import codes CONTENT_TYPE_FORM_URLENCODED = 'application/x-www-form-urlencoded' CONTENT_TYPE_MULTI_PART = 'multipart/form-data' @@ -150,13 +151,12 @@ class HTTPDigestAuth(AuthBase): return 'Digest %s' % (base) - def handle_302(self, r, **kwargs): + def handle_redirect(self, r, **kwargs): """Reset num_401_calls counter on redirects.""" - try: - delattr(self, 'num_401_calls') - except AttributeError: - pass - return r + if r.status_code in ( + codes.temporary_redirect, + codes.permanent_redirect): + setattr(self, 'num_401_calls', 1) def handle_401(self, r, **kwargs): """Takes the given response and tries digest-auth, if needed.""" @@ -190,7 +190,7 @@ class HTTPDigestAuth(AuthBase): return _r - setattr(self, 'num_401_calls', 1) + setattr(self, 'num_401_calls', num_401_calls + 1) return r def __call__(self, r): @@ -202,5 +202,5 @@ class HTTPDigestAuth(AuthBase): except AttributeError: pass r.register_hook('response', self.handle_401) - r.register_hook('response', self.handle_302) + r.register_hook('response', self.handle_redirect) return r From 4594667f07cac83ea95a19747dea1588aa2cb59e Mon Sep 17 00:00:00 2001 From: mikecool1000 Date: Wed, 8 Oct 2014 19:26:07 -0700 Subject: [PATCH 242/545] Update utils.py Fixed -2250 --- requests/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/utils.py b/requests/utils.py index 1868f861..058b608b 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -567,7 +567,7 @@ def parse_header_links(value): replace_chars = " '\"" - for val in value.split(","): + for val in re.split(",\ *<",value): try: url, params = val.split(";", 1) except ValueError: From ce1ac88543238c865e797eb59bbaa092c4d43ec7 Mon Sep 17 00:00:00 2001 From: mikecool1000 Date: Thu, 9 Oct 2014 12:22:18 -0700 Subject: [PATCH 243/545] Fixed #2250 --- requests/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/utils.py b/requests/utils.py index 058b608b..06e66a80 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -567,7 +567,7 @@ def parse_header_links(value): replace_chars = " '\"" - for val in re.split(",\ *<",value): + for val in re.split(",\ *<", value): try: url, params = val.split(";", 1) except ValueError: From 589f2f6d6e4b3934637c7de2b3e56b298cf21343 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Thu, 9 Oct 2014 21:07:01 -0500 Subject: [PATCH 244/545] Add Release History to the sidebar Closes #2269 --- docs/_templates/sidebarintro.html | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 09fe51a4..00375def 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -49,4 +49,5 @@
  • Requests @ GitHub
  • Requests @ PyPI
  • Issue Tracker
  • +
  • Release History
From 8339b60fd5bbe0ef9dae1764b3964f515364a54f Mon Sep 17 00:00:00 2001 From: Michael DeLay Date: Fri, 10 Oct 2014 09:58:30 -0700 Subject: [PATCH 245/545] Made more better --- requests/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/utils.py b/requests/utils.py index 06e66a80..182348da 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -567,7 +567,7 @@ def parse_header_links(value): replace_chars = " '\"" - for val in re.split(",\ *<", value): + for val in re.split(", *<", value): try: url, params = val.split(";", 1) except ValueError: From 4d498db3ed2398bf777a64121bdd974f7dc93b0a Mon Sep 17 00:00:00 2001 From: morganthrapp Date: Tue, 14 Oct 2014 10:56:05 -0400 Subject: [PATCH 246/545] Update sidebarintro.html Fix issue #2279. "Gittip" changed to "Gratipay". Tip button extended from 48px to 60px. --- docs/_templates/sidebarintro.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 00375def..a5d5c748 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -17,12 +17,12 @@

Donate

- If you love Requests, consider supporting the author on Gittip: + If you love Requests, consider supporting the author on Gratipay:

+ width="60pt" height="20pt">

From 3a6348dbb9af4b811def72c67637bba04706916e Mon Sep 17 00:00:00 2001 From: syedsuhail Date: Sun, 19 Oct 2014 13:18:26 +0530 Subject: [PATCH 247/545] Fix #2288. Change urllib3 and chardet workflow --- AUTHORS.rst | 1 + Makefile | 10 ++-------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 7c02b4c0..a1b5ec46 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -156,3 +156,4 @@ Patches and Suggestions - Jonathan Wong (`@ContinuousFunction `_) - Martin Jul (`@mjul `_) - Joe Alcorn (`@buttscicles `_) +- Syed Suhail Ahmed (`@syedsuhail `_) diff --git a/Makefile b/Makefile index 687f21a3..dea33a3f 100644 --- a/Makefile +++ b/Makefile @@ -20,16 +20,10 @@ certs: deps: urllib3 chardet urllib3: - rm -fr requests/packages/urllib3 - git clone https://github.com/shazow/urllib3.git - mv urllib3/urllib3 requests/packages/ - rm -fr urllib3 + git clone https://github.com/shazow/urllib3.git && rm -fr requests/packages/urllib3 && mv urllib3/urllib3 requests/packages/ && rm -fr urllib3 chardet: - rm -fr requests/packages/chardet - git clone https://github.com/chardet/chardet.git - mv chardet/chardet requests/packages/ - rm -fr chardet + git clone https://github.com/chardet/chardet.git && rm -fr requests/packages/chardet && mv chardet/chardet requests/packages/ && rm -fr chardet publish: python setup.py register From 96efb5ec9638f2a057e0206db62ee673a3e610dc Mon Sep 17 00:00:00 2001 From: syedsuhail Date: Sun, 19 Oct 2014 14:06:02 +0530 Subject: [PATCH 248/545] Fix #2279. Update layout css --- docs/_templates/sidebarintro.html | 6 +++--- docs/_templates/sidebarlogo.html | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index a5d5c748..e60cf451 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -17,12 +17,12 @@

Donate

- If you love Requests, consider supporting the author on Gratipay: + If you love Requests, consider supporting the author on Gratipay:

+ src="https://www.gratipay.com/kennethreitz/widget.html" + width="80pt" height="20pt">

diff --git a/docs/_templates/sidebarlogo.html b/docs/_templates/sidebarlogo.html index 6aca76eb..930fdb3d 100644 --- a/docs/_templates/sidebarlogo.html +++ b/docs/_templates/sidebarlogo.html @@ -17,12 +17,12 @@

Donate

- If you love Requests, consider supporting the author on Gittip: + If you love Requests, consider supporting the author on Gittip:

+ src="https://www.gratipay.com/kennethreitz/widget.html" + width="80pt" height="20pt">

Get Updates

From f5ff05be1ed04bcc50e33af28fb54382466a32e8 Mon Sep 17 00:00:00 2001 From: Val Tenyotkin Date: Tue, 21 Oct 2014 18:03:11 -0700 Subject: [PATCH 249/545] Adding a custom line delimiter to iter_lines() --- requests/models.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index 17e55988..6689ef7f 100644 --- a/requests/models.py +++ b/requests/models.py @@ -682,7 +682,7 @@ class Response(object): return chunks - def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None): + def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None, newline=None): """Iterates over the response data, one line at a time. When stream=True is set on the request, this avoids reading the content at once into memory for large responses. @@ -694,7 +694,11 @@ class Response(object): if pending is not None: chunk = pending + chunk - lines = chunk.splitlines() + + if newline: + lines = chunk.split(newline) + else: + lines = chunk.splitlines() if lines and lines[-1] and chunk and lines[-1][-1] == chunk[-1]: pending = lines.pop() From 3d8823cafc82fd9849eef6650a54a61554079b61 Mon Sep 17 00:00:00 2001 From: Yossi Gottlieb Date: Thu, 23 Oct 2014 13:46:44 +0300 Subject: [PATCH 250/545] Clean up handle_redirect. --- requests/auth.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/requests/auth.py b/requests/auth.py index b2b341f5..010919f3 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -153,9 +153,7 @@ class HTTPDigestAuth(AuthBase): def handle_redirect(self, r, **kwargs): """Reset num_401_calls counter on redirects.""" - if r.status_code in ( - codes.temporary_redirect, - codes.permanent_redirect): + if r.is_redirect: setattr(self, 'num_401_calls', 1) def handle_401(self, r, **kwargs): From 3602690886a0b1795f721664e93c96f4b4c9f5a9 Mon Sep 17 00:00:00 2001 From: Val Tenyotkin Date: Fri, 24 Oct 2014 16:37:35 -0700 Subject: [PATCH 251/545] Changing parameter name `newline` ==> `delimiter`. --- requests/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requests/models.py b/requests/models.py index 6689ef7f..3f6a4f92 100644 --- a/requests/models.py +++ b/requests/models.py @@ -682,7 +682,7 @@ class Response(object): return chunks - def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None, newline=None): + def iter_lines(self, chunk_size=ITER_CHUNK_SIZE, decode_unicode=None, delimiter=None): """Iterates over the response data, one line at a time. When stream=True is set on the request, this avoids reading the content at once into memory for large responses. @@ -695,8 +695,8 @@ class Response(object): if pending is not None: chunk = pending + chunk - if newline: - lines = chunk.split(newline) + if delimiter: + lines = chunk.split(delimiter) else: lines = chunk.splitlines() From cfd6e25749dd17c6b511043a6720b672f74029ed Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Sat, 25 Oct 2014 13:54:34 +0200 Subject: [PATCH 252/545] fix failing test "test_prepare_unicode_url" --- requests/models.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/requests/models.py b/requests/models.py index 17e55988..8cc6e3b7 100644 --- a/requests/models.py +++ b/requests/models.py @@ -22,8 +22,8 @@ from .packages.urllib3.util import parse_url from .packages.urllib3.exceptions import ( DecodeError, ReadTimeoutError, ProtocolError) from .exceptions import ( - HTTPError, RequestException, MissingSchema, InvalidURL, - ChunkedEncodingError, ContentDecodingError, ConnectionError, + HTTPError, RequestException, MissingSchema, InvalidURL, + ChunkedEncodingError, ContentDecodingError, ConnectionError, StreamConsumedError) from .utils import ( guess_filename, get_auth_from_url, requote_uri, @@ -511,8 +511,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): def prepare_hooks(self, hooks): """Prepares the given hooks.""" - for event in hooks: - self.register_hook(event, hooks[event]) + if hooks is not None: + for event in hooks: + self.register_hook(event, hooks[event]) class Response(object): From c044a70796f5ba7cbe8f5a4c45a849c3acf09037 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Sat, 25 Oct 2014 13:54:47 +0200 Subject: [PATCH 253/545] update AUTHORS --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index a1b5ec46..71171d08 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -157,3 +157,4 @@ Patches and Suggestions - Martin Jul (`@mjul `_) - Joe Alcorn (`@buttscicles `_) - Syed Suhail Ahmed (`@syedsuhail `_) +- Scott Sadler (`@ssadler `_) From 77687de5099b6b65cde5901a29a3b1c5e751b8a2 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Sun, 26 Oct 2014 12:40:05 +0100 Subject: [PATCH 254/545] Revert "fix failing test "test_prepare_unicode_url"" This reverts commit cfd6e25749dd17c6b511043a6720b672f74029ed. --- requests/models.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/requests/models.py b/requests/models.py index 8cc6e3b7..17e55988 100644 --- a/requests/models.py +++ b/requests/models.py @@ -22,8 +22,8 @@ from .packages.urllib3.util import parse_url from .packages.urllib3.exceptions import ( DecodeError, ReadTimeoutError, ProtocolError) from .exceptions import ( - HTTPError, RequestException, MissingSchema, InvalidURL, - ChunkedEncodingError, ContentDecodingError, ConnectionError, + HTTPError, RequestException, MissingSchema, InvalidURL, + ChunkedEncodingError, ContentDecodingError, ConnectionError, StreamConsumedError) from .utils import ( guess_filename, get_auth_from_url, requote_uri, @@ -511,9 +511,8 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): def prepare_hooks(self, hooks): """Prepares the given hooks.""" - if hooks is not None: - for event in hooks: - self.register_hook(event, hooks[event]) + for event in hooks: + self.register_hook(event, hooks[event]) class Response(object): From 26ed7c2e1dce4271f3a3b111a167a6dcca3acddf Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Sun, 26 Oct 2014 12:49:46 +0100 Subject: [PATCH 255/545] fix failing test "test_prepare_unicode_url" --- requests/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requests/models.py b/requests/models.py index 17e55988..e216efae 100644 --- a/requests/models.py +++ b/requests/models.py @@ -22,8 +22,8 @@ from .packages.urllib3.util import parse_url from .packages.urllib3.exceptions import ( DecodeError, ReadTimeoutError, ProtocolError) from .exceptions import ( - HTTPError, RequestException, MissingSchema, InvalidURL, - ChunkedEncodingError, ContentDecodingError, ConnectionError, + HTTPError, RequestException, MissingSchema, InvalidURL, + ChunkedEncodingError, ContentDecodingError, ConnectionError, StreamConsumedError) from .utils import ( guess_filename, get_auth_from_url, requote_uri, @@ -310,7 +310,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): # such as OAuth to work on a fully prepared request. # This MUST go after prepare_auth. Authenticators could add a hook - self.prepare_hooks(hooks) + self.prepare_hooks(hooks if hooks is not None else []) def __repr__(self): return '' % (self.method) From 8e69c9f0a16b37990df4430eb81a5138520689ac Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Sun, 26 Oct 2014 14:41:56 +0100 Subject: [PATCH 256/545] Revert "fix failing test "test_prepare_unicode_url"" This reverts commit 26ed7c2e1dce4271f3a3b111a167a6dcca3acddf. --- requests/models.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requests/models.py b/requests/models.py index e216efae..17e55988 100644 --- a/requests/models.py +++ b/requests/models.py @@ -22,8 +22,8 @@ from .packages.urllib3.util import parse_url from .packages.urllib3.exceptions import ( DecodeError, ReadTimeoutError, ProtocolError) from .exceptions import ( - HTTPError, RequestException, MissingSchema, InvalidURL, - ChunkedEncodingError, ContentDecodingError, ConnectionError, + HTTPError, RequestException, MissingSchema, InvalidURL, + ChunkedEncodingError, ContentDecodingError, ConnectionError, StreamConsumedError) from .utils import ( guess_filename, get_auth_from_url, requote_uri, @@ -310,7 +310,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): # such as OAuth to work on a fully prepared request. # This MUST go after prepare_auth. Authenticators could add a hook - self.prepare_hooks(hooks if hooks is not None else []) + self.prepare_hooks(hooks) def __repr__(self): return '' % (self.method) From 583859b8204a1e9dd8a337f1ca06e31e8f79b5f8 Mon Sep 17 00:00:00 2001 From: Scott Sadler Date: Sun, 26 Oct 2014 14:45:12 +0100 Subject: [PATCH 257/545] fix failing test "test_prepare_unicode_url" \(take 3\) --- test_requests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index 0d93893b..467c5bc6 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1511,7 +1511,8 @@ def test_prepare_unicode_url(): p = PreparedRequest() p.prepare( method='GET', - url=u('http://www.example.com/üniçø∂é') + url=u('http://www.example.com/üniçø∂é'), + hooks=[] ) assert_copy(p, p.copy()) From f2f3bdea8da5f4ba7f56d6a9dd4b2b5f414abf32 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Sun, 26 Oct 2014 12:46:23 -0700 Subject: [PATCH 258/545] Note about read timeout errors and max_retries --- HISTORY.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 0d0d216d..4e7903dd 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -49,6 +49,8 @@ Release History **Behavioral Changes** - ``Connection: keep-alive`` header is now sent automatically. +- Read timeout errors will no longer be retried when you create a Session with + ``max_retries`` greater than zero. **Improvements** From d505b94fa47dbce4eae4db09d0056b7808b71b01 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 26 Oct 2014 19:24:37 -0500 Subject: [PATCH 259/545] Add DeprecationWarnings to inform users of plans After a long discussion in IRC and on several issues, the developers of requests have decided to remove specific functions from requests.utils in version 3.0.0. To give users ample time to prepare for this, we've added DeprecationWarnings long in advance. See also the planning of this in issue #2266. --- requests/utils.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/requests/utils.py b/requests/utils.py index 182348da..aa5c140e 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -19,6 +19,7 @@ import re import sys import socket import struct +import warnings from . import __version__ from . import certs @@ -287,6 +288,11 @@ def get_encodings_from_content(content): :param content: bytestring to extract encodings from. """ + warnings.warn(( + 'In requests 3.0, get_encodings_from_content will be removed. For ' + 'more information, please see the discussion on issue #2266. (This' + ' warning should only appear once.)'), + DeprecationWarning) charset_re = re.compile(r']', flags=re.I) pragma_re = re.compile(r']', flags=re.I) @@ -354,6 +360,11 @@ def get_unicode_from_response(r): 2. fall back and replace all unicode characters """ + warnings.warn(( + 'In requests 3.0, get_unicode_from_response will be removed. For ' + 'more information, please see the discussion on issue #2266. (This' + ' warning should only appear once.)'), + DeprecationWarning) tried_encodings = [] From 387c8f852cbb6ee2bdbb073b23871e18b7767d53 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Thu, 23 Oct 2014 20:49:18 -0700 Subject: [PATCH 260/545] Cap the redirect_cache size to prevent memory abuse --- requests/sessions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requests/sessions.py b/requests/sessions.py index d701ff2e..75be1da4 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -21,6 +21,7 @@ from .hooks import default_hooks, dispatch_hook from .utils import to_key_val_list, default_headers, to_native_string from .exceptions import ( TooManyRedirects, InvalidSchema, ChunkedEncodingError, ContentDecodingError) +from .packages.urllib3._collections import RecentlyUsedContainer from .structures import CaseInsensitiveDict from .adapters import HTTPAdapter @@ -327,7 +328,8 @@ class Session(SessionRedirectMixin): self.mount('https://', HTTPAdapter()) self.mount('http://', HTTPAdapter()) - self.redirect_cache = {} + # Only store 1000 redirects to prevent using infinite memory + self.redirect_cache = RecentlyUsedContainer(1000) def __enter__(self): return self From a703e24573d46871f5cd63631e33778281981f24 Mon Sep 17 00:00:00 2001 From: Kevin Burke Date: Mon, 27 Oct 2014 09:56:18 -0700 Subject: [PATCH 261/545] Update documentation about max_retries to reflect code --- HISTORY.rst | 2 -- requests/adapters.py | 6 ++++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 4e7903dd..0d0d216d 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -49,8 +49,6 @@ Release History **Behavioral Changes** - ``Connection: keep-alive`` header is now sent automatically. -- Read timeout errors will no longer be retried when you create a Session with - ``max_retries`` greater than zero. **Improvements** diff --git a/requests/adapters.py b/requests/adapters.py index abb25d11..01a5f338 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -60,8 +60,10 @@ class HTTPAdapter(BaseAdapter): :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 int max_retries: The maximum number of retries each connection - should attempt. Note, this applies only to failed connections and - timeouts, never to requests where the server returns a response. + should attempt. Note, this applies only to failed DNS lookups, socket + connections and connection timeouts, never to requests where data has + made it to the server. By default, Requests does not retry failed + connections. :param pool_block: Whether the connection pool should block for connections. Usage:: From 29ef1081583115c8bb2c8a592b883dc7c835240c Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 31 Oct 2014 21:20:04 -0500 Subject: [PATCH 262/545] Use to_native_string instead of builtin_str Fixes #2316 --- requests/sessions.py | 4 ++-- test_requests.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index d701ff2e..dfce7cf9 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -13,7 +13,7 @@ from collections import Mapping from datetime import datetime from .auth import _basic_auth_str -from .compat import cookielib, OrderedDict, urljoin, urlparse, builtin_str +from .compat import cookielib, OrderedDict, urljoin, urlparse from .cookies import ( cookiejar_from_dict, extract_cookies_to_jar, RequestsCookieJar, merge_cookies) from .models import Request, PreparedRequest, DEFAULT_REDIRECT_LIMIT @@ -425,7 +425,7 @@ class Session(SessionRedirectMixin): If Tuple, ('cert', 'key') pair. """ - method = builtin_str(method) + method = to_native_string(method) # Create the Request. req = Request( diff --git a/test_requests.py b/test_requests.py index 467c5bc6..b2e12d06 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1389,6 +1389,11 @@ class TestTimeout: except ConnectTimeout: pass + def test_encoded_methods(self): + """See: https://github.com/kennethreitz/requests/issues/2316""" + r = requests.request(b'GET', httpbin('get')) + assert r.ok + SendCall = collections.namedtuple('SendCall', ('args', 'kwargs')) From f28bde7250cfe18a2bb9351dae17908783d8a93e Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Fri, 7 Nov 2014 09:19:18 +0100 Subject: [PATCH 263/545] Pass strict to urllib3. --- requests/adapters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/adapters.py b/requests/adapters.py index abb25d11..aae42bd7 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -123,7 +123,7 @@ class HTTPAdapter(BaseAdapter): self._pool_block = block self.poolmanager = PoolManager(num_pools=connections, maxsize=maxsize, - block=block, **pool_kwargs) + block=block, strict=True, **pool_kwargs) def proxy_manager_for(self, proxy, **proxy_kwargs): """Return urllib3 ProxyManager for the given proxy. From 3155bc99362a8c6ab136b6a3bb999732617cd2e5 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 7 Nov 2014 08:55:17 -0600 Subject: [PATCH 264/545] Close sessions created in the functional API This is related to #1882 and #1685. By calling close on the session, we clear the PoolManager operated by the Session and close all sockets. Fixes #1882 Partially-fixes #1685 --- requests/api.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/requests/api.py b/requests/api.py index 4eaaf9e6..1469b05c 100644 --- a/requests/api.py +++ b/requests/api.py @@ -46,7 +46,12 @@ def request(method, url, **kwargs): """ session = sessions.Session() - return session.request(method=method, url=url, **kwargs) + response = session.request(method=method, url=url, **kwargs) + # By explicitly closing the session, we avoid leaving sockets open which + # can trigger a ResourceWarning in some cases, and look like a memory leak + # in others. + session.close() + return response def get(url, **kwargs): From 34f6088c8f3f2bac942c84ce2ffa92dfdee5aa4e Mon Sep 17 00:00:00 2001 From: Alexander Nelzin Date: Wed, 12 Nov 2014 15:23:23 +0300 Subject: [PATCH 265/545] Added test for overriding Content-Length. --- test_requests.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/test_requests.py b/test_requests.py index b2e12d06..4a05cb2e 100755 --- a/test_requests.py +++ b/test_requests.py @@ -103,6 +103,14 @@ class RequestsTestCase(unittest.TestCase): head_req = requests.Request('HEAD', httpbin('head')).prepare() assert 'Content-Length' not in head_req.headers + def test_override_content_length(self): + headers = { + 'Content-Length': 'not zero' + } + r = requests.Request('POST', httpbin('post'), headers=headers).prepare() + assert 'Content-Length' in r.headers + assert r.headers['Content-Length'] == 'not zero' + def test_path_is_not_double_encoded(self): request = requests.Request('GET', "http://0.0.0.0/get/test case").prepare() From ee50afef59ca47879144448ae056a2836f3d4534 Mon Sep 17 00:00:00 2001 From: Alexander Nelzin Date: Wed, 12 Nov 2014 15:35:27 +0300 Subject: [PATCH 266/545] Fixed. --- requests/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 3f6a4f92..2370b67f 100644 --- a/requests/models.py +++ b/requests/models.py @@ -472,7 +472,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): l = super_len(body) if l: self.headers['Content-Length'] = builtin_str(l) - elif self.method not in ('GET', 'HEAD'): + elif (self.method not in ('GET', 'HEAD')) and (self.headers.get('Content-Length') is None): self.headers['Content-Length'] = '0' def prepare_auth(self, auth, url=''): From 67c505781cece96763b86c47967efb785f33b079 Mon Sep 17 00:00:00 2001 From: Akira Kitada Date: Wed, 12 Nov 2014 23:46:22 +0900 Subject: [PATCH 267/545] Fix HTTPDigestAuth not to treat non-file as a file Ensure pos is set to None when the body is not a file so that HTTPDigestAuth detects the type of the body correctly. --- requests/auth.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/requests/auth.py b/requests/auth.py index 010919f3..618a902a 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -198,7 +198,11 @@ class HTTPDigestAuth(AuthBase): try: self.pos = r.body.tell() except AttributeError: - pass + # In the case of HTTPDigestAuth being reused and the body of + # the previous request was a file-like object, pos has the + # file position of the previous body. Ensure it's set to + # None. + self.pos = None r.register_hook('response', self.handle_401) r.register_hook('response', self.handle_redirect) return r From 2bc7762cb6c91f49e5b116d3eb9f93af01e331f2 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 10 Sep 2014 11:57:20 -0500 Subject: [PATCH 268/545] Update how we handle retries to be consistent with documentation --- requests/adapters.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index 40088900..df3345fd 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -33,7 +33,7 @@ from .auth import _basic_auth_str DEFAULT_POOLBLOCK = False DEFAULT_POOLSIZE = 10 -DEFAULT_RETRIES = 0 +DEFAULT_RETRIES = object() class BaseAdapter(object): @@ -79,7 +79,10 @@ class HTTPAdapter(BaseAdapter): def __init__(self, pool_connections=DEFAULT_POOLSIZE, pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES, pool_block=DEFAULT_POOLBLOCK): - self.max_retries = max_retries + if max_retries is DEFAULT_RETRIES: + self.max_retries = Retry(0, read=False) + else: + self.max_retries = Retry.from_int(max_retries) self.config = {} self.proxy_manager = {} @@ -360,7 +363,7 @@ class HTTPAdapter(BaseAdapter): assert_same_host=False, preload_content=False, decode_content=False, - retries=Retry(self.max_retries, read=False), + retries=self.max_retries, timeout=timeout ) From f54a4e3de186495a5254d21ae698c677c71b90c3 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 24 Oct 2014 21:58:11 -0500 Subject: [PATCH 269/545] Update urllib3 to df4ec5cce1 --- requests/packages/urllib3/_collections.py | 7 ++- requests/packages/urllib3/connection.py | 12 ++++- requests/packages/urllib3/connectionpool.py | 4 +- .../packages/urllib3/contrib/pyopenssl.py | 18 +++++++- requests/packages/urllib3/exceptions.py | 13 +++--- requests/packages/urllib3/util/retry.py | 24 ++++++---- requests/packages/urllib3/util/url.py | 45 ++++++++++++++++++- 7 files changed, 97 insertions(+), 26 deletions(-) diff --git a/requests/packages/urllib3/_collections.py b/requests/packages/urllib3/_collections.py index d77ebb8d..784342a4 100644 --- a/requests/packages/urllib3/_collections.py +++ b/requests/packages/urllib3/_collections.py @@ -14,7 +14,7 @@ try: # Python 2.7+ from collections import OrderedDict except ImportError: from .packages.ordered_dict import OrderedDict -from .packages.six import itervalues +from .packages.six import iterkeys, itervalues __all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict'] @@ -85,8 +85,7 @@ class RecentlyUsedContainer(MutableMapping): def clear(self): with self.lock: # Copy pointers to all values, then wipe the mapping - # under Python 2, this copies the list of values twice :-| - values = list(self._container.values()) + values = list(itervalues(self._container)) self._container.clear() if self.dispose_func: @@ -95,7 +94,7 @@ class RecentlyUsedContainer(MutableMapping): def keys(self): with self.lock: - return self._container.keys() + return list(iterkeys(self._container)) class HTTPHeaderDict(MutableMapping): diff --git a/requests/packages/urllib3/connection.py b/requests/packages/urllib3/connection.py index c6e1959a..cebdd867 100644 --- a/requests/packages/urllib3/connection.py +++ b/requests/packages/urllib3/connection.py @@ -3,6 +3,7 @@ import sys import socket from socket import timeout as SocketTimeout import warnings +from .packages import six try: # Python 3 from http.client import HTTPConnection as _HTTPConnection, HTTPException @@ -26,12 +27,19 @@ except (ImportError, AttributeError): # Platform-specific: No SSL. pass +try: # Python 3: + # Not a no-op, we're adding this to the namespace so it can be imported. + ConnectionError = ConnectionError +except NameError: # Python 2: + class ConnectionError(Exception): + pass + + from .exceptions import ( ConnectTimeoutError, SystemTimeWarning, ) from .packages.ssl_match_hostname import match_hostname -from .packages import six from .util.ssl_ import ( resolve_cert_reqs, @@ -40,8 +48,8 @@ from .util.ssl_ import ( assert_fingerprint, ) -from .util import connection +from .util import connection port_by_scheme = { 'http': 80, diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index 9cc2a955..ac6e0ca6 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -32,7 +32,7 @@ from .connection import ( port_by_scheme, DummyConnection, HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection, - HTTPException, BaseSSLError, + HTTPException, BaseSSLError, ConnectionError ) from .request import RequestMethods from .response import HTTPResponse @@ -542,7 +542,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): release_conn = True raise SSLError(e) - except (TimeoutError, HTTPException, SocketError) as e: + except (TimeoutError, HTTPException, SocketError, ConnectionError) as e: if conn: # Discard the connection for these exceptions. It will be # be replaced during the next _get_conn() call. diff --git a/requests/packages/urllib3/contrib/pyopenssl.py b/requests/packages/urllib3/contrib/pyopenssl.py index 24de9e40..3c6b26c1 100644 --- a/requests/packages/urllib3/contrib/pyopenssl.py +++ b/requests/packages/urllib3/contrib/pyopenssl.py @@ -29,7 +29,7 @@ Now you can use :mod:`urllib3` as you normally would, and it will support SNI when the required modules are installed. Activating this module also has the positive side effect of disabling SSL/TLS -encryption in Python 2 (see `CRIME attack`_). +compression in Python 2 (see `CRIME attack`_). If you want to configure the default list of supported cipher suites, you can set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable. @@ -199,8 +199,21 @@ class WrappedSocket(object): def settimeout(self, timeout): return self.socket.settimeout(timeout) + def _send_until_done(self, data): + while True: + try: + return self.connection.send(data) + except OpenSSL.SSL.WantWriteError: + _, wlist, _ = select.select([], [self.socket], [], + self.socket.gettimeout()) + if not wlist: + raise timeout() + continue + def sendall(self, data): - return self.connection.sendall(data) + while len(data): + sent = self._send_until_done(data) + data = data[sent:] def close(self): if self._makefile_refs < 1: @@ -248,6 +261,7 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, ssl_version=None): ctx = OpenSSL.SSL.Context(_openssl_versions[ssl_version]) if certfile: + keyfile = keyfile or certfile # Match behaviour of the normal python ssl library ctx.use_certificate_file(certfile) if keyfile: ctx.use_privatekey_file(keyfile) diff --git a/requests/packages/urllib3/exceptions.py b/requests/packages/urllib3/exceptions.py index 7519ba98..0c6fd3c5 100644 --- a/requests/packages/urllib3/exceptions.py +++ b/requests/packages/urllib3/exceptions.py @@ -72,11 +72,8 @@ class MaxRetryError(RequestError): def __init__(self, pool, url, reason=None): self.reason = reason - message = "Max retries exceeded with url: %s" % url - if reason: - message += " (Caused by %r)" % reason - else: - message += " (Caused by redirect)" + message = "Max retries exceeded with url: %s (Caused by %r)" % ( + url, reason) RequestError.__init__(self, pool, url, message) @@ -141,6 +138,12 @@ class LocationParseError(LocationValueError): self.location = location +class ResponseError(HTTPError): + "Used as a container for an error reason supplied in a MaxRetryError." + GENERIC_ERROR = 'too many error responses' + SPECIFIC_ERROR = 'too many {status_code} error responses' + + class SecurityWarning(HTTPWarning): "Warned when perfoming security reducing actions" pass diff --git a/requests/packages/urllib3/util/retry.py b/requests/packages/urllib3/util/retry.py index eb560dfc..aeaf8a02 100644 --- a/requests/packages/urllib3/util/retry.py +++ b/requests/packages/urllib3/util/retry.py @@ -2,10 +2,11 @@ import time import logging from ..exceptions import ( - ProtocolError, ConnectTimeoutError, - ReadTimeoutError, MaxRetryError, + ProtocolError, + ReadTimeoutError, + ResponseError, ) from ..packages import six @@ -36,7 +37,6 @@ class Retry(object): Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless retries are disabled, in which case the causing exception will be raised. - :param int total: Total number of retries to allow. Takes precedence over other counts. @@ -184,8 +184,8 @@ class Retry(object): return isinstance(err, ConnectTimeoutError) def _is_read_error(self, err): - """ Errors that occur after the request has been started, so we can't - assume that the server did not process any of it. + """ Errors that occur after the request has been started, so we should + assume that the server began processing it. """ return isinstance(err, (ReadTimeoutError, ProtocolError)) @@ -198,8 +198,7 @@ class Retry(object): return self.status_forcelist and status_code in self.status_forcelist def is_exhausted(self): - """ Are we out of retries? - """ + """ Are we out of retries? """ retry_counts = (self.total, self.connect, self.read, self.redirect) retry_counts = list(filter(None, retry_counts)) if not retry_counts: @@ -230,6 +229,7 @@ class Retry(object): connect = self.connect read = self.read redirect = self.redirect + cause = 'unknown' if error and self._is_connection_error(error): # Connect retry? @@ -251,10 +251,16 @@ class Retry(object): # Redirect retry? if redirect is not None: redirect -= 1 + cause = 'too many redirects' else: - # FIXME: Nothing changed, scenario doesn't make sense. + # Incrementing because of a server error like a 500 in + # status_forcelist and a the given method is in the whitelist _observed_errors += 1 + cause = ResponseError.GENERIC_ERROR + if response and response.status: + cause = ResponseError.SPECIFIC_ERROR.format( + status_code=response.status) new_retry = self.new( total=total, @@ -262,7 +268,7 @@ class Retry(object): _observed_errors=_observed_errors) if new_retry.is_exhausted(): - raise MaxRetryError(_pool, url, error) + raise MaxRetryError(_pool, url, error or ResponseError(cause)) log.debug("Incremented Retry for (url='%s'): %r" % (url, new_retry)) diff --git a/requests/packages/urllib3/util/url.py b/requests/packages/urllib3/util/url.py index 487d456c..b2ec834f 100644 --- a/requests/packages/urllib3/util/url.py +++ b/requests/packages/urllib3/util/url.py @@ -40,6 +40,48 @@ class Url(namedtuple('Url', url_attrs)): return '%s:%d' % (self.host, self.port) return self.host + @property + def url(self): + """ + Convert self into a url + + This function should more or less round-trip with :func:`.parse_url`. The + returned url may not be exactly the same as the url inputted to + :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls + with a blank port will have : removed). + + Example: :: + + >>> U = parse_url('http://google.com/mail/') + >>> U.url + 'http://google.com/mail/' + >>> Url('http', 'username:password', 'host.com', 80, + ... '/path', 'query', 'fragment').url + 'http://username:password@host.com:80/path?query#fragment' + """ + scheme, auth, host, port, path, query, fragment = self + url = '' + + # We use "is not None" we want things to happen with empty strings (or 0 port) + if scheme is not None: + url += scheme + '://' + if auth is not None: + url += auth + '@' + if host is not None: + url += host + if port is not None: + url += ':' + str(port) + if path is not None: + url += path + if query is not None: + url += '?' + query + if fragment is not None: + url += '#' + fragment + + return url + + def __str__(self): + return self.url def split_first(s, delims): """ @@ -84,7 +126,7 @@ def parse_url(url): Example:: >>> parse_url('http://google.com/mail/') - Url(scheme='http', host='google.com', port=None, path='/', ...) + Url(scheme='http', host='google.com', port=None, path='/mail/', ...) >>> parse_url('google.com:80') Url(scheme=None, host='google.com', port=80, path=None, ...) >>> parse_url('/foo?bar') @@ -162,7 +204,6 @@ def parse_url(url): return Url(scheme, auth, host, port, path, query, fragment) - def get_host(url): """ Deprecated. Use :func:`.parse_url` instead. From 2eb7e3c80b345f6c6e13fadf6f1ba98efccdd2e2 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 24 Oct 2014 22:23:15 -0500 Subject: [PATCH 270/545] Add last few changes and add a quick test --- requests/adapters.py | 10 +++++++--- requests/exceptions.py | 5 +++++ test_requests.py | 14 +++++++++++++- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index df3345fd..723b8179 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -26,14 +26,15 @@ from .packages.urllib3.exceptions import ProxyError as _ProxyError from .packages.urllib3.exceptions import ProtocolError from .packages.urllib3.exceptions import ReadTimeoutError from .packages.urllib3.exceptions import SSLError as _SSLError +from .packages.urllib3.exceptions import ResponseError from .cookies import extract_cookies_to_jar from .exceptions import (ConnectionError, ConnectTimeout, ReadTimeout, SSLError, - ProxyError) + ProxyError, RetryError) from .auth import _basic_auth_str DEFAULT_POOLBLOCK = False DEFAULT_POOLSIZE = 10 -DEFAULT_RETRIES = object() +DEFAULT_RETRIES = 0 class BaseAdapter(object): @@ -79,7 +80,7 @@ class HTTPAdapter(BaseAdapter): def __init__(self, pool_connections=DEFAULT_POOLSIZE, pool_maxsize=DEFAULT_POOLSIZE, max_retries=DEFAULT_RETRIES, pool_block=DEFAULT_POOLBLOCK): - if max_retries is DEFAULT_RETRIES: + if max_retries == DEFAULT_RETRIES: self.max_retries = Retry(0, read=False) else: self.max_retries = Retry.from_int(max_retries) @@ -415,6 +416,9 @@ class HTTPAdapter(BaseAdapter): if isinstance(e.reason, ConnectTimeoutError): raise ConnectTimeout(e, request=request) + if isinstance(e.reason, ResponseError): + raise RetryError(e, request=request) + raise ConnectionError(e, request=request) except _ProxyError as e: diff --git a/requests/exceptions.py b/requests/exceptions.py index 34c7a0db..89135a80 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -90,5 +90,10 @@ class ChunkedEncodingError(RequestException): class ContentDecodingError(RequestException, BaseHTTPError): """Failed to decode response content""" + class StreamConsumedError(RequestException, TypeError): """The content for this response was already consumed""" + + +class RetryError(RequestException): + """Custom retries logic failed""" diff --git a/test_requests.py b/test_requests.py index 4a05cb2e..4624f095 100755 --- a/test_requests.py +++ b/test_requests.py @@ -20,7 +20,7 @@ from requests.compat import ( from requests.cookies import cookiejar_from_dict, morsel_to_cookie from requests.exceptions import (ConnectionError, ConnectTimeout, InvalidSchema, InvalidURL, MissingSchema, - ReadTimeout, Timeout) + ReadTimeout, Timeout, RetryError) from requests.models import PreparedRequest from requests.structures import CaseInsensitiveDict from requests.sessions import SessionRedirectMixin @@ -1520,6 +1520,7 @@ def test_prepared_request_complete_copy(): ) assert_copy(p, p.copy()) + def test_prepare_unicode_url(): p = PreparedRequest() p.prepare( @@ -1529,5 +1530,16 @@ def test_prepare_unicode_url(): ) assert_copy(p, p.copy()) + +def test_urllib3_retries(): + from requests.packages.urllib3.util import Retry + s = requests.Session() + s.mount('https://', HTTPAdapter(max_retries=Retry( + total=2, status_forcelist=[500] + ))) + + with pytest.raises(RetryError): + s.get('https://httpbin.org/status/500') + if __name__ == '__main__': unittest.main() From adf475ef82cbd29f63814c0626f64926deb2355b Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 25 Oct 2014 08:31:03 -0500 Subject: [PATCH 271/545] Update HTTPAdapter docstring --- requests/adapters.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requests/adapters.py b/requests/adapters.py index 723b8179..c892853b 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -64,7 +64,9 @@ class HTTPAdapter(BaseAdapter): should attempt. Note, this applies only to failed DNS lookups, socket connections and connection timeouts, never to requests where data has made it to the server. By default, Requests does not retry failed - connections. + connections. If you need granular control over the conditions under + which we retry a request, import urllib3's ``Retry`` class and pass + that instead. :param pool_block: Whether the connection pool should block for connections. Usage:: From 087a27aba97d3eac017d321e37bf1970f8833c1a Mon Sep 17 00:00:00 2001 From: ContinuousFunction Date: Sat, 15 Nov 2014 16:58:25 -0800 Subject: [PATCH 272/545] Partially addresses Issue #1572 Addresses the LocationParseError but not the DecodeError from kennethreitz#1572. When running test_requests.py, I got an error in test_session_pickling which resulted in a TypeError. I'm not sure of the reason for the TypeError but I have commented out that test. --- requests/models.py | 7 +++++-- test_requests.py | 23 ++++++++++++++--------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/requests/models.py b/requests/models.py index 2370b67f..b95b5beb 100644 --- a/requests/models.py +++ b/requests/models.py @@ -20,7 +20,7 @@ from .packages.urllib3.fields import RequestField from .packages.urllib3.filepost import encode_multipart_formdata from .packages.urllib3.util import parse_url from .packages.urllib3.exceptions import ( - DecodeError, ReadTimeoutError, ProtocolError) + DecodeError, ReadTimeoutError, ProtocolError, LocationParseError) from .exceptions import ( HTTPError, RequestException, MissingSchema, InvalidURL, ChunkedEncodingError, ContentDecodingError, ConnectionError, @@ -351,7 +351,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): return # Support for unicode domain names and paths. - scheme, auth, host, port, path, query, fragment = parse_url(url) + try: + scheme, auth, host, port, path, query, fragment = parse_url(url) + except LocationParseError as e: + raise ConnectionError(e.message) if not scheme: raise MissingSchema("Invalid URL {0!r}: No schema supplied. " diff --git a/test_requests.py b/test_requests.py index 4a05cb2e..6e49f027 100755 --- a/test_requests.py +++ b/test_requests.py @@ -309,6 +309,11 @@ class RequestsTestCase(unittest.TestCase): with pytest.raises(ConnectionError): requests.get("http://httpbin.org:1") + def test_LocationParseError(self): + """Inputing a URL that cannot be parsed should raise a ConnectionError""" + with pytest.raises(ConnectionError): + requests.get("http://fe80::5054:ff:fe5a:fc0") + def test_basicauth_with_netrc(self): auth = ('user', 'pass') wrong_auth = ('wronguser', 'wrongpass') @@ -820,15 +825,15 @@ class RequestsTestCase(unittest.TestCase): assert str(error) == 'message' assert error.response == response - def test_session_pickling(self): - r = requests.Request('GET', httpbin('get')) - s = requests.Session() - - s = pickle.loads(pickle.dumps(s)) - s.proxies = getproxies() - - r = s.send(r.prepare()) - assert r.status_code == 200 +## def test_session_pickling(self): +## r = requests.Request('GET', httpbin('get')) +## s = requests.Session() +## +## s = pickle.loads(pickle.dumps(s)) +## s.proxies = getproxies() +## +## r = s.send(r.prepare()) +## assert r.status_code == 200 def test_fixes_1329(self): """ From 8b03f6027832a9d79f951ca1e5f27278f8b72af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dan=20Michael=20O=2E=20Hegg=C3=B8?= Date: Mon, 17 Nov 2014 11:39:33 +0100 Subject: [PATCH 273/545] Docs: Add more section labels for referencing so sections can be linked from other projects using Intersphinx --- docs/user/advanced.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index b1e334bb..6ec61eac 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -5,6 +5,7 @@ Advanced Usage This document covers some of Requests more advanced features. +.. _session-objects: Session Objects --------------- @@ -50,6 +51,8 @@ parameters. All values that are contained within a session are directly available to you. See the :ref:`Session API Docs ` to learn more. +.. _request-and-response-objects: + Request and Response Objects ---------------------------- @@ -82,6 +85,8 @@ request, and then the request's headers:: {'Accept-Encoding': 'identity, deflate, compress, gzip', 'Accept': '*/*', 'User-Agent': 'python-requests/1.2.0'} +.. _prepared-requests: + Prepared Requests ----------------- @@ -189,6 +194,7 @@ If you specify a wrong path or an invalid cert:: >>> requests.get('https://kennethreitz.com', cert='/wrong_path/server.pem') SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib +.. _body-content-workflow: Body Content Workflow --------------------- @@ -228,6 +234,7 @@ consider using ``contextlib.closing`` (`documented here`_), like this:: .. _`documented here`: http://docs.python.org/2/library/contextlib.html#contextlib.closing +.. _keep-alive: Keep-Alive ---------- @@ -240,6 +247,7 @@ Note that connections are only released back to the pool for reuse once all body data has been read; be sure to either set ``stream`` to ``False`` or read the ``content`` property of the ``Response`` object. +.. _streaming-uploads: Streaming Uploads ----------------- @@ -251,6 +259,7 @@ file-like object for your body:: with open('massive-body', 'rb') as f: requests.post('http://some.url/streamed', data=f) +.. _chunk-encoding: Chunk-Encoded Requests ---------------------- @@ -267,6 +276,7 @@ a length) for your body:: requests.post('http://some.url/chunked', data=gen()) +.. _multipart: POST Multiple Multipart-Encoded Files ------------------------------------- @@ -290,6 +300,7 @@ To do that, just set files to a list of tuples of (form_field_name, file_info): ... } +.. _event-hooks: Event Hooks ----------- @@ -329,6 +340,7 @@ Let's print some request method arguments at runtime:: http://httpbin.org +.. _custom-auth: Custom Authentication --------------------- @@ -387,6 +399,7 @@ set ``stream`` to ``True`` and iterate over the response with if line: print(json.loads(line)) +.. _proxies: Proxies ------- From 3246b1fe172ca3d4f098e85467234ded2f833b31 Mon Sep 17 00:00:00 2001 From: ContinuousFunction Date: Sun, 16 Nov 2014 16:39:08 -0800 Subject: [PATCH 274/545] Changed ConnectionError to InvalidURL --- requests/models.py | 2 +- test_requests.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requests/models.py b/requests/models.py index b95b5beb..8a71e28b 100644 --- a/requests/models.py +++ b/requests/models.py @@ -354,7 +354,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): try: scheme, auth, host, port, path, query, fragment = parse_url(url) except LocationParseError as e: - raise ConnectionError(e.message) + raise InvalidURL(e.message) if not scheme: raise MissingSchema("Invalid URL {0!r}: No schema supplied. " diff --git a/test_requests.py b/test_requests.py index 6e49f027..d176ef45 100755 --- a/test_requests.py +++ b/test_requests.py @@ -310,8 +310,8 @@ class RequestsTestCase(unittest.TestCase): requests.get("http://httpbin.org:1") def test_LocationParseError(self): - """Inputing a URL that cannot be parsed should raise a ConnectionError""" - with pytest.raises(ConnectionError): + """Inputing a URL that cannot be parsed should raise an InvalidURL error""" + with pytest.raises(InvalidURL): requests.get("http://fe80::5054:ff:fe5a:fc0") def test_basicauth_with_netrc(self): From c8baf881257ff3194879ad79bef81c27007b4dcf Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 17 Nov 2014 22:13:35 -0600 Subject: [PATCH 275/545] Properly serialize RecentlyUsedContainers for cache RecentlyUsedContainers are threadsafe so they require a lock and as such cannot be serialized with pickle directly. To handle it, we need to convert it to a dictionary first and then back when deserializing. Fixes #2345 --- requests/sessions.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index c2f42b14..ba8eaa3d 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -36,6 +36,8 @@ from .status_codes import codes # formerly defined here, reexposed here for backward compatibility from .models import REDIRECT_STATI +REDIRECT_CACHE_SIZE = 1000 + def merge_setting(request_setting, session_setting, dict_class=OrderedDict): """ @@ -274,7 +276,7 @@ class Session(SessionRedirectMixin): __attrs__ = [ 'headers', 'cookies', 'auth', 'proxies', 'hooks', 'params', 'verify', 'cert', 'prefetch', 'adapters', 'stream', 'trust_env', - 'max_redirects', 'redirect_cache' + 'max_redirects', ] def __init__(self): @@ -329,7 +331,7 @@ class Session(SessionRedirectMixin): self.mount('http://', HTTPAdapter()) # Only store 1000 redirects to prevent using infinite memory - self.redirect_cache = RecentlyUsedContainer(1000) + self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE) def __enter__(self): return self @@ -660,12 +662,19 @@ class Session(SessionRedirectMixin): self.adapters[key] = self.adapters.pop(key) def __getstate__(self): - return dict((attr, getattr(self, attr, None)) for attr in self.__attrs__) + state = dict((attr, getattr(self, attr, None)) for attr in self.__attrs__) + state['redirect_cache'] = dict(self.redirect_cache) + return state def __setstate__(self, state): + redirect_cache = state.pop('redirect_cache', {}) for attr, value in state.items(): setattr(self, attr, value) + self.redirect_cache = RecentlyUsedContainer(REDIRECT_CACHE_SIZE) + for redirect, to in redirect_cache.items(): + self.redirect_cache[redirect] = to + def session(): """Returns a :class:`Session` for context-management.""" From 2a5364cf72fb8156773d6304c0e3e21472de8002 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Sun, 23 Nov 2014 17:54:44 -0800 Subject: [PATCH 276/545] url was already parsed, don't urlparse twice --- requests/sessions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/sessions.py b/requests/sessions.py index c2f42b14..4eb374fd 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -129,7 +129,7 @@ class SessionRedirectMixin(object): # Facilitate relative 'location' headers, as allowed by RFC 7231. # (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource') # Compliant with RFC3986, we percent encode the url. - if not urlparse(url).netloc: + if not parsed.netloc: url = urljoin(resp.url, requote_uri(url)) else: url = requote_uri(url) From 7aa6c62d6d917e11f81b166d1d6c9e60340783ac Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 1 Dec 2014 14:40:32 -0500 Subject: [PATCH 277/545] updatee chardet, urllib3 --- requests/packages/chardet/__init__.py | 2 +- requests/packages/chardet/chardetect.py | 66 ++++-- requests/packages/chardet/jpcntx.py | 8 + requests/packages/chardet/latin1prober.py | 6 +- requests/packages/chardet/mbcssm.py | 9 +- requests/packages/chardet/sjisprober.py | 2 +- .../packages/chardet/universaldetector.py | 4 +- requests/packages/urllib3/__init__.py | 2 +- requests/packages/urllib3/_collections.py | 7 +- requests/packages/urllib3/connection.py | 24 ++- requests/packages/urllib3/connectionpool.py | 64 +++--- .../packages/urllib3/contrib/pyopenssl.py | 25 ++- requests/packages/urllib3/exceptions.py | 13 +- requests/packages/urllib3/request.py | 28 ++- requests/packages/urllib3/util/retry.py | 24 ++- requests/packages/urllib3/util/ssl_.py | 202 ++++++++++++++---- requests/packages/urllib3/util/url.py | 45 +++- 17 files changed, 393 insertions(+), 138 deletions(-) diff --git a/requests/packages/chardet/__init__.py b/requests/packages/chardet/__init__.py index e4f0799d..82c2a48d 100644 --- a/requests/packages/chardet/__init__.py +++ b/requests/packages/chardet/__init__.py @@ -15,7 +15,7 @@ # 02110-1301 USA ######################### END LICENSE BLOCK ######################### -__version__ = "2.2.1" +__version__ = "2.3.0" from sys import version_info diff --git a/requests/packages/chardet/chardetect.py b/requests/packages/chardet/chardetect.py index ecd0163b..ffe892f2 100755 --- a/requests/packages/chardet/chardetect.py +++ b/requests/packages/chardet/chardetect.py @@ -12,34 +12,68 @@ Example:: If no paths are provided, it takes its input from stdin. """ -from io import open -from sys import argv, stdin +from __future__ import absolute_import, print_function, unicode_literals + +import argparse +import sys +from io import open + +from chardet import __version__ from chardet.universaldetector import UniversalDetector -def description_of(file, name='stdin'): - """Return a string describing the probable encoding of a file.""" +def description_of(lines, name='stdin'): + """ + Return a string describing the probable encoding of a file or + list of strings. + + :param lines: The lines to get the encoding of. + :type lines: Iterable of bytes + :param name: Name of file or collection of lines + :type name: str + """ u = UniversalDetector() - for line in file: + for line in lines: u.feed(line) u.close() result = u.result if result['encoding']: - return '%s: %s with confidence %s' % (name, - result['encoding'], - result['confidence']) + return '{0}: {1} with confidence {2}'.format(name, result['encoding'], + result['confidence']) else: - return '%s: no result' % name + return '{0}: no result'.format(name) -def main(): - if len(argv) <= 1: - print(description_of(stdin)) - else: - for path in argv[1:]: - with open(path, 'rb') as f: - print(description_of(f, path)) +def main(argv=None): + ''' + Handles command line arguments and gets things started. + + :param argv: List of arguments, as if specified on the command-line. + If None, ``sys.argv[1:]`` is used instead. + :type argv: list of str + ''' + # Get command line arguments + parser = argparse.ArgumentParser( + description="Takes one or more file paths and reports their detected \ + encodings", + formatter_class=argparse.ArgumentDefaultsHelpFormatter, + conflict_handler='resolve') + parser.add_argument('input', + help='File whose encoding we would like to determine.', + type=argparse.FileType('rb'), nargs='*', + default=[sys.stdin]) + parser.add_argument('--version', action='version', + version='%(prog)s {0}'.format(__version__)) + args = parser.parse_args(argv) + + for f in args.input: + if f.isatty(): + print("You are running chardetect interactively. Press " + + "CTRL-D twice at the start of a blank line to signal the " + + "end of your input. If you want help, run chardetect " + + "--help\n", file=sys.stderr) + print(description_of(f, f.name)) if __name__ == '__main__': diff --git a/requests/packages/chardet/jpcntx.py b/requests/packages/chardet/jpcntx.py index f7f69ba4..59aeb6a8 100644 --- a/requests/packages/chardet/jpcntx.py +++ b/requests/packages/chardet/jpcntx.py @@ -177,6 +177,12 @@ class JapaneseContextAnalysis: return -1, 1 class SJISContextAnalysis(JapaneseContextAnalysis): + def __init__(self): + self.charset_name = "SHIFT_JIS" + + def get_charset_name(self): + return self.charset_name + def get_order(self, aBuf): if not aBuf: return -1, 1 @@ -184,6 +190,8 @@ class SJISContextAnalysis(JapaneseContextAnalysis): first_char = wrap_ord(aBuf[0]) if ((0x81 <= first_char <= 0x9F) or (0xE0 <= first_char <= 0xFC)): charLen = 2 + if (first_char == 0x87) or (0xFA <= first_char <= 0xFC): + self.charset_name = "CP932" else: charLen = 1 diff --git a/requests/packages/chardet/latin1prober.py b/requests/packages/chardet/latin1prober.py index ad695f57..eef35735 100644 --- a/requests/packages/chardet/latin1prober.py +++ b/requests/packages/chardet/latin1prober.py @@ -129,11 +129,11 @@ class Latin1Prober(CharSetProber): if total < 0.01: confidence = 0.0 else: - confidence = ((self._mFreqCounter[3] / total) - - (self._mFreqCounter[1] * 20.0 / total)) + confidence = ((self._mFreqCounter[3] - self._mFreqCounter[1] * 20.0) + / total) if confidence < 0.0: confidence = 0.0 # lower the confidence of latin1 so that other more accurate # detector can take priority. - confidence = confidence * 0.5 + confidence = confidence * 0.73 return confidence diff --git a/requests/packages/chardet/mbcssm.py b/requests/packages/chardet/mbcssm.py index 3f93cfb0..efe678ca 100644 --- a/requests/packages/chardet/mbcssm.py +++ b/requests/packages/chardet/mbcssm.py @@ -353,7 +353,7 @@ SJIS_cls = ( 2,2,2,2,2,2,2,2, # 68 - 6f 2,2,2,2,2,2,2,2, # 70 - 77 2,2,2,2,2,2,2,1, # 78 - 7f - 3,3,3,3,3,3,3,3, # 80 - 87 + 3,3,3,3,3,2,2,3, # 80 - 87 3,3,3,3,3,3,3,3, # 88 - 8f 3,3,3,3,3,3,3,3, # 90 - 97 3,3,3,3,3,3,3,3, # 98 - 9f @@ -369,9 +369,8 @@ SJIS_cls = ( 2,2,2,2,2,2,2,2, # d8 - df 3,3,3,3,3,3,3,3, # e0 - e7 3,3,3,3,3,4,4,4, # e8 - ef - 4,4,4,4,4,4,4,4, # f0 - f7 - 4,4,4,4,4,0,0,0 # f8 - ff -) + 3,3,3,3,3,3,3,3, # f0 - f7 + 3,3,3,3,3,0,0,0) # f8 - ff SJIS_st = ( @@ -571,5 +570,3 @@ UTF8SMModel = {'classTable': UTF8_cls, 'stateTable': UTF8_st, 'charLenTable': UTF8CharLenTable, 'name': 'UTF-8'} - -# flake8: noqa diff --git a/requests/packages/chardet/sjisprober.py b/requests/packages/chardet/sjisprober.py index b173614e..cd0e9e70 100644 --- a/requests/packages/chardet/sjisprober.py +++ b/requests/packages/chardet/sjisprober.py @@ -47,7 +47,7 @@ class SJISProber(MultiByteCharSetProber): self._mContextAnalyzer.reset() def get_charset_name(self): - return "SHIFT_JIS" + return self._mContextAnalyzer.get_charset_name() def feed(self, aBuf): aLen = len(aBuf) diff --git a/requests/packages/chardet/universaldetector.py b/requests/packages/chardet/universaldetector.py index 9a03ad3d..476522b9 100644 --- a/requests/packages/chardet/universaldetector.py +++ b/requests/packages/chardet/universaldetector.py @@ -71,9 +71,9 @@ class UniversalDetector: if not self._mGotData: # If the data starts with BOM, we know it is UTF - if aBuf[:3] == codecs.BOM: + if aBuf[:3] == codecs.BOM_UTF8: # EF BB BF UTF-8 with BOM - self.result = {'encoding': "UTF-8", 'confidence': 1.0} + self.result = {'encoding': "UTF-8-SIG", 'confidence': 1.0} elif aBuf[:4] == codecs.BOM_UTF32_LE: # FF FE 00 00 UTF-32, little-endian BOM self.result = {'encoding': "UTF-32LE", 'confidence': 1.0} diff --git a/requests/packages/urllib3/__init__.py b/requests/packages/urllib3/__init__.py index 4b36b5ae..dfc82d03 100644 --- a/requests/packages/urllib3/__init__.py +++ b/requests/packages/urllib3/__init__.py @@ -57,7 +57,7 @@ del NullHandler # Set security warning to only go off once by default. import warnings -warnings.simplefilter('module', exceptions.SecurityWarning) +warnings.simplefilter('always', exceptions.SecurityWarning) def disable_warnings(category=exceptions.HTTPWarning): """ diff --git a/requests/packages/urllib3/_collections.py b/requests/packages/urllib3/_collections.py index d77ebb8d..784342a4 100644 --- a/requests/packages/urllib3/_collections.py +++ b/requests/packages/urllib3/_collections.py @@ -14,7 +14,7 @@ try: # Python 2.7+ from collections import OrderedDict except ImportError: from .packages.ordered_dict import OrderedDict -from .packages.six import itervalues +from .packages.six import iterkeys, itervalues __all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict'] @@ -85,8 +85,7 @@ class RecentlyUsedContainer(MutableMapping): def clear(self): with self.lock: # Copy pointers to all values, then wipe the mapping - # under Python 2, this copies the list of values twice :-| - values = list(self._container.values()) + values = list(itervalues(self._container)) self._container.clear() if self.dispose_func: @@ -95,7 +94,7 @@ class RecentlyUsedContainer(MutableMapping): def keys(self): with self.lock: - return self._container.keys() + return list(iterkeys(self._container)) class HTTPHeaderDict(MutableMapping): diff --git a/requests/packages/urllib3/connection.py b/requests/packages/urllib3/connection.py index c6e1959a..e5de769d 100644 --- a/requests/packages/urllib3/connection.py +++ b/requests/packages/urllib3/connection.py @@ -3,6 +3,7 @@ import sys import socket from socket import timeout as SocketTimeout import warnings +from .packages import six try: # Python 3 from http.client import HTTPConnection as _HTTPConnection, HTTPException @@ -26,12 +27,20 @@ except (ImportError, AttributeError): # Platform-specific: No SSL. pass +try: # Python 3: + # Not a no-op, we're adding this to the namespace so it can be imported. + ConnectionError = ConnectionError +except NameError: # Python 2: + class ConnectionError(Exception): + pass + + from .exceptions import ( ConnectTimeoutError, SystemTimeWarning, + SecurityWarning, ) from .packages.ssl_match_hostname import match_hostname -from .packages import six from .util.ssl_ import ( resolve_cert_reqs, @@ -40,8 +49,8 @@ from .util.ssl_ import ( assert_fingerprint, ) -from .util import connection +from .util import connection port_by_scheme = { 'http': 80, @@ -233,8 +242,15 @@ class VerifiedHTTPSConnection(HTTPSConnection): self.assert_fingerprint) elif resolved_cert_reqs != ssl.CERT_NONE \ and self.assert_hostname is not False: - match_hostname(self.sock.getpeercert(), - self.assert_hostname or hostname) + cert = self.sock.getpeercert() + if not cert.get('subjectAltName', ()): + warnings.warn(( + 'Certificate has no `subjectAltName`, falling back to check for a `commonName` for now. ' + 'This feature is being removed by major browsers and deprecated by RFC 2818. ' + '(See https://github.com/shazow/urllib3/issues/497 for details.)'), + SecurityWarning + ) + match_hostname(cert, self.assert_hostname or hostname) self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED or self.assert_fingerprint is not None) diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index 9cc2a955..70ee4eed 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -32,7 +32,7 @@ from .connection import ( port_by_scheme, DummyConnection, HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection, - HTTPException, BaseSSLError, + HTTPException, BaseSSLError, ConnectionError ) from .request import RequestMethods from .response import HTTPResponse @@ -278,6 +278,23 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # can be removed later return Timeout.from_float(timeout) + def _raise_timeout(self, err, url, timeout_value): + """Is the error actually a timeout? Will raise a ReadTimeout or pass""" + + if isinstance(err, SocketTimeout): + raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + + # See the above comment about EAGAIN in Python 3. In Python 2 we have + # to specifically catch it and throw the timeout error + if hasattr(err, 'errno') and err.errno in _blocking_errnos: + raise ReadTimeoutError(self, url, "Read timed out. (read timeout=%s)" % timeout_value) + + # Catch possible read timeouts thrown as SSL errors. If not the + # case, rethrow the original. We need to do this because of: + # http://bugs.python.org/issue10272 + 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) + def _make_request(self, conn, method, url, timeout=_Default, **httplib_request_kw): """ @@ -301,7 +318,12 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): conn.timeout = timeout_obj.connect_timeout # Trigger any extra validation we need to do. - self._validate_conn(conn) + try: + self._validate_conn(conn) + except (SocketTimeout, BaseSSLError) as e: + # Py2 raises this as a BaseSSLError, Py3 raises it as socket timeout. + self._raise_timeout(err=e, url=url, timeout_value=conn.timeout) + raise # conn.request() calls httplib.*.request, not the method in # urllib3.request. It also calls makefile (recv) on the socket. @@ -331,28 +353,8 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): httplib_response = conn.getresponse(buffering=True) except TypeError: # Python 2.6 and older httplib_response = conn.getresponse() - except SocketTimeout: - raise ReadTimeoutError( - self, url, "Read timed out. (read timeout=%s)" % read_timeout) - - except BaseSSLError as e: - # Catch possible read timeouts thrown as SSL errors. If not the - # case, rethrow the original. We need to do this because of: - # http://bugs.python.org/issue10272 - if 'timed out' in str(e) or \ - 'did not complete (read)' in str(e): # Python 2.6 - raise ReadTimeoutError( - self, url, "Read timed out. (read timeout=%s)" % read_timeout) - - raise - - except SocketError as e: # Platform-specific: Python 2 - # See the above comment about EAGAIN in Python 3. In Python 2 we - # have to specifically catch it and throw the timeout error - if e.errno in _blocking_errnos: - raise ReadTimeoutError( - self, url, "Read timed out. (read timeout=%s)" % read_timeout) - + except (SocketTimeout, BaseSSLError, SocketError) as e: + self._raise_timeout(err=e, url=url, timeout_value=read_timeout) raise # AppEngine doesn't have a version attr. @@ -537,12 +539,15 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): raise EmptyPoolError(self, "No pool connections are available.") except (BaseSSLError, CertificateError) as e: - # Release connection unconditionally because there is no way to - # close it externally in case of exception. - release_conn = True + # Close the connection. If a connection is reused on which there + # was a Certificate error, the next request will certainly raise + # another Certificate error. + if conn: + conn.close() + conn = None raise SSLError(e) - except (TimeoutError, HTTPException, SocketError) as e: + except (TimeoutError, HTTPException, SocketError, ConnectionError) as e: if conn: # Discard the connection for these exceptions. It will be # be replaced during the next _get_conn() call. @@ -725,8 +730,7 @@ class HTTPSConnectionPool(HTTPConnectionPool): warnings.warn(( 'Unverified HTTPS request is being made. ' 'Adding certificate verification is strongly advised. See: ' - 'https://urllib3.readthedocs.org/en/latest/security.html ' - '(This warning will only appear once by default.)'), + 'https://urllib3.readthedocs.org/en/latest/security.html'), InsecureRequestWarning) diff --git a/requests/packages/urllib3/contrib/pyopenssl.py b/requests/packages/urllib3/contrib/pyopenssl.py index 24de9e40..8229090c 100644 --- a/requests/packages/urllib3/contrib/pyopenssl.py +++ b/requests/packages/urllib3/contrib/pyopenssl.py @@ -29,7 +29,7 @@ Now you can use :mod:`urllib3` as you normally would, and it will support SNI when the required modules are installed. Activating this module also has the positive side effect of disabling SSL/TLS -encryption in Python 2 (see `CRIME attack`_). +compression in Python 2 (see `CRIME attack`_). If you want to configure the default list of supported cipher suites, you can set the ``urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST`` variable. @@ -70,9 +70,14 @@ HAS_SNI = SUBJ_ALT_NAME_SUPPORT # Map from urllib3 to PyOpenSSL compatible parameter-values. _openssl_versions = { ssl.PROTOCOL_SSLv23: OpenSSL.SSL.SSLv23_METHOD, - ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD, ssl.PROTOCOL_TLSv1: OpenSSL.SSL.TLSv1_METHOD, } + +try: + _openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD}) +except AttributeError: + pass + _openssl_verify = { ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, @@ -199,8 +204,21 @@ class WrappedSocket(object): def settimeout(self, timeout): return self.socket.settimeout(timeout) + def _send_until_done(self, data): + while True: + try: + return self.connection.send(data) + except OpenSSL.SSL.WantWriteError: + _, wlist, _ = select.select([], [self.socket], [], + self.socket.gettimeout()) + if not wlist: + raise timeout() + continue + def sendall(self, data): - return self.connection.sendall(data) + while len(data): + sent = self._send_until_done(data) + data = data[sent:] def close(self): if self._makefile_refs < 1: @@ -248,6 +266,7 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, ssl_version=None): ctx = OpenSSL.SSL.Context(_openssl_versions[ssl_version]) if certfile: + keyfile = keyfile or certfile # Match behaviour of the normal python ssl library ctx.use_certificate_file(certfile) if keyfile: ctx.use_privatekey_file(keyfile) diff --git a/requests/packages/urllib3/exceptions.py b/requests/packages/urllib3/exceptions.py index 7519ba98..0c6fd3c5 100644 --- a/requests/packages/urllib3/exceptions.py +++ b/requests/packages/urllib3/exceptions.py @@ -72,11 +72,8 @@ class MaxRetryError(RequestError): def __init__(self, pool, url, reason=None): self.reason = reason - message = "Max retries exceeded with url: %s" % url - if reason: - message += " (Caused by %r)" % reason - else: - message += " (Caused by redirect)" + message = "Max retries exceeded with url: %s (Caused by %r)" % ( + url, reason) RequestError.__init__(self, pool, url, message) @@ -141,6 +138,12 @@ class LocationParseError(LocationValueError): self.location = location +class ResponseError(HTTPError): + "Used as a container for an error reason supplied in a MaxRetryError." + GENERIC_ERROR = 'too many error responses' + SPECIFIC_ERROR = 'too many {status_code} error responses' + + class SecurityWarning(HTTPWarning): "Warned when perfoming security reducing actions" pass diff --git a/requests/packages/urllib3/request.py b/requests/packages/urllib3/request.py index 51fe2386..b08d6c92 100644 --- a/requests/packages/urllib3/request.py +++ b/requests/packages/urllib3/request.py @@ -118,18 +118,24 @@ class RequestMethods(object): which is used to compose the body of the request. The random boundary string can be explicitly set with the ``multipart_boundary`` parameter. """ - if encode_multipart: - body, content_type = encode_multipart_formdata( - fields or {}, boundary=multipart_boundary) - else: - body, content_type = (urlencode(fields or {}), - 'application/x-www-form-urlencoded') - if headers is None: headers = self.headers - headers_ = {'Content-Type': content_type} - headers_.update(headers) + extra_kw = {'headers': {}} - return self.urlopen(method, url, body=body, headers=headers_, - **urlopen_kw) + if fields: + if 'body' in urlopen_kw: + raise TypeError('request got values for both \'fields\' and \'body\', can only specify one.') + + if encode_multipart: + body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary) + else: + body, content_type = urlencode(fields), 'application/x-www-form-urlencoded' + + extra_kw['body'] = body + extra_kw['headers'] = {'Content-Type': content_type} + + extra_kw['headers'].update(headers) + extra_kw.update(urlopen_kw) + + return self.urlopen(method, url, **extra_kw) diff --git a/requests/packages/urllib3/util/retry.py b/requests/packages/urllib3/util/retry.py index eb560dfc..aeaf8a02 100644 --- a/requests/packages/urllib3/util/retry.py +++ b/requests/packages/urllib3/util/retry.py @@ -2,10 +2,11 @@ import time import logging from ..exceptions import ( - ProtocolError, ConnectTimeoutError, - ReadTimeoutError, MaxRetryError, + ProtocolError, + ReadTimeoutError, + ResponseError, ) from ..packages import six @@ -36,7 +37,6 @@ class Retry(object): Errors will be wrapped in :class:`~urllib3.exceptions.MaxRetryError` unless retries are disabled, in which case the causing exception will be raised. - :param int total: Total number of retries to allow. Takes precedence over other counts. @@ -184,8 +184,8 @@ class Retry(object): return isinstance(err, ConnectTimeoutError) def _is_read_error(self, err): - """ Errors that occur after the request has been started, so we can't - assume that the server did not process any of it. + """ Errors that occur after the request has been started, so we should + assume that the server began processing it. """ return isinstance(err, (ReadTimeoutError, ProtocolError)) @@ -198,8 +198,7 @@ class Retry(object): return self.status_forcelist and status_code in self.status_forcelist def is_exhausted(self): - """ Are we out of retries? - """ + """ Are we out of retries? """ retry_counts = (self.total, self.connect, self.read, self.redirect) retry_counts = list(filter(None, retry_counts)) if not retry_counts: @@ -230,6 +229,7 @@ class Retry(object): connect = self.connect read = self.read redirect = self.redirect + cause = 'unknown' if error and self._is_connection_error(error): # Connect retry? @@ -251,10 +251,16 @@ class Retry(object): # Redirect retry? if redirect is not None: redirect -= 1 + cause = 'too many redirects' else: - # FIXME: Nothing changed, scenario doesn't make sense. + # Incrementing because of a server error like a 500 in + # status_forcelist and a the given method is in the whitelist _observed_errors += 1 + cause = ResponseError.GENERIC_ERROR + if response and response.status: + cause = ResponseError.SPECIFIC_ERROR.format( + status_code=response.status) new_retry = self.new( total=total, @@ -262,7 +268,7 @@ class Retry(object): _observed_errors=_observed_errors) if new_retry.is_exhausted(): - raise MaxRetryError(_pool, url, error) + raise MaxRetryError(_pool, url, error or ResponseError(cause)) log.debug("Incremented Retry for (url='%s'): %r" % (url, new_retry)) diff --git a/requests/packages/urllib3/util/ssl_.py b/requests/packages/urllib3/util/ssl_.py index 9cfe2d2a..a788b1b9 100644 --- a/requests/packages/urllib3/util/ssl_.py +++ b/requests/packages/urllib3/util/ssl_.py @@ -4,18 +4,84 @@ from hashlib import md5, sha1 from ..exceptions import SSLError -try: # Test for SSL features - SSLContext = None - HAS_SNI = False +SSLContext = None +HAS_SNI = False +create_default_context = None - import ssl +import errno +import ssl + +try: # Test for SSL features from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 - from ssl import SSLContext # Modern SSL? from ssl import HAS_SNI # Has SNI? except ImportError: pass +try: + from ssl import OP_NO_SSLv2, OP_NO_SSLv3, OP_NO_COMPRESSION +except ImportError: + OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 + OP_NO_COMPRESSION = 0x20000 + +try: + from ssl import _DEFAULT_CIPHERS +except ImportError: + _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:ECDH+RC4:' + 'DH+RC4:RSA+RC4:!aNULL:!eNULL:!MD5' + ) + +try: + from ssl import SSLContext # Modern SSL? +except ImportError: + import sys + + class SSLContext(object): # Platform-specific: Python 2 & 3.1 + supports_set_ciphers = sys.version_info >= (2, 7) + + def __init__(self, protocol_version): + self.protocol = protocol_version + # Use default values from a real SSLContext + self.check_hostname = False + self.verify_mode = ssl.CERT_NONE + self.ca_certs = None + self.options = 0 + self.certfile = None + self.keyfile = None + self.ciphers = None + + def load_cert_chain(self, certfile, keyfile): + self.certfile = certfile + self.keyfile = keyfile + + def load_verify_locations(self, location): + self.ca_certs = location + + def set_ciphers(self, cipher_suite): + if not self.supports_set_ciphers: + raise TypeError( + 'Your version of Python does not support setting ' + 'a custom cipher suite. Please upgrade to Python ' + '2.7, 3.2, or later if you need this functionality.' + ) + self.ciphers = cipher_suite + + def wrap_socket(self, socket, server_hostname=None): + kwargs = { + 'keyfile': self.keyfile, + 'certfile': self.certfile, + 'ca_certs': self.ca_certs, + 'cert_reqs': self.verify_mode, + 'ssl_version': self.protocol, + } + if self.supports_set_ciphers: # Platform-specific: Python 2.7+ + return wrap_socket(socket, ciphers=self.ciphers, **kwargs) + else: # Platform-specific: Python 2.6 + return wrap_socket(socket, **kwargs) + + def assert_fingerprint(cert, fingerprint): """ Checks if given fingerprint matches the supplied certificate. @@ -91,42 +157,98 @@ def resolve_ssl_version(candidate): return candidate -if SSLContext is not None: # Python 3.2+ - def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, - ca_certs=None, server_hostname=None, - ssl_version=None): - """ - All arguments except `server_hostname` have the same meaning as for - :func:`ssl.wrap_socket` +def create_urllib3_context(ssl_version=None, cert_reqs=ssl.CERT_REQUIRED, + options=None, ciphers=None): + """All arguments have the same meaning as ``ssl_wrap_socket``. - :param server_hostname: - Hostname of the expected certificate - """ - context = SSLContext(ssl_version) - context.verify_mode = cert_reqs + By default, this function does a lot of the same work that + ``ssl.create_default_context`` does on Python 3.4+. It: - # Disable TLS compression to migitate CRIME attack (issue #309) - OP_NO_COMPRESSION = 0x20000 - context.options |= OP_NO_COMPRESSION + - Disables SSLv2, SSLv3, and compression + - Sets a restricted set of server ciphers - if ca_certs: - try: - context.load_verify_locations(ca_certs) - # Py32 raises IOError - # Py33 raises FileNotFoundError - except Exception as e: # Reraise as SSLError + If you wish to enable SSLv3, you can do:: + + from urllib3.util import ssl_ + context = ssl_.create_urllib3_context() + context.options &= ~ssl_.OP_NO_SSLv3 + + You can do the same to enable compression (substituting ``COMPRESSION`` + for ``SSLv3`` in the last line above). + + :param ssl_version: + The desired protocol version to use. This will default to + PROTOCOL_SSLv23 which will negotiate the highest protocol that both + the server and your installation of OpenSSL support. + :param cert_reqs: + Whether to require the certificate verification. This defaults to + ``ssl.CERT_REQUIRED``. + :param options: + Specific OpenSSL options. These default to ``ssl.OP_NO_SSLv2``, + ``ssl.OP_NO_SSLv3``, ``ssl.OP_NO_COMPRESSION``. + :param ciphers: + Which cipher suites to allow the server to select. + :returns: + Constructed SSLContext object with specified options + :rtype: SSLContext + """ + context = SSLContext(ssl_version or ssl.PROTOCOL_SSLv23) + + if options is None: + options = 0 + # SSLv2 is easily broken and is considered harmful and dangerous + options |= OP_NO_SSLv2 + # SSLv3 has several problems and is now dangerous + options |= OP_NO_SSLv3 + # Disable compression to prevent CRIME attacks for OpenSSL 1.0+ + # (issue #309) + options |= OP_NO_COMPRESSION + + context.options |= options + + if getattr(context, 'supports_set_ciphers', True): # Platform-specific: Python 2.6 + context.set_ciphers(ciphers or _DEFAULT_CIPHERS) + + context.verify_mode = cert_reqs + if getattr(context, 'check_hostname', None) is not None: # Platform-specific: Python 3.2 + context.check_hostname = (context.verify_mode == ssl.CERT_REQUIRED) + return context + + +def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, + ca_certs=None, server_hostname=None, + ssl_version=None, ciphers=None, ssl_context=None): + """ + All arguments except for server_hostname and ssl_context have the same + meaning as they do when using :func:`ssl.wrap_socket`. + + :param server_hostname: + When SNI is supported, the expected hostname of the certificate + :param ssl_context: + A pre-made :class:`SSLContext` object. If none is provided, one will + be created using :func:`create_urllib3_context`. + :param ciphers: + 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. + """ + context = ssl_context + if context is None: + context = create_urllib3_context(ssl_version, cert_reqs, + ciphers=ciphers) + + if ca_certs: + try: + context.load_verify_locations(ca_certs) + except IOError as e: # Platform-specific: Python 2.6, 2.7, 3.2 + raise SSLError(e) + # Py33 raises FileNotFoundError which subclasses OSError + # These are not equivalent unless we check the errno attribute + except OSError as e: # Platform-specific: Python 3.3 and beyond + if e.errno == errno.ENOENT: raise SSLError(e) - if certfile: - # FIXME: This block needs a test. - context.load_cert_chain(certfile, keyfile) - if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI - return context.wrap_socket(sock, server_hostname=server_hostname) - return context.wrap_socket(sock) - -else: # Python 3.1 and earlier - def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, - ca_certs=None, server_hostname=None, - ssl_version=None): - return wrap_socket(sock, keyfile=keyfile, certfile=certfile, - ca_certs=ca_certs, cert_reqs=cert_reqs, - ssl_version=ssl_version) + raise + if certfile: + context.load_cert_chain(certfile, keyfile) + if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI + return context.wrap_socket(sock, server_hostname=server_hostname) + return context.wrap_socket(sock) diff --git a/requests/packages/urllib3/util/url.py b/requests/packages/urllib3/util/url.py index 487d456c..b2ec834f 100644 --- a/requests/packages/urllib3/util/url.py +++ b/requests/packages/urllib3/util/url.py @@ -40,6 +40,48 @@ class Url(namedtuple('Url', url_attrs)): return '%s:%d' % (self.host, self.port) return self.host + @property + def url(self): + """ + Convert self into a url + + This function should more or less round-trip with :func:`.parse_url`. The + returned url may not be exactly the same as the url inputted to + :func:`.parse_url`, but it should be equivalent by the RFC (e.g., urls + with a blank port will have : removed). + + Example: :: + + >>> U = parse_url('http://google.com/mail/') + >>> U.url + 'http://google.com/mail/' + >>> Url('http', 'username:password', 'host.com', 80, + ... '/path', 'query', 'fragment').url + 'http://username:password@host.com:80/path?query#fragment' + """ + scheme, auth, host, port, path, query, fragment = self + url = '' + + # We use "is not None" we want things to happen with empty strings (or 0 port) + if scheme is not None: + url += scheme + '://' + if auth is not None: + url += auth + '@' + if host is not None: + url += host + if port is not None: + url += ':' + str(port) + if path is not None: + url += path + if query is not None: + url += '?' + query + if fragment is not None: + url += '#' + fragment + + return url + + def __str__(self): + return self.url def split_first(s, delims): """ @@ -84,7 +126,7 @@ def parse_url(url): Example:: >>> parse_url('http://google.com/mail/') - Url(scheme='http', host='google.com', port=None, path='/', ...) + Url(scheme='http', host='google.com', port=None, path='/mail/', ...) >>> parse_url('google.com:80') Url(scheme=None, host='google.com', port=80, path=None, ...) >>> parse_url('/foo?bar') @@ -162,7 +204,6 @@ def parse_url(url): return Url(scheme, auth, host, port, path, query, fragment) - def get_host(url): """ Deprecated. Use :func:`.parse_url` instead. From cf82d4406be55c71983d05d7f6e18917540b5c69 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 1 Dec 2014 16:21:41 -0600 Subject: [PATCH 278/545] Update tests to work offline --- test_requests.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test_requests.py b/test_requests.py index 4624f095..2d3ee628 100755 --- a/test_requests.py +++ b/test_requests.py @@ -258,7 +258,7 @@ class RequestsTestCase(unittest.TestCase): """Do not send headers in Session.headers with None values.""" ses = requests.Session() ses.headers['Accept-Encoding'] = None - req = requests.Request('GET', 'http://httpbin.org/get') + req = requests.Request('GET', httpbin('get')) prep = ses.prepare_request(req) assert 'Accept-Encoding' not in prep.headers @@ -1008,12 +1008,12 @@ class RequestsTestCase(unittest.TestCase): assert s == "Basic dGVzdDp0ZXN0" def test_requests_history_is_saved(self): - r = requests.get('https://httpbin.org/redirect/5') + r = requests.get(httpbin('redirect/5')) total = r.history[-1].history i = 0 for item in r.history: assert item.history == total[0:i] - i=i+1 + i = i + 1 def test_json_param_post_content_type_works(self): r = requests.post( @@ -1350,7 +1350,7 @@ class TestMorselToCookieMaxAge(unittest.TestCase): class TestTimeout: def test_stream_timeout(self): try: - requests.get('https://httpbin.org/delay/10', timeout=2.0) + requests.get(httpbin('delay/10'), timeout=2.0) except requests.exceptions.Timeout as e: assert 'Read timed out' in e.args[0].args[0] @@ -1450,7 +1450,7 @@ class TestRedirects: def test_requests_are_updated_each_time(self): session = RedirectSession([303, 307]) - prep = requests.Request('POST', 'http://httpbin.org/post').prepare() + prep = requests.Request('POST', httpbin('post')).prepare() r0 = session.send(prep) assert r0.request.method == 'POST' assert session.calls[-1] == SendCall((r0.request,), {}) @@ -1534,12 +1534,12 @@ def test_prepare_unicode_url(): def test_urllib3_retries(): from requests.packages.urllib3.util import Retry s = requests.Session() - s.mount('https://', HTTPAdapter(max_retries=Retry( + s.mount('http://', HTTPAdapter(max_retries=Retry( total=2, status_forcelist=[500] ))) with pytest.raises(RetryError): - s.get('https://httpbin.org/status/500') + s.get(httpbin('status/500')) if __name__ == '__main__': unittest.main() From 9a0ce939353a3ab4873088fb34d0683749b60e92 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 1 Dec 2014 17:02:42 -0600 Subject: [PATCH 279/545] Add updates for 2.5.0 --- HISTORY.rst | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 0d0d216d..5828c9d2 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,41 @@ Release History --------------- +2.5.0 (2014-12-01) +++++++++++++++++++ + +**Improvements** + +- Allow usage of urllib3's Retry object with HTTPAdapters (#2216) +- The ``iter_lines`` method on a response now accepts a delimiter with which + to split the content (#2295) + +**Behavioural Changes** + +- Add deprecation warnings to functions in requests.utils that will be removed + in 3.0 (#2309) +- Sessions used by the functional API are always closed (#2326) +- Restrict requests to HTTP/1.1 and HTTP/1.0 (stop accepting HTTP/0.9) (#2323) + +**Bugfixes** + +- Only parse the URL once (#2353) +- Allow Content-Length header to always be overriden (#2332) +- Properly handle files in HTTPDigestAuth (#2333) +- Cap redirect_cache size to prevent memory abuse (#2299) +- Fix HTTPDigestAuth handling of redirects after authenticating successfully + (#2253) +- Fix crash with custom method parameter to Session.request (#2317) +- Fix how Link headers are parsed using the regular expression library (#2271) + +**Documentation** + +- Add more references for interlinking (#2348) +- Update CSS for theme (#2290) +- Update width of buttons and sidebar (#2289) +- Replace references of Gittip with Gratipay (#2282) +- Add link to changelog in sidebar (#2273) + 2.4.3 (2014-10-06) ++++++++++++++++++ From 33b25176b86efcfa79bc93bae9be8e4affc48262 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 1 Dec 2014 17:03:24 -0600 Subject: [PATCH 280/545] Bump version to 2.5.0 --- requests/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/__init__.py b/requests/__init__.py index d5e1956e..22cd57d1 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.4.3' -__build__ = 0x020403 +__version__ = '2.5.0' +__build__ = 0x020500 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2014 Kenneth Reitz' From bf2e73522f09dd131c5c8f78efd3ef6d6bc1d4d7 Mon Sep 17 00:00:00 2001 From: Corey Farwell Date: Mon, 8 Dec 2014 13:04:27 -0500 Subject: [PATCH 281/545] Enable GitHub syntax highlighting on README --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 521ab6f2..e9f63ef8 100644 --- a/README.rst +++ b/README.rst @@ -19,7 +19,7 @@ perform the simplest of tasks. Things shouldn't be this way. Not in Python. -.. code-block:: pycon +.. code-block:: python >>> r = requests.get('https://api.github.com', auth=('user', 'pass')) >>> r.status_code From 508f4b1ca514b2f18adaba807eaa81e0c298b8e1 Mon Sep 17 00:00:00 2001 From: rakesh Date: Tue, 9 Dec 2014 01:59:55 +0530 Subject: [PATCH 282/545] Updated the broken link to twitter streaming API documentation --- docs/user/advanced.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 6ec61eac..ed990666 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -384,7 +384,7 @@ Streaming Requests With :class:`requests.Response.iter_lines()` you can easily iterate over streaming APIs such as the `Twitter Streaming -API `_. Simply +API `_. Simply set ``stream`` to ``True`` and iterate over the response with :class:`~requests.Response.iter_lines()`:: From d61540551943df57aa0dece5e44e130309dcafec Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 8 Dec 2014 20:48:40 -0600 Subject: [PATCH 283/545] Copy pip's import machinery wholesale --- requests/packages/__init__.py | 79 ++++++++++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 1 deletion(-) diff --git a/requests/packages/__init__.py b/requests/packages/__init__.py index d62c4b71..5c329d6f 100644 --- a/requests/packages/__init__.py +++ b/requests/packages/__init__.py @@ -1,3 +1,80 @@ +""" +pip._vendor is for vendoring dependencies of pip to prevent needing pip to +depend on something external. + +Files inside of pip._vendor should be considered immutable and should only be +updated to versions from upstream. +""" from __future__ import absolute_import -from . import urllib3 +import sys + + +class VendorAlias(object): + + def __init__(self): + self._vendor_name = __name__ + self._vendor_pkg = self._vendor_name + "." + + def find_module(self, fullname, path=None): + if fullname.startswith(self._vendor_pkg): + return self + + def load_module(self, name): + # Ensure that this only works for the vendored name + if not name.startswith(self._vendor_pkg): + raise ImportError( + "Cannot import %s, must be a subpackage of '%s'." % ( + name, self._vendor_name, + ) + ) + + # Check to see if we already have this item in sys.modules, if we do + # then simply return that. + if name in sys.modules: + return sys.modules[name] + + # Check to see if we can import the vendor name + try: + # We do this dance here because we want to try and import this + # module without hitting a recursion error because of a bunch of + # VendorAlias instances on sys.meta_path + real_meta_path = sys.meta_path[:] + try: + sys.meta_path = [ + m for m in sys.meta_path + if not isinstance(m, VendorAlias) + ] + __import__(name) + module = sys.modules[name] + finally: + # Re-add any additions to sys.meta_path that were made while + # during the import we just did, otherwise things like + # pip._vendor.six.moves will fail. + for m in sys.meta_path: + if m not in real_meta_path: + real_meta_path.append(m) + + # Restore sys.meta_path with any new items. + sys.meta_path = real_meta_path + except ImportError: + # We can't import the vendor name, so we'll try to import the + # "real" name. + real_name = name[len(self._vendor_pkg):] + try: + __import__(real_name) + module = sys.modules[real_name] + except ImportError: + raise ImportError("No module named '%s'" % (name,)) + + # If we've gotten here we've found the module we're looking for, either + # as part of our vendored package, or as the real name, so we'll add + # it to sys.modules as the vendored name so that we don't have to do + # the lookup again. + sys.modules[name] = module + + # Finally, return the loaded module + return module + + +sys.meta_path.append(VendorAlias()) From e8d02ea0bbc05042e618a7ca115f4fca7b2deeb9 Mon Sep 17 00:00:00 2001 From: Arthur Darcet Date: Fri, 12 Dec 2014 16:11:32 +0100 Subject: [PATCH 284/545] utils.guess_filename fails if the given parameter looks like a file object but has a non-string name attribute e.g. a cherrypy uploaded file behave like a regular file, except that its name attribute is an int and passing it directly to requests fails because of that --- AUTHORS.rst | 1 + requests/utils.py | 2 +- test_requests.py | 8 ++++++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 71171d08..3f2a4d30 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -158,3 +158,4 @@ Patches and Suggestions - Joe Alcorn (`@buttscicles `_) - Syed Suhail Ahmed (`@syedsuhail `_) - Scott Sadler (`@ssadler `_) +- Arthur Darcet (`@arthurdarcet `_) diff --git a/requests/utils.py b/requests/utils.py index aa5c140e..74679414 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -115,7 +115,7 @@ def get_netrc_auth(url): def guess_filename(obj): """Tries to guess the filename of the given object.""" name = getattr(obj, 'name', None) - if name and name[0] != '<' and name[-1] != '>': + if name and isinstance(name, builtin_str) and name[0] != '<' and name[-1] != '>': return os.path.basename(name) diff --git a/test_requests.py b/test_requests.py index 2d3ee628..68ee08c5 100755 --- a/test_requests.py +++ b/test_requests.py @@ -928,6 +928,14 @@ class RequestsTestCase(unittest.TestCase): assert 'multipart/form-data' in p.headers['Content-Type'] + def test_can_send_file_object_with_non_string_filename(self): + f = io.BytesIO() + f.name = 2 + r = requests.Request('POST', httpbin('post'), files={'f': f}) + p = r.prepare() + + assert 'multipart/form-data' in p.headers['Content-Type'] + def test_autoset_header_values_are_native(self): data = 'this is a string' length = '16' From da82fb4b1d287caff3a2a99692be111eccbd1347 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 12 Dec 2014 21:12:31 -0600 Subject: [PATCH 285/545] Give proper attribution to pip --- requests/packages/__init__.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/requests/packages/__init__.py b/requests/packages/__init__.py index 5c329d6f..ec6a9e06 100644 --- a/requests/packages/__init__.py +++ b/requests/packages/__init__.py @@ -1,9 +1,24 @@ """ -pip._vendor is for vendoring dependencies of pip to prevent needing pip to -depend on something external. +Copyright (c) Donald Stufft, pip, and individual contributors -Files inside of pip._vendor should be considered immutable and should only be -updated to versions from upstream. +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ from __future__ import absolute_import @@ -50,7 +65,7 @@ class VendorAlias(object): finally: # Re-add any additions to sys.meta_path that were made while # during the import we just did, otherwise things like - # pip._vendor.six.moves will fail. + # requests.packages.urllib3.poolmanager will fail. for m in sys.meta_path: if m not in real_meta_path: real_meta_path.append(m) From 01b58ba04e694384119e33b05726b81757022b2e Mon Sep 17 00:00:00 2001 From: Ben Edelman Date: Mon, 15 Dec 2014 01:37:19 -0500 Subject: [PATCH 286/545] Fix a typo in a comment I just fixed a minor typo: "throws" is misspelled as "thows". --- requests/compat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/compat.py b/requests/compat.py index be5a1ed6..c07726ee 100644 --- a/requests/compat.py +++ b/requests/compat.py @@ -76,7 +76,7 @@ is_solaris = ('solar==' in str(sys.platform).lower()) # Complete guess. try: import simplejson as json except (ImportError, SyntaxError): - # simplejson does not support Python 3.2, it thows a SyntaxError + # simplejson does not support Python 3.2, it throws a SyntaxError # because of u'...' Unicode literals. import json From 4c61fef13f53db220f95032b72e6e374970bf272 Mon Sep 17 00:00:00 2001 From: ContinuousFunction Date: Mon, 15 Dec 2014 13:41:10 -0500 Subject: [PATCH 287/545] Uncommented test in test_requests.py Uncommented test_sesion_pickling in test_requests.py and ran the file to make sure the test passes. --- test_requests.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test_requests.py b/test_requests.py index 51d071e5..34348d3e 100755 --- a/test_requests.py +++ b/test_requests.py @@ -825,15 +825,15 @@ class RequestsTestCase(unittest.TestCase): assert str(error) == 'message' assert error.response == response -## def test_session_pickling(self): -## r = requests.Request('GET', httpbin('get')) -## s = requests.Session() -## -## s = pickle.loads(pickle.dumps(s)) -## s.proxies = getproxies() -## -## r = s.send(r.prepare()) -## assert r.status_code == 200 + def test_session_pickling(self): + r = requests.Request('GET', httpbin('get')) + s = requests.Session() + + s = pickle.loads(pickle.dumps(s)) + s.proxies = getproxies() + + r = s.send(r.prepare()) + assert r.status_code == 200 def test_fixes_1329(self): """ From 925e975295e2fe772268e748c6c074af7b0ae47d Mon Sep 17 00:00:00 2001 From: daftshady Date: Tue, 16 Dec 2014 16:55:13 +0900 Subject: [PATCH 288/545] catch exception more specifically in Response.ok --- requests/models.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/requests/models.py b/requests/models.py index 2370b67f..17ff4660 100644 --- a/requests/models.py +++ b/requests/models.py @@ -22,9 +22,8 @@ from .packages.urllib3.util import parse_url from .packages.urllib3.exceptions import ( DecodeError, ReadTimeoutError, ProtocolError) from .exceptions import ( - HTTPError, RequestException, MissingSchema, InvalidURL, - ChunkedEncodingError, ContentDecodingError, ConnectionError, - StreamConsumedError) + HTTPError, MissingSchema, InvalidURL, ChunkedEncodingError, + ContentDecodingError, ConnectionError, StreamConsumedError) from .utils import ( guess_filename, get_auth_from_url, requote_uri, stream_decode_response_unicode, to_key_val_list, parse_header_links, @@ -615,7 +614,7 @@ class Response(object): def ok(self): try: self.raise_for_status() - except RequestException: + except HTTPError: return False return True From bd3cf95e34aa49c8d764c899672048df107e0d70 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 16 Dec 2014 22:49:24 -0600 Subject: [PATCH 289/545] Fix error handling on Python 3 --- requests/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 22b6d110..b728c84e 100644 --- a/requests/models.py +++ b/requests/models.py @@ -353,7 +353,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): try: scheme, auth, host, port, path, query, fragment = parse_url(url) except LocationParseError as e: - raise InvalidURL(e.message) + raise InvalidURL(*e.args) if not scheme: raise MissingSchema("Invalid URL {0!r}: No schema supplied. " From 86c3ecfd341e4e86977317f03ae344d363c63e3a Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 23 Dec 2014 10:40:31 -0600 Subject: [PATCH 290/545] Fix bug in renegotiating a nonce with the server If a session runs long enough (without constant activity) then the server can expire the nonce the session has negotiated. If that happens the session will get a new 401 response which we were immediately returning to the user. A user would then have to essentially reinitialize session.auth each time they get an unexpected 401. Also, there's no need for setattr calls when we can simply assign the attribute on the instance. --- requests/auth.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/requests/auth.py b/requests/auth.py index 618a902a..b950181d 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -67,6 +67,7 @@ class HTTPDigestAuth(AuthBase): self.nonce_count = 0 self.chal = {} self.pos = None + self.num_401_calls = 1 def build_digest_header(self, method, url): @@ -154,7 +155,7 @@ class HTTPDigestAuth(AuthBase): def handle_redirect(self, r, **kwargs): """Reset num_401_calls counter on redirects.""" if r.is_redirect: - setattr(self, 'num_401_calls', 1) + self.num_401_calls = 1 def handle_401(self, r, **kwargs): """Takes the given response and tries digest-auth, if needed.""" @@ -168,7 +169,7 @@ class HTTPDigestAuth(AuthBase): if 'digest' in s_auth.lower() and num_401_calls < 2: - setattr(self, 'num_401_calls', num_401_calls + 1) + self.num_401_calls += 1 pat = re.compile(r'digest ', flags=re.IGNORECASE) self.chal = parse_dict_header(pat.sub('', s_auth, count=1)) @@ -188,7 +189,7 @@ class HTTPDigestAuth(AuthBase): return _r - setattr(self, 'num_401_calls', num_401_calls + 1) + self.num_401_calls = 1 return r def __call__(self, r): From ce5b5fe227f939cd0195aa47822b8404357ced5d Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 23 Dec 2014 11:50:31 -0600 Subject: [PATCH 291/545] Add release notes for 2.5.1 --- HISTORY.rst | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 5828c9d2..9f7161c6 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,19 @@ Release History --------------- +2.5.1 (2014-12-23) +++++++++++++++++++ + +**Behavioural Changes** + +- Only catch HTTPErrors in raise_for_status (#2382) + +**Bugfixes** + +- Handle LocationParseError from urllib3 (#2344) +- Handle file-like object filenames that are not strings (#2379) +- Unbreak HTTPDigestAuth handler. Allow new nonces to be negotiated (#2389) + 2.5.0 (2014-12-01) ++++++++++++++++++ From b83131779c701720a9ae9efae78996277d416269 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 23 Dec 2014 11:54:01 -0600 Subject: [PATCH 292/545] Bump version to 2.5.1 --- requests/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/__init__.py b/requests/__init__.py index 22cd57d1..ac2b06c8 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.5.0' -__build__ = 0x020500 +__version__ = '2.5.1' +__build__ = 0x020501 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2014 Kenneth Reitz' From f4ec3d2309c681777211c5aba8c9fe3144f21ae2 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 24 Dec 2014 14:33:14 +0000 Subject: [PATCH 293/545] Clean up cookie docs and display them. --- docs/api.rst | 6 ++++ requests/cookies.py | 88 ++++++++++++++++++++++++--------------------- 2 files changed, 54 insertions(+), 40 deletions(-) diff --git a/docs/api.rst b/docs/api.rst index 7c5dae2e..7225a837 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -88,6 +88,12 @@ Cookies .. autofunction:: requests.utils.cookiejar_from_dict .. autofunction:: requests.utils.add_dict_to_cookiejar +.. autoclass:: requests.cookies.RequestsCookieJar + :inherited-members: + +.. autoclass:: requests.cookies.CookieConflictError + :inherited-members: + Encodings ~~~~~~~~~ diff --git a/requests/cookies.py b/requests/cookies.py index 831c49c6..a972c643 100644 --- a/requests/cookies.py +++ b/requests/cookies.py @@ -157,26 +157,28 @@ class CookieConflictError(RuntimeError): 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 don't specify one, since some clients may expect response.cookies and session.cookies to support dict operations. - Don't use the dict interface internally; it's just for compatibility with - with external client code. All `requests` code should work out of the box - with externally provided instances of CookieJar, e.g., LWPCookieJar and - FileCookieJar. - - Caution: dictionary operations that are normally O(1) may be O(n). + Requests does not use the dict interface internally; it's just for + compatibility with external client code. All `requests` code should work + out of the box with externally provided instances of ``CookieJar``, e.g. + ``LWPCookieJar`` and ``FileCookieJar``. 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): """Dict-like get() that also supports optional domain and path args in 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: return self._find_no_duplicates(name, domain, path) except KeyError: @@ -199,37 +201,38 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping): return c def iterkeys(self): - """Dict-like iterkeys() that returns an iterator of names of cookies from the jar. - See itervalues() and iteritems().""" + """Dict-like iterkeys() that returns an iterator of names of cookies + from the jar. See itervalues() and iteritems().""" for cookie in iter(self): yield cookie.name def keys(self): - """Dict-like keys() that returns a list of names of cookies from the jar. - See values() and items().""" + """Dict-like keys() that returns a list of names of cookies from the + jar. See values() and items().""" return list(self.iterkeys()) def itervalues(self): - """Dict-like itervalues() that returns an iterator of values of cookies from the jar. - See iterkeys() and iteritems().""" + """Dict-like itervalues() that returns an iterator of values of cookies + from the jar. See iterkeys() and iteritems().""" for cookie in iter(self): yield cookie.value def values(self): - """Dict-like values() that returns a list of values of cookies from the jar. - See keys() and items().""" + """Dict-like values() that returns a list of values of cookies from the + jar. See keys() and items().""" return list(self.itervalues()) def iteritems(self): - """Dict-like iteritems() that returns an iterator of name-value tuples from the jar. - See iterkeys() and itervalues().""" + """Dict-like iteritems() that returns an iterator of name-value tuples + from the jar. See iterkeys() and itervalues().""" for cookie in iter(self): yield cookie.name, cookie.value def items(self): - """Dict-like items() that returns a list of name-value tuples from the jar. - See keys() and values(). Allows client-code to call "dict(RequestsCookieJar) - and get a vanilla python dict of key value pairs.""" + """Dict-like items() that returns a list of name-value tuples from the + jar. See keys() and values(). Allows client-code to call + ``dict(RequestsCookieJar)`` and get a vanilla python dict of key value + pairs.""" return list(self.iteritems()) def list_domains(self): @@ -259,8 +262,9 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping): return False # there is only one domain in jar def get_dict(self, domain=None, path=None): - """Takes as an argument an optional domain and path and returns a plain old - Python dict of name-value pairs of cookies that meet the requirements.""" + """Takes as an argument an optional domain and path and returns a plain + old Python dict of name-value pairs of cookies that meet the + requirements.""" dictionary = {} for cookie in iter(self): if (domain is None or cookie.domain == domain) and (path is None @@ -269,21 +273,23 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping): return dictionary def __getitem__(self, name): - """Dict-like __getitem__() for compatibility with client code. Throws exception - if there are more than one cookie with name. In that case, use the more - explicit get() method instead. Caution: operation is O(n), not O(1).""" + """Dict-like __getitem__() for compatibility with client code. Throws + exception if there are more than one cookie with name. In that case, + use the more explicit get() method instead. Caution: operation is O(n), + not O(1).""" return self._find_no_duplicates(name) def __setitem__(self, name, value): - """Dict-like __setitem__ for compatibility with client code. Throws exception - if there is already a cookie of that name in the jar. In that case, use the more - explicit set() method instead.""" + """Dict-like __setitem__ for compatibility with client code. Throws + exception if there is already a cookie of that name in the jar. In that + case, use the more explicit set() method instead.""" self.set(name, value) 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) def set_cookie(self, cookie, *args, **kwargs): @@ -300,10 +306,11 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping): super(RequestsCookieJar, self).update(other) def _find(self, name, domain=None, path=None): - """Requests uses this method internally to get cookie values. Takes as args name - and optional domain and path. Returns a cookie.value. If there are conflicting cookies, - _find arbitrarily chooses one. See _find_no_duplicates if you want an exception thrown - if there are conflicting cookies.""" + """Requests uses this method internally to get cookie values. Takes as + args name and optional domain and path. Returns a cookie.value. If + there are conflicting cookies, _find arbitrarily chooses one. See + _find_no_duplicates if you want an exception thrown if there are + conflicting cookies.""" for cookie in iter(self): if cookie.name == name: if domain is None or cookie.domain == domain: @@ -313,10 +320,11 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping): raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) def _find_no_duplicates(self, name, domain=None, path=None): - """__get_item__ and get call _find_no_duplicates -- never used in Requests internally. - Takes as args name and optional domain and path. Returns a cookie.value. - Throws KeyError if cookie is not found and CookieConflictError if there are - multiple cookies that match name and optionally domain and path.""" + """__get_item__ and get call _find_no_duplicates -- never used in + Requests internally. Takes as args name and optional domain and path. + Returns a cookie.value. Throws KeyError if cookie is not found and + CookieConflictError if there are multiple cookies that match name and + optionally domain and path.""" toReturn = None for cookie in iter(self): if cookie.name == name: @@ -440,7 +448,7 @@ def merge_cookies(cookiejar, cookies): """ if not isinstance(cookiejar, cookielib.CookieJar): raise ValueError('You can only merge into CookieJar') - + if isinstance(cookies, dict): cookiejar = cookiejar_from_dict( cookies, cookiejar=cookiejar, overwrite=False) From 101425ebfc747d4c0173fc42e64654c6bcb45fe1 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 24 Dec 2014 14:54:49 +0000 Subject: [PATCH 294/545] Enhance documentation for clarity. --- requests/cookies.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/requests/cookies.py b/requests/cookies.py index a972c643..6969fe5c 100644 --- a/requests/cookies.py +++ b/requests/cookies.py @@ -165,7 +165,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping): session.cookies to support dict operations. Requests does not use the dict interface internally; it's just for - compatibility with external client code. All `requests` code should work + compatibility with external client code. All requests code should work out of the box with externally provided instances of ``CookieJar``, e.g. ``LWPCookieJar`` and ``FileCookieJar``. @@ -275,8 +275,9 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping): def __getitem__(self, name): """Dict-like __getitem__() for compatibility with client code. Throws exception if there are more than one cookie with name. In that case, - use the more 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) @@ -320,11 +321,11 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping): raise KeyError('name=%r, domain=%r, path=%r' % (name, domain, path)) def _find_no_duplicates(self, name, domain=None, path=None): - """__get_item__ and get call _find_no_duplicates -- never used in - Requests internally. Takes as args name and optional domain and path. - Returns a cookie.value. Throws KeyError if cookie is not found and - CookieConflictError if there are multiple cookies that match name and - optionally domain and path.""" + """Both ``__get_item__`` and ``get`` call this function: it's never + used elsewhere in Requests. Takes as args name and optional domain and + path. Returns a cookie.value. Throws KeyError if cookie is not found + and CookieConflictError if there are multiple cookies that match name + and optionally domain and path.""" toReturn = None for cookie in iter(self): if cookie.name == name: From 892f2560c1fc99260a8cb073478c0c6e7245bd93 Mon Sep 17 00:00:00 2001 From: Shrayas Date: Thu, 1 Jan 2015 09:04:50 +0530 Subject: [PATCH 295/545] Changing year in all copyright information - Happy new year! Thanks for this awesome library :) --- LICENSE | 2 +- docs/conf.py | 2 +- requests/__init__.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/LICENSE b/LICENSE index 8c5e7584..a103fc91 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2014 Kenneth Reitz +Copyright 2015 Kenneth Reitz Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/docs/conf.py b/docs/conf.py index 4521eed4..4969857b 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -46,7 +46,7 @@ master_doc = 'index' # General information about the project. project = u'Requests' -copyright = u'2014. A Kenneth Reitz Project' +copyright = u'2015. A Kenneth Reitz Project' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the diff --git a/requests/__init__.py b/requests/__init__.py index ac2b06c8..0ec35660 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -36,7 +36,7 @@ usage: The other HTTP methods are supported - see `requests.api`. Full documentation is at . -:copyright: (c) 2014 by Kenneth Reitz. +:copyright: (c) 2015 by Kenneth Reitz. :license: Apache 2.0, see LICENSE for more details. """ @@ -46,7 +46,7 @@ __version__ = '2.5.1' __build__ = 0x020501 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' -__copyright__ = 'Copyright 2014 Kenneth Reitz' +__copyright__ = 'Copyright 2015 Kenneth Reitz' # Attempt to enable urllib3's SNI support, if possible try: From 673bd6afce7ca407c1863be5f6049edd4f5d43b0 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 6 Jan 2015 00:56:47 -0500 Subject: [PATCH 296/545] javascripts --- docs/_themes/kr/layout.html | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/_themes/kr/layout.html b/docs/_themes/kr/layout.html index cf977d8f..1b7a67c2 100644 --- a/docs/_themes/kr/layout.html +++ b/docs/_themes/kr/layout.html @@ -68,5 +68,15 @@ })(); + + {%- endblock %} From 15597c27cd62c56892963545c526be7dcdd7854d Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 8 Jan 2015 09:52:50 -0500 Subject: [PATCH 297/545] Requests Pro --- docs/_templates/sidebarintro.html | 8 ++------ docs/_templates/sidebarlogo.html | 3 ++- docs/_themes/kr/layout.html | 3 +++ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index e60cf451..d12a4407 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -17,12 +17,8 @@

Donate

- If you love Requests, consider supporting the author on Gratipay: -

-

- + If you love Requests, consider supporting the author: + Buy Requests Pro

diff --git a/docs/_templates/sidebarlogo.html b/docs/_templates/sidebarlogo.html index 930fdb3d..181d70f3 100644 --- a/docs/_templates/sidebarlogo.html +++ b/docs/_templates/sidebarlogo.html @@ -17,7 +17,8 @@

Donate

- If you love Requests, consider supporting the author on Gittip: + If you love Requests, consider supporting the author: + Buy Requests Pro

-

Get Updates

Receive updates on new releases and upcoming projects.

From ac34e3b346aa22d16a34bf869be1343d0d4236f1 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 8 Jan 2015 09:55:40 -0500 Subject: [PATCH 299/545] simpler button --- docs/_templates/sidebarintro.html | 8 +++----- docs/_templates/sidebarlogo.html | 9 +++------ 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index d12a4407..8e46fbc9 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -15,11 +15,9 @@

-

Donate

-

- If you love Requests, consider supporting the author: - Buy Requests Pro -

+

+ Buy Requests Pro +

Get Updates

diff --git a/docs/_templates/sidebarlogo.html b/docs/_templates/sidebarlogo.html index 3ac39151..928cd2fd 100644 --- a/docs/_templates/sidebarlogo.html +++ b/docs/_templates/sidebarlogo.html @@ -14,12 +14,9 @@ development release.

- -

Donate

-

- If you love Requests, consider supporting the author: - Buy Requests Pro -

+

+ Buy Requests Pro +

Get Updates

Receive updates on new releases and upcoming projects.

From 09658b80c0a090f54e8a30a7531deb96ff8da11e Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 8 Jan 2015 09:58:31 -0500 Subject: [PATCH 300/545] certifi --- docs/_templates/sidebarintro.html | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 8e46fbc9..9ed32a0c 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -40,6 +40,7 @@

Useful Links

    +
  • Certifi Project — Recommended CA Bundle
  • Requests @ GitHub
  • Requests @ PyPI
  • Issue Tracker
  • From 176b3652877b7a9c1b6fce6248661101912abf6a Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 8 Jan 2015 10:01:39 -0500 Subject: [PATCH 301/545] certifi --- docs/_templates/sidebarintro.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 9ed32a0c..ef2a2b0d 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -41,6 +41,10 @@

    Useful Links

    • Certifi Project — Recommended CA Bundle
    • +
    • Requests-Toolbelt
    • + +
      +
    • Requests @ GitHub
    • Requests @ PyPI
    • Issue Tracker
    • From b176ccd481eb0c327e6b55254f580b59b3aecce4 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 8 Jan 2015 10:04:58 -0500 Subject: [PATCH 302/545] drawn towards it rather than pushed away --- docs/_templates/sidebarintro.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index ef2a2b0d..11d32667 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -40,10 +40,10 @@

      Useful Links

        -
      • Certifi Project — Recommended CA Bundle
      • +
      • Certifi: Best CA Bundle
      • Requests-Toolbelt
      • -
        +

      • Requests @ GitHub
      • Requests @ PyPI
      • From d2d576b6b1101e2871c82f63adf2c2b534c2dabc Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 9 Jan 2015 20:55:54 -0600 Subject: [PATCH 303/545] Update urllib3 to a27758625e4169330fcf965652b1093faf5aaaa2 --- requests/packages/urllib3/__init__.py | 2 +- requests/packages/urllib3/connectionpool.py | 47 ++++++++++++------- .../packages/urllib3/contrib/pyopenssl.py | 5 ++ requests/packages/urllib3/util/retry.py | 2 +- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/requests/packages/urllib3/__init__.py b/requests/packages/urllib3/__init__.py index dfc82d03..d7592ae7 100644 --- a/requests/packages/urllib3/__init__.py +++ b/requests/packages/urllib3/__init__.py @@ -55,7 +55,7 @@ def add_stderr_logger(level=logging.DEBUG): del NullHandler -# Set security warning to only go off once by default. +# Set security warning to always go off by default. import warnings warnings.simplefilter('always', exceptions.SecurityWarning) diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index 70ee4eed..8bdf228f 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -266,6 +266,10 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): """ pass + def _prepare_proxy(self, conn): + # Nothing to do for HTTP connections. + pass + def _get_timeout(self, timeout): """ Helper that always returns a :class:`urllib3.util.Timeout` """ if timeout is _Default: @@ -510,11 +514,18 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): try: # Request a connection from the queue. + timeout_obj = self._get_timeout(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. httplib_response = self._make_request(conn, method, url, - timeout=timeout, + timeout=timeout_obj, body=body, headers=headers) # If we're going to release the connection in ``finally:``, then @@ -673,24 +684,26 @@ class HTTPSConnectionPool(HTTPConnectionPool): assert_fingerprint=self.assert_fingerprint) 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 + 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): """ Return a fresh :class:`httplib.HTTPSConnection`. diff --git a/requests/packages/urllib3/contrib/pyopenssl.py b/requests/packages/urllib3/contrib/pyopenssl.py index 8229090c..ee657fb3 100644 --- a/requests/packages/urllib3/contrib/pyopenssl.py +++ b/requests/packages/urllib3/contrib/pyopenssl.py @@ -191,6 +191,11 @@ class WrappedSocket(object): return b'' else: raise + except OpenSSL.SSL.ZeroReturnError as e: + if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: + return b'' + else: + raise except OpenSSL.SSL.WantReadError: rd, wd, ed = select.select( [self.socket], [], [], self.socket.gettimeout()) diff --git a/requests/packages/urllib3/util/retry.py b/requests/packages/urllib3/util/retry.py index aeaf8a02..7e0959df 100644 --- a/requests/packages/urllib3/util/retry.py +++ b/requests/packages/urllib3/util/retry.py @@ -190,7 +190,7 @@ class Retry(object): return isinstance(err, (ReadTimeoutError, ProtocolError)) 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: return False From 751b35c346e1cf373fcf4b2f19683ebfbd9dae3d Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 26 Dec 2014 20:02:16 -0600 Subject: [PATCH 304/545] Attempt to quote anyway if unquoting fails Fixes #2356 --- requests/utils.py | 16 ++++++++++++---- test_requests.py | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/requests/utils.py b/requests/utils.py index 74679414..29413964 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -418,10 +418,18 @@ def requote_uri(uri): This function passes the given URI through an unquote/quote cycle to ensure that it is fully and consistently quoted. """ - # 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 = "!#$%&'()*+,/:;=?@[]~" + safe_without_percent = "!#$&'()*+,/:;=?@[]~" + try: + # 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): diff --git a/test_requests.py b/test_requests.py index 34348d3e..9337b0e2 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1301,6 +1301,22 @@ class UtilsTestCase(unittest.TestCase): assert username == percent_encoding_test_chars assert password == percent_encoding_test_chars + def test_requote_uri_with_unquoted_percents(self): + """Ensure we handle unquoted percent signs in redirects. + + See: https://github.com/kennethreitz/requests/issues/2356 + """ + from requests.utils import requote_uri + bad_uri = 'http://example.com/fiz?buz=%ppicture' + quoted = 'http://example.com/fiz?buz=%25ppicture' + assert quoted == requote_uri(bad_uri) + + def test_requote_uri_properly_requotes(self): + """Ensure requoting doesn't break expectations.""" + from requests.utils import requote_uri + quoted = 'http://example.com/fiz?buz=%25ppicture' + assert quoted == requote_uri(quoted) + class TestMorselToCookieExpires(unittest.TestCase): From f37fe551bbbf6ac8d07c35c64888b4ab289c967d Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 18 Jan 2015 21:17:11 -0600 Subject: [PATCH 305/545] Remove entirely unnecessary and unused bits from requests.compat --- requests/compat.py | 52 ---------------------------------------------- 1 file changed, 52 deletions(-) diff --git a/requests/compat.py b/requests/compat.py index c07726ee..dc35ee04 100644 --- a/requests/compat.py +++ b/requests/compat.py @@ -21,58 +21,6 @@ is_py2 = (_ver[0] == 2) #: Python 3.x? 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: import simplejson as json except (ImportError, SyntaxError): From 677bbe30d7b26b006d8b38107e1db9cbd429ac2f Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 19 Jan 2015 18:50:50 -0600 Subject: [PATCH 306/545] Move noncebit to the only place it is used Since we only allow for "auth" qop-value, hardcode it Fixes #2408 --- requests/auth.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requests/auth.py b/requests/auth.py index b950181d..d1c48251 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -124,13 +124,15 @@ class HTTPDigestAuth(AuthBase): s += os.urandom(8) cnonce = (hashlib.sha1(s).hexdigest()[:16]) - noncebit = "%s:%s:%s:%s:%s" % (nonce, ncvalue, cnonce, qop, HA2) if _algorithm == 'MD5-SESS': HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce)) if qop is None: respdig = KD(HA1, "%s:%s" % (nonce, HA2)) elif qop == 'auth' or 'auth' in qop.split(','): + noncebit = "%s:%s:%s:%s:%s" % ( + nonce, ncvalue, cnonce, 'auth', HA2 + ) respdig = KD(HA1, noncebit) else: # XXX handle auth-int. From eec44dbbb1a0ce0427bf5151aa32ad5fcd1898e5 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 18 Jan 2015 21:51:49 -0600 Subject: [PATCH 307/545] Check that a filename is a basestring instance Instead of only checking one or another type of string-like object that we accept, let's be able to check both. Previously, we only checked if the filename was an instance of the native str type which on Python 2 excluded unicode filenames and bytes-like filenames on Python 3. Fixes #2411 --- requests/compat.py | 1 - requests/utils.py | 6 ++++-- test_requests.py | 26 ++++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/requests/compat.py b/requests/compat.py index c07726ee..57f474c0 100644 --- a/requests/compat.py +++ b/requests/compat.py @@ -99,7 +99,6 @@ if is_py2: basestring = basestring numeric_types = (int, long, float) - elif is_py3: 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 diff --git a/requests/utils.py b/requests/utils.py index 74679414..bdf86078 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -25,7 +25,8 @@ from . import __version__ from . import certs from .compat import parse_http_list as _parse_list_header 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 .structures import CaseInsensitiveDict from .exceptions import InvalidURL @@ -115,7 +116,8 @@ def get_netrc_auth(url): def guess_filename(obj): """Tries to guess the filename of the given object.""" 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) diff --git a/test_requests.py b/test_requests.py index 34348d3e..940f0973 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1265,6 +1265,32 @@ class UtilsTestCase(unittest.TestCase): 'http://localhost.localdomain:5000/v1.0/') == {} assert get_environ_proxies('http://www.requests.com/') != {} + def test_guess_filename_when_int(self): + from requests.utils import guess_filename + assert None is guess_filename(1) + + def test_guess_filename_when_filename_is_an_int(self): + from requests.utils import guess_filename + fake = type('Fake', (object,), {'name': 1})() + assert None is guess_filename(fake) + + def test_guess_filename_with_file_like_obj(self): + from requests.utils import guess_filename + from requests import compat + fake = type('Fake', (object,), {'name': b'value'})() + guessed_name = guess_filename(fake) + assert b'value' == guessed_name + assert isinstance(guessed_name, compat.bytes) + + def test_guess_filename_with_unicode_name(self): + from requests.utils import guess_filename + from requests import compat + filename = b'value'.decode('utf-8') + fake = type('Fake', (object,), {'name': filename})() + guessed_name = guess_filename(fake) + assert filename == guessed_name + assert isinstance(guessed_name, compat.str) + def test_is_ipv4_address(self): from requests.utils import is_ipv4_address assert is_ipv4_address('8.8.8.8') From 74d85d0a2f21b6d888e8f5a5c4988e04ca522afc Mon Sep 17 00:00:00 2001 From: Chris Kuehl Date: Thu, 22 Jan 2015 19:37:33 -0800 Subject: [PATCH 308/545] HISTORY: replace n-dash to workaround pip bug There exists a problem (maybe a bug?) in pip when using a locale like `LC_ALL=C` (which is commonly used by CI environments and system configuration tools such as Puppet), where the PKG-INFO file is decoded using ascii, raising a UnicodeDecodeError when PKG-INFO contains non-ASCII characters (such as the n-dash removed by this commit): > Downloading/unpacking requests from https://pypi.python.org/packages/source/r/requests/requests-2.5.1.tar.gz > Downloading requests-2.5.1.tar.gz (443Kb): 443Kb downloaded > Running setup.py egg_info for package requests > > Exception: > Traceback (most recent call last): > File "/usr/lib/python3/dist-packages/pip/basecommand.py", line 104, in main > status = self.run(options, args) > File "/usr/lib/python3/dist-packages/pip/commands/install.py", line 245, in run > requirement_set.prepare_files(finder, force_root_egg_info=self.bundle, bundle=self.bundle) > File "/usr/lib/python3/dist-packages/pip/req.py", line 1014, in prepare_files > req_to_install.assert_source_matches_version() > File "/usr/lib/python3/dist-packages/pip/req.py", line 359, in assert_source_matches_version > version = self.installed_version > File "/usr/lib/python3/dist-packages/pip/req.py", line 351, in installed_version > return self.pkg_info()['version'] > File "/usr/lib/python3/dist-packages/pip/req.py", line 318, in pkg_info > data = self.egg_info_data('PKG-INFO') > File "/usr/lib/python3/dist-packages/pip/req.py", line 261, in egg_info_data > data = fp.read() > File "/usr/lib/python3.2/encodings/ascii.py", line 26, in decode > return codecs.ascii_decode(input, self.errors)[0] > UnicodeDecodeError: 'ascii' codec can't decode byte 0xe2 in position 8161: ordinal not in range(128) I'm not able to reproduce this by installing the package directly, only by installing another package which depends on it (via `install_requires`). The command I used was `LC_ALL=C /usr/bin/pip-3.2 install --upgrade ocflib`, with "pip 1.1 from /usr/lib/python3/dist-packages (python 3.2)" (which is the packaged version for Debian 7). Although this should probably be addressed ultimately in pip, replacing this character in HISTORY is a pain-free and pragmatic way to help developers and system administrators who may be stuck with old versions of pip for many years to come. --- HISTORY.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 9f7161c6..b6364be4 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -103,7 +103,7 @@ Release History - Support for connect timeouts! Timeout now accepts a tuple (connect, read) which is used to set individual connect and read timeouts. - Allow copying of PreparedRequests without headers/cookies. - Updated bundled urllib3 version. -- Refactored settings loading from environment — new `Session.merge_environment_settings`. +- Refactored settings loading from environment -- new `Session.merge_environment_settings`. - Handle socket errors in iter_content. From fb1971778f0a199e65193af4bbd0a040fd3808f8 Mon Sep 17 00:00:00 2001 From: Zhaoyu Luo Date: Sun, 25 Jan 2015 18:11:22 -0600 Subject: [PATCH 309/545] Bug fix: field uri in digest authentication should not be empty when encounter http redirections --- requests/auth.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requests/auth.py b/requests/auth.py index b950181d..593208f3 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -103,7 +103,8 @@ class HTTPDigestAuth(AuthBase): # XXX not implemented yet entdig = None 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: path += '?' + p_parsed.query From ff56e431ad94ce54ad0fb8e11113474ea1362112 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 28 Jan 2015 08:16:05 -0600 Subject: [PATCH 310/545] Update support documentation to be more accurate We were missing instructions to report security vulnerabilities, and all of the documentation referred to Kenneth as the only source of support. We were also missing a link to StackOverflow. --- docs/community/support.rst | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/docs/community/support.rst b/docs/community/support.rst index 7f8c332d..6985fa5b 100644 --- a/docs/community/support.rst +++ b/docs/community/support.rst @@ -5,12 +5,27 @@ Support If you have questions or issues about Requests, there are several options: +StackOverflow +------------- + +If your question does not contain sensitive (possibly proprietary) +information or can be properly anonymized, please ask a question on +`StackOverflow `_ +and use the tag ``python-requests``. + Send a Tweet ------------ If your question is less than 140 characters, feel free to send a tweet to -`@kennethreitz `_. +`@kennethreitz `_ or +`@sigmavirus24 `_. +Vulnerability Disclosure +------------------------ + +If you think you have found a potential security vulnerability in requests, +please email `sigmavirus24 `_ and +`Lukasa `_ directly. **Do not file a public issue.** File an Issue ------------- @@ -34,4 +49,9 @@ IRC The official Freenode channel for Requests is `#python-requests `_ -I'm also available as **kennethreitz** on Freenode. +The core developers of requests are on IRC throughout the day. +You can find them in ``#python-requests`` as: + +- kennethreitz +- lukasa +- sigmavirus24 From 02f852cc41aec89e1f5147cc87732927b80b8420 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 28 Jan 2015 08:58:05 -0600 Subject: [PATCH 311/545] Add Cory's twitter account Added some more detail to the Vulnerability section --- docs/community/support.rst | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/docs/community/support.rst b/docs/community/support.rst index 6985fa5b..8270c480 100644 --- a/docs/community/support.rst +++ b/docs/community/support.rst @@ -17,8 +17,9 @@ Send a Tweet ------------ If your question is less than 140 characters, feel free to send a tweet to -`@kennethreitz `_ or -`@sigmavirus24 `_. +`@kennethreitz `_, +`@sigmavirus24 `_, or +`@lukasaoz `_. Vulnerability Disclosure ------------------------ @@ -27,6 +28,15 @@ If you think you have found a potential security vulnerability in requests, please email `sigmavirus24 `_ and `Lukasa `_ directly. **Do not file a public issue.** +If English is not your first language, please try to describe the problem and +its impact to the best of your ability. For greater detail please use your native +language and we will try our best to translate it using online services. Please +also include the code you used to find the problem and the shortest amount of code +necessar to reproduce it. Please do not disclose this to anyone else. We will +retrieve a CVE identifier if necessary and give you full credit under whatever +name or alias you provide. We will respect your privacy and will only publicize +your involvement if you grant us permission. + File an Issue ------------- From 3a5e2b2f3de4d46ea247ad88a26c09867d048712 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 28 Jan 2015 08:58:43 -0600 Subject: [PATCH 312/545] Add a missing comma --- docs/community/support.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/community/support.rst b/docs/community/support.rst index 8270c480..9f18c9e3 100644 --- a/docs/community/support.rst +++ b/docs/community/support.rst @@ -29,7 +29,7 @@ please email `sigmavirus24 `_ and `Lukasa `_ directly. **Do not file a public issue.** If English is not your first language, please try to describe the problem and -its impact to the best of your ability. For greater detail please use your native +its impact to the best of your ability. For greater detail, please use your native language and we will try our best to translate it using online services. Please also include the code you used to find the problem and the shortest amount of code necessar to reproduce it. Please do not disclose this to anyone else. We will From e6448578776f6abe5116017103f6503369dccf1b Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 28 Jan 2015 20:48:22 -0600 Subject: [PATCH 313/545] Fix typo. Use smaller paragraphs. Add CVE list --- docs/community/support.rst | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/docs/community/support.rst b/docs/community/support.rst index 9f18c9e3..d73f7022 100644 --- a/docs/community/support.rst +++ b/docs/community/support.rst @@ -30,12 +30,26 @@ please email `sigmavirus24 `_ and If English is not your first language, please try to describe the problem and its impact to the best of your ability. For greater detail, please use your native -language and we will try our best to translate it using online services. Please -also include the code you used to find the problem and the shortest amount of code -necessar to reproduce it. Please do not disclose this to anyone else. We will -retrieve a CVE identifier if necessary and give you full credit under whatever -name or alias you provide. We will respect your privacy and will only publicize -your involvement if you grant us permission. +language and we will try our best to translate it using online services. + +Please also include the code you used to find the problem and the shortest amount +of code necessary to reproduce it. + +Please do not disclose this to anyone else. We will retrieve a CVE identifier if +necessary and give you full credit under whatever name or alias you provide. +We will only request an identifier when we have a fix and can publish it in a release. + +We will respect your privacy and will only publicize your involvement if you grant +us permission. + +Previous CVEs +~~~~~~~~~~~~~ + +- Fixed in 2.3.0 + + - `CVE 2014-1829 `_ + + - `CVE 2014-1830 `_ File an Issue ------------- From 53f33eb473ec55746098670d178e1e1936003da6 Mon Sep 17 00:00:00 2001 From: Tom S Date: Tue, 3 Feb 2015 09:16:38 +0000 Subject: [PATCH 314/545] quickstart: using a list as a value in query params --- docs/user/quickstart.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index b501b1b4..febcf8ca 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -73,6 +73,13 @@ You can see that the URL has been correctly encoded by printing the URL:: Note that any dictionary key whose value is ``None`` will not be added to the URL's query string. +In order to pass a list of items as a value you must mark the key as +referring to a list like string by appending ``[]`` to the key:: + + >>> payload = {'key1': 'value1', 'key2[]': ['value2', 'value3']} + >>> r = requests.get("http://httpbin.org/get", params=payload) + >>> print(r.url) + http://httpbin.org/get?key1=value1&key2%5B%5D=value2&key2%5B%5D=value3 Response Content ---------------- From ced6550a2555002f6c081d75547f30fd3fc5e29c Mon Sep 17 00:00:00 2001 From: Colin Dickson Date: Tue, 3 Feb 2015 21:23:07 -0500 Subject: [PATCH 315/545] split test_connection_error into two distinct tests, and changed "unknown url" test URL since fooobarbangbazbing.httpbin.org currently gives a valid response. --- test_requests.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test_requests.py b/test_requests.py index 224b0dfe..3f496fc8 100755 --- a/test_requests.py +++ b/test_requests.py @@ -301,11 +301,13 @@ class RequestsTestCase(unittest.TestCase): r = s.get(url) assert r.status_code == 200 - def test_connection_error(self): + def test_connection_error_invalid_domain(self): """Connecting to an unknown domain should raise a ConnectionError""" with pytest.raises(ConnectionError): - requests.get("http://fooobarbangbazbing.httpbin.org") + requests.get("http://doesnotexist.google.com") + def test_connection_error_invalid_port(self): + """Connecting to an invalid port should raise a ConnectionError""" with pytest.raises(ConnectionError): requests.get("http://httpbin.org:1") From 80aee7a2252f15d6790f15e1d98d59885e9c7e7b Mon Sep 17 00:00:00 2001 From: Colin Dickson Date: Wed, 4 Feb 2015 21:05:03 -0500 Subject: [PATCH 316/545] add a timeout value to connection_error_invalid_port test to accelerate failure --- test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index 3f496fc8..6e527848 100755 --- a/test_requests.py +++ b/test_requests.py @@ -309,7 +309,7 @@ class RequestsTestCase(unittest.TestCase): def test_connection_error_invalid_port(self): """Connecting to an invalid port should raise a ConnectionError""" with pytest.raises(ConnectionError): - requests.get("http://httpbin.org:1") + requests.get("http://httpbin.org:1", timeout=1) def test_LocationParseError(self): """Inputing a URL that cannot be parsed should raise an InvalidURL error""" From 291b6b24b7a4645dc7b8b9ee8919c62c65b85fef Mon Sep 17 00:00:00 2001 From: Nicholas Avenell Date: Fri, 6 Feb 2015 13:46:38 +0000 Subject: [PATCH 317/545] Update to use readthedocs.org instead of rtfd.org https://toolbelt.rtfd.org causes an SSL error because the wildcard SSL cert is for *.readthedocs.org. --- docs/user/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index febcf8ca..0878973f 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -275,7 +275,7 @@ In the event you are posting a very large file as a ``multipart/form-data`` request, you may want to stream the request. By default, ``requests`` does not support this, but there is a separate package which does - ``requests-toolbelt``. You should read `the toolbelt's documentation -`_ for more details about how to use it. +`_ for more details about how to use it. For sending multiple files in one request refer to the :ref:`advanced ` section. From b86330d1f15f04e77093800efb8c6bd6909e8c8e Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Tue, 10 Feb 2015 19:03:59 +0000 Subject: [PATCH 318/545] Update certificate bundle. --- requests/cacert.pem | 1185 +++++++++++++++++++++++-------------------- 1 file changed, 635 insertions(+), 550 deletions(-) diff --git a/requests/cacert.pem b/requests/cacert.pem index 729fe15d..96b02a73 100644 --- a/requests/cacert.pem +++ b/requests/cacert.pem @@ -1,83 +1,3 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. -# Subject: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. -# Label: "GTE CyberTrust Global Root" -# Serial: 421 -# MD5 Fingerprint: ca:3d:d3:68:f1:03:5c:d0:32:fa:b8:2b:59:e8:5a:db -# SHA1 Fingerprint: 97:81:79:50:d8:1c:96:70:cc:34:d8:09:cf:79:44:31:36:7e:f4:74 -# SHA256 Fingerprint: a5:31:25:18:8d:21:10:aa:96:4b:02:c7:b7:c6:da:32:03:17:08:94:e5:fb:71:ff:fb:66:67:d5:e6:81:0a:36 ------BEGIN CERTIFICATE----- -MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD -VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv -bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv -b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV -UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU -cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds -b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH -iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS -r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 -04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r -GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 -3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P -lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ ------END CERTIFICATE----- - -# Issuer: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division -# Subject: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division -# Label: "Thawte Server CA" -# Serial: 1 -# MD5 Fingerprint: c5:70:c4:a2:ed:53:78:0c:c8:10:53:81:64:cb:d0:1d -# SHA1 Fingerprint: 23:e5:94:94:51:95:f2:41:48:03:b4:d5:64:d2:a3:a3:f5:d8:8b:8c -# SHA256 Fingerprint: b4:41:0b:73:e2:e6:ea:ca:47:fb:c4:2f:8f:a4:01:8a:f4:38:1d:c5:4c:fa:a8:44:50:46:1e:ed:09:45:4d:e9 ------BEGIN CERTIFICATE----- -MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx -FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD -VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv -biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm -MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx -MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT -DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3 -dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl -cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3 -DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD -gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91 -yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX -L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj -EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG -7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e -QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ -qdq5snUb9kLy78fyGPmJvKP/iiMucEc= ------END CERTIFICATE----- - -# Issuer: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division -# Subject: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division -# Label: "Thawte Premium Server CA" -# Serial: 1 -# MD5 Fingerprint: 06:9f:69:79:16:66:90:02:1b:8c:8c:a2:c3:07:6f:3a -# SHA1 Fingerprint: 62:7f:8d:78:27:65:63:99:d2:7d:7f:90:44:c9:fe:b3:f3:3e:fa:9a -# SHA256 Fingerprint: ab:70:36:36:5c:71:54:aa:29:c2:c2:9f:5d:41:91:16:3b:16:2a:22:25:01:13:57:d5:6d:07:ff:a7:bc:1f:72 ------BEGIN CERTIFICATE----- -MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx -FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD -VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv -biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy -dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t -MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB -MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG -A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp -b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl -cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv -bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE -VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ -ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR -uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG -9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI -hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM -pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg== ------END CERTIFICATE----- # Issuer: O=Equifax OU=Equifax Secure Certificate Authority # Subject: O=Equifax OU=Equifax Secure Certificate Authority @@ -106,55 +26,6 @@ A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y 1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 -----END CERTIFICATE----- -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Label: "Verisign Class 3 Public Primary Certification Authority" -# Serial: 149843929435818692848040365716851702463 -# MD5 Fingerprint: 10:fc:63:5d:f6:26:3e:0d:f3:25:be:5f:79:cd:67:67 -# SHA1 Fingerprint: 74:2c:31:92:e6:07:e4:24:eb:45:49:54:2b:e1:bb:c5:3e:61:74:e2 -# SHA256 Fingerprint: e7:68:56:34:ef:ac:f6:9a:ce:93:9a:6b:25:5b:7b:4f:ab:ef:42:93:5b:50:a2:65:ac:b5:cb:60:27:e4:4e:70 ------BEGIN CERTIFICATE----- -MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz -cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 -MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV -BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt -YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN -ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE -BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is -I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G -CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do -lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc -AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k ------END CERTIFICATE----- - -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network -# Label: "Verisign Class 3 Public Primary Certification Authority - G2" -# Serial: 167285380242319648451154478808036881606 -# MD5 Fingerprint: a2:33:9b:4c:74:78:73:d4:6c:e7:c1:f3:8d:cb:5c:e9 -# SHA1 Fingerprint: 85:37:1c:a6:e5:50:14:3d:ce:28:03:47:1b:de:3a:09:e8:f8:77:0f -# SHA256 Fingerprint: 83:ce:3c:12:29:68:8a:59:3d:48:5f:81:97:3c:0f:91:95:43:1e:da:37:cc:5e:36:43:0e:79:c7:a8:88:63:8b ------BEGIN CERTIFICATE----- -MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ -BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh -c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy -MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp -emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X -DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw -FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg -UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo -YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 -MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB -AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 -pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 -13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID -AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk -U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i -F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY -oJ2daZH9 ------END CERTIFICATE----- - # Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA # Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA # Label: "GlobalSign Root CA" @@ -214,84 +85,6 @@ AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== -----END CERTIFICATE----- -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority -# Label: "ValiCert Class 1 VA" -# Serial: 1 -# MD5 Fingerprint: 65:58:ab:15:ad:57:6c:1e:a8:a7:b5:69:ac:bf:ff:eb -# SHA1 Fingerprint: e5:df:74:3c:b6:01:c4:9b:98:43:dc:ab:8c:e8:6a:81:10:9f:e4:8e -# SHA256 Fingerprint: f4:c1:49:55:1a:30:13:a3:5b:c7:bf:fe:17:a7:f3:44:9b:c1:ab:5b:5a:0a:e7:4b:06:c2:3b:90:00:4c:01:04 ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy -NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y -LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+ -TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y -TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0 -LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW -I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw -nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI ------END CERTIFICATE----- - -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority -# Label: "ValiCert Class 2 VA" -# Serial: 1 -# MD5 Fingerprint: a9:23:75:9b:ba:49:36:6e:31:c2:db:f2:e7:66:ba:87 -# SHA1 Fingerprint: 31:7a:2a:d0:7f:2b:33:5e:f5:a1:c3:4e:4b:57:e8:b7:d8:f1:fc:a6 -# SHA256 Fingerprint: 58:d0:17:27:9c:d4:dc:63:ab:dd:b1:96:a6:c9:90:6c:30:c4:e0:87:83:ea:e8:c1:60:99:54:d6:93:55:59:6b ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy -NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY -dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9 -WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS -v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v -UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu -IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC -W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd ------END CERTIFICATE----- - -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority -# Label: "RSA Root Certificate 1" -# Serial: 1 -# MD5 Fingerprint: a2:6f:53:b7:ee:40:db:4a:68:e7:fa:18:d9:10:4b:72 -# SHA1 Fingerprint: 69:bd:8c:f4:9c:d3:00:fb:59:2e:17:93:ca:55:6a:f3:ec:aa:35:fb -# SHA256 Fingerprint: bc:23:f9:8a:31:3c:b9:2d:e3:bb:fc:3a:5a:9f:44:61:ac:39:49:4c:4a:e1:5a:9e:9d:f1:31:e9:9b:73:01:9a ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy -NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD -cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs -2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY -JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE -Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ -n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A -PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu ------END CERTIFICATE----- - # Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only # Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only # Label: "Verisign Class 3 Public Primary Certification Authority - G3" @@ -356,42 +149,6 @@ fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== -----END CERTIFICATE----- -# Issuer: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Subject: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Label: "Entrust.net Secure Server CA" -# Serial: 927650371 -# MD5 Fingerprint: df:f2:80:73:cc:f1:e6:61:73:fc:f5:42:e9:c5:7c:ee -# SHA1 Fingerprint: 99:a6:9b:e6:1a:fe:88:6b:4d:2b:82:00:7c:b8:54:fc:31:7e:15:39 -# SHA256 Fingerprint: 62:f2:40:27:8c:56:4c:4d:d8:bf:7d:9d:4f:6f:36:6e:a8:94:d2:2f:5f:34:d9:89:a9:83:ac:ec:2f:ff:ed:50 ------BEGIN CERTIFICATE----- -MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC -VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u -ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc -KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u -ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 -MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE -ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j -b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF -bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg -U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA -A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ -I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 -wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC -AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb -oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 -BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p -dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk -MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp -b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu -dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 -MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi -E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa -MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI -hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN -95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd -2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= ------END CERTIFICATE----- - # Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited # Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited # Label: "Entrust.net Premium 2048 Secure Server CA" @@ -478,30 +235,6 @@ Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv 8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV -----END CERTIFICATE----- -# Issuer: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. -# Subject: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. -# Label: "Equifax Secure eBusiness CA 1" -# Serial: 4 -# MD5 Fingerprint: 64:9c:ef:2e:44:fc:c6:8f:52:07:d0:51:73:8f:cb:3d -# SHA1 Fingerprint: da:40:18:8b:91:89:a3:ed:ee:ae:da:97:fe:2f:9d:f5:b7:d1:8a:41 -# SHA256 Fingerprint: cf:56:ff:46:a4:a1:86:10:9d:d9:65:84:b5:ee:b5:8a:51:0c:42:75:b0:e5:f9:4f:40:bb:ae:86:5e:19:f6:73 ------BEGIN CERTIFICATE----- -MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT -ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw -MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j -LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ -KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo -RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu -WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw -Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD -AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK -eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM -zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+ -WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN -/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ== ------END CERTIFICATE----- - # Issuer: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network # Subject: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network # Label: "AddTrust Low-Value Services Root" @@ -831,77 +564,6 @@ OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS -----END CERTIFICATE----- -# Issuer: CN=America Online Root Certification Authority 1 O=America Online Inc. -# Subject: CN=America Online Root Certification Authority 1 O=America Online Inc. -# Label: "America Online Root Certification Authority 1" -# Serial: 1 -# MD5 Fingerprint: 14:f1:08:ad:9d:fa:64:e2:89:e7:1c:cf:a8:ad:7d:5e -# SHA1 Fingerprint: 39:21:c1:15:c1:5d:0e:ca:5c:cb:5b:c4:f0:7d:21:d8:05:0b:56:6a -# SHA256 Fingerprint: 77:40:73:12:c6:3a:15:3d:5b:c0:0b:4e:51:75:9c:df:da:c2:37:dc:2a:33:b6:79:46:e9:8e:9b:fa:68:0a:e3 ------BEGIN CERTIFICATE----- -MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP -bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2 -MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft -ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk -hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym -1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW -OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb -2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko -O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU -AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB -BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF -Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb -LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir -oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C -MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds -sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 ------END CERTIFICATE----- - -# Issuer: CN=America Online Root Certification Authority 2 O=America Online Inc. -# Subject: CN=America Online Root Certification Authority 2 O=America Online Inc. -# Label: "America Online Root Certification Authority 2" -# Serial: 1 -# MD5 Fingerprint: d6:ed:3c:ca:e2:66:0f:af:10:43:0d:77:9b:04:09:bf -# SHA1 Fingerprint: 85:b5:ff:67:9b:0c:79:96:1f:c8:6e:44:22:00:46:13:db:17:92:84 -# SHA256 Fingerprint: 7d:3b:46:5a:60:14:e5:26:c0:af:fc:ee:21:27:d2:31:17:27:ad:81:1c:26:84:2d:00:6a:f3:73:06:cc:80:bd ------BEGIN CERTIFICATE----- -MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP -bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2 -MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft -ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC -206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci -KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2 -JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9 -BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e -Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B -PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67 -Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq -Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ -o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3 -+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj -YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj -FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE -AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn -xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2 -LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc -obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8 -CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe -IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA -DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F -AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX -Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb -AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl -Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw -RY8mkaKO/qk= ------END CERTIFICATE----- - # Issuer: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association # Subject: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association # Label: "Visa eCommerce Root" @@ -1272,39 +934,6 @@ u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== -----END CERTIFICATE----- -# Issuer: O=TDC Internet OU=TDC Internet Root CA -# Subject: O=TDC Internet OU=TDC Internet Root CA -# Label: "TDC Internet Root CA" -# Serial: 986490188 -# MD5 Fingerprint: 91:f4:03:55:20:a1:f8:63:2c:62:de:ac:fb:61:1c:8e -# SHA1 Fingerprint: 21:fc:bd:8e:7f:6c:af:05:1b:d1:b3:43:ec:a8:e7:61:47:f2:0f:8a -# SHA256 Fingerprint: 48:98:c6:88:8c:0c:ff:b0:d3:e3:1a:ca:8a:37:d4:e3:51:5f:f7:46:d0:26:35:d8:66:46:cf:a0:a3:18:5a:e7 ------BEGIN CERTIFICATE----- -MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJE -SzEVMBMGA1UEChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQg -Um9vdCBDQTAeFw0wMTA0MDUxNjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNV -BAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJuZXQxHTAbBgNVBAsTFFREQyBJbnRl -cm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxLhA -vJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20jxsNu -Zp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a -0vnRrEvLznWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc1 -4izbSysseLlJ28TQx5yc5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGN -eGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcD -R0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZIAYb4QgEBBAQDAgAHMGUG -A1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMMVERDIElu -dGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxME -Q1JMMTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3 -WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAw -HQYDVR0OBBYEFGxkAcf9hW2syNqeUAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJ -KoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4IBAQBO -Q8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540mgwV5dOy0uaOX -wTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+ -2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm89 -9qNLPg7kbWzbO0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0 -jUNAE4z9mQNUecYu6oah9jrUCbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38 -aQNiuJkFBT1reBK9sG9l ------END CERTIFICATE----- - # Issuer: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com # Subject: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com # Label: "UTN DATACorp SGC Root CA" @@ -1490,84 +1119,6 @@ f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK 8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI -----END CERTIFICATE----- -# Issuer: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Subject: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Label: "NetLock Business (Class B) Root" -# Serial: 105 -# MD5 Fingerprint: 39:16:aa:b9:6a:41:e1:14:69:df:9e:6c:3b:72:dc:b6 -# SHA1 Fingerprint: 87:9f:4b:ee:05:df:98:58:3b:e3:60:d6:33:e7:0d:3f:fe:98:71:af -# SHA256 Fingerprint: 39:df:7b:68:2b:7b:93:8f:84:71:54:81:cc:de:8d:60:d8:f2:2e:c5:98:87:7d:0a:aa:c1:2b:59:18:2b:03:12 ------BEGIN CERTIFICATE----- -MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx -ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 -b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD -EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05 -OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G -A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh -Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l -dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG -SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK -gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX -iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc -Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E -BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G -SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu -b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh -bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv -Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln -aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0 -IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh -c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph -biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo -ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP -UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj -YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo -dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA -bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06 -sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa -n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS -NitjrFgBazMpUIaD8QFI ------END CERTIFICATE----- - -# Issuer: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Subject: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Label: "NetLock Express (Class C) Root" -# Serial: 104 -# MD5 Fingerprint: 4f:eb:f1:f0:70:c2:80:63:5d:58:9f:da:12:3c:a9:c4 -# SHA1 Fingerprint: e3:92:51:2f:0a:cf:f5:05:df:f6:de:06:7f:75:37:e1:65:ea:57:4b -# SHA256 Fingerprint: 0b:5e:ed:4e:84:64:03:cf:55:e0:65:84:84:40:ed:2a:82:75:8b:f5:b9:aa:1f:25:3d:46:13:cf:a0:80:ff:3f ------BEGIN CERTIFICATE----- -MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx -ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 -b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD -EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X -DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw -DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u -c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr -TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN -BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA -OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC -2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW -RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P -AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW -ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0 -YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz -b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO -ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB -IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs -b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs -ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s -YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg -a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g -SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0 -aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg -YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg -Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY -ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g -pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4 -Fp1hBWeAyNDYpQcCNJgEjTME1A== ------END CERTIFICATE----- - # Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Label: "XRamp Global CA Root" @@ -1757,40 +1308,6 @@ LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl pYYsfPQS -----END CERTIFICATE----- -# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Label: "Firmaprofesional Root CA" -# Serial: 1 -# MD5 Fingerprint: 11:92:79:40:3c:b1:83:40:e5:ab:66:4a:67:92:80:df -# SHA1 Fingerprint: a9:62:8f:4b:98:a9:1b:48:35:ba:d2:c1:46:32:86:bb:66:64:6a:8c -# SHA256 Fingerprint: c1:cf:0b:52:09:64:35:e3:f1:b7:1d:aa:ec:45:5a:23:11:c8:40:4f:55:83:a9:e2:13:c6:9d:85:7d:94:33:05 ------BEGIN CERTIFICATE----- -MIIEVzCCAz+gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnTELMAkGA1UEBhMCRVMx -IjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1 -dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 -MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20w -HhcNMDExMDI0MjIwMDAwWhcNMTMxMDI0MjIwMDAwWjCBnTELMAkGA1UEBhMCRVMx -IjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1 -dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 -MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20w -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDnIwNvbyOlXnjOlSztlB5u -Cp4Bx+ow0Syd3Tfom5h5VtP8c9/Qit5Vj1H5WuretXDE7aTt/6MNbg9kUDGvASdY -rv5sp0ovFy3Tc9UTHI9ZpTQsHVQERc1ouKDAA6XPhUJHlShbz++AbOCQl4oBPB3z -hxAwJkh91/zpnZFx/0GaqUC1N5wpIE8fUuOgfRNtVLcK3ulqTgesrBlf3H5idPay -BQC6haD9HThuy1q7hryUZzM1gywfI834yJFxzJeL764P3CkDG8A563DtwW4O2GcL -iam8NeTvtjS0pbbELaW+0MOUJEjb35bTALVmGotmBQ/dPz/LP6pemkr4tErvlTcb -AgMBAAGjgZ8wgZwwKgYDVR0RBCMwIYYfaHR0cDovL3d3dy5maXJtYXByb2Zlc2lv -bmFsLmNvbTASBgNVHRMBAf8ECDAGAQH/AgEBMCsGA1UdEAQkMCKADzIwMDExMDI0 -MjIwMDAwWoEPMjAxMzEwMjQyMjAwMDBaMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E -FgQUMwugZtHq2s7eYpMEKFK1FH84aLcwDQYJKoZIhvcNAQEFBQADggEBAEdz/o0n -VPD11HecJ3lXV7cVVuzH2Fi3AQL0M+2TUIiefEaxvT8Ub/GzR0iLjJcG1+p+o1wq -u00vR+L4OQbJnC4xGgN49Lw4xiKLMzHwFgQEffl25EvXwOaD7FnMP97/T2u3Z36m -hoEyIwOdyPdfwUpgpZKpsaSgYMN4h7Mi8yrrW6ntBas3D7Hi05V2Y1Z0jFhyGzfl -ZKG+TQyTmAyX9odtsz/ny4Cm7YjHX1BiAuiZdBbQ5rQ58SfLyEDW44YQqSMSkuBp -QWOnryULwMWSyx6Yo1q6xTMPoJcB3X/ge9YGVM+h4k0460tQtcsm9MracEpqoeJ5 -quGnM/b9Sh/22WA= ------END CERTIFICATE----- - # Issuer: CN=Swisscom Root CA 1 O=Swisscom OU=Digital Certificate Services # Subject: CN=Swisscom Root CA 1 O=Swisscom OU=Digital Certificate Services # Label: "Swisscom Root CA 1" @@ -2617,51 +2134,6 @@ t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- -# Issuer: CN=AC Raíz Certicámara S.A. O=Sociedad Cameral de Certificación Digital - Certicámara S.A. -# Subject: CN=AC Raíz Certicámara S.A. O=Sociedad Cameral de Certificación Digital - Certicámara S.A. -# Label: "AC Ra\xC3\xADz Certic\xC3\xA1mara S.A." -# Serial: 38908203973182606954752843738508300 -# MD5 Fingerprint: 93:2a:3e:f6:fd:23:69:0d:71:20:d4:2b:47:99:2b:a6 -# SHA1 Fingerprint: cb:a1:c5:f8:b0:e3:5e:b8:b9:45:12:d3:f9:34:a2:e9:06:10:d3:36 -# SHA256 Fingerprint: a6:c5:1e:0d:a5:ca:0a:93:09:d2:e4:c0:e4:0c:2a:f9:10:7a:ae:82:03:85:7f:e1:98:e3:e7:69:e3:43:08:5c ------BEGIN CERTIFICATE----- -MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsx -CzAJBgNVBAYTAkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRp -ZmljYWNpw7NuIERpZ2l0YWwgLSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwa -QUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4wHhcNMDYxMTI3MjA0NjI5WhcNMzAw -NDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+U29jaWVkYWQgQ2Ft -ZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJhIFMu -QS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkq -hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeG -qentLhM0R7LQcNzJPNCNyu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzL -fDe3fezTf3MZsGqy2IiKLUV0qPezuMDU2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQ -Y5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU34ojC2I+GdV75LaeHM/J4 -Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP2yYe68yQ -54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+b -MMCm8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48j -ilSH5L887uvDdUhfHjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++Ej -YfDIJss2yKHzMI+ko6Kh3VOz3vCaMh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/zt -A/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK5lw1omdMEWux+IBkAC1vImHF -rEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1bczwmPS9KvqfJ -pxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE -AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCB -lTCBkgYEVR0gADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFy -YS5jb20vZHBjLzBaBggrBgEFBQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW50 -7WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2UgcHVlZGVuIGVuY29udHJhciBlbiBs -YSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEfAygPU3zmpFmps4p6 -xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuXEpBc -unvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/ -Jre7Ir5v/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dp -ezy4ydV/NgIlqmjCMRW3MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42 -gzmRkBDI8ck1fj+404HGIGQatlDCIaR43NAvO2STdPCWkPHv+wlaNECW8DYSwaN0 -jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wkeZBWN7PGKX6jD/EpOe9+ -XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f/RWmnkJD -W2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/ -RL5hRqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35r -MDOhYil/SrnhLecUIw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxk -BYn8eNZcLCZDqQ== ------END CERTIFICATE----- - # Issuer: CN=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA # Subject: CN=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA # Label: "TC TrustCenter Class 2 CA II" @@ -3535,28 +3007,6 @@ r0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdK Z05phkOTOPu220+DkdRgfks+KzgHVZhepA== -----END CERTIFICATE----- -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Label: "Verisign Class 3 Public Primary Certification Authority" -# Serial: 80507572722862485515306429940691309246 -# MD5 Fingerprint: ef:5a:f1:33:ef:f1:cd:bb:51:02:ee:12:14:4b:96:c4 -# SHA1 Fingerprint: a1:db:63:93:91:6f:17:e4:18:55:09:40:04:15:c7:02:40:b0:ae:6b -# SHA256 Fingerprint: a4:b6:b3:99:6f:c2:f3:06:b3:fd:86:81:bd:63:41:3d:8c:50:09:cc:4f:a3:29:c2:cc:f0:e2:fa:1b:14:03:05 ------BEGIN CERTIFICATE----- -MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz -cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 -MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV -BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt -YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN -ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE -BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is -I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G -CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i -2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ -2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ ------END CERTIFICATE----- - # Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. # Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. # Label: "Microsec e-Szigno Root CA 2009" @@ -5024,3 +4474,638 @@ wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy KwbQBM0= -----END CERTIFICATE----- + +# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Label: "TeliaSonera Root CA v1" +# Serial: 199041966741090107964904287217786801558 +# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c +# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 +# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw +NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv +b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD +VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F +VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 +7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X +Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ +/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs +81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm +dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe +Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu +sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 +pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs +slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ +arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD +VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG +9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl +dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj +TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed +Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 +Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI +OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 +vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW +t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn +HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx +SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +# Issuer: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi +# Subject: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi +# Label: "E-Tugra Certification Authority" +# Serial: 7667447206703254355 +# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49 +# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39 +# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV +BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC +aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV +BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1 +Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz +MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+ +BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp +em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY +B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH +D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF +Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo +q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D +k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH +fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut +dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM +ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8 +zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX +U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6 +Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5 +XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF +Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR +HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY +GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c +77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3 ++GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK +vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6 +FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl +yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P +AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD +y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d +NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 2" +# Serial: 1 +# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a +# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 +# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot 2011 O=Atos +# Subject: CN=Atos TrustedRoot 2011 O=Atos +# Label: "Atos TrustedRoot 2011" +# Serial: 6643877497813316402 +# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 +# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 +# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 1 G3" +# Serial: 687049649626669250736271037606554624078720034195 +# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab +# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 +# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2 G3" +# Serial: 390156079458959257446133169266079962026824725800 +# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 +# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 +# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3 G3" +# Serial: 268090761170461462463995952157327242137089239581 +# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 +# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d +# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G2" +# Serial: 15385348160840213938643033620894905419 +# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d +# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f +# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G3" +# Serial: 15459312981008553731928384953135426796 +# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb +# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 +# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G2" +# Serial: 4293743540046975378534879503202253541 +# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 +# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 +# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G3" +# Serial: 7089244469030293291760083333884364146 +# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca +# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e +# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Trusted Root G4" +# Serial: 7451500558977370777930084869016614236 +# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 +# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 +# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +# Issuer: CN=Certification Authority of WoSign O=WoSign CA Limited +# Subject: CN=Certification Authority of WoSign O=WoSign CA Limited +# Label: "WoSign" +# Serial: 125491772294754854453622855443212256657 +# MD5 Fingerprint: a1:f2:f9:b5:d2:c8:7a:74:b8:f3:05:f1:d7:e1:84:8d +# SHA1 Fingerprint: b9:42:94:bf:91:ea:8f:b6:4b:e6:10:97:c7:fb:00:13:59:b6:76:cb +# SHA256 Fingerprint: 4b:22:d5:a6:ae:c9:9f:3c:db:79:aa:5e:c0:68:38:47:9c:d5:ec:ba:71:64:f7:f2:2d:c1:d6:5f:63:d8:57:08 +-----BEGIN CERTIFICATE----- +MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBV +MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNV +BAMTIUNlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgw +MTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFX +b1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvcqN +rLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1U +fcIiePyOCbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcScc +f+Hb0v1naMQFXQoOXXDX2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2 +ZjC1vt7tj/id07sBMOby8w7gLJKA84X5KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4M +x1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR+ScPewavVIMYe+HdVHpR +aG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ezEC8wQjch +zDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDar +uHqklWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221K +mYo0SLwX3OSACCK28jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvA +Sh0JWzko/amrzgD5LkhLJuYwTKVYyrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWv +HYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0CAwEAAaNCMEAwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R8bNLtwYgFP6H +EtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1 +LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJ +MuYhOZO9sxXqT2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2e +JXLOC62qx1ViC777Y7NhRCOjy+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VN +g64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC2nz4SNAzqfkHx5Xh9T71XXG68pWp +dIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes5cVAWubXbHssw1ab +R80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/EaEQ +PkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGce +xGATVdVhmVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+ +J7x6v+Db9NpSvd4MVHAxkUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMl +OtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGikpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWT +ee5Ehr7XHuQe+w== +-----END CERTIFICATE----- + +# Issuer: CN=CA 沃通根证书 O=WoSign CA Limited +# Subject: CN=CA 沃通根证书 O=WoSign CA Limited +# Label: "WoSign China" +# Serial: 106921963437422998931660691310149453965 +# MD5 Fingerprint: 78:83:5b:52:16:76:c4:24:3b:83:78:e8:ac:da:9a:93 +# SHA1 Fingerprint: 16:32:47:8d:89:f9:21:3a:92:00:85:63:f5:a4:a7:d3:12:40:8a:d6 +# SHA256 Fingerprint: d6:f0:34:bd:94:aa:23:3f:02:97:ec:a4:24:5b:28:39:73:e4:47:aa:59:0f:31:0c:77:f4:8f:df:83:11:22:54 +-----BEGIN CERTIFICATE----- +MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBG +MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNV +BAMMEkNBIOayg+mAmuagueivgeS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgw +MTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRl +ZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k8H/r +D195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld1 +9AXbbQs5uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExf +v5RxadmWPgxDT74wwJ85dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnk +UkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+L +NVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFyb7Ao65vh4YOhn0pdr8yb ++gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc76DbT52V +qyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6K +yX2m+Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0G +AbQOXDBGVWCvOGU6yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaK +J/kR8slC/k7e3x9cxKSGhxYzoacXGKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwEC +AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUAA4ICAQBqinA4 +WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6 +yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj +/feTZU7n85iYr83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6 +jBAyvd0zaziGfjk9DgNyp115j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2 +ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0AkLppRQjbbpCBhqcqBT/mhDn4t/lX +X0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97qA4bLJyuQHCH2u2n +FoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Yjj4D +u9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10l +O1Hm13ZBONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Le +ie2uPAmvylezkolwQOQvT8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR1 +2KvxAmLBsX5VYc8T1yaw15zLKYs4SgsOkI26oQ== +-----END CERTIFICATE----- + +# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Label: "COMODO RSA Certification Authority" +# Serial: 101909084537582093308941363524873193117 +# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 +# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 +# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Label: "USERTrust RSA Certification Authority" +# Serial: 2645093764781058787591871645665788717 +# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 +# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e +# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Label: "USERTrust ECC Certification Authority" +# Serial: 123013823720199481456569720443997572134 +# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 +# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 +# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Label: "GlobalSign ECC Root CA - R4" +# Serial: 14367148294922964480859022125800977897474 +# MD5 Fingerprint: 20:f0:27:68:d1:7e:a0:9d:0e:e6:2a:ca:df:5c:89:8e +# SHA1 Fingerprint: 69:69:56:2e:40:80:f4:24:a1:e7:19:9f:14:ba:f3:ee:58:ab:6a:bb +# SHA256 Fingerprint: be:c9:49:11:c2:95:56:76:db:6c:0a:55:09:86:d7:6e:3b:a0:05:66:7c:44:2c:97:62:b4:fb:b7:73:de:22:8c +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ +FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F +uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX +kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs +ewv4n4Q= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Label: "GlobalSign ECC Root CA - R5" +# Serial: 32785792099990507226680698011560947931244 +# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 +# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa +# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- From 62df872871049f1911bbcfee194d603e9704613e Mon Sep 17 00:00:00 2001 From: Joe Smith Date: Wed, 11 Feb 2015 10:22:10 -0800 Subject: [PATCH 319/545] Upgrade urllib3 to 490d3a227fadb626cd54a240b9d0922f849914b4 --- requests/packages/urllib3/_collections.py | 14 +++++++-- requests/packages/urllib3/connectionpool.py | 25 ++++++++++++++- requests/packages/urllib3/poolmanager.py | 8 +++++ requests/packages/urllib3/response.py | 34 ++++++++++++++++++--- requests/packages/urllib3/util/ssl_.py | 9 ++++-- 5 files changed, 79 insertions(+), 11 deletions(-) diff --git a/requests/packages/urllib3/_collections.py b/requests/packages/urllib3/_collections.py index 784342a4..92d07a42 100644 --- a/requests/packages/urllib3/_collections.py +++ b/requests/packages/urllib3/_collections.py @@ -1,7 +1,7 @@ from collections import Mapping, MutableMapping try: from threading import RLock -except ImportError: # Platform-specific: No threads available +except ImportError: # Platform-specific: No threads available class RLock: def __enter__(self): pass @@ -10,7 +10,7 @@ except ImportError: # Platform-specific: No threads available pass -try: # Python 2.7+ +try: # Python 2.7+ from collections import OrderedDict except ImportError: from .packages.ordered_dict import OrderedDict @@ -161,7 +161,7 @@ class HTTPHeaderDict(MutableMapping): def getlist(self, key): """Returns a list of all the values for the named field. Returns an empty list if the key doesn't exist.""" - return self[key].split(', ') if key in self else [] + return [v for k, v in self._data.get(key.lower(), [])] def copy(self): h = HTTPHeaderDict() @@ -196,3 +196,11 @@ class HTTPHeaderDict(MutableMapping): def __repr__(self): return '%s(%r)' % (self.__class__.__name__, dict(self.items())) + + def update(self, *args, **kwds): + headers = args[0] + if isinstance(headers, HTTPHeaderDict): + for key in iterkeys(headers._data): + self._data.setdefault(key.lower(), []).extend(headers._data[key]) + else: + super(HTTPHeaderDict, self).update(*args, **kwds) diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index 8bdf228f..ce178448 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -72,6 +72,21 @@ class ConnectionPool(object): return '%s(host=%r, port=%r)' % (type(self).__name__, 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 _blocking_errnos = set([errno.EAGAIN, errno.EWOULDBLOCK]) @@ -353,7 +368,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # Receive the response from the server 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) except TypeError: # Python 2.6 and older httplib_response = conn.getresponse() @@ -558,6 +573,14 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): conn = None raise SSLError(e) + except SSLError: + # Treat SSLError separately from BaseSSLError to preserve + # traceback. + if conn: + conn.close() + conn = None + raise + except (TimeoutError, HTTPException, SocketError, ConnectionError) as e: if conn: # Discard the connection for these exceptions. It will be diff --git a/requests/packages/urllib3/poolmanager.py b/requests/packages/urllib3/poolmanager.py index 515dc962..9a701e45 100644 --- a/requests/packages/urllib3/poolmanager.py +++ b/requests/packages/urllib3/poolmanager.py @@ -64,6 +64,14 @@ class PoolManager(RequestMethods): self.pools = RecentlyUsedContainer(num_pools, dispose_func=lambda p: p.close()) + 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): """ Create a new :class:`ConnectionPool` based on host, port and scheme. diff --git a/requests/packages/urllib3/response.py b/requests/packages/urllib3/response.py index e69de957..70970685 100644 --- a/requests/packages/urllib3/response.py +++ b/requests/packages/urllib3/response.py @@ -2,6 +2,7 @@ import zlib import io from socket import timeout as SocketTimeout +from .packages import six from ._collections import HTTPHeaderDict from .exceptions import ProtocolError, DecodeError, ReadTimeoutError from .packages.six import string_types as basestring, binary_type @@ -9,7 +10,6 @@ from .connection import HTTPException, BaseSSLError from .util.response import is_fp_closed - class DeflateDecoder(object): def __init__(self): @@ -21,6 +21,9 @@ class DeflateDecoder(object): return getattr(self._obj, name) def decompress(self, data): + if not data: + return data + if not self._first_try: return self._obj.decompress(data) @@ -36,9 +39,23 @@ class DeflateDecoder(object): 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): if mode == 'gzip': - return zlib.decompressobj(16 + zlib.MAX_WBITS) + return GzipDecoder() return DeflateDecoder() @@ -202,7 +219,7 @@ class HTTPResponse(io.IOBase): except BaseSSLError as e: # FIXME: Is there a better way to differentiate between SSLErrors? - if not 'read operation timed out' in str(e): # Defensive: + 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 @@ -270,7 +287,16 @@ class HTTPResponse(io.IOBase): headers = HTTPHeaderDict() for k, v in r.getheaders(): - headers.add(k, v) + if k.lower() != 'set-cookie': + headers.add(k, v) + + if six.PY3: # Python 3: + cookies = r.msg.get_all('set-cookie') or tuple() + else: # Python 2: + cookies = r.msg.getheaders('set-cookie') + + for cookie in cookies: + headers.add('set-cookie', cookie) # HTTPResponse objects in Python 3 don't have a .strict attribute strict = getattr(r, 'strict', 0) diff --git a/requests/packages/urllib3/util/ssl_.py b/requests/packages/urllib3/util/ssl_.py index a788b1b9..7ad1b305 100644 --- a/requests/packages/urllib3/util/ssl_.py +++ b/requests/packages/urllib3/util/ssl_.py @@ -1,5 +1,5 @@ from binascii import hexlify, unhexlify -from hashlib import md5, sha1 +from hashlib import md5, sha1, sha256 from ..exceptions import SSLError @@ -96,7 +96,8 @@ def assert_fingerprint(cert, fingerprint): # this digest. hashfunc_map = { 16: md5, - 20: sha1 + 20: sha1, + 32: sha256, } fingerprint = fingerprint.replace(':', '').lower() @@ -211,7 +212,9 @@ def create_urllib3_context(ssl_version=None, cert_reqs=ssl.CERT_REQUIRED, context.verify_mode = cert_reqs 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 From 344b51327caccca6fd02b28e46495b3d2f02173f Mon Sep 17 00:00:00 2001 From: Kevin Yap Date: Wed, 18 Feb 2015 22:36:16 -0800 Subject: [PATCH 320/545] Update README to use Shields badges Shields badges are friendlier to retina displays. --- README.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index e9f63ef8..40995fe9 100644 --- a/README.rst +++ b/README.rst @@ -1,11 +1,11 @@ Requests: HTTP for Humans ========================= -.. image:: https://badge.fury.io/py/requests.png - :target: http://badge.fury.io/py/requests +.. image:: https://img.shields.io/pypi/v/requests.svg + :target: https://pypi.python.org/pypi/requests -.. image:: https://pypip.in/d/requests/badge.png - :target: https://crate.io/packages/requests/ +.. image:: https://img.shields.io/pypi/dm/requests.svg + :target: https://pypi.python.org/pypi/requests Requests is an Apache2 Licensed HTTP library, written in Python, for human From ed73d9e45d96ba47c6c67796d5ddd7b5d98cc96f Mon Sep 17 00:00:00 2001 From: requiredfield Date: Fri, 20 Feb 2015 12:20:18 -0600 Subject: [PATCH 321/545] document combination of repeated response headers --- docs/user/quickstart.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 0878973f..6fd3602e 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -347,6 +347,15 @@ So, we can access the headers using any capitalization we want:: >>> r.headers.get('content-type') 'application/json' +It is also special in that the server could have sent the same header multiple +times with different values, but requests combines them so they can be +represented in the dictionary within a single mapping, as per +`RFC 7230 `_: + + > A recipient MAY combine multiple header fields with the same field name + > into one "field-name: field-value" pair, without changing the semantics + > of the message, by appending each subsequent field value to the combined + > field value in order, separated by a comma. Cookies ------- From 58e513579a0934dbd737077f3069c04d68cf1a52 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 23 Feb 2015 16:02:30 -0600 Subject: [PATCH 322/545] Update urllib3 to 29aa09bde9c42cc9a8d79aac47ee3d362b438cca --- requests/packages/urllib3/_collections.py | 227 ++++++++++++++----- requests/packages/urllib3/connectionpool.py | 5 +- requests/packages/urllib3/poolmanager.py | 11 +- requests/packages/urllib3/response.py | 32 ++- requests/packages/urllib3/util/connection.py | 1 + requests/packages/urllib3/util/ssl_.py | 4 +- 6 files changed, 197 insertions(+), 83 deletions(-) diff --git a/requests/packages/urllib3/_collections.py b/requests/packages/urllib3/_collections.py index 92d07a42..06412ddf 100644 --- a/requests/packages/urllib3/_collections.py +++ b/requests/packages/urllib3/_collections.py @@ -14,12 +14,14 @@ try: # Python 2.7+ from collections import OrderedDict except ImportError: from .packages.ordered_dict import OrderedDict -from .packages.six import iterkeys, itervalues +from .packages.six import iterkeys, itervalues, PY3 __all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict'] +MULTIPLE_HEADERS_ALLOWED = frozenset(['cookie', 'set-cookie', 'set-cookie2']) + _Null = object() @@ -97,7 +99,14 @@ class RecentlyUsedContainer(MutableMapping): return list(iterkeys(self._container)) -class HTTPHeaderDict(MutableMapping): +_dict_setitem = dict.__setitem__ +_dict_getitem = dict.__getitem__ +_dict_delitem = dict.__delitem__ +_dict_contains = dict.__contains__ +_dict_setdefault = dict.setdefault + + +class HTTPHeaderDict(dict): """ :param headers: An iterable of field-value pairs. Must not contain multiple field names @@ -129,25 +138,72 @@ class HTTPHeaderDict(MutableMapping): 'foo=bar, baz=quxx' >>> headers['Content-Length'] '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): - self._data = {} - if headers is None: - headers = {} - self.update(headers, **kwargs) + dict.__init__(self) + if headers is not None: + self.extend(headers) + if kwargs: + self.extend(kwargs) - def add(self, key, value): + def __setitem__(self, key, val): + return _dict_setitem(self, key.lower(), (key, val)) + + def __getitem__(self, key): + val = _dict_getitem(self, key.lower()) + return ', '.join(val[1:]) + + def __delitem__(self, key): + return _dict_delitem(self, key.lower()) + + def __contains__(self, key): + return _dict_contains(self, key.lower()) + + 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((k1, self[k1]) for k1 in self) == dict((k2, other[k2]) for k2 in other) + + def __ne__(self, other): + return not self.__eq__(other) + + values = MutableMapping.values + get = MutableMapping.get + update = MutableMapping.update + + if not PY3: # Python 2 + iterkeys = MutableMapping.iterkeys + itervalues = MutableMapping.itervalues + + __marker = object() + + 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 exists. @@ -156,51 +212,108 @@ class HTTPHeaderDict(MutableMapping): >>> headers['foo'] '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 = _dict_setdefault(self, 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 + if key_lower in MULTIPLE_HEADERS_ALLOWED: + # Need to convert the tuple to list for further extension + _dict_setitem(self, key_lower, [vals[0], vals[1], val]) + else: + _dict_setitem(self, key_lower, new_vals) + + def extend(*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) > 2: + raise TypeError("update() takes at most 2 positional " + "arguments ({} given)".format(len(args))) + elif not args: + raise TypeError("update() takes at least 1 argument (0 given)") + self = args[0] + other = args[1] if len(args) >= 2 else () + + if 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): """Returns a list of all the values for the named field. Returns an empty list if the key doesn't exist.""" - return [v for k, v in self._data.get(key.lower(), [])] + try: + vals = _dict_getitem(self, key.lower()) + except KeyError: + return [] + else: + if isinstance(vals, tuple): + return [vals[1]] + else: + return vals[1:] - def copy(self): - h = HTTPHeaderDict() - for key in self._data: - for rawkey, value in self._data[key]: - 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] + # Backwards compatibility for httplib + getheaders = getlist + getallmatchingheaders = getlist + iget = getlist def __repr__(self): - return '%s(%r)' % (self.__class__.__name__, dict(self.items())) + return "%s(%s)" % (type(self).__name__, dict(self.itermerged())) - def update(self, *args, **kwds): - headers = args[0] - if isinstance(headers, HTTPHeaderDict): - for key in iterkeys(headers._data): - self._data.setdefault(key.lower(), []).extend(headers._data[key]) - else: - super(HTTPHeaderDict, self).update(*args, **kwds) + def copy(self): + clone = type(self)() + for key in self: + val = _dict_getitem(self, key) + if isinstance(val, list): + # Don't need to convert tuples + val = list(val) + _dict_setitem(clone, key, val) + return clone + + def iteritems(self): + """Iterate over all header lines, including duplicate ones.""" + for key in self: + vals = _dict_getitem(self, key) + 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 = _dict_getitem(self, key) + yield val[0], ', '.join(val[1:]) + + def items(self): + return list(self.iteritems()) + + @classmethod + def from_httplib(cls, message, duplicates=('set-cookie',)): # Python 2 + """Read headers from a Python 2 httplib message object.""" + ret = cls(message.items()) + # ret now contains only the last header line for each duplicate. + # Importing with all duplicates would be nice, but this would + # mean to repeat most of the raw parsing already done, when the + # message object was created. Extracting only the headers of interest + # separately, the cookies, should be faster and requires less + # extra code. + for key in duplicates: + ret.discard(key) + for val in message.getheaders(key): + ret.add(key, val) + return ret diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index ce178448..0085345c 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -588,14 +588,13 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): conn.close() conn = None - stacktrace = sys.exc_info()[2] if isinstance(e, SocketError) and self.proxy: e = ProxyError('Cannot connect to proxy.', e) elif isinstance(e, (SocketError, HTTPException)): e = ProtocolError('Connection aborted.', e) - retries = retries.increment(method, url, error=e, - _pool=self, _stacktrace=stacktrace) + retries = retries.increment(method, url, error=e, _pool=self, + _stacktrace=sys.exc_info()[2]) retries.sleep() # Keep track of the error for the retry warning. diff --git a/requests/packages/urllib3/poolmanager.py b/requests/packages/urllib3/poolmanager.py index 9a701e45..b8d1e745 100644 --- a/requests/packages/urllib3/poolmanager.py +++ b/requests/packages/urllib3/poolmanager.py @@ -8,7 +8,7 @@ except ImportError: from ._collections import RecentlyUsedContainer from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool from .connectionpool import port_by_scheme -from .exceptions import LocationValueError +from .exceptions import LocationValueError, MaxRetryError from .request import RequestMethods from .util.url import parse_url from .util.retry import Retry @@ -175,7 +175,14 @@ class PoolManager(RequestMethods): if not isinstance(retries, Retry): 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 log.info("Redirecting %s -> %s" % (url, redirect_location)) diff --git a/requests/packages/urllib3/response.py b/requests/packages/urllib3/response.py index 70970685..34cd3d70 100644 --- a/requests/packages/urllib3/response.py +++ b/requests/packages/urllib3/response.py @@ -2,10 +2,9 @@ import zlib import io from socket import timeout as SocketTimeout -from .packages import six from ._collections import HTTPHeaderDict from .exceptions import ProtocolError, DecodeError, ReadTimeoutError -from .packages.six import string_types as basestring, binary_type +from .packages.six import string_types as basestring, binary_type, PY3 from .connection import HTTPException, BaseSSLError from .util.response import is_fp_closed @@ -93,9 +92,10 @@ class HTTPResponse(io.IOBase): strict=0, preload_content=True, decode_content=True, original_response=None, pool=None, connection=None): - self.headers = HTTPHeaderDict() - if headers: - self.headers.update(headers) + if isinstance(headers, HTTPHeaderDict): + self.headers = headers + else: + self.headers = HTTPHeaderDict(headers) self.status = status self.version = version self.reason = reason @@ -284,23 +284,16 @@ class HTTPResponse(io.IOBase): Remaining parameters are passed to the HTTPResponse constructor, along with ``original_response=r``. """ - - headers = HTTPHeaderDict() - for k, v in r.getheaders(): - if k.lower() != 'set-cookie': - headers.add(k, v) - - if six.PY3: # Python 3: - cookies = r.msg.get_all('set-cookie') or tuple() - else: # Python 2: - cookies = r.msg.getheaders('set-cookie') - - for cookie in cookies: - headers.add('set-cookie', cookie) + headers = r.msg + if not isinstance(headers, HTTPHeaderDict): + if PY3: # Python 3 + headers = HTTPHeaderDict(headers.items()) + else: # Python 2 + headers = HTTPHeaderDict.from_httplib(headers) # HTTPResponse objects in Python 3 don't have a .strict attribute strict = getattr(r, 'strict', 0) - return ResponseCls(body=r, + resp = ResponseCls(body=r, headers=headers, status=r.status, version=r.version, @@ -308,6 +301,7 @@ class HTTPResponse(io.IOBase): strict=strict, original_response=r, **response_kw) + return resp # Backwards-compatibility methods for httplib.HTTPResponse def getheaders(self): diff --git a/requests/packages/urllib3/util/connection.py b/requests/packages/urllib3/util/connection.py index 2156993a..859aec6e 100644 --- a/requests/packages/urllib3/util/connection.py +++ b/requests/packages/urllib3/util/connection.py @@ -82,6 +82,7 @@ def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, err = _ if sock is not None: sock.close() + sock = None if err is not None: raise err diff --git a/requests/packages/urllib3/util/ssl_.py b/requests/packages/urllib3/util/ssl_.py index 7ad1b305..ee1d3223 100644 --- a/requests/packages/urllib3/util/ssl_.py +++ b/requests/packages/urllib3/util/ssl_.py @@ -29,8 +29,8 @@ try: except ImportError: _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:ECDH+RC4:' - 'DH+RC4:RSA+RC4:!aNULL:!eNULL:!MD5' + 'DH+HIGH:ECDH+3DES:DH+3DES:RSA+AESGCM:RSA+AES:RSA+HIGH:RSA+3DES:!aNULL:' + '!eNULL:!MD5' ) try: From d8be2473d1a586a3673d728d49e10fd4286e3b0e Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 23 Feb 2015 16:26:40 -0600 Subject: [PATCH 323/545] Update HISTORY and version for v2.5.2 --- HISTORY.rst | 47 ++++++++++++++++++++++++++++++++++++++++++++ requests/__init__.py | 4 ++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index b6364be4..269b725a 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,53 @@ Release History --------------- +2.5.2 (2015-02-23) +++++++++++++++++++ + +**Features and Improvements** + +- Add sha256 fingerprint support. (`shazow/urllib3#540`_) + +- Improve the performance of headers. (`shazow/urllib3#544`_) + +**Bugfixes** + +- Copy pip's import machinery. When downstream redistributors remove + requests.packages.urllib3 the import machinery will continue to let those + same symbols work. Example usage in requests' documentation and 3rd-party + libraries relying on the vendored copies of urllib3 will work without having + to fallback to the system urllib3. + +- Attempt to quote parts of the URL on redirect if unquoting and then quoting + fails. (#2356) + +- Fix filename type check for multipart form-data uploads. (#2411) + +- Properly handle the case where a server issuing digest authentication + challenges provides both auth and auth-int qop-values. (#2408) + +- Fix a socket leak. (`shazow/urllib3#549`_) + +- Fix multiple ``Set-Cookie`` headers properly. (`shazow/urllib3#534`_) + +- Disable the built-in hostname verification. (`shazow/urllib3#526`_) + +- Fix the behaviour of decoding an exhausted stream. (`shazow/urllib3#535`_) + +**Security** + +- Pulled in an updated ``cacert.pem``. + +- Drop RC4 from the default cipher list. (`shazow/urllib3#551`_) + +.. _shazow/urllib3#551: https://github.com/shazow/urllib3/pull/551 +.. _shazow/urllib3#549: https://github.com/shazow/urllib3/pull/549 +.. _shazow/urllib3#544: https://github.com/shazow/urllib3/pull/544 +.. _shazow/urllib3#540: https://github.com/shazow/urllib3/pull/540 +.. _shazow/urllib3#535: https://github.com/shazow/urllib3/pull/535 +.. _shazow/urllib3#534: https://github.com/shazow/urllib3/pull/534 +.. _shazow/urllib3#526: https://github.com/shazow/urllib3/pull/526 + 2.5.1 (2014-12-23) ++++++++++++++++++ diff --git a/requests/__init__.py b/requests/__init__.py index 0ec35660..70b50ae1 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.5.1' -__build__ = 0x020501 +__version__ = '2.5.2' +__build__ = 0x020502 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2015 Kenneth Reitz' From b58318163cadbe1f0738690eb779afcec786757e Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 24 Feb 2015 09:44:51 -0600 Subject: [PATCH 324/545] Revert "Update certificate bundle." --- requests/cacert.pem | 1185 ++++++++++++++++++++----------------------- 1 file changed, 550 insertions(+), 635 deletions(-) diff --git a/requests/cacert.pem b/requests/cacert.pem index 96b02a73..729fe15d 100644 --- a/requests/cacert.pem +++ b/requests/cacert.pem @@ -1,3 +1,83 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. +# Subject: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. +# Label: "GTE CyberTrust Global Root" +# Serial: 421 +# MD5 Fingerprint: ca:3d:d3:68:f1:03:5c:d0:32:fa:b8:2b:59:e8:5a:db +# SHA1 Fingerprint: 97:81:79:50:d8:1c:96:70:cc:34:d8:09:cf:79:44:31:36:7e:f4:74 +# SHA256 Fingerprint: a5:31:25:18:8d:21:10:aa:96:4b:02:c7:b7:c6:da:32:03:17:08:94:e5:fb:71:ff:fb:66:67:d5:e6:81:0a:36 +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD +VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv +bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv +b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV +UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU +cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds +b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH +iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS +r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 +04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r +GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 +3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P +lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +-----END CERTIFICATE----- + +# Issuer: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division +# Subject: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division +# Label: "Thawte Server CA" +# Serial: 1 +# MD5 Fingerprint: c5:70:c4:a2:ed:53:78:0c:c8:10:53:81:64:cb:d0:1d +# SHA1 Fingerprint: 23:e5:94:94:51:95:f2:41:48:03:b4:d5:64:d2:a3:a3:f5:d8:8b:8c +# SHA256 Fingerprint: b4:41:0b:73:e2:e6:ea:ca:47:fb:c4:2f:8f:a4:01:8a:f4:38:1d:c5:4c:fa:a8:44:50:46:1e:ed:09:45:4d:e9 +-----BEGIN CERTIFICATE----- +MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD +VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm +MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx +MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3 +dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl +cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3 +DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91 +yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX +L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj +EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG +7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e +QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ +qdq5snUb9kLy78fyGPmJvKP/iiMucEc= +-----END CERTIFICATE----- + +# Issuer: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division +# Subject: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division +# Label: "Thawte Premium Server CA" +# Serial: 1 +# MD5 Fingerprint: 06:9f:69:79:16:66:90:02:1b:8c:8c:a2:c3:07:6f:3a +# SHA1 Fingerprint: 62:7f:8d:78:27:65:63:99:d2:7d:7f:90:44:c9:fe:b3:f3:3e:fa:9a +# SHA256 Fingerprint: ab:70:36:36:5c:71:54:aa:29:c2:c2:9f:5d:41:91:16:3b:16:2a:22:25:01:13:57:d5:6d:07:ff:a7:bc:1f:72 +-----BEGIN CERTIFICATE----- +MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD +VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy +dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t +MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB +MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG +A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp +b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl +cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv +bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE +VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ +ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR +uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG +9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI +hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM +pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg== +-----END CERTIFICATE----- # Issuer: O=Equifax OU=Equifax Secure Certificate Authority # Subject: O=Equifax OU=Equifax Secure Certificate Authority @@ -26,6 +106,55 @@ A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y 1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 -----END CERTIFICATE----- +# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority +# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority +# Label: "Verisign Class 3 Public Primary Certification Authority" +# Serial: 149843929435818692848040365716851702463 +# MD5 Fingerprint: 10:fc:63:5d:f6:26:3e:0d:f3:25:be:5f:79:cd:67:67 +# SHA1 Fingerprint: 74:2c:31:92:e6:07:e4:24:eb:45:49:54:2b:e1:bb:c5:3e:61:74:e2 +# SHA256 Fingerprint: e7:68:56:34:ef:ac:f6:9a:ce:93:9a:6b:25:5b:7b:4f:ab:ef:42:93:5b:50:a2:65:ac:b5:cb:60:27:e4:4e:70 +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE +BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is +I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G +CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do +lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc +AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k +-----END CERTIFICATE----- + +# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network +# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network +# Label: "Verisign Class 3 Public Primary Certification Authority - G2" +# Serial: 167285380242319648451154478808036881606 +# MD5 Fingerprint: a2:33:9b:4c:74:78:73:d4:6c:e7:c1:f3:8d:cb:5c:e9 +# SHA1 Fingerprint: 85:37:1c:a6:e5:50:14:3d:ce:28:03:47:1b:de:3a:09:e8:f8:77:0f +# SHA256 Fingerprint: 83:ce:3c:12:29:68:8a:59:3d:48:5f:81:97:3c:0f:91:95:43:1e:da:37:cc:5e:36:43:0e:79:c7:a8:88:63:8b +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh +c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy +MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp +emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X +DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw +FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg +UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo +YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 +MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 +pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 +13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID +AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk +U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i +F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY +oJ2daZH9 +-----END CERTIFICATE----- + # Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA # Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA # Label: "GlobalSign Root CA" @@ -85,6 +214,84 @@ AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== -----END CERTIFICATE----- +# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority +# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority +# Label: "ValiCert Class 1 VA" +# Serial: 1 +# MD5 Fingerprint: 65:58:ab:15:ad:57:6c:1e:a8:a7:b5:69:ac:bf:ff:eb +# SHA1 Fingerprint: e5:df:74:3c:b6:01:c4:9b:98:43:dc:ab:8c:e8:6a:81:10:9f:e4:8e +# SHA256 Fingerprint: f4:c1:49:55:1a:30:13:a3:5b:c7:bf:fe:17:a7:f3:44:9b:c1:ab:5b:5a:0a:e7:4b:06:c2:3b:90:00:4c:01:04 +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy +NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y +LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+ +TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y +TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0 +LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW +I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw +nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI +-----END CERTIFICATE----- + +# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority +# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority +# Label: "ValiCert Class 2 VA" +# Serial: 1 +# MD5 Fingerprint: a9:23:75:9b:ba:49:36:6e:31:c2:db:f2:e7:66:ba:87 +# SHA1 Fingerprint: 31:7a:2a:d0:7f:2b:33:5e:f5:a1:c3:4e:4b:57:e8:b7:d8:f1:fc:a6 +# SHA256 Fingerprint: 58:d0:17:27:9c:d4:dc:63:ab:dd:b1:96:a6:c9:90:6c:30:c4:e0:87:83:ea:e8:c1:60:99:54:d6:93:55:59:6b +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy +NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY +dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9 +WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS +v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v +UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu +IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC +W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd +-----END CERTIFICATE----- + +# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority +# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority +# Label: "RSA Root Certificate 1" +# Serial: 1 +# MD5 Fingerprint: a2:6f:53:b7:ee:40:db:4a:68:e7:fa:18:d9:10:4b:72 +# SHA1 Fingerprint: 69:bd:8c:f4:9c:d3:00:fb:59:2e:17:93:ca:55:6a:f3:ec:aa:35:fb +# SHA256 Fingerprint: bc:23:f9:8a:31:3c:b9:2d:e3:bb:fc:3a:5a:9f:44:61:ac:39:49:4c:4a:e1:5a:9e:9d:f1:31:e9:9b:73:01:9a +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy +NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD +cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs +2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY +JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE +Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ +n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A +PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu +-----END CERTIFICATE----- + # Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only # Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only # Label: "Verisign Class 3 Public Primary Certification Authority - G3" @@ -149,6 +356,42 @@ fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== -----END CERTIFICATE----- +# Issuer: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Subject: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Label: "Entrust.net Secure Server CA" +# Serial: 927650371 +# MD5 Fingerprint: df:f2:80:73:cc:f1:e6:61:73:fc:f5:42:e9:c5:7c:ee +# SHA1 Fingerprint: 99:a6:9b:e6:1a:fe:88:6b:4d:2b:82:00:7c:b8:54:fc:31:7e:15:39 +# SHA256 Fingerprint: 62:f2:40:27:8c:56:4c:4d:d8:bf:7d:9d:4f:6f:36:6e:a8:94:d2:2f:5f:34:d9:89:a9:83:ac:ec:2f:ff:ed:50 +-----BEGIN CERTIFICATE----- +MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC +VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u +ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc +KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u +ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 +MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE +ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j +b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF +bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg +U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA +A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ +I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 +wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC +AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb +oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 +BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p +dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk +MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp +b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu +dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 +MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi +E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa +MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI +hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN +95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd +2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= +-----END CERTIFICATE----- + # Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited # Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited # Label: "Entrust.net Premium 2048 Secure Server CA" @@ -235,6 +478,30 @@ Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv 8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV -----END CERTIFICATE----- +# Issuer: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. +# Subject: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. +# Label: "Equifax Secure eBusiness CA 1" +# Serial: 4 +# MD5 Fingerprint: 64:9c:ef:2e:44:fc:c6:8f:52:07:d0:51:73:8f:cb:3d +# SHA1 Fingerprint: da:40:18:8b:91:89:a3:ed:ee:ae:da:97:fe:2f:9d:f5:b7:d1:8a:41 +# SHA256 Fingerprint: cf:56:ff:46:a4:a1:86:10:9d:d9:65:84:b5:ee:b5:8a:51:0c:42:75:b0:e5:f9:4f:40:bb:ae:86:5e:19:f6:73 +-----BEGIN CERTIFICATE----- +MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT +ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw +MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j +LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ +KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo +RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu +WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw +Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD +AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK +eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM +zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+ +WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN +/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ== +-----END CERTIFICATE----- + # Issuer: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network # Subject: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network # Label: "AddTrust Low-Value Services Root" @@ -564,6 +831,77 @@ OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS -----END CERTIFICATE----- +# Issuer: CN=America Online Root Certification Authority 1 O=America Online Inc. +# Subject: CN=America Online Root Certification Authority 1 O=America Online Inc. +# Label: "America Online Root Certification Authority 1" +# Serial: 1 +# MD5 Fingerprint: 14:f1:08:ad:9d:fa:64:e2:89:e7:1c:cf:a8:ad:7d:5e +# SHA1 Fingerprint: 39:21:c1:15:c1:5d:0e:ca:5c:cb:5b:c4:f0:7d:21:d8:05:0b:56:6a +# SHA256 Fingerprint: 77:40:73:12:c6:3a:15:3d:5b:c0:0b:4e:51:75:9c:df:da:c2:37:dc:2a:33:b6:79:46:e9:8e:9b:fa:68:0a:e3 +-----BEGIN CERTIFICATE----- +MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP +bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2 +MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft +ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP +ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk +hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym +1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW +OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb +2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko +O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU +AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB +BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF +Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb +LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir +oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C +MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds +sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 +-----END CERTIFICATE----- + +# Issuer: CN=America Online Root Certification Authority 2 O=America Online Inc. +# Subject: CN=America Online Root Certification Authority 2 O=America Online Inc. +# Label: "America Online Root Certification Authority 2" +# Serial: 1 +# MD5 Fingerprint: d6:ed:3c:ca:e2:66:0f:af:10:43:0d:77:9b:04:09:bf +# SHA1 Fingerprint: 85:b5:ff:67:9b:0c:79:96:1f:c8:6e:44:22:00:46:13:db:17:92:84 +# SHA256 Fingerprint: 7d:3b:46:5a:60:14:e5:26:c0:af:fc:ee:21:27:d2:31:17:27:ad:81:1c:26:84:2d:00:6a:f3:73:06:cc:80:bd +-----BEGIN CERTIFICATE----- +MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP +bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2 +MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft +ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg +Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP +ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC +206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci +KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2 +JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9 +BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e +Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B +PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67 +Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq +Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ +o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3 ++L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj +YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj +FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE +AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn +xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2 +LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc +obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8 +CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe +IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA +DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F +AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX +Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb +AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl +Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw +RY8mkaKO/qk= +-----END CERTIFICATE----- + # Issuer: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association # Subject: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association # Label: "Visa eCommerce Root" @@ -934,6 +1272,39 @@ u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== -----END CERTIFICATE----- +# Issuer: O=TDC Internet OU=TDC Internet Root CA +# Subject: O=TDC Internet OU=TDC Internet Root CA +# Label: "TDC Internet Root CA" +# Serial: 986490188 +# MD5 Fingerprint: 91:f4:03:55:20:a1:f8:63:2c:62:de:ac:fb:61:1c:8e +# SHA1 Fingerprint: 21:fc:bd:8e:7f:6c:af:05:1b:d1:b3:43:ec:a8:e7:61:47:f2:0f:8a +# SHA256 Fingerprint: 48:98:c6:88:8c:0c:ff:b0:d3:e3:1a:ca:8a:37:d4:e3:51:5f:f7:46:d0:26:35:d8:66:46:cf:a0:a3:18:5a:e7 +-----BEGIN CERTIFICATE----- +MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJE +SzEVMBMGA1UEChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQg +Um9vdCBDQTAeFw0wMTA0MDUxNjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNV +BAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJuZXQxHTAbBgNVBAsTFFREQyBJbnRl +cm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxLhA +vJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20jxsNu +Zp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a +0vnRrEvLznWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc1 +4izbSysseLlJ28TQx5yc5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGN +eGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcD +R0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZIAYb4QgEBBAQDAgAHMGUG +A1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMMVERDIElu +dGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxME +Q1JMMTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3 +WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAw +HQYDVR0OBBYEFGxkAcf9hW2syNqeUAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJ +KoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4IBAQBO +Q8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540mgwV5dOy0uaOX +wTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+ +2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm89 +9qNLPg7kbWzbO0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0 +jUNAE4z9mQNUecYu6oah9jrUCbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38 +aQNiuJkFBT1reBK9sG9l +-----END CERTIFICATE----- + # Issuer: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com # Subject: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com # Label: "UTN DATACorp SGC Root CA" @@ -1119,6 +1490,84 @@ f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK 8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI -----END CERTIFICATE----- +# Issuer: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok +# Subject: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok +# Label: "NetLock Business (Class B) Root" +# Serial: 105 +# MD5 Fingerprint: 39:16:aa:b9:6a:41:e1:14:69:df:9e:6c:3b:72:dc:b6 +# SHA1 Fingerprint: 87:9f:4b:ee:05:df:98:58:3b:e3:60:d6:33:e7:0d:3f:fe:98:71:af +# SHA256 Fingerprint: 39:df:7b:68:2b:7b:93:8f:84:71:54:81:cc:de:8d:60:d8:f2:2e:c5:98:87:7d:0a:aa:c1:2b:59:18:2b:03:12 +-----BEGIN CERTIFICATE----- +MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx +ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 +b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD +EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05 +OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G +A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh +Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l +dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG +SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK +gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX +iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc +Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E +BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G +SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu +b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh +bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv +Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln +aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0 +IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh +c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph +biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo +ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP +UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj +YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo +dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA +bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06 +sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa +n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS +NitjrFgBazMpUIaD8QFI +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok +# Subject: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok +# Label: "NetLock Express (Class C) Root" +# Serial: 104 +# MD5 Fingerprint: 4f:eb:f1:f0:70:c2:80:63:5d:58:9f:da:12:3c:a9:c4 +# SHA1 Fingerprint: e3:92:51:2f:0a:cf:f5:05:df:f6:de:06:7f:75:37:e1:65:ea:57:4b +# SHA256 Fingerprint: 0b:5e:ed:4e:84:64:03:cf:55:e0:65:84:84:40:ed:2a:82:75:8b:f5:b9:aa:1f:25:3d:46:13:cf:a0:80:ff:3f +-----BEGIN CERTIFICATE----- +MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx +ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 +b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD +EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X +DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw +DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u +c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr +TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA +OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC +2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW +RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P +AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW +ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0 +YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz +b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO +ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB +IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs +b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs +ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s +YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg +a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g +SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0 +aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg +YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg +Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY +ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g +pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4 +Fp1hBWeAyNDYpQcCNJgEjTME1A== +-----END CERTIFICATE----- + # Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Label: "XRamp Global CA Root" @@ -1308,6 +1757,40 @@ LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl pYYsfPQS -----END CERTIFICATE----- +# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 +# Label: "Firmaprofesional Root CA" +# Serial: 1 +# MD5 Fingerprint: 11:92:79:40:3c:b1:83:40:e5:ab:66:4a:67:92:80:df +# SHA1 Fingerprint: a9:62:8f:4b:98:a9:1b:48:35:ba:d2:c1:46:32:86:bb:66:64:6a:8c +# SHA256 Fingerprint: c1:cf:0b:52:09:64:35:e3:f1:b7:1d:aa:ec:45:5a:23:11:c8:40:4f:55:83:a9:e2:13:c6:9d:85:7d:94:33:05 +-----BEGIN CERTIFICATE----- +MIIEVzCCAz+gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnTELMAkGA1UEBhMCRVMx +IjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1 +dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20w +HhcNMDExMDI0MjIwMDAwWhcNMTMxMDI0MjIwMDAwWjCBnTELMAkGA1UEBhMCRVMx +IjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1 +dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 +MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20w +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDnIwNvbyOlXnjOlSztlB5u +Cp4Bx+ow0Syd3Tfom5h5VtP8c9/Qit5Vj1H5WuretXDE7aTt/6MNbg9kUDGvASdY +rv5sp0ovFy3Tc9UTHI9ZpTQsHVQERc1ouKDAA6XPhUJHlShbz++AbOCQl4oBPB3z +hxAwJkh91/zpnZFx/0GaqUC1N5wpIE8fUuOgfRNtVLcK3ulqTgesrBlf3H5idPay +BQC6haD9HThuy1q7hryUZzM1gywfI834yJFxzJeL764P3CkDG8A563DtwW4O2GcL +iam8NeTvtjS0pbbELaW+0MOUJEjb35bTALVmGotmBQ/dPz/LP6pemkr4tErvlTcb +AgMBAAGjgZ8wgZwwKgYDVR0RBCMwIYYfaHR0cDovL3d3dy5maXJtYXByb2Zlc2lv +bmFsLmNvbTASBgNVHRMBAf8ECDAGAQH/AgEBMCsGA1UdEAQkMCKADzIwMDExMDI0 +MjIwMDAwWoEPMjAxMzEwMjQyMjAwMDBaMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E +FgQUMwugZtHq2s7eYpMEKFK1FH84aLcwDQYJKoZIhvcNAQEFBQADggEBAEdz/o0n +VPD11HecJ3lXV7cVVuzH2Fi3AQL0M+2TUIiefEaxvT8Ub/GzR0iLjJcG1+p+o1wq +u00vR+L4OQbJnC4xGgN49Lw4xiKLMzHwFgQEffl25EvXwOaD7FnMP97/T2u3Z36m +hoEyIwOdyPdfwUpgpZKpsaSgYMN4h7Mi8yrrW6ntBas3D7Hi05V2Y1Z0jFhyGzfl +ZKG+TQyTmAyX9odtsz/ny4Cm7YjHX1BiAuiZdBbQ5rQ58SfLyEDW44YQqSMSkuBp +QWOnryULwMWSyx6Yo1q6xTMPoJcB3X/ge9YGVM+h4k0460tQtcsm9MracEpqoeJ5 +quGnM/b9Sh/22WA= +-----END CERTIFICATE----- + # Issuer: CN=Swisscom Root CA 1 O=Swisscom OU=Digital Certificate Services # Subject: CN=Swisscom Root CA 1 O=Swisscom OU=Digital Certificate Services # Label: "Swisscom Root CA 1" @@ -2134,6 +2617,51 @@ t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- +# Issuer: CN=AC Raíz Certicámara S.A. O=Sociedad Cameral de Certificación Digital - Certicámara S.A. +# Subject: CN=AC Raíz Certicámara S.A. O=Sociedad Cameral de Certificación Digital - Certicámara S.A. +# Label: "AC Ra\xC3\xADz Certic\xC3\xA1mara S.A." +# Serial: 38908203973182606954752843738508300 +# MD5 Fingerprint: 93:2a:3e:f6:fd:23:69:0d:71:20:d4:2b:47:99:2b:a6 +# SHA1 Fingerprint: cb:a1:c5:f8:b0:e3:5e:b8:b9:45:12:d3:f9:34:a2:e9:06:10:d3:36 +# SHA256 Fingerprint: a6:c5:1e:0d:a5:ca:0a:93:09:d2:e4:c0:e4:0c:2a:f9:10:7a:ae:82:03:85:7f:e1:98:e3:e7:69:e3:43:08:5c +-----BEGIN CERTIFICATE----- +MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsx +CzAJBgNVBAYTAkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRp +ZmljYWNpw7NuIERpZ2l0YWwgLSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwa +QUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4wHhcNMDYxMTI3MjA0NjI5WhcNMzAw +NDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+U29jaWVkYWQgQ2Ft +ZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJhIFMu +QS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkq +hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeG +qentLhM0R7LQcNzJPNCNyu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzL +fDe3fezTf3MZsGqy2IiKLUV0qPezuMDU2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQ +Y5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU34ojC2I+GdV75LaeHM/J4 +Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP2yYe68yQ +54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+b +MMCm8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48j +ilSH5L887uvDdUhfHjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++Ej +YfDIJss2yKHzMI+ko6Kh3VOz3vCaMh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/zt +A/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK5lw1omdMEWux+IBkAC1vImHF +rEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1bczwmPS9KvqfJ +pxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE +AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCB +lTCBkgYEVR0gADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFy +YS5jb20vZHBjLzBaBggrBgEFBQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW50 +7WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2UgcHVlZGVuIGVuY29udHJhciBlbiBs +YSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEfAygPU3zmpFmps4p6 +xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuXEpBc +unvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/ +Jre7Ir5v/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dp +ezy4ydV/NgIlqmjCMRW3MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42 +gzmRkBDI8ck1fj+404HGIGQatlDCIaR43NAvO2STdPCWkPHv+wlaNECW8DYSwaN0 +jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wkeZBWN7PGKX6jD/EpOe9+ +XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f/RWmnkJD +W2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/ +RL5hRqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35r +MDOhYil/SrnhLecUIw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxk +BYn8eNZcLCZDqQ== +-----END CERTIFICATE----- + # Issuer: CN=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA # Subject: CN=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA # Label: "TC TrustCenter Class 2 CA II" @@ -3007,6 +3535,28 @@ r0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdK Z05phkOTOPu220+DkdRgfks+KzgHVZhepA== -----END CERTIFICATE----- +# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority +# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority +# Label: "Verisign Class 3 Public Primary Certification Authority" +# Serial: 80507572722862485515306429940691309246 +# MD5 Fingerprint: ef:5a:f1:33:ef:f1:cd:bb:51:02:ee:12:14:4b:96:c4 +# SHA1 Fingerprint: a1:db:63:93:91:6f:17:e4:18:55:09:40:04:15:c7:02:40:b0:ae:6b +# SHA256 Fingerprint: a4:b6:b3:99:6f:c2:f3:06:b3:fd:86:81:bd:63:41:3d:8c:50:09:cc:4f:a3:29:c2:cc:f0:e2:fa:1b:14:03:05 +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE +BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is +I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G +CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i +2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ +2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ +-----END CERTIFICATE----- + # Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. # Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. # Label: "Microsec e-Szigno Root CA 2009" @@ -4474,638 +5024,3 @@ wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy KwbQBM0= -----END CERTIFICATE----- - -# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera -# Label: "TeliaSonera Root CA v1" -# Serial: 199041966741090107964904287217786801558 -# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c -# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 -# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 ------BEGIN CERTIFICATE----- -MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw -NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv -b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD -VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 -MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F -VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 -7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X -Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ -/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs -81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm -dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe -Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu -sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 -pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs -slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ -arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD -VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG -9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl -dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx -0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj -TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed -Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 -Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI -OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 -vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW -t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn -HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx -SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= ------END CERTIFICATE----- - -# Issuer: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi -# Subject: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi -# Label: "E-Tugra Certification Authority" -# Serial: 7667447206703254355 -# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49 -# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39 -# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c ------BEGIN CERTIFICATE----- -MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV -BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC -aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV -BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1 -Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz -MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+ -BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp -em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN -ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 -MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY -B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH -D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF -Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo -q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D -k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH -fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut -dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM -ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8 -zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn -rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX -U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6 -Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5 -XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF -Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR -HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY -GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c -77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3 -+GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK -vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6 -FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl -yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P -AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD -y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d -NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== ------END CERTIFICATE----- - -# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center -# Label: "T-TeleSec GlobalRoot Class 2" -# Serial: 1 -# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a -# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 -# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 ------BEGIN CERTIFICATE----- -MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx -KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd -BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl -YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 -OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy -aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 -ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G -CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd -AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC -FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi -1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq -jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ -wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj -QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ -WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy -NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC -uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw -IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 -g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN -9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP -BSeOE6Fuwg== ------END CERTIFICATE----- - -# Issuer: CN=Atos TrustedRoot 2011 O=Atos -# Subject: CN=Atos TrustedRoot 2011 O=Atos -# Label: "Atos TrustedRoot 2011" -# Serial: 6643877497813316402 -# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 -# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 -# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 ------BEGIN CERTIFICATE----- -MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE -AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG -EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM -FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC -REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp -Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM -VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ -SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ -4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L -cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi -eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV -HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG -A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 -DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j -vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP -DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc -maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D -lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv -KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 1 G3" -# Serial: 687049649626669250736271037606554624078720034195 -# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab -# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 -# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL -BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc -BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 -MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM -aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV -wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe -rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 -68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh -4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp -UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o -abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc -3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G -KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt -hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO -Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt -zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD -ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC -MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 -cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN -qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 -YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv -b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 -8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k -NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj -ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp -q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt -nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 2 G3" -# Serial: 390156079458959257446133169266079962026824725800 -# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 -# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 -# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL -BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc -BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 -MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM -aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf -qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW -n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym -c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ -O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 -o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j -IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq -IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz -8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh -vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l -7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG -cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD -ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 -AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC -roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga -W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n -lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE -+V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV -csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd -dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg -KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM -HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 -WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M ------END CERTIFICATE----- - -# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited -# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited -# Label: "QuoVadis Root CA 3 G3" -# Serial: 268090761170461462463995952157327242137089239581 -# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 -# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d -# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 ------BEGIN CERTIFICATE----- -MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL -BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc -BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 -MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM -aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR -/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu -FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR -U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c -ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR -FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k -A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw -eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl -sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp -VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q -A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ -ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD -ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px -KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI -FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv -oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg -u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP -0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf -3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl -8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ -DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN -PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ -ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root G2" -# Serial: 15385348160840213938643033620894905419 -# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d -# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f -# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 ------BEGIN CERTIFICATE----- -MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv -b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG -EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl -cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi -MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA -n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc -biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp -EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA -bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu -YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB -AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW -BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI -QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I -0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni -lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 -B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv -ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo -IhNzbM8m9Yop5w== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Assured ID Root G3" -# Serial: 15459312981008553731928384953135426796 -# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb -# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 -# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 ------BEGIN CERTIFICATE----- -MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw -CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu -ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg -RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV -UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu -Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq -hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf -Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q -RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ -BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD -AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY -JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv -6pZjamVFkpUBtA== ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root G2" -# Serial: 4293743540046975378534879503202253541 -# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 -# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 -# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f ------BEGIN CERTIFICATE----- -MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH -MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT -MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j -b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG -9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI -2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx -1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ -q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz -tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ -vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP -BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV -5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY -1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 -NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG -Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 -8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe -pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl -MrY= ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Global Root G3" -# Serial: 7089244469030293291760083333884364146 -# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca -# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e -# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 ------BEGIN CERTIFICATE----- -MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw -CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu -ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe -Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw -EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x -IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF -K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG -fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO -Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd -BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx -AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ -oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 -sycX ------END CERTIFICATE----- - -# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com -# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com -# Label: "DigiCert Trusted Root G4" -# Serial: 7451500558977370777930084869016614236 -# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 -# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 -# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 ------BEGIN CERTIFICATE----- -MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi -MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 -d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg -RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV -UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu -Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG -SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y -ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If -xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV -ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO -DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ -jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ -CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi -EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM -fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY -uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK -chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t -9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB -hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD -ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 -SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd -+SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc -fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa -sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N -cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N -0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie -4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI -r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 -/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm -gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ ------END CERTIFICATE----- - -# Issuer: CN=Certification Authority of WoSign O=WoSign CA Limited -# Subject: CN=Certification Authority of WoSign O=WoSign CA Limited -# Label: "WoSign" -# Serial: 125491772294754854453622855443212256657 -# MD5 Fingerprint: a1:f2:f9:b5:d2:c8:7a:74:b8:f3:05:f1:d7:e1:84:8d -# SHA1 Fingerprint: b9:42:94:bf:91:ea:8f:b6:4b:e6:10:97:c7:fb:00:13:59:b6:76:cb -# SHA256 Fingerprint: 4b:22:d5:a6:ae:c9:9f:3c:db:79:aa:5e:c0:68:38:47:9c:d5:ec:ba:71:64:f7:f2:2d:c1:d6:5f:63:d8:57:08 ------BEGIN CERTIFICATE----- -MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBV -MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNV -BAMTIUNlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgw -MTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFX -b1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNhdGlvbiBBdXRob3Jp -dHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvcqN -rLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1U -fcIiePyOCbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcScc -f+Hb0v1naMQFXQoOXXDX2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2 -ZjC1vt7tj/id07sBMOby8w7gLJKA84X5KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4M -x1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR+ScPewavVIMYe+HdVHpR -aG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ezEC8wQjch -zDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDar -uHqklWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221K -mYo0SLwX3OSACCK28jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvA -Sh0JWzko/amrzgD5LkhLJuYwTKVYyrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWv -HYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0CAwEAAaNCMEAwDgYDVR0PAQH/ -BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R8bNLtwYgFP6H -EtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1 -LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJ -MuYhOZO9sxXqT2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2e -JXLOC62qx1ViC777Y7NhRCOjy+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VN -g64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC2nz4SNAzqfkHx5Xh9T71XXG68pWp -dIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes5cVAWubXbHssw1ab -R80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/EaEQ -PkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGce -xGATVdVhmVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+ -J7x6v+Db9NpSvd4MVHAxkUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMl -OtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGikpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWT -ee5Ehr7XHuQe+w== ------END CERTIFICATE----- - -# Issuer: CN=CA 沃通根证书 O=WoSign CA Limited -# Subject: CN=CA 沃通根证书 O=WoSign CA Limited -# Label: "WoSign China" -# Serial: 106921963437422998931660691310149453965 -# MD5 Fingerprint: 78:83:5b:52:16:76:c4:24:3b:83:78:e8:ac:da:9a:93 -# SHA1 Fingerprint: 16:32:47:8d:89:f9:21:3a:92:00:85:63:f5:a4:a7:d3:12:40:8a:d6 -# SHA256 Fingerprint: d6:f0:34:bd:94:aa:23:3f:02:97:ec:a4:24:5b:28:39:73:e4:47:aa:59:0f:31:0c:77:f4:8f:df:83:11:22:54 ------BEGIN CERTIFICATE----- -MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBG -MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNV -BAMMEkNBIOayg+mAmuagueivgeS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgw -MTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRl -ZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjANBgkqhkiG9w0BAQEF -AAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k8H/r -D195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld1 -9AXbbQs5uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExf -v5RxadmWPgxDT74wwJ85dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnk -UkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+L -NVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFyb7Ao65vh4YOhn0pdr8yb -+gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc76DbT52V -qyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6K -yX2m+Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0G -AbQOXDBGVWCvOGU6yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaK -J/kR8slC/k7e3x9cxKSGhxYzoacXGKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwEC -AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O -BBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUAA4ICAQBqinA4 -WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6 -yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj -/feTZU7n85iYr83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6 -jBAyvd0zaziGfjk9DgNyp115j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2 -ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0AkLppRQjbbpCBhqcqBT/mhDn4t/lX -X0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97qA4bLJyuQHCH2u2n -FoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Yjj4D -u9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10l -O1Hm13ZBONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Le -ie2uPAmvylezkolwQOQvT8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR1 -2KvxAmLBsX5VYc8T1yaw15zLKYs4SgsOkI26oQ== ------END CERTIFICATE----- - -# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited -# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited -# Label: "COMODO RSA Certification Authority" -# Serial: 101909084537582093308941363524873193117 -# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 -# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 -# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 ------BEGIN CERTIFICATE----- -MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB -hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G -A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV -BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 -MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT -EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR -Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh -dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR -6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X -pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC -9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV -/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf -Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z -+pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w -qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah -SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC -u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf -Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq -crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E -FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB -/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl -wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM -4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV -2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna -FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ -CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK -boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke -jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL -S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb -QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl -0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB -NVOFBkpdn627G190 ------END CERTIFICATE----- - -# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network -# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network -# Label: "USERTrust RSA Certification Authority" -# Serial: 2645093764781058787591871645665788717 -# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 -# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e -# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 ------BEGIN CERTIFICATE----- -MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB -iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl -cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV -BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw -MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV -BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU -aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy -dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK -AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B -3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY -tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ -Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 -VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT -79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 -c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT -Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l -c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee -UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE -Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd -BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G -A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF -Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO -VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 -ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs -8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR -iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze -Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ -XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ -qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB -VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB -L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG -jjxDah2nGN59PRbxYvnKkKj9 ------END CERTIFICATE----- - -# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network -# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network -# Label: "USERTrust ECC Certification Authority" -# Serial: 123013823720199481456569720443997572134 -# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 -# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 -# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a ------BEGIN CERTIFICATE----- -MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL -MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl -eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT -JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx -MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT -Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg -VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm -aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo -I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng -o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G -A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD -VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB -zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW -RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 -# Label: "GlobalSign ECC Root CA - R4" -# Serial: 14367148294922964480859022125800977897474 -# MD5 Fingerprint: 20:f0:27:68:d1:7e:a0:9d:0e:e6:2a:ca:df:5c:89:8e -# SHA1 Fingerprint: 69:69:56:2e:40:80:f4:24:a1:e7:19:9f:14:ba:f3:ee:58:ab:6a:bb -# SHA256 Fingerprint: be:c9:49:11:c2:95:56:76:db:6c:0a:55:09:86:d7:6e:3b:a0:05:66:7c:44:2c:97:62:b4:fb:b7:73:de:22:8c ------BEGIN CERTIFICATE----- -MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk -MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH -bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX -DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD -QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu -MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ -FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw -DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F -uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX -kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs -ewv4n4Q= ------END CERTIFICATE----- - -# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 -# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 -# Label: "GlobalSign ECC Root CA - R5" -# Serial: 32785792099990507226680698011560947931244 -# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 -# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa -# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 ------BEGIN CERTIFICATE----- -MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk -MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH -bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX -DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD -QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu -MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc -8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke -hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD -VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI -KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg -515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO -xwy8p2Fp8fc74SrL+SvzZpA3 ------END CERTIFICATE----- From 628474d62666e6691ec35677a918f5e97c35cda2 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 24 Feb 2015 10:30:17 -0600 Subject: [PATCH 325/545] Update HISTORY and release version for 2.5.3 --- HISTORY.rst | 8 ++++++++ requests/__init__.py | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 269b725a..1fc4f3bc 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,14 @@ Release History --------------- +2.5.3 (2015-02-24) +++++++++++++++++++ + +**Bugfixes** + +- Revert changes to our vendored certificate bundle. For more context see + (#2455, #2456, and http://bugs.python.org/issue23476) + 2.5.2 (2015-02-23) ++++++++++++++++++ diff --git a/requests/__init__.py b/requests/__init__.py index 70b50ae1..b90d792e 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.5.2' -__build__ = 0x020502 +__version__ = '2.5.3' +__build__ = 0x020503 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2015 Kenneth Reitz' From 6ea3f2ada87177559188bfc9148af1b2384f38c6 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 28 Feb 2015 20:13:45 -0600 Subject: [PATCH 326/545] Only alias the imports for vendored dependencies While discussion the issue, Donald Stufft (@dstufft) and I realized the simplest solution is to simply add an alias per vendored dependency. The resulting changes are simple and effective. It prevents the issue in 2.5.2 and 2.5.3 where the following would work: from requests.packages import webbrowser This now appropriately raises an ImportError. Closes #2465 --- requests/packages/__init__.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/requests/packages/__init__.py b/requests/packages/__init__.py index ec6a9e06..7b2a0913 100644 --- a/requests/packages/__init__.py +++ b/requests/packages/__init__.py @@ -27,9 +27,10 @@ import sys class VendorAlias(object): - def __init__(self): + def __init__(self, package_name): + self._package_name = package_name self._vendor_name = __name__ - self._vendor_pkg = self._vendor_name + "." + self._vendor_pkg = self._vendor_name + "." + self._package_name def find_module(self, fullname, path=None): if fullname.startswith(self._vendor_pkg): @@ -92,4 +93,4 @@ class VendorAlias(object): return module -sys.meta_path.append(VendorAlias()) +sys.meta_path.extend([VendorAlias("urllib3"), VendorAlias("chardet")]) From 9d8a57f2a29de97682d85d71ea98354ac8223442 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 28 Feb 2015 20:19:13 -0600 Subject: [PATCH 327/545] Add a test to ensure the alias does not regress --- test_requests.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test_requests.py b/test_requests.py index 6e527848..57f39a62 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1598,5 +1598,12 @@ def test_urllib3_retries(): with pytest.raises(RetryError): s.get(httpbin('status/500')) +def test_vendor_aliases(): + from requests.packages import urllib3 + from requests.packages import chardet + + with pytest.raises(ImportError): + from requests.packages import webbrowser + if __name__ == '__main__': unittest.main() From 2669f0c0b1e98c9ea36d2614d48b07ad150ea276 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 28 Feb 2015 23:04:22 -0600 Subject: [PATCH 328/545] Fix when packages are unvendored When working these changes back upstream to pip, we realized that the previous fix wasn't ideal since unvendoring the packages broke the imports. For example, if urllib3 were unvendored, then the following would fail: from requests.packages import urllib3 --- requests/packages/__init__.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/requests/packages/__init__.py b/requests/packages/__init__.py index 7b2a0913..4dcf870f 100644 --- a/requests/packages/__init__.py +++ b/requests/packages/__init__.py @@ -27,10 +27,13 @@ import sys class VendorAlias(object): - def __init__(self, package_name): - self._package_name = package_name + def __init__(self, package_names): + self._package_names = package_names self._vendor_name = __name__ - self._vendor_pkg = self._vendor_name + "." + self._package_name + self._vendor_pkg = self._vendor_name + "." + self._vendor_pkgs = [ + self._vendor_pkg + name for name in self._package_names + ] def find_module(self, fullname, path=None): if fullname.startswith(self._vendor_pkg): @@ -45,6 +48,14 @@ class VendorAlias(object): ) ) + if not (name == self._vendor_name or + any(name.startswith(pkg) for pkg in self._vendor_pkgs)): + raise ImportError( + "Cannot import %s, must be one of %s." % ( + name, self._vendor_pkgs + ) + ) + # Check to see if we already have this item in sys.modules, if we do # then simply return that. if name in sys.modules: @@ -93,4 +104,4 @@ class VendorAlias(object): return module -sys.meta_path.extend([VendorAlias("urllib3"), VendorAlias("chardet")]) +sys.meta_path.append(VendorAlias(["urllib3", "chardet"])) From 11b12c3e07c542f9027b3cd3fac0d59573d28c2b Mon Sep 17 00:00:00 2001 From: "Rasmus Scholer (TimelineX)" Date: Tue, 3 Mar 2015 02:44:52 +0100 Subject: [PATCH 329/545] models.RequestEncodingMixin._encode_files now takes bytearray as fp in addition to the currently supported bytes and str input. --- requests/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index b728c84e..4b2648f6 100644 --- a/requests/models.py +++ b/requests/models.py @@ -145,7 +145,7 @@ class RequestEncodingMixin(object): fp = v if isinstance(fp, str): fp = StringIO(fp) - if isinstance(fp, bytes): + if isinstance(fp, (bytes, bytearray)): fp = BytesIO(fp) rf = RequestField(name=k, data=fp.read(), From 326f77d57c3fdb45bf1d263a8b4ecef781f39850 Mon Sep 17 00:00:00 2001 From: "Rasmus Scholer (TimelineX)" Date: Tue, 3 Mar 2015 03:41:00 +0100 Subject: [PATCH 330/545] Created test case for using bytes and bytearray objects with files argument to Request. --- test_requests.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test_requests.py b/test_requests.py index 6e527848..4f38b042 100755 --- a/test_requests.py +++ b/test_requests.py @@ -935,6 +935,19 @@ class RequestsTestCase(unittest.TestCase): assert 'multipart/form-data' in p.headers['Content-Type'] + def test_can_send_bytes_bytearray_objects_with_files(self): + # Test bytes: + data = {'a': 0.0} + files = {'b': b'foo'} + r = requests.Request('POST', httpbin('post'), data=data, files=files) + p = r.prepare() + assert 'multipart/form-data' in p.headers['Content-Type'] + # Test bytearrays: + files = {'b': bytearray(b'foo')} + r = requests.Request('POST', httpbin('post'), data=data, files=files) + p = r.prepare() + assert 'multipart/form-data' in p.headers['Content-Type'] + def test_can_send_file_object_with_non_string_filename(self): f = io.BytesIO() f.name = 2 From 1887f32b17326fdaced7b57da349dc5ad7873435 Mon Sep 17 00:00:00 2001 From: "Rasmus Scholer (TimelineX)" Date: Tue, 3 Mar 2015 14:37:39 -0500 Subject: [PATCH 331/545] test_can_send_bytes_bytearray_objects_with_files test now uses string input, not float. --- test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index 4f38b042..69a10616 100755 --- a/test_requests.py +++ b/test_requests.py @@ -937,7 +937,7 @@ class RequestsTestCase(unittest.TestCase): def test_can_send_bytes_bytearray_objects_with_files(self): # Test bytes: - data = {'a': 0.0} + data = {'a': 'this is a string'} files = {'b': b'foo'} r = requests.Request('POST', httpbin('post'), data=data, files=files) p = r.prepare() From e7ac8ea0c106be330912c2d043756a714d115e4a Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 4 Mar 2015 08:11:39 -0600 Subject: [PATCH 332/545] Add PGP Key fingerprints --- docs/community/support.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/community/support.rst b/docs/community/support.rst index d73f7022..babecb8a 100644 --- a/docs/community/support.rst +++ b/docs/community/support.rst @@ -28,6 +28,12 @@ If you think you have found a potential security vulnerability in requests, please email `sigmavirus24 `_ and `Lukasa `_ directly. **Do not file a public issue.** +Our PGP Key fingerprints are: + +- 0161 BB7E B208 B5E0 4FDC 9F81 D9DA 0A04 9113 F853 (@sigmavirus24) + +- (@lukasa) + If English is not your first language, please try to describe the problem and its impact to the best of your ability. For greater detail, please use your native language and we will try our best to translate it using online services. From ec700a9a1338975cbd340001091aa2c6d98c8988 Mon Sep 17 00:00:00 2001 From: "Rasmus Scholer (TimelineX)" Date: Wed, 4 Mar 2015 10:21:38 -0500 Subject: [PATCH 333/545] Avoid duplicating files data when creating a Request with str/bytes/bytearray in the files argument. 30-40% performance improvement. --- requests/models.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/requests/models.py b/requests/models.py index 4b2648f6..317d0ee4 100644 --- a/requests/models.py +++ b/requests/models.py @@ -143,12 +143,13 @@ class RequestEncodingMixin(object): else: fn = guess_filename(v) or k fp = v - if isinstance(fp, str): - fp = StringIO(fp) - if isinstance(fp, (bytes, bytearray)): - fp = BytesIO(fp) - rf = RequestField(name=k, data=fp.read(), + if isinstance(fp, (str, bytes, bytearray)): + fdata = fp + else: + fdata = fp.read() + + rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) rf.make_multipart(content_type=ft) new_fields.append(rf) From bcfcd9f980440e7bb3f38d57c1332009b8d31642 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 4 Mar 2015 20:16:25 +0000 Subject: [PATCH 334/545] Add Lukasa's GPG key fingerprint. --- docs/community/support.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/community/support.rst b/docs/community/support.rst index babecb8a..96f600a1 100644 --- a/docs/community/support.rst +++ b/docs/community/support.rst @@ -32,7 +32,7 @@ Our PGP Key fingerprints are: - 0161 BB7E B208 B5E0 4FDC 9F81 D9DA 0A04 9113 F853 (@sigmavirus24) -- (@lukasa) +- 90DC AE40 FEA7 4B14 9B70 662D F25F 2144 EEC1 373D (@lukasa) If English is not your first language, please try to describe the problem and its impact to the best of your ability. For greater detail, please use your native From 62200cb07e76b0b6189bc64d1ca35d43514d0566 Mon Sep 17 00:00:00 2001 From: Priit Laes Date: Sat, 7 Mar 2015 09:40:25 +0200 Subject: [PATCH 335/545] Add testcase to demonstrate r.iter_lines() reentrancy issue --- test_requests.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test_requests.py b/test_requests.py index 07430a8e..15406a22 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1052,6 +1052,23 @@ class RequestsTestCase(unittest.TestCase): assert 'application/json' in r.request.headers['Content-Type'] assert {'life': 42} == r.json()['json'] + def test_response_iter_lines(self): + r = requests.get(httpbin('stream/4'), stream=True) + assert r.status_code == 200 + + it = r.iter_lines() + next(it) + assert len(list(it)) == 3 + + @pytest.mark.xfail + def test_response_iter_lines_reentrant(self): + """Response.iter_lines() is not reentrant safe""" + r = requests.get(httpbin('stream/4'), stream=True) + assert r.status_code == 200 + + next(r.iter_lines()) + assert len(list(r.iter_lines())) == 3 + class TestContentEncodingDetection(unittest.TestCase): From 4497a26c376c60360d05644b574be0db3c887fe3 Mon Sep 17 00:00:00 2001 From: Priit Laes Date: Sat, 7 Mar 2015 11:04:43 +0200 Subject: [PATCH 336/545] Document Response.iter_lines() reentrancy issues --- docs/user/advanced.rst | 13 +++++++++++++ requests/models.py | 2 ++ 2 files changed, 15 insertions(+) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index ed990666..a2fe653b 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -399,6 +399,19 @@ set ``stream`` to ``True`` and iterate over the response with if line: print(json.loads(line)) +.. warning:: + + :class:`~requests.Response.iter_lines()` is not reentrant safe. + Calling this method multiple times causes some of the received data + being lost. In case you need to call it from multiple places, use + the resulting iterator object instead:: + + lines = r.iter_lines() + # Save the first line for later or just skip it + first_line = next(lines) + for line in lines: + print(line) + .. _proxies: Proxies diff --git a/requests/models.py b/requests/models.py index 4b2648f6..ce1eb40e 100644 --- a/requests/models.py +++ b/requests/models.py @@ -688,6 +688,8 @@ class Response(object): """Iterates over the response data, one line at a time. When stream=True is set on the request, this avoids reading the content at once into memory for large responses. + + .. note:: This method is not reentrant safe. """ pending = None From a0790f37b762ec737b2a4a13c167aad2defa8575 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 10 Mar 2015 19:34:34 -0500 Subject: [PATCH 337/545] Update urllib3 to 43b5b2b452e4344374de7d08ececcca495079b8d --- requests/packages/urllib3/__init__.py | 2 +- requests/packages/urllib3/_collections.py | 27 ++++++++++++----------- requests/packages/urllib3/exceptions.py | 5 +++++ requests/packages/urllib3/util/ssl_.py | 11 ++++++++- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/requests/packages/urllib3/__init__.py b/requests/packages/urllib3/__init__.py index d7592ae7..0660b9c8 100644 --- a/requests/packages/urllib3/__init__.py +++ b/requests/packages/urllib3/__init__.py @@ -4,7 +4,7 @@ urllib3 - Thread-safe connection pooling and re-using. __author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' __license__ = 'MIT' -__version__ = 'dev' +__version__ = '1.10.2' from .connectionpool import ( diff --git a/requests/packages/urllib3/_collections.py b/requests/packages/urllib3/_collections.py index 06412ddf..cc424de0 100644 --- a/requests/packages/urllib3/_collections.py +++ b/requests/packages/urllib3/_collections.py @@ -20,8 +20,6 @@ from .packages.six import iterkeys, itervalues, PY3 __all__ = ['RecentlyUsedContainer', 'HTTPHeaderDict'] -MULTIPLE_HEADERS_ALLOWED = frozenset(['cookie', 'set-cookie', 'set-cookie2']) - _Null = object() @@ -143,7 +141,10 @@ class HTTPHeaderDict(dict): def __init__(self, headers=None, **kwargs): dict.__init__(self) if headers is not None: - self.extend(headers) + if isinstance(headers, HTTPHeaderDict): + self._copy_from(headers) + else: + self.extend(headers) if kwargs: self.extend(kwargs) @@ -223,11 +224,8 @@ class HTTPHeaderDict(dict): vals.append(val) else: # vals should be a tuple then, i.e. only one item so far - if key_lower in MULTIPLE_HEADERS_ALLOWED: - # Need to convert the tuple to list for further extension - _dict_setitem(self, key_lower, [vals[0], vals[1], val]) - else: - _dict_setitem(self, key_lower, new_vals) + # Need to convert the tuple to list for further extension + _dict_setitem(self, key_lower, [vals[0], vals[1], val]) def extend(*args, **kwargs): """Generic import function for any type of header-like object. @@ -276,14 +274,17 @@ class HTTPHeaderDict(dict): def __repr__(self): return "%s(%s)" % (type(self).__name__, dict(self.itermerged())) - def copy(self): - clone = type(self)() - for key in self: - val = _dict_getitem(self, key) + def _copy_from(self, other): + for key in other: + val = _dict_getitem(other, key) if isinstance(val, list): # Don't need to convert tuples val = list(val) - _dict_setitem(clone, key, val) + _dict_setitem(self, key, val) + + def copy(self): + clone = type(self)() + clone._copy_from(self) return clone def iteritems(self): diff --git a/requests/packages/urllib3/exceptions.py b/requests/packages/urllib3/exceptions.py index 0c6fd3c5..5d523011 100644 --- a/requests/packages/urllib3/exceptions.py +++ b/requests/packages/urllib3/exceptions.py @@ -157,3 +157,8 @@ class InsecureRequestWarning(SecurityWarning): class SystemTimeWarning(SecurityWarning): "Warned when system time is suspected to be wrong" pass + + +class InsecurePlatformWarning(SecurityWarning): + "Warned when certain SSL configuration is not available on a platform." + pass diff --git a/requests/packages/urllib3/util/ssl_.py b/requests/packages/urllib3/util/ssl_.py index ee1d3223..e7e7dfae 100644 --- a/requests/packages/urllib3/util/ssl_.py +++ b/requests/packages/urllib3/util/ssl_.py @@ -1,7 +1,7 @@ from binascii import hexlify, unhexlify from hashlib import md5, sha1, sha256 -from ..exceptions import SSLError +from ..exceptions import SSLError, InsecurePlatformWarning SSLContext = None @@ -10,6 +10,7 @@ create_default_context = None import errno import ssl +import warnings try: # Test for SSL features from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 @@ -69,6 +70,14 @@ except ImportError: self.ciphers = cipher_suite def wrap_socket(self, socket, server_hostname=None): + warnings.warn( + 'A true SSLContext object is not available. This prevents ' + 'urllib3 from configuring SSL appropriately and may cause ' + 'certain SSL connections to fail. For more information, see ' + 'https://urllib3.readthedocs.org/en/latest/security.html' + '#insecureplatformwarning.', + InsecurePlatformWarning + ) kwargs = { 'keyfile': self.keyfile, 'certfile': self.certfile, From c4bd6ea1501ee24a7e45d3e888f11d2a52e41469 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 11 Mar 2015 20:52:12 -0500 Subject: [PATCH 338/545] Import urllib3's Retry location from the right place Importing from urllib3's top-level location causes the namespace to be urllib3.util.retry.Retry instead of requests.packages.urllib3.util.retry.Retry. Without this fix, an using requests with an un-vendored version of urllib3 will break when urllib3's retry handling kicks in. Closes shazow/urllib3#567 --- requests/adapters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/adapters.py b/requests/adapters.py index c892853b..02e0dd1f 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -11,10 +11,10 @@ and maintain connections. import socket from .models import Response -from .packages.urllib3 import Retry from .packages.urllib3.poolmanager import PoolManager, proxy_from_url from .packages.urllib3.response import HTTPResponse from .packages.urllib3.util import Timeout as TimeoutSauce +from .packages.urllib3.util.retry import Retry from .compat import urlparse, basestring from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers, prepend_scheme_if_needed, get_auth_from_url, urldefragauth) From 1acc4992d42950b54a7be1b0dcacdaac35d32436 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 13 Mar 2015 21:47:29 -0500 Subject: [PATCH 339/545] Parse version number out of requests/__init__.py With the new import machinery in requests/packages/__init__.py people using `python setup.py test` will not be able to run the tests the first time on a new environment. The VendorAlias logic breaks the import machinery and the tests fail. By removing the import of requests in setup.py, we avoid the issue where sys.meta_path continues to have the version of VendorAlias from the import in setup.py. Closes #2462 --- setup.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/setup.py b/setup.py index 813fc879..4637291f 100755 --- a/setup.py +++ b/setup.py @@ -1,10 +1,9 @@ #!/usr/bin/env python import os +import re import sys -import requests - from codecs import open try: @@ -29,6 +28,18 @@ packages = [ requires = [] +version = '' +with open('requests/__init__.py', 'r') as fd: + reg = re.compile(r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]') + for line in fd: + m = reg.match(line) + if m: + version = m.group(1) + break + +if not version: + raise RuntimeError('Cannot find version information') + with open('README.rst', 'r', 'utf-8') as f: readme = f.read() with open('HISTORY.rst', 'r', 'utf-8') as f: @@ -36,7 +47,7 @@ with open('HISTORY.rst', 'r', 'utf-8') as f: setup( name='requests', - version=requests.__version__, + version=version, description='Python HTTP for Humans.', long_description=readme + '\n\n' + history, author='Kenneth Reitz', @@ -60,7 +71,6 @@ setup( 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4' - ), extras_require={ 'security': ['pyOpenSSL', 'ndg-httpsclient', 'pyasn1'], From 3bd8afbff29e50b38f889b2f688785a669b9aafc Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 14 Mar 2015 08:49:55 +0000 Subject: [PATCH 340/545] Don't ascribe cookies to the target domain. --- requests/sessions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/sessions.py b/requests/sessions.py index 4f306963..9d5498c6 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -171,7 +171,7 @@ class SessionRedirectMixin(object): except KeyError: pass - extract_cookies_to_jar(prepared_request._cookies, prepared_request, resp.raw) + extract_cookies_to_jar(prepared_request._cookies, req, resp.raw) prepared_request._cookies.update(self.cookies) prepared_request.prepare_cookies(prepared_request._cookies) From 573d460aa7f7883951adc1fe9f05a89130d84b09 Mon Sep 17 00:00:00 2001 From: Ulrich Petri Date: Thu, 12 Mar 2015 13:54:29 +0100 Subject: [PATCH 341/545] Add return type field to entry-point docstrings Fixes: #2483 --- AUTHORS.rst | 1 + requests/api.py | 31 +++++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 3f2a4d30..6ced9b5b 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -159,3 +159,4 @@ Patches and Suggestions - Syed Suhail Ahmed (`@syedsuhail `_) - Scott Sadler (`@ssadler `_) - Arthur Darcet (`@arthurdarcet `_) +- Ulrich Petri (`@ulope `_) diff --git a/requests/api.py b/requests/api.py index 1469b05c..98c92298 100644 --- a/requests/api.py +++ b/requests/api.py @@ -16,7 +16,6 @@ from . import sessions def request(method, url, **kwargs): """Constructs and sends a :class:`Request `. - Returns :class:`Response ` object. :param method: method for the new :class:`Request` object. :param url: URL for the new :class:`Request` object. @@ -37,6 +36,8 @@ def request(method, url, **kwargs): :param verify: (optional) if ``True``, the SSL cert will be verified. A CA_BUNDLE path can also be provided. :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. + :return: :class:`Response ` object + :rtype: requests.Response Usage:: @@ -55,10 +56,12 @@ def request(method, url, **kwargs): def get(url, **kwargs): - """Sends a GET request. Returns :class:`Response` object. + """Sends a GET request. :param url: URL for the new :class:`Request` object. :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response """ kwargs.setdefault('allow_redirects', True) @@ -66,10 +69,12 @@ def get(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 \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response """ kwargs.setdefault('allow_redirects', True) @@ -77,10 +82,12 @@ def options(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 \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response """ kwargs.setdefault('allow_redirects', False) @@ -88,44 +95,52 @@ def head(url, **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 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 \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response """ return request('post', url, data=data, json=json, **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 data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response """ return request('put', url, data=data, **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 data: (optional) Dictionary, bytes, or file-like object to send in the body of the :class:`Request`. :param \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response """ return request('patch', url, data=data, **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 \*\*kwargs: Optional arguments that ``request`` takes. + :return: :class:`Response ` object + :rtype: requests.Response """ return request('delete', url, **kwargs) From f7c85685a8e484715649c13bacae6adc7f5f3908 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 14 Mar 2015 11:29:05 +0000 Subject: [PATCH 342/545] Add explanatory comment for cookie change --- requests/sessions.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/requests/sessions.py b/requests/sessions.py index 9d5498c6..ef3f22bc 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -171,6 +171,9 @@ class SessionRedirectMixin(object): except KeyError: pass + # 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.prepare_cookies(prepared_request._cookies) From 80a3e87bf39f05a7b255cd757d84bb46e23c3a86 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 14 Mar 2015 11:42:38 -0500 Subject: [PATCH 343/545] Bump version and add release notes for 2.6.0 --- HISTORY.rst | 30 ++++++++++++++++++++++++++++++ requests/__init__.py | 2 +- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 1fc4f3bc..254656c5 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,36 @@ Release History --------------- +2.6.0 (2015-03-14) +++++++++++++++++++ + +**Bugfixes** + +- Fix handling of cookies on redirect. Previously a cookie without a host + value set would use the hostname for the redirected URL exposing requests + users to session fixation attacks and potentially cookie stealing. This was + disclosed privately by Matthew Daley of `BugFuzz `_. + An CVE identifier has not yet been assigned for this. This affects all + versions of requests from v2.1.0 to v2.5.3 (inclusive on both ends). + +- Fix error when requests is an ``install_requires`` dependency and ``python + setup.py test`` is run. (#2462) + +- Fix error when urllib3 is unbundled and requests continues to use the + vendored import location. + +- Include fixes to ``urllib3``'s header handling. + +- Requests' handling of unvendored dependencies is now more restrictive. + +**Features and Improvements** + +- Support bytearrays when passed as parameters in the ``files`` argument. + (#2468) + +- Avoid data duplication when creating a request with ``str``, ``bytes``, or + ``bytearray`` input to the ``files`` argument. + 2.5.3 (2015-02-24) ++++++++++++++++++ diff --git a/requests/__init__.py b/requests/__init__.py index b90d792e..446500bf 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,7 +42,7 @@ is at . """ __title__ = 'requests' -__version__ = '2.5.3' +__version__ = '2.6.0' __build__ = 0x020503 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' From 6e2f1eb402096dfce912f7ff44cae55e466c3afc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=2EYasoob=20Ullah=20Khalid=20=E2=98=BA?= Date: Sun, 15 Mar 2015 01:02:09 +0500 Subject: [PATCH 344/545] made the quickstart more reader friendly. Closes #2418 --- docs/user/quickstart.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 6fd3602e..6573b462 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -186,10 +186,9 @@ For example, we didn't specify our content-type in the previous example:: >>> import json >>> url = 'https://api.github.com/some/endpoint' - >>> payload = {'some': 'data'} - >>> headers = {'content-type': 'application/json'} + >>> headers = {'user-agent': 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36'} - >>> r = requests.post(url, data=json.dumps(payload), headers=headers) + >>> r = requests.get(url, headers=headers) More complicated POST requests From 3385c789991197d9b08da1de203615f6f40c2738 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sun, 15 Mar 2015 11:49:32 +0000 Subject: [PATCH 345/545] Update 2.6.0 changelog with CVE number. --- HISTORY.rst | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 254656c5..e56121df 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -8,12 +8,12 @@ Release History **Bugfixes** -- Fix handling of cookies on redirect. Previously a cookie without a host - value set would use the hostname for the redirected URL exposing requests - users to session fixation attacks and potentially cookie stealing. This was - disclosed privately by Matthew Daley of `BugFuzz `_. - An CVE identifier has not yet been assigned for this. This affects all - versions of requests from v2.1.0 to v2.5.3 (inclusive on both ends). +- CVE-2015-2296: Fix handling of cookies on redirect. Previously a cookie + without a host value set would use the hostname for the redirected URL + exposing requests users to session fixation attacks and potentially cookie + stealing. This was disclosed privately by Matthew Daley of + `BugFuzz `_. This affects all versions of requests from + v2.1.0 to v2.5.3 (inclusive on both ends). - Fix error when requests is an ``install_requires`` dependency and ``python setup.py test`` is run. (#2462) From 3711e04f37329b7252e13b410bc0ab04a6c4645c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=2EYasoob=20Ullah=20Khalid=20=E2=98=BA?= Date: Sun, 15 Mar 2015 22:42:43 +0500 Subject: [PATCH 346/545] edited the useragent string --- docs/user/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 6573b462..e26abaad 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -186,7 +186,7 @@ For example, we didn't specify our content-type in the previous example:: >>> import json >>> url = 'https://api.github.com/some/endpoint' - >>> headers = {'user-agent': 'Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.115 Safari/537.36'} + >>> headers = {'user-agent': 'my-app/0.0.1'} >>> r = requests.get(url, headers=headers) From dfa0a2925f2bba7dd922a23fc9237ce306ea42b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=2EYasoob=20Ullah=20Khalid=20=E2=98=BA?= Date: Tue, 17 Mar 2015 01:29:33 +0500 Subject: [PATCH 347/545] added myself to authors.rst --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 6ced9b5b..8a40bd97 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -160,3 +160,4 @@ Patches and Suggestions - Scott Sadler (`@ssadler `_) - Arthur Darcet (`@arthurdarcet `_) - Ulrich Petri (`@ulope `_) +- Muhammad Yasoob Ullah Khalid (`@yasoob `_) \ No newline at end of file From b974cd374165214bc55712b41082d8b3dba43c67 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Tue, 17 Mar 2015 20:07:28 +0000 Subject: [PATCH 348/545] Describe our new vulnerabilities process. --- docs/community/support.rst | 36 ---------- docs/community/vulnerabilities.rst | 102 +++++++++++++++++++++++++++++ docs/index.rst | 1 + 3 files changed, 103 insertions(+), 36 deletions(-) create mode 100644 docs/community/vulnerabilities.rst diff --git a/docs/community/support.rst b/docs/community/support.rst index 96f600a1..63069308 100644 --- a/docs/community/support.rst +++ b/docs/community/support.rst @@ -21,42 +21,6 @@ If your question is less than 140 characters, feel free to send a tweet to `@sigmavirus24 `_, or `@lukasaoz `_. -Vulnerability Disclosure ------------------------- - -If you think you have found a potential security vulnerability in requests, -please email `sigmavirus24 `_ and -`Lukasa `_ directly. **Do not file a public issue.** - -Our PGP Key fingerprints are: - -- 0161 BB7E B208 B5E0 4FDC 9F81 D9DA 0A04 9113 F853 (@sigmavirus24) - -- 90DC AE40 FEA7 4B14 9B70 662D F25F 2144 EEC1 373D (@lukasa) - -If English is not your first language, please try to describe the problem and -its impact to the best of your ability. For greater detail, please use your native -language and we will try our best to translate it using online services. - -Please also include the code you used to find the problem and the shortest amount -of code necessary to reproduce it. - -Please do not disclose this to anyone else. We will retrieve a CVE identifier if -necessary and give you full credit under whatever name or alias you provide. -We will only request an identifier when we have a fix and can publish it in a release. - -We will respect your privacy and will only publicize your involvement if you grant -us permission. - -Previous CVEs -~~~~~~~~~~~~~ - -- Fixed in 2.3.0 - - - `CVE 2014-1829 `_ - - - `CVE 2014-1830 `_ - File an Issue ------------- diff --git a/docs/community/vulnerabilities.rst b/docs/community/vulnerabilities.rst new file mode 100644 index 00000000..09e672ca --- /dev/null +++ b/docs/community/vulnerabilities.rst @@ -0,0 +1,102 @@ +Vulnerability Disclosure +======================== + +If you think you have found a potential security vulnerability in requests, +please email `sigmavirus24 `_ and +`Lukasa `_ directly. **Do not file a public issue.** + +Our PGP Key fingerprints are: + +- 0161 BB7E B208 B5E0 4FDC 9F81 D9DA 0A04 9113 F853 (@sigmavirus24) + +- 90DC AE40 FEA7 4B14 9B70 662D F25F 2144 EEC1 373D (@lukasa) + +If English is not your first language, please try to describe the problem and +its impact to the best of your ability. For greater detail, please use your +native language and we will try our best to translate it using online services. + +Please also include the code you used to find the problem and the shortest +amount of code necessary to reproduce it. + +Please do not disclose this to anyone else. We will retrieve a CVE identifier +if necessary and give you full credit under whatever name or alias you provide. +We will only request an identifier when we have a fix and can publish it in a +release. + +We will respect your privacy and will only publicize your involvement if you +grant us permission. + +Process +------- + +This following information discusses the process the requests project follows +in response to vulnerability disclosures. If you are disclosing a +vulnerability, this section of the documentation lets you know how we will +respond to your disclosure. + +Timeline +~~~~~~~~ + +When you report an issue, one of the project members will respond to you within +two days *at the outside*. In most cases responses will be faster, usually +within 12 hours. This initial response will confirm receipt of the report. + +If we were able to rapidly reproduce the issue, the initial response will also +contain confirmation of the issue. If we are not, we will often ask for more +information about the reproduction scenario. + +Our goal is to have a fix for any vulnerability released within two weeks of +the initial disclosure. This may potentially involve shipping an interim +release that simply disables function while a more mature fix can be prepared, +but will in the vast majority of cases mean shipping a complete release as soon +as possible. + +Throughout the fix process we will keep you up to speed with how the fix is +progressing. Once the fix is prepared, we will notify you that we believe we +have a fix. Often we will ask you to confirm the fix resolves the problem in +your environment, especially if we are not confident of our reproduction +scenario. + +At this point, we will prepare for the release. We will obtain a CVE number +if one is required, providing you with full credit for the discovery. We will +also decide on a planned release date, and let you know when it is. This +release date will *always* be on a weekday. + +At this point we will reach out to our major downstream packagers to notify +them of an impending security-related patch so they can make arrangements. +Currently the list of people we actively contact *ahead of a public release* +is: + +- Ralph Bean, Red Hat (@ralphbean) +- Daniele Tricoli, Debian (@eriol) + +We will notify these individuals at least a week ahead of our planned release +date to ensure that they have sufficient time to prepare. If you believe you +should be on this list, please let one of the maintainers know at one of the +email addresses at the top of this article. + +On release day, we will push the patch to our public repository, along with an +updated changelog that describes the issue and credits you. We will then issue +a PyPI release containing the patch. + +At this point, we will publicise the release. This will involve mails to +mailing lists, Tweets, and all other communication mechanisms available to the +core team. + +We will also explicitly mention which commits contain the fix to make it easier +for other distributors and users to easily patch their own versions of requests +if upgrading is not an option. + +Previous CVEs +------------- + +- Fixed in 2.6.0 + + - `CVE 2015-2296 `_, + reported by Matthew Daley of `BugFuzz `_. + +- Fixed in 2.3.0 + + - `CVE 2014-1829 `_ + + - `CVE 2014-1830 `_ \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index 1701e7f2..b3edef68 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -116,6 +116,7 @@ Requests ecosystem and community. community/faq community/out-there.rst community/support + community/vulnerabilities community/updates API Documentation From bedd2be0a75c355229e43cb71bac398400d81e7a Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 18 Mar 2015 21:27:20 +0000 Subject: [PATCH 349/545] Clarify that we ship patches early. --- docs/community/vulnerabilities.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/community/vulnerabilities.rst b/docs/community/vulnerabilities.rst index 09e672ca..20e5cce6 100644 --- a/docs/community/vulnerabilities.rst +++ b/docs/community/vulnerabilities.rst @@ -63,9 +63,11 @@ also decide on a planned release date, and let you know when it is. This release date will *always* be on a weekday. At this point we will reach out to our major downstream packagers to notify -them of an impending security-related patch so they can make arrangements. -Currently the list of people we actively contact *ahead of a public release* -is: +them of an impending security-related patch so they can make arrangements. In +addition, these packagers will be provided with the intended patch ahead of +time, to ensure that they are able to promptly release their downstream +packages. Currently the list of people we actively contact *ahead of a public +release* is: - Ralph Bean, Red Hat (@ralphbean) - Daniele Tricoli, Debian (@eriol) From 84cc36017a53f99e10401e8d5f050c74e8529990 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 18 Mar 2015 21:31:55 +0000 Subject: [PATCH 350/545] Expand on chunked handling. --- docs/user/advanced.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index a2fe653b..494ba696 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -268,13 +268,18 @@ Requests also supports Chunked transfer encoding for outgoing and incoming reque To send a chunk-encoded request, simply provide a generator (or any iterator without a length) for your body:: - def gen(): yield 'hi' yield 'there' requests.post('http://some.url/chunked', data=gen()) +For chunked encoded responses, it's best to iterate over the data using +:meth:`Response.iter_content() `. In +an ideal situation you'll have set ``stream=True`` on the request, in which +case you can iterate chunk-by-chunk by calling ``iter_content`` with a chunk +size parameter of ``None``. + .. _multipart: From b7e33707627ed41531008a5a0d483437c3beed28 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 18 Mar 2015 21:40:03 +0000 Subject: [PATCH 351/545] 'At least' --- docs/community/vulnerabilities.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/community/vulnerabilities.rst b/docs/community/vulnerabilities.rst index 20e5cce6..34444b2d 100644 --- a/docs/community/vulnerabilities.rst +++ b/docs/community/vulnerabilities.rst @@ -39,7 +39,8 @@ Timeline When you report an issue, one of the project members will respond to you within two days *at the outside*. In most cases responses will be faster, usually -within 12 hours. This initial response will confirm receipt of the report. +within 12 hours. This initial response will at the very least confirm receipt +of the report. If we were able to rapidly reproduce the issue, the initial response will also contain confirmation of the issue. If we are not, we will often ask for more From 54791d1970531cdb44d60e2f0abc186933f1a7ed Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 21 Mar 2015 16:27:21 +0000 Subject: [PATCH 352/545] Expand on what 'elapsed' means. --- requests/models.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 419cf0a8..cb153aa5 100644 --- a/requests/models.py +++ b/requests/models.py @@ -573,7 +573,11 @@ class Response(object): self.cookies = cookiejar_from_dict({}) #: 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) #: The :class:`PreparedRequest ` object to which this From 1b5317ea088b26e386c53e6a4f55558915467bb5 Mon Sep 17 00:00:00 2001 From: Josh Brown Date: Mon, 23 Mar 2015 16:01:48 +1100 Subject: [PATCH 353/545] Minor Patch TypeError thrown --- AUTHORS.rst | 3 ++- requests/packages/urllib3/connectionpool.py | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 8a40bd97..10f48cb5 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -160,4 +160,5 @@ Patches and Suggestions - Scott Sadler (`@ssadler `_) - Arthur Darcet (`@arthurdarcet `_) - Ulrich Petri (`@ulope `_) -- Muhammad Yasoob Ullah Khalid (`@yasoob `_) \ No newline at end of file +- Muhammad Yasoob Ullah Khalid (`@yasoob `_) +- Josh Brown (`@Montycarlo `_) diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index 0085345c..bc804629 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -311,8 +311,14 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # Catch possible read timeouts thrown as SSL errors. If not the # case, rethrow the original. We need to do this because of: # http://bugs.python.org/issue10272 - 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) + # Wrapped in a try/catch because python 2.7 throws TypeError, + # SSLError doesn't override __str__ + try: + 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) + except TypeError: + raise err + def _make_request(self, conn, method, url, timeout=_Default, **httplib_request_kw): From b2ce5be9d049a73e0adb475156435b854355f234 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 23 Mar 2015 10:11:13 -0500 Subject: [PATCH 354/545] Revert "Minor Patch TypeError thrown" --- AUTHORS.rst | 3 +-- requests/packages/urllib3/connectionpool.py | 10 ++-------- 2 files changed, 3 insertions(+), 10 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 10f48cb5..8a40bd97 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -160,5 +160,4 @@ Patches and Suggestions - Scott Sadler (`@ssadler `_) - Arthur Darcet (`@arthurdarcet `_) - Ulrich Petri (`@ulope `_) -- Muhammad Yasoob Ullah Khalid (`@yasoob `_) -- Josh Brown (`@Montycarlo `_) +- Muhammad Yasoob Ullah Khalid (`@yasoob `_) \ No newline at end of file diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index bc804629..0085345c 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -311,14 +311,8 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # Catch possible read timeouts thrown as SSL errors. If not the # case, rethrow the original. We need to do this because of: # http://bugs.python.org/issue10272 - # Wrapped in a try/catch because python 2.7 throws TypeError, - # SSLError doesn't override __str__ - try: - 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) - except TypeError: - raise err - + 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) def _make_request(self, conn, method, url, timeout=_Default, **httplib_request_kw): From ce234d53a5b8d829eca4f27857e6ea2625575fe8 Mon Sep 17 00:00:00 2001 From: deronnax Date: Wed, 25 Mar 2015 18:21:34 +0100 Subject: [PATCH 355/545] shorter and faster version extraction from __init__.py --- setup.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/setup.py b/setup.py index 4637291f..67ca2eed 100755 --- a/setup.py +++ b/setup.py @@ -30,12 +30,8 @@ requires = [] version = '' with open('requests/__init__.py', 'r') as fd: - reg = re.compile(r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]') - for line in fd: - m = reg.match(line) - if m: - version = m.group(1) - break + version = re.search(r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', + fd.read()).group(1) if not version: raise RuntimeError('Cannot find version information') From 1145fe64a54d5d1f813b96bf2923aff954267554 Mon Sep 17 00:00:00 2001 From: deronnax Date: Thu, 26 Mar 2015 15:53:19 +0100 Subject: [PATCH 356/545] matching the version only at the begining of lines --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 67ca2eed..f98f5281 100755 --- a/setup.py +++ b/setup.py @@ -30,8 +30,8 @@ requires = [] version = '' with open('requests/__init__.py', 'r') as fd: - version = re.search(r'__version__\s*=\s*[\'"]([^\'"]*)[\'"]', - fd.read()).group(1) + version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', + fd.read(), re.MULTILINE).group(1) if not version: raise RuntimeError('Cannot find version information') From 587eb193ddbc2dfc10c2bdbf1718cd2267a0e77f Mon Sep 17 00:00:00 2001 From: Paul van der Linden Date: Wed, 18 Mar 2015 11:29:18 +0000 Subject: [PATCH 357/545] fix resolve redirect to pass all original arguments --- AUTHORS.rst | 3 ++- requests/sessions.py | 14 +++----------- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 8a40bd97..28586809 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -160,4 +160,5 @@ Patches and Suggestions - Scott Sadler (`@ssadler `_) - Arthur Darcet (`@arthurdarcet `_) - Ulrich Petri (`@ulope `_) -- Muhammad Yasoob Ullah Khalid (`@yasoob `_) \ No newline at end of file +- Muhammad Yasoob Ullah Khalid (`@yasoob `_) +- Paul van der Linden (`@pvanderlinden `_) diff --git a/requests/sessions.py b/requests/sessions.py index ef3f22bc..820919ee 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -90,7 +90,7 @@ def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict): class SessionRedirectMixin(object): 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.""" i = 0 @@ -193,6 +193,7 @@ class SessionRedirectMixin(object): cert=cert, proxies=proxies, allow_redirects=False, + **adapter_kwargs ) extract_cookies_to_jar(self.cookies, prepared_request, resp.raw) @@ -560,10 +561,6 @@ class Session(SessionRedirectMixin): # Set up variables needed for resolve_redirects and dispatching of hooks allow_redirects = kwargs.pop('allow_redirects', True) stream = kwargs.get('stream') - timeout = kwargs.get('timeout') - verify = kwargs.get('verify') - cert = kwargs.get('cert') - proxies = kwargs.get('proxies') hooks = request.hooks # Get the appropriate adapter to use @@ -591,12 +588,7 @@ class Session(SessionRedirectMixin): extract_cookies_to_jar(self.cookies, request, r.raw) # Redirect resolving generator. - gen = self.resolve_redirects(r, request, - stream=stream, - timeout=timeout, - verify=verify, - cert=cert, - proxies=proxies) + gen = self.resolve_redirects(r, request, **kwargs) # Resolve redirects if allowed. history = [resp for resp in gen] if allow_redirects else [] From 0ad075c430a2f2b1d428dd50b4726dc0742d15ce Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 31 Mar 2015 12:44:50 -0400 Subject: [PATCH 358/545] Update index.rst --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index b3edef68..f013414b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -48,7 +48,7 @@ Testimonials Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku, PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington Post, Twitter, SoundCloud, Kippt, Readability, Sony, and Federal US Institutions that prefer to be unnamed -use Requests internally. It has been downloaded over 23,000,000 times from PyPI. +use Requests internally. It has been downloaded over 40,000,000 times from PyPI. **Armin Ronacher** Requests is the perfect example how beautiful an API can be with the From 845e8f943f47ed3b88e19ff8351e6f248a9d6ab9 Mon Sep 17 00:00:00 2001 From: exvito Date: Thu, 2 Apr 2015 14:24:54 +0100 Subject: [PATCH 359/545] Issue #2334 - HTTPAuthDigest - Making it thread-safe The existing code counts the number of 401 responses in the num_401_calls authenticator attribute. This is in place so as to ensure the necessary auth header is sent, while avoiding infinite 401 loops (issue #547). This commit makes num_401_calls an instance of threading.local() (previously an integer), using num_401_calls.value as the counter. It ensures that concurrent authentication requests get each their own counter and behave as expected (otherwise every other concurrent request would have its authentication fail). --- requests/auth.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/requests/auth.py b/requests/auth.py index d1c48251..289da366 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -11,6 +11,7 @@ import os import re import time import hashlib +import threading from base64 import b64encode @@ -67,7 +68,7 @@ class HTTPDigestAuth(AuthBase): self.nonce_count = 0 self.chal = {} self.pos = None - self.num_401_calls = 1 + self.num_401_calls = threading.local() def build_digest_header(self, method, url): @@ -157,7 +158,7 @@ class HTTPDigestAuth(AuthBase): def handle_redirect(self, r, **kwargs): """Reset num_401_calls counter on redirects.""" if r.is_redirect: - self.num_401_calls = 1 + self.num_401_calls.value = 1 def handle_401(self, r, **kwargs): """Takes the given response and tries digest-auth, if needed.""" @@ -166,12 +167,12 @@ class HTTPDigestAuth(AuthBase): # Rewind the file position indicator of the body to where # it was to resend the request. r.request.body.seek(self.pos) - num_401_calls = getattr(self, 'num_401_calls', 1) + num_401_calls = getattr(self.num_401_calls, 'value', 1) s_auth = r.headers.get('www-authenticate', '') if 'digest' in s_auth.lower() and num_401_calls < 2: - self.num_401_calls += 1 + self.num_401_calls.value += 1 pat = re.compile(r'digest ', flags=re.IGNORECASE) self.chal = parse_dict_header(pat.sub('', s_auth, count=1)) @@ -191,7 +192,7 @@ class HTTPDigestAuth(AuthBase): return _r - self.num_401_calls = 1 + self.num_401_calls.value = 1 return r def __call__(self, r): @@ -208,4 +209,6 @@ class HTTPDigestAuth(AuthBase): self.pos = None r.register_hook('response', self.handle_401) r.register_hook('response', self.handle_redirect) + self.num_401_calls.value = 1 + return r From e65360dbaf252a136a011adf639e5dce880a2412 Mon Sep 17 00:00:00 2001 From: exvito Date: Thu, 2 Apr 2015 21:29:51 +0100 Subject: [PATCH 360/545] Issue #2334 - HTTPDigestAuth - Replace getattr utilization Following Lukasa + kennethreitz suggestion. --- requests/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/auth.py b/requests/auth.py index 289da366..5c5332c8 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -167,7 +167,7 @@ class HTTPDigestAuth(AuthBase): # Rewind the file position indicator of the body to where # it was to resend the request. r.request.body.seek(self.pos) - num_401_calls = getattr(self.num_401_calls, 'value', 1) + num_401_calls = self.num_401_calls.value s_auth = r.headers.get('www-authenticate', '') if 'digest' in s_auth.lower() and num_401_calls < 2: From e8d9bc55bc9fae82e3ba934d3f7a93e94a72d094 Mon Sep 17 00:00:00 2001 From: exvito Date: Fri, 3 Apr 2015 14:21:29 +0100 Subject: [PATCH 361/545] Issue #2334 - HTTPDigestAuth - All state now in thread local storage Following feedback from tardyp and @vincentxb. --- requests/auth.py | 65 ++++++++++++++++++++++++++++-------------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/requests/auth.py b/requests/auth.py index 5c5332c8..7555114e 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -64,19 +64,24 @@ class HTTPDigestAuth(AuthBase): def __init__(self, username, password): self.username = username self.password = password - self.last_nonce = '' - self.nonce_count = 0 - self.chal = {} - self.pos = None - self.num_401_calls = threading.local() + # Keep state in per-thread local storage + self.tl = threading.local() + self.init_per_thread_state() + + def init_per_thread_state(self): + self.tl.last_nonce = '' + self.tl.nonce_count = 0 + self.tl.chal = {} + self.tl.pos = None + self.tl.num_401_calls = None def build_digest_header(self, method, url): - realm = self.chal['realm'] - nonce = self.chal['nonce'] - qop = self.chal.get('qop') - algorithm = self.chal.get('algorithm') - opaque = self.chal.get('opaque') + realm = self.tl.chal['realm'] + nonce = self.tl.chal['nonce'] + qop = self.tl.chal.get('qop') + algorithm = self.tl.chal.get('algorithm') + opaque = self.tl.chal.get('opaque') if algorithm is None: _algorithm = 'MD5' @@ -114,12 +119,12 @@ class HTTPDigestAuth(AuthBase): HA1 = hash_utf8(A1) HA2 = hash_utf8(A2) - if nonce == self.last_nonce: - self.nonce_count += 1 + if nonce == self.tl.last_nonce: + self.tl.nonce_count += 1 else: - self.nonce_count = 1 - ncvalue = '%08x' % self.nonce_count - s = str(self.nonce_count).encode('utf-8') + self.tl.nonce_count = 1 + ncvalue = '%08x' % self.tl.nonce_count + s = str(self.tl.nonce_count).encode('utf-8') s += nonce.encode('utf-8') s += time.ctime().encode('utf-8') s += os.urandom(8) @@ -139,7 +144,7 @@ class HTTPDigestAuth(AuthBase): # XXX handle auth-int. return None - self.last_nonce = nonce + self.tl.last_nonce = nonce # XXX should the partial digests be encoded too? base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \ @@ -158,23 +163,23 @@ class HTTPDigestAuth(AuthBase): def handle_redirect(self, r, **kwargs): """Reset num_401_calls counter on redirects.""" if r.is_redirect: - self.num_401_calls.value = 1 + self.tl.num_401_calls = 1 def handle_401(self, r, **kwargs): """Takes the given response and tries digest-auth, if needed.""" - if self.pos is not None: + if self.tl.pos is not None: # Rewind the file position indicator of the body to where # it was to resend the request. - r.request.body.seek(self.pos) - num_401_calls = self.num_401_calls.value + r.request.body.seek(self.tl.pos) + num_401_calls = self.tl.num_401_calls s_auth = r.headers.get('www-authenticate', '') if 'digest' in s_auth.lower() and num_401_calls < 2: - self.num_401_calls.value += 1 + self.tl.num_401_calls += 1 pat = re.compile(r'digest ', flags=re.IGNORECASE) - self.chal = parse_dict_header(pat.sub('', s_auth, count=1)) + self.tl.chal = parse_dict_header(pat.sub('', s_auth, count=1)) # Consume content and release the original connection # to allow our new request to reuse the same one. @@ -192,23 +197,29 @@ class HTTPDigestAuth(AuthBase): return _r - self.num_401_calls.value = 1 + self.tl.num_401_calls = 1 return r def __call__(self, r): + # When called from a thread other than the one that __init__'ed us + # per-thread state may be missing: initialize it if that's the case. + try: + self.tl.last_nonce + except AttributeError: + self.init_per_thread_state() # If we have a saved nonce, skip the 401 - if self.last_nonce: + if self.tl.last_nonce: r.headers['Authorization'] = self.build_digest_header(r.method, r.url) try: - self.pos = r.body.tell() + self.tl.pos = r.body.tell() except AttributeError: # In the case of HTTPDigestAuth being reused and the body of # the previous request was a file-like object, pos has the # file position of the previous body. Ensure it's set to # None. - self.pos = None + self.tl.pos = None r.register_hook('response', self.handle_401) r.register_hook('response', self.handle_redirect) - self.num_401_calls.value = 1 + self.tl.num_401_calls = 1 return r From 898465756a600958fdfee6aef43134cab7d75a7d Mon Sep 17 00:00:00 2001 From: benjaminran Date: Fri, 3 Apr 2015 07:58:42 -0700 Subject: [PATCH 362/545] added custom header precedence info --- docs/user/quickstart.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index e26abaad..cb187797 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -190,6 +190,15 @@ For example, we didn't specify our content-type in the previous example:: >>> r = requests.get(url, headers=headers) +Note: Custom headers are given less precedence than more specific sources of information. For instance: + +* Authorization headers will be overridden by any alternative auth source found. +* Authorization headers will be removed if you get redirected off-host. +* Proxy-Authorization headers will be overridden by proxy credentials provided in the URL. +* Content-Length headers will be overridden when we can determine the length of the content. + +Furthermore, Requests does not change its behavior at all based on which custom headers are specified. The headers are simply passed on into the final request. + More complicated POST requests ------------------------------ From 142e24de7b456d09b88bbbe0b9c978755254ca95 Mon Sep 17 00:00:00 2001 From: Pierre Tardy Date: Fri, 3 Apr 2015 15:38:59 +0200 Subject: [PATCH 363/545] digestauth: threadsafe test Signed-off-by: Pierre Tardy --- test_requests.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test_requests.py b/test_requests.py index 15406a22..22af2995 100755 --- a/test_requests.py +++ b/test_requests.py @@ -32,6 +32,11 @@ try: except ImportError: import io as StringIO +try: + from multiprocessing.pool import ThreadPool +except ImportError: + ThreadPool = None + if is_py3: def u(s): return s @@ -411,6 +416,21 @@ class RequestsTestCase(unittest.TestCase): r = requests.get(url, auth=auth) assert '"auth"' in r.request.headers['Authorization'] + def test_DIGESTAUTH_THREADED(self): + + auth = HTTPDigestAuth('user', 'pass') + url = httpbin('digest-auth', 'auth', 'user', 'pass') + session = requests.Session() + session.auth=auth + + def do_request(i): + r = session.get(url) + assert '"auth"' in r.request.headers['Authorization'] + return 1 + if ThreadPool is not None: + pool = ThreadPool(processes=50) + pool.map(do_request, range(100)) + def test_POSTBIN_GET_POST_FILES(self): url = httpbin('post') From 36dea434065fa174105ac220152d94f7a39827b5 Mon Sep 17 00:00:00 2001 From: exvito Date: Fri, 3 Apr 2015 17:04:33 +0100 Subject: [PATCH 364/545] Issue #2334 - HTTPDigestAuth - Improved per-thread state init Inspired in @tardyp approach. --- requests/auth.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/requests/auth.py b/requests/auth.py index 7555114e..f19f9dff 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -66,14 +66,16 @@ class HTTPDigestAuth(AuthBase): self.password = password # Keep state in per-thread local storage self.tl = threading.local() - self.init_per_thread_state() def init_per_thread_state(self): - self.tl.last_nonce = '' - self.tl.nonce_count = 0 - self.tl.chal = {} - self.tl.pos = None - self.tl.num_401_calls = None + # Ensure state is initialized just once per-thread + if not hasattr(self.tl, 'init'): + self.tl.init = True + self.tl.last_nonce = '' + self.tl.nonce_count = 0 + self.tl.chal = {} + self.tl.pos = None + self.tl.num_401_calls = None def build_digest_header(self, method, url): @@ -201,12 +203,8 @@ class HTTPDigestAuth(AuthBase): return r def __call__(self, r): - # When called from a thread other than the one that __init__'ed us - # per-thread state may be missing: initialize it if that's the case. - try: - self.tl.last_nonce - except AttributeError: - self.init_per_thread_state() + # Initialize per-thread state, if needed + self.init_per_thread_state() # If we have a saved nonce, skip the 401 if self.tl.last_nonce: r.headers['Authorization'] = self.build_digest_header(r.method, r.url) From 5a69137ac871c38f4597b574e93de07d9ff9655f Mon Sep 17 00:00:00 2001 From: exvito Date: Sat, 4 Apr 2015 14:25:08 +0100 Subject: [PATCH 365/545] Issue #2334 - HTTPDigestAuth - Renamed thread local attribute Per @sigmavirus24 suggestion: private and more readable. --- requests/auth.py | 61 ++++++++++++++++++++++++------------------------ 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/requests/auth.py b/requests/auth.py index f19f9dff..3268300d 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -65,25 +65,25 @@ class HTTPDigestAuth(AuthBase): self.username = username self.password = password # Keep state in per-thread local storage - self.tl = threading.local() + self._thread_local = threading.local() def init_per_thread_state(self): # Ensure state is initialized just once per-thread - if not hasattr(self.tl, 'init'): - self.tl.init = True - self.tl.last_nonce = '' - self.tl.nonce_count = 0 - self.tl.chal = {} - self.tl.pos = None - self.tl.num_401_calls = None + 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): - realm = self.tl.chal['realm'] - nonce = self.tl.chal['nonce'] - qop = self.tl.chal.get('qop') - algorithm = self.tl.chal.get('algorithm') - opaque = self.tl.chal.get('opaque') + realm = self._thread_local.chal['realm'] + nonce = self._thread_local.chal['nonce'] + qop = self._thread_local.chal.get('qop') + algorithm = self._thread_local.chal.get('algorithm') + opaque = self._thread_local.chal.get('opaque') if algorithm is None: _algorithm = 'MD5' @@ -121,12 +121,12 @@ class HTTPDigestAuth(AuthBase): HA1 = hash_utf8(A1) HA2 = hash_utf8(A2) - if nonce == self.tl.last_nonce: - self.tl.nonce_count += 1 + if nonce == self._thread_local.last_nonce: + self._thread_local.nonce_count += 1 else: - self.tl.nonce_count = 1 - ncvalue = '%08x' % self.tl.nonce_count - s = str(self.tl.nonce_count).encode('utf-8') + self._thread_local.nonce_count = 1 + ncvalue = '%08x' % self._thread_local.nonce_count + s = str(self._thread_local.nonce_count).encode('utf-8') s += nonce.encode('utf-8') s += time.ctime().encode('utf-8') s += os.urandom(8) @@ -146,7 +146,7 @@ class HTTPDigestAuth(AuthBase): # XXX handle auth-int. return None - self.tl.last_nonce = nonce + self._thread_local.last_nonce = nonce # XXX should the partial digests be encoded too? base = 'username="%s", realm="%s", nonce="%s", uri="%s", ' \ @@ -165,23 +165,22 @@ class HTTPDigestAuth(AuthBase): def handle_redirect(self, r, **kwargs): """Reset num_401_calls counter on redirects.""" if r.is_redirect: - self.tl.num_401_calls = 1 + self._thread_local.num_401_calls = 1 def handle_401(self, r, **kwargs): """Takes the given response and tries digest-auth, if needed.""" - if self.tl.pos is not None: + if self._thread_local.pos is not None: # Rewind the file position indicator of the body to where # it was to resend the request. - r.request.body.seek(self.tl.pos) - num_401_calls = self.tl.num_401_calls + r.request.body.seek(self._thread_local.pos) 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.tl.num_401_calls += 1 + self._thread_local.num_401_calls += 1 pat = re.compile(r'digest ', flags=re.IGNORECASE) - self.tl.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 # to allow our new request to reuse the same one. @@ -199,25 +198,25 @@ class HTTPDigestAuth(AuthBase): return _r - self.tl.num_401_calls = 1 + self._thread_local.num_401_calls = 1 return 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 self.tl.last_nonce: + if self._thread_local.last_nonce: r.headers['Authorization'] = self.build_digest_header(r.method, r.url) try: - self.tl.pos = r.body.tell() + self._thread_local.pos = r.body.tell() except AttributeError: # In the case of HTTPDigestAuth being reused and the body of # the previous request was a file-like object, pos has the # file position of the previous body. Ensure it's set to # None. - self.tl.pos = None + self._thread_local.pos = None r.register_hook('response', self.handle_401) r.register_hook('response', self.handle_redirect) - self.tl.num_401_calls = 1 + self._thread_local.num_401_calls = 1 return r From 1b1866934b407dfe10bd75a7d81bdeb218934f70 Mon Sep 17 00:00:00 2001 From: benjaminran Date: Sun, 5 Apr 2015 14:23:10 -0700 Subject: [PATCH 366/545] updated custom header precedence info with .netrc example --- docs/user/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index cb187797..7b14610c 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -192,7 +192,7 @@ For example, we didn't specify our content-type in the previous example:: Note: Custom headers are given less precedence than more specific sources of information. For instance: -* Authorization headers will be overridden by any alternative auth source found. +* Authorization headers will be overridden if credentials are passed via the ``auth`` parameter or are specified in a ``.netrc`` accessible in the environment. * Authorization headers will be removed if you get redirected off-host. * Proxy-Authorization headers will be overridden by proxy credentials provided in the URL. * Content-Length headers will be overridden when we can determine the length of the content. From 1b5bfe681b4c0a987e97ae78b2034db7b7ce3d01 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 5 Apr 2015 20:48:35 -0500 Subject: [PATCH 367/545] Place VendorAlias first in meta_path When other libraries or tools add items to the meta_path, we need to preempt some of their import hooks to be sure modules can be properly found. This also prevents problems importing built-in modules on Python 2 where it will first attempt to import something like: requests.packages.chardet.sys By placing our VendorAlias first, the above will fail and then it will fall back to trying to import sys directly instead. Closes #2465 --- requests/packages/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/packages/__init__.py b/requests/packages/__init__.py index 4dcf870f..b73b4d1b 100644 --- a/requests/packages/__init__.py +++ b/requests/packages/__init__.py @@ -104,4 +104,4 @@ class VendorAlias(object): return module -sys.meta_path.append(VendorAlias(["urllib3", "chardet"])) +sys.meta_path.insert(0, VendorAlias(["urllib3", "chardet"])) From dacf69a16df787c4a4c4b4185b86e597b2414445 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 5 Apr 2015 21:49:10 -0500 Subject: [PATCH 368/545] Fix build version number in metadata Closes #2530 --- requests/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/__init__.py b/requests/__init__.py index 446500bf..47165761 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -43,7 +43,7 @@ is at . __title__ = 'requests' __version__ = '2.6.0' -__build__ = 0x020503 +__build__ = 0x020600 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2015 Kenneth Reitz' From 36093e69c70b9b24dd1e007befa35ad891313f51 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 5 Apr 2015 21:27:33 -0500 Subject: [PATCH 369/545] Copy a PreparedRequest's CookieJar reliably When a PreparedRequests's cookie jar is not a RequestsCookieJar instance, it will not have a "copy" method. By adding _copy_cookie_jar we can reliably copy cookie jars so that we have an actual copy instead of the same instance on different prepared requests. This also updates the RequestsCookieJar.update logic to create copies of cookies from the other jar. Closes #2527 --- requests/cookies.py | 18 +++++++++++++++++- requests/models.py | 4 ++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/requests/cookies.py b/requests/cookies.py index 6969fe5c..1fbc934c 100644 --- a/requests/cookies.py +++ b/requests/cookies.py @@ -6,6 +6,7 @@ Compatibility code to be able to use `cookielib.CookieJar` with requests. requests.utils imports from here, so be careful with imports. """ +import copy import time import collections from .compat import cookielib, urlparse, urlunparse, Morsel @@ -302,7 +303,7 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping): """Updates this jar with cookies from another CookieJar or dict-like""" if isinstance(other, cookielib.CookieJar): for cookie in other: - self.set_cookie(cookie) + self.set_cookie(copy.copy(cookie)) else: super(RequestsCookieJar, self).update(other) @@ -359,6 +360,21 @@ class RequestsCookieJar(cookielib.CookieJar, collections.MutableMapping): return new_cj +def _copy_cookie_jar(jar): + if jar is None: + return None + + if hasattr(jar, 'copy'): + # We're dealing with an instane 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): """Make a cookie from underspecified parameters. diff --git a/requests/models.py b/requests/models.py index cb153aa5..12304adb 100644 --- a/requests/models.py +++ b/requests/models.py @@ -15,7 +15,7 @@ from .hooks import default_hooks from .structures import CaseInsensitiveDict 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.filepost import encode_multipart_formdata from .packages.urllib3.util import parse_url @@ -320,7 +320,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): p.method = self.method p.url = self.url 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.hooks = self.hooks return p From e3c7b43fc98aab11bd228498a775a528a06429f0 Mon Sep 17 00:00:00 2001 From: Smiley Barry Date: Mon, 6 Apr 2015 21:00:51 +0300 Subject: [PATCH 370/545] #2532: Documented strange behaviour in "PreparedRequest.prepare_cookies", caused by cookielib. Also documented a workaround if absolutely necessary. --- requests/models.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 12304adb..806004c0 100644 --- a/requests/models.py +++ b/requests/models.py @@ -501,7 +501,15 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): self.prepare_content_length(self.body) 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 ` object. Any subsequent calls + to ``prepare_cookies`` will have no actual effect, unless the "Cookie" + header is removed beforehand.""" if isinstance(cookies, cookielib.CookieJar): self._cookies = cookies From ccc03b93ef003de381cdafc5f483080a85553e94 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 10 Apr 2015 13:22:14 -0400 Subject: [PATCH 371/545] Test --- README.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.rst b/README.rst index 40995fe9..6c5a6dc1 100644 --- a/README.rst +++ b/README.rst @@ -8,6 +8,8 @@ Requests: HTTP for Humans :target: https://pypi.python.org/pypi/requests + + Requests is an Apache2 Licensed HTTP library, written in Python, for human beings. From 0cd9f5855e395c04bf99a68e0059784fb9a90cf3 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Fri, 10 Apr 2015 14:40:52 -0400 Subject: [PATCH 372/545] Keepers of the Three Crystals. --- AUTHORS.rst | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 8a40bd97..e24f3858 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -1,14 +1,10 @@ Requests is written and maintained by Kenneth Reitz and various contributors: -Development Lead -```````````````` - -- Kenneth Reitz `@kennethreitz `_ - -Core Contributors -````````````````` +Keepers of the Three Crystals +````````````````````````````` +- Kenneth Reitz `@kennethreitz `_, Keeper of the Master Crystal. - Cory Benfield `@lukasa `_ - Ian Cordasco `@sigmavirus24 `_ From f89b5a5d90d7b45a7f59dbd2bb29ae2f400aba44 Mon Sep 17 00:00:00 2001 From: sh1buy Date: Sat, 11 Apr 2015 00:23:07 +0300 Subject: [PATCH 373/545] fix bug 2540 --- requests/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 806004c0..869a9424 100644 --- a/requests/models.py +++ b/requests/models.py @@ -358,7 +358,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if not scheme: raise MissingSchema("Invalid URL {0!r}: No schema supplied. " - "Perhaps you meant http://{0}?".format(url)) + "Perhaps you meant http://{0}?".format(url.encode('utf8'))) if not host: raise InvalidURL("Invalid URL %r: No host supplied" % url) From f02d0f9e79a3aa3f0b716871f457ceb8f446bdba Mon Sep 17 00:00:00 2001 From: sh1buy Date: Sat, 11 Apr 2015 01:41:02 +0300 Subject: [PATCH 374/545] Fix bug 2540 --- requests/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 869a9424..545c5257 100644 --- a/requests/models.py +++ b/requests/models.py @@ -358,7 +358,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if not scheme: raise MissingSchema("Invalid URL {0!r}: No schema supplied. " - "Perhaps you meant http://{0}?".format(url.encode('utf8'))) + "Perhaps you meant http://{0}?".format(to_native_string(url,encoding='utf8'))) if not host: raise InvalidURL("Invalid URL %r: No host supplied" % url) From 599f8343512277671d08e043a0505a82630c641d Mon Sep 17 00:00:00 2001 From: sh1buy Date: Sat, 11 Apr 2015 03:29:21 +0300 Subject: [PATCH 375/545] fix bug 2540 --- requests/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 545c5257..31bff1ac 100644 --- a/requests/models.py +++ b/requests/models.py @@ -358,7 +358,8 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if not scheme: raise MissingSchema("Invalid URL {0!r}: No schema supplied. " - "Perhaps you meant http://{0}?".format(to_native_string(url,encoding='utf8'))) + "Perhaps you meant http://{0}?" + .format(to_native_string(url, 'utf8'))) if not host: raise InvalidURL("Invalid URL %r: No host supplied" % url) From a0632d8df045f9e56625a856a14df8941922eced Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 10 Apr 2015 22:03:26 -0500 Subject: [PATCH 376/545] Minor formatting change --- requests/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index 31bff1ac..752c58c1 100644 --- a/requests/models.py +++ b/requests/models.py @@ -358,8 +358,8 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if not scheme: raise MissingSchema("Invalid URL {0!r}: No schema supplied. " - "Perhaps you meant http://{0}?" - .format(to_native_string(url, 'utf8'))) + "Perhaps you meant http://{0}?".format( + to_native_string(url, 'utf8'))) if not host: raise InvalidURL("Invalid URL %r: No host supplied" % url) From 8cf5e670418264242f02f9c52291253817933f19 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 13 Apr 2015 11:39:23 -0400 Subject: [PATCH 377/545] Recommended packages. --- docs/community/recommended.rst | 26 ++++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 27 insertions(+) create mode 100644 docs/community/recommended.rst diff --git a/docs/community/recommended.rst b/docs/community/recommended.rst new file mode 100644 index 00000000..cbd18c26 --- /dev/null +++ b/docs/community/recommended.rst @@ -0,0 +1,26 @@ +.. _recommended: + +Recommended Packages +==================== + +Requests has a great variety of powerful and useful third-party extensions. +This page provides an overview of some of the best of them. + +CacheControl +------------ + +`CacheControl`_ is an extension that adds a full HTTP cache to requests. This +makes your web requests substantially more efficient, and should be used +whenever you're making a lot of web requests. + +.. _CacheControl: https://cachecontrol.readthedocs.org/en/latest/ + +Requests-Oauthlib +----------------- + +`requests-oauthlib`_ makes it possible to do the OAuth dance from requests +automatically. This is useful for the large number of websites that use OAuth +to provide authentication. It also provides a lot of tweaks that handle ways +that specific OAuth providers differ from the standard specifications. + +.. _requests-oauthlib: https://requests-oauthlib.readthedocs.org/en/latest/ diff --git a/docs/index.rst b/docs/index.rst index f013414b..12b24c9e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -114,6 +114,7 @@ Requests ecosystem and community. :maxdepth: 1 community/faq + community/recommended community/out-there.rst community/support community/vulnerabilities From 576e18a79686c84f908f7bdad7cfd56028404c1e Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 17 Apr 2015 08:51:02 -0400 Subject: [PATCH 378/545] recommended packages and extensions --- docs/_templates/sidebarintro.html | 2 +- docs/community/recommended.rst | 25 ++++++++++++++++++++++--- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 11d32667..8e169c9c 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -41,7 +41,7 @@

        Useful Links

        • Certifi: Best CA Bundle
        • -
        • Requests-Toolbelt
        • +
        • Recomended Packages
        • diff --git a/docs/community/recommended.rst b/docs/community/recommended.rst index cbd18c26..d2cd74f7 100644 --- a/docs/community/recommended.rst +++ b/docs/community/recommended.rst @@ -6,19 +6,38 @@ Recommended Packages Requests has a great variety of powerful and useful third-party extensions. This page provides an overview of some of the best of them. +Certifi CA Bundle +----------------- + +`Certifi`_ is a carefully curated collection of Root Certificates for +validating the trustworthiness of SSL certificates while verifying the +identity of TLS hosts. It has been extracted from the Requests project. + +.. _Certifi: http://certifi.io/en/latest/ + CacheControl ------------ -`CacheControl`_ is an extension that adds a full HTTP cache to requests. This +`CacheControl`_ is an extension that adds a full HTTP cache to Requests. This makes your web requests substantially more efficient, and should be used whenever you're making a lot of web requests. .. _CacheControl: https://cachecontrol.readthedocs.org/en/latest/ -Requests-Oauthlib +Requests-Toolbelt ----------------- -`requests-oauthlib`_ makes it possible to do the OAuth dance from requests +`Requests-Toolbelt`_ is a collection of utilities that some users of Requests may desire, +but do not belong in Requests proper. This library is actively maintained +by members of the Requests core team, and reflects the functionality most +requested by users within the community. + +.. _Requests-Toolbelt: http://toolbelt.readthedocs.org/en/latest/index.html + +Requests-OAuthlib +----------------- + +`requests-oauthlib`_ makes it possible to do the OAuth dance from Requests automatically. This is useful for the large number of websites that use OAuth to provide authentication. It also provides a lot of tweaks that handle ways that specific OAuth providers differ from the standard specifications. From 16eeee14367550680d9c3cd084b9d4addd743755 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Fri, 17 Apr 2015 08:52:47 -0400 Subject: [PATCH 379/545] and extensions --- docs/_templates/sidebarintro.html | 3 +-- docs/community/recommended.rst | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 8e169c9c..03bf7e0e 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -40,8 +40,7 @@

          Useful Links

            -
          • Certifi: Best CA Bundle
          • -
          • Recomended Packages
          • +
          • Recomended Packages and Extensions
          • diff --git a/docs/community/recommended.rst b/docs/community/recommended.rst index d2cd74f7..b7a550a0 100644 --- a/docs/community/recommended.rst +++ b/docs/community/recommended.rst @@ -1,7 +1,7 @@ .. _recommended: -Recommended Packages -==================== +Recommended Packages and Extensions +=================================== Requests has a great variety of powerful and useful third-party extensions. This page provides an overview of some of the best of them. From 35045e851a75b23d3d4e50853dfc1c5555110895 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 20 Apr 2015 20:13:57 -0500 Subject: [PATCH 380/545] Simplify PreparedRequest.prepare API Do not require that hooks be passed as an empty list to PreparedRequest.prepare. In the event hooks is None in prepare or prepare_hooks, use an empty list as a default. Related to #2552 --- requests/models.py | 4 ++++ test_requests.py | 1 - 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 752c58c1..45b3ea96 100644 --- a/requests/models.py +++ b/requests/models.py @@ -523,6 +523,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): def prepare_hooks(self, 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: self.register_hook(event, hooks[event]) diff --git a/test_requests.py b/test_requests.py index 15406a22..cad8c055 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1613,7 +1613,6 @@ def test_prepare_unicode_url(): p.prepare( method='GET', url=u('http://www.example.com/üniçø∂é'), - hooks=[] ) assert_copy(p, p.copy()) From 4cfc5bcd965ed060bf109edad28c89c93c0f8562 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Apr 2015 17:15:51 -0400 Subject: [PATCH 381/545] add @sigmavirus42's blog post --- docs/community/out-there.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/community/out-there.rst b/docs/community/out-there.rst index 235efd69..de41f1d4 100644 --- a/docs/community/out-there.rst +++ b/docs/community/out-there.rst @@ -30,3 +30,4 @@ Articles & Talks - `Issac Kelly's 'Consuming Web APIs' talk `_ - `Blog post about Requests via Yum `_ - `Russian blog post introducing Requests `_ +- `Sending JSON in Requests `_ From 1f0e5775f28d6c2db3430fdc16a095762816bf87 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Tue, 21 Apr 2015 17:18:04 -0400 Subject: [PATCH 382/545] add betamax --- docs/community/recommended.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/community/recommended.rst b/docs/community/recommended.rst index b7a550a0..99a16b9e 100644 --- a/docs/community/recommended.rst +++ b/docs/community/recommended.rst @@ -43,3 +43,15 @@ to provide authentication. It also provides a lot of tweaks that handle ways that specific OAuth providers differ from the standard specifications. .. _requests-oauthlib: https://requests-oauthlib.readthedocs.org/en/latest/ + + +Betamax +------- + +`Betamax`_ records your HTTP interactions so the NSA does not have to. +A VCR imitation designed only for Python-Requests. + +.. _betamax: https://github.com/sigmavirus24/betamax + + + From 5fcd843eb23e8e84cd5f60c6d372ef4d678f80fe Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 22 Apr 2015 08:17:39 -0500 Subject: [PATCH 383/545] Update urllib3 to 10b7a0fefa6596f47a9a6afc80f1f4d1ae950b66 --- requests/packages/urllib3/__init__.py | 4 +- requests/packages/urllib3/_collections.py | 45 +++++----- requests/packages/urllib3/connection.py | 2 + requests/packages/urllib3/connectionpool.py | 1 - .../packages/urllib3/contrib/pyopenssl.py | 23 +---- requests/packages/urllib3/exceptions.py | 5 ++ requests/packages/urllib3/response.py | 89 +++++++++++++++++-- requests/packages/urllib3/util/ssl_.py | 38 +++++--- 8 files changed, 146 insertions(+), 61 deletions(-) diff --git a/requests/packages/urllib3/__init__.py b/requests/packages/urllib3/__init__.py index 0660b9c8..8cd51444 100644 --- a/requests/packages/urllib3/__init__.py +++ b/requests/packages/urllib3/__init__.py @@ -55,9 +55,11 @@ def add_stderr_logger(level=logging.DEBUG): del NullHandler -# Set security warning to always go off by default. import warnings +# SecurityWarning's always go off by default. warnings.simplefilter('always', exceptions.SecurityWarning) +# InsecurePlatformWarning's don't vary between requests, so we keep it default. +warnings.simplefilter('default', exceptions.InsecurePlatformWarning) def disable_warnings(category=exceptions.HTTPWarning): """ diff --git a/requests/packages/urllib3/_collections.py b/requests/packages/urllib3/_collections.py index cc424de0..279416ce 100644 --- a/requests/packages/urllib3/_collections.py +++ b/requests/packages/urllib3/_collections.py @@ -227,20 +227,20 @@ class HTTPHeaderDict(dict): # Need to convert the tuple to list for further extension _dict_setitem(self, key_lower, [vals[0], vals[1], val]) - def extend(*args, **kwargs): + 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) > 2: - raise TypeError("update() takes at most 2 positional " + if len(args) > 1: + raise TypeError("extend() takes at most 1 positional " "arguments ({} given)".format(len(args))) - elif not args: - raise TypeError("update() takes at least 1 argument (0 given)") - self = args[0] - other = args[1] if len(args) >= 2 else () + other = args[0] if len(args) >= 1 else () - if isinstance(other, Mapping): + 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"): @@ -304,17 +304,20 @@ class HTTPHeaderDict(dict): return list(self.iteritems()) @classmethod - def from_httplib(cls, message, duplicates=('set-cookie',)): # Python 2 + def from_httplib(cls, message): # Python 2 """Read headers from a Python 2 httplib message object.""" - ret = cls(message.items()) - # ret now contains only the last header line for each duplicate. - # Importing with all duplicates would be nice, but this would - # mean to repeat most of the raw parsing already done, when the - # message object was created. Extracting only the headers of interest - # separately, the cookies, should be faster and requires less - # extra code. - for key in duplicates: - ret.discard(key) - for val in message.getheaders(key): - ret.add(key, val) - return ret + # 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) diff --git a/requests/packages/urllib3/connection.py b/requests/packages/urllib3/connection.py index e5de769d..2a8c3596 100644 --- a/requests/packages/urllib3/connection.py +++ b/requests/packages/urllib3/connection.py @@ -260,3 +260,5 @@ if ssl: # Make a copy for testing. UnverifiedHTTPSConnection = HTTPSConnection HTTPSConnection = VerifiedHTTPSConnection +else: + HTTPSConnection = DummyConnection diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index 0085345c..117269ac 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -735,7 +735,6 @@ class HTTPSConnectionPool(HTTPConnectionPool): % (self.num_connections, self.host)) 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 " "module is not available.") diff --git a/requests/packages/urllib3/contrib/pyopenssl.py b/requests/packages/urllib3/contrib/pyopenssl.py index ee657fb3..b2c34a89 100644 --- a/requests/packages/urllib3/contrib/pyopenssl.py +++ b/requests/packages/urllib3/contrib/pyopenssl.py @@ -38,8 +38,6 @@ Module Variables ---------------- :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 .. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) @@ -85,22 +83,7 @@ _openssl_verify = { + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, } -# A secure default. -# 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" +DEFAULT_SSL_CIPHER_LIST = util.ssl_.DEFAULT_CIPHERS orig_util_HAS_SNI = util.HAS_SNI @@ -299,7 +282,9 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, try: cnx.do_handshake() except OpenSSL.SSL.WantReadError: - select.select([sock], [], []) + rd, _, _ = select.select([sock], [], [], sock.gettimeout()) + if not rd: + raise timeout('select timed out') continue except OpenSSL.SSL.Error as e: raise ssl.SSLError('bad handshake', e) diff --git a/requests/packages/urllib3/exceptions.py b/requests/packages/urllib3/exceptions.py index 5d523011..31bda1c0 100644 --- a/requests/packages/urllib3/exceptions.py +++ b/requests/packages/urllib3/exceptions.py @@ -162,3 +162,8 @@ class SystemTimeWarning(SecurityWarning): class InsecurePlatformWarning(SecurityWarning): "Warned when certain SSL configuration is not available on a platform." pass + + +class ResponseNotChunked(ProtocolError, ValueError): + "Response needs to be chunked in order to read it as chunks." + pass diff --git a/requests/packages/urllib3/response.py b/requests/packages/urllib3/response.py index 34cd3d70..7e08fffe 100644 --- a/requests/packages/urllib3/response.py +++ b/requests/packages/urllib3/response.py @@ -1,9 +1,15 @@ +try: + import http.client as httplib +except ImportError: + import httplib import zlib import io from socket import timeout as SocketTimeout from ._collections import HTTPHeaderDict -from .exceptions import ProtocolError, DecodeError, ReadTimeoutError +from .exceptions import ( + ProtocolError, DecodeError, ReadTimeoutError, ResponseNotChunked +) from .packages.six import string_types as basestring, binary_type, PY3 from .connection import HTTPException, BaseSSLError from .util.response import is_fp_closed @@ -117,8 +123,17 @@ class HTTPResponse(io.IOBase): if hasattr(body, 'read'): self._fp = body - if preload_content and not self._body: - self._body = self.read(decode_content=decode_content) + # Are we using the chunked-style of transfer encoding? + self.chunked = False + self.chunk_left = None + tr_enc = self.headers.get('transfer-encoding', '') + if tr_enc.lower() == "chunked": + self.chunked = True + + # We certainly don't want to preload content when the response is chunked. + if not self.chunked: + if preload_content and not self._body: + self._body = self.read(decode_content=decode_content) def get_redirect_location(self): """ @@ -269,11 +284,15 @@ class HTTPResponse(io.IOBase): If True, will attempt to decode the body based on the 'content-encoding' header. """ - while not is_fp_closed(self._fp): - data = self.read(amt=amt, decode_content=decode_content) + if self.chunked: + for line in self.read_chunked(amt): + yield line + else: + while not is_fp_closed(self._fp): + data = self.read(amt=amt, decode_content=decode_content) - if data: - yield data + if data: + yield data @classmethod def from_httplib(ResponseCls, r, **response_kw): @@ -351,3 +370,59 @@ class HTTPResponse(io.IOBase): else: b[:len(temp)] = temp return len(temp) + + def read_chunked(self, amt=None): + # 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.") + while True: + # First, we'll figure out length of a chunk and then + # we'll try to read it from socket. + if self.chunk_left is None: + line = self._fp.fp.readline() + line = line.decode() + # See RFC 7230: Chunked Transfer Coding. + i = line.find(';') + if i >= 0: + line = line[:i] # Strip chunk-extensions. + try: + self.chunk_left = int(line, 16) + except ValueError: + # Invalid chunked protocol response, abort. + self.close() + raise httplib.IncompleteRead(''.join(line)) + if self.chunk_left == 0: + break + if amt is None: + chunk = self._fp._safe_read(self.chunk_left) + yield 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 + yield 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 + yield value + else: # amt > self.chunk_left + yield 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 + + # 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". + self.release_conn() + diff --git a/requests/packages/urllib3/util/ssl_.py b/requests/packages/urllib3/util/ssl_.py index e7e7dfae..b846d42c 100644 --- a/requests/packages/urllib3/util/ssl_.py +++ b/requests/packages/urllib3/util/ssl_.py @@ -9,10 +9,10 @@ HAS_SNI = False create_default_context = None import errno -import ssl import warnings try: # Test for SSL features + import ssl from ssl import wrap_socket, CERT_NONE, PROTOCOL_SSLv23 from ssl import HAS_SNI # Has SNI? except ImportError: @@ -25,14 +25,24 @@ except ImportError: OP_NO_SSLv2, OP_NO_SSLv3 = 0x1000000, 0x2000000 OP_NO_COMPRESSION = 0x20000 -try: - from ssl import _DEFAULT_CIPHERS -except ImportError: - _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' - ) +# A secure default. +# 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_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: from ssl import SSLContext # Modern SSL? @@ -40,7 +50,8 @@ except ImportError: import sys 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): self.protocol = protocol_version @@ -167,7 +178,7 @@ def resolve_ssl_version(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): """All arguments have the same meaning as ``ssl_wrap_socket``. @@ -204,6 +215,9 @@ def create_urllib3_context(ssl_version=None, cert_reqs=ssl.CERT_REQUIRED, """ 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: options = 0 # SSLv2 is easily broken and is considered harmful and dangerous @@ -217,7 +231,7 @@ def create_urllib3_context(ssl_version=None, cert_reqs=ssl.CERT_REQUIRED, context.options |= options 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 if getattr(context, 'check_hostname', None) is not None: # Platform-specific: Python 3.2 From cfb9e66b04136f014412a8d638b2d69603557c1d Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 22 Apr 2015 08:43:20 -0500 Subject: [PATCH 384/545] Update history and version number for v2.6.1 --- HISTORY.rst | 23 +++++++++++++++++++++++ requests/__init__.py | 4 ++-- 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index e56121df..19391150 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,29 @@ Release History --------------- +2.6.1 (2015-04-22) +++++++++++++++++++ + +**Bugfixes** + +- Fix issue with import machinery to deal with an unvendored urllib3 and + chardet when operating from within a PyInstaller executable. (#2465 redux) + +- Simplify the PreparedRequest.prepare API: We no longer require the user to + pass an empty list to the hooks keyword argument. (c.f. #2552) + +- Resolve redirects now receives and forwards all of the original arguments to + the adapter. (#2503) + +- Handle UnicodeDecodeErrors when trying to deal with a unicode URL that + cannot be encoded in ASCII. (#2540) + +- Populate the parsed path of the URI field when performing Digest + Authentication. (#2426) + +- Copy a PreparedRequest's CookieJar more reliably when it is not an instance + of RequestsCookieJar. (#2527) + 2.6.0 (2015-03-14) ++++++++++++++++++ diff --git a/requests/__init__.py b/requests/__init__.py index 47165761..4174629d 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.6.0' -__build__ = 0x020600 +__version__ = '2.6.1' +__build__ = 0x020601 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2015 Kenneth Reitz' From 3d339ed7e61261832347190185113b890b55009e Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 22 Apr 2015 18:39:40 +0100 Subject: [PATCH 385/545] Update urllib3 to 0b744993 (cherry picked from commit df63ee3e13672caa6b3256c6579fa85caefb2729) --- requests/packages/urllib3/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/packages/urllib3/__init__.py b/requests/packages/urllib3/__init__.py index 8cd51444..333060c2 100644 --- a/requests/packages/urllib3/__init__.py +++ b/requests/packages/urllib3/__init__.py @@ -4,7 +4,7 @@ urllib3 - Thread-safe connection pooling and re-using. __author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' __license__ = 'MIT' -__version__ = '1.10.2' +__version__ = '1.10.3' from .connectionpool import ( From 3e3fc7683cab2d401981fdf01ae8cdc40c8d6222 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 22 Apr 2015 16:26:14 -0500 Subject: [PATCH 386/545] Remove VendorAlias meta_path hook --- requests/packages/__init__.py | 106 +--------------------------------- 1 file changed, 1 insertion(+), 105 deletions(-) diff --git a/requests/packages/__init__.py b/requests/packages/__init__.py index b73b4d1b..d62c4b71 100644 --- a/requests/packages/__init__.py +++ b/requests/packages/__init__.py @@ -1,107 +1,3 @@ -""" -Copyright (c) Donald Stufft, pip, and individual contributors - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -""" from __future__ import absolute_import -import sys - - -class VendorAlias(object): - - def __init__(self, package_names): - self._package_names = package_names - self._vendor_name = __name__ - self._vendor_pkg = self._vendor_name + "." - self._vendor_pkgs = [ - self._vendor_pkg + name for name in self._package_names - ] - - def find_module(self, fullname, path=None): - if fullname.startswith(self._vendor_pkg): - return self - - def load_module(self, name): - # Ensure that this only works for the vendored name - if not name.startswith(self._vendor_pkg): - raise ImportError( - "Cannot import %s, must be a subpackage of '%s'." % ( - name, self._vendor_name, - ) - ) - - if not (name == self._vendor_name or - any(name.startswith(pkg) for pkg in self._vendor_pkgs)): - raise ImportError( - "Cannot import %s, must be one of %s." % ( - name, self._vendor_pkgs - ) - ) - - # Check to see if we already have this item in sys.modules, if we do - # then simply return that. - if name in sys.modules: - return sys.modules[name] - - # Check to see if we can import the vendor name - try: - # We do this dance here because we want to try and import this - # module without hitting a recursion error because of a bunch of - # VendorAlias instances on sys.meta_path - real_meta_path = sys.meta_path[:] - try: - sys.meta_path = [ - m for m in sys.meta_path - if not isinstance(m, VendorAlias) - ] - __import__(name) - module = sys.modules[name] - finally: - # Re-add any additions to sys.meta_path that were made while - # during the import we just did, otherwise things like - # requests.packages.urllib3.poolmanager will fail. - for m in sys.meta_path: - if m not in real_meta_path: - real_meta_path.append(m) - - # Restore sys.meta_path with any new items. - sys.meta_path = real_meta_path - except ImportError: - # We can't import the vendor name, so we'll try to import the - # "real" name. - real_name = name[len(self._vendor_pkg):] - try: - __import__(real_name) - module = sys.modules[real_name] - except ImportError: - raise ImportError("No module named '%s'" % (name,)) - - # If we've gotten here we've found the module we're looking for, either - # as part of our vendored package, or as the real name, so we'll add - # it to sys.modules as the vendored name so that we don't have to do - # the lookup again. - sys.modules[name] = module - - # Finally, return the loaded module - return module - - -sys.meta_path.insert(0, VendorAlias(["urllib3", "chardet"])) +from . import urllib3 From ae7c28eb8e8df89555b50d766b77dc922c1500ce Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Wed, 22 Apr 2015 16:52:40 -0500 Subject: [PATCH 387/545] Update notes for 2.6.1 --- HISTORY.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 19391150..d0967b7d 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -8,8 +8,7 @@ Release History **Bugfixes** -- Fix issue with import machinery to deal with an unvendored urllib3 and - chardet when operating from within a PyInstaller executable. (#2465 redux) +- Remove VendorAlias import machinery introduced in v2.5.2. - Simplify the PreparedRequest.prepare API: We no longer require the user to pass an empty list to the hooks keyword argument. (c.f. #2552) From 73b9b6906d5cd94bd18751d7c5dce15a5d47eab0 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Thu, 23 Apr 2015 11:27:42 -0500 Subject: [PATCH 388/545] Update urllib3 to 7b82da0fa3a13514d11a863e379e7541d0ec46ef --- requests/packages/urllib3/response.py | 52 +++++++++++++++++---------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/requests/packages/urllib3/response.py b/requests/packages/urllib3/response.py index 7e08fffe..f1ea9bb5 100644 --- a/requests/packages/urllib3/response.py +++ b/requests/packages/urllib3/response.py @@ -172,6 +172,36 @@ class HTTPResponse(io.IOBase): """ 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: + if 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 and self._decoder: + buf = self._decoder.decompress(binary_type()) + data += buf + self._decoder.flush() + + return data + def read(self, amt=None, decode_content=None, cache_content=False): """ Similar to :meth:`httplib.HTTPResponse.read`, but with two additional @@ -193,12 +223,7 @@ class HTTPResponse(io.IOBase): after having ``.read()`` the file object. (Overridden if ``amt`` is set.) """ - # 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: - if content_encoding in self.CONTENT_DECODERS: - self._decoder = _get_decoder(content_encoding) + self._init_decoder() if decode_content is None: decode_content = self.decode_content @@ -247,17 +272,7 @@ class HTTPResponse(io.IOBase): self._fp_bytes_read += len(data) - try: - 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() + data = self._decode(data, decode_content, flush_decoder) if cache_content: self._body = data @@ -284,9 +299,10 @@ class HTTPResponse(io.IOBase): If True, will attempt to decode the body based on the 'content-encoding' header. """ + self._init_decoder() if self.chunked: for line in self.read_chunked(amt): - yield line + yield self._decode(line, decode_content, True) else: while not is_fp_closed(self._fp): data = self.read(amt=amt, decode_content=decode_content) From ca66267d2cf8adc67ed8857f3f57b45ffde21e01 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Thu, 23 Apr 2015 11:29:10 -0500 Subject: [PATCH 389/545] Add release notes for 2.6.2 Closes #2561 --- HISTORY.rst | 8 ++++++++ requests/__init__.py | 4 ++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index d0967b7d..815a1734 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,14 @@ Release History --------------- +2.6.2 (2015-04-23) +++++++++++++++++++ + +**Bugfixes** + +- Fix regression where compressed data that was sent as chunked data was not + properly decompressed. (#2561) + 2.6.1 (2015-04-22) ++++++++++++++++++ diff --git a/requests/__init__.py b/requests/__init__.py index 4174629d..46116bea 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.6.1' -__build__ = 0x020601 +__version__ = '2.6.2' +__build__ = 0x020602 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2015 Kenneth Reitz' From b7fc3a1250cb13ed3b55010ccc33f2681f0ffa4a Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 24 Apr 2015 12:05:18 +0200 Subject: [PATCH 390/545] Import aliases for Debian Alternative to #2375 --- requests/packages/__init__.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/requests/packages/__init__.py b/requests/packages/__init__.py index d62c4b71..a1592748 100644 --- a/requests/packages/__init__.py +++ b/requests/packages/__init__.py @@ -1,3 +1,14 @@ from __future__ import absolute_import +import sys -from . import urllib3 +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 From 93216afbaf09290aa1cadb2f5811f18f4c48f983 Mon Sep 17 00:00:00 2001 From: Markus Unterwaditzer Date: Fri, 24 Apr 2015 15:29:19 +0200 Subject: [PATCH 391/545] Add information about the package stub --- requests/packages/README.rst | 7 +++++-- requests/packages/__init__.py | 22 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/requests/packages/README.rst b/requests/packages/README.rst index c42f376b..83e0c625 100644 --- a/requests/packages/README.rst +++ b/requests/packages/README.rst @@ -1,8 +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 +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. diff --git a/requests/packages/__init__.py b/requests/packages/__init__.py index a1592748..971c2ad0 100644 --- a/requests/packages/__init__.py +++ b/requests/packages/__init__.py @@ -1,3 +1,25 @@ +''' +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. + +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 From 773e730d80aa5e590ca0fb0fa079a9b4f11d6403 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Fri, 24 Apr 2015 19:28:01 -0500 Subject: [PATCH 392/545] Add new release process to docs Closes #2569 --- docs/community/release-process.rst | 51 ++++++++++++++++++++++++++++++ docs/index.rst | 3 +- 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 docs/community/release-process.rst diff --git a/docs/community/release-process.rst b/docs/community/release-process.rst new file mode 100644 index 00000000..5fad3f46 --- /dev/null +++ b/docs/community/release-process.rst @@ -0,0 +1,51 @@ +Release Process and Rules +========================= + +.. versionadded:: v2.6.2 + +Starting with the version to be released after ``v2.6.2``, the following rules +will govern and describe how the Requests core team produces a new release. + +Major Releases +-------------- + +A major release will include breaking changes. When it is versioned, it will +be versioned as ``vX.0.0``. For example, if the previous release was +``v10.2.9`` the next version will be ``v11.0.0``. + +Breaking changes are changes that break backwards compatibility with prior +versions. If the project were to change the ``text`` attribute on a +``Response`` object to a method, that would only happen in a Major release. + +Major releases may also include miscellaneous bug fixes and upgrades to +vendored packages. + +Minor Releases +-------------- + +A minor release will not include breaking changes but may include +miscellaneous bug fixes and upgrades to vendored packages. If the previous +version of Requests released was ``v10.2.9`` a minor release would be +versioned as ``v10.3.0``. + +Minor releases will be backwards compatible with releases that have the same +major version number. In other words, all versions that would start with +``v10.`` should be compatible with each other. + +Hotfix Releases +--------------- + +A hotfix release will only include bug fixes that were missed when the project +released the previous version. If the previous version of Requests released +``v10.2.9`` the hotfix release would be versioned as ``v10.2.10``. + +Hotfixes will **not** include upgrades to vendored dependences after +``v2.6.2`` + +Reasoning +--------- + +In the 2.5 and 2.6 release series, the Requests core team upgraded vendored +dependencies and caused a great deal of headaches for both users and the core +team. To reduce this pain, we're forming a concrete set of procedures so +expectations will be properly set. diff --git a/docs/index.rst b/docs/index.rst index 12b24c9e..e5d07d53 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -115,10 +115,11 @@ Requests ecosystem and community. community/faq community/recommended - community/out-there.rst + community/out-there community/support community/vulnerabilities community/updates + community/release-process API Documentation ----------------- From f74fc486be3ba16914a46df809ca33fbffb28629 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 25 Apr 2015 09:29:40 -0500 Subject: [PATCH 393/545] Update version number examples. Strip whitespace Also I've included a bit more verbiage around why a major release would happen and some reassurances that they will be rare. --- docs/community/release-process.rst | 43 ++++++++++++++++-------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/docs/community/release-process.rst b/docs/community/release-process.rst index 5fad3f46..adb86d5c 100644 --- a/docs/community/release-process.rst +++ b/docs/community/release-process.rst @@ -3,49 +3,52 @@ Release Process and Rules .. versionadded:: v2.6.2 -Starting with the version to be released after ``v2.6.2``, the following rules +Starting with the version to be released after ``v2.6.2``, the following rules will govern and describe how the Requests core team produces a new release. Major Releases -------------- -A major release will include breaking changes. When it is versioned, it will -be versioned as ``vX.0.0``. For example, if the previous release was -``v10.2.9`` the next version will be ``v11.0.0``. +A major release will include breaking changes. When it is versioned, it will +be versioned as ``vX.0.0``. For example, if the previous release was +``v10.2.7`` the next version will be ``v11.0.0``. -Breaking changes are changes that break backwards compatibility with prior -versions. If the project were to change the ``text`` attribute on a +Breaking changes are changes that break backwards compatibility with prior +versions. If the project were to change the ``text`` attribute on a ``Response`` object to a method, that would only happen in a Major release. -Major releases may also include miscellaneous bug fixes and upgrades to -vendored packages. +Major releases may also include miscellaneous bug fixes and upgrades to +vendored packages. The core developers of Requests are committed to providing +a good user experience. This means we're also committed to preserving +backwards compatibility as much as possible. Major releases will be infrequent +and will need strong justifications before they are considered. Minor Releases -------------- -A minor release will not include breaking changes but may include -miscellaneous bug fixes and upgrades to vendored packages. If the previous -version of Requests released was ``v10.2.9`` a minor release would be +A minor release will not include breaking changes but may include +miscellaneous bug fixes and upgrades to vendored packages. If the previous +version of Requests released was ``v10.2.7`` a minor release would be versioned as ``v10.3.0``. -Minor releases will be backwards compatible with releases that have the same -major version number. In other words, all versions that would start with +Minor releases will be backwards compatible with releases that have the same +major version number. In other words, all versions that would start with ``v10.`` should be compatible with each other. Hotfix Releases --------------- -A hotfix release will only include bug fixes that were missed when the project -released the previous version. If the previous version of Requests released -``v10.2.9`` the hotfix release would be versioned as ``v10.2.10``. +A hotfix release will only include bug fixes that were missed when the project +released the previous version. If the previous version of Requests released +``v10.2.7`` the hotfix release would be versioned as ``v10.2.8``. -Hotfixes will **not** include upgrades to vendored dependences after +Hotfixes will **not** include upgrades to vendored dependences after ``v2.6.2`` Reasoning --------- -In the 2.5 and 2.6 release series, the Requests core team upgraded vendored -dependencies and caused a great deal of headaches for both users and the core -team. To reduce this pain, we're forming a concrete set of procedures so +In the 2.5 and 2.6 release series, the Requests core team upgraded vendored +dependencies and caused a great deal of headaches for both users and the core +team. To reduce this pain, we're forming a concrete set of procedures so expectations will be properly set. From 9dba20395296f26a56fde851cbfa8ac6cef5a3bf Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 25 Apr 2015 21:39:00 +0100 Subject: [PATCH 394/545] Add basic contributing guide --- docs/dev/contributing.rst | 161 ++++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + 2 files changed, 162 insertions(+) create mode 100644 docs/dev/contributing.rst diff --git a/docs/dev/contributing.rst b/docs/dev/contributing.rst new file mode 100644 index 00000000..472010cc --- /dev/null +++ b/docs/dev/contributing.rst @@ -0,0 +1,161 @@ +.. _contributing: + +Contributor's Guide +=================== + +If you're reading this you're probably interested in contributing to +``requests``. First, I'd like to say: thankyou! Open source projects +live-and-die based on the support they receive from others, and the fact that +you're even *considering* supporting ``requests`` is incredibly generous of +you. + +This document lays out guidelines and advice for contributing to ``requests``. +If you're thinking of contributing, start by reading this thoroughly and +getting a feel for how contributing to the project works. If you've still got +questions after reading this, you should go ahead and contact either +`Ian Cordasco`_ or `Cory Benfield`_, the active maintainers. + +The guide is split into sections based on the type of contribution you're +thinking of making, with a section that covers general guidelines for all +contributors. + +.. _Ian Cordasco: http://www.coglib.com/~icordasc/ +.. _Cory Benfield: https://lukasa.co.uk/about + + +All Contributions +----------------- + +Be Cordial Or Be On Your Way +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``requests`` has one very important guideline governing all forms of +contribution, including things like reporting bugs or requesting features. The +guideline is `be cordial or be on your way`_. **All contributions are +welcome**, but they come with an implicit social contract: everyone must be +treated with respect. + +This can be a difficult area to judge, so the maintainers will enforce the +following policy. If any contributor acts rudely or aggressively towards any +other contributor, **regardless of whether they perceive themselves to be +acting in retaliation for an earlier breach of this guideline**, they will be +subject to the following procedure: + +1. They must apologise. This apology must be genuine in nature: "I'm sorry you + were offended" is not sufficient. The judgement of 'genuine' is at the + discretion of the maintainers. +2. If the apology is not offered, any outstanding and future contributions from + the violating contributor will be rejected immediately. + +Everyone involved in the ``requests`` project, the maintainers included, are +bound by this policy. Failing to abide by it leads to the offender being kicked +off the project. + +.. _be cordial or be on your way: http://kennethreitz.org/be-cordial-or-be-on-your-way/ + +.. _early-feedback: + +Get Early Feedback +~~~~~~~~~~~~~~~~~~ + +If you are contributing, do not feel the need to sit on your contribution until +it is perfectly polished and complete. It helps everyone involved for you to +seek feedback as early as you possibly can. Submitting an early, unfinished +version of your contribution for feedback in no way prejudices your chances of +getting that contribution accepted, and can save you from putting a lot of work +into a contribution that is not suitable for the project. + +Contribution Suitability +~~~~~~~~~~~~~~~~~~~~~~~~ + +The project maintainer has the last word on whether or not a contribution is +suitable for ``requests``. All contributions will be considered, but from time +to time contributions will be rejected because they do not suit the project. + +If your contribution is rejected, don't despair! So long as you followed these +guidelines, you'll have a much better chance of getting your next contribution +accepted. + + +Code Contributions +------------------ + +Steps +~~~~~ + +When contributing code, you'll want to follow this checklist: + +1. Fork the repository on GitHub. +2. Run the tests to confirm they all pass on your system. If they don't, you'll + need to investigate why they fail. If you're unable to diagnose this + yourself, raise it as a bug report by following the guidelines in this + document: :ref:`bug-reports`. +3. Write tests that demonstrate your bug or feature. Ensure that they fail. +4. Make your change. +5. Run the entire test suite again, confirming that all tests pass *including + the ones you just added*. +6. Send a GitHub Pull Request to the main repository's ``master`` branch. + GitHub Pull Requests are the expected method of code collaboration on this + project. If you object to the GitHub workflow, you may mail a patch to any + of the maintainers listed above. + +The following sub-sections go into more detail on some of the points above. + +Code Review +~~~~~~~~~~~ + +Contributions will not be merged until they've been code reviewed. You should +implement any code review feedback unless you strongly object to it. In the +event that you object to the code review feedback, you should make your case +clearly and calmly. If, after doing so, the feedback is judged to still apply, +you must either apply the feedback or withdraw your contribution. + +New Contributors +~~~~~~~~~~~~~~~~ + +If you are new or relatively new to Open Source, welcome! ``requests`` aims to +be a gentle introduction to the world of Open Source. If you're concerned about +how best to contribute, please consider mailing a maintainer (listed above) and +asking for help. + +Please also check the :ref:`early-feedback` section. + +Documentation Contributions +--------------------------- + +Documentation improvements are always welcome! The documentation files live in +the ``docs/`` directory of the codebase. They're written in +`reStructuredText`_, and use `Sphinx`_ to generate the full suite of +documentation. + +When contributing documentation, please attempt to follow the style of the +documentation files. This means a soft-limit of 79 characters wide in your text +files and a semi-formal prose style. + +.. _reStructuredText: http://docutils.sourceforge.net/rst.html +.. _Sphinx: http://sphinx-doc.org/index.html + + +.. _bug-reports: + +Bug Reports +----------- + +Bug reports are hugely important! Before you raise one, though, please check +through the `GitHub issues`_, **both open and closed**, to confirm that the bug +hasn't been reported before. Duplicate bug reports are a huge drain on the time +of other contributors, and should be avoided as much as possible. + +.. _GitHub issues: https://github.com/kennethreitz/requests/issues + + +Feature Requests +---------------- + +Requests is in a perpeptual feature freeze. The maintainers believe that +requests contains every major feature currently required by the vast majority +of users. + +If you believe there is a feature missing, feel free to raise a feature +request, but please do be aware that the overwhelming likelihood is that your +feature request will not be accepted. diff --git a/docs/index.rst b/docs/index.rst index 12b24c9e..fb6a8457 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -141,6 +141,7 @@ you. .. toctree:: :maxdepth: 1 + dev/contributing dev/philosophy dev/todo dev/authors From 535c51f5481ab6276ac43dc73264a6d24aefec0f Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sat, 25 Apr 2015 17:36:14 -0400 Subject: [PATCH 395/545] style of contributing.rst --- docs/dev/contributing.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/dev/contributing.rst b/docs/dev/contributing.rst index 472010cc..115464ad 100644 --- a/docs/dev/contributing.rst +++ b/docs/dev/contributing.rst @@ -4,12 +4,12 @@ Contributor's Guide =================== If you're reading this you're probably interested in contributing to -``requests``. First, I'd like to say: thankyou! Open source projects +``Requests``. First, I'd like to say: thankyou! Open source projects live-and-die based on the support they receive from others, and the fact that -you're even *considering* supporting ``requests`` is incredibly generous of +you're even *considering* supporting ``Requests`` is incredibly generous of you. -This document lays out guidelines and advice for contributing to ``requests``. +This document lays out guidelines and advice for contributing to ``Requests``. If you're thinking of contributing, start by reading this thoroughly and getting a feel for how contributing to the project works. If you've still got questions after reading this, you should go ahead and contact either @@ -29,7 +29,7 @@ All Contributions Be Cordial Or Be On Your Way ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``requests`` has one very important guideline governing all forms of +``Requests`` has one very important guideline governing all forms of contribution, including things like reporting bugs or requesting features. The guideline is `be cordial or be on your way`_. **All contributions are welcome**, but they come with an implicit social contract: everyone must be @@ -47,7 +47,7 @@ subject to the following procedure: 2. If the apology is not offered, any outstanding and future contributions from the violating contributor will be rejected immediately. -Everyone involved in the ``requests`` project, the maintainers included, are +Everyone involved in the ``Requests`` project, the maintainers included, are bound by this policy. Failing to abide by it leads to the offender being kicked off the project. @@ -69,7 +69,7 @@ Contribution Suitability ~~~~~~~~~~~~~~~~~~~~~~~~ The project maintainer has the last word on whether or not a contribution is -suitable for ``requests``. All contributions will be considered, but from time +suitable for ``Requests``. All contributions will be considered, but from time to time contributions will be rejected because they do not suit the project. If your contribution is rejected, don't despair! So long as you followed these @@ -113,7 +113,7 @@ you must either apply the feedback or withdraw your contribution. New Contributors ~~~~~~~~~~~~~~~~ -If you are new or relatively new to Open Source, welcome! ``requests`` aims to +If you are new or relatively new to Open Source, welcome! ``Requests`` aims to be a gentle introduction to the world of Open Source. If you're concerned about how best to contribute, please consider mailing a maintainer (listed above) and asking for help. From 51938de6b74cdf15b000bd8da76467784f8293e6 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sat, 25 Apr 2015 17:42:48 -0400 Subject: [PATCH 396/545] improve contributing section --- docs/dev/contributing.rst | 44 ++++++++++++--------------------------- 1 file changed, 13 insertions(+), 31 deletions(-) diff --git a/docs/dev/contributing.rst b/docs/dev/contributing.rst index 115464ad..572e408a 100644 --- a/docs/dev/contributing.rst +++ b/docs/dev/contributing.rst @@ -4,16 +4,16 @@ Contributor's Guide =================== If you're reading this you're probably interested in contributing to -``Requests``. First, I'd like to say: thankyou! Open source projects +Requests. First, We'd like to say: thank you! Open source projects live-and-die based on the support they receive from others, and the fact that -you're even *considering* supporting ``Requests`` is incredibly generous of +you're even considering supporting Requests is very generous of you. -This document lays out guidelines and advice for contributing to ``Requests``. +This document lays out guidelines and advice for contributing to Requests. If you're thinking of contributing, start by reading this thoroughly and -getting a feel for how contributing to the project works. If you've still got -questions after reading this, you should go ahead and contact either -`Ian Cordasco`_ or `Cory Benfield`_, the active maintainers. +getting a feel for how contributing to the project works. If you have any +questions, feel free to reach out to either `Ian Cordasco`_ or `Cory Benfield`_, +the primary maintainers. The guide is split into sections based on the type of contribution you're thinking of making, with a section that covers general guidelines for all @@ -29,27 +29,10 @@ All Contributions Be Cordial Or Be On Your Way ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -``Requests`` has one very important guideline governing all forms of -contribution, including things like reporting bugs or requesting features. The -guideline is `be cordial or be on your way`_. **All contributions are -welcome**, but they come with an implicit social contract: everyone must be -treated with respect. - -This can be a difficult area to judge, so the maintainers will enforce the -following policy. If any contributor acts rudely or aggressively towards any -other contributor, **regardless of whether they perceive themselves to be -acting in retaliation for an earlier breach of this guideline**, they will be -subject to the following procedure: - -1. They must apologise. This apology must be genuine in nature: "I'm sorry you - were offended" is not sufficient. The judgement of 'genuine' is at the - discretion of the maintainers. -2. If the apology is not offered, any outstanding and future contributions from - the violating contributor will be rejected immediately. - -Everyone involved in the ``Requests`` project, the maintainers included, are -bound by this policy. Failing to abide by it leads to the offender being kicked -off the project. +Requests has one very important rule governing all forms of contribution, +including reporting bugs or requesting features. This golden rule is +`be cordial or be on your way`_. **All contributions are welcome**, as long as +everyone involved is treated with respect. .. _be cordial or be on your way: http://kennethreitz.org/be-cordial-or-be-on-your-way/ @@ -69,7 +52,7 @@ Contribution Suitability ~~~~~~~~~~~~~~~~~~~~~~~~ The project maintainer has the last word on whether or not a contribution is -suitable for ``Requests``. All contributions will be considered, but from time +suitable for Requests. All contributions will be considered, but from time to time contributions will be rejected because they do not suit the project. If your contribution is rejected, don't despair! So long as you followed these @@ -96,8 +79,7 @@ When contributing code, you'll want to follow this checklist: the ones you just added*. 6. Send a GitHub Pull Request to the main repository's ``master`` branch. GitHub Pull Requests are the expected method of code collaboration on this - project. If you object to the GitHub workflow, you may mail a patch to any - of the maintainers listed above. + project. The following sub-sections go into more detail on some of the points above. @@ -113,7 +95,7 @@ you must either apply the feedback or withdraw your contribution. New Contributors ~~~~~~~~~~~~~~~~ -If you are new or relatively new to Open Source, welcome! ``Requests`` aims to +If you are new or relatively new to Open Source, welcome! Requests aims to be a gentle introduction to the world of Open Source. If you're concerned about how best to contribute, please consider mailing a maintainer (listed above) and asking for help. From 1f85ce86700130e17e553b943e85048345d36041 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sat, 25 Apr 2015 17:44:25 -0400 Subject: [PATCH 397/545] be cordial --- docs/dev/contributing.rst | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/dev/contributing.rst b/docs/dev/contributing.rst index 572e408a..53b2a8e5 100644 --- a/docs/dev/contributing.rst +++ b/docs/dev/contributing.rst @@ -26,8 +26,10 @@ contributors. All Contributions ----------------- -Be Cordial Or Be On Your Way -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Be Cordial +~~~~~~~~~~ + +**Be cordial or be on your way.** Requests has one very important rule governing all forms of contribution, including reporting bugs or requesting features. This golden rule is From e85920455485389a99719184ae4fee4848352328 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sat, 25 Apr 2015 17:49:12 -0400 Subject: [PATCH 398/545] new community --- docs/community/updates.rst | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/docs/community/updates.rst b/docs/community/updates.rst index 65e8c093..e19b48db 100644 --- a/docs/community/updates.rst +++ b/docs/community/updates.rst @@ -19,21 +19,11 @@ The best way to track the development of Requests is through Twitter ------- -I often tweet about new features and releases of Requests. +The author, Kenneth Reitz, often tweets about new features and releases of Requests. Follow `@kennethreitz `_ for updates. -Mailing List ------------- - -There's a low-volume mailing list for Requests. To subscribe to the -mailing list, send an email to -`requests@librelist.org `_. - - - - Software Updates ================ From ff71b25efc44460d941b97a021a2b58bc3e0ef2e Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Sat, 25 Apr 2015 17:50:03 -0400 Subject: [PATCH 399/545] release and version history --- docs/community/updates.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/community/updates.rst b/docs/community/updates.rst index e19b48db..b4897a83 100644 --- a/docs/community/updates.rst +++ b/docs/community/updates.rst @@ -1,8 +1,6 @@ .. _updates: - - Community Updates ================= @@ -24,8 +22,9 @@ The author, Kenneth Reitz, often tweets about new features and releases of Reque Follow `@kennethreitz `_ for updates. -Software Updates -================ + +Release and Version History +=========================== .. include:: ../../HISTORY.rst From 6e9ebc06d1056d5a488e8d9af75ae82f458bf714 Mon Sep 17 00:00:00 2001 From: Matt Havard Date: Thu, 30 Apr 2015 23:54:34 -0400 Subject: [PATCH 400/545] Make docstring of api.get more clear --- requests/api.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/requests/api.py b/requests/api.py index 98c92298..d40fa380 100644 --- a/requests/api.py +++ b/requests/api.py @@ -55,17 +55,18 @@ def request(method, url, **kwargs): return response -def get(url, **kwargs): +def get(url, params=None, **kwargs): """Sends a GET request. :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. :return: :class:`Response ` object :rtype: requests.Response """ kwargs.setdefault('allow_redirects', True) - return request('get', url, **kwargs) + return request('get', url, params=params, **kwargs) def options(url, **kwargs): From ee7389da98092f6e63f68a490e7f71651c9ec047 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sun, 3 May 2015 15:02:40 +0100 Subject: [PATCH 401/545] Synchronize urllib3 to 1.10.4 --- requests/packages/urllib3/__init__.py | 7 +- requests/packages/urllib3/response.py | 122 +++++++++++++++----------- requests/packages/urllib3/util/url.py | 2 + 3 files changed, 78 insertions(+), 53 deletions(-) diff --git a/requests/packages/urllib3/__init__.py b/requests/packages/urllib3/__init__.py index 333060c2..f48ac4af 100644 --- a/requests/packages/urllib3/__init__.py +++ b/requests/packages/urllib3/__init__.py @@ -4,7 +4,7 @@ urllib3 - Thread-safe connection pooling and re-using. __author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' __license__ = 'MIT' -__version__ = '1.10.3' +__version__ = '1.10.4' from .connectionpool import ( @@ -57,9 +57,10 @@ del NullHandler import warnings # SecurityWarning's always go off by default. -warnings.simplefilter('always', exceptions.SecurityWarning) +warnings.simplefilter('always', exceptions.SecurityWarning, append=True) # InsecurePlatformWarning's don't vary between requests, so we keep it default. -warnings.simplefilter('default', exceptions.InsecurePlatformWarning) +warnings.simplefilter('default', exceptions.InsecurePlatformWarning, + append=True) def disable_warnings(category=exceptions.HTTPWarning): """ diff --git a/requests/packages/urllib3/response.py b/requests/packages/urllib3/response.py index f1ea9bb5..24140c4c 100644 --- a/requests/packages/urllib3/response.py +++ b/requests/packages/urllib3/response.py @@ -126,14 +126,15 @@ class HTTPResponse(io.IOBase): # Are we using the chunked-style of transfer encoding? self.chunked = False self.chunk_left = None - tr_enc = self.headers.get('transfer-encoding', '') - if tr_enc.lower() == "chunked": + 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 # We certainly don't want to preload content when the response is chunked. - if not self.chunked: - if preload_content and not self._body: - self._body = self.read(decode_content=decode_content) + if not self.chunked and preload_content and not self._body: + self._body = self.read(decode_content=decode_content) def get_redirect_location(self): """ @@ -179,9 +180,8 @@ class HTTPResponse(io.IOBase): # 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: - if content_encoding in self.CONTENT_DECODERS: - self._decoder = _get_decoder(content_encoding) + 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): """ @@ -299,10 +299,9 @@ class HTTPResponse(io.IOBase): If True, will attempt to decode the body based on the 'content-encoding' header. """ - self._init_decoder() if self.chunked: - for line in self.read_chunked(amt): - yield self._decode(line, decode_content, True) + 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) @@ -387,48 +386,70 @@ class HTTPResponse(io.IOBase): b[:len(temp)] = temp return len(temp) - def read_chunked(self, amt=None): - # FIXME: Rewrite this method and make it a class with - # a better structured logic. + 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.") + + if self._original_response and self._original_response._method.upper() == 'HEAD': + # Don't bother reading the body of a HEAD request. + # FIXME: Can we do this somehow without accessing private httplib _method? + self._original_response.close() + return + while True: - # First, we'll figure out length of a chunk and then - # we'll try to read it from socket. - if self.chunk_left is None: - line = self._fp.fp.readline() - line = line.decode() - # See RFC 7230: Chunked Transfer Coding. - i = line.find(';') - if i >= 0: - line = line[:i] # Strip chunk-extensions. - try: - self.chunk_left = int(line, 16) - except ValueError: - # Invalid chunked protocol response, abort. - self.close() - raise httplib.IncompleteRead(''.join(line)) - if self.chunk_left == 0: - break - if amt is None: - chunk = self._fp._safe_read(self.chunk_left) - yield 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 - yield 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 - yield value - else: # amt > self.chunk_left - yield 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 + self._update_chunk_length() + if self.chunk_left == 0: + break + chunk = self._handle_chunk(amt) + yield self._decode(chunk, decode_content=decode_content, + flush_decoder=True) # Chunk content ends with \r\n: discard it. while True: @@ -440,5 +461,6 @@ class HTTPResponse(io.IOBase): break # We read everything; close the "file". + if self._original_response: + self._original_response.close() self.release_conn() - diff --git a/requests/packages/urllib3/util/url.py b/requests/packages/urllib3/util/url.py index b2ec834f..e58050cd 100644 --- a/requests/packages/urllib3/util/url.py +++ b/requests/packages/urllib3/util/url.py @@ -15,6 +15,8 @@ class Url(namedtuple('Url', url_attrs)): def __new__(cls, scheme=None, auth=None, host=None, port=None, path=None, query=None, fragment=None): + if path and not path.startswith('/'): + path = '/' + path return super(Url, cls).__new__(cls, scheme, auth, host, port, path, query, fragment) From b7bd29734022e634634d908e10bbf7a5ac15e76b Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sun, 3 May 2015 15:45:47 +0100 Subject: [PATCH 402/545] v2.7.0 --- HISTORY.rst | 11 +++++++++++ requests/__init__.py | 4 ++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 815a1734..09446b34 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,17 @@ Release History --------------- +2.7.0 (2015-05-03) +++++++++++++++++++ + +This is the first release that follows our new release process. For more, see +[our documentation](http://docs.python-requests.org/en/latest/community/release-process/). + +**Bugfixes** + +- Updated urllib3 to 1.10.4, resolving several bugs involving chunked transfer + encoding and response framing. + 2.6.2 (2015-04-23) ++++++++++++++++++ diff --git a/requests/__init__.py b/requests/__init__.py index 46116bea..4bca66e7 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.6.2' -__build__ = 0x020602 +__version__ = '2.7.0' +__build__ = 0x020700 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2015 Kenneth Reitz' From ba7975fd165d8511c3c83039a65deffa7341d4ca Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Sun, 3 May 2015 20:34:09 -0400 Subject: [PATCH 403/545] use reST link syntax --- HISTORY.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/HISTORY.rst b/HISTORY.rst index 09446b34..8cd2b391 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -7,7 +7,8 @@ Release History ++++++++++++++++++ This is the first release that follows our new release process. For more, see -[our documentation](http://docs.python-requests.org/en/latest/community/release-process/). +`our documentation +`_. **Bugfixes** From 3475399a472ad0813a18d0b1fbeaec773f6cdeab Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 6 May 2015 12:35:14 -0700 Subject: [PATCH 404/545] fix captilization --- requests/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/__init__.py b/requests/__init__.py index 47165761..a403663b 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -6,7 +6,7 @@ # / """ -requests HTTP library +Requests HTTP library ~~~~~~~~~~~~~~~~~~~~~ Requests is an HTTP library, written in Python, for human beings. Basic GET From ab84f9be5740d4649d734e73b84f17f85e52ffc9 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 6 May 2015 12:36:26 -0700 Subject: [PATCH 405/545] removed poor code --- requests/models.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/requests/models.py b/requests/models.py index 752c58c1..caf13d66 100644 --- a/requests/models.py +++ b/requests/models.py @@ -42,12 +42,11 @@ REDIRECT_STATI = ( codes.temporary_redirect, # 307 codes.permanent_redirect, # 308 ) + DEFAULT_REDIRECT_LIMIT = 30 CONTENT_CHUNK_SIZE = 10 * 1024 ITER_CHUNK_SIZE = 512 -json_dumps = json.dumps - class RequestEncodingMixin(object): @property @@ -425,7 +424,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if json is not None: content_type = 'application/json' - body = json_dumps(json) + body = json.dumps(json) is_stream = all([ hasattr(data, '__iter__'), From 74609c5c8f94cbae3d3fc72e0b0a53cae19cc29c Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Wed, 6 May 2015 12:42:43 -0700 Subject: [PATCH 406/545] cleanup styles a bit --- requests/models.py | 39 +++++++++++---------------------------- 1 file changed, 11 insertions(+), 28 deletions(-) diff --git a/requests/models.py b/requests/models.py index caf13d66..cb05e030 100644 --- a/requests/models.py +++ b/requests/models.py @@ -148,8 +148,7 @@ class RequestEncodingMixin(object): else: fdata = fp.read() - rf = RequestField(name=k, data=fdata, - filename=fn, headers=fh) + rf = RequestField(name=k, data=fdata, filename=fn, headers=fh) rf.make_multipart(content_type=ft) new_fields.append(rf) @@ -206,17 +205,8 @@ class Request(RequestHooksMixin): """ - def __init__(self, - method=None, - url=None, - headers=None, - files=None, - data=None, - params=None, - auth=None, - cookies=None, - hooks=None, - json=None): + def __init__(self, method=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. data = [] if data is None else data @@ -295,8 +285,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): self.hooks = default_hooks() def prepare(self, method=None, url=None, headers=None, files=None, - data=None, params=None, auth=None, cookies=None, hooks=None, - json=None): + data=None, params=None, auth=None, cookies=None, hooks=None, json=None): """Prepares the entire request with the given parameters.""" self.prepare_method(method) @@ -305,6 +294,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): self.prepare_cookies(cookies) self.prepare_body(data, files, json) self.prepare_auth(auth, url) + # Note that prepare_auth must be last to enable authentication schemes # such as OAuth to work on a fully prepared request. @@ -356,9 +346,10 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): raise InvalidURL(*e.args) if not scheme: - raise MissingSchema("Invalid URL {0!r}: No schema supplied. " - "Perhaps you meant http://{0}?".format( - to_native_string(url, 'utf8'))) + error = ("Invalid URL {0!r}: No schema supplied. Perhaps you meant http://{0}?") + error = error.format(to_native_string(url, 'utf8')) + + raise MissingSchema(error) if not host: raise InvalidURL("Invalid URL %r: No host supplied" % url) @@ -532,16 +523,8 @@ class Response(object): """ __attrs__ = [ - '_content', - 'status_code', - 'headers', - 'url', - 'history', - 'encoding', - 'reason', - 'cookies', - 'elapsed', - 'request', + '_content', 'status_code', 'headers', 'url', 'history', + 'encoding', 'reason', 'cookies', 'elapsed', 'request' ] def __init__(self): From c6c8d649af10cd5660b625fca4f44bd00541d518 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= Date: Tue, 12 May 2015 23:21:03 +0000 Subject: [PATCH 407/545] do not blindly catch all AttributeErrors see shazow/urllib3#618 --- requests/models.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/requests/models.py b/requests/models.py index f32dd5f6..f0006334 100644 --- a/requests/models.py +++ b/requests/models.py @@ -648,9 +648,10 @@ class Response(object): If decode_unicode is True, content will be decoded using the best available encoding based on the response. """ + def generate(): - try: - # Special case for urllib3. + # Special case for urllib3. + if hasattr(self.raw, 'stream'): try: for chunk in self.raw.stream(chunk_size, decode_content=True): yield chunk @@ -660,7 +661,7 @@ class Response(object): raise ContentDecodingError(e) except ReadTimeoutError as e: raise ConnectionError(e) - except AttributeError: + else: # Standard file-like object. while True: chunk = self.raw.read(chunk_size) From 86aff09103608fb5cbb11dbfbcf35e5e4927a5da Mon Sep 17 00:00:00 2001 From: Saimadhav Heblikar Date: Thu, 14 May 2015 11:42:07 +0530 Subject: [PATCH 408/545] Auth handler calls close instead of raw.close_conn --- requests/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/auth.py b/requests/auth.py index 0ff9c298..03c3302a 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -179,7 +179,7 @@ class HTTPDigestAuth(AuthBase): # Consume content and release the original connection # to allow our new request to reuse the same one. r.content - r.raw.release_conn() + r.close() prep = r.request.copy() extract_cookies_to_jar(prep._cookies, r.request, r.raw) prep.prepare_cookies(prep._cookies) From cda95d3cdee339f10d48287395368e31c386f296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?latyas=28=E6=87=92=29?= Date: Thu, 14 May 2015 18:44:33 +0800 Subject: [PATCH 409/545] morsel['max-age'] may be a str. Example: here are Set-Cookie list: ```python sclst = ['dwac_bcIBMiaagZmkYaaadeYtg11eVR=j9jHdmPjUhgDOhyH9f89X4lQgehEmflVyeA%3D|dw-only|||CNY|false|Asia%2FShanghai|true; Path=/', 'sid=j9jHdmPjUhgDOhyH9f89X4lQgehEmflVyeA; Path=/', 'geoLocation=CN; Path=/', 'dwpersonalization_fae107a9dd0fc32ed99532ec1977f31f=bc8sEiaagZqRsaaadk8XoNTL8h20150506; Expires=Sun, 14-Jun-2015 10:37:07 GMT; Path=/', 'dwanonymous_fae107a9dd0fc32ed99532ec1977f31f=abjpA8kng31LjPp8ZEERDT4XVg; Version=1; Comment="Demandware anonymous cookie for site Sites-abercrombie_cn-Site"; Max-Age=15552000; Expires=Tue, 10-Nov-2015 10:37:07 GMT; Path=/', 'myStore=91156; Path=/', 'AF_PREF=en_CN; Path=/', 'dwsid=MiHJy3KYZKDcN0lZg4HS1zSpj1VV4s_tFu39ar0KXNpAx9aX8X2LlvZQ9m5fOOknb6QXtmmukHcOjmivYf31hg==; path=/; HttpOnly'] ``` ```python for sc in sclst: C = Cookie.SimpleCookie(sc) for morsel in C.values(): cookie = requests.cookies.morsel_to_cookie(morsel) cookiejar.set_cookie(cookie) ``` Then, exception occured ```shell File "/Library/Python/2.7/site-packages/requests/cookies.py", line 402, in morsel_to_cookie expires = time.time() + morsel['max-age'] TypeError: unsupported operand type(s) for +: 'float' and 'str' ``` As Cookie.SimpleCookie is in STL, should `morsel_to_cookie` check the type of `max-age`? On the other hand, if **max-age** can not be converted to float, it's illegal obviously. --- requests/cookies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/cookies.py b/requests/cookies.py index 1fbc934c..8ca64260 100644 --- a/requests/cookies.py +++ b/requests/cookies.py @@ -415,7 +415,7 @@ def morsel_to_cookie(morsel): expires = None if morsel['max-age']: - expires = time.time() + morsel['max-age'] + expires = time.time() + float(morsel['max-age']) elif morsel['expires']: time_template = '%a, %d-%b-%Y %H:%M:%S GMT' expires = time.mktime( From fb6dade63d967623e5fb7505e05d22995dcec00e Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 13 May 2015 07:04:57 +0100 Subject: [PATCH 410/545] Partially revert ab84f9be5740d4649d734e73b84f17f85e52ffc9 --- requests/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index f32dd5f6..152cb519 100644 --- a/requests/models.py +++ b/requests/models.py @@ -30,7 +30,8 @@ from .utils import ( iter_slices, guess_json_utf, super_len, to_native_string) from .compat import ( 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 #: The set of HTTP status codes that indicate an automatically @@ -415,7 +416,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if json is not None: content_type = 'application/json' - body = json.dumps(json) + body = complexjson.dumps(json) is_stream = all([ hasattr(data, '__iter__'), From 553907aa6b588c6006babdf746e34748d630595d Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 14 May 2015 22:30:24 +0100 Subject: [PATCH 411/545] Use complexjson everywhere --- requests/models.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index 152cb519..65ffc026 100644 --- a/requests/models.py +++ b/requests/models.py @@ -792,14 +792,16 @@ class Response(object): encoding = guess_json_utf(self.content) if encoding is not None: try: - return json.loads(self.content.decode(encoding), **kwargs) + return complexjson.loads( + self.content.decode(encoding), **kwargs + ) except UnicodeDecodeError: # Wrong UTF codec detected; usually because it's not UTF-8 # but some other 8-bit codec. This is an RFC violation, # and the server didn't bother to tell us what codec *was* # used. pass - return json.loads(self.text, **kwargs) + return complexjson.loads(self.text, **kwargs) @property def links(self): From d3bd3630ee9599f1bdbc71e0efb3d3b21ee0100e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?latyas=28=E6=87=92=29?= Date: Fri, 15 May 2015 10:16:35 +0800 Subject: [PATCH 412/545] max-age may be not digits --- requests/cookies.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/requests/cookies.py b/requests/cookies.py index 8ca64260..c0865106 100644 --- a/requests/cookies.py +++ b/requests/cookies.py @@ -415,7 +415,10 @@ def morsel_to_cookie(morsel): expires = None if morsel['max-age']: - expires = time.time() + float(morsel['max-age']) + try: + expires = time.time() + float(morsel['max-age']) + except ValueError: + pass elif morsel['expires']: time_template = '%a, %d-%b-%Y %H:%M:%S GMT' expires = time.mktime( From 395a3e888506d3713dba030b73adc21ca99a305c Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 13 May 2015 07:04:57 +0100 Subject: [PATCH 413/545] Partially revert ab84f9be5740d4649d734e73b84f17f85e52ffc9 --- requests/models.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index f0006334..a7bef46b 100644 --- a/requests/models.py +++ b/requests/models.py @@ -30,7 +30,8 @@ from .utils import ( iter_slices, guess_json_utf, super_len, to_native_string) from .compat import ( 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 #: The set of HTTP status codes that indicate an automatically @@ -415,7 +416,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if json is not None: content_type = 'application/json' - body = json.dumps(json) + body = complexjson.dumps(json) is_stream = all([ hasattr(data, '__iter__'), From 059e105f8f5897ddf2da7c2317bef995ee099ab5 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 14 May 2015 22:30:24 +0100 Subject: [PATCH 414/545] Use complexjson everywhere --- requests/models.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index a7bef46b..ccaf5968 100644 --- a/requests/models.py +++ b/requests/models.py @@ -793,14 +793,16 @@ class Response(object): encoding = guess_json_utf(self.content) if encoding is not None: try: - return json.loads(self.content.decode(encoding), **kwargs) + return complexjson.loads( + self.content.decode(encoding), **kwargs + ) except UnicodeDecodeError: # Wrong UTF codec detected; usually because it's not UTF-8 # but some other 8-bit codec. This is an RFC violation, # and the server didn't bother to tell us what codec *was* # used. pass - return json.loads(self.text, **kwargs) + return complexjson.loads(self.text, **kwargs) @property def links(self): From dd0f164f8ed8e6f936c8af4442f6ddfa4d302fd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?latyas=28=E6=87=92=29?= Date: Fri, 15 May 2015 10:28:22 +0800 Subject: [PATCH 415/545] Update cookies.py --- requests/cookies.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requests/cookies.py b/requests/cookies.py index c0865106..88b478c7 100644 --- a/requests/cookies.py +++ b/requests/cookies.py @@ -416,13 +416,13 @@ def morsel_to_cookie(morsel): expires = None if morsel['max-age']: try: - expires = time.time() + float(morsel['max-age']) + expires = int(time.time() + int(morsel['max-age'])) except ValueError: - pass + raise TypeError('max-age: %s must be integer' % morsel['max-age']) elif morsel['expires']: time_template = '%a, %d-%b-%Y %H:%M:%S GMT' - expires = time.mktime( - time.strptime(morsel['expires'], time_template)) - time.timezone + expires = int(time.mktime( + time.strptime(morsel['expires'], time_template)) - time.timezone) return create_cookie( comment=morsel['comment'], comment_url=bool(morsel['comment']), From d866d8a44d76ff2f013f27d3f5d2b5b2a7766923 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 21 May 2015 10:25:42 +1000 Subject: [PATCH 416/545] Fixed typos in HISTORY.rst --- HISTORY.rst | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 8cd2b391..a9149917 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -162,7 +162,7 @@ This is the first release that follows our new release process. For more, see **Bugfixes** - Only parse the URL once (#2353) -- Allow Content-Length header to always be overriden (#2332) +- Allow Content-Length header to always be overridden (#2332) - Properly handle files in HTTPDigestAuth (#2333) - Cap redirect_cache size to prevent memory abuse (#2299) - Fix HTTPDigestAuth handling of redirects after authenticating successfully @@ -510,7 +510,7 @@ This is not a backwards compatible change. - Digest Authentication improvements. - Ensure proxy exclusions work properly. - Clearer UnicodeError exceptions. -- Automatic casting of URLs to tsrings (fURL and such) +- Automatic casting of URLs to strings (fURL and such) - Bugfixes. 0.13.6 (2012-08-06) @@ -562,7 +562,7 @@ This is not a backwards compatible change. - Removal of Requests.async in favor of `grequests `_ - Allow disabling of cookie persistiance. -- New implimentation of safe_mode +- New implementation of safe_mode - cookies.get now supports default argument - Session cookies not saved when Session.request is called with return_response=False - Env: no_proxy support. @@ -679,7 +679,7 @@ This is not a backwards compatible change. * ``Response.content`` is now bytes-only. (*Backwards Incompatible*) * New ``Response.text`` is unicode-only. -* If no ``Response.encoding`` is specified and ``chardet`` is available, ``Respoonse.text`` will guess an encoding. +* If no ``Response.encoding`` is specified and ``chardet`` is available, ``Response.text`` will guess an encoding. * Default to ISO-8859-1 (Western) encoding for "text" subtypes. * Removal of `decode_unicode`. (*Backwards Incompatible*) * New multiple-hooks system. @@ -799,7 +799,7 @@ This is not a backwards compatible change. 0.7.5 (2011-11-04) ++++++++++++++++++ -* Response.content = None if there was an invalid repsonse. +* Response.content = None if there was an invalid response. * Redirection auth handling. 0.7.4 (2011-10-26) @@ -886,7 +886,7 @@ This is not a backwards compatible change. ++++++++++++++++++ * New callback hook system -* New persistient sessions object and context manager +* New persistent sessions object and context manager * Transparent Dict-cookie handling * Status code reference object * Removed Response.cached @@ -920,7 +920,7 @@ This is not a backwards compatible change. * Redirect Fixes * settings.verbose stream writing * Querystrings for all methods -* URLErrors (Connection Refused, Timeout, Invalid URLs) are treated as explicity raised +* URLErrors (Connection Refused, Timeout, Invalid URLs) are treated as explicitly raised ``r.requests.get('hwe://blah'); r.raise_for_status()`` From d249c0a9c6dccf2bd63c3d1aad365f37cc4591a1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 21 May 2015 10:31:42 +1000 Subject: [PATCH 417/545] Fixed typo in contributing.rst --- docs/dev/contributing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/dev/contributing.rst b/docs/dev/contributing.rst index 53b2a8e5..075d042a 100644 --- a/docs/dev/contributing.rst +++ b/docs/dev/contributing.rst @@ -136,7 +136,7 @@ of other contributors, and should be avoided as much as possible. Feature Requests ---------------- -Requests is in a perpeptual feature freeze. The maintainers believe that +Requests is in a perpetual feature freeze. The maintainers believe that requests contains every major feature currently required by the vast majority of users. From 8d0889b91b7e9d487e69eea00baed666a25259af Mon Sep 17 00:00:00 2001 From: colin Date: Sun, 24 May 2015 09:59:35 -0400 Subject: [PATCH 418/545] fix contextlib.closing bug for sessions where content is not consumed (issue #2593) --- requests/models.py | 3 +++ test_requests.py | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/requests/models.py b/requests/models.py index ccaf5968..7ab21f78 100644 --- a/requests/models.py +++ b/requests/models.py @@ -842,4 +842,7 @@ class Response(object): *Note: Should not normally need to be called explicitly.* """ + if not self._content_consumed: + return self.raw.close() + return self.raw.release_conn() diff --git a/test_requests.py b/test_requests.py index cad8c055..92cdb154 100755 --- a/test_requests.py +++ b/test_requests.py @@ -9,6 +9,8 @@ import os import pickle import unittest import collections +import contextlib +import random import io import requests @@ -1252,6 +1254,27 @@ class TestCaseInsensitiveDict(unittest.TestCase): assert frozenset(cid) == keyset +class TestSessionContextClosing(unittest.TestCase): + + def _perform_session_without_consuming(self): + session = requests.Session() + + urls = [ + 'http://reddit.com', + 'http://github.com', + 'http://bitbucket.org', + ] + + for _ in range(25): + with contextlib.closing(session.get(random.choice(urls), stream=True)) as r: + pass + + return "OK" + + def test_stream_session_content_not_consumed(self): + self.assertEqual(self._perform_session_without_consuming(), "OK") + + class UtilsTestCase(unittest.TestCase): def test_super_len_io_streams(self): From 1da62a65d9b28306c3be7ad603b2721374ce7d13 Mon Sep 17 00:00:00 2001 From: colin Date: Sun, 24 May 2015 09:59:35 -0400 Subject: [PATCH 419/545] fix contextlib.closing bug for sessions where content is not consumed (issue #2593) --- AUTHORS.rst | 1 + requests/models.py | 3 +++ test_requests.py | 10 ++++++++++ 3 files changed, 14 insertions(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index b8191086..5ac39e4b 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -158,3 +158,4 @@ Patches and Suggestions - Ulrich Petri (`@ulope `_) - Muhammad Yasoob Ullah Khalid (`@yasoob `_) - Paul van der Linden (`@pvanderlinden `_) +- Colin Dickson (`@colindickson `_) diff --git a/requests/models.py b/requests/models.py index ccaf5968..7ab21f78 100644 --- a/requests/models.py +++ b/requests/models.py @@ -842,4 +842,7 @@ class Response(object): *Note: Should not normally need to be called explicitly.* """ + if not self._content_consumed: + return self.raw.close() + return self.raw.release_conn() diff --git a/test_requests.py b/test_requests.py index cad8c055..cf93c48a 100755 --- a/test_requests.py +++ b/test_requests.py @@ -9,6 +9,7 @@ import os import pickle import unittest import collections +import contextlib import io import requests @@ -1060,6 +1061,15 @@ class RequestsTestCase(unittest.TestCase): next(it) assert len(list(it)) == 3 + def test_unconsumed_session_response_closes_connection(self): + s = requests.session() + + with contextlib.closing(s.get(httpbin('stream/4'), stream=True)) as response: + pass + + self.assertEqual(response._content_consumed, False) + self.assertTrue(response.raw.closed, True) + @pytest.mark.xfail def test_response_iter_lines_reentrant(self): """Response.iter_lines() is not reentrant safe""" From 7ba130a0cb66c94eae2604a4a59d55d4be9f2713 Mon Sep 17 00:00:00 2001 From: Andrew Widdersheim Date: Tue, 26 May 2015 09:16:24 -0400 Subject: [PATCH 420/545] Docs: Add more section labels for referencing Allows for sections to be linked from other projects using Intersphinx. --- docs/user/advanced.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index a2fe653b..5b0d61cd 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -448,6 +448,8 @@ To use HTTP Basic Auth with your proxy, use the `http://user:password@host/` syn Note that proxy URLs must include the scheme. +.. _compliance: + Compliance ---------- @@ -474,6 +476,8 @@ specification in this case. If you require a different encoding, you can manually set the :attr:`Response.encoding ` property, or use the raw :attr:`Response.content `. +.. _http-verbs: + HTTP Verbs ---------- @@ -647,6 +651,8 @@ headers. Excellent. Time to write a Python program that abuses the GitHub API in all kinds of exciting ways, 4995 more times. +.. _link-headers: + Link Headers ------------ @@ -669,6 +675,8 @@ Requests will automatically parse these link headers and make them easily consum >>> r.links["last"] {'url': 'https://api.github.com/users/kennethreitz/repos?page=7&per_page=10', 'rel': 'last'} +.. _transport-adapters: + Transport Adapters ------------------ @@ -737,6 +745,8 @@ SSLv3: .. _`described here`: http://www.kennethreitz.org/essays/the-future-of-python-http .. _`urllib3`: https://github.com/shazow/urllib3 +.. _blocking-or-nonblocking: + Blocking Or Non-Blocking? ------------------------- @@ -754,6 +764,8 @@ Two excellent examples are `grequests`_ and `requests-futures`_. .. _`grequests`: https://github.com/kennethreitz/grequests .. _`requests-futures`: https://github.com/ross/requests-futures +.. _timeouts: + Timeouts -------- @@ -792,6 +804,8 @@ coffee. .. _`connect()`: http://linux.die.net/man/2/connect +.. _ca-certificates: + CA Certificates --------------- From fd31453aa25f80f04e1ce36de9abff0460bd136a Mon Sep 17 00:00:00 2001 From: colin Date: Mon, 25 May 2015 17:08:06 -0400 Subject: [PATCH 421/545] fix unittest typo --- test_requests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_requests.py b/test_requests.py index cf93c48a..4ffd6847 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1067,8 +1067,8 @@ class RequestsTestCase(unittest.TestCase): with contextlib.closing(s.get(httpbin('stream/4'), stream=True)) as response: pass - self.assertEqual(response._content_consumed, False) - self.assertTrue(response.raw.closed, True) + self.assertFalse(response._content_consumed) + self.assertTrue(response.raw.closed) @pytest.mark.xfail def test_response_iter_lines_reentrant(self): From 2e62df456ae82aedb12987a317d29b1a9123633e Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 13 Jun 2015 08:10:53 +0100 Subject: [PATCH 422/545] Avoid double releasing chunked upload connections Resolves #2636 --- requests/adapters.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index 02e0dd1f..edc1af68 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -407,9 +407,6 @@ class HTTPAdapter(BaseAdapter): # Then, reraise so that we can handle the actual exception. low_conn.close() raise - else: - # All is well, return the connection to the pool. - conn._put_conn(low_conn) except (ProtocolError, socket.error) as err: raise ConnectionError(err, request=request) From e3bdec5934a4bdd00405acbb714aec81980f6097 Mon Sep 17 00:00:00 2001 From: Marc Abramowitz Date: Mon, 22 Jun 2015 13:22:59 -0700 Subject: [PATCH 423/545] Display URL as part of HTTP error messages It seems convenient to include the URL in the error message in case you get an unexpected error. E.g.: In [1]: import requests In [2]: resp = requests.get('http://www.google.com/eofdfdfdfdfd') In [3]: resp Out[3]: In [4]: resp.raise_for_status() --------------------------------------------------------------------------- HTTPError Traceback (most recent call last) in () ----> 1 resp.raise_for_status() /Users/marca/dev/git-repos/requests/requests/models.py in raise_for_status(self) 835 836 if http_error_msg: --> 837 raise HTTPError(http_error_msg, response=self) 838 839 def close(self): HTTPError: 404 Client Error: Not Found for url: http://www.google.com/eofdfdfdfdfd --- requests/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index 7ab21f78..4270c647 100644 --- a/requests/models.py +++ b/requests/models.py @@ -828,10 +828,10 @@ class Response(object): http_error_msg = '' 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: - 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: raise HTTPError(http_error_msg, response=self) From a560e094279c755b7a9bd5f3d29c303155ed8b27 Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Mon, 29 Jun 2015 10:56:51 +0900 Subject: [PATCH 424/545] Allow get_netrc_auth to raise parse/permission errors to caller If the netrc file exists but cannot be parsed or read, get_netrc_auth silently fails. Add a new argument `ignore_errors` which when set to False will cause any parse/permission errors to be raised to the caller. The default value is True, which means the default behavior is unchanged. Fixes #2654 Change-Id: I7436aaaf593178673ab84fd9e7ab4bcb0e3fe75e --- requests/utils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/requests/utils.py b/requests/utils.py index 8fba62dd..db841a36 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -67,7 +67,7 @@ def super_len(o): return len(o.getvalue()) -def get_netrc_auth(url): +def get_netrc_auth(url, ignore_errors=True): """Returns the Requests tuple auth for a given url from netrc.""" try: @@ -106,7 +106,8 @@ def get_netrc_auth(url): except (NetrcParseError, IOError): # If there was a parsing error or a permissions issue reading the file, # we'll just skip netrc auth - pass + if not ignore_errors: + raise # AppEngine hackiness. except (ImportError, AttributeError): From 884cb7a7fbaea704427ed878dfbdeae3744c9e71 Mon Sep 17 00:00:00 2001 From: David Pursehouse Date: Mon, 29 Jun 2015 12:22:57 +0900 Subject: [PATCH 425/545] Change ignore_errors to raise_errors in get_netrc_auth Change-Id: Ib82c7c614edafc15e5db858d9c1b9a73aebd8a95 --- requests/utils.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requests/utils.py b/requests/utils.py index db841a36..3fd0e41f 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -67,7 +67,7 @@ def super_len(o): return len(o.getvalue()) -def get_netrc_auth(url, ignore_errors=True): +def get_netrc_auth(url, raise_errors=False): """Returns the Requests tuple auth for a given url from netrc.""" try: @@ -105,8 +105,8 @@ def get_netrc_auth(url, ignore_errors=True): return (_netrc[login_i], _netrc[2]) except (NetrcParseError, IOError): # If there was a parsing error or a permissions issue reading the file, - # we'll just skip netrc auth - if not ignore_errors: + # we'll just skip netrc auth unless explicitly asked to raise errors. + if raise_errors: raise # AppEngine hackiness. From e9a1e35f8989691afe99407d9b65a67684aa8bac Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Fri, 3 Jul 2015 17:00:38 +0100 Subject: [PATCH 426/545] Only pass useful timeouts to _get_conn --- requests/adapters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requests/adapters.py b/requests/adapters.py index edc1af68..841cbbfe 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -35,6 +35,7 @@ from .auth import _basic_auth_str DEFAULT_POOLBLOCK = False DEFAULT_POOLSIZE = 10 DEFAULT_RETRIES = 0 +DEFAULT_POOL_TIMEOUT = None class BaseAdapter(object): @@ -375,7 +376,7 @@ class HTTPAdapter(BaseAdapter): if hasattr(conn, 'proxy_pool'): conn = conn.proxy_pool - low_conn = conn._get_conn(timeout=timeout) + low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT) try: low_conn.putrequest(request.method, From 147a305da8c7c5e1fa44d073f97c865f4e81bfd7 Mon Sep 17 00:00:00 2001 From: Gert Van Gool Date: Mon, 13 Jul 2015 14:21:12 +0200 Subject: [PATCH 427/545] Adds extra tests for CaseInsensitiveDict - Test for NotImplemented in __eq__ - Adds test for copy() - Adds test for __repr__() --- test_requests.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test_requests.py b/test_requests.py index 4ffd6847..168a2d93 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1224,6 +1224,7 @@ class TestCaseInsensitiveDict(unittest.TestCase): del othercid['spam'] assert cid != othercid assert cid == {'spam': 'blueval', 'eggs': 'redval'} + assert cid.__eq__(object()) == NotImplemented def test_setdefault(self): cid = CaseInsensitiveDict({'Spam': 'blueval'}) @@ -1261,6 +1262,20 @@ class TestCaseInsensitiveDict(unittest.TestCase): assert frozenset(cid.keys()) == keyset assert frozenset(cid) == keyset + def test_copy(self): + cid = CaseInsensitiveDict({ + 'Accept': 'application/json', + 'user-Agent': 'requests', + }) + assert cid == cid.copy() + + def test_repr(self): + cid = CaseInsensitiveDict({ + 'Accept': 'application/json', + 'user-Agent': 'requests', + }) + assert repr(cid) == "{'Accept': 'application/json', 'user-Agent': 'requests'}" + class UtilsTestCase(unittest.TestCase): From 47e71d1774745543b743bdf570d9d8e7151355e6 Mon Sep 17 00:00:00 2001 From: Gert Van Gool Date: Mon, 13 Jul 2015 15:07:38 +0200 Subject: [PATCH 428/545] Don't test the NotImplemented, just make sure it's False --- test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index 168a2d93..aac4d8d0 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1224,7 +1224,7 @@ class TestCaseInsensitiveDict(unittest.TestCase): del othercid['spam'] assert cid != othercid assert cid == {'spam': 'blueval', 'eggs': 'redval'} - assert cid.__eq__(object()) == NotImplemented + assert cid != object() def test_setdefault(self): cid = CaseInsensitiveDict({'Spam': 'blueval'}) From 3b325f1bc5df476c4c4e93c5a742d81e2c5bd950 Mon Sep 17 00:00:00 2001 From: Gert Van Gool Date: Tue, 14 Jul 2015 08:10:13 +0200 Subject: [PATCH 429/545] assert that the copy() is not the original --- test_requests.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index aac4d8d0..5f4f472a 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1267,7 +1267,10 @@ class TestCaseInsensitiveDict(unittest.TestCase): 'Accept': 'application/json', 'user-Agent': 'requests', }) - assert cid == cid.copy() + cid_copy = cid.copy() + assert cid == cid_copy + cid['changed'] = True + assert cid != cid_copy def test_repr(self): cid = CaseInsensitiveDict({ From beaa7e4180ad6679424dda3d6bbd0368591ada17 Mon Sep 17 00:00:00 2001 From: Peter Marsh Date: Wed, 15 Jul 2015 23:28:09 +0100 Subject: [PATCH 430/545] Fix quickstart "Custom Headers" example intro Previously this section prefaced an example with: For example, we didn't specify our content-type But, the actual example set a custom user-agent header on the request. This changes it to say "user-agent" instead which matches the given example. --- docs/user/quickstart.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 7b14610c..cac4ace0 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -182,7 +182,7 @@ Custom Headers If you'd like to add HTTP headers to a request, simply pass in a ``dict`` to the ``headers`` parameter. -For example, we didn't specify our content-type in the previous example:: +For example, we didn't specify our user-agent in the previous example:: >>> import json >>> url = 'https://api.github.com/some/endpoint' From 5ec7576f2311b5b881ff2426e05b6b35a9aa8bdb Mon Sep 17 00:00:00 2001 From: Susan Tan Date: Fri, 17 Jul 2015 01:18:02 -0700 Subject: [PATCH 431/545] Catch and wrap ClosedPoolError Partially resolves #1572: "urllib3 exceptions passing through requests API". Inspired from Lukasa's 2605be11d82d42438ac7c3993810c955bde74cef. --- requests/adapters.py | 4 ++++ test_requests.py | 10 ++++++++++ 2 files changed, 14 insertions(+) diff --git a/requests/adapters.py b/requests/adapters.py index 841cbbfe..cf468608 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -19,6 +19,7 @@ from .compat import urlparse, basestring from .utils import (DEFAULT_CA_BUNDLE_PATH, get_encoding_from_headers, prepend_scheme_if_needed, get_auth_from_url, urldefragauth) from .structures import CaseInsensitiveDict +from .packages.urllib3.exceptions import ClosedPoolError from .packages.urllib3.exceptions import ConnectTimeoutError from .packages.urllib3.exceptions import HTTPError as _HTTPError from .packages.urllib3.exceptions import MaxRetryError @@ -421,6 +422,9 @@ class HTTPAdapter(BaseAdapter): raise ConnectionError(e, request=request) + except ClosedPoolError as e: + raise ConnectionError(e, request=request) + except _ProxyError as e: raise ProxyError(e) diff --git a/test_requests.py b/test_requests.py index 5f4f472a..6b43a92c 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1655,6 +1655,16 @@ def test_urllib3_retries(): with pytest.raises(RetryError): s.get(httpbin('status/500')) + +def test_urllib3_pool_connection_closed(): + s = requests.Session() + s.mount('http://', HTTPAdapter(pool_connections=0, pool_maxsize=0)) + + try: + s.get(httpbin('status/200')) + except ConnectionError as e: + assert u"HTTPConnectionPool(host='httpbin.org', port=80): Pool is closed." in str(e.message) + def test_vendor_aliases(): from requests.packages import urllib3 from requests.packages import chardet From 2b5cdc56f36f083666fb9abaed2adc9b9c60529b Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 18 Jul 2015 16:41:29 +0100 Subject: [PATCH 432/545] Expand on chunk length --- docs/user/advanced.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 494ba696..fccc7320 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -278,7 +278,8 @@ For chunked encoded responses, it's best to iterate over the data using :meth:`Response.iter_content() `. In an ideal situation you'll have set ``stream=True`` on the request, in which case you can iterate chunk-by-chunk by calling ``iter_content`` with a chunk -size parameter of ``None``. +size parameter of ``None``. If you want to set a maximum size of the chunk, +you can set a chunk size parameter to any integer. .. _multipart: From d7026bfb2a015c2187009e7e159bc6abc251c355 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 18 Jul 2015 10:43:26 -0500 Subject: [PATCH 433/545] Upgrade urllib3 to 7fcfcd08a6a2150f93144d111904d1e4fb65c909 --- requests/packages/urllib3/__init__.py | 4 +- requests/packages/urllib3/connection.py | 14 +- requests/packages/urllib3/connectionpool.py | 45 ++-- .../packages/urllib3/contrib/appengine.py | 222 ++++++++++++++++++ .../packages/urllib3/contrib/pyopenssl.py | 26 +- requests/packages/urllib3/exceptions.py | 21 ++ requests/packages/urllib3/poolmanager.py | 6 +- requests/packages/urllib3/request.py | 12 +- requests/packages/urllib3/response.py | 148 +++++++----- requests/packages/urllib3/util/connection.py | 2 + requests/packages/urllib3/util/response.py | 54 +++++ requests/packages/urllib3/util/retry.py | 2 +- requests/packages/urllib3/util/ssl_.py | 31 ++- 13 files changed, 476 insertions(+), 111 deletions(-) create mode 100644 requests/packages/urllib3/contrib/appengine.py diff --git a/requests/packages/urllib3/__init__.py b/requests/packages/urllib3/__init__.py index f48ac4af..b80f19d2 100644 --- a/requests/packages/urllib3/__init__.py +++ b/requests/packages/urllib3/__init__.py @@ -4,7 +4,7 @@ urllib3 - Thread-safe connection pooling and re-using. __author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' __license__ = 'MIT' -__version__ = '1.10.4' +__version__ = 'dev' from .connectionpool import ( @@ -58,6 +58,8 @@ del NullHandler import warnings # 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) # InsecurePlatformWarning's don't vary between requests, so we keep it default. warnings.simplefilter('default', exceptions.InsecurePlatformWarning, append=True) diff --git a/requests/packages/urllib3/connection.py b/requests/packages/urllib3/connection.py index 2a8c3596..f64dd1a9 100644 --- a/requests/packages/urllib3/connection.py +++ b/requests/packages/urllib3/connection.py @@ -38,7 +38,7 @@ except NameError: # Python 2: from .exceptions import ( ConnectTimeoutError, SystemTimeWarning, - SecurityWarning, + SubjectAltNameWarning, ) from .packages.ssl_match_hostname import match_hostname @@ -192,6 +192,9 @@ class VerifiedHTTPSConnection(HTTPSConnection): cert_reqs=None, ca_certs=None, assert_hostname=None, assert_fingerprint=None): + if ca_certs and cert_reqs is None: + cert_reqs = 'CERT_REQUIRED' + self.key_file = key_file self.cert_file = cert_file self.cert_reqs = cert_reqs @@ -245,10 +248,11 @@ class VerifiedHTTPSConnection(HTTPSConnection): cert = self.sock.getpeercert() if not cert.get('subjectAltName', ()): warnings.warn(( - 'Certificate has no `subjectAltName`, falling back to check for a `commonName` for now. ' - 'This feature is being removed by major browsers and deprecated by RFC 2818. ' - '(See https://github.com/shazow/urllib3/issues/497 for details.)'), - SecurityWarning + 'Certificate for {0} has no `subjectAltName`, falling back to check for a ' + '`commonName` for now. This feature is being removed by major browsers and ' + 'deprecated by RFC 2818. (See https://github.com/shazow/urllib3/issues/497 ' + 'for details.)'.format(hostname)), + SubjectAltNameWarning ) match_hostname(cert, self.assert_hostname or hostname) diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index 117269ac..c9587257 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -17,6 +17,7 @@ from .exceptions import ( ClosedPoolError, ProtocolError, EmptyPoolError, + HeaderParsingError, HostChangedError, LocationValueError, MaxRetryError, @@ -38,9 +39,10 @@ from .request import RequestMethods from .response import HTTPResponse from .util.connection import is_connection_dropped +from .util.response import assert_header_parsing from .util.retry import Retry from .util.timeout import Timeout -from .util.url import get_host +from .util.url import get_host, Url xrange = six.moves.xrange @@ -120,7 +122,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): :param maxsize: 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 been used. @@ -381,8 +383,19 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): log.debug("\"%s %s %s\" %s %s" % (method, url, http_version, httplib_response.status, 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 + def _absolute_url(self, path): + return Url(scheme=self.scheme, host=self.host, port=self.port, path=path).url + def close(self): """ Close all pooled connections and disable the pool. @@ -409,7 +422,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # TODO: Add optional support for socket.gethostbyname checking. scheme, host, port = get_host(url) - + # Use explicit default port for comparison when none is given if self.port and not port: port = port_by_scheme.get(scheme) @@ -568,25 +581,22 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # Close the connection. If a connection is reused on which there # was a Certificate error, the next request will certainly raise # another Certificate error. - if conn: - conn.close() - conn = None + conn = conn and conn.close() + release_conn = True raise SSLError(e) except SSLError: # Treat SSLError separately from BaseSSLError to preserve # traceback. - if conn: - conn.close() - conn = None + conn = conn and conn.close() + release_conn = True raise except (TimeoutError, HTTPException, SocketError, ConnectionError) as e: - if conn: - # Discard the connection for these exceptions. It will be - # be replaced during the next _get_conn() call. - conn.close() - conn = None + # Discard the connection for these exceptions. It will be + # be replaced during the next _get_conn() call. + conn = conn and conn.close() + release_conn = True if isinstance(e, SocketError) and self.proxy: e = ProxyError('Cannot connect to proxy.', e) @@ -626,6 +636,9 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): retries = retries.increment(method, url, response=response, _pool=self) except MaxRetryError: 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 return response @@ -683,6 +696,10 @@ class HTTPSConnectionPool(HTTPConnectionPool): HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize, block, headers, retries, _proxy, _proxy_headers, **conn_kw) + + if ca_certs and cert_reqs is None: + cert_reqs = 'CERT_REQUIRED' + self.key_file = key_file self.cert_file = cert_file self.cert_reqs = cert_reqs diff --git a/requests/packages/urllib3/contrib/appengine.py b/requests/packages/urllib3/contrib/appengine.py new file mode 100644 index 00000000..ed9d8b81 --- /dev/null +++ b/requests/packages/urllib3/contrib/appengine.py @@ -0,0 +1,222 @@ +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_v2(): + 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 e.message: + 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 e.message: + 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_v1(): + # 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'] + + 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 not timeout.read is 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_v1() or + is_prod_appengine_v2()) + + +def is_appengine_sandbox(): + return is_appengine() and not is_prod_appengine_v2() + + +def is_local_appengine(): + return ('APPENGINE_RUNTIME' in os.environ and + 'Development/' in os.environ['SERVER_SOFTWARE']) + + +def is_prod_appengine_v1(): + return ('APPENGINE_RUNTIME' in os.environ and + 'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and + not is_prod_appengine_v2()) + + +def is_prod_appengine_v2(): + return os.environ.get('GAE_VM', False) == 'true' diff --git a/requests/packages/urllib3/contrib/pyopenssl.py b/requests/packages/urllib3/contrib/pyopenssl.py index b2c34a89..19c5b4ee 100644 --- a/requests/packages/urllib3/contrib/pyopenssl.py +++ b/requests/packages/urllib3/contrib/pyopenssl.py @@ -85,6 +85,14 @@ _openssl_verify = { DEFAULT_SSL_CIPHER_LIST = util.ssl_.DEFAULT_CIPHERS +# OpenSSL will only write 16K at a time +SSL_WRITE_BLOCKSIZE = 16384 + +try: + _ = memoryview + has_memoryview = True +except NameError: + has_memoryview = False orig_util_HAS_SNI = util.HAS_SNI orig_connection_ssl_wrap_socket = connection.ssl_wrap_socket @@ -204,13 +212,21 @@ class WrappedSocket(object): continue def sendall(self, data): - while len(data): - sent = self._send_until_done(data) - data = data[sent:] + if has_memoryview and not isinstance(data, memoryview): + data = memoryview(data) + + total_sent = 0 + while total_sent < len(data): + 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): if self._makefile_refs < 1: - return self.connection.shutdown() + return self.connection.close() else: self._makefile_refs -= 1 @@ -287,7 +303,7 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, raise timeout('select timed out') continue except OpenSSL.SSL.Error as e: - raise ssl.SSLError('bad handshake', e) + raise ssl.SSLError('bad handshake: %r' % e) break return WrappedSocket(cnx, sock) diff --git a/requests/packages/urllib3/exceptions.py b/requests/packages/urllib3/exceptions.py index 31bda1c0..36ce0d1e 100644 --- a/requests/packages/urllib3/exceptions.py +++ b/requests/packages/urllib3/exceptions.py @@ -149,6 +149,11 @@ class SecurityWarning(HTTPWarning): pass +class SubjectAltNameWarning(SecurityWarning): + "Warned when connecting to a host with a certificate missing a SAN." + pass + + class InsecureRequestWarning(SecurityWarning): "Warned when making an unverified HTTPS request." pass @@ -167,3 +172,19 @@ class InsecurePlatformWarning(SecurityWarning): 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) diff --git a/requests/packages/urllib3/poolmanager.py b/requests/packages/urllib3/poolmanager.py index b8d1e745..76b6a129 100644 --- a/requests/packages/urllib3/poolmanager.py +++ b/requests/packages/urllib3/poolmanager.py @@ -8,7 +8,7 @@ except ImportError: from ._collections import RecentlyUsedContainer from .connectionpool import HTTPConnectionPool, HTTPSConnectionPool from .connectionpool import port_by_scheme -from .exceptions import LocationValueError, MaxRetryError +from .exceptions import LocationValueError, MaxRetryError, ProxySchemeUnknown from .request import RequestMethods from .util.url import parse_url from .util.retry import Retry @@ -227,8 +227,8 @@ class ProxyManager(PoolManager): port = port_by_scheme.get(proxy.scheme, 80) proxy = proxy._replace(port=port) - assert proxy.scheme in ("http", "https"), \ - 'Not supported proxy scheme %s' % proxy.scheme + if proxy.scheme not in ("http", "https"): + raise ProxySchemeUnknown(proxy.scheme) self.proxy = proxy self.proxy_headers = proxy_headers or {} diff --git a/requests/packages/urllib3/request.py b/requests/packages/urllib3/request.py index b08d6c92..a1a12bc5 100644 --- a/requests/packages/urllib3/request.py +++ b/requests/packages/urllib3/request.py @@ -71,14 +71,22 @@ class RequestMethods(object): headers=headers, **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 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: 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, encode_multipart=True, multipart_boundary=None, diff --git a/requests/packages/urllib3/response.py b/requests/packages/urllib3/response.py index 24140c4c..15d4aaca 100644 --- a/requests/packages/urllib3/response.py +++ b/requests/packages/urllib3/response.py @@ -2,6 +2,7 @@ try: import http.client as httplib except ImportError: import httplib +from contextlib import contextmanager import zlib import io from socket import timeout as SocketTimeout @@ -12,7 +13,7 @@ from .exceptions import ( ) from .packages.six import string_types as basestring, binary_type, PY3 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): @@ -202,6 +203,47 @@ class HTTPResponse(io.IOBase): return data + @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. + """ + 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 as e: + # This includes IncompleteRead. + raise ProtocolError('Connection broken: %r' % e, e) + except Exception: + # 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 and not self._original_response.isclosed(): + self._original_response.close() + + raise + finally: + if self._original_response and self._original_response.isclosed(): + self.release_conn() + def read(self, amt=None, decode_content=None, cache_content=False): """ Similar to :meth:`httplib.HTTPResponse.read`, but with two additional @@ -231,45 +273,28 @@ class HTTPResponse(io.IOBase): return flush_decoder = False + data = None - try: - try: - if amt is None: - # cStringIO doesn't like amt=None - data = self._fp.read() + with self._error_catcher(): + if amt is None: + # cStringIO doesn't like amt=None + 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 - 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 '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 as e: - # This includes IncompleteRead. - raise ProtocolError('Connection broken: %r' % e, e) + if data: self._fp_bytes_read += len(data) data = self._decode(data, decode_content, flush_decoder) @@ -277,11 +302,8 @@ class HTTPResponse(io.IOBase): if cache_content: 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): """ @@ -319,6 +341,7 @@ class HTTPResponse(io.IOBase): with ``original_response=r``. """ headers = r.msg + if not isinstance(headers, HTTPHeaderDict): if PY3: # Python 3 headers = HTTPHeaderDict(headers.items()) @@ -437,30 +460,29 @@ class HTTPResponse(io.IOBase): raise ResponseNotChunked("Response is not chunked. " "Header 'transfer-encoding: chunked' is missing.") - if self._original_response and self._original_response._method.upper() == 'HEAD': - # Don't bother reading the body of a HEAD request. - # FIXME: Can we do this somehow without accessing private httplib _method? + # 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 - while True: - self._update_chunk_length() - if self.chunk_left == 0: - break - chunk = self._handle_chunk(amt) - yield self._decode(chunk, decode_content=decode_content, - flush_decoder=True) + with self._error_catcher(): + while True: + self._update_chunk_length() + if self.chunk_left == 0: + break + chunk = self._handle_chunk(amt) + yield self._decode(chunk, decode_content=decode_content, + flush_decoder=True) - # 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 + # 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() - self.release_conn() + # We read everything; close the "file". + if self._original_response: + self._original_response.close() diff --git a/requests/packages/urllib3/util/connection.py b/requests/packages/urllib3/util/connection.py index 859aec6e..9ed5a64c 100644 --- a/requests/packages/urllib3/util/connection.py +++ b/requests/packages/urllib3/util/connection.py @@ -60,6 +60,8 @@ def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, """ host, port = address + if host.startswith('['): + host = host.strip('[]') err = None for res in socket.getaddrinfo(host, port, 0, socket.SOCK_STREAM): af, socktype, proto, canonname, sa = res diff --git a/requests/packages/urllib3/util/response.py b/requests/packages/urllib3/util/response.py index 45fff552..767ee158 100644 --- a/requests/packages/urllib3/util/response.py +++ b/requests/packages/urllib3/util/response.py @@ -1,3 +1,11 @@ +try: + import http.client as httplib +except ImportError: + import httplib + +from ..exceptions import HeaderParsingError + + def is_fp_closed(obj): """ Checks whether a given file-like object is closed. @@ -20,3 +28,49 @@ def is_fp_closed(obj): pass 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 {}.'.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, wether a 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' diff --git a/requests/packages/urllib3/util/retry.py b/requests/packages/urllib3/util/retry.py index 7e0959df..1fb1f23b 100644 --- a/requests/packages/urllib3/util/retry.py +++ b/requests/packages/urllib3/util/retry.py @@ -94,7 +94,7 @@ class Retry(object): 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 - than :attr:`Retry.MAX_BACKOFF`. + than :attr:`Retry.BACKOFF_MAX`. By default, backoff is disabled (set to 0). diff --git a/requests/packages/urllib3/util/ssl_.py b/requests/packages/urllib3/util/ssl_.py index b846d42c..311378bf 100644 --- a/requests/packages/urllib3/util/ssl_.py +++ b/requests/packages/urllib3/util/ssl_.py @@ -8,6 +8,13 @@ SSLContext = None HAS_SNI = False create_default_context = None +# Maps the length of a digest to a possible hash function producing this digest +HASHFUNC_MAP = { + 32: md5, + 40: sha1, + 64: sha256, +} + import errno import warnings @@ -112,31 +119,21 @@ def assert_fingerprint(cert, fingerprint): 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, - 32: sha256, - } - fingerprint = fingerprint.replace(':', '').lower() - digest_length, odd = divmod(len(fingerprint), 2) - - if odd or digest_length not in hashfunc_map: - raise SSLError('Fingerprint is of invalid length.') + digest_length = len(fingerprint) + hashfunc = HASHFUNC_MAP.get(digest_length) + if not hashfunc: + raise SSLError( + 'Fingerprint of invalid length: {0}'.format(fingerprint)) # We need encode() here for py32; works on py2 and p33. fingerprint_bytes = unhexlify(fingerprint.encode()) - hashfunc = hashfunc_map[digest_length] - cert_digest = hashfunc(cert).digest() - if not cert_digest == fingerprint_bytes: + if cert_digest != fingerprint_bytes: raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".' - .format(hexlify(fingerprint_bytes), - hexlify(cert_digest))) + .format(fingerprint, hexlify(cert_digest))) def resolve_cert_reqs(candidate): From b8a297baab501f5818a8041ca6dac15b1d29dafd Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 18 Jul 2015 10:47:23 -0500 Subject: [PATCH 434/545] Remove broken CaseInsensitiveDict repr test Fixes #2668 --- test_requests.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test_requests.py b/test_requests.py index 5f4f472a..a5d63464 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1272,13 +1272,6 @@ class TestCaseInsensitiveDict(unittest.TestCase): cid['changed'] = True assert cid != cid_copy - def test_repr(self): - cid = CaseInsensitiveDict({ - 'Accept': 'application/json', - 'user-Agent': 'requests', - }) - assert repr(cid) == "{'Accept': 'application/json', 'user-Agent': 'requests'}" - class UtilsTestCase(unittest.TestCase): From 31817b66c6b793689c93a5b558384fb53d0576f7 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sat, 18 Jul 2015 10:57:28 -0500 Subject: [PATCH 435/545] Fix pool connection closed test --- test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index 3c7d5351..a4bea18f 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1683,7 +1683,7 @@ def test_urllib3_pool_connection_closed(): try: s.get(httpbin('status/200')) except ConnectionError as e: - assert u"HTTPConnectionPool(host='httpbin.org', port=80): Pool is closed." in str(e.message) + assert u"HTTPConnectionPool(host='httpbin.org', port=80): Pool is closed." in str(e) def test_vendor_aliases(): from requests.packages import urllib3 From 6648defcddeff2e5e4022f51a9fd7b6c224bc472 Mon Sep 17 00:00:00 2001 From: Christopher Peplin Date: Sat, 18 Jul 2015 18:50:29 -0400 Subject: [PATCH 436/545] Fix docs for passing a list of values for a query string The special `[]` notation at the end of the field name is not necessary to get a field to appear with multiple values in the query string. --- docs/user/quickstart.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index cac4ace0..42202648 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -73,13 +73,12 @@ You can see that the URL has been correctly encoded by printing the URL:: Note that any dictionary key whose value is ``None`` will not be added to the URL's query string. -In order to pass a list of items as a value you must mark the key as -referring to a list like string by appending ``[]`` to the key:: +You can also pass a list of items as a value:: - >>> payload = {'key1': 'value1', 'key2[]': ['value2', 'value3']} + >>> payload = {'key1': 'value1', 'key2': ['value2', 'value3']} >>> r = requests.get("http://httpbin.org/get", params=payload) >>> print(r.url) - http://httpbin.org/get?key1=value1&key2%5B%5D=value2&key2%5B%5D=value3 + http://httpbin.org/get?key1=value1&key2=value2&key2=value3 Response Content ---------------- From e8a4784c8fa8cb137c6d1c102059f0b2f501c175 Mon Sep 17 00:00:00 2001 From: Matej Stuchlik Date: Tue, 4 Aug 2015 15:05:35 +0200 Subject: [PATCH 437/545] Mention SNI being backported to Python2.7.9 in FAQ That happened as part of PEP 466 - "Network Security Enhancements for Python 2.7" --- docs/community/faq.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/community/faq.rst b/docs/community/faq.rst index c5daee46..f869ee9a 100644 --- a/docs/community/faq.rst +++ b/docs/community/faq.rst @@ -79,9 +79,9 @@ when servers are using `Virtual Hosting`_. When such servers are hosting more than one SSL site they need to be able to return the appropriate certificate based on the hostname the client is connecting to. -Python3's SSL module includes native support for SNI. This support has not been -back ported to Python2. For information on using SNI with Requests on Python2 -refer to this `Stack Overflow answer`_. +Python3 and Python 2.7.9+ include native support for SNI in their SSL modules. +For information on using SNI with Requests on Python < 2.7.9 refer to this +`Stack Overflow answer`_. .. _`Server-Name-Indication`: https://en.wikipedia.org/wiki/Server_Name_Indication .. _`virtual hosting`: https://en.wikipedia.org/wiki/Virtual_hosting From 276202f51ee9967969eafc1880c4785c80d63d3b Mon Sep 17 00:00:00 2001 From: Akira J Date: Tue, 4 Aug 2015 22:47:12 +0100 Subject: [PATCH 438/545] Fix merge setting for not preserving original order of dict parameters --- requests/sessions.py | 4 +--- test_requests.py | 7 +++++++ 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index 820919ee..31844eb8 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -63,12 +63,10 @@ def merge_setting(request_setting, session_setting, dict_class=OrderedDict): merged_setting.update(to_key_val_list(request_setting)) # Remove keys that are set to None. - for (k, v) in request_setting.items(): + for (k, v) in merged_setting.items(): if v is None: del merged_setting[k] - merged_setting = dict((k, v) for (k, v) in merged_setting.items() if v is not None) - return merged_setting diff --git a/test_requests.py b/test_requests.py index a5d63464..7e5e4d8f 100755 --- a/test_requests.py +++ b/test_requests.py @@ -125,6 +125,13 @@ class RequestsTestCase(unittest.TestCase): "http://example.com/path?key=value#fragment", params={"a": "b"}).prepare() assert request.url == "http://example.com/path?key=value&a=b#fragment" + def test_params_original_order_is_preserved_by_default(self): + param_ordered_dict = collections.OrderedDict((('z', 1), ('a', 1), ('k', 1), ('d', 1))) + session = requests.Session() + request = requests.Request('GET', 'http://example.com/', params=param_ordered_dict) + prep = session.prepare_request(request) + assert prep.url == 'http://example.com/?z=1&a=1&k=1&d=1' + def test_mixed_case_scheme_acceptable(self): s = requests.Session() s.proxies = getproxies() From 75c1776bde6398489c1b6722cf4f612b0061bfc1 Mon Sep 17 00:00:00 2001 From: Lukas Graf Date: Fri, 7 Aug 2015 20:21:39 +0200 Subject: [PATCH 439/545] Document use of sessions as context managers. --- docs/user/advanced.rst | 9 +++++++++ requests/sessions.py | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index bfdea39c..d36331ad 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -42,6 +42,15 @@ Any dictionaries that you pass to a request method will be merged with the session-level values that are set. The method-level parameters override session parameters. +Sessions can also be used as context managers:: + + with requests.Session() as s: + s.get('http://httpbin.org/cookies/set/sessioncookie/123456789') + +This will make sure the session is closed as soon as the ``with`` block is +exited, even if unhandled exceptions occured. + + .. admonition:: Remove a Value From a Dict Parameter Sometimes you'll want to omit session-level keys from a dict parameter. To diff --git a/requests/sessions.py b/requests/sessions.py index 820919ee..71ced5a5 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -275,6 +275,12 @@ class Session(SessionRedirectMixin): >>> s = requests.Session() >>> s.get('http://httpbin.org/get') 200 + + Or as a context manager:: + + >>> with requests.Session() as s: + >>> s.get('http://httpbin.org/get') + 200 """ __attrs__ = [ From 8f3ddbeefba5f3c57310741259328dd84854bcef Mon Sep 17 00:00:00 2001 From: Lukas Graf Date: Fri, 7 Aug 2015 20:25:02 +0200 Subject: [PATCH 440/545] Mention connection pooling in session docs. --- docs/user/advanced.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index d36331ad..080ca24b 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -12,7 +12,10 @@ Session Objects The Session object allows you to persist certain parameters across requests. It also persists cookies across all requests made from the -Session instance. +Session instance, and will use ``urllib3``'s `connection pooling`_. So if +you're making several requests to the same host, the underlying TCP +connection will be reused, which can result in a significant performance +increase (see `HTTP persistent connection`_). A Session object has all the methods of the main Requests API. @@ -836,5 +839,7 @@ system. For the sake of security we recommend upgrading certifi frequently! +.. _HTTP persistent connection: https://en.wikipedia.org/wiki/HTTP_persistent_connection +.. _connection pooling: https://urllib3.readthedocs.org/en/latest/pools.html .. _certifi: http://certifi.io/ .. _Mozilla trust store: https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt From 1bb91653ccb9ddb8252697eb10b506fb4108feb0 Mon Sep 17 00:00:00 2001 From: Lukas Graf Date: Sat, 8 Aug 2015 00:19:10 +0200 Subject: [PATCH 441/545] Docs: Clarify that method-level params are not persisted in sessions. --- docs/api.rst | 2 ++ docs/user/advanced.rst | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 7225a837..8d277c81 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -81,6 +81,8 @@ Status Code Lookup >>> requests.codes['\o/'] 200 +.. _api-cookies: + Cookies ~~~~~~~ diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 080ca24b..8466a4af 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -45,6 +45,24 @@ Any dictionaries that you pass to a request method will be merged with the session-level values that are set. The method-level parameters override session parameters. +Note, however, that method-level parameters will *not* be persisted across +requests, even if using a session. This example will only send the cookies +with the first request, but not the second:: + + s = requests.Session() + r = s.get('http://httpbin.org/cookies', cookies={'from-my': 'browser'}) + print(r.text) + # '{"cookies": {"from-my": "browser"}}' + + r = s.get('http://httpbin.org/cookies') + print(r.text) + # '{"cookies": {}}' + + +If you want to manually add cookies to your session, use the +:ref:`Cookie utility functions ` to manipulate +:attr:`Session.cookies `. + Sessions can also be used as context managers:: with requests.Session() as s: From b6127ca9ca7adf7f5a55c4fe6602afa5744707d0 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 13 Aug 2015 15:04:49 -0400 Subject: [PATCH 442/545] Ignore empty fields in no_proxy --- requests/utils.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requests/utils.py b/requests/utils.py index 3fd0e41f..129134aa 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -499,7 +499,9 @@ def should_bypass_proxies(url): if no_proxy: # 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. - no_proxy = no_proxy.replace(' ', '').split(',') + no_proxy = ( + host for host in no_proxy.replace(' ', '').split(',') if host + ) ip = netloc.split(':')[0] if is_ipv4_address(ip): From 5a9097dc4b8c90a3b501a403177ec5314064e395 Mon Sep 17 00:00:00 2001 From: Smiley Barry Date: Fri, 14 Aug 2015 17:15:51 +0300 Subject: [PATCH 443/545] Update quickstart.rst Added note about the `json` parameter in `requests.post`. --- docs/user/quickstart.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 7b14610c..2277528c 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -229,6 +229,15 @@ For example, the GitHub API v3 accepts JSON-Encoded POST/PATCH data:: >>> payload = {'some': 'data'} >>> r = requests.post(url, data=json.dumps(payload)) + +You can use the `json` parameter (added in version 2.4.2) to avoid encoding the +data yourself: + + >>> import json + >>> url = 'https://api.github.com/some/endpoint' + >>> payload = {'some': 'data'} + + >>> r = requests.post(url, json=payload) POST a Multipart-Encoded File From b1de7e728721750c21988895d3c9de8f627b81ea Mon Sep 17 00:00:00 2001 From: Smiley Barry Date: Fri, 14 Aug 2015 17:31:54 +0300 Subject: [PATCH 444/545] Changed phrasing to be a little clearer. --- docs/user/quickstart.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 2277528c..3e2730bb 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -230,8 +230,8 @@ For example, the GitHub API v3 accepts JSON-Encoded POST/PATCH data:: >>> r = requests.post(url, data=json.dumps(payload)) -You can use the `json` parameter (added in version 2.4.2) to avoid encoding the -data yourself: +Instead of encoding the ``dict`` yourself, you can also pass it directly using +the ``json`` parameter (added in version 2.4.2) and it will be encoded automatically: >>> import json >>> url = 'https://api.github.com/some/endpoint' From f40bd0f7074ccda6be66c77f39442770d7669af0 Mon Sep 17 00:00:00 2001 From: Lukas Graf Date: Sat, 15 Aug 2015 11:45:44 +0200 Subject: [PATCH 445/545] Docs: Fix links to `timeouts` section by using :ref: instead of linking to .html files. --- requests/adapters.py | 4 ++-- requests/api.py | 4 ++-- requests/sessions.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index 841cbbfe..cdc5744c 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -327,8 +327,8 @@ class HTTPAdapter(BaseAdapter): :param request: The :class:`PreparedRequest ` being sent. :param stream: (optional) Whether to stream the request content. :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 `_) tuple. + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. :type timeout: float or tuple :param verify: (optional) Whether to verify SSL certificates. :param cert: (optional) Any user-provided SSL certificate to be trusted. diff --git a/requests/api.py b/requests/api.py index d40fa380..72a777b2 100644 --- a/requests/api.py +++ b/requests/api.py @@ -27,8 +27,8 @@ def request(method, url, **kwargs): :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': ('filename', fileobj)}``) for multipart encoding upload. :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 - before giving up, as a float, or a (`connect timeout, read timeout - `_) tuple. + before giving up, as a float, or a :ref:`(connect timeout, read + timeout) ` tuple. :type timeout: float or tuple :param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. :type allow_redirects: bool diff --git a/requests/sessions.py b/requests/sessions.py index 71ced5a5..50291fac 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -424,8 +424,8 @@ class Session(SessionRedirectMixin): :param auth: (optional) Auth tuple or callable to enable Basic/Digest/Custom HTTP Auth. :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 `_) tuple. + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. :type timeout: float or tuple :param allow_redirects: (optional) Set to True by default. :type allow_redirects: bool From 2411b1f56aa0259e3ddb6c85c55c5dd1b9d0a082 Mon Sep 17 00:00:00 2001 From: Smiley Barry Date: Sat, 15 Aug 2015 21:00:13 +0300 Subject: [PATCH 446/545] Added name to AUTHORS after PR #2724 --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 5ac39e4b..a765a19c 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -159,3 +159,4 @@ Patches and Suggestions - Muhammad Yasoob Ullah Khalid (`@yasoob `_) - Paul van der Linden (`@pvanderlinden `_) - Colin Dickson (`@colindickson `_) +- Smiley Barry (`@smiley `_) From 7197afa8874a3751a3dfafffc715c598e3be55ca Mon Sep 17 00:00:00 2001 From: Baptiste Mispelon Date: Sat, 15 Aug 2015 21:53:11 +0200 Subject: [PATCH 447/545] Fixed minor rendering issue in documentation code blocks --- docs/user/quickstart.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 2e9f633f..0b3e8f0b 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -228,9 +228,9 @@ For example, the GitHub API v3 accepts JSON-Encoded POST/PATCH data:: >>> payload = {'some': 'data'} >>> r = requests.post(url, data=json.dumps(payload)) - + Instead of encoding the ``dict`` yourself, you can also pass it directly using -the ``json`` parameter (added in version 2.4.2) and it will be encoded automatically: +the ``json`` parameter (added in version 2.4.2) and it will be encoded automatically:: >>> import json >>> url = 'https://api.github.com/some/endpoint' @@ -257,7 +257,7 @@ Requests makes it simple to upload Multipart-encoded files:: ... } -You can set the filename, content_type and headers explicitly: +You can set the filename, content_type and headers explicitly:: >>> url = 'http://httpbin.org/post' >>> files = {'file': ('report.xls', open('report.xls', 'rb'), 'application/vnd.ms-excel', {'Expires': '0'})} From 4947cd01ae4ebcfb1ce97345d1e9232c331331da Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Fri, 21 Aug 2015 08:44:09 +0100 Subject: [PATCH 448/545] Avoid altering dictionary size during iteration, by gathering keys that need to be deleted first. Fixes `RuntimeError` on Python 3 introduced in 53ea23128e375ef84511fd2b22b740eca15d0854 --- requests/sessions.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index 7c754608..c3ef363c 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -62,10 +62,11 @@ def merge_setting(request_setting, session_setting, dict_class=OrderedDict): merged_setting = dict_class(to_key_val_list(session_setting)) merged_setting.update(to_key_val_list(request_setting)) - # Remove keys that are set to None. - for (k, v) in merged_setting.items(): - if v is None: - del merged_setting[k] + # Remove keys that are set to None. Extract keys first to avoid altering + # the dictionary during iteration. + none_keys = [k for (k, v) in merged_setting.items() if v is None] + for key in none_keys: + del merged_setting[key] return merged_setting From 9d88810d52a2f70bba96b6f0360daa99c0531969 Mon Sep 17 00:00:00 2001 From: qingyunha Date: Mon, 24 Aug 2015 19:48:17 +0800 Subject: [PATCH 449/545] modify proxy_headers docstrings --- requests/adapters.py | 1 - 1 file changed, 1 deletion(-) diff --git a/requests/adapters.py b/requests/adapters.py index cdc5744c..60afb583 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -310,7 +310,6 @@ class HTTPAdapter(BaseAdapter): :class:`HTTPAdapter `. :param proxies: The url of the proxy being used for this request. - :param kwargs: Optional additional keyword arguments. """ headers = {} username, password = get_auth_from_url(proxy) From 48fe34447de02bfe9827321fafe3277741d77c38 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Thu, 20 Aug 2015 21:59:57 +0000 Subject: [PATCH 450/545] Implement per-host proxies This change allows the proxy dict to be have entries of the form {'://': ''}. Host-specific proxies will be used in preference to the scheme-specific proxies (i.e., proxy dict entries with keys like 'http' or 'https'). Fixes #2722 --- requests/adapters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requests/adapters.py b/requests/adapters.py index 60afb583..0f3dc4da 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -239,7 +239,8 @@ class HTTPAdapter(BaseAdapter): :param proxies: (optional) A Requests-style dictionary of proxies used on this request. """ proxies = proxies or {} - proxy = proxies.get(urlparse(url.lower()).scheme) + u = urlparse(url.lower()) + proxy = proxies.get(u.scheme+'://'+u.hostname, proxies.get(u.scheme)) if proxy: proxy = prepend_scheme_if_needed(proxy, 'http') From 8b03cd96cbdc79666ae49e1b0b63b53ddb4c6bdf Mon Sep 17 00:00:00 2001 From: Everett Toews Date: Mon, 24 Aug 2015 18:28:41 -0500 Subject: [PATCH 451/545] Include file-like object in data arg description --- requests/sessions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index c3ef363c..651fc188 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -410,8 +410,8 @@ class Session(SessionRedirectMixin): :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 data: (optional) Dictionary or bytes 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 to send in the body of the :class:`Request`. :param headers: (optional) Dictionary of HTTP Headers to send with the From 66567e037b5805dc81b80745bf2468a568aa04f3 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Mon, 24 Aug 2015 20:47:22 -0500 Subject: [PATCH 452/545] Fix tests on python 2.6 --- test_requests.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test_requests.py b/test_requests.py index 7e5e4d8f..33fafdf4 100755 --- a/test_requests.py +++ b/test_requests.py @@ -17,7 +17,9 @@ import pytest from requests.adapters import HTTPAdapter from requests.auth import HTTPDigestAuth, _basic_auth_str from requests.compat import ( - Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3, builtin_str) + Morsel, cookielib, getproxies, str, urljoin, urlparse, is_py3, + builtin_str, OrderedDict + ) from requests.cookies import cookiejar_from_dict, morsel_to_cookie from requests.exceptions import (ConnectionError, ConnectTimeout, InvalidSchema, InvalidURL, MissingSchema, @@ -126,7 +128,7 @@ class RequestsTestCase(unittest.TestCase): assert request.url == "http://example.com/path?key=value&a=b#fragment" def test_params_original_order_is_preserved_by_default(self): - param_ordered_dict = collections.OrderedDict((('z', 1), ('a', 1), ('k', 1), ('d', 1))) + param_ordered_dict = OrderedDict((('z', 1), ('a', 1), ('k', 1), ('d', 1))) session = requests.Session() request = requests.Request('GET', 'http://example.com/', params=param_ordered_dict) prep = session.prepare_request(request) From 56844d128754b923211a981618903c34fd36ed7b Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Tue, 25 Aug 2015 14:53:52 +0000 Subject: [PATCH 453/545] Add documentation for host-specific proxies --- docs/user/advanced.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 8466a4af..78fc6054 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -459,12 +459,18 @@ If you need to use a proxy, you can configure individual requests with the import requests proxies = { + "http://10.20.1.128": "http://10.10.1.10:5323", + "https://10.20.1.140": "http://10.10.1.10:6431" "http": "http://10.10.1.10:3128", "https": "http://10.10.1.10:1080", } requests.get("http://example.org", proxies=proxies) +A request's ``://`` is looked up in the proxies +dictionary first, to provide host-specific proxies. If no +host-specific proxy is found, the request's scheme is looked up. + You can also configure proxies by setting the environment variables ``HTTP_PROXY`` and ``HTTPS_PROXY``. From 618514231bd24d7eec7b8d486f4a55cb7ef8d522 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Tue, 25 Aug 2015 15:11:27 +0000 Subject: [PATCH 454/545] Reword documentation for per-host proxies --- docs/user/advanced.rst | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 78fc6054..c0e6aabc 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -459,18 +459,12 @@ If you need to use a proxy, you can configure individual requests with the import requests proxies = { - "http://10.20.1.128": "http://10.10.1.10:5323", - "https://10.20.1.140": "http://10.10.1.10:6431" "http": "http://10.10.1.10:3128", "https": "http://10.10.1.10:1080", } requests.get("http://example.org", proxies=proxies) -A request's ``://`` is looked up in the proxies -dictionary first, to provide host-specific proxies. If no -host-specific proxy is found, the request's scheme is looked up. - You can also configure proxies by setting the environment variables ``HTTP_PROXY`` and ``HTTPS_PROXY``. @@ -488,8 +482,15 @@ To use HTTP Basic Auth with your proxy, use the `http://user:password@host/` syn "http": "http://user:pass@10.10.1.10:3128/", } -Note that proxy URLs must include the scheme. +To give a proxy for a specific scheme and host, use the +`scheme://host` form for the key:: + proxies = { + "http://10.20.1.128": "http://10.10.1.10:5323", + } + +Note that proxy URLs must include the scheme. + .. _compliance: Compliance From 22075f02d0f41ee8d6e7328361b59c3b2d54bdaf Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Tue, 25 Aug 2015 18:21:20 +0000 Subject: [PATCH 455/545] Clarify the per-host proxy code --- requests/adapters.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index 0f3dc4da..cc6c8971 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -239,8 +239,10 @@ class HTTPAdapter(BaseAdapter): :param proxies: (optional) A Requests-style dictionary of proxies used on this request. """ proxies = proxies or {} - u = urlparse(url.lower()) - proxy = proxies.get(u.scheme+'://'+u.hostname, proxies.get(u.scheme)) + urlparts = urlparse(url.lower()) + proxy = proxies.get(urlparts.scheme+'://'+urlparts.hostname) + if proxy is None: + proxy = proxies.get(urlparts.scheme) if proxy: proxy = prepend_scheme_if_needed(proxy, 'http') From a2e828d4d735c79786a0d8e54ead9831e96456a2 Mon Sep 17 00:00:00 2001 From: csparpa Date: Tue, 25 Aug 2015 22:09:10 +0200 Subject: [PATCH 456/545] Add link to IT translation --- docs/_templates/sidebarintro.html | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 03bf7e0e..524c73f3 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -34,8 +34,7 @@
          • Japanese
          • Chinese
          • Portuguese
          • - - +
          • Italian

          Useful Links

          From d7a3aa618839e304c324e6092953131a1f43c647 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Thu, 27 Aug 2015 17:26:14 +0000 Subject: [PATCH 457/545] Fix another place the proxy-selecting logic is used. --- requests/adapters.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index cc6c8971..b9b3326e 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -278,10 +278,12 @@ class HTTPAdapter(BaseAdapter): :param proxies: A dictionary of schemes to proxy URLs. """ proxies = proxies or {} - scheme = urlparse(request.url).scheme - proxy = proxies.get(scheme) + urlparts = urlparse(request.url.lower()) + proxy = proxies.get(urlparts.scheme+'://'+urlparts.hostname) + if proxy is None: + proxy = proxies.get(urlparts.scheme) - if proxy and scheme != 'https': + if proxy and urlparts.scheme != 'https': url = urldefragauth(request.url) else: url = request.path_url From 2c8721540ad70cadb3c71912b1ab8577f2f8fd53 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Thu, 27 Aug 2015 17:26:45 +0000 Subject: [PATCH 458/545] Fix documentation for proxies in the Session class --- requests/sessions.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index c3ef363c..a12f297b 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -299,9 +299,9 @@ class Session(SessionRedirectMixin): #: :class:`Request `. self.auth = None - #: Dictionary mapping protocol to the URL of the proxy (e.g. - #: {'http': 'foo.bar:3128'}) to be used on each - #: :class:`Request `. + #: Dictionary mapping protocol or protocol and host to the URL of the proxy + #: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to + #: be used on each :class:`Request `. self.proxies = {} #: Event-handling hooks. @@ -428,8 +428,8 @@ class Session(SessionRedirectMixin): :type timeout: float or tuple :param allow_redirects: (optional) Set to True by default. :type allow_redirects: bool - :param proxies: (optional) Dictionary mapping protocol to the URL of - the proxy. + :param proxies: (optional) Dictionary mapping protocol or protocol and + hostname to the URL of the proxy. :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. :param verify: (optional) if ``True``, the SSL cert will be verified. From 500f15da1fe54e3773be42f7770680d1f86b699f Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Thu, 27 Aug 2015 17:38:18 +0000 Subject: [PATCH 459/545] Factor out the proxy selection code --- requests/adapters.py | 18 +++++------------- requests/utils.py | 8 ++++++++ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index b9b3326e..f61ea824 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -17,7 +17,8 @@ from .packages.urllib3.util import Timeout as TimeoutSauce from .packages.urllib3.util.retry import Retry from .compat import urlparse, basestring 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) from .structures import CaseInsensitiveDict from .packages.urllib3.exceptions import ConnectTimeoutError from .packages.urllib3.exceptions import HTTPError as _HTTPError @@ -238,11 +239,7 @@ class HTTPAdapter(BaseAdapter): :param url: The URL to connect to. :param proxies: (optional) A Requests-style dictionary of proxies used on this request. """ - proxies = proxies or {} - urlparts = urlparse(url.lower()) - proxy = proxies.get(urlparts.scheme+'://'+urlparts.hostname) - if proxy is None: - proxy = proxies.get(urlparts.scheme) + proxy = select_proxy(url, proxies) if proxy: proxy = prepend_scheme_if_needed(proxy, 'http') @@ -277,13 +274,8 @@ class HTTPAdapter(BaseAdapter): :param request: The :class:`PreparedRequest ` being sent. :param proxies: A dictionary of schemes to proxy URLs. """ - proxies = proxies or {} - urlparts = urlparse(request.url.lower()) - proxy = proxies.get(urlparts.scheme+'://'+urlparts.hostname) - if proxy is None: - proxy = proxies.get(urlparts.scheme) - - if proxy and urlparts.scheme != 'https': + proxy = select_proxy(request.url, proxies) + if proxy and not request.url.lower().startswith('https'): url = urldefragauth(request.url) else: url = request.path_url diff --git a/requests/utils.py b/requests/utils.py index 3fd0e41f..ddc2c00a 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -537,6 +537,14 @@ def get_environ_proxies(url): else: return getproxies() +def select_proxy(url, proxies): + """Select a proxy, if applicable.""" + proxies = proxies or {} + urlparts = urlparse(url.lower()) + 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"): """Return a string representing the default user agent.""" From c80121df91997535f4c27f9c44c7aca10ad39af6 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Thu, 27 Aug 2015 17:41:12 +0000 Subject: [PATCH 460/545] Fix documentation for the proxies dictionary --- requests/adapters.py | 2 +- requests/utils.py | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index f61ea824..206978b3 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -272,7 +272,7 @@ class HTTPAdapter(BaseAdapter): :class:`HTTPAdapter `. :param request: The :class:`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. """ proxy = select_proxy(request.url, proxies) if proxy and not request.url.lower().startswith('https'): diff --git a/requests/utils.py b/requests/utils.py index ddc2c00a..95bf6b5f 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -538,7 +538,11 @@ def get_environ_proxies(url): return getproxies() def select_proxy(url, proxies): - """Select a proxy, if applicable.""" + """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.lower()) proxy = proxies.get(urlparts.scheme+'://'+urlparts.hostname) From fb496e4e86cafd78fd510e6d1ecf539c0545bcae Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Thu, 27 Aug 2015 18:20:11 +0000 Subject: [PATCH 461/545] Add test for per-host proxy selection --- test_requests.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test_requests.py b/test_requests.py index 7e5e4d8f..c67e569e 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1325,6 +1325,15 @@ class UtilsTestCase(unittest.TestCase): 'http://localhost.localdomain:5000/v1.0/') == {} assert get_environ_proxies('http://www.requests.com/') != {} + def test_select_proxies(self): + """Make sure we can select per-host proxies correctly.""" + from requests.utils import select_proxy + proxies = {'http': 'http://http.proxy', + 'http://some.host': 'http://some.host.proxy'} + assert select_proxy('hTTp://u:p@Some.Host/path', proxies) == 'http://some.host.proxy' + assert select_proxy('hTTp://u:p@Other.Host/path', proxies) == 'http://http.proxy' + assert select_proxy('hTTps://Other.Host', proxies) is None + def test_guess_filename_when_int(self): from requests.utils import guess_filename assert None is guess_filename(1) From f3d4480a33b367cf020b0ed447bca10797706094 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Fri, 28 Aug 2015 20:06:45 +0000 Subject: [PATCH 462/545] Use url parsing to check the scheme --- requests/adapters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requests/adapters.py b/requests/adapters.py index 206978b3..96d64327 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -275,7 +275,8 @@ class HTTPAdapter(BaseAdapter): :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. """ proxy = select_proxy(request.url, proxies) - if proxy and not request.url.lower().startswith('https'): + scheme = urlparse(request.url.lower()).scheme + if proxy and scheme != 'https': url = urldefragauth(request.url) else: url = request.path_url From 4c6540898c101a606e1a50e539f01a82163bdae0 Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Fri, 28 Aug 2015 20:20:07 +0000 Subject: [PATCH 463/545] don't lowercase a url before urlparsing it urlparse automatically lowercases the scheme and hostname --- requests/adapters.py | 2 +- requests/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index 96d64327..f911fc57 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -275,7 +275,7 @@ class HTTPAdapter(BaseAdapter): :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs. """ proxy = select_proxy(request.url, proxies) - scheme = urlparse(request.url.lower()).scheme + scheme = urlparse(request.url).scheme if proxy and scheme != 'https': url = urldefragauth(request.url) else: diff --git a/requests/utils.py b/requests/utils.py index 95bf6b5f..3d4c7945 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -544,7 +544,7 @@ def select_proxy(url, proxies): :param proxies: A dictionary of schemes or schemes and hosts to proxy URLs """ proxies = proxies or {} - urlparts = urlparse(url.lower()) + urlparts = urlparse(url) proxy = proxies.get(urlparts.scheme+'://'+urlparts.hostname) if proxy is None: proxy = proxies.get(urlparts.scheme) From 00d8c24fe171db90cf2226f7753e00ca706b50ae Mon Sep 17 00:00:00 2001 From: Carlos Vargas Date: Fri, 28 Aug 2015 19:12:10 -0700 Subject: [PATCH 464/545] Add link to ES translation. --- docs/_templates/sidebarintro.html | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 524c73f3..52ea77e3 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -35,6 +35,7 @@
        • Chinese
        • Portuguese
        • Italian
        • +
        • Spanish

        Useful Links

        From 7c958ee28e54be838785b9f4388045005a7f1ffb Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 31 Aug 2015 10:26:06 +0100 Subject: [PATCH 465/545] Document the string form of the verify parameter. --- docs/user/advanced.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 8466a4af..f53cbed7 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -201,7 +201,11 @@ I don't have SSL setup on this domain, so it fails. Excellent. GitHub does thoug >>> requests.get('https://github.com', verify=True) -You can pass ``verify`` the path to a CA_BUNDLE file with certificates of trusted CAs. This list of trusted CAs can also be specified through the ``REQUESTS_CA_BUNDLE`` environment variable. +You can pass ``verify`` the path to a CA_BUNDLE file with certificates of trusted CAs:: + + >>> requests.get('https://github.com', verify='/path/to/certfile') + +This list of trusted CAs can also be specified through the ``REQUESTS_CA_BUNDLE`` environment variable. Requests can also ignore verifying the SSL certificate if you set ``verify`` to False. From 704a922fc2d77bbc1b24fe2a02ad6876bdc16d5c Mon Sep 17 00:00:00 2001 From: Jason Grout Date: Fri, 4 Sep 2015 22:55:38 +0000 Subject: [PATCH 466/545] Clarify documentation for per-host proxies Make sure it's clear that a per-host proxy matches a scheme and exact hostname. --- docs/user/advanced.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index c0e6aabc..3b060a0e 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -483,7 +483,10 @@ To use HTTP Basic Auth with your proxy, use the `http://user:password@host/` syn } To give a proxy for a specific scheme and host, use the -`scheme://host` form for the key:: +`scheme://hostname` form for the key. This will match for +any request to the given scheme and exact hostname. + +:: proxies = { "http://10.20.1.128": "http://10.10.1.10:5323", From f4eaefa63e80b323762c4e719342717fd523663b Mon Sep 17 00:00:00 2001 From: Maximilian Hils Date: Sat, 5 Sep 2015 12:07:49 +0200 Subject: [PATCH 467/545] fix rfc quotation --- docs/user/quickstart.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 0b3e8f0b..f8b78801 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -368,10 +368,10 @@ times with different values, but requests combines them so they can be represented in the dictionary within a single mapping, as per `RFC 7230 `_: - > A recipient MAY combine multiple header fields with the same field name - > into one "field-name: field-value" pair, without changing the semantics - > of the message, by appending each subsequent field value to the combined - > field value in order, separated by a comma. + A recipient MAY combine multiple header fields with the same field name + into one "field-name: field-value" pair, without changing the semantics + of the message, by appending each subsequent field value to the combined + field value in order, separated by a comma. Cookies ------- From 37037607b5f3da32ff1897caca3c38e210f04c37 Mon Sep 17 00:00:00 2001 From: Ravi Prakash Putchala Date: Tue, 8 Sep 2015 14:45:46 +0530 Subject: [PATCH 468/545] Fixed issue #2756 Now 'json' parameter will be used to prepare body only if the 'data' parameter is not present --- requests/models.py | 4 ++-- test_requests.py | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index 4270c647..9fa367d3 100644 --- a/requests/models.py +++ b/requests/models.py @@ -414,7 +414,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): content_type = None length = None - if json is not None: + if not data and json is not None: content_type = 'application/json' body = complexjson.dumps(json) @@ -443,7 +443,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if files: (body, content_type) = self._encode_files(files, data) else: - if data and json is None: + if data: body = self._encode_params(data) if isinstance(data, basestring) or hasattr(data, 'read'): content_type = None diff --git a/test_requests.py b/test_requests.py index 28ea5730..fedf7293 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1062,6 +1062,13 @@ class RequestsTestCase(unittest.TestCase): assert 'application/json' in r.request.headers['Content-Type'] assert {'life': 42} == r.json()['json'] + def test_json_param_post_should_not_override_data_param(self): + r = requests.Request(method='POST', url='http://httpbin.org/post', + data={'stuff'.encode('utf-8'): 'elixr'}, + json={'music'.encode('utf-8'): 'flute'}) + prep = r.prepare() + assert 'stuff=elixr' == prep.body + def test_response_iter_lines(self): r = requests.get(httpbin('stream/4'), stream=True) assert r.status_code == 200 From 7a0cd16c1a5be16f3df395ee12ed11d0ddde7837 Mon Sep 17 00:00:00 2001 From: Ravi Prakash Putchala Date: Wed, 9 Sep 2015 13:04:24 +0530 Subject: [PATCH 469/545] changed the null check for 'data' parameter to be more explicit. removed the superfluous encode from the testcase. --- requests/models.py | 2 +- test_requests.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index 9fa367d3..06e84362 100644 --- a/requests/models.py +++ b/requests/models.py @@ -414,7 +414,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): content_type = None length = None - if not data and json is not None: + if data == {} and json is not None: content_type = 'application/json' body = complexjson.dumps(json) diff --git a/test_requests.py b/test_requests.py index fedf7293..87b47a19 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1065,7 +1065,7 @@ class RequestsTestCase(unittest.TestCase): def test_json_param_post_should_not_override_data_param(self): r = requests.Request(method='POST', url='http://httpbin.org/post', data={'stuff'.encode('utf-8'): 'elixr'}, - json={'music'.encode('utf-8'): 'flute'}) + json={'music': 'flute'}) prep = r.prepare() assert 'stuff=elixr' == prep.body From 1f48a4c9ea81d6b2128a7e1199be2dfbb81b643d Mon Sep 17 00:00:00 2001 From: Ravi Prakash Putchala Date: Wed, 9 Sep 2015 14:43:22 +0530 Subject: [PATCH 470/545] Removed the other 'encode' as well, in the test case. --- test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index 87b47a19..b5be37fc 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1064,7 +1064,7 @@ class RequestsTestCase(unittest.TestCase): def test_json_param_post_should_not_override_data_param(self): r = requests.Request(method='POST', url='http://httpbin.org/post', - data={'stuff'.encode('utf-8'): 'elixr'}, + data={'stuff': 'elixr'}, json={'music': 'flute'}) prep = r.prepare() assert 'stuff=elixr' == prep.body From 96e8e8b92f177b3672dc5566a1e7cd010c872ccf Mon Sep 17 00:00:00 2001 From: Usman Ehtesham Gul Date: Thu, 10 Sep 2015 12:59:39 -0400 Subject: [PATCH 471/545] Remove redundant function call list when iterating over status codes --- requests/status_codes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/status_codes.py b/requests/status_codes.py index e0887f21..1db7fc04 100644 --- a/requests/status_codes.py +++ b/requests/status_codes.py @@ -82,7 +82,7 @@ _codes = { codes = LookupDict(name='status_codes') -for (code, titles) in list(_codes.items()): +for code, titles in _codes.items(): for title in titles: setattr(codes, title, code) if not title.startswith('\\'): From e9c94e0f0662c33aa3a94c784f64ae575ce97c7b Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Thu, 10 Sep 2015 22:45:59 +0200 Subject: [PATCH 472/545] docs: Fix typos --- docs/_templates/sidebarintro.html | 2 +- docs/user/advanced.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_templates/sidebarintro.html b/docs/_templates/sidebarintro.html index 52ea77e3..d2cbfe9d 100644 --- a/docs/_templates/sidebarintro.html +++ b/docs/_templates/sidebarintro.html @@ -40,7 +40,7 @@

        Useful Links

          -
        • Recomended Packages and Extensions
        • +
        • Recommended Packages and Extensions
        • diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index dcd9e228..5c9df948 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -69,7 +69,7 @@ Sessions can also be used as context managers:: s.get('http://httpbin.org/cookies/set/sessioncookie/123456789') This will make sure the session is closed as soon as the ``with`` block is -exited, even if unhandled exceptions occured. +exited, even if unhandled exceptions occurred. .. admonition:: Remove a Value From a Dict Parameter From 2cee3bb57a464d0ae48892661a9edf44790d373b Mon Sep 17 00:00:00 2001 From: Jim Browne Date: Fri, 11 Sep 2015 10:48:18 -0700 Subject: [PATCH 473/545] docs: move CA Certificates next to SSL Cert Verification --- docs/user/advanced.rst | 44 +++++++++++++++++++++--------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 5c9df948..175fc9b7 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -228,6 +228,28 @@ If you specify a wrong path or an invalid cert:: >>> requests.get('https://kennethreitz.com', cert='/wrong_path/server.pem') SSLError: [Errno 336265225] _ssl.c:347: error:140B0009:SSL routines:SSL_CTX_use_PrivateKey_file:PEM lib +.. _ca-certificates: + +CA Certificates +--------------- + +By default Requests bundles a set of root CAs that it trusts, sourced from the +`Mozilla trust store`_. However, these are only updated once for each Requests +version. This means that if you pin a Requests version your certificates can +become extremely out of date. + +From Requests version 2.4.0 onwards, Requests will attempt to use certificates +from `certifi`_ if it is present on the system. This allows for users to update +their trusted certificates without having to change the code that runs on their +system. + +For the sake of security we recommend upgrading certifi frequently! + +.. _HTTP persistent connection: https://en.wikipedia.org/wiki/HTTP_persistent_connection +.. _connection pooling: https://urllib3.readthedocs.org/en/latest/pools.html +.. _certifi: http://certifi.io/ +.. _Mozilla trust store: https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt + .. _body-content-workflow: Body Content Workflow @@ -853,25 +875,3 @@ coffee. r = requests.get('https://github.com', timeout=None) .. _`connect()`: http://linux.die.net/man/2/connect - -.. _ca-certificates: - -CA Certificates ---------------- - -By default Requests bundles a set of root CAs that it trusts, sourced from the -`Mozilla trust store`_. However, these are only updated once for each Requests -version. This means that if you pin a Requests version your certificates can -become extremely out of date. - -From Requests version 2.4.0 onwards, Requests will attempt to use certificates -from `certifi`_ if it is present on the system. This allows for users to update -their trusted certificates without having to change the code that runs on their -system. - -For the sake of security we recommend upgrading certifi frequently! - -.. _HTTP persistent connection: https://en.wikipedia.org/wiki/HTTP_persistent_connection -.. _connection pooling: https://urllib3.readthedocs.org/en/latest/pools.html -.. _certifi: http://certifi.io/ -.. _Mozilla trust store: https://hg.mozilla.org/mozilla-central/raw-file/tip/security/nss/lib/ckfw/builtins/certdata.txt From c5a41269d9795e7c63d72777b7ea360e380544d8 Mon Sep 17 00:00:00 2001 From: ueg1990 Date: Sat, 12 Sep 2015 20:43:25 -0400 Subject: [PATCH 474/545] Some pythonic suggestions for requests/hooks.py --- requests/hooks.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/requests/hooks.py b/requests/hooks.py index 5dfaf6b6..ba060519 100644 --- a/requests/hooks.py +++ b/requests/hooks.py @@ -12,34 +12,26 @@ Available hooks: The response generated from a Request. """ - +from collections import defaultdict HOOKS = ['response'] def default_hooks(): - hooks = {} - for event in HOOKS: - hooks[event] = [] - return hooks + return dict((event, []) for event in HOOKS) # TODO: response is the only one def dispatch_hook(key, hooks, hook_data, **kwargs): """Dispatches a hook dictionary on a given piece of data.""" - hooks = hooks or dict() - - if key in hooks: - hooks = hooks.get(key) - + hooks = hooks.get(key) + if hooks: if hasattr(hooks, '__call__'): hooks = [hooks] - for hook in hooks: _hook_data = hook(hook_data, **kwargs) if _hook_data is not None: hook_data = _hook_data - - return hook_data + return hook_data \ No newline at end of file From 649aeaa489d66807a30b9e7ea508589f036ba883 Mon Sep 17 00:00:00 2001 From: ueg1990 Date: Sun, 13 Sep 2015 06:36:39 -0400 Subject: [PATCH 475/545] Removed unused module from hooks.py --- requests/hooks.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/requests/hooks.py b/requests/hooks.py index ba060519..9da94366 100644 --- a/requests/hooks.py +++ b/requests/hooks.py @@ -12,11 +12,8 @@ Available hooks: The response generated from a Request. """ -from collections import defaultdict - HOOKS = ['response'] - def default_hooks(): return dict((event, []) for event in HOOKS) @@ -34,4 +31,4 @@ def dispatch_hook(key, hooks, hook_data, **kwargs): _hook_data = hook(hook_data, **kwargs) if _hook_data is not None: hook_data = _hook_data - return hook_data \ No newline at end of file + return hook_data From b8df8911c3591ce1abe823fcde91f6fa4f5ae570 Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Thu, 24 Sep 2015 13:40:49 -0400 Subject: [PATCH 476/545] 60m times --- docs/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.rst b/docs/index.rst index 04ba7e50..d1ebbd58 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -48,7 +48,7 @@ Testimonials Her Majesty's Government, Amazon, Google, Twilio, Runscope, Mozilla, Heroku, PayPal, NPR, Obama for America, Transifex, Native Instruments, The Washington Post, Twitter, SoundCloud, Kippt, Readability, Sony, and Federal US Institutions that prefer to be unnamed -use Requests internally. It has been downloaded over 40,000,000 times from PyPI. +use Requests internally. It has been downloaded over 60,000,000 times from PyPI. **Armin Ronacher** Requests is the perfect example how beautiful an API can be with the From 76a0d3fcb9a826435e938e0c268083b014375aaa Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Sun, 27 Sep 2015 21:48:42 -0500 Subject: [PATCH 477/545] Remove offer to pair with others from README --- README.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/README.rst b/README.rst index 6c5a6dc1..99d30e72 100644 --- a/README.rst +++ b/README.rst @@ -77,7 +77,6 @@ Contribute ---------- #. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. There is a `Contributor Friendly`_ tag for issues that should be ideal for people who are not very familiar with the codebase yet. -#. If you feel uncomfortable or uncertain about an issue or your changes, feel free to email @sigmavirus24 and he will happily help you via email, Skype, remote pairing or whatever you are comfortable with. #. Fork `the repository`_ on GitHub to start making your changes to the **master** branch (or branch off of it). #. Write a test which shows that the bug was fixed or that the feature works as expected. #. Send a pull request and bug the maintainer until it gets merged and published. :) Make sure to add yourself to AUTHORS_. From 79675a0638190560fb4d4afa26452f9118574daa Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 1 Oct 2015 10:26:14 +0100 Subject: [PATCH 478/545] Update test dependencies --- requirements.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 2ac5d3f3..1f5297db 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -py==1.4.12 -pytest==2.3.4 -pytest-cov==1.6 +py==1.4.30 +pytest==2.8.1 +pytest-cov==2.1.0 wheel From bf38d5bf529c89be336cd380067faea83b9287f4 Mon Sep 17 00:00:00 2001 From: sumitbinnani Date: Fri, 2 Oct 2015 13:50:52 +0530 Subject: [PATCH 479/545] Update models.py Updated Documentation: ```data``` as well as ```files``` supersedes json --- requests/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 4270c647..6d6265d0 100644 --- a/requests/models.py +++ b/requests/models.py @@ -192,7 +192,7 @@ class Request(RequestHooksMixin): :param headers: dictionary of headers to send. :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 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 auth: Auth handler or (user, pass) tuple. :param cookies: dictionary or CookieJar of cookies to attach to this request. From 63b3b9be55e0a836ec312dd2197366c260fd2eb0 Mon Sep 17 00:00:00 2001 From: Doctor-love Date: Fri, 2 Oct 2015 14:47:50 +0200 Subject: [PATCH 480/545] Clarified description for option "trust_env" --- requests/sessions.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requests/sessions.py b/requests/sessions.py index 9c0dd73d..496fdb2e 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -325,7 +325,8 @@ class Session(SessionRedirectMixin): #: limit, a :class:`TooManyRedirects` exception is raised. self.max_redirects = DEFAULT_REDIRECT_LIMIT - #: Should we trust the environment? + #: Trust environement settings for proxy configuration, default + #: authentication and similar. self.trust_env = True #: A CookieJar containing all currently outstanding cookies set on this From 443d07e0d3bcbf5a23314a4ddf95c67bba533ddb Mon Sep 17 00:00:00 2001 From: Hosam Aly Date: Fri, 2 Oct 2015 23:58:48 +0100 Subject: [PATCH 481/545] Fix #2799: Update Quickstart documentation of Response.json() --- docs/user/quickstart.rst | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index f8b78801..49bf67ba 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -145,8 +145,14 @@ There's also a builtin JSON decoder, in case you're dealing with JSON data:: [{u'repository': {u'open_issues': 0, u'url': 'https://github.com/... In case the JSON decoding fails, ``r.json`` raises an exception. For example, if -the response gets a 401 (Unauthorized), attempting ``r.json`` raises ``ValueError: -No JSON object could be decoded`` +the response gets a 204 (No Content), or if the response contains invalid JSON, +attempting ``r.json`` raises ``ValueError: No JSON object could be decoded``. + +It should be noted that the success of the call to ``r.json`` does **not** +indicate the success of the response. Some servers may return a JSON object in a +failed response (e.g. error details with HTTP 500). Such JSON will be decoded +and returned. To check that a request is successful, use ``r.ok == True`` or +``r.raise_for_status()``. Raw Response Content From 086e19d8e175cccb630ee3589affdd2f38075ecd Mon Sep 17 00:00:00 2001 From: Hosam Aly Date: Sat, 3 Oct 2015 16:34:36 +0100 Subject: [PATCH 482/545] Clarify the contract of Response#ok, and avoid exceptions in it --- requests/models.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/requests/models.py b/requests/models.py index 6d6265d0..a822dd15 100644 --- a/requests/models.py +++ b/requests/models.py @@ -616,11 +616,8 @@ class Response(object): @property def ok(self): - try: - self.raise_for_status() - except HTTPError: - return False - return True + """True if the status code does *not* indicate an error, i.e. status code < 400.""" + return self.status_code < 400 @property def is_redirect(self): From a89d5c003a8708e9555968f810311f3e019281c4 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 3 Oct 2015 18:32:28 +0100 Subject: [PATCH 483/545] Revert "Clarify the contract of Response#ok, and avoid exceptions in it" This reverts commit 086e19d8e175cccb630ee3589affdd2f38075ecd. --- requests/models.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/requests/models.py b/requests/models.py index a822dd15..6d6265d0 100644 --- a/requests/models.py +++ b/requests/models.py @@ -616,8 +616,11 @@ class Response(object): @property def ok(self): - """True if the status code does *not* indicate an error, i.e. status code < 400.""" - return self.status_code < 400 + try: + self.raise_for_status() + except HTTPError: + return False + return True @property def is_redirect(self): From 139418475cd6bedd3d383cd3e442698b888c51c2 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 3 Oct 2015 18:33:25 +0100 Subject: [PATCH 484/545] Remove reference to ok. --- docs/user/quickstart.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 49bf67ba..d08561d2 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -151,8 +151,8 @@ attempting ``r.json`` raises ``ValueError: No JSON object could be decoded``. It should be noted that the success of the call to ``r.json`` does **not** indicate the success of the response. Some servers may return a JSON object in a failed response (e.g. error details with HTTP 500). Such JSON will be decoded -and returned. To check that a request is successful, use ``r.ok == True`` or -``r.raise_for_status()``. +and returned. To check that a request is successful, use +``r.raise_for_status()`` or check ``r.status_code`` is what you expect. Raw Response Content From 2a635ba87d7c7c3ef75e7f2013a2cd1a207a0e1a Mon Sep 17 00:00:00 2001 From: Alexandre Sieira Date: Mon, 5 Oct 2015 11:18:23 -0300 Subject: [PATCH 485/545] Update default user-agent (#2785) * Updated default user agent so that it includes only the requests version; * Added PyCharm / IDEA project directory to `.gitignore`. --- .gitignore | 1 + requests/utils.py | 28 +--------------------------- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 9fcc6c3d..ff9256c6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ requests.egg-info/ *.swp *.egg env/ +.idea/ .workon diff --git a/requests/utils.py b/requests/utils.py index 3d4c7945..0bac9a44 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -552,33 +552,7 @@ def select_proxy(url, proxies): def default_user_agent(name="python-requests"): """Return a string representing the default user agent.""" - _implementation = platform.python_implementation() - - 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)]) + return '%s/%s' % (name, __version__) def default_headers(): From 4e44c2ab640f01739602da46929a24ded1516081 Mon Sep 17 00:00:00 2001 From: Alexandre Sieira Date: Mon, 5 Oct 2015 11:36:21 -0300 Subject: [PATCH 486/545] Removing change to .gitignore (#2785) --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index ff9256c6..9fcc6c3d 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ requests.egg-info/ *.swp *.egg env/ -.idea/ .workon From 09bd3232dd1572f1b6c435fdcca4275687fcb937 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 5 Oct 2015 15:38:24 +0100 Subject: [PATCH 487/545] Update urllib3 to 1.12 --- requests/packages/urllib3/__init__.py | 2 +- requests/packages/urllib3/_collections.py | 58 +++++++++---------- requests/packages/urllib3/connection.py | 19 ++++-- requests/packages/urllib3/connectionpool.py | 20 ++++--- .../packages/urllib3/contrib/pyopenssl.py | 6 +- requests/packages/urllib3/exceptions.py | 3 + requests/packages/urllib3/response.py | 5 +- requests/packages/urllib3/util/connection.py | 8 +-- requests/packages/urllib3/util/response.py | 5 +- requests/packages/urllib3/util/ssl_.py | 23 +++++--- 10 files changed, 84 insertions(+), 65 deletions(-) diff --git a/requests/packages/urllib3/__init__.py b/requests/packages/urllib3/__init__.py index b80f19d2..86bb71d2 100644 --- a/requests/packages/urllib3/__init__.py +++ b/requests/packages/urllib3/__init__.py @@ -4,7 +4,7 @@ urllib3 - Thread-safe connection pooling and re-using. __author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' __license__ = 'MIT' -__version__ = 'dev' +__version__ = '1.12' from .connectionpool import ( diff --git a/requests/packages/urllib3/_collections.py b/requests/packages/urllib3/_collections.py index 279416ce..b68b9a59 100644 --- a/requests/packages/urllib3/_collections.py +++ b/requests/packages/urllib3/_collections.py @@ -97,14 +97,7 @@ class RecentlyUsedContainer(MutableMapping): return list(iterkeys(self._container)) -_dict_setitem = dict.__setitem__ -_dict_getitem = dict.__getitem__ -_dict_delitem = dict.__delitem__ -_dict_contains = dict.__contains__ -_dict_setdefault = dict.setdefault - - -class HTTPHeaderDict(dict): +class HTTPHeaderDict(MutableMapping): """ :param headers: An iterable of field-value pairs. Must not contain multiple field names @@ -139,7 +132,8 @@ class HTTPHeaderDict(dict): """ def __init__(self, headers=None, **kwargs): - dict.__init__(self) + super(HTTPHeaderDict, self).__init__() + self._container = {} if headers is not None: if isinstance(headers, HTTPHeaderDict): self._copy_from(headers) @@ -149,38 +143,44 @@ class HTTPHeaderDict(dict): self.extend(kwargs) def __setitem__(self, key, val): - return _dict_setitem(self, key.lower(), (key, val)) + self._container[key.lower()] = (key, val) + return self._container[key.lower()] def __getitem__(self, key): - val = _dict_getitem(self, key.lower()) + val = self._container[key.lower()] return ', '.join(val[1:]) def __delitem__(self, key): - return _dict_delitem(self, key.lower()) + del self._container[key.lower()] def __contains__(self, key): - return _dict_contains(self, key.lower()) + 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((k1, self[k1]) for k1 in self) == dict((k2, other[k2]) for k2 in 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) - values = MutableMapping.values - get = MutableMapping.get - update = MutableMapping.update - 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. @@ -216,7 +216,7 @@ class HTTPHeaderDict(dict): key_lower = key.lower() new_vals = key, val # Keep the common case aka no item present as fast as possible - vals = _dict_setdefault(self, key_lower, new_vals) + 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): @@ -225,7 +225,7 @@ class HTTPHeaderDict(dict): else: # vals should be a tuple then, i.e. only one item so far # Need to convert the tuple to list for further extension - _dict_setitem(self, key_lower, [vals[0], vals[1], val]) + self._container[key_lower] = [vals[0], vals[1], val] def extend(self, *args, **kwargs): """Generic import function for any type of header-like object. @@ -236,7 +236,7 @@ class HTTPHeaderDict(dict): raise TypeError("extend() takes at most 1 positional " "arguments ({} 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) @@ -257,7 +257,7 @@ class HTTPHeaderDict(dict): """Returns a list of all the values for the named field. Returns an empty list if the key doesn't exist.""" try: - vals = _dict_getitem(self, key.lower()) + vals = self._container[key.lower()] except KeyError: return [] else: @@ -276,11 +276,11 @@ class HTTPHeaderDict(dict): def _copy_from(self, other): for key in other: - val = _dict_getitem(other, key) + val = other.getlist(key) if isinstance(val, list): # Don't need to convert tuples val = list(val) - _dict_setitem(self, key, val) + self._container[key.lower()] = [key] + val def copy(self): clone = type(self)() @@ -290,14 +290,14 @@ class HTTPHeaderDict(dict): def iteritems(self): """Iterate over all header lines, including duplicate ones.""" for key in self: - vals = _dict_getitem(self, key) + 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 = _dict_getitem(self, key) + val = self._container[key.lower()] yield val[0], ', '.join(val[1:]) def items(self): @@ -307,16 +307,16 @@ class HTTPHeaderDict(dict): 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 + # 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())) diff --git a/requests/packages/urllib3/connection.py b/requests/packages/urllib3/connection.py index f64dd1a9..3eab1e28 100644 --- a/requests/packages/urllib3/connection.py +++ b/requests/packages/urllib3/connection.py @@ -1,7 +1,7 @@ import datetime import sys import socket -from socket import timeout as SocketTimeout +from socket import error as SocketError, timeout as SocketTimeout import warnings from .packages import six @@ -36,9 +36,10 @@ except NameError: # Python 2: from .exceptions import ( + NewConnectionError, ConnectTimeoutError, - SystemTimeWarning, SubjectAltNameWarning, + SystemTimeWarning, ) from .packages.ssl_match_hostname import match_hostname @@ -133,11 +134,15 @@ class HTTPConnection(_HTTPConnection, object): conn = connection.create_connection( (self.host, self.port), self.timeout, **extra_kw) - except SocketTimeout: + except SocketTimeout as e: raise ConnectTimeoutError( self, "Connection to %s timed out. (connect timeout=%s)" % (self.host, self.timeout)) + except SocketError as e: + raise NewConnectionError( + self, "Failed to establish a new connection: %s" % e) + return conn def _prepare_conn(self, conn): @@ -185,20 +190,23 @@ class VerifiedHTTPSConnection(HTTPSConnection): """ cert_reqs = None ca_certs = None + ca_cert_dir = None ssl_version = None assert_fingerprint = None def set_cert(self, key_file=None, cert_file=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 and cert_reqs is None: + if (ca_certs or ca_cert_dir) and cert_reqs is None: cert_reqs = 'CERT_REQUIRED' self.key_file = key_file self.cert_file = cert_file self.cert_reqs = cert_reqs self.ca_certs = ca_certs + self.ca_cert_dir = ca_cert_dir self.assert_hostname = assert_hostname self.assert_fingerprint = assert_fingerprint @@ -237,6 +245,7 @@ class VerifiedHTTPSConnection(HTTPSConnection): self.sock = ssl_wrap_socket(conn, self.key_file, self.cert_file, cert_reqs=resolved_cert_reqs, ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, server_hostname=hostname, ssl_version=resolved_ssl_version) diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index c9587257..b38ac68d 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -22,10 +22,12 @@ from .exceptions import ( LocationValueError, MaxRetryError, ProxyError, + ConnectTimeoutError, ReadTimeoutError, SSLError, TimeoutError, InsecureRequestWarning, + NewConnectionError, ) from .packages.ssl_match_hostname import CertificateError from .packages import six @@ -422,7 +424,7 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): # TODO: Add optional support for socket.gethostbyname checking. scheme, host, port = get_host(url) - + # Use explicit default port for comparison when none is given if self.port and not port: port = port_by_scheme.get(scheme) @@ -592,13 +594,13 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): release_conn = True raise - except (TimeoutError, HTTPException, SocketError, ConnectionError) as e: + except (TimeoutError, HTTPException, SocketError, ProtocolError) as e: # Discard the connection for these exceptions. It will be # be replaced during the next _get_conn() call. conn = conn and conn.close() release_conn = True - if isinstance(e, SocketError) and self.proxy: + if isinstance(e, (SocketError, NewConnectionError)) and self.proxy: e = ProxyError('Cannot connect to proxy.', e) elif isinstance(e, (SocketError, HTTPException)): e = ProtocolError('Connection aborted.', e) @@ -675,10 +677,10 @@ class HTTPSConnectionPool(HTTPConnectionPool): ``assert_hostname`` and ``host`` in this order to verify connections. If ``assert_hostname`` is False, no verification is done. - The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs`` and - ``ssl_version`` are only used if :mod:`ssl` is available and are fed into - :meth:`urllib3.util.ssl_wrap_socket` to upgrade the connection socket - into an SSL socket. + The ``key_file``, ``cert_file``, ``cert_reqs``, ``ca_certs``, + ``ca_cert_dir``, and ``ssl_version`` are only used if :mod:`ssl` is + available and are fed into :meth:`urllib3.util.ssl_wrap_socket` to upgrade + the connection socket into an SSL socket. """ scheme = 'https' @@ -691,7 +693,7 @@ class HTTPSConnectionPool(HTTPConnectionPool): key_file=None, cert_file=None, cert_reqs=None, ca_certs=None, ssl_version=None, assert_hostname=None, assert_fingerprint=None, - **conn_kw): + ca_cert_dir=None, **conn_kw): HTTPConnectionPool.__init__(self, host, port, strict, timeout, maxsize, block, headers, retries, _proxy, _proxy_headers, @@ -704,6 +706,7 @@ class HTTPSConnectionPool(HTTPConnectionPool): self.cert_file = cert_file self.cert_reqs = cert_reqs self.ca_certs = ca_certs + self.ca_cert_dir = ca_cert_dir self.ssl_version = ssl_version self.assert_hostname = assert_hostname self.assert_fingerprint = assert_fingerprint @@ -719,6 +722,7 @@ class HTTPSConnectionPool(HTTPConnectionPool): cert_file=self.cert_file, cert_reqs=self.cert_reqs, ca_certs=self.ca_certs, + ca_cert_dir=self.ca_cert_dir, assert_hostname=self.assert_hostname, assert_fingerprint=self.assert_fingerprint) conn.ssl_version = self.ssl_version diff --git a/requests/packages/urllib3/contrib/pyopenssl.py b/requests/packages/urllib3/contrib/pyopenssl.py index 19c5b4ee..c20ae46d 100644 --- a/requests/packages/urllib3/contrib/pyopenssl.py +++ b/requests/packages/urllib3/contrib/pyopenssl.py @@ -267,7 +267,7 @@ def _verify_callback(cnx, x509, err_no, err_depth, return_code): def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=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]) if certfile: keyfile = keyfile or certfile # Match behaviour of the normal python ssl library @@ -276,9 +276,9 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, ctx.use_privatekey_file(keyfile) if cert_reqs != ssl.CERT_NONE: ctx.set_verify(_openssl_verify[cert_reqs], _verify_callback) - if ca_certs: + if ca_certs or ca_cert_dir: try: - ctx.load_verify_locations(ca_certs, None) + ctx.load_verify_locations(ca_certs, ca_cert_dir) except OpenSSL.SSL.Error as e: raise ssl.SSLError('bad ca_certs: %r' % ca_certs, e) else: diff --git a/requests/packages/urllib3/exceptions.py b/requests/packages/urllib3/exceptions.py index 36ce0d1e..9607d65f 100644 --- a/requests/packages/urllib3/exceptions.py +++ b/requests/packages/urllib3/exceptions.py @@ -112,6 +112,9 @@ class ConnectTimeoutError(TimeoutError): "Raised when a socket timeout occurs while connecting to a server" pass +class NewConnectionError(ConnectTimeoutError, PoolError): + "Raised when we fail to establish a new connection. Usually ECONNREFUSED." + pass class EmptyPoolError(PoolError): "Raised when a pool runs out of connections and no more are allowed." diff --git a/requests/packages/urllib3/response.py b/requests/packages/urllib3/response.py index 15d4aaca..788eb6ca 100644 --- a/requests/packages/urllib3/response.py +++ b/requests/packages/urllib3/response.py @@ -1,7 +1,3 @@ -try: - import http.client as httplib -except ImportError: - import httplib from contextlib import contextmanager import zlib import io @@ -12,6 +8,7 @@ from .exceptions import ( 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 .util.response import is_fp_closed, is_response_to_head diff --git a/requests/packages/urllib3/util/connection.py b/requests/packages/urllib3/util/connection.py index 9ed5a64c..4f2f0f18 100644 --- a/requests/packages/urllib3/util/connection.py +++ b/requests/packages/urllib3/util/connection.py @@ -80,16 +80,16 @@ def create_connection(address, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, sock.connect(sa) return sock - except socket.error as _: - err = _ + except socket.error as e: + err = e if sock is not None: sock.close() sock = None if err is not None: 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): diff --git a/requests/packages/urllib3/util/response.py b/requests/packages/urllib3/util/response.py index 767ee158..2c1de154 100644 --- a/requests/packages/urllib3/util/response.py +++ b/requests/packages/urllib3/util/response.py @@ -1,7 +1,4 @@ -try: - import http.client as httplib -except ImportError: - import httplib +from ..packages.six.moves import http_client as httplib from ..exceptions import HeaderParsingError diff --git a/requests/packages/urllib3/util/ssl_.py b/requests/packages/urllib3/util/ssl_.py index 311378bf..47b817e3 100644 --- a/requests/packages/urllib3/util/ssl_.py +++ b/requests/packages/urllib3/util/ssl_.py @@ -75,8 +75,11 @@ except ImportError: self.certfile = certfile self.keyfile = keyfile - def load_verify_locations(self, location): - self.ca_certs = location + def load_verify_locations(self, cafile=None, capath=None): + self.ca_certs = cafile + + if capath is not None: + raise SSLError("CA directories not supported in older Pythons") def set_ciphers(self, cipher_suite): if not self.supports_set_ciphers: @@ -240,10 +243,11 @@ def create_urllib3_context(ssl_version=None, cert_reqs=None, def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=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 - meaning as they do when using :func:`ssl.wrap_socket`. + All arguments except for server_hostname, ssl_context, and ca_cert_dir have + the same meaning as they do when using :func:`ssl.wrap_socket`. :param server_hostname: When SNI is supported, the expected hostname of the certificate @@ -253,15 +257,19 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, :param ciphers: 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. + :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 if context is None: context = create_urllib3_context(ssl_version, cert_reqs, ciphers=ciphers) - if ca_certs: + if ca_certs or ca_cert_dir: 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 raise SSLError(e) # Py33 raises FileNotFoundError which subclasses OSError @@ -270,6 +278,7 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, if e.errno == errno.ENOENT: raise SSLError(e) raise + if certfile: context.load_cert_chain(certfile, keyfile) if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI From 8dcd9f12de656706af70122b84d3c5bfb7e77fda Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 5 Oct 2015 15:38:31 +0100 Subject: [PATCH 488/545] Prepare changelog for 2.8.0 --- HISTORY.rst | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index a9149917..2c1c2b13 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,41 @@ Release History --------------- +2.8.0 (2015-10-05) +++++++++++++++++++ + +**Minor Improvements** (Backwards Compatible) + +- Requests now supports per-host proxies. This allows the ``proxies`` + dictionary to have entries of the form + ``{'://': ''}``. Host-specific proxies will be used + in preference to the previously-supported scheme-specific ones, but the + previous syntax will continue to work. +- ``Response.raise_for_status`` now prints the URL that failed as part of the + exception message. +- ``requests.utils.get_netrc_auth`` now takes an ``raise_errors`` kwarg, + defaulting to ``False``. When ``True``, errors parsing ``.netrc`` files cause + exceptions to be thrown. +- Change to bundled projects import logic to make it easier to unbundle + requests downstream. + +**Bugfixes** + +- The ``json`` parameter to ``post()`` and friends will now only be used if + neither ``data`` nor ``files`` are present, consistent with the + documentation. +- We now ignore empty fields in the ``NO_PROXY`` enviroment variable. +- Fixed problem where ``httplib.BadStatusLine`` would get raised if combining + ``stream=True`` with ``contextlib.closing``. +- Prevented bugs where we would attempt to return the same connection back to + the connection pool twice when sending a Chunked body. +- Miscellaneous minor internal changes. +- Digest Auth support is now thread safe. + +**Updates** + +- Updated urllib3 to 1.12. + 2.7.0 (2015-05-03) ++++++++++++++++++ From 397f10bac76b74e3e6efcb5c4be2fe735382d7bc Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 5 Oct 2015 15:41:04 +0100 Subject: [PATCH 489/545] Increment version numbers. --- requests/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/__init__.py b/requests/__init__.py index d2471284..546ee2bf 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.7.0' -__build__ = 0x020700 +__version__ = '2.8.0' +__build__ = 0x020800 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2015 Kenneth Reitz' From ba7d09354a5bbe552a863ea3927199932fe76832 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 5 Oct 2015 15:57:05 +0100 Subject: [PATCH 490/545] Changelog for #2785. --- HISTORY.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 2c1c2b13..4ac66ce0 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -20,6 +20,8 @@ Release History exceptions to be thrown. - Change to bundled projects import logic to make it easier to unbundle requests downstream. +- Changed the default User-Agent string to avoid leaking data on Linux: now + contains only the requests version. **Bugfixes** From 23051979f431ba7512c68968f44a9ff498c112a3 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 8 Oct 2015 08:09:53 +0100 Subject: [PATCH 491/545] Update requests certificate bundle. This brings it up-to-date with the certifi 2015.9.6.2 release, specifically its `weak.pem` bundle. --- requests/cacert.pem | 2180 +++++++++++++++++++++++++++---------------- 1 file changed, 1385 insertions(+), 795 deletions(-) diff --git a/requests/cacert.pem b/requests/cacert.pem index 729fe15d..6a66daa9 100644 --- a/requests/cacert.pem +++ b/requests/cacert.pem @@ -1,83 +1,3 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -# Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. -# Subject: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. -# Label: "GTE CyberTrust Global Root" -# Serial: 421 -# MD5 Fingerprint: ca:3d:d3:68:f1:03:5c:d0:32:fa:b8:2b:59:e8:5a:db -# SHA1 Fingerprint: 97:81:79:50:d8:1c:96:70:cc:34:d8:09:cf:79:44:31:36:7e:f4:74 -# SHA256 Fingerprint: a5:31:25:18:8d:21:10:aa:96:4b:02:c7:b7:c6:da:32:03:17:08:94:e5:fb:71:ff:fb:66:67:d5:e6:81:0a:36 ------BEGIN CERTIFICATE----- -MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD -VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv -bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv -b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV -UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU -cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds -b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH -iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS -r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 -04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r -GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 -3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P -lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ ------END CERTIFICATE----- - -# Issuer: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division -# Subject: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division -# Label: "Thawte Server CA" -# Serial: 1 -# MD5 Fingerprint: c5:70:c4:a2:ed:53:78:0c:c8:10:53:81:64:cb:d0:1d -# SHA1 Fingerprint: 23:e5:94:94:51:95:f2:41:48:03:b4:d5:64:d2:a3:a3:f5:d8:8b:8c -# SHA256 Fingerprint: b4:41:0b:73:e2:e6:ea:ca:47:fb:c4:2f:8f:a4:01:8a:f4:38:1d:c5:4c:fa:a8:44:50:46:1e:ed:09:45:4d:e9 ------BEGIN CERTIFICATE----- -MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx -FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD -VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv -biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm -MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx -MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT -DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3 -dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl -cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3 -DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD -gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91 -yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX -L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj -EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG -7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e -QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ -qdq5snUb9kLy78fyGPmJvKP/iiMucEc= ------END CERTIFICATE----- - -# Issuer: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division -# Subject: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division -# Label: "Thawte Premium Server CA" -# Serial: 1 -# MD5 Fingerprint: 06:9f:69:79:16:66:90:02:1b:8c:8c:a2:c3:07:6f:3a -# SHA1 Fingerprint: 62:7f:8d:78:27:65:63:99:d2:7d:7f:90:44:c9:fe:b3:f3:3e:fa:9a -# SHA256 Fingerprint: ab:70:36:36:5c:71:54:aa:29:c2:c2:9f:5d:41:91:16:3b:16:2a:22:25:01:13:57:d5:6d:07:ff:a7:bc:1f:72 ------BEGIN CERTIFICATE----- -MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx -FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD -VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv -biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy -dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t -MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB -MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG -A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp -b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl -cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv -bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE -VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ -ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR -uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG -9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI -hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM -pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg== ------END CERTIFICATE----- # Issuer: O=Equifax OU=Equifax Secure Certificate Authority # Subject: O=Equifax OU=Equifax Secure Certificate Authority @@ -106,55 +26,6 @@ A4GBAFjOKer89961zgK5F7WF0bnj4JXMJTENAKaSbn+2kmOeUJXRmm/kEd5jhW6Y 1voqZiegDfqnc1zqcPGUIWVEX/r87yloqaKHee9570+sB3c4 -----END CERTIFICATE----- -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Label: "Verisign Class 3 Public Primary Certification Authority" -# Serial: 149843929435818692848040365716851702463 -# MD5 Fingerprint: 10:fc:63:5d:f6:26:3e:0d:f3:25:be:5f:79:cd:67:67 -# SHA1 Fingerprint: 74:2c:31:92:e6:07:e4:24:eb:45:49:54:2b:e1:bb:c5:3e:61:74:e2 -# SHA256 Fingerprint: e7:68:56:34:ef:ac:f6:9a:ce:93:9a:6b:25:5b:7b:4f:ab:ef:42:93:5b:50:a2:65:ac:b5:cb:60:27:e4:4e:70 ------BEGIN CERTIFICATE----- -MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz -cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 -MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV -BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt -YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN -ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE -BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is -I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G -CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do -lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc -AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k ------END CERTIFICATE----- - -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network -# Label: "Verisign Class 3 Public Primary Certification Authority - G2" -# Serial: 167285380242319648451154478808036881606 -# MD5 Fingerprint: a2:33:9b:4c:74:78:73:d4:6c:e7:c1:f3:8d:cb:5c:e9 -# SHA1 Fingerprint: 85:37:1c:a6:e5:50:14:3d:ce:28:03:47:1b:de:3a:09:e8:f8:77:0f -# SHA256 Fingerprint: 83:ce:3c:12:29:68:8a:59:3d:48:5f:81:97:3c:0f:91:95:43:1e:da:37:cc:5e:36:43:0e:79:c7:a8:88:63:8b ------BEGIN CERTIFICATE----- -MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ -BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh -c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy -MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp -emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X -DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw -FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg -UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo -YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 -MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB -AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 -pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 -13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID -AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk -U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i -F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY -oJ2daZH9 ------END CERTIFICATE----- - # Issuer: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA # Subject: CN=GlobalSign Root CA O=GlobalSign nv-sa OU=Root CA # Label: "GlobalSign Root CA" @@ -214,84 +85,6 @@ AfvDbbnvRG15RjF+Cv6pgsH/76tuIMRQyV+dTZsXjAzlAcmgQWpzU/qlULRuJQ/7 TBj0/VLZjmmx6BEP3ojY+x1J96relc8geMJgEtslQIxq/H5COEBkEveegeGTLg== -----END CERTIFICATE----- -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority -# Label: "ValiCert Class 1 VA" -# Serial: 1 -# MD5 Fingerprint: 65:58:ab:15:ad:57:6c:1e:a8:a7:b5:69:ac:bf:ff:eb -# SHA1 Fingerprint: e5:df:74:3c:b6:01:c4:9b:98:43:dc:ab:8c:e8:6a:81:10:9f:e4:8e -# SHA256 Fingerprint: f4:c1:49:55:1a:30:13:a3:5b:c7:bf:fe:17:a7:f3:44:9b:c1:ab:5b:5a:0a:e7:4b:06:c2:3b:90:00:4c:01:04 ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy -NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y -LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+ -TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y -TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0 -LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW -I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw -nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI ------END CERTIFICATE----- - -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority -# Label: "ValiCert Class 2 VA" -# Serial: 1 -# MD5 Fingerprint: a9:23:75:9b:ba:49:36:6e:31:c2:db:f2:e7:66:ba:87 -# SHA1 Fingerprint: 31:7a:2a:d0:7f:2b:33:5e:f5:a1:c3:4e:4b:57:e8:b7:d8:f1:fc:a6 -# SHA256 Fingerprint: 58:d0:17:27:9c:d4:dc:63:ab:dd:b1:96:a6:c9:90:6c:30:c4:e0:87:83:ea:e8:c1:60:99:54:d6:93:55:59:6b ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy -NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY -dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9 -WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS -v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v -UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu -IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC -W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd ------END CERTIFICATE----- - -# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority -# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority -# Label: "RSA Root Certificate 1" -# Serial: 1 -# MD5 Fingerprint: a2:6f:53:b7:ee:40:db:4a:68:e7:fa:18:d9:10:4b:72 -# SHA1 Fingerprint: 69:bd:8c:f4:9c:d3:00:fb:59:2e:17:93:ca:55:6a:f3:ec:aa:35:fb -# SHA256 Fingerprint: bc:23:f9:8a:31:3c:b9:2d:e3:bb:fc:3a:5a:9f:44:61:ac:39:49:4c:4a:e1:5a:9e:9d:f1:31:e9:9b:73:01:9a ------BEGIN CERTIFICATE----- -MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 -IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz -BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y -aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG -9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy -NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y -azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs -YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw -Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl -cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD -cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs -2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY -JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE -Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ -n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A -PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu ------END CERTIFICATE----- - # Issuer: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only # Subject: CN=VeriSign Class 3 Public Primary Certification Authority - G3 O=VeriSign, Inc. OU=VeriSign Trust Network/(c) 1999 VeriSign, Inc. - For authorized use only # Label: "Verisign Class 3 Public Primary Certification Authority - G3" @@ -356,42 +149,6 @@ fjaF3H48ZwC15DtS4KjrXRX5xm3wrR0OhbepmnMUWluPQSjA1egtTaRezarZ7c7c bLvSHgCwIe34QWKCudiyxLtGUPMxxY8BqHTr9Xgn2uf3ZkPznoM+IKrDNWCRzg== -----END CERTIFICATE----- -# Issuer: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Subject: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited -# Label: "Entrust.net Secure Server CA" -# Serial: 927650371 -# MD5 Fingerprint: df:f2:80:73:cc:f1:e6:61:73:fc:f5:42:e9:c5:7c:ee -# SHA1 Fingerprint: 99:a6:9b:e6:1a:fe:88:6b:4d:2b:82:00:7c:b8:54:fc:31:7e:15:39 -# SHA256 Fingerprint: 62:f2:40:27:8c:56:4c:4d:d8:bf:7d:9d:4f:6f:36:6e:a8:94:d2:2f:5f:34:d9:89:a9:83:ac:ec:2f:ff:ed:50 ------BEGIN CERTIFICATE----- -MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC -VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u -ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc -KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u -ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 -MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE -ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j -b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF -bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg -U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA -A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ -I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 -wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC -AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb -oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 -BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p -dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk -MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp -b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu -dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 -MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi -E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa -MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI -hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN -95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd -2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= ------END CERTIFICATE----- - # Issuer: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited # Subject: CN=Entrust.net Certification Authority (2048) O=Entrust.net OU=www.entrust.net/CPS_2048 incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited # Label: "Entrust.net Premium 2048 Secure Server CA" @@ -454,54 +211,6 @@ ksLi4xaNmjICq44Y3ekQEe5+NauQrz4wlHrQMz2nZQ/1/I6eYs9HRCwBXbsdtTLS R9I4LtD+gdwyah617jzV/OeBHRnDJELqYzmp -----END CERTIFICATE----- -# Issuer: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. -# Subject: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. -# Label: "Equifax Secure Global eBusiness CA" -# Serial: 1 -# MD5 Fingerprint: 8f:5d:77:06:27:c4:98:3c:5b:93:78:e7:d7:7d:9b:cc -# SHA1 Fingerprint: 7e:78:4a:10:1c:82:65:cc:2d:e1:f1:6d:47:b4:40:ca:d9:0a:19:45 -# SHA256 Fingerprint: 5f:0b:62:ea:b5:e3:53:ea:65:21:65:16:58:fb:b6:53:59:f4:43:28:0a:4a:fb:d1:04:d7:7d:10:f9:f0:4c:07 ------BEGIN CERTIFICATE----- -MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT -ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw -MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj -dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l -c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC -UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc -58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/ -o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH -MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr -aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA -A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA -Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv -8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV ------END CERTIFICATE----- - -# Issuer: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. -# Subject: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. -# Label: "Equifax Secure eBusiness CA 1" -# Serial: 4 -# MD5 Fingerprint: 64:9c:ef:2e:44:fc:c6:8f:52:07:d0:51:73:8f:cb:3d -# SHA1 Fingerprint: da:40:18:8b:91:89:a3:ed:ee:ae:da:97:fe:2f:9d:f5:b7:d1:8a:41 -# SHA256 Fingerprint: cf:56:ff:46:a4:a1:86:10:9d:d9:65:84:b5:ee:b5:8a:51:0c:42:75:b0:e5:f9:4f:40:bb:ae:86:5e:19:f6:73 ------BEGIN CERTIFICATE----- -MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT -ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw -MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j -LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ -KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo -RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu -WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw -Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD -AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK -eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM -zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+ -WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN -/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ== ------END CERTIFICATE----- - # Issuer: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network # Subject: CN=AddTrust Class 1 CA Root O=AddTrust AB OU=AddTrust TTP Network # Label: "AddTrust Low-Value Services Root" @@ -831,77 +540,6 @@ OCiNUW7dFGdTbHFcJoRNdVq2fmBWqU2t+5sel/MN2dKXVHfaPRK34B7vCAas+YWH QMAJKOSLakhT2+zNVVXxxvjpoixMptEmX36vWkzaH6byHCx+rgIW0lbQL1dTR+iS -----END CERTIFICATE----- -# Issuer: CN=America Online Root Certification Authority 1 O=America Online Inc. -# Subject: CN=America Online Root Certification Authority 1 O=America Online Inc. -# Label: "America Online Root Certification Authority 1" -# Serial: 1 -# MD5 Fingerprint: 14:f1:08:ad:9d:fa:64:e2:89:e7:1c:cf:a8:ad:7d:5e -# SHA1 Fingerprint: 39:21:c1:15:c1:5d:0e:ca:5c:cb:5b:c4:f0:7d:21:d8:05:0b:56:6a -# SHA256 Fingerprint: 77:40:73:12:c6:3a:15:3d:5b:c0:0b:4e:51:75:9c:df:da:c2:37:dc:2a:33:b6:79:46:e9:8e:9b:fa:68:0a:e3 ------BEGIN CERTIFICATE----- -MIIDpDCCAoygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP -bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAxMB4XDTAyMDUyODA2 -MDAwMFoXDTM3MTExOTIwNDMwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft -ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAKgv6KRpBgNHw+kqmP8ZonCaxlCyfqXfaE0bfA+2l2h9LaaLl+lk -hsmj76CGv2BlnEtUiMJIxUo5vxTjWVXlGbR0yLQFOVwWpeKVBeASrlmLojNoWBym -1BW32J/X3HGrfpq/m44zDyL9Hy7nBzbvYjnF3cu6JRQj3gzGPTzOggjmZj7aUTsW -OqMFf6Dch9Wc/HKpoH145LcxVR5lu9RhsCFg7RAycsWSJR74kEoYeEfffjA3PlAb -2xzTa5qGUwew76wGePiEmf4hjUyAtgyC9mZweRrTT6PP8c9GsEsPPt2IYriMqQko -O3rHl+Ee5fSfwMCuJKDIodkP1nsmgmkyPacCAwEAAaNjMGEwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUAK3Zo/Z59m50qX8zPYEX10zPM94wHwYDVR0jBBgwFoAU -AK3Zo/Z59m50qX8zPYEX10zPM94wDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEB -BQUAA4IBAQB8itEfGDeC4Liwo+1WlchiYZwFos3CYiZhzRAW18y0ZTTQEYqtqKkF -Zu90821fnZmv9ov761KyBZiibyrFVL0lvV+uyIbqRizBs73B6UlwGBaXCBOMIOAb -LjpHyx7kADCVW/RFo8AasAFOq73AI25jP4BKxQft3OJvx8Fi8eNy1gTIdGcL+oir -oQHIb/AUr9KZzVGTfu0uOMe9zkZQPXLjeSWdm4grECDdpbgyn43gKd8hdIaC2y+C -MMbHNYaz+ZZfRtsMRf3zUMNvxsNIrUam4SdHCh0Om7bCd39j8uB9Gr784N/Xx6ds -sPmuujz9dLQR6FgNgLzTqIA6me11zEZ7 ------END CERTIFICATE----- - -# Issuer: CN=America Online Root Certification Authority 2 O=America Online Inc. -# Subject: CN=America Online Root Certification Authority 2 O=America Online Inc. -# Label: "America Online Root Certification Authority 2" -# Serial: 1 -# MD5 Fingerprint: d6:ed:3c:ca:e2:66:0f:af:10:43:0d:77:9b:04:09:bf -# SHA1 Fingerprint: 85:b5:ff:67:9b:0c:79:96:1f:c8:6e:44:22:00:46:13:db:17:92:84 -# SHA256 Fingerprint: 7d:3b:46:5a:60:14:e5:26:c0:af:fc:ee:21:27:d2:31:17:27:ad:81:1c:26:84:2d:00:6a:f3:73:06:cc:80:bd ------BEGIN CERTIFICATE----- -MIIFpDCCA4ygAwIBAgIBATANBgkqhkiG9w0BAQUFADBjMQswCQYDVQQGEwJVUzEc -MBoGA1UEChMTQW1lcmljYSBPbmxpbmUgSW5jLjE2MDQGA1UEAxMtQW1lcmljYSBP -bmxpbmUgUm9vdCBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAyMB4XDTAyMDUyODA2 -MDAwMFoXDTM3MDkyOTE0MDgwMFowYzELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0Ft -ZXJpY2EgT25saW5lIEluYy4xNjA0BgNVBAMTLUFtZXJpY2EgT25saW5lIFJvb3Qg -Q2VydGlmaWNhdGlvbiBBdXRob3JpdHkgMjCCAiIwDQYJKoZIhvcNAQEBBQADggIP -ADCCAgoCggIBAMxBRR3pPU0Q9oyxQcngXssNt79Hc9PwVU3dxgz6sWYFas14tNwC -206B89enfHG8dWOgXeMHDEjsJcQDIPT/DjsS/5uN4cbVG7RtIuOx238hZK+GvFci -KtZHgVdEglZTvYYUAQv8f3SkWq7xuhG1m1hagLQ3eAkzfDJHA1zEpYNI9FdWboE2 -JxhP7JsowtS013wMPgwr38oE18aO6lhOqKSlGBxsRZijQdEt0sdtjRnxrXm3gT+9 -BoInLRBYBbV4Bbkv2wxrkJB+FFk4u5QkE+XRnRTf04JNRvCAOVIyD+OEsnpD8l7e -Xz8d3eOyG6ChKiMDbi4BFYdcpnV1x5dhvt6G3NRI270qv0pV2uh9UPu0gBe4lL8B -PeraunzgWGcXuVjgiIZGZ2ydEEdYMtA1fHkqkKJaEBEjNa0vzORKW6fIJ/KD3l67 -Xnfn6KVuY8INXWHQjNJsWiEOyiijzirplcdIz5ZvHZIlyMbGwcEMBawmxNJ10uEq -Z8A9W6Wa6897GqidFEXlD6CaZd4vKL3Ob5Rmg0gp2OpljK+T2WSfVVcmv2/LNzGZ -o2C7HK2JNDJiuEMhBnIMoVxtRsX6Kc8w3onccVvdtjc+31D1uAclJuW8tf48ArO3 -+L5DwYcRlJ4jbBeKuIonDFRH8KmzwICMoCfrHRnjB453cMor9H124HhnAgMBAAGj -YzBhMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFE1FwWg4u3OpaaEg5+31IqEj -FNeeMB8GA1UdIwQYMBaAFE1FwWg4u3OpaaEg5+31IqEjFNeeMA4GA1UdDwEB/wQE -AwIBhjANBgkqhkiG9w0BAQUFAAOCAgEAZ2sGuV9FOypLM7PmG2tZTiLMubekJcmn -xPBUlgtk87FYT15R/LKXeydlwuXK5w0MJXti4/qftIe3RUavg6WXSIylvfEWK5t2 -LHo1YGwRgJfMqZJS5ivmae2p+DYtLHe/YUjRYwu5W1LtGLBDQiKmsXeu3mnFzccc -obGlHBD7GL4acN3Bkku+KVqdPzW+5X1R+FXgJXUjhx5c3LqdsKyzadsXg8n33gy8 -CNyRnqjQ1xU3c6U1uPx+xURABsPr+CKAXEfOAuMRn0T//ZoyzH1kUQ7rVyZ2OuMe -IjzCpjbdGe+n/BLzJsBZMYVMnNjP36TMzCmT/5RtdlwTCJfy7aULTd3oyWgOZtMA -DjMSW7yV5TKQqLPGbIOtd+6Lfn6xqavT4fG2wLHqiMDn05DpKJKUe2h7lyoKZy2F -AjgQ5ANh1NolNscIWC2hp1GvMApJ9aZphwctREZ2jirlmjvXGKL8nDgQzMY70rUX -Om/9riW99XJZZLF0KjhfGEzfz3EEWjbUvy+ZnOjZurGV5gJLIaFb1cFPj65pbVPb -AZO1XB4Y3WRayhgoPmMEEf0cjQAPuDffZ4qdZqkCapH/E8ovXYO8h5Ns3CRRFgQl -Zvqz2cK6Kb6aSDiCmfS/O0oxGfm/jiEzFMpPVF/7zvuPcX/9XhmgD0uRuMRUvAaw -RY8mkaKO/qk= ------END CERTIFICATE----- - # Issuer: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association # Subject: CN=Visa eCommerce Root O=VISA OU=Visa International Service Association # Label: "Visa eCommerce Root" @@ -1272,39 +910,6 @@ u3pCMuiufi3IvKwUv9kP2Vv8wfl6leF9fpb8cbDCTMjfRTTJzg3ynGQI0DvDKcWy iJf2fL1LuCAWZwWN4jvBcj+UlTfHXbme2JOhF4//DGYVwSR8MnwDHTuhWEUykw== -----END CERTIFICATE----- -# Issuer: O=TDC Internet OU=TDC Internet Root CA -# Subject: O=TDC Internet OU=TDC Internet Root CA -# Label: "TDC Internet Root CA" -# Serial: 986490188 -# MD5 Fingerprint: 91:f4:03:55:20:a1:f8:63:2c:62:de:ac:fb:61:1c:8e -# SHA1 Fingerprint: 21:fc:bd:8e:7f:6c:af:05:1b:d1:b3:43:ec:a8:e7:61:47:f2:0f:8a -# SHA256 Fingerprint: 48:98:c6:88:8c:0c:ff:b0:d3:e3:1a:ca:8a:37:d4:e3:51:5f:f7:46:d0:26:35:d8:66:46:cf:a0:a3:18:5a:e7 ------BEGIN CERTIFICATE----- -MIIEKzCCAxOgAwIBAgIEOsylTDANBgkqhkiG9w0BAQUFADBDMQswCQYDVQQGEwJE -SzEVMBMGA1UEChMMVERDIEludGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQg -Um9vdCBDQTAeFw0wMTA0MDUxNjMzMTdaFw0yMTA0MDUxNzAzMTdaMEMxCzAJBgNV -BAYTAkRLMRUwEwYDVQQKEwxUREMgSW50ZXJuZXQxHTAbBgNVBAsTFFREQyBJbnRl -cm5ldCBSb290IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxLhA -vJHVYx/XmaCLDEAedLdInUaMArLgJF/wGROnN4NrXceO+YQwzho7+vvOi20jxsNu -Zp+Jpd/gQlBn+h9sHvTQBda/ytZO5GhgbEaqHF1j4QeGDmUApy6mcca8uYGoOn0a -0vnRrEvLznWv3Hv6gXPU/Lq9QYjUdLP5Xjg6PEOo0pVOd20TDJ2PeAG3WiAfAzc1 -4izbSysseLlJ28TQx5yc5IogCSEWVmb/Bexb4/DPqyQkXsN/cHoSxNK1EKC2IeGN -eGlVRGn1ypYcNIUXJXfi9i8nmHj9eQY6otZaQ8H/7AQ77hPv01ha/5Lr7K7a8jcD -R0G2l8ktCkEiu7vmpwIDAQABo4IBJTCCASEwEQYJYIZIAYb4QgEBBAQDAgAHMGUG -A1UdHwReMFwwWqBYoFakVDBSMQswCQYDVQQGEwJESzEVMBMGA1UEChMMVERDIElu -dGVybmV0MR0wGwYDVQQLExRUREMgSW50ZXJuZXQgUm9vdCBDQTENMAsGA1UEAxME -Q1JMMTArBgNVHRAEJDAigA8yMDAxMDQwNTE2MzMxN1qBDzIwMjEwNDA1MTcwMzE3 -WjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAUbGQBx/2FbazI2p5QCIUItTxWqFAw -HQYDVR0OBBYEFGxkAcf9hW2syNqeUAiFCLU8VqhQMAwGA1UdEwQFMAMBAf8wHQYJ -KoZIhvZ9B0EABBAwDhsIVjUuMDo0LjADAgSQMA0GCSqGSIb3DQEBBQUAA4IBAQBO -Q8zR3R0QGwZ/t6T609lN+yOfI1Rb5osvBCiLtSdtiaHsmGnc540mgwV5dOy0uaOX -wTUA/RXaOYE6lTGQ3pfphqiZdwzlWqCE/xIWrG64jcN7ksKsLtB9KOy282A4aW8+ -2ARVPp7MVdK6/rtHBNcK2RYKNCn1WBPVT8+PVkuzHu7TmHnaCB4Mb7j4Fifvwm89 -9qNLPg7kbWzbO0ESm70NRyN/PErQr8Cv9u8btRXE64PECV90i9kR+8JWsTz4cMo0 -jUNAE4z9mQNUecYu6oah9jrUCbz0vGbMPVjQV0kK7iXiQe4T+Zs4NNEA9X7nlB38 -aQNiuJkFBT1reBK9sG9l ------END CERTIFICATE----- - # Issuer: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com # Subject: CN=UTN - DATACorp SGC O=The USERTRUST Network OU=http://www.usertrust.com # Label: "UTN DATACorp SGC Root CA" @@ -1490,84 +1095,6 @@ f1qbFFgBJ34TUMdrKuZoPL9coAob4Q566eKAw+np9v1sEZ7Q5SgnK1QyQhSCdeZK 8CtmdWOMovsEPoMOmzbwGOQmIMOM8CgHrTwXZoi1/baI -----END CERTIFICATE----- -# Issuer: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Subject: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Label: "NetLock Business (Class B) Root" -# Serial: 105 -# MD5 Fingerprint: 39:16:aa:b9:6a:41:e1:14:69:df:9e:6c:3b:72:dc:b6 -# SHA1 Fingerprint: 87:9f:4b:ee:05:df:98:58:3b:e3:60:d6:33:e7:0d:3f:fe:98:71:af -# SHA256 Fingerprint: 39:df:7b:68:2b:7b:93:8f:84:71:54:81:cc:de:8d:60:d8:f2:2e:c5:98:87:7d:0a:aa:c1:2b:59:18:2b:03:12 ------BEGIN CERTIFICATE----- -MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx -ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 -b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD -EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05 -OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G -A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh -Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l -dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG -SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK -gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX -iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc -Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E -BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G -SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu -b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh -bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv -Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln -aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0 -IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh -c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph -biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo -ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP -UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj -YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo -dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA -bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06 -sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa -n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS -NitjrFgBazMpUIaD8QFI ------END CERTIFICATE----- - -# Issuer: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Subject: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok -# Label: "NetLock Express (Class C) Root" -# Serial: 104 -# MD5 Fingerprint: 4f:eb:f1:f0:70:c2:80:63:5d:58:9f:da:12:3c:a9:c4 -# SHA1 Fingerprint: e3:92:51:2f:0a:cf:f5:05:df:f6:de:06:7f:75:37:e1:65:ea:57:4b -# SHA256 Fingerprint: 0b:5e:ed:4e:84:64:03:cf:55:e0:65:84:84:40:ed:2a:82:75:8b:f5:b9:aa:1f:25:3d:46:13:cf:a0:80:ff:3f ------BEGIN CERTIFICATE----- -MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx -ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 -b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD -EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X -DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw -DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u -c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr -TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN -BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA -OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC -2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW -RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P -AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW -ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0 -YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz -b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO -ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB -IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs -b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs -ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s -YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg -a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g -SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0 -aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg -YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg -Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY -ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g -pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4 -Fp1hBWeAyNDYpQcCNJgEjTME1A== ------END CERTIFICATE----- - # Issuer: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Subject: CN=XRamp Global Certification Authority O=XRamp Security Services Inc OU=www.xrampsecurity.com # Label: "XRamp Global CA Root" @@ -1757,40 +1284,6 @@ LMDDav7v3Aun+kbfYNucpllQdSNpc5Oy+fwC00fmcc4QAu4njIT/rEUNE1yDMuAl pYYsfPQS -----END CERTIFICATE----- -# Issuer: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Subject: CN=Autoridad de Certificacion Firmaprofesional CIF A62634068 -# Label: "Firmaprofesional Root CA" -# Serial: 1 -# MD5 Fingerprint: 11:92:79:40:3c:b1:83:40:e5:ab:66:4a:67:92:80:df -# SHA1 Fingerprint: a9:62:8f:4b:98:a9:1b:48:35:ba:d2:c1:46:32:86:bb:66:64:6a:8c -# SHA256 Fingerprint: c1:cf:0b:52:09:64:35:e3:f1:b7:1d:aa:ec:45:5a:23:11:c8:40:4f:55:83:a9:e2:13:c6:9d:85:7d:94:33:05 ------BEGIN CERTIFICATE----- -MIIEVzCCAz+gAwIBAgIBATANBgkqhkiG9w0BAQUFADCBnTELMAkGA1UEBhMCRVMx -IjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1 -dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 -MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20w -HhcNMDExMDI0MjIwMDAwWhcNMTMxMDI0MjIwMDAwWjCBnTELMAkGA1UEBhMCRVMx -IjAgBgNVBAcTGUMvIE11bnRhbmVyIDI0NCBCYXJjZWxvbmExQjBABgNVBAMTOUF1 -dG9yaWRhZCBkZSBDZXJ0aWZpY2FjaW9uIEZpcm1hcHJvZmVzaW9uYWwgQ0lGIEE2 -MjYzNDA2ODEmMCQGCSqGSIb3DQEJARYXY2FAZmlybWFwcm9mZXNpb25hbC5jb20w -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDnIwNvbyOlXnjOlSztlB5u -Cp4Bx+ow0Syd3Tfom5h5VtP8c9/Qit5Vj1H5WuretXDE7aTt/6MNbg9kUDGvASdY -rv5sp0ovFy3Tc9UTHI9ZpTQsHVQERc1ouKDAA6XPhUJHlShbz++AbOCQl4oBPB3z -hxAwJkh91/zpnZFx/0GaqUC1N5wpIE8fUuOgfRNtVLcK3ulqTgesrBlf3H5idPay -BQC6haD9HThuy1q7hryUZzM1gywfI834yJFxzJeL764P3CkDG8A563DtwW4O2GcL -iam8NeTvtjS0pbbELaW+0MOUJEjb35bTALVmGotmBQ/dPz/LP6pemkr4tErvlTcb -AgMBAAGjgZ8wgZwwKgYDVR0RBCMwIYYfaHR0cDovL3d3dy5maXJtYXByb2Zlc2lv -bmFsLmNvbTASBgNVHRMBAf8ECDAGAQH/AgEBMCsGA1UdEAQkMCKADzIwMDExMDI0 -MjIwMDAwWoEPMjAxMzEwMjQyMjAwMDBaMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4E -FgQUMwugZtHq2s7eYpMEKFK1FH84aLcwDQYJKoZIhvcNAQEFBQADggEBAEdz/o0n -VPD11HecJ3lXV7cVVuzH2Fi3AQL0M+2TUIiefEaxvT8Ub/GzR0iLjJcG1+p+o1wq -u00vR+L4OQbJnC4xGgN49Lw4xiKLMzHwFgQEffl25EvXwOaD7FnMP97/T2u3Z36m -hoEyIwOdyPdfwUpgpZKpsaSgYMN4h7Mi8yrrW6ntBas3D7Hi05V2Y1Z0jFhyGzfl -ZKG+TQyTmAyX9odtsz/ny4Cm7YjHX1BiAuiZdBbQ5rQ58SfLyEDW44YQqSMSkuBp -QWOnryULwMWSyx6Yo1q6xTMPoJcB3X/ge9YGVM+h4k0460tQtcsm9MracEpqoeJ5 -quGnM/b9Sh/22WA= ------END CERTIFICATE----- - # Issuer: CN=Swisscom Root CA 1 O=Swisscom OU=Digital Certificate Services # Subject: CN=Swisscom Root CA 1 O=Swisscom OU=Digital Certificate Services # Label: "Swisscom Root CA 1" @@ -2014,38 +1507,6 @@ rscL9yuwNwXsvFcj4jjSm2jzVhKIT0J8uDHEtdvkyCE06UgRNe76x5JXxZ805Mf2 9w4LTJxoeHtxMcfrHuBnQfO3oKfN5XozNmr6mis= -----END CERTIFICATE----- -# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=(c) 2005 TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. -# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=(c) 2005 TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. -# Label: "TURKTRUST Certificate Services Provider Root 1" -# Serial: 1 -# MD5 Fingerprint: f1:6a:22:18:c9:cd:df:ce:82:1d:1d:b7:78:5c:a9:a5 -# SHA1 Fingerprint: 79:98:a3:08:e1:4d:65:85:e6:c2:1e:15:3a:71:9f:ba:5a:d3:4a:d9 -# SHA256 Fingerprint: 44:04:e3:3b:5e:14:0d:cf:99:80:51:fd:fc:80:28:c7:c8:16:15:c5:ee:73:7b:11:1b:58:82:33:a9:b5:35:a0 ------BEGIN CERTIFICATE----- -MIID+zCCAuOgAwIBAgIBATANBgkqhkiG9w0BAQUFADCBtzE/MD0GA1UEAww2VMOc -UktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sx -c8SxMQswCQYDVQQGDAJUUjEPMA0GA1UEBwwGQU5LQVJBMVYwVAYDVQQKDE0oYykg -MjAwNSBUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8 -dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjAeFw0wNTA1MTMxMDI3MTdaFw0xNTAz -MjIxMDI3MTdaMIG3MT8wPQYDVQQDDDZUw5xSS1RSVVNUIEVsZWt0cm9uaWsgU2Vy -dGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLExCzAJBgNVBAYMAlRSMQ8wDQYD -VQQHDAZBTktBUkExVjBUBgNVBAoMTShjKSAyMDA1IFTDnFJLVFJVU1QgQmlsZ2kg -xLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2ZW5sacSfaSBIaXptZXRsZXJpIEEu -xZ4uMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAylIF1mMD2Bxf3dJ7 -XfIMYGFbazt0K3gNfUW9InTojAPBxhEqPZW8qZSwu5GXyGl8hMW0kWxsE2qkVa2k -heiVfrMArwDCBRj1cJ02i67L5BuBf5OI+2pVu32Fks66WJ/bMsW9Xe8iSi9BB35J -YbOG7E6mQW6EvAPs9TscyB/C7qju6hJKjRTP8wrgUDn5CDX4EVmt5yLqS8oUBt5C -urKZ8y1UiBAG6uEaPj1nH/vO+3yC6BFdSsG5FOpU2WabfIl9BJpiyelSPJ6c79L1 -JuTm5Rh8i27fbMx4W09ysstcP4wFjdFMjK2Sx+F4f2VsSQZQLJ4ywtdKxnWKWU51 -b0dewQIDAQABoxAwDjAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQAV -9VX/N5aAWSGk/KEVTCD21F/aAyT8z5Aa9CEKmu46sWrv7/hg0Uw2ZkUd82YCdAR7 -kjCo3gp2D++Vbr3JN+YaDayJSFvMgzbC9UZcWYJWtNX+I7TYVBxEq8Sn5RTOPEFh -fEPmzcSBCYsk+1Ql1haolgxnB2+zUEfjHCQo3SqYpGH+2+oSN7wBGjSFvW5P55Fy -B0SFHljKVETd96y5y4khctuPwGkplyqjrhgjlxxBKot8KsF8kOipKMDTkcatKIdA -aLX/7KfS0zgYnNN9aV3wxqUeJBujR/xpB2jn5Jq07Q+hh4cCzofSSE7hvP/L8XKS -RGQDJereW26fyfJOrN3H ------END CERTIFICATE----- - # Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. (c) Kasım 2005 # Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. (c) Kasım 2005 # Label: "TURKTRUST Certificate Services Provider Root 2" @@ -2617,152 +2078,6 @@ t0QmwCbAr1UwnjvVNioZBPRcHv/PLLf/0P2HQBHVESO7SMAhqaQoLf0V+LBOK/Qw WyH8EZE0vkHve52Xdf+XlcCWWC/qu0bXu+TZLg== -----END CERTIFICATE----- -# Issuer: CN=AC Raíz Certicámara S.A. O=Sociedad Cameral de Certificación Digital - Certicámara S.A. -# Subject: CN=AC Raíz Certicámara S.A. O=Sociedad Cameral de Certificación Digital - Certicámara S.A. -# Label: "AC Ra\xC3\xADz Certic\xC3\xA1mara S.A." -# Serial: 38908203973182606954752843738508300 -# MD5 Fingerprint: 93:2a:3e:f6:fd:23:69:0d:71:20:d4:2b:47:99:2b:a6 -# SHA1 Fingerprint: cb:a1:c5:f8:b0:e3:5e:b8:b9:45:12:d3:f9:34:a2:e9:06:10:d3:36 -# SHA256 Fingerprint: a6:c5:1e:0d:a5:ca:0a:93:09:d2:e4:c0:e4:0c:2a:f9:10:7a:ae:82:03:85:7f:e1:98:e3:e7:69:e3:43:08:5c ------BEGIN CERTIFICATE----- -MIIGZjCCBE6gAwIBAgIPB35Sk3vgFeNX8GmMy+wMMA0GCSqGSIb3DQEBBQUAMHsx -CzAJBgNVBAYTAkNPMUcwRQYDVQQKDD5Tb2NpZWRhZCBDYW1lcmFsIGRlIENlcnRp -ZmljYWNpw7NuIERpZ2l0YWwgLSBDZXJ0aWPDoW1hcmEgUy5BLjEjMCEGA1UEAwwa -QUMgUmHDrXogQ2VydGljw6FtYXJhIFMuQS4wHhcNMDYxMTI3MjA0NjI5WhcNMzAw -NDAyMjE0MjAyWjB7MQswCQYDVQQGEwJDTzFHMEUGA1UECgw+U29jaWVkYWQgQ2Ft -ZXJhbCBkZSBDZXJ0aWZpY2FjacOzbiBEaWdpdGFsIC0gQ2VydGljw6FtYXJhIFMu -QS4xIzAhBgNVBAMMGkFDIFJhw616IENlcnRpY8OhbWFyYSBTLkEuMIICIjANBgkq -hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAq2uJo1PMSCMI+8PPUZYILrgIem08kBeG -qentLhM0R7LQcNzJPNCNyu5LF6vQhbCnIwTLqKL85XXbQMpiiY9QngE9JlsYhBzL -fDe3fezTf3MZsGqy2IiKLUV0qPezuMDU2s0iiXRNWhU5cxh0T7XrmafBHoi0wpOQ -Y5fzp6cSsgkiBzPZkc0OnB8OIMfuuzONj8LSWKdf/WU34ojC2I+GdV75LaeHM/J4 -Ny+LvB2GNzmxlPLYvEqcgxhaBvzz1NS6jBUJJfD5to0EfhcSM2tXSExP2yYe68yQ -54v5aHxwD6Mq0Do43zeX4lvegGHTgNiRg0JaTASJaBE8rF9ogEHMYELODVoqDA+b -MMCm8Ibbq0nXl21Ii/kDwFJnmxL3wvIumGVC2daa49AZMQyth9VXAnow6IYm+48j -ilSH5L887uvDdUhfHjlvgWJsxS3EF1QZtzeNnDeRyPYL1epjb4OsOMLzP96a++Ej -YfDIJss2yKHzMI+ko6Kh3VOz3vCaMh+DkXkwwakfU5tTohVTP92dsxA7SH2JD/zt -A/X7JWR1DhcZDY8AFmd5ekD8LVkH2ZD6mq093ICK5lw1omdMEWux+IBkAC1vImHF -rEsm5VoQgpukg3s0956JkSCXjrdCx2bD0Omk1vUgjcTDlaxECp1bczwmPS9KvqfJ -pxAe+59QafMCAwEAAaOB5jCB4zAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQE -AwIBBjAdBgNVHQ4EFgQU0QnQ6dfOeXRU+Tows/RtLAMDG2gwgaAGA1UdIASBmDCB -lTCBkgYEVR0gADCBiTArBggrBgEFBQcCARYfaHR0cDovL3d3dy5jZXJ0aWNhbWFy -YS5jb20vZHBjLzBaBggrBgEFBQcCAjBOGkxMaW1pdGFjaW9uZXMgZGUgZ2FyYW50 -7WFzIGRlIGVzdGUgY2VydGlmaWNhZG8gc2UgcHVlZGVuIGVuY29udHJhciBlbiBs -YSBEUEMuMA0GCSqGSIb3DQEBBQUAA4ICAQBclLW4RZFNjmEfAygPU3zmpFmps4p6 -xbD/CHwso3EcIRNnoZUSQDWDg4902zNc8El2CoFS3UnUmjIz75uny3XlesuXEpBc -unvFm9+7OSPI/5jOCk0iAUgHforA1SBClETvv3eiiWdIG0ADBaGJ7M9i4z0ldma/ -Jre7Ir5v/zlXdLp6yQGVwZVR6Kss+LGGIOk/yzVb0hfpKv6DExdA7ohiZVvVO2Dp -ezy4ydV/NgIlqmjCMRW3MGXrfx1IebHPOeJCgBbT9ZMj/EyXyVo3bHwi2ErN0o42 -gzmRkBDI8ck1fj+404HGIGQatlDCIaR43NAvO2STdPCWkPHv+wlaNECW8DYSwaN0 -jJN+Qd53i+yG2dIPPy3RzECiiWZIHiCznCNZc6lEc7wkeZBWN7PGKX6jD/EpOe9+ -XCgycDWs2rjIdWb8m0w5R44bb5tNAlQiM+9hup4phO9OSzNHdpdqy35f/RWmnkJD -W2ZaiogN9xa5P1FlK2Zqi9E4UqLWRhH6/JocdJ6PlwsCT2TG9WjTSy3/pDceiz+/ -RL5hRqGEPQgnTIEgd4kI6mdAXmwIUV80WoyWaM3X94nCHNMyAK9Sy9NgWyo6R35r -MDOhYil/SrnhLecUIw4OGEfhefwVVdCx/CVxY3UzHCMrr1zZ7Ud3YA47Dx7SwNxk -BYn8eNZcLCZDqQ== ------END CERTIFICATE----- - -# Issuer: CN=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA -# Subject: CN=TC TrustCenter Class 2 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 2 CA -# Label: "TC TrustCenter Class 2 CA II" -# Serial: 941389028203453866782103406992443 -# MD5 Fingerprint: ce:78:33:5c:59:78:01:6e:18:ea:b9:36:a0:b9:2e:23 -# SHA1 Fingerprint: ae:50:83:ed:7c:f4:5c:bc:8f:61:c6:21:fe:68:5d:79:42:21:15:6e -# SHA256 Fingerprint: e6:b8:f8:76:64:85:f8:07:ae:7f:8d:ac:16:70:46:1f:07:c0:a1:3e:ef:3a:1f:f7:17:53:8d:7a:ba:d3:91:b4 ------BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIOLmoAAQACH9dSISwRXDswDQYJKoZIhvcNAQEFBQAwdjEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV -BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDIgQ0ExJTAjBgNVBAMTHFRDIFRydXN0 -Q2VudGVyIENsYXNzIDIgQ0EgSUkwHhcNMDYwMTEyMTQzODQzWhcNMjUxMjMxMjI1 -OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i -SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQTElMCMGA1UEAxMc -VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMiBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAKuAh5uO8MN8h9foJIIRszzdQ2Lu+MNF2ujhoF/RKrLqk2jf -tMjWQ+nEdVl//OEd+DFwIxuInie5e/060smp6RQvkL4DUsFJzfb95AhmC1eKokKg -uNV/aVyQMrKXDcpK3EY+AlWJU+MaWss2xgdW94zPEfRMuzBwBJWl9jmM/XOBCH2J -XjIeIqkiRUuwZi4wzJ9l/fzLganx4Duvo4bRierERXlQXa7pIXSSTYtZgo+U4+lK -8edJsBTj9WLL1XK9H7nSn6DNqPoByNkN39r8R52zyFTfSUrxIan+GE7uSNQZu+99 -5OKdy1u2bv/jzVrndIIFuoAlOMvkaZ6vQaoahPUCAwEAAaOCATQwggEwMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTjq1RMgKHbVkO3 -kUrL84J6E1wIqzCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy -dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18yX2NhX0lJLmNybIaBn2xkYXA6 -Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz -JTIwMiUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290 -Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u -TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEAjNfffu4bgBCzg/XbEeprS6iS -GNn3Bzn1LL4GdXpoUxUc6krtXvwjshOg0wn/9vYua0Fxec3ibf2uWWuFHbhOIprt -ZjluS5TmVfwLG4t3wVMTZonZKNaL80VKY7f9ewthXbhtvsPcW3nS7Yblok2+XnR8 -au0WOB9/WIFaGusyiC2y8zl3gK9etmF1KdsjTYjKUCjLhdLTEKJZbtOTVAB6okaV -hgWcqRmY5TFyDADiZ9lA4CQze28suVyrZZ0srHbqNZn1l7kPJOzHdiEoZa5X6AeI -dUpWoNIFOqTmjZKILPPy4cHGYdtBxceb9w4aUUXCYWvcZCcXjFq32nQozZfkvQ== ------END CERTIFICATE----- - -# Issuer: CN=TC TrustCenter Class 3 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 3 CA -# Subject: CN=TC TrustCenter Class 3 CA II O=TC TrustCenter GmbH OU=TC TrustCenter Class 3 CA -# Label: "TC TrustCenter Class 3 CA II" -# Serial: 1506523511417715638772220530020799 -# MD5 Fingerprint: 56:5f:aa:80:61:12:17:f6:67:21:e6:2b:6d:61:56:8e -# SHA1 Fingerprint: 80:25:ef:f4:6e:70:c8:d4:72:24:65:84:fe:40:3b:8a:8d:6a:db:f5 -# SHA256 Fingerprint: 8d:a0:84:fc:f9:9c:e0:77:22:f8:9b:32:05:93:98:06:fa:5c:b8:11:e1:c8:13:f6:a1:08:c7:d3:36:b3:40:8e ------BEGIN CERTIFICATE----- -MIIEqjCCA5KgAwIBAgIOSkcAAQAC5aBd1j8AUb8wDQYJKoZIhvcNAQEFBQAwdjEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxIjAgBgNV -BAsTGVRDIFRydXN0Q2VudGVyIENsYXNzIDMgQ0ExJTAjBgNVBAMTHFRDIFRydXN0 -Q2VudGVyIENsYXNzIDMgQ0EgSUkwHhcNMDYwMTEyMTQ0MTU3WhcNMjUxMjMxMjI1 -OTU5WjB2MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIgR21i -SDEiMCAGA1UECxMZVEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQTElMCMGA1UEAxMc -VEMgVHJ1c3RDZW50ZXIgQ2xhc3MgMyBDQSBJSTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBALTgu1G7OVyLBMVMeRwjhjEQY0NVJz/GRcekPewJDRoeIMJW -Ht4bNwcwIi9v8Qbxq63WyKthoy9DxLCyLfzDlml7forkzMA5EpBCYMnMNWju2l+Q -Vl/NHE1bWEnrDgFPZPosPIlY2C8u4rBo6SI7dYnWRBpl8huXJh0obazovVkdKyT2 -1oQDZogkAHhg8fir/gKya/si+zXmFtGt9i4S5Po1auUZuV3bOx4a+9P/FRQI2Alq -ukWdFHlgfa9Aigdzs5OW03Q0jTo3Kd5c7PXuLjHCINy+8U9/I1LZW+Jk2ZyqBwi1 -Rb3R0DHBq1SfqdLDYmAD8bs5SpJKPQq5ncWg/jcCAwEAAaOCATQwggEwMA8GA1Ud -EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBTUovyfs8PYA9NX -XAek0CSnwPIA1DCB7QYDVR0fBIHlMIHiMIHfoIHcoIHZhjVodHRwOi8vd3d3LnRy -dXN0Y2VudGVyLmRlL2NybC92Mi90Y19jbGFzc18zX2NhX0lJLmNybIaBn2xkYXA6 -Ly93d3cudHJ1c3RjZW50ZXIuZGUvQ049VEMlMjBUcnVzdENlbnRlciUyMENsYXNz -JTIwMyUyMENBJTIwSUksTz1UQyUyMFRydXN0Q2VudGVyJTIwR21iSCxPVT1yb290 -Y2VydHMsREM9dHJ1c3RjZW50ZXIsREM9ZGU/Y2VydGlmaWNhdGVSZXZvY2F0aW9u -TGlzdD9iYXNlPzANBgkqhkiG9w0BAQUFAAOCAQEANmDkcPcGIEPZIxpC8vijsrlN -irTzwppVMXzEO2eatN9NDoqTSheLG43KieHPOh6sHfGcMrSOWXaiQYUlN6AT0PV8 -TtXqluJucsG7Kv5sbviRmEb8yRtXW+rIGjs/sFGYPAfaLFkB2otE6OF0/ado3VS6 -g0bsyEa1+K+XwDsJHI/OcpY9M1ZwvJbL2NV9IJqDnxrcOfHFcqMRA/07QlIp2+gB -95tejNaNhk4Z+rwcvsUhpYeeeC422wlxo3I0+GzjBgnyXlal092Y+tTmBvTwtiBj -S+opvaqCZh77gaqnN60TGOaSw4HBM7uIHqHn4rS9MWwOUT1v+5ZWgOI2F9Hc5A== ------END CERTIFICATE----- - -# Issuer: CN=TC TrustCenter Universal CA I O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA -# Subject: CN=TC TrustCenter Universal CA I O=TC TrustCenter GmbH OU=TC TrustCenter Universal CA -# Label: "TC TrustCenter Universal CA I" -# Serial: 601024842042189035295619584734726 -# MD5 Fingerprint: 45:e1:a5:72:c5:a9:36:64:40:9e:f5:e4:58:84:67:8c -# SHA1 Fingerprint: 6b:2f:34:ad:89:58:be:62:fd:b0:6b:5c:ce:bb:9d:d9:4f:4e:39:f3 -# SHA256 Fingerprint: eb:f3:c0:2a:87:89:b1:fb:7d:51:19:95:d6:63:b7:29:06:d9:13:ce:0d:5e:10:56:8a:8a:77:e2:58:61:67:e7 ------BEGIN CERTIFICATE----- -MIID3TCCAsWgAwIBAgIOHaIAAQAC7LdggHiNtgYwDQYJKoZIhvcNAQEFBQAweTEL -MAkGA1UEBhMCREUxHDAaBgNVBAoTE1RDIFRydXN0Q2VudGVyIEdtYkgxJDAiBgNV -BAsTG1RDIFRydXN0Q2VudGVyIFVuaXZlcnNhbCBDQTEmMCQGA1UEAxMdVEMgVHJ1 -c3RDZW50ZXIgVW5pdmVyc2FsIENBIEkwHhcNMDYwMzIyMTU1NDI4WhcNMjUxMjMx -MjI1OTU5WjB5MQswCQYDVQQGEwJERTEcMBoGA1UEChMTVEMgVHJ1c3RDZW50ZXIg -R21iSDEkMCIGA1UECxMbVEMgVHJ1c3RDZW50ZXIgVW5pdmVyc2FsIENBMSYwJAYD -VQQDEx1UQyBUcnVzdENlbnRlciBVbml2ZXJzYWwgQ0EgSTCCASIwDQYJKoZIhvcN -AQEBBQADggEPADCCAQoCggEBAKR3I5ZEr5D0MacQ9CaHnPM42Q9e3s9B6DGtxnSR -JJZ4Hgmgm5qVSkr1YnwCqMqs+1oEdjneX/H5s7/zA1hV0qq34wQi0fiU2iIIAI3T -fCZdzHd55yx4Oagmcw6iXSVphU9VDprvxrlE4Vc93x9UIuVvZaozhDrzznq+VZeu -jRIPFDPiUHDDSYcTvFHe15gSWu86gzOSBnWLknwSaHtwag+1m7Z3W0hZneTvWq3z -wZ7U10VOylY0Ibw+F1tvdwxIAUMpsN0/lm7mlaoMwCC2/T42J5zjXM9OgdwZu5GQ -fezmlwQek8wiSdeXhrYTCjxDI3d+8NzmzSQfO4ObNDqDNOMCAwEAAaNjMGEwHwYD -VR0jBBgwFoAUkqR1LKSevoFE63n8isWVpesQdXMwDwYDVR0TAQH/BAUwAwEB/zAO -BgNVHQ8BAf8EBAMCAYYwHQYDVR0OBBYEFJKkdSyknr6BROt5/IrFlaXrEHVzMA0G -CSqGSIb3DQEBBQUAA4IBAQAo0uCG1eb4e/CX3CJrO5UUVg8RMKWaTzqwOuAGy2X1 -7caXJ/4l8lfmXpWMPmRgFVp/Lw0BxbFg/UU1z/CyvwbZ71q+s2IhtNerNXxTPqYn -8aEt2hojnczd7Dwtnic0XQ/CNnm8yUpiLe1r2X1BQ3y2qsrtYbE3ghUJGooWMNjs -ydZHcnhLEEYUjl8Or+zHL6sQ17bxbuyGssLoDZJz3KL0Dzq/YSMQiZxIQG5wALPT -ujdEWBF6AmqI8Dc08BnprNRlc/ZpjGSUOnmFKbAWKwyCPwacx/0QK54PLLae4xW/ -2TYcuiUaUj0a7CIMHOCkoj3w6DnPgcB77V0fb8XQC9eY ------END CERTIFICATE----- - # Issuer: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center # Subject: CN=Deutsche Telekom Root CA 2 O=Deutsche Telekom AG OU=T-TeleSec Trust Center # Label: "Deutsche Telekom Root CA 2" @@ -2793,36 +2108,6 @@ xbrYNuSD7Odlt79jWvNGr4GUN9RBjNYj1h7P9WgbRGOiWrqnNVmh5XAFmw4jV5mU Cm26OWMohpLzGITY+9HPBVZkVw== -----END CERTIFICATE----- -# Issuer: CN=ComSign Secured CA O=ComSign -# Subject: CN=ComSign Secured CA O=ComSign -# Label: "ComSign Secured CA" -# Serial: 264725503855295744117309814499492384489 -# MD5 Fingerprint: 40:01:25:06:8d:21:43:6a:0e:43:00:9c:e7:43:f3:d5 -# SHA1 Fingerprint: f9:cd:0e:2c:da:76:24:c1:8f:bd:f0:f0:ab:b6:45:b8:f7:fe:d5:7a -# SHA256 Fingerprint: 50:79:41:c7:44:60:a0:b4:70:86:22:0d:4e:99:32:57:2a:b5:d1:b5:bb:cb:89:80:ab:1c:b1:76:51:a8:44:d2 ------BEGIN CERTIFICATE----- -MIIDqzCCApOgAwIBAgIRAMcoRwmzuGxFjB36JPU2TukwDQYJKoZIhvcNAQEFBQAw -PDEbMBkGA1UEAxMSQ29tU2lnbiBTZWN1cmVkIENBMRAwDgYDVQQKEwdDb21TaWdu -MQswCQYDVQQGEwJJTDAeFw0wNDAzMjQxMTM3MjBaFw0yOTAzMTYxNTA0NTZaMDwx -GzAZBgNVBAMTEkNvbVNpZ24gU2VjdXJlZCBDQTEQMA4GA1UEChMHQ29tU2lnbjEL -MAkGA1UEBhMCSUwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDGtWhf -HZQVw6QIVS3joFd67+l0Kru5fFdJGhFeTymHDEjWaueP1H5XJLkGieQcPOqs49oh -gHMhCu95mGwfCP+hUH3ymBvJVG8+pSjsIQQPRbsHPaHA+iqYHU4Gk/v1iDurX8sW -v+bznkqH7Rnqwp9D5PGBpX8QTz7RSmKtUxvLg/8HZaWSLWapW7ha9B20IZFKF3ue -Mv5WJDmyVIRD9YTC2LxBkMyd1mja6YJQqTtoz7VdApRgFrFD2UNd3V2Hbuq7s8lr -9gOUCXDeFhF6K+h2j0kQmHe5Y1yLM5d19guMsqtb3nQgJT/j8xH5h2iGNXHDHYwt -6+UarA9z1YJZQIDTAgMBAAGjgacwgaQwDAYDVR0TBAUwAwEB/zBEBgNVHR8EPTA7 -MDmgN6A1hjNodHRwOi8vZmVkaXIuY29tc2lnbi5jby5pbC9jcmwvQ29tU2lnblNl -Y3VyZWRDQS5jcmwwDgYDVR0PAQH/BAQDAgGGMB8GA1UdIwQYMBaAFMFL7XC29z58 -ADsAj8c+DkWfHl3sMB0GA1UdDgQWBBTBS+1wtvc+fAA7AI/HPg5Fnx5d7DANBgkq -hkiG9w0BAQUFAAOCAQEAFs/ukhNQq3sUnjO2QiBq1BW9Cav8cujvR3qQrFHBZE7p -iL1DRYHjZiM/EoZNGeQFsOY3wo3aBijJD4mkU6l1P7CW+6tMM1X5eCZGbxs2mPtC -dsGCuY7e+0X5YxtiOzkGynd6qDwJz2w2PQ8KRUtpFhpFfTMDZflScZAmlaxMDPWL -kz/MdXSFmLr/YnpNH4n+rr2UAJm/EaXc4HnFFgt9AmEd6oX5AhVP51qJThRv4zdL -hfXBPGHg/QVBspJ/wx2g0K5SZGBrGMYmnNj1ZOQ2GmKfig8+/21OGVZOIJFsnzQz -OjRXUDpvgV4GxvU+fE6OK85lBi5d0ipTdF7Tbieejw== ------END CERTIFICATE----- - # Issuer: CN=Cybertrust Global Root O=Cybertrust, Inc # Subject: CN=Cybertrust Global Root O=Cybertrust, Inc # Label: "Cybertrust Global Root" @@ -2960,34 +2245,6 @@ h7U/2k3ZIQAw3pDaDtMaSKk+hQsUi4y8QZ5q9w5wwDX3OaJdZtB7WZ+oRxKaJyOk LY4ng5IgodcVf/EuGO70SH8vf/GhGLWhC5SgYiAynB321O+/TIho -----END CERTIFICATE----- -# Issuer: CN=Buypass Class 3 CA 1 O=Buypass AS-983163327 -# Subject: CN=Buypass Class 3 CA 1 O=Buypass AS-983163327 -# Label: "Buypass Class 3 CA 1" -# Serial: 2 -# MD5 Fingerprint: df:3c:73:59:81:e7:39:50:81:04:4c:34:a2:cb:b3:7b -# SHA1 Fingerprint: 61:57:3a:11:df:0e:d8:7e:d5:92:65:22:ea:d0:56:d7:44:b3:23:71 -# SHA256 Fingerprint: b7:b1:2b:17:1f:82:1d:aa:99:0c:d0:fe:50:87:b1:28:44:8b:a8:e5:18:4f:84:c5:1e:02:b5:c8:fb:96:2b:24 ------BEGIN CERTIFICATE----- -MIIDUzCCAjugAwIBAgIBAjANBgkqhkiG9w0BAQUFADBLMQswCQYDVQQGEwJOTzEd -MBsGA1UECgwUQnV5cGFzcyBBUy05ODMxNjMzMjcxHTAbBgNVBAMMFEJ1eXBhc3Mg -Q2xhc3MgMyBDQSAxMB4XDTA1MDUwOTE0MTMwM1oXDTE1MDUwOTE0MTMwM1owSzEL -MAkGA1UEBhMCTk8xHTAbBgNVBAoMFEJ1eXBhc3MgQVMtOTgzMTYzMzI3MR0wGwYD -VQQDDBRCdXlwYXNzIENsYXNzIDMgQ0EgMTCCASIwDQYJKoZIhvcNAQEBBQADggEP -ADCCAQoCggEBAKSO13TZKWTeXx+HgJHqTjnmGcZEC4DVC69TB4sSveZn8AKxifZg -isRbsELRwCGoy+Gb72RRtqfPFfV0gGgEkKBYouZ0plNTVUhjP5JW3SROjvi6K//z -NIqeKNc0n6wv1g/xpC+9UrJJhW05NfBEMJNGJPO251P7vGGvqaMU+8IXF4Rs4HyI -+MkcVyzwPX6UvCWThOiaAJpFBUJXgPROztmuOfbIUxAMZTpHe2DC1vqRycZxbL2R -hzyRhkmr8w+gbCZ2Xhysm3HljbybIR6c1jh+JIAVMYKWsUnTYjdbiAwKYjT+p0h+ -mbEwi5A3lRyoH6UsjfRVyNvdWQrCrXig9IsCAwEAAaNCMEAwDwYDVR0TAQH/BAUw -AwEB/zAdBgNVHQ4EFgQUOBTmyPCppAP0Tj4io1vy1uCtQHQwDgYDVR0PAQH/BAQD -AgEGMA0GCSqGSIb3DQEBBQUAA4IBAQABZ6OMySU9E2NdFm/soT4JXJEVKirZgCFP -Bdy7pYmrEzMqnji3jG8CcmPHc3ceCQa6Oyh7pEfJYWsICCD8igWKH7y6xsL+z27s -EzNxZy5p+qksP2bAEllNC1QCkoS72xLvg3BweMhT+t/Gxv/ciC8HwEmdMldg0/L2 -mSlf56oBzKwzqBwKu5HEA6BvtjT5htOzdlSY9EqBs1OdTUDs5XcTRa9bqh/YL0yC -e/4qxFi7T/ye/QNlGioOw6UgFpRreaaiErS7GqQjel/wroQk5PMr+4okoyeYZdow -dXb8GZHo2+ubPzK/QJcHJrrM85SFSnonk8+QQtS4Wxam58tAA915 ------END CERTIFICATE----- - # Issuer: CN=EBG Elektronik Sertifika Hizmet Sağlayıcısı O=EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. # Subject: CN=EBG Elektronik Sertifika Hizmet Sağlayıcısı O=EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. # Label: "EBG Elektronik Sertifika Hizmet Sa\xC4\x9Flay\xc4\xb1\x63\xc4\xb1s\xc4\xb1" @@ -3535,28 +2792,6 @@ r0CodaxWkHS4oJyleW/c6RrIaQXpuvoDs3zk4E7Czp3otkYNbn5XOmeUwssfnHdK Z05phkOTOPu220+DkdRgfks+KzgHVZhepA== -----END CERTIFICATE----- -# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority -# Label: "Verisign Class 3 Public Primary Certification Authority" -# Serial: 80507572722862485515306429940691309246 -# MD5 Fingerprint: ef:5a:f1:33:ef:f1:cd:bb:51:02:ee:12:14:4b:96:c4 -# SHA1 Fingerprint: a1:db:63:93:91:6f:17:e4:18:55:09:40:04:15:c7:02:40:b0:ae:6b -# SHA256 Fingerprint: a4:b6:b3:99:6f:c2:f3:06:b3:fd:86:81:bd:63:41:3d:8c:50:09:cc:4f:a3:29:c2:cc:f0:e2:fa:1b:14:03:05 ------BEGIN CERTIFICATE----- -MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG -A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz -cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 -MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV -BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt -YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN -ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE -BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is -I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G -CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i -2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ -2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ ------END CERTIFICATE----- - # Issuer: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. # Subject: CN=Microsec e-Szigno Root CA 2009 O=Microsec Ltd. # Label: "Microsec e-Szigno Root CA 2009" @@ -3589,36 +2824,6 @@ tyERzAMBVnCnEJIeGzSBHq2cGsMEPO0CYdYeBvNfOofyK/FFh+U9rNHHV4S9a67c HMN1Rq41Bab2XD0h7lbwyYIiLXpUq3DDfSJlgnCW -----END CERTIFICATE----- -# Issuer: CN=e-Guven Kok Elektronik Sertifika Hizmet Saglayicisi O=Elektronik Bilgi Guvenligi A.S. -# Subject: CN=e-Guven Kok Elektronik Sertifika Hizmet Saglayicisi O=Elektronik Bilgi Guvenligi A.S. -# Label: "E-Guven Kok Elektronik Sertifika Hizmet Saglayicisi" -# Serial: 91184789765598910059173000485363494069 -# MD5 Fingerprint: 3d:41:29:cb:1e:aa:11:74:cd:5d:b0:62:af:b0:43:5b -# SHA1 Fingerprint: dd:e1:d2:a9:01:80:2e:1d:87:5e:84:b3:80:7e:4b:b1:fd:99:41:34 -# SHA256 Fingerprint: e6:09:07:84:65:a4:19:78:0c:b6:ac:4c:1c:0b:fb:46:53:d9:d9:cc:6e:b3:94:6e:b7:f3:d6:99:97:ba:d5:98 ------BEGIN CERTIFICATE----- -MIIDtjCCAp6gAwIBAgIQRJmNPMADJ72cdpW56tustTANBgkqhkiG9w0BAQUFADB1 -MQswCQYDVQQGEwJUUjEoMCYGA1UEChMfRWxla3Ryb25payBCaWxnaSBHdXZlbmxp -Z2kgQS5TLjE8MDoGA1UEAxMzZS1HdXZlbiBLb2sgRWxla3Ryb25payBTZXJ0aWZp -a2EgSGl6bWV0IFNhZ2xheWljaXNpMB4XDTA3MDEwNDExMzI0OFoXDTE3MDEwNDEx -MzI0OFowdTELMAkGA1UEBhMCVFIxKDAmBgNVBAoTH0VsZWt0cm9uaWsgQmlsZ2kg -R3V2ZW5saWdpIEEuUy4xPDA6BgNVBAMTM2UtR3V2ZW4gS29rIEVsZWt0cm9uaWsg -U2VydGlmaWthIEhpem1ldCBTYWdsYXlpY2lzaTCCASIwDQYJKoZIhvcNAQEBBQAD -ggEPADCCAQoCggEBAMMSIJ6wXgBljU5Gu4Bc6SwGl9XzcslwuedLZYDBS75+PNdU -MZTe1RK6UxYC6lhj71vY8+0qGqpxSKPcEC1fX+tcS5yWCEIlKBHMilpiAVDV6wlT -L/jDj/6z/P2douNffb7tC+Bg62nsM+3YjfsSSYMAyYuXjDtzKjKzEve5TfL0TW3H -5tYmNwjy2f1rXKPlSFxYvEK+A1qBuhw1DADT9SN+cTAIJjjcJRFHLfO6IxClv7wC -90Nex/6wN1CZew+TzuZDLMN+DfIcQ2Zgy2ExR4ejT669VmxMvLz4Bcpk9Ok0oSy1 -c+HCPujIyTQlCFzz7abHlJ+tiEMl1+E5YP6sOVkCAwEAAaNCMEAwDgYDVR0PAQH/ -BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFJ/uRLOU1fqRTy7ZVZoE -VtstxNulMA0GCSqGSIb3DQEBBQUAA4IBAQB/X7lTW2M9dTLn+sR0GstG30ZpHFLP -qk/CaOv/gKlR6D1id4k9CnU58W5dF4dvaAXBlGzZXd/aslnLpRCKysw5zZ/rTt5S -/wzw9JKp8mxTq5vSR6AfdPebmvEvFZ96ZDAYBzwqD2fK/A+JYZ1lpTzlvBNbCNvj -/+27BrtqBrF6T2XGgv0enIu1De5Iu7i9qgi0+6N8y5/NkHZchpZ4Vwpm+Vganf2X -KWDeEaaQHBkc7gGWIjQ0LpH5t8Qn0Xvmv/uARFoW5evg1Ao4vOSR49XrXMGs3xtq -fJ7lddK2l4fbzIcrQzqECK+rPNv3PGYxhrCdU3nt+CPeQuMtgvEP5fqX ------END CERTIFICATE----- - # Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 # Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign Root CA - R3 # Label: "GlobalSign Root CA - R3" @@ -5024,3 +4229,1388 @@ wa19hAM8EHiGG3njxPPyBJUgriOCxLM6AGK/5jYk4Ve6xx6QddVfP5VhK8E7zeWz aGHQRiapIVJpLesux+t3zqY6tQMzT3bR51xUAV3LePTJDL/PEo4XLSNolOer/qmy KwbQBM0= -----END CERTIFICATE----- + +# Issuer: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Subject: CN=TeliaSonera Root CA v1 O=TeliaSonera +# Label: "TeliaSonera Root CA v1" +# Serial: 199041966741090107964904287217786801558 +# MD5 Fingerprint: 37:41:49:1b:18:56:9a:26:f5:ad:c2:66:fb:40:a5:4c +# SHA1 Fingerprint: 43:13:bb:96:f1:d5:86:9b:c1:4e:6a:92:f6:cf:f6:34:69:87:82:37 +# SHA256 Fingerprint: dd:69:36:fe:21:f8:f0:77:c1:23:a1:a5:21:c1:22:24:f7:22:55:b7:3e:03:a7:26:06:93:e8:a2:4b:0f:a3:89 +-----BEGIN CERTIFICATE----- +MIIFODCCAyCgAwIBAgIRAJW+FqD3LkbxezmCcvqLzZYwDQYJKoZIhvcNAQEFBQAw +NzEUMBIGA1UECgwLVGVsaWFTb25lcmExHzAdBgNVBAMMFlRlbGlhU29uZXJhIFJv +b3QgQ0EgdjEwHhcNMDcxMDE4MTIwMDUwWhcNMzIxMDE4MTIwMDUwWjA3MRQwEgYD +VQQKDAtUZWxpYVNvbmVyYTEfMB0GA1UEAwwWVGVsaWFTb25lcmEgUm9vdCBDQSB2 +MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAMK+6yfwIaPzaSZVfp3F +VRaRXP3vIb9TgHot0pGMYzHw7CTww6XScnwQbfQ3t+XmfHnqjLWCi65ItqwA3GV1 +7CpNX8GH9SBlK4GoRz6JI5UwFpB/6FcHSOcZrr9FZ7E3GwYq/t75rH2D+1665I+X +Z75Ljo1kB1c4VWk0Nj0TSO9P4tNmHqTPGrdeNjPUtAa9GAH9d4RQAEX1jF3oI7x+ +/jXh7VB7qTCNGdMJjmhnXb88lxhTuylixcpecsHHltTbLaC0H2kD7OriUPEMPPCs +81Mt8Bz17Ww5OXOAFshSsCPN4D7c3TxHoLs1iuKYaIu+5b9y7tL6pe0S7fyYGKkm +dtwoSxAgHNN/Fnct7W+A90m7UwW7XWjH1Mh1Fj+JWov3F0fUTPHSiXk+TT2YqGHe +Oh7S+F4D4MHJHIzTjU3TlTazN19jY5szFPAtJmtTfImMMsJu7D0hADnJoWjiUIMu +sDor8zagrC/kb2HCUQk5PotTubtn2txTuXZZNp1D5SDgPTJghSJRt8czu90VL6R4 +pgd7gUY2BIbdeTXHlSw7sKMXNeVzH7RcWe/a6hBle3rQf5+ztCo3O3CLm1u5K7fs +slESl1MpWtTwEhDcTwK7EpIvYtQ/aUN8Ddb8WHUBiJ1YFkveupD/RwGJBmr2X7KQ +arMCpgKIv7NHfirZ1fpoeDVNAgMBAAGjPzA9MA8GA1UdEwEB/wQFMAMBAf8wCwYD +VR0PBAQDAgEGMB0GA1UdDgQWBBTwj1k4ALP1j5qWDNXr+nuqF+gTEjANBgkqhkiG +9w0BAQUFAAOCAgEAvuRcYk4k9AwI//DTDGjkk0kiP0Qnb7tt3oNmzqjMDfz1mgbl +dxSR651Be5kqhOX//CHBXfDkH1e3damhXwIm/9fH907eT/j3HEbAek9ALCI18Bmx +0GtnLLCo4MBANzX2hFxc469CeP6nyQ1Q6g2EdvZR74NTxnr/DlZJLo961gzmJ1Tj +TQpgcmLNkQfWpb/ImWvtxBnmq0wROMVvMeJuScg/doAmAyYp4Db29iBT4xdwNBed +Y2gea+zDTYa4EzAvXUYNR0PVG6pZDrlcjQZIrXSHX8f8MVRBE+LHIQ6e4B4N4cB7 +Q4WQxYpYxmUKeFfyxiMPAdkgS94P+5KFdSpcc41teyWRyu5FrgZLAMzTsVlQ2jqI +OylDRl6XK1TOU2+NSueW+r9xDkKLfP0ooNBIytrEgUy7onOTJsjrDNYmiLbAJM+7 +vVvrdX3pCI6GMyx5dwlppYn8s3CQh3aP0yK7Qs69cwsgJirQmz1wHiRszYd2qReW +t88NkvuOGKmYSdGe/mBEciG5Ge3C9THxOUiIkCR1VBatzvT4aRRkOfujuLpwQMcn +HL/EVlP6Y2XQ8xwOFvVrhlhNGNTkDY6lnVuR3HYkUD/GKvvZt5y11ubQ2egZixVx +SK236thZiNSQvxaz2emsWWFUyBy6ysHK4bkgTI86k4mloMy/0/Z1pHWWbVY= +-----END CERTIFICATE----- + +# Issuer: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi +# Subject: CN=E-Tugra Certification Authority O=E-Tuğra EBG Bilişim Teknolojileri ve Hizmetleri A.Ş. OU=E-Tugra Sertifikasyon Merkezi +# Label: "E-Tugra Certification Authority" +# Serial: 7667447206703254355 +# MD5 Fingerprint: b8:a1:03:63:b0:bd:21:71:70:8a:6f:13:3a:bb:79:49 +# SHA1 Fingerprint: 51:c6:e7:08:49:06:6e:f3:92:d4:5c:a0:0d:6d:a3:62:8f:c3:52:39 +# SHA256 Fingerprint: b0:bf:d5:2b:b0:d7:d9:bd:92:bf:5d:4d:c1:3d:a2:55:c0:2c:54:2f:37:83:65:ea:89:39:11:f5:5e:55:f2:3c +-----BEGIN CERTIFICATE----- +MIIGSzCCBDOgAwIBAgIIamg+nFGby1MwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNV +BAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+BgNVBAoMN0UtVHXEn3JhIEVCRyBC +aWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhpem1ldGxlcmkgQS7Fni4xJjAkBgNV +BAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBNZXJrZXppMSgwJgYDVQQDDB9FLVR1 +Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTEzMDMwNTEyMDk0OFoXDTIz +MDMwMzEyMDk0OFowgbIxCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExQDA+ +BgNVBAoMN0UtVHXEn3JhIEVCRyBCaWxpxZ9pbSBUZWtub2xvamlsZXJpIHZlIEhp +em1ldGxlcmkgQS7Fni4xJjAkBgNVBAsMHUUtVHVncmEgU2VydGlmaWthc3lvbiBN +ZXJrZXppMSgwJgYDVQQDDB9FLVR1Z3JhIENlcnRpZmljYXRpb24gQXV0aG9yaXR5 +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA4vU/kwVRHoViVF56C/UY +B4Oufq9899SKa6VjQzm5S/fDxmSJPZQuVIBSOTkHS0vdhQd2h8y/L5VMzH2nPbxH +D5hw+IyFHnSOkm0bQNGZDbt1bsipa5rAhDGvykPL6ys06I+XawGb1Q5KCKpbknSF +Q9OArqGIW66z6l7LFpp3RMih9lRozt6Plyu6W0ACDGQXwLWTzeHxE2bODHnv0ZEo +q1+gElIwcxmOj+GMB6LDu0rw6h8VqO4lzKRG+Bsi77MOQ7osJLjFLFzUHPhdZL3D +k14opz8n8Y4e0ypQBaNV2cvnOVPAmJ6MVGKLJrD3fY185MaeZkJVgkfnsliNZvcH +fC425lAcP9tDJMW/hkd5s3kc91r0E+xs+D/iWR+V7kI+ua2oMoVJl0b+SzGPWsut +dEcf6ZG33ygEIqDUD13ieU/qbIWGvaimzuT6w+Gzrt48Ue7LE3wBf4QOXVGUnhMM +ti6lTPk5cDZvlsouDERVxcr6XQKj39ZkjFqzAQqptQpHF//vkUAqjqFGOjGY5RH8 +zLtJVor8udBhmm9lbObDyz51Sf6Pp+KJxWfXnUYTTjF2OySznhFlhqt/7x3U+Lzn +rFpct1pHXFXOVbQicVtbC/DP3KBhZOqp12gKY6fgDT+gr9Oq0n7vUaDmUStVkhUX +U8u3Zg5mTPj5dUyQ5xJwx0UCAwEAAaNjMGEwHQYDVR0OBBYEFC7j27JJ0JxUeVz6 +Jyr+zE7S6E5UMA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAULuPbsknQnFR5 +XPonKv7MTtLoTlQwDgYDVR0PAQH/BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4ICAQAF +Nzr0TbdF4kV1JI+2d1LoHNgQk2Xz8lkGpD4eKexd0dCrfOAKkEh47U6YA5n+KGCR +HTAduGN8qOY1tfrTYXbm1gdLymmasoR6d5NFFxWfJNCYExL/u6Au/U5Mh/jOXKqY +GwXgAEZKgoClM4so3O0409/lPun++1ndYYRP0lSWE2ETPo+Aab6TR7U1Q9Jauz1c +77NCR807VRMGsAnb/WP2OogKmW9+4c4bU2pEZiNRCHu8W1Ki/QY3OEBhj0qWuJA3 ++GbHeJAAFS6LrVE1Uweoa2iu+U48BybNCAVwzDk/dr2l02cmAYamU9JgO3xDf1WK +vJUawSg5TB9D0pH0clmKuVb8P7Sd2nCcdlqMQ1DujjByTd//SffGqWfZbawCEeI6 +FiWnWAjLb1NBnEg4R2gz0dfHj9R0IdTDBZB6/86WiLEVKV0jq9BgoRJP3vQXzTLl +yb/IQ639Lo7xr+L0mPoSHyDYwKcMhcWQ9DstliaxLL5Mq+ux0orJ23gTDx4JnW2P +AJ8C2sH6H3p6CcRK5ogql5+Ji/03X186zjhZhkuvcQu02PJwT58yE+Owp1fl2tpD +y4Q08ijE6m30Ku/Ba3ba+367hTzSU8JNvnHhRdH9I2cNE3X7z2VnIp2usAnRCf8d +NL/+I5c30jn6PQ0GC7TbO6Orb1wdtn7os4I07QZcJA== +-----END CERTIFICATE----- + +# Issuer: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Subject: CN=T-TeleSec GlobalRoot Class 2 O=T-Systems Enterprise Services GmbH OU=T-Systems Trust Center +# Label: "T-TeleSec GlobalRoot Class 2" +# Serial: 1 +# MD5 Fingerprint: 2b:9b:9e:e4:7b:6c:1f:00:72:1a:cc:c1:77:79:df:6a +# SHA1 Fingerprint: 59:0d:2d:7d:88:4f:40:2e:61:7e:a5:62:32:17:65:cf:17:d8:94:e9 +# SHA256 Fingerprint: 91:e2:f5:78:8d:58:10:eb:a7:ba:58:73:7d:e1:54:8a:8e:ca:cd:01:45:98:bc:0b:14:3e:04:1b:17:05:25:52 +-----BEGIN CERTIFICATE----- +MIIDwzCCAqugAwIBAgIBATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMCREUx +KzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnByaXNlIFNlcnZpY2VzIEdtYkgxHzAd +BgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50ZXIxJTAjBgNVBAMMHFQtVGVsZVNl +YyBHbG9iYWxSb290IENsYXNzIDIwHhcNMDgxMDAxMTA0MDE0WhcNMzMxMDAxMjM1 +OTU5WjCBgjELMAkGA1UEBhMCREUxKzApBgNVBAoMIlQtU3lzdGVtcyBFbnRlcnBy +aXNlIFNlcnZpY2VzIEdtYkgxHzAdBgNVBAsMFlQtU3lzdGVtcyBUcnVzdCBDZW50 +ZXIxJTAjBgNVBAMMHFQtVGVsZVNlYyBHbG9iYWxSb290IENsYXNzIDIwggEiMA0G +CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCqX9obX+hzkeXaXPSi5kfl82hVYAUd +AqSzm1nzHoqvNK38DcLZSBnuaY/JIPwhqgcZ7bBcrGXHX+0CfHt8LRvWurmAwhiC +FoT6ZrAIxlQjgeTNuUk/9k9uN0goOA/FvudocP05l03Sx5iRUKrERLMjfTlH6VJi +1hKTXrcxlkIF+3anHqP1wvzpesVsqXFP6st4vGCvx9702cu+fjOlbpSD8DT6Iavq +jnKgP6TeMFvvhk1qlVtDRKgQFRzlAVfFmPHmBiiRqiDFt1MmUUOyCxGVWOHAD3bZ +wI18gfNycJ5v/hqO2V81xrJvNHy+SE/iWjnX2J14np+GPgNeGYtEotXHAgMBAAGj +QjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBS/ +WSA2AHmgoCJrjNXyYdK4LMuCSjANBgkqhkiG9w0BAQsFAAOCAQEAMQOiYQsfdOhy +NsZt+U2e+iKo4YFWz827n+qrkRk4r6p8FU3ztqONpfSO9kSpp+ghla0+AGIWiPAC +uvxhI+YzmzB6azZie60EI4RYZeLbK4rnJVM3YlNfvNoBYimipidx5joifsFvHZVw +IEoHNN/q/xWA5brXethbdXwFeilHfkCoMRN3zUA7tFFHei4R40cR3p1m0IvVVGb6 +g1XqfMIpiRvpb7PO4gWEyS8+eIVibslfwXhjdFjASBgMmTnrpMwatXlajRWc2BQN +9noHV8cigwUtPJslJj0Ys6lDfMjIq2SPDqO/nBudMNva0Bkuqjzx+zOAduTNrRlP +BSeOE6Fuwg== +-----END CERTIFICATE----- + +# Issuer: CN=Atos TrustedRoot 2011 O=Atos +# Subject: CN=Atos TrustedRoot 2011 O=Atos +# Label: "Atos TrustedRoot 2011" +# Serial: 6643877497813316402 +# MD5 Fingerprint: ae:b9:c4:32:4b:ac:7f:5d:66:cc:77:94:bb:2a:77:56 +# SHA1 Fingerprint: 2b:b1:f5:3e:55:0c:1d:c5:f1:d4:e6:b7:6a:46:4b:55:06:02:ac:21 +# SHA256 Fingerprint: f3:56:be:a2:44:b7:a9:1e:b3:5d:53:ca:9a:d7:86:4a:ce:01:8e:2d:35:d5:f8:f9:6d:df:68:a6:f4:1a:a4:74 +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIIXDPLYixfszIwDQYJKoZIhvcNAQELBQAwPDEeMBwGA1UE +AwwVQXRvcyBUcnVzdGVkUm9vdCAyMDExMQ0wCwYDVQQKDARBdG9zMQswCQYDVQQG +EwJERTAeFw0xMTA3MDcxNDU4MzBaFw0zMDEyMzEyMzU5NTlaMDwxHjAcBgNVBAMM +FUF0b3MgVHJ1c3RlZFJvb3QgMjAxMTENMAsGA1UECgwEQXRvczELMAkGA1UEBhMC +REUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCVhTuXbyo7LjvPpvMp +Nb7PGKw+qtn4TaA+Gke5vJrf8v7MPkfoepbCJI419KkM/IL9bcFyYie96mvr54rM +VD6QUM+A1JX76LWC1BTFtqlVJVfbsVD2sGBkWXppzwO3bw2+yj5vdHLqqjAqc2K+ +SZFhyBH+DgMq92og3AIVDV4VavzjgsG1xZ1kCWyjWZgHJ8cblithdHFsQ/H3NYkQ +4J7sVaE3IqKHBAUsR320HLliKWYoyrfhk/WklAOZuXCFteZI6o1Q/NnezG8HDt0L +cp2AMBYHlT8oDv3FdU9T1nSatCQujgKRz3bFmx5VdJx4IbHwLfELn8LVlhgf8FQi +eowHAgMBAAGjfTB7MB0GA1UdDgQWBBSnpQaxLKYJYO7Rl+lwrrw7GWzbITAPBgNV +HRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFKelBrEspglg7tGX6XCuvDsZbNshMBgG +A1UdIAQRMA8wDQYLKwYBBAGwLQMEAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3 +DQEBCwUAA4IBAQAmdzTblEiGKkGdLD4GkGDEjKwLVLgfuXvTBznk+j57sj1O7Z8j +vZfza1zv7v1Apt+hk6EKhqzvINB5Ab149xnYJDE0BAGmuhWawyfc2E8PzBhj/5kP +DpFrdRbhIfzYJsdHt6bPWHJxfrrhTZVHO8mvbaG0weyJ9rQPOLXiZNwlz6bb65pc +maHFCN795trV1lpFDMS3wrUU77QR/w4VtfX128a961qn8FYiqTxlVMYVqL2Gns2D +lmh6cYGJ4Qvh6hEbaAjMaZ7snkGeRDImeuKHCnE96+RapNLbxc3G3mB/ufNPRJLv +KrcYPqcZ2Qt9sTdBQrC6YB3y/gkRsPCHe6ed +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 1 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 1 G3" +# Serial: 687049649626669250736271037606554624078720034195 +# MD5 Fingerprint: a4:bc:5b:3f:fe:37:9a:fa:64:f0:e2:fa:05:3d:0b:ab +# SHA1 Fingerprint: 1b:8e:ea:57:96:29:1a:c9:39:ea:b8:0a:81:1a:73:73:c0:93:79:67 +# SHA256 Fingerprint: 8a:86:6f:d1:b2:76:b5:7e:57:8e:92:1c:65:82:8a:2b:ed:58:e9:f2:f2:88:05:41:34:b7:f1:f4:bf:c9:cc:74 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIUeFhfLq0sGUvjNwc1NBMotZbUZZMwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMSBHMzAeFw0xMjAxMTIxNzI3NDRaFw00 +MjAxMTIxNzI3NDRaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDEgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCgvlAQjunybEC0BJyFuTHK3C3kEakEPBtV +wedYMB0ktMPvhd6MLOHBPd+C5k+tR4ds7FtJwUrVu4/sh6x/gpqG7D0DmVIB0jWe +rNrwU8lmPNSsAgHaJNM7qAJGr6Qc4/hzWHa39g6QDbXwz8z6+cZM5cOGMAqNF341 +68Xfuw6cwI2H44g4hWf6Pser4BOcBRiYz5P1sZK0/CPTz9XEJ0ngnjybCKOLXSoh +4Pw5qlPafX7PGglTvF0FBM+hSo+LdoINofjSxxR3W5A2B4GbPgb6Ul5jxaYA/qXp +UhtStZI5cgMJYr2wYBZupt0lwgNm3fME0UDiTouG9G/lg6AnhF4EwfWQvTA9xO+o +abw4m6SkltFi2mnAAZauy8RRNOoMqv8hjlmPSlzkYZqn0ukqeI1RPToV7qJZjqlc +3sX5kCLliEVx3ZGZbHqfPT2YfF72vhZooF6uCyP8Wg+qInYtyaEQHeTTRCOQiJ/G +KubX9ZqzWB4vMIkIG1SitZgj7Ah3HJVdYdHLiZxfokqRmu8hqkkWCKi9YSgxyXSt +hfbZxbGL0eUQMk1fiyA6PEkfM4VZDdvLCXVDaXP7a3F98N/ETH3Goy7IlXnLc6KO +Tk0k+17kBL5yG6YnLUlamXrXXAkgt3+UuU/xDRxeiEIbEbfnkduebPRq34wGmAOt +zCjvpUfzUwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUo5fW816iEOGrRZ88F2Q87gFwnMwwDQYJKoZIhvcNAQELBQAD +ggIBABj6W3X8PnrHX3fHyt/PX8MSxEBd1DKquGrX1RUVRpgjpeaQWxiZTOOtQqOC +MTaIzen7xASWSIsBx40Bz1szBpZGZnQdT+3Btrm0DWHMY37XLneMlhwqI2hrhVd2 +cDMT/uFPpiN3GPoajOi9ZcnPP/TJF9zrx7zABC4tRi9pZsMbj/7sPtPKlL92CiUN +qXsCHKnQO18LwIE6PWThv6ctTr1NxNgpxiIY0MWscgKCP6o6ojoilzHdCGPDdRS5 +YCgtW2jgFqlmgiNR9etT2DGbe+m3nUvriBbP+V04ikkwj+3x6xn0dxoxGE1nVGwv +b2X52z3sIexe9PSLymBlVNFxZPT5pqOBMzYzcfCkeF9OrYMh3jRJjehZrJ3ydlo2 +8hP0r+AJx2EqbPfgna67hkooby7utHnNkDPDs3b69fBsnQGQ+p6Q9pxyz0fawx/k +NSBT8lTR32GDpgLiJTjehTItXnOQUl1CxM49S+H5GYQd1aJQzEH7QRTDvdbJWqNj +ZgKAvQU6O0ec7AAmTPWIUb+oI38YB7AL7YsmoWTTYUrrXJ/es69nA7Mf3W1daWhp +q1467HxpvMc7hU6eFbm0FU/DlXpY18ls6Wy58yljXrQs8C097Vpl4KlbQMJImYFt +nh8GKjwStIsPm6Ik8KaN1nrgS7ZklmOVhMJKzRwuJIczYOXD +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 2 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 2 G3" +# Serial: 390156079458959257446133169266079962026824725800 +# MD5 Fingerprint: af:0c:86:6e:bf:40:2d:7f:0b:3e:12:50:ba:12:3d:06 +# SHA1 Fingerprint: 09:3c:61:f3:8b:8b:dc:7d:55:df:75:38:02:05:00:e1:25:f5:c8:36 +# SHA256 Fingerprint: 8f:e4:fb:0a:f9:3a:4d:0d:67:db:0b:eb:b2:3e:37:c7:1b:f3:25:dc:bc:dd:24:0e:a0:4d:af:58:b4:7e:18:40 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIURFc0JFuBiZs18s64KztbpybwdSgwDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMiBHMzAeFw0xMjAxMTIxODU5MzJaFw00 +MjAxMTIxODU5MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDIgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQChriWyARjcV4g/Ruv5r+LrI3HimtFhZiFf +qq8nUeVuGxbULX1QsFN3vXg6YOJkApt8hpvWGo6t/x8Vf9WVHhLL5hSEBMHfNrMW +n4rjyduYNM7YMxcoRvynyfDStNVNCXJJ+fKH46nafaF9a7I6JaltUkSs+L5u+9ym +c5GQYaYDFCDy54ejiK2toIz/pgslUiXnFgHVy7g1gQyjO/Dh4fxaXc6AcW34Sas+ +O7q414AB+6XrW7PFXmAqMaCvN+ggOp+oMiwMzAkd056OXbxMmO7FGmh77FOm6RQ1 +o9/NgJ8MSPsc9PG/Srj61YxxSscfrf5BmrODXfKEVu+lV0POKa2Mq1W/xPtbAd0j +IaFYAI7D0GoT7RPjEiuA3GfmlbLNHiJuKvhB1PLKFAeNilUSxmn1uIZoL1NesNKq +IcGY5jDjZ1XHm26sGahVpkUG0CM62+tlXSoREfA7T8pt9DTEceT/AFr2XK4jYIVz +8eQQsSWu1ZK7E8EM4DnatDlXtas1qnIhO4M15zHfeiFuuDIIfR0ykRVKYnLP43eh +vNURG3YBZwjgQQvD6xVu+KQZ2aKrr+InUlYrAoosFCT5v0ICvybIxo/gbjh9Uy3l +7ZizlWNof/k19N+IxWA1ksB8aRxhlRbQ694Lrz4EEEVlWFA4r0jyWbYW8jwNkALG +cC4BrTwV1wIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQU7edvdlq/YOxJW8ald7tyFnGbxD0wDQYJKoZIhvcNAQELBQAD +ggIBAJHfgD9DCX5xwvfrs4iP4VGyvD11+ShdyLyZm3tdquXK4Qr36LLTn91nMX66 +AarHakE7kNQIXLJgapDwyM4DYvmL7ftuKtwGTTwpD4kWilhMSA/ohGHqPHKmd+RC +roijQ1h5fq7KpVMNqT1wvSAZYaRsOPxDMuHBR//47PERIjKWnML2W2mWeyAMQ0Ga +W/ZZGYjeVYg3UQt4XAoeo0L9x52ID8DyeAIkVJOviYeIyUqAHerQbj5hLja7NQ4n +lv1mNDthcnPxFlxHBlRJAHpYErAK74X9sbgzdWqTHBLmYF5vHX/JHyPLhGGfHoJE ++V+tYlUkmlKY7VHnoX6XOuYvHxHaU4AshZ6rNRDbIl9qxV6XU/IyAgkwo1jwDQHV +csaxfGl7w/U2Rcxhbl5MlMVerugOXou/983g7aEOGzPuVBj+D77vfoRrQ+NwmNtd +dbINWQeFFSM51vHfqSYP1kjHs6Yi9TM3WpVHn3u6GBVv/9YUZINJ0gpnIdsPNWNg +KCLjsZWDzYWm3S8P52dSbrsvhXz1SnPnxT7AvSESBT/8twNJAlvIJebiVDj1eYeM +HVOyToV7BjjHLPj4sHKNJeV3UvQDHEimUF+IIDBu8oJDqz2XhOdT+yHBTw8imoa4 +WSr2Rz0ZiC3oheGe7IUIarFsNMkd7EgrO3jtZsSOeWmD3n+M +-----END CERTIFICATE----- + +# Issuer: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Subject: CN=QuoVadis Root CA 3 G3 O=QuoVadis Limited +# Label: "QuoVadis Root CA 3 G3" +# Serial: 268090761170461462463995952157327242137089239581 +# MD5 Fingerprint: df:7d:b9:ad:54:6f:68:a1:df:89:57:03:97:43:b0:d7 +# SHA1 Fingerprint: 48:12:bd:92:3c:a8:c4:39:06:e7:30:6d:27:96:e6:a4:cf:22:2e:7d +# SHA256 Fingerprint: 88:ef:81:de:20:2e:b0:18:45:2e:43:f8:64:72:5c:ea:5f:bd:1f:c2:d9:d2:05:73:07:09:c5:d8:b8:69:0f:46 +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIULvWbAiin23r/1aOp7r0DoM8Sah0wDQYJKoZIhvcNAQEL +BQAwSDELMAkGA1UEBhMCQk0xGTAXBgNVBAoTEFF1b1ZhZGlzIExpbWl0ZWQxHjAc +BgNVBAMTFVF1b1ZhZGlzIFJvb3QgQ0EgMyBHMzAeFw0xMjAxMTIyMDI2MzJaFw00 +MjAxMTIyMDI2MzJaMEgxCzAJBgNVBAYTAkJNMRkwFwYDVQQKExBRdW9WYWRpcyBM +aW1pdGVkMR4wHAYDVQQDExVRdW9WYWRpcyBSb290IENBIDMgRzMwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCzyw4QZ47qFJenMioKVjZ/aEzHs286IxSR +/xl/pcqs7rN2nXrpixurazHb+gtTTK/FpRp5PIpM/6zfJd5O2YIyC0TeytuMrKNu +FoM7pmRLMon7FhY4futD4tN0SsJiCnMK3UmzV9KwCoWdcTzeo8vAMvMBOSBDGzXR +U7Ox7sWTaYI+FrUoRqHe6okJ7UO4BUaKhvVZR74bbwEhELn9qdIoyhA5CcoTNs+c +ra1AdHkrAj80//ogaX3T7mH1urPnMNA3I4ZyYUUpSFlob3emLoG+B01vr87ERROR +FHAGjx+f+IdpsQ7vw4kZ6+ocYfx6bIrc1gMLnia6Et3UVDmrJqMz6nWB2i3ND0/k +A9HvFZcba5DFApCTZgIhsUfei5pKgLlVj7WiL8DWM2fafsSntARE60f75li59wzw +eyuxwHApw0BiLTtIadwjPEjrewl5qW3aqDCYz4ByA4imW0aucnl8CAMhZa634Ryl +sSqiMd5mBPfAdOhx3v89WcyWJhKLhZVXGqtrdQtEPREoPHtht+KPZ0/l7DxMYIBp +VzgeAVuNVejH38DMdyM0SXV89pgR6y3e7UEuFAUCf+D+IOs15xGsIs5XPd7JMG0Q +A4XN8f+MFrXBsj6IbGB/kE+V9/YtrQE5BwT6dYB9v0lQ7e/JxHwc64B+27bQ3RP+ +ydOc17KXqQIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +BjAdBgNVHQ4EFgQUxhfQvKjqAkPyGwaZXSuQILnXnOQwDQYJKoZIhvcNAQELBQAD +ggIBADRh2Va1EodVTd2jNTFGu6QHcrxfYWLopfsLN7E8trP6KZ1/AvWkyaiTt3px +KGmPc+FSkNrVvjrlt3ZqVoAh313m6Tqe5T72omnHKgqwGEfcIHB9UqM+WXzBusnI +FUBhynLWcKzSt/Ac5IYp8M7vaGPQtSCKFWGafoaYtMnCdvvMujAWzKNhxnQT5Wvv +oxXqA/4Ti2Tk08HS6IT7SdEQTXlm66r99I0xHnAUrdzeZxNMgRVhvLfZkXdxGYFg +u/BYpbWcC/ePIlUnwEsBbTuZDdQdm2NnL9DuDcpmvJRPpq3t/O5jrFc/ZSXPsoaP +0Aj/uHYUbt7lJ+yreLVTubY/6CD50qi+YUbKh4yE8/nxoGibIh6BJpsQBJFxwAYf +3KDTuVan45gtf4Od34wrnDKOMpTwATwiKp9Dwi7DmDkHOHv8XgBCH/MyJnmDhPbl +8MFREsALHgQjDFSlTC9JxUrRtm5gDWv8a4uFJGS3iQ6rJUdbPM9+Sb3H6QrG2vd+ +DhcI00iX0HGS8A85PjRqHH3Y8iKuu2n0M7SmSFXRDw4m6Oy2Cy2nhTXN/VnIn9HN +PlopNLk9hM6xZdRZkZFWdSHBd575euFgndOtBBj0fOtek49TSiIp+EgrPk2GrFt/ +ywaZWWDYWGWVjUTR939+J399roD1B0y2PpxxVJkES/1Y+Zj0 +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G2" +# Serial: 15385348160840213938643033620894905419 +# MD5 Fingerprint: 92:38:b9:f8:63:24:82:65:2c:57:33:e6:fe:81:8f:9d +# SHA1 Fingerprint: a1:4b:48:d9:43:ee:0a:0e:40:90:4f:3c:e0:a4:c0:91:93:51:5d:3f +# SHA256 Fingerprint: 7d:05:eb:b6:82:33:9f:8c:94:51:ee:09:4e:eb:fe:fa:79:53:a1:14:ed:b2:f4:49:49:45:2f:ab:7d:2f:c1:85 +-----BEGIN CERTIFICATE----- +MIIDljCCAn6gAwIBAgIQC5McOtY5Z+pnI7/Dr5r0SzANBgkqhkiG9w0BAQsFADBl +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJv +b3QgRzIwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQG +EwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNl +cnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDZ5ygvUj82ckmIkzTz+GoeMVSA +n61UQbVH35ao1K+ALbkKz3X9iaV9JPrjIgwrvJUXCzO/GU1BBpAAvQxNEP4Htecc +biJVMWWXvdMX0h5i89vqbFCMP4QMls+3ywPgym2hFEwbid3tALBSfK+RbLE4E9Hp +EgjAALAcKxHad3A2m67OeYfcgnDmCXRwVWmvo2ifv922ebPynXApVfSr/5Vh88lA +bx3RvpO704gqu52/clpWcTs/1PPRCv4o76Pu2ZmvA9OPYLfykqGxvYmJHzDNw6Yu +YjOuFgJ3RFrngQo8p0Quebg/BLxcoIfhG69Rjs3sLPr4/m3wOnyqi+RnlTGNAgMB +AAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQW +BBTOw0q5mVXyuNtgv6l+vVa1lzan1jANBgkqhkiG9w0BAQsFAAOCAQEAyqVVjOPI +QW5pJ6d1Ee88hjZv0p3GeDgdaZaikmkuOGybfQTUiaWxMTeKySHMq2zNixya1r9I +0jJmwYrA8y8678Dj1JGG0VDjA9tzd29KOVPt3ibHtX2vK0LRdWLjSisCx1BL4Gni +lmwORGYQRI+tBev4eaymG+g3NJ1TyWGqolKvSnAWhsI6yLETcDbYz+70CjTVW0z9 +B5yiutkBclzzTcHdDrEcDcRjvq30FPuJ7KJBDkzMyFdA0G4Dqs0MjomZmWzwPDCv +ON9vvKO+KSAnq3T/EyJ43pdSVR6DtVQgA+6uwE9W3jfMw3+qBCe703e4YtsXfJwo +IhNzbM8m9Yop5w== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Assured ID Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Assured ID Root G3" +# Serial: 15459312981008553731928384953135426796 +# MD5 Fingerprint: 7c:7f:65:31:0c:81:df:8d:ba:3e:99:e2:5c:ad:6e:fb +# SHA1 Fingerprint: f5:17:a2:4f:9a:48:c6:c9:f8:a2:00:26:9f:dc:0f:48:2c:ab:30:89 +# SHA256 Fingerprint: 7e:37:cb:8b:4c:47:09:0c:ab:36:55:1b:a6:f4:5d:b8:40:68:0f:ba:16:6a:95:2d:b1:00:71:7f:43:05:3f:c2 +-----BEGIN CERTIFICATE----- +MIICRjCCAc2gAwIBAgIQC6Fa+h3foLVJRK/NJKBs7DAKBggqhkjOPQQDAzBlMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3Qg +RzMwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBlMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSQwIgYDVQQDExtEaWdpQ2VydCBBc3N1cmVkIElEIFJvb3QgRzMwdjAQBgcq +hkjOPQIBBgUrgQQAIgNiAAQZ57ysRGXtzbg/WPuNsVepRC0FFfLvC/8QdJ+1YlJf +Zn4f5dwbRXkLzMZTCp2NXQLZqVneAlr2lSoOjThKiknGvMYDOAdfVdp+CW7if17Q +RSAPWXYQ1qAk8C3eNvJsKTmjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/ +BAQDAgGGMB0GA1UdDgQWBBTL0L2p4ZgFUaFNN6KDec6NHSrkhDAKBggqhkjOPQQD +AwNnADBkAjAlpIFFAmsSS3V0T8gj43DydXLefInwz5FyYZ5eEJJZVrmDxxDnOOlY +JjZ91eQ0hjkCMHw2U/Aw5WJjOpnitqM7mzT6HtoQknFekROn3aRukswy1vUhZscv +6pZjamVFkpUBtA== +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G2 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G2" +# Serial: 4293743540046975378534879503202253541 +# MD5 Fingerprint: e4:a6:8a:c8:54:ac:52:42:46:0a:fd:72:48:1b:2a:44 +# SHA1 Fingerprint: df:3c:24:f9:bf:d6:66:76:1b:26:80:73:fe:06:d1:cc:8d:4f:82:a4 +# SHA256 Fingerprint: cb:3c:cb:b7:60:31:e5:e0:13:8f:8d:d3:9a:23:f9:de:47:ff:c3:5e:43:c1:14:4c:ea:27:d4:6a:5a:b1:cb:5f +-----BEGIN CERTIFICATE----- +MIIDjjCCAnagAwIBAgIQAzrx5qcRqaC7KGSxHQn65TANBgkqhkiG9w0BAQsFADBh +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH +MjAeFw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVT +MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j +b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEcyMIIBIjANBgkqhkiG +9w0BAQEFAAOCAQ8AMIIBCgKCAQEAuzfNNNx7a8myaJCtSnX/RrohCgiN9RlUyfuI +2/Ou8jqJkTx65qsGGmvPrC3oXgkkRLpimn7Wo6h+4FR1IAWsULecYxpsMNzaHxmx +1x7e/dfgy5SDN67sH0NO3Xss0r0upS/kqbitOtSZpLYl6ZtrAGCSYP9PIUkY92eQ +q2EGnI/yuum06ZIya7XzV+hdG82MHauVBJVJ8zUtluNJbd134/tJS7SsVQepj5Wz +tCO7TG1F8PapspUwtP1MVYwnSlcUfIKdzXOS0xZKBgyMUNGPHgm+F6HmIcr9g+UQ +vIOlCsRnKPZzFBQ9RnbDhxSJITRNrw9FDKZJobq7nMWxM4MphQIDAQABo0IwQDAP +BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAdBgNVHQ4EFgQUTiJUIBiV +5uNu5g/6+rkS7QYXjzkwDQYJKoZIhvcNAQELBQADggEBAGBnKJRvDkhj6zHd6mcY +1Yl9PMWLSn/pvtsrF9+wX3N3KjITOYFnQoQj8kVnNeyIv/iPsGEMNKSuIEyExtv4 +NeF22d+mQrvHRAiGfzZ0JFrabA0UWTW98kndth/Jsw1HKj2ZL7tcu7XUIOGZX1NG +Fdtom/DzMNU+MeKNhJ7jitralj41E6Vf8PlwUHBHQRFXGU7Aj64GxJUTFy8bJZ91 +8rGOmaFvE7FBcf6IKshPECBV1/MUReXgRPTqh5Uykw7+U0b6LJ3/iyK5S9kJRaTe +pLiaWN0bfVKfjllDiIGknibVb63dDcY3fe0Dkhvld1927jyNxF1WW6LZZm6zNTfl +MrY= +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Global Root G3 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Global Root G3" +# Serial: 7089244469030293291760083333884364146 +# MD5 Fingerprint: f5:5d:a4:50:a5:fb:28:7e:1e:0f:0d:cc:96:57:56:ca +# SHA1 Fingerprint: 7e:04:de:89:6a:3e:66:6d:00:e6:87:d3:3f:fa:d9:3b:e8:3d:34:9e +# SHA256 Fingerprint: 31:ad:66:48:f8:10:41:38:c7:38:f3:9e:a4:32:01:33:39:3e:3a:18:cc:02:29:6e:f9:7c:2a:c9:ef:67:31:d0 +-----BEGIN CERTIFICATE----- +MIICPzCCAcWgAwIBAgIQBVVWvPJepDU1w6QP1atFcjAKBggqhkjOPQQDAzBhMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cu +ZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBHMzAe +Fw0xMzA4MDExMjAwMDBaFw0zODAxMTUxMjAwMDBaMGExCzAJBgNVBAYTAlVTMRUw +EwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20x +IDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IEczMHYwEAYHKoZIzj0CAQYF +K4EEACIDYgAE3afZu4q4C/sLfyHS8L6+c/MzXRq8NOrexpu80JX28MzQC7phW1FG +fp4tn+6OYwwX7Adw9c+ELkCDnOg/QW07rdOkFFk2eJ0DQ+4QE2xy3q6Ip6FrtUPO +Z9wj/wMco+I+o0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBhjAd +BgNVHQ4EFgQUs9tIpPmhxdiuNkHMEWNpYim8S8YwCgYIKoZIzj0EAwMDaAAwZQIx +AK288mw/EkrRLTnDCgmXc/SINoyIJ7vmiI1Qhadj+Z4y3maTD/HMsQmP3Wyr+mt/ +oAIwOWZbwmSNuJ5Q3KjVSaLtx9zRSX8XAbjIho9OjIgrqJqpisXRAL34VOKa5Vt8 +sycX +-----END CERTIFICATE----- + +# Issuer: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Subject: CN=DigiCert Trusted Root G4 O=DigiCert Inc OU=www.digicert.com +# Label: "DigiCert Trusted Root G4" +# Serial: 7451500558977370777930084869016614236 +# MD5 Fingerprint: 78:f2:fc:aa:60:1f:2f:b4:eb:c9:37:ba:53:2e:75:49 +# SHA1 Fingerprint: dd:fb:16:cd:49:31:c9:73:a2:03:7d:3f:c8:3a:4d:7d:77:5d:05:e4 +# SHA256 Fingerprint: 55:2f:7b:dc:f1:a7:af:9e:6c:e6:72:01:7f:4f:12:ab:f7:72:40:c7:8e:76:1a:c2:03:d1:d9:d2:0a:c8:99:88 +-----BEGIN CERTIFICATE----- +MIIFkDCCA3igAwIBAgIQBZsbV56OITLiOQe9p3d1XDANBgkqhkiG9w0BAQwFADBi +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3Qg +RzQwHhcNMTMwODAxMTIwMDAwWhcNMzgwMTE1MTIwMDAwWjBiMQswCQYDVQQGEwJV +UzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3d3cuZGlnaWNlcnQu +Y29tMSEwHwYDVQQDExhEaWdpQ2VydCBUcnVzdGVkIFJvb3QgRzQwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQC/5pBzaN675F1KPDAiMGkz7MKnJS7JIT3y +ithZwuEppz1Yq3aaza57G4QNxDAf8xukOBbrVsaXbR2rsnnyyhHS5F/WBTxSD1If +xp4VpX6+n6lXFllVcq9ok3DCsrp1mWpzMpTREEQQLt+C8weE5nQ7bXHiLQwb7iDV +ySAdYyktzuxeTsiT+CFhmzTrBcZe7FsavOvJz82sNEBfsXpm7nfISKhmV1efVFiO +DCu3T6cw2Vbuyntd463JT17lNecxy9qTXtyOj4DatpGYQJB5w3jHtrHEtWoYOAMQ +jdjUN6QuBX2I9YI+EJFwq1WCQTLX2wRzKm6RAXwhTNS8rhsDdV14Ztk6MUSaM0C/ +CNdaSaTC5qmgZ92kJ7yhTzm1EVgX9yRcRo9k98FpiHaYdj1ZXUJ2h4mXaXpI8OCi +EhtmmnTK3kse5w5jrubU75KSOp493ADkRSWJtppEGSt+wJS00mFt6zPZxd9LBADM +fRyVw4/3IbKyEbe7f/LVjHAsQWCqsWMYRJUadmJ+9oCw++hkpjPRiQfhvbfmQ6QY +uKZ3AeEPlAwhHbJUKSWJbOUOUlFHdL4mrLZBdd56rF+NP8m800ERElvlEFDrMcXK +chYiCd98THU/Y+whX8QgUWtvsauGi0/C1kVfnSD8oR7FwI+isX4KJpn15GkvmB0t +9dmpsh3lGwIDAQABo0IwQDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIB +hjAdBgNVHQ4EFgQU7NfjgtJxXWRM3y5nP+e6mK4cD08wDQYJKoZIhvcNAQEMBQAD +ggIBALth2X2pbL4XxJEbw6GiAI3jZGgPVs93rnD5/ZpKmbnJeFwMDF/k5hQpVgs2 +SV1EY+CtnJYYZhsjDT156W1r1lT40jzBQ0CuHVD1UvyQO7uYmWlrx8GnqGikJ9yd ++SeuMIW59mdNOj6PWTkiU0TryF0Dyu1Qen1iIQqAyHNm0aAFYF/opbSnr6j3bTWc +fFqK1qI4mfN4i/RN0iAL3gTujJtHgXINwBQy7zBZLq7gcfJW5GqXb5JQbZaNaHqa +sjYUegbyJLkJEVDXCLG4iXqEI2FCKeWjzaIgQdfRnGTZ6iahixTXTBmyUEFxPT9N +cCOGDErcgdLMMpSEDQgJlxxPwO5rIHQw0uA5NBCFIRUBCOhVMt5xSdkoF1BN5r5N +0XWs0Mr7QbhDparTwwVETyw2m+L64kW4I1NsBm9nVX9GtUw/bihaeSbSpKhil9Ie +4u1Ki7wb/UdKDd9nZn6yW0HQO+T0O/QEY+nvwlQAUaCKKsnOeMzV6ocEGLPOr0mI +r/OSmbaz5mEP0oUA51Aa5BuVnRmhuZyxm7EAHu/QD09CbMkKvO5D+jpxpchNJqU1 +/YldvIViHTLSoCtU7ZpXwdv6EM8Zt4tKG48BtieVU+i2iW1bvGjUI+iLUaJW+fCm +gKDWHrO8Dw9TdSmq6hN35N6MgSGtBxBHEa2HPQfRdbzP82Z+ +-----END CERTIFICATE----- + +# Issuer: CN=Certification Authority of WoSign O=WoSign CA Limited +# Subject: CN=Certification Authority of WoSign O=WoSign CA Limited +# Label: "WoSign" +# Serial: 125491772294754854453622855443212256657 +# MD5 Fingerprint: a1:f2:f9:b5:d2:c8:7a:74:b8:f3:05:f1:d7:e1:84:8d +# SHA1 Fingerprint: b9:42:94:bf:91:ea:8f:b6:4b:e6:10:97:c7:fb:00:13:59:b6:76:cb +# SHA256 Fingerprint: 4b:22:d5:a6:ae:c9:9f:3c:db:79:aa:5e:c0:68:38:47:9c:d5:ec:ba:71:64:f7:f2:2d:c1:d6:5f:63:d8:57:08 +-----BEGIN CERTIFICATE----- +MIIFdjCCA16gAwIBAgIQXmjWEXGUY1BWAGjzPsnFkTANBgkqhkiG9w0BAQUFADBV +MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxKjAoBgNV +BAMTIUNlcnRpZmljYXRpb24gQXV0aG9yaXR5IG9mIFdvU2lnbjAeFw0wOTA4MDgw +MTAwMDFaFw0zOTA4MDgwMTAwMDFaMFUxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFX +b1NpZ24gQ0EgTGltaXRlZDEqMCgGA1UEAxMhQ2VydGlmaWNhdGlvbiBBdXRob3Jp +dHkgb2YgV29TaWduMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvcqN +rLiRFVaXe2tcesLea9mhsMMQI/qnobLMMfo+2aYpbxY94Gv4uEBf2zmoAHqLoE1U +fcIiePyOCbiohdfMlZdLdNiefvAA5A6JrkkoRBoQmTIPJYhTpA2zDxIIFgsDcScc +f+Hb0v1naMQFXQoOXXDX2JegvFNBmpGN9J42Znp+VsGQX+axaCA2pIwkLCxHC1l2 +ZjC1vt7tj/id07sBMOby8w7gLJKA84X5KIq0VC6a7fd2/BVoFutKbOsuEo/Uz/4M +x1wdC34FMr5esAkqQtXJTpCzWQ27en7N1QhatH/YHGkR+ScPewavVIMYe+HdVHpR +aG53/Ma/UkpmRqGyZxq7o093oL5d//xWC0Nyd5DKnvnyOfUNqfTq1+ezEC8wQjch +zDBwyYaYD8xYTYO7feUapTeNtqwylwA6Y3EkHp43xP901DfA4v6IRmAR3Qg/UDar +uHqklWJqbrDKaiFaafPz+x1wOZXzp26mgYmhiMU7ccqjUu6Du/2gd/Tkb+dC221K +mYo0SLwX3OSACCK28jHAPwQ+658geda4BmRkAjHXqc1S+4RFaQkAKtxVi8QGRkvA +Sh0JWzko/amrzgD5LkhLJuYwTKVYyrREgk/nkR4zw7CT/xH8gdLKH3Ep3XZPkiWv +HYG3Dy+MwwbMLyejSuQOmbp8HkUff6oZRZb9/D0CAwEAAaNCMEAwDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFOFmzw7R8bNLtwYgFP6H +EtX2/vs+MA0GCSqGSIb3DQEBBQUAA4ICAQCoy3JAsnbBfnv8rWTjMnvMPLZdRtP1 +LOJwXcgu2AZ9mNELIaCJWSQBnfmvCX0KI4I01fx8cpm5o9dU9OpScA7F9dY74ToJ +MuYhOZO9sxXqT2r09Ys/L3yNWC7F4TmgPsc9SnOeQHrAK2GpZ8nzJLmzbVUsWh2e +JXLOC62qx1ViC777Y7NhRCOjy+EaDveaBk3e1CNOIZZbOVtXHS9dCF4Jef98l7VN +g64N1uajeeAz0JmWAjCnPv/So0M/BVoG6kQC2nz4SNAzqfkHx5Xh9T71XXG68pWp +dIhhWeO/yloTunK0jF02h+mmxTwTv97QRCbut+wucPrXnbes5cVAWubXbHssw1ab +R80LzvobtCHXt2a49CUwi1wNuepnsvRtrtWhnk/Yn+knArAdBtaP4/tIEp9/EaEQ +PkxROpaw0RPxx9gmrjrKkcRpnd8BKWRRb2jaFOwIQZeQjdCygPLPwj2/kWjFgGce +xGATVdVhmVd8upUPYUk6ynW8yQqTP2cOEvIo4jEbwFcW3wh8GcF+Dx+FHgo2fFt+ +J7x6v+Db9NpSvd4MVHAxkUOVyLzwPt0JfjBkUO1/AaQzZ01oT74V77D2AhGiGxMl +OtzCWfHjXEa7ZywCRuoeSKbmW9m1vFGikpbbqsY3Iqb+zCB0oy2pLmvLwIIRIbWT +ee5Ehr7XHuQe+w== +-----END CERTIFICATE----- + +# Issuer: CN=CA 沃通根证书 O=WoSign CA Limited +# Subject: CN=CA 沃通根证书 O=WoSign CA Limited +# Label: "WoSign China" +# Serial: 106921963437422998931660691310149453965 +# MD5 Fingerprint: 78:83:5b:52:16:76:c4:24:3b:83:78:e8:ac:da:9a:93 +# SHA1 Fingerprint: 16:32:47:8d:89:f9:21:3a:92:00:85:63:f5:a4:a7:d3:12:40:8a:d6 +# SHA256 Fingerprint: d6:f0:34:bd:94:aa:23:3f:02:97:ec:a4:24:5b:28:39:73:e4:47:aa:59:0f:31:0c:77:f4:8f:df:83:11:22:54 +-----BEGIN CERTIFICATE----- +MIIFWDCCA0CgAwIBAgIQUHBrzdgT/BtOOzNy0hFIjTANBgkqhkiG9w0BAQsFADBG +MQswCQYDVQQGEwJDTjEaMBgGA1UEChMRV29TaWduIENBIExpbWl0ZWQxGzAZBgNV +BAMMEkNBIOayg+mAmuagueivgeS5pjAeFw0wOTA4MDgwMTAwMDFaFw0zOTA4MDgw +MTAwMDFaMEYxCzAJBgNVBAYTAkNOMRowGAYDVQQKExFXb1NpZ24gQ0EgTGltaXRl +ZDEbMBkGA1UEAwwSQ0Eg5rKD6YCa5qC56K+B5LmmMIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEA0EkhHiX8h8EqwqzbdoYGTufQdDTc7WU1/FDWiD+k8H/r +D195L4mx/bxjWDeTmzj4t1up+thxx7S8gJeNbEvxUNUqKaqoGXqW5pWOdO2XCld1 +9AXbbQs5uQF/qvbW2mzmBeCkTVL829B0txGMe41P/4eDrv8FAxNXUDf+jJZSEExf +v5RxadmWPgxDT74wwJ85dE8GRV2j1lY5aAfMh09Qd5Nx2UQIsYo06Yms25tO4dnk +UkWMLhQfkWsZHWgpLFbE4h4TV2TwYeO5Ed+w4VegG63XX9Gv2ystP9Bojg/qnw+L +NVgbExz03jWhCl3W6t8Sb8D7aQdGctyB9gQjF+BNdeFyb7Ao65vh4YOhn0pdr8yb ++gIgthhid5E7o9Vlrdx8kHccREGkSovrlXLp9glk3Kgtn3R46MGiCWOc76DbT52V +qyBPt7D3h1ymoOQ3OMdc4zUPLK2jgKLsLl3Az+2LBcLmc272idX10kaO6m1jGx6K +yX2m+Jzr5dVjhU1zZmkR/sgO9MHHZklTfuQZa/HpelmjbX7FF+Ynxu8b22/8DU0G +AbQOXDBGVWCvOGU6yke6rCzMRh+yRpY/8+0mBe53oWprfi1tWFxK1I5nuPHa1UaK +J/kR8slC/k7e3x9cxKSGhxYzoacXGKUN5AXlK8IrC6KVkLn9YDxOiT7nnO4fuwEC +AwEAAaNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFOBNv9ybQV0T6GTwp+kVpOGBwboxMA0GCSqGSIb3DQEBCwUAA4ICAQBqinA4 +WbbaixjIvirTthnVZil6Xc1bL3McJk6jfW+rtylNpumlEYOnOXOvEESS5iVdT2H6 +yAa+Tkvv/vMx/sZ8cApBWNromUuWyXi8mHwCKe0JgOYKOoICKuLJL8hWGSbueBwj +/feTZU7n85iYr83d2Z5AiDEoOqsuC7CsDCT6eiaY8xJhEPRdF/d+4niXVOKM6Cm6 +jBAyvd0zaziGfjk9DgNyp115j0WKWa5bIW4xRtVZjc8VX90xJc/bYNaBRHIpAlf2 +ltTW/+op2znFuCyKGo3Oy+dCMYYFaA6eFN0AkLppRQjbbpCBhqcqBT/mhDn4t/lX +X0ykeVoQDF7Va/81XwVRHmyjdanPUIPTfPRm94KNPQx96N97qA4bLJyuQHCH2u2n +FoJavjVsIE4iYdm8UXrNemHcSxH5/mc0zy4EZmFcV5cjjPOGG0jfKq+nwf/Yjj4D +u9gqsPoUJbJRa4ZDhS4HIxaAjUz7tGM7zMN07RujHv41D198HRaG9Q7DlfEvr10l +O1Hm13ZBONFLAzkopR6RctR9q5czxNM+4Gm2KHmgCY0c0f9BckgG/Jou5yD5m6Le +ie2uPAmvylezkolwQOQvT8Jwg0DXJCxr5wkf09XHwQj02w47HAcLQxGEIYbpgNR1 +2KvxAmLBsX5VYc8T1yaw15zLKYs4SgsOkI26oQ== +-----END CERTIFICATE----- + +# Issuer: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Subject: CN=COMODO RSA Certification Authority O=COMODO CA Limited +# Label: "COMODO RSA Certification Authority" +# Serial: 101909084537582093308941363524873193117 +# MD5 Fingerprint: 1b:31:b0:71:40:36:cc:14:36:91:ad:c4:3e:fd:ec:18 +# SHA1 Fingerprint: af:e5:d2:44:a8:d1:19:42:30:ff:47:9f:e2:f8:97:bb:cd:7a:8c:b4 +# SHA256 Fingerprint: 52:f0:e1:c4:e5:8e:c6:29:29:1b:60:31:7f:07:46:71:b8:5d:7e:a8:0d:5b:07:27:34:63:53:4b:32:b4:02:34 +-----BEGIN CERTIFICATE----- +MIIF2DCCA8CgAwIBAgIQTKr5yttjb+Af907YWwOGnTANBgkqhkiG9w0BAQwFADCB +hTELMAkGA1UEBhMCR0IxGzAZBgNVBAgTEkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4G +A1UEBxMHU2FsZm9yZDEaMBgGA1UEChMRQ09NT0RPIENBIExpbWl0ZWQxKzApBgNV +BAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMTE5 +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBhTELMAkGA1UEBhMCR0IxGzAZBgNVBAgT +EkdyZWF0ZXIgTWFuY2hlc3RlcjEQMA4GA1UEBxMHU2FsZm9yZDEaMBgGA1UEChMR +Q09NT0RPIENBIExpbWl0ZWQxKzApBgNVBAMTIkNPTU9ETyBSU0EgQ2VydGlmaWNh +dGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCR +6FSS0gpWsawNJN3Fz0RndJkrN6N9I3AAcbxT38T6KhKPS38QVr2fcHK3YX/JSw8X +pz3jsARh7v8Rl8f0hj4K+j5c+ZPmNHrZFGvnnLOFoIJ6dq9xkNfs/Q36nGz637CC +9BR++b7Epi9Pf5l/tfxnQ3K9DADWietrLNPtj5gcFKt+5eNu/Nio5JIk2kNrYrhV +/erBvGy2i/MOjZrkm2xpmfh4SDBF1a3hDTxFYPwyllEnvGfDyi62a+pGx8cgoLEf +Zd5ICLqkTqnyg0Y3hOvozIFIQ2dOciqbXL1MGyiKXCJ7tKuY2e7gUYPDCUZObT6Z ++pUX2nwzV0E8jVHtC7ZcryxjGt9XyD+86V3Em69FmeKjWiS0uqlWPc9vqv9JWL7w +qP/0uK3pN/u6uPQLOvnoQ0IeidiEyxPx2bvhiWC4jChWrBQdnArncevPDt09qZah +SL0896+1DSJMwBGB7FY79tOi4lu3sgQiUpWAk2nojkxl8ZEDLXB0AuqLZxUpaVIC +u9ffUGpVRr+goyhhf3DQw6KqLCGqR84onAZFdr+CGCe01a60y1Dma/RMhnEw6abf +Fobg2P9A3fvQQoh/ozM6LlweQRGBY84YcWsr7KaKtzFcOmpH4MN5WdYgGq/yapiq +crxXStJLnbsQ/LBMQeXtHT1eKJ2czL+zUdqnR+WEUwIDAQABo0IwQDAdBgNVHQ4E +FgQUu69+Aj36pvE8hI6t7jiY7NkyMtQwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB +/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAArx1UaEt65Ru2yyTUEUAJNMnMvl +wFTPoCWOAvn9sKIN9SCYPBMtrFaisNZ+EZLpLrqeLppysb0ZRGxhNaKatBYSaVqM +4dc+pBroLwP0rmEdEBsqpIt6xf4FpuHA1sj+nq6PK7o9mfjYcwlYRm6mnPTXJ9OV +2jeDchzTc+CiR5kDOF3VSXkAKRzH7JsgHAckaVd4sjn8OoSgtZx8jb8uk2Intzna +FxiuvTwJaP+EmzzV1gsD41eeFPfR60/IvYcjt7ZJQ3mFXLrrkguhxuhoqEwWsRqZ +CuhTLJK7oQkYdQxlqHvLI7cawiiFwxv/0Cti76R7CZGYZ4wUAc1oBmpjIXUDgIiK +boHGhfKppC3n9KUkEEeDys30jXlYsQab5xoq2Z0B15R97QNKyvDb6KkBPvVWmcke +jkk9u+UJueBPSZI9FoJAzMxZxuY67RIuaTxslbH9qh17f4a+Hg4yRvv7E491f0yL +S0Zj/gA0QHDBw7mh3aZw4gSzQbzpgJHqZJx64SIDqZxubw5lT2yHh17zbqD5daWb +QOhTsiedSrnAdyGN/4fy3ryM7xfft0kL0fJuMAsaDk527RH89elWsn2/x20Kk4yl +0MC2Hb46TpSi125sC8KKfPog88Tk5c0NqMuRkrF8hey1FGlmDoLnzc7ILaZRfyHB +NVOFBkpdn627G190 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust RSA Certification Authority O=The USERTRUST Network +# Label: "USERTrust RSA Certification Authority" +# Serial: 2645093764781058787591871645665788717 +# MD5 Fingerprint: 1b:fe:69:d1:91:b7:19:33:a3:72:a8:0f:e1:55:e5:b5 +# SHA1 Fingerprint: 2b:8f:1b:57:33:0d:bb:a2:d0:7a:6c:51:f7:0e:e9:0d:da:b9:ad:8e +# SHA256 Fingerprint: e7:93:c9:b0:2f:d8:aa:13:e2:1c:31:22:8a:cc:b0:81:19:64:3b:74:9c:89:89:64:b1:74:6d:46:c3:d4:cb:d2 +-----BEGIN CERTIFICATE----- +MIIF3jCCA8agAwIBAgIQAf1tMPyjylGoG7xkDjUDLTANBgkqhkiG9w0BAQwFADCB +iDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0pl +cnNleSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNV +BAMTJVVTRVJUcnVzdCBSU0EgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAw +MjAxMDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNV +BAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVU +aGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBSU0EgQ2Vy +dGlmaWNhdGlvbiBBdXRob3JpdHkwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQCAEmUXNg7D2wiz0KxXDXbtzSfTTK1Qg2HiqiBNCS1kCdzOiZ/MPans9s/B +3PHTsdZ7NygRK0faOca8Ohm0X6a9fZ2jY0K2dvKpOyuR+OJv0OwWIJAJPuLodMkY +tJHUYmTbf6MG8YgYapAiPLz+E/CHFHv25B+O1ORRxhFnRghRy4YUVD+8M/5+bJz/ +Fp0YvVGONaanZshyZ9shZrHUm3gDwFA66Mzw3LyeTP6vBZY1H1dat//O+T23LLb2 +VN3I5xI6Ta5MirdcmrS3ID3KfyI0rn47aGYBROcBTkZTmzNg95S+UzeQc0PzMsNT +79uq/nROacdrjGCT3sTHDN/hMq7MkztReJVni+49Vv4M0GkPGw/zJSZrM233bkf6 +c0Plfg6lZrEpfDKEY1WJxA3Bk1QwGROs0303p+tdOmw1XNtB1xLaqUkL39iAigmT +Yo61Zs8liM2EuLE/pDkP2QKe6xJMlXzzawWpXhaDzLhn4ugTncxbgtNMs+1b/97l +c6wjOy0AvzVVdAlJ2ElYGn+SNuZRkg7zJn0cTRe8yexDJtC/QV9AqURE9JnnV4ee +UB9XVKg+/XRjL7FQZQnmWEIuQxpMtPAlR1n6BB6T1CZGSlCBst6+eLf8ZxXhyVeE +Hg9j1uliutZfVS7qXMYoCAQlObgOK6nyTJccBz8NUvXt7y+CDwIDAQABo0IwQDAd +BgNVHQ4EFgQUU3m/WqorSs9UgOHYm8Cd8rIDZsswDgYDVR0PAQH/BAQDAgEGMA8G +A1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEMBQADggIBAFzUfA3P9wF9QZllDHPF +Up/L+M+ZBn8b2kMVn54CVVeWFPFSPCeHlCjtHzoBN6J2/FNQwISbxmtOuowhT6KO +VWKR82kV2LyI48SqC/3vqOlLVSoGIG1VeCkZ7l8wXEskEVX/JJpuXior7gtNn3/3 +ATiUFJVDBwn7YKnuHKsSjKCaXqeYalltiz8I+8jRRa8YFWSQEg9zKC7F4iRO/Fjs +8PRF/iKz6y+O0tlFYQXBl2+odnKPi4w2r78NBc5xjeambx9spnFixdjQg3IM8WcR +iQycE0xyNN+81XHfqnHd4blsjDwSXWXavVcStkNr/+XeTWYRUc+ZruwXtuhxkYze +Sf7dNXGiFSeUHM9h4ya7b6NnJSFd5t0dCy5oGzuCr+yDZ4XUmFF0sbmZgIn/f3gZ +XHlKYC6SQK5MNyosycdiyA5d9zZbyuAlJQG03RoHnHcAP9Dc1ew91Pq7P8yF1m9/ +qS3fuQL39ZeatTXaw2ewh0qpKJ4jjv9cJ2vhsE/zB+4ALtRZh8tSQZXq9EfX7mRB +VXyNWQKV3WKdwrnuWih0hKWbt5DHDAff9Yk2dDLWKMGwsAvgnEzDHNb842m1R0aB +L6KCq9NjRHDEjf8tM7qtj3u1cIiuPhnPQCjY/MiQu12ZIvVS5ljFH4gxQ+6IHdfG +jjxDah2nGN59PRbxYvnKkKj9 +-----END CERTIFICATE----- + +# Issuer: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Subject: CN=USERTrust ECC Certification Authority O=The USERTRUST Network +# Label: "USERTrust ECC Certification Authority" +# Serial: 123013823720199481456569720443997572134 +# MD5 Fingerprint: fa:68:bc:d9:b5:7f:ad:fd:c9:1d:06:83:28:cc:24:c1 +# SHA1 Fingerprint: d1:cb:ca:5d:b2:d5:2a:7f:69:3b:67:4d:e5:f0:5a:1d:0c:95:7d:f0 +# SHA256 Fingerprint: 4f:f4:60:d5:4b:9c:86:da:bf:bc:fc:57:12:e0:40:0d:2b:ed:3f:bc:4d:4f:bd:aa:86:e0:6a:dc:d2:a9:ad:7a +-----BEGIN CERTIFICATE----- +MIICjzCCAhWgAwIBAgIQXIuZxVqUxdJxVt7NiYDMJjAKBggqhkjOPQQDAzCBiDEL +MAkGA1UEBhMCVVMxEzARBgNVBAgTCk5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNl +eSBDaXR5MR4wHAYDVQQKExVUaGUgVVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMT +JVVTRVJUcnVzdCBFQ0MgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkwHhcNMTAwMjAx +MDAwMDAwWhcNMzgwMTE4MjM1OTU5WjCBiDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Ck5ldyBKZXJzZXkxFDASBgNVBAcTC0plcnNleSBDaXR5MR4wHAYDVQQKExVUaGUg +VVNFUlRSVVNUIE5ldHdvcmsxLjAsBgNVBAMTJVVTRVJUcnVzdCBFQ0MgQ2VydGlm +aWNhdGlvbiBBdXRob3JpdHkwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAAQarFRaqflo +I+d61SRvU8Za2EurxtW20eZzca7dnNYMYf3boIkDuAUU7FfO7l0/4iGzzvfUinng +o4N+LZfQYcTxmdwlkWOrfzCjtHDix6EznPO/LlxTsV+zfTJ/ijTjeXmjQjBAMB0G +A1UdDgQWBBQ64QmG1M8ZwpZ2dEl23OA1xmNjmjAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAwNoADBlAjA2Z6EWCNzklwBBHU6+4WMB +zzuqQhFkoJ2UOQIReVx7Hfpkue4WQrO/isIJxOzksU0CMQDpKmFHjFJKS04YcPbW +RNZu9YO6bVi9JNlWSOrvxKJGgYhqOkbRqZtNyWHa0V1Xahg= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R4 +# Label: "GlobalSign ECC Root CA - R4" +# Serial: 14367148294922964480859022125800977897474 +# MD5 Fingerprint: 20:f0:27:68:d1:7e:a0:9d:0e:e6:2a:ca:df:5c:89:8e +# SHA1 Fingerprint: 69:69:56:2e:40:80:f4:24:a1:e7:19:9f:14:ba:f3:ee:58:ab:6a:bb +# SHA256 Fingerprint: be:c9:49:11:c2:95:56:76:db:6c:0a:55:09:86:d7:6e:3b:a0:05:66:7c:44:2c:97:62:b4:fb:b7:73:de:22:8c +-----BEGIN CERTIFICATE----- +MIIB4TCCAYegAwIBAgIRKjikHJYKBN5CsiilC+g0mAIwCgYIKoZIzj0EAwIwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI0MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI0MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEuMZ5049sJQ6fLjkZHAOkrprlOQcJ +FspjsbmG+IpXwVfOQvpzofdlQv8ewQCybnMO/8ch5RikqtlxP6jUuc6MHaNCMEAw +DgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFSwe61F +uOJAf/sKbvu+M8k8o4TVMAoGCCqGSM49BAMCA0gAMEUCIQDckqGgE6bPA7DmxCGX +kPoUVy0D7O48027KqGx2vKLeuwIgJ6iFJzWbVsaj8kfSt24bAgAXqmemFZHe+pTs +ewv4n4Q= +-----END CERTIFICATE----- + +# Issuer: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Subject: CN=GlobalSign O=GlobalSign OU=GlobalSign ECC Root CA - R5 +# Label: "GlobalSign ECC Root CA - R5" +# Serial: 32785792099990507226680698011560947931244 +# MD5 Fingerprint: 9f:ad:3b:1c:02:1e:8a:ba:17:74:38:81:0c:a2:bc:08 +# SHA1 Fingerprint: 1f:24:c6:30:cd:a4:18:ef:20:69:ff:ad:4f:dd:5f:46:3a:1b:69:aa +# SHA256 Fingerprint: 17:9f:bc:14:8a:3d:d0:0f:d2:4e:a1:34:58:cc:43:bf:a7:f5:9c:81:82:d7:83:a5:13:f6:eb:ec:10:0c:89:24 +-----BEGIN CERTIFICATE----- +MIICHjCCAaSgAwIBAgIRYFlJ4CYuu1X5CneKcflK2GwwCgYIKoZIzj0EAwMwUDEk +MCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBDQSAtIFI1MRMwEQYDVQQKEwpH +bG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWduMB4XDTEyMTExMzAwMDAwMFoX +DTM4MDExOTAzMTQwN1owUDEkMCIGA1UECxMbR2xvYmFsU2lnbiBFQ0MgUm9vdCBD +QSAtIFI1MRMwEQYDVQQKEwpHbG9iYWxTaWduMRMwEQYDVQQDEwpHbG9iYWxTaWdu +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAER0UOlvt9Xb/pOdEh+J8LttV7HpI6SFkc +8GIxLcB6KP4ap1yztsyX50XUWPrRd21DosCHZTQKH3rd6zwzocWdTaRvQZU4f8ke +hOvRnkmSh5SHDDqFSmafnVmTTZdhBoZKo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYD +VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUPeYpSJvqB8ohREom3m7e0oPQn1kwCgYI +KoZIzj0EAwMDaAAwZQIxAOVpEslu28YxuglB4Zf4+/2a4n0Sye18ZNPLBSWLVtmg +515dTguDnFt2KaAJJiFqYgIwcdK1j1zqO+F4CYWodZI7yFz9SO8NdCKoCOJuxUnO +xwy8p2Fp8fc74SrL+SvzZpA3 +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden Root CA - G3 O=Staat der Nederlanden +# Label: "Staat der Nederlanden Root CA - G3" +# Serial: 10003001 +# MD5 Fingerprint: 0b:46:67:07:db:10:2f:19:8c:35:50:60:d1:0b:f4:37 +# SHA1 Fingerprint: d8:eb:6b:41:51:92:59:e0:f3:e7:85:00:c0:3d:b6:88:97:c9:ee:fc +# SHA256 Fingerprint: 3c:4f:b0:b9:5a:b8:b3:00:32:f4:32:b8:6f:53:5f:e1:72:c1:85:d0:fd:39:86:58:37:cf:36:18:7f:a6:f4:28 +-----BEGIN CERTIFICATE----- +MIIFdDCCA1ygAwIBAgIEAJiiOTANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSswKQYDVQQDDCJTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gUm9vdCBDQSAtIEczMB4XDTEzMTExNDExMjg0MloX +DTI4MTExMzIzMDAwMFowWjELMAkGA1UEBhMCTkwxHjAcBgNVBAoMFVN0YWF0IGRl +ciBOZWRlcmxhbmRlbjErMCkGA1UEAwwiU3RhYXQgZGVyIE5lZGVybGFuZGVuIFJv +b3QgQ0EgLSBHMzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL4yolQP +cPssXFnrbMSkUeiFKrPMSjTysF/zDsccPVMeiAho2G89rcKezIJnByeHaHE6n3WW +IkYFsO2tx1ueKt6c/DrGlaf1F2cY5y9JCAxcz+bMNO14+1Cx3Gsy8KL+tjzk7FqX +xz8ecAgwoNzFs21v0IJyEavSgWhZghe3eJJg+szeP4TrjTgzkApyI/o1zCZxMdFy +KJLZWyNtZrVtB0LrpjPOktvA9mxjeM3KTj215VKb8b475lRgsGYeCasH/lSJEULR +9yS6YHgamPfJEf0WwTUaVHXvQ9Plrk7O53vDxk5hUUurmkVLoR9BvUhTFXFkC4az +5S6+zqQbwSmEorXLCCN2QyIkHxcE1G6cxvx/K2Ya7Irl1s9N9WMJtxU51nus6+N8 +6U78dULI7ViVDAZCopz35HCz33JvWjdAidiFpNfxC95DGdRKWCyMijmev4SH8RY7 +Ngzp07TKbBlBUgmhHbBqv4LvcFEhMtwFdozL92TkA1CvjJFnq8Xy7ljY3r735zHP +bMk7ccHViLVlvMDoFxcHErVc0qsgk7TmgoNwNsXNo42ti+yjwUOH5kPiNL6VizXt +BznaqB16nzaeErAMZRKQFWDZJkBE41ZgpRDUajz9QdwOWke275dhdU/Z/seyHdTt +XUmzqWrLZoQT1Vyg3N9udwbRcXXIV2+vD3dbAgMBAAGjQjBAMA8GA1UdEwEB/wQF +MAMBAf8wDgYDVR0PAQH/BAQDAgEGMB0GA1UdDgQWBBRUrfrHkleuyjWcLhL75Lpd +INyUVzANBgkqhkiG9w0BAQsFAAOCAgEAMJmdBTLIXg47mAE6iqTnB/d6+Oea31BD +U5cqPco8R5gu4RV78ZLzYdqQJRZlwJ9UXQ4DO1t3ApyEtg2YXzTdO2PCwyiBwpwp +LiniyMMB8jPqKqrMCQj3ZWfGzd/TtiunvczRDnBfuCPRy5FOCvTIeuXZYzbB1N/8 +Ipf3YF3qKS9Ysr1YvY2WTxB1v0h7PVGHoTx0IsL8B3+A3MSs/mrBcDCw6Y5p4ixp +gZQJut3+TcCDjJRYwEYgr5wfAvg1VUkvRtTA8KCWAg8zxXHzniN9lLf9OtMJgwYh +/WA9rjLA0u6NpvDntIJ8CsxwyXmA+P5M9zWEGYox+wrZ13+b8KKaa8MFSu1BYBQw +0aoRQm7TIwIEC8Zl3d1Sd9qBa7Ko+gE4uZbqKmxnl4mUnrzhVNXkanjvSr0rmj1A +fsbAddJu+2gw7OyLnflJNZoaLNmzlTnVHpL3prllL+U9bTpITAjc5CgSKL59NVzq +4BZ+Extq1z7XnvwtdbLBFNUjA9tbbws+eC8N3jONFrdI54OagQ97wUNNVQQXOEpR +1VmiiXTTn74eS9fGbbeIJG9gkaSChVtWQbzQRKtqE77RLFi3EjNYsjdj3BP1lB0/ +QFH1T/U67cjF68IeHRaVesd+QnGTbksVtzDfqu1XhUisHWrdOWnk4Xl4vs4Fv6EM +94B7IWcnMFk= +-----END CERTIFICATE----- + +# Issuer: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Subject: CN=Staat der Nederlanden EV Root CA O=Staat der Nederlanden +# Label: "Staat der Nederlanden EV Root CA" +# Serial: 10000013 +# MD5 Fingerprint: fc:06:af:7b:e8:1a:f1:9a:b4:e8:d2:70:1f:c0:f5:ba +# SHA1 Fingerprint: 76:e2:7e:c1:4f:db:82:c1:c0:a6:75:b5:05:be:3d:29:b4:ed:db:bb +# SHA256 Fingerprint: 4d:24:91:41:4c:fe:95:67:46:ec:4c:ef:a6:cf:6f:72:e2:8a:13:29:43:2f:9d:8a:90:7a:c4:cb:5d:ad:c1:5a +-----BEGIN CERTIFICATE----- +MIIFcDCCA1igAwIBAgIEAJiWjTANBgkqhkiG9w0BAQsFADBYMQswCQYDVQQGEwJO +TDEeMBwGA1UECgwVU3RhYXQgZGVyIE5lZGVybGFuZGVuMSkwJwYDVQQDDCBTdGFh +dCBkZXIgTmVkZXJsYW5kZW4gRVYgUm9vdCBDQTAeFw0xMDEyMDgxMTE5MjlaFw0y +MjEyMDgxMTEwMjhaMFgxCzAJBgNVBAYTAk5MMR4wHAYDVQQKDBVTdGFhdCBkZXIg +TmVkZXJsYW5kZW4xKTAnBgNVBAMMIFN0YWF0IGRlciBOZWRlcmxhbmRlbiBFViBS +b290IENBMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA48d+ifkkSzrS +M4M1LGns3Amk41GoJSt5uAg94JG6hIXGhaTK5skuU6TJJB79VWZxXSzFYGgEt9nC +UiY4iKTWO0Cmws0/zZiTs1QUWJZV1VD+hq2kY39ch/aO5ieSZxeSAgMs3NZmdO3d +Z//BYY1jTw+bbRcwJu+r0h8QoPnFfxZpgQNH7R5ojXKhTbImxrpsX23Wr9GxE46p +rfNeaXUmGD5BKyF/7otdBwadQ8QpCiv8Kj6GyzyDOvnJDdrFmeK8eEEzduG/L13l +pJhQDBXd4Pqcfzho0LKmeqfRMb1+ilgnQ7O6M5HTp5gVXJrm0w912fxBmJc+qiXb +j5IusHsMX/FjqTf5m3VpTCgmJdrV8hJwRVXj33NeN/UhbJCONVrJ0yPr08C+eKxC +KFhmpUZtcALXEPlLVPxdhkqHz3/KRawRWrUgUY0viEeXOcDPusBCAUCZSCELa6fS +/ZbV0b5GnUngC6agIk440ME8MLxwjyx1zNDFjFE7PZQIZCZhfbnDZY8UnCHQqv0X +cgOPvZuM5l5Tnrmd74K74bzickFbIZTTRTeU0d8JOV3nI6qaHcptqAqGhYqCvkIH +1vI4gnPah1vlPNOePqc7nvQDs/nxfRN0Av+7oeX6AHkcpmZBiFxgV6YuCcS6/ZrP +px9Aw7vMWgpVSzs4dlG4Y4uElBbmVvMCAwEAAaNCMEAwDwYDVR0TAQH/BAUwAwEB +/zAOBgNVHQ8BAf8EBAMCAQYwHQYDVR0OBBYEFP6rAJCYniT8qcwaivsnuL8wbqg7 +MA0GCSqGSIb3DQEBCwUAA4ICAQDPdyxuVr5Os7aEAJSrR8kN0nbHhp8dB9O2tLsI +eK9p0gtJ3jPFrK3CiAJ9Brc1AsFgyb/E6JTe1NOpEyVa/m6irn0F3H3zbPB+po3u +2dfOWBfoqSmuc0iH55vKbimhZF8ZE/euBhD/UcabTVUlT5OZEAFTdfETzsemQUHS +v4ilf0X8rLiltTMMgsT7B/Zq5SWEXwbKwYY5EdtYzXc7LMJMD16a4/CrPmEbUCTC +wPTxGfARKbalGAKb12NMcIxHowNDXLldRqANb/9Zjr7dn3LDWyvfjFvO5QxGbJKy +CqNMVEIYFRIYvdr8unRu/8G2oGTYqV9Vrp9canaW2HNnh/tNf1zuacpzEPuKqf2e +vTY4SUmH9A4U8OmHuD+nT3pajnnUk+S7aFKErGzp85hwVXIy+TSrK0m1zSBi5Dp6 +Z2Orltxtrpfs/J92VoguZs9btsmksNcFuuEnL5O7Jiqik7Ab846+HUCjuTaPPoIa +Gl6I6lD4WeKDRikL40Rc4ZW2aZCaFG+XroHPaO+Zmr615+F/+PoTRxZMzG0IQOeL +eG9QgkRQP2YGiqtDhFZKDyAthg710tvSeopLzaXoTvFeJiUBWSOgftL2fiFX1ye8 +FVdMpEbB4IMeDExNH08GGeL5qPQ6gqGyeUN51q1veieQA6TqJIc/2b3Z6fJfUEkc +7uzXLg== +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Commercial Root CA 1 O=IdenTrust +# Label: "IdenTrust Commercial Root CA 1" +# Serial: 13298821034946342390520003877796839426 +# MD5 Fingerprint: b3:3e:77:73:75:ee:a0:d3:e3:7e:49:63:49:59:bb:c7 +# SHA1 Fingerprint: df:71:7e:aa:4a:d9:4e:c9:55:84:99:60:2d:48:de:5f:bc:f0:3a:25 +# SHA256 Fingerprint: 5d:56:49:9b:e4:d2:e0:8b:cf:ca:d0:8a:3e:38:72:3d:50:50:3b:de:70:69:48:e4:2f:55:60:30:19:e5:28:ae +-----BEGIN CERTIFICATE----- +MIIFYDCCA0igAwIBAgIQCgFCgAAAAUUjyES1AAAAAjANBgkqhkiG9w0BAQsFADBK +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScwJQYDVQQDEx5JZGVu +VHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwHhcNMTQwMTE2MTgxMjIzWhcNMzQw +MTE2MTgxMjIzWjBKMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MScw +JQYDVQQDEx5JZGVuVHJ1c3QgQ29tbWVyY2lhbCBSb290IENBIDEwggIiMA0GCSqG +SIb3DQEBAQUAA4ICDwAwggIKAoICAQCnUBneP5k91DNG8W9RYYKyqU+PZ4ldhNlT +3Qwo2dfw/66VQ3KZ+bVdfIrBQuExUHTRgQ18zZshq0PirK1ehm7zCYofWjK9ouuU ++ehcCuz/mNKvcbO0U59Oh++SvL3sTzIwiEsXXlfEU8L2ApeN2WIrvyQfYo3fw7gp +S0l4PJNgiCL8mdo2yMKi1CxUAGc1bnO/AljwpN3lsKImesrgNqUZFvX9t++uP0D1 +bVoE/c40yiTcdCMbXTMTEl3EASX2MN0CXZ/g1Ue9tOsbobtJSdifWwLziuQkkORi +T0/Br4sOdBeo0XKIanoBScy0RnnGF7HamB4HWfp1IYVl3ZBWzvurpWCdxJ35UrCL +vYf5jysjCiN2O/cz4ckA82n5S6LgTrx+kzmEB/dEcH7+B1rlsazRGMzyNeVJSQjK +Vsk9+w8YfYs7wRPCTY/JTw436R+hDmrfYi7LNQZReSzIJTj0+kuniVyc0uMNOYZK +dHzVWYfCP04MXFL0PfdSgvHqo6z9STQaKPNBiDoT7uje/5kdX7rL6B7yuVBgwDHT +c+XvvqDtMwt0viAgxGds8AgDelWAf0ZOlqf0Hj7h9tgJ4TNkK2PXMl6f+cB7D3hv +l7yTmvmcEpB4eoCHFddydJxVdHixuuFucAS6T6C6aMN7/zHwcz09lCqxC0EOoP5N +iGVreTO01wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB +/zAdBgNVHQ4EFgQU7UQZwNPwBovupHu+QucmVMiONnYwDQYJKoZIhvcNAQELBQAD +ggIBAA2ukDL2pkt8RHYZYR4nKM1eVO8lvOMIkPkp165oCOGUAFjvLi5+U1KMtlwH +6oi6mYtQlNeCgN9hCQCTrQ0U5s7B8jeUeLBfnLOic7iPBZM4zY0+sLj7wM+x8uwt +LRvM7Kqas6pgghstO8OEPVeKlh6cdbjTMM1gCIOQ045U8U1mwF10A0Cj7oV+wh93 +nAbowacYXVKV7cndJZ5t+qntozo00Fl72u1Q8zW/7esUTTHHYPTa8Yec4kjixsU3 ++wYQ+nVZZjFHKdp2mhzpgq7vmrlR94gjmmmVYjzlVYA211QC//G5Xc7UI2/YRYRK +W2XviQzdFKcgyxilJbQN+QHwotL0AMh0jqEqSI5l2xPE4iUXfeu+h1sXIFRRk0pT +AwvsXcoz7WL9RccvW9xYoIA55vrX/hMUpu09lEpCdNTDd1lzzY9GvlU47/rokTLq +l1gEIt44w8y8bckzOmoKaT+gyOpyj4xjhiO9bTyWnpXgSUyqorkqG5w2gXjtw+hG +4iZZRHUe2XWJUc0QhJ1hYMtd+ZciTY6Y5uN/9lu7rs3KSoFrXgvzUeF0K+l+J6fZ +mUlO+KWA2yUPHGNiiskzZ2s8EIPGrd6ozRaOjfAHN3Gf8qv8QfXBi+wAN10J5U6A +7/qxXDgGpRtK4dw4LTzcqx+QGtVKnO7RcGzM7vRX+Bi6hG6H +-----END CERTIFICATE----- + +# Issuer: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Subject: CN=IdenTrust Public Sector Root CA 1 O=IdenTrust +# Label: "IdenTrust Public Sector Root CA 1" +# Serial: 13298821034946342390521976156843933698 +# MD5 Fingerprint: 37:06:a5:b0:fc:89:9d:ba:f4:6b:8c:1a:64:cd:d5:ba +# SHA1 Fingerprint: ba:29:41:60:77:98:3f:f4:f3:ef:f2:31:05:3b:2e:ea:6d:4d:45:fd +# SHA256 Fingerprint: 30:d0:89:5a:9a:44:8a:26:20:91:63:55:22:d1:f5:20:10:b5:86:7a:ca:e1:2c:78:ef:95:8f:d4:f4:38:9f:2f +-----BEGIN CERTIFICATE----- +MIIFZjCCA06gAwIBAgIQCgFCgAAAAUUjz0Z8AAAAAjANBgkqhkiG9w0BAQsFADBN +MQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0MSowKAYDVQQDEyFJZGVu +VHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwHhcNMTQwMTE2MTc1MzMyWhcN +MzQwMTE2MTc1MzMyWjBNMQswCQYDVQQGEwJVUzESMBAGA1UEChMJSWRlblRydXN0 +MSowKAYDVQQDEyFJZGVuVHJ1c3QgUHVibGljIFNlY3RvciBSb290IENBIDEwggIi +MA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC2IpT8pEiv6EdrCvsnduTyP4o7 +ekosMSqMjbCpwzFrqHd2hCa2rIFCDQjrVVi7evi8ZX3yoG2LqEfpYnYeEe4IFNGy +RBb06tD6Hi9e28tzQa68ALBKK0CyrOE7S8ItneShm+waOh7wCLPQ5CQ1B5+ctMlS +bdsHyo+1W/CD80/HLaXIrcuVIKQxKFdYWuSNG5qrng0M8gozOSI5Cpcu81N3uURF +/YTLNiCBWS2ab21ISGHKTN9T0a9SvESfqy9rg3LvdYDaBjMbXcjaY8ZNzaxmMc3R +3j6HEDbhuaR672BQssvKplbgN6+rNBM5Jeg5ZuSYeqoSmJxZZoY+rfGwyj4GD3vw +EUs3oERte8uojHH01bWRNszwFcYr3lEXsZdMUD2xlVl8BX0tIdUAvwFnol57plzy +9yLxkA2T26pEUWbMfXYD62qoKjgZl3YNa4ph+bz27nb9cCvdKTz4Ch5bQhyLVi9V +GxyhLrXHFub4qjySjmm2AcG1hp2JDws4lFTo6tyePSW8Uybt1as5qsVATFSrsrTZ +2fjXctscvG29ZV/viDUqZi/u9rNl8DONfJhBaUYPQxxp+pu10GFqzcpL2UyQRqsV +WaFHVCkugyhfHMKiq3IXAAaOReyL4jM9f9oZRORicsPfIsbyVtTdX5Vy7W1f90gD +W/3FKqD2cyOEEBsB5wIDAQABo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/ +BAUwAwEB/zAdBgNVHQ4EFgQU43HgntinQtnbcZFrlJPrw6PRFKMwDQYJKoZIhvcN +AQELBQADggIBAEf63QqwEZE4rU1d9+UOl1QZgkiHVIyqZJnYWv6IAcVYpZmxI1Qj +t2odIFflAWJBF9MJ23XLblSQdf4an4EKwt3X9wnQW3IV5B4Jaj0z8yGa5hV+rVHV +DRDtfULAj+7AmgjVQdZcDiFpboBhDhXAuM/FSRJSzL46zNQuOAXeNf0fb7iAaJg9 +TaDKQGXSc3z1i9kKlT/YPyNtGtEqJBnZhbMX73huqVjRI9PHE+1yJX9dsXNw0H8G +lwmEKYBhHfpe/3OsoOOJuBxxFcbeMX8S3OFtm6/n6J91eEyrRjuazr8FGF1NFTwW +mhlQBJqymm9li1JfPFgEKCXAZmExfrngdbkaqIHWchezxQMxNRF4eKLg6TCMf4Df +WN88uieW4oA0beOY02QnrEh+KHdcxiVhJfiFDGX6xDIvpZgF5PgLZxYWxoK4Mhn5 ++bl53B/N66+rDt0b20XkeucC4pVd/GnwU2lhlXV5C15V5jgclKlZM57IcXR5f1GJ +tshquDDIajjDbp7hNxbqBWJMWxJH7ae0s1hWx0nzfxJoCTFx8G34Tkf71oXuxVhA +GaQdp/lLQzfcaFpPz+vCZHTetBXZ9FRUGi8c15dxVJCO2SCdUyt/q4/i6jC8UDfv +8Ue1fXwsBOxonbRJRBD0ckscZOf85muQ3Wl9af0AVqW3rLatt8o+Ae+c +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - G2 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2009 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - G2" +# Serial: 1246989352 +# MD5 Fingerprint: 4b:e2:c9:91:96:65:0c:f4:0e:5a:93:92:a0:0a:fe:b2 +# SHA1 Fingerprint: 8c:f4:27:fd:79:0c:3a:d1:66:06:8d:e8:1e:57:ef:bb:93:22:72:d4 +# SHA256 Fingerprint: 43:df:57:74:b0:3e:7f:ef:5f:e4:0d:93:1a:7b:ed:f1:bb:2e:6b:42:73:8c:4e:6d:38:41:10:3d:3a:a7:f3:39 +-----BEGIN CERTIFICATE----- +MIIEPjCCAyagAwIBAgIESlOMKDANBgkqhkiG9w0BAQsFADCBvjELMAkGA1UEBhMC +VVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50 +cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3Qs +IEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVz +dCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRzIwHhcNMDkwNzA3MTcy +NTU0WhcNMzAxMjA3MTc1NTU0WjCBvjELMAkGA1UEBhMCVVMxFjAUBgNVBAoTDUVu +dHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3d3cuZW50cnVzdC5uZXQvbGVnYWwt +dGVybXMxOTA3BgNVBAsTMChjKSAyMDA5IEVudHJ1c3QsIEluYy4gLSBmb3IgYXV0 +aG9yaXplZCB1c2Ugb25seTEyMDAGA1UEAxMpRW50cnVzdCBSb290IENlcnRpZmlj +YXRpb24gQXV0aG9yaXR5IC0gRzIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQC6hLZy254Ma+KZ6TABp3bqMriVQRrJ2mFOWHLP/vaCeb9zYQYKpSfYs1/T +RU4cctZOMvJyig/3gxnQaoCAAEUesMfnmr8SVycco2gvCoe9amsOXmXzHHfV1IWN +cCG0szLni6LVhjkCsbjSR87kyUnEO6fe+1R9V77w6G7CebI6C1XiUJgWMhNcL3hW +wcKUs/Ja5CeanyTXxuzQmyWC48zCxEXFjJd6BmsqEZ+pCm5IO2/b1BEZQvePB7/1 +U1+cPvQXLOZprE4yTGJ36rfo5bs0vBmLrpxR57d+tVOxMyLlbc9wPBr64ptntoP0 +jaWvYkxN4FisZDQSA/i2jZRjJKRxAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBRqciZ60B7vfec7aVHUbI2fkBJmqzAN +BgkqhkiG9w0BAQsFAAOCAQEAeZ8dlsa2eT8ijYfThwMEYGprmi5ZiXMRrEPR9RP/ +jTkrwPK9T3CMqS/qF8QLVJ7UG5aYMzyorWKiAHarWWluBh1+xLlEjZivEtRh2woZ +Rkfz6/djwUAFQKXSt/S1mja/qYh2iARVBCuch38aNzx+LaUa2NSJXsq9rD1s2G2v +1fN2D807iDginWyTmsQ9v4IbZT+mD12q/OWyFcq1rca8PdCE6OoGcrBNOTJ4vz4R +nAuknZoh8/CbCzB428Hch0P+vGOaysXCHMnHjf87ElgI5rY97HosTvuDls4MPGmH +VHOkc8KT/1EQrBVUAdj8BbGJoX90g5pJ19xOe4pIb4tF9g== +-----END CERTIFICATE----- + +# Issuer: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Subject: CN=Entrust Root Certification Authority - EC1 O=Entrust, Inc. OU=See www.entrust.net/legal-terms/(c) 2012 Entrust, Inc. - for authorized use only +# Label: "Entrust Root Certification Authority - EC1" +# Serial: 51543124481930649114116133369 +# MD5 Fingerprint: b6:7e:1d:f0:58:c5:49:6c:24:3b:3d:ed:98:18:ed:bc +# SHA1 Fingerprint: 20:d8:06:40:df:9b:25:f5:12:25:3a:11:ea:f7:59:8a:eb:14:b5:47 +# SHA256 Fingerprint: 02:ed:0e:b2:8c:14:da:45:16:5c:56:67:91:70:0d:64:51:d7:fb:56:f0:b2:ab:1d:3b:8e:b0:70:e5:6e:df:f5 +-----BEGIN CERTIFICATE----- +MIIC+TCCAoCgAwIBAgINAKaLeSkAAAAAUNCR+TAKBggqhkjOPQQDAzCBvzELMAkG +A1UEBhMCVVMxFjAUBgNVBAoTDUVudHJ1c3QsIEluYy4xKDAmBgNVBAsTH1NlZSB3 +d3cuZW50cnVzdC5uZXQvbGVnYWwtdGVybXMxOTA3BgNVBAsTMChjKSAyMDEyIEVu +dHJ1c3QsIEluYy4gLSBmb3IgYXV0aG9yaXplZCB1c2Ugb25seTEzMDEGA1UEAxMq +RW50cnVzdCBSb290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IC0gRUMxMB4XDTEy +MTIxODE1MjUzNloXDTM3MTIxODE1NTUzNlowgb8xCzAJBgNVBAYTAlVTMRYwFAYD +VQQKEw1FbnRydXN0LCBJbmMuMSgwJgYDVQQLEx9TZWUgd3d3LmVudHJ1c3QubmV0 +L2xlZ2FsLXRlcm1zMTkwNwYDVQQLEzAoYykgMjAxMiBFbnRydXN0LCBJbmMuIC0g +Zm9yIGF1dGhvcml6ZWQgdXNlIG9ubHkxMzAxBgNVBAMTKkVudHJ1c3QgUm9vdCBD +ZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEVDMTB2MBAGByqGSM49AgEGBSuBBAAi +A2IABIQTydC6bUF74mzQ61VfZgIaJPRbiWlH47jCffHyAsWfoPZb1YsGGYZPUxBt +ByQnoaD41UcZYUx9ypMn6nQM72+WCf5j7HBdNq1nd67JnXxVRDqiY1Ef9eNi1KlH +Bz7MIKNCMEAwDgYDVR0PAQH/BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0O +BBYEFLdj5xrdjekIplWDpOBqUEFlEUJJMAoGCCqGSM49BAMDA2cAMGQCMGF52OVC +R98crlOZF7ZvHH3hvxGU0QOIdeSNiaSKd0bebWHvAvX7td/M/k7//qnmpwIwW5nX +hTcGtXsI/esni0qU+eH6p44mCOh8kmhtc9hvJqwhAriZtyZBWyVgrtBIGu4G +-----END CERTIFICATE----- + +# Issuer: CN=CFCA EV ROOT O=China Financial Certification Authority +# Subject: CN=CFCA EV ROOT O=China Financial Certification Authority +# Label: "CFCA EV ROOT" +# Serial: 407555286 +# MD5 Fingerprint: 74:e1:b6:ed:26:7a:7a:44:30:33:94:ab:7b:27:81:30 +# SHA1 Fingerprint: e2:b8:29:4b:55:84:ab:6b:58:c2:90:46:6c:ac:3f:b8:39:8f:84:83 +# SHA256 Fingerprint: 5c:c3:d7:8e:4e:1d:5e:45:54:7a:04:e6:87:3e:64:f9:0c:f9:53:6d:1c:cc:2e:f8:00:f3:55:c4:c5:fd:70:fd +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIEGErM1jANBgkqhkiG9w0BAQsFADBWMQswCQYDVQQGEwJD +TjEwMC4GA1UECgwnQ2hpbmEgRmluYW5jaWFsIENlcnRpZmljYXRpb24gQXV0aG9y +aXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJPT1QwHhcNMTIwODA4MDMwNzAxWhcNMjkx +MjMxMDMwNzAxWjBWMQswCQYDVQQGEwJDTjEwMC4GA1UECgwnQ2hpbmEgRmluYW5j +aWFsIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRUwEwYDVQQDDAxDRkNBIEVWIFJP +T1QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDXXWvNED8fBVnVBU03 +sQ7smCuOFR36k0sXgiFxEFLXUWRwFsJVaU2OFW2fvwwbwuCjZ9YMrM8irq93VCpL +TIpTUnrD7i7es3ElweldPe6hL6P3KjzJIx1qqx2hp/Hz7KDVRM8Vz3IvHWOX6Jn5 +/ZOkVIBMUtRSqy5J35DNuF++P96hyk0g1CXohClTt7GIH//62pCfCqktQT+x8Rgp +7hZZLDRJGqgG16iI0gNyejLi6mhNbiyWZXvKWfry4t3uMCz7zEasxGPrb382KzRz +EpR/38wmnvFyXVBlWY9ps4deMm/DGIq1lY+wejfeWkU7xzbh72fROdOXW3NiGUgt +hxwG+3SYIElz8AXSG7Ggo7cbcNOIabla1jj0Ytwli3i/+Oh+uFzJlU9fpy25IGvP +a931DfSCt/SyZi4QKPaXWnuWFo8BGS1sbn85WAZkgwGDg8NNkt0yxoekN+kWzqot +aK8KgWU6cMGbrU1tVMoqLUuFG7OA5nBFDWteNfB/O7ic5ARwiRIlk9oKmSJgamNg +TnYGmE69g60dWIolhdLHZR4tjsbftsbhf4oEIRUpdPA+nJCdDC7xij5aqgwJHsfV +PKPtl8MeNPo4+QgO48BdK4PRVmrJtqhUUy54Mmc9gn900PvhtgVguXDbjgv5E1hv +cWAQUhC5wUEJ73IfZzF4/5YFjQIDAQABo2MwYTAfBgNVHSMEGDAWgBTj/i39KNAL +tbq2osS/BqoFjJP7LzAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwIBBjAd +BgNVHQ4EFgQU4/4t/SjQC7W6tqLEvwaqBYyT+y8wDQYJKoZIhvcNAQELBQADggIB +ACXGumvrh8vegjmWPfBEp2uEcwPenStPuiB/vHiyz5ewG5zz13ku9Ui20vsXiObT +ej/tUxPQ4i9qecsAIyjmHjdXNYmEwnZPNDatZ8POQQaIxffu2Bq41gt/UP+TqhdL +jOztUmCypAbqTuv0axn96/Ua4CUqmtzHQTb3yHQFhDmVOdYLO6Qn+gjYXB74BGBS +ESgoA//vU2YApUo0FmZ8/Qmkrp5nGm9BC2sGE5uPhnEFtC+NiWYzKXZUmhH4J/qy +P5Hgzg0b8zAarb8iXRvTvyUFTeGSGn+ZnzxEk8rUQElsgIfXBDrDMlI1Dlb4pd19 +xIsNER9Tyx6yF7Zod1rg1MvIB671Oi6ON7fQAUtDKXeMOZePglr4UeWJoBjnaH9d +Ci77o0cOPaYjesYBx4/IXr9tgFa+iiS6M+qf4TIRnvHST4D2G0CvOJ4RUHlzEhLN +5mydLIhyPDCBBpEi6lmt2hkuIsKNuYyH4Ga8cyNfIWRjgEj1oDwYPZTISEEdQLpe +/v5WOaHIz16eGWRGENoXkbcFgKyLmZJ956LYBws2J+dIeWCKw9cTXPhyQN9Ky8+Z +AAoACxGV2lZFA4gKn2fQ1XmxqI1AbQ3CekD6819kR5LLU7m7Wc5P/dAVUwHY3+vZ +5nbv0CO7O6l5s9UCKc2Jo5YPSjXnTkLAdc0Hz+Ys63su +-----END CERTIFICATE----- + +# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H5 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H5 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Label: "TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H5" +# Serial: 156233699172481 +# MD5 Fingerprint: da:70:8e:f0:22:df:93:26:f6:5f:9f:d3:15:06:52:4e +# SHA1 Fingerprint: c4:18:f6:4d:46:d1:df:00:3d:27:30:13:72:43:a9:12:11:c6:75:fb +# SHA256 Fingerprint: 49:35:1b:90:34:44:c1:85:cc:dc:5c:69:3d:24:d8:55:5c:b2:08:d6:a8:14:13:07:69:9f:4a:f0:63:19:9d:78 +-----BEGIN CERTIFICATE----- +MIIEJzCCAw+gAwIBAgIHAI4X/iQggTANBgkqhkiG9w0BAQsFADCBsTELMAkGA1UE +BhMCVFIxDzANBgNVBAcMBkFua2FyYTFNMEsGA1UECgxEVMOcUktUUlVTVCBCaWxn +aSDEsGxldGnFn2ltIHZlIEJpbGnFn2ltIEfDvHZlbmxpxJ9pIEhpem1ldGxlcmkg +QS7Fni4xQjBABgNVBAMMOVTDnFJLVFJVU1QgRWxla3Ryb25payBTZXJ0aWZpa2Eg +SGl6bWV0IFNhxJ9sYXnEsWPEsXPEsSBINTAeFw0xMzA0MzAwODA3MDFaFw0yMzA0 +MjgwODA3MDFaMIGxMQswCQYDVQQGEwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYD +VQQKDERUw5xSS1RSVVNUIEJpbGdpIMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8 +dmVubGnEn2kgSGl6bWV0bGVyaSBBLsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBF +bGVrdHJvbmlrIFNlcnRpZmlrYSBIaXptZXQgU2HEn2xhecSxY8Sxc8SxIEg1MIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApCUZ4WWe60ghUEoI5RHwWrom +/4NZzkQqL/7hzmAD/I0Dpe3/a6i6zDQGn1k19uwsu537jVJp45wnEFPzpALFp/kR +Gml1bsMdi9GYjZOHp3GXDSHHmflS0yxjXVW86B8BSLlg/kJK9siArs1mep5Fimh3 +4khon6La8eHBEJ/rPCmBp+EyCNSgBbGM+42WAA4+Jd9ThiI7/PS98wl+d+yG6w8z +5UNP9FR1bSmZLmZaQ9/LXMrI5Tjxfjs1nQ/0xVqhzPMggCTTV+wVunUlm+hkS7M0 +hO8EuPbJbKoCPrZV4jI3X/xml1/N1p7HIL9Nxqw/dV8c7TKcfGkAaZHjIxhT6QID +AQABo0IwQDAdBgNVHQ4EFgQUVpkHHtOsDGlktAxQR95DLL4gwPswDgYDVR0PAQH/ +BAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEBAJ5FdnsX +SDLyOIspve6WSk6BGLFRRyDN0GSxDsnZAdkJzsiZ3GglE9Rc8qPoBP5yCccLqh0l +VX6Wmle3usURehnmp349hQ71+S4pL+f5bFgWV1Al9j4uPqrtd3GqqpmWRgqujuwq +URawXs3qZwQcWDD1YIq9pr1N5Za0/EKJAWv2cMhQOQwt1WbZyNKzMrcbGW3LM/nf +peYVhDfwwvJllpKQd/Ct9JDpEXjXk4nAPQu6KfTomZ1yju2dL+6SfaHx/126M2CF +Yv4HAqGEVka+lgqaE9chTLd8B59OTj+RdPsnnRHM3eaxynFNExc5JsUpISuTKWqW ++qtB4Uu2NQvAmxU= +-----END CERTIFICATE----- + +# Issuer: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H6 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Subject: CN=TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H6 O=TÜRKTRUST Bilgi İletişim ve Bilişim Güvenliği Hizmetleri A.Ş. +# Label: "TÜRKTRUST Elektronik Sertifika Hizmet Sağlayıcısı H6" +# Serial: 138134509972618 +# MD5 Fingerprint: f8:c5:ee:2a:6b:be:95:8d:08:f7:25:4a:ea:71:3e:46 +# SHA1 Fingerprint: 8a:5c:8c:ee:a5:03:e6:05:56:ba:d8:1b:d4:f6:c9:b0:ed:e5:2f:e0 +# SHA256 Fingerprint: 8d:e7:86:55:e1:be:7f:78:47:80:0b:93:f6:94:d2:1d:36:8c:c0:6e:03:3e:7f:ab:04:bb:5e:b9:9d:a6:b7:00 +-----BEGIN CERTIFICATE----- +MIIEJjCCAw6gAwIBAgIGfaHyZeyKMA0GCSqGSIb3DQEBCwUAMIGxMQswCQYDVQQG +EwJUUjEPMA0GA1UEBwwGQW5rYXJhMU0wSwYDVQQKDERUw5xSS1RSVVNUIEJpbGdp +IMSwbGV0acWfaW0gdmUgQmlsacWfaW0gR8O8dmVubGnEn2kgSGl6bWV0bGVyaSBB +LsWeLjFCMEAGA1UEAww5VMOcUktUUlVTVCBFbGVrdHJvbmlrIFNlcnRpZmlrYSBI +aXptZXQgU2HEn2xhecSxY8Sxc8SxIEg2MB4XDTEzMTIxODA5MDQxMFoXDTIzMTIx +NjA5MDQxMFowgbExCzAJBgNVBAYTAlRSMQ8wDQYDVQQHDAZBbmthcmExTTBLBgNV +BAoMRFTDnFJLVFJVU1QgQmlsZ2kgxLBsZXRpxZ9pbSB2ZSBCaWxpxZ9pbSBHw7x2 +ZW5sacSfaSBIaXptZXRsZXJpIEEuxZ4uMUIwQAYDVQQDDDlUw5xSS1RSVVNUIEVs +ZWt0cm9uaWsgU2VydGlmaWthIEhpem1ldCBTYcSfbGF5xLFjxLFzxLEgSDYwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdsGjW6L0UlqMACprx9MfMkU1x +eHe59yEmFXNRFpQJRwXiM/VomjX/3EsvMsew7eKC5W/a2uqsxgbPJQ1BgfbBOCK9 ++bGlprMBvD9QFyv26WZV1DOzXPhDIHiTVRZwGTLmiddk671IUP320EEDwnS3/faA +z1vFq6TWlRKb55cTMgPp1KtDWxbtMyJkKbbSk60vbNg9tvYdDjTu0n2pVQ8g9P0p +u5FbHH3GQjhtQiht1AH7zYiXSX6484P4tZgvsycLSF5W506jM7NE1qXyGJTtHB6p +lVxiSvgNZ1GpryHV+DKdeboaX+UEVU0TRv/yz3THGmNtwx8XEsMeED5gCLMxAgMB +AAGjQjBAMB0GA1UdDgQWBBTdVRcT9qzoSCHK77Wv0QAy7Z6MtTAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAb1gNl0Oq +FlQ+v6nfkkU/hQu7VtMMUszIv3ZnXuaqs6fvuay0EBQNdH49ba3RfdCaqaXKGDsC +QC4qnFAUi/5XfldcEQlLNkVS9z2sFP1E34uXI9TDwe7UU5X+LEr+DXCqu4svLcsy +o4LyVN/Y8t3XSHLuSqMplsNEzm61kod2pLv0kmzOLBQJZo6NrRa1xxsJYTvjIKID +gI6tflEATseWhvtDmHd9KMeP2Cpu54Rvl0EpABZeTeIT6lnAY2c6RPuY/ATTMHKm +9ocJV612ph1jmv3XZch4gyt1O6VbuA1df74jrlZVlFjvH4GMKrLN5ptjnhi85WsG +tAuYSyher4hYyw== +-----END CERTIFICATE----- + +# Issuer: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 +# Subject: CN=Certinomis - Root CA O=Certinomis OU=0002 433998903 +# Label: "Certinomis - Root CA" +# Serial: 1 +# MD5 Fingerprint: 14:0a:fd:8d:a8:28:b5:38:69:db:56:7e:61:22:03:3f +# SHA1 Fingerprint: 9d:70:bb:01:a5:a4:a0:18:11:2e:f7:1c:01:b9:32:c5:34:e7:88:a8 +# SHA256 Fingerprint: 2a:99:f5:bc:11:74:b7:3c:bb:1d:62:08:84:e0:1c:34:e5:1c:cb:39:78:da:12:5f:0e:33:26:88:83:bf:41:58 +-----BEGIN CERTIFICATE----- +MIIFkjCCA3qgAwIBAgIBATANBgkqhkiG9w0BAQsFADBaMQswCQYDVQQGEwJGUjET +MBEGA1UEChMKQ2VydGlub21pczEXMBUGA1UECxMOMDAwMiA0MzM5OTg5MDMxHTAb +BgNVBAMTFENlcnRpbm9taXMgLSBSb290IENBMB4XDTEzMTAyMTA5MTcxOFoXDTMz +MTAyMTA5MTcxOFowWjELMAkGA1UEBhMCRlIxEzARBgNVBAoTCkNlcnRpbm9taXMx +FzAVBgNVBAsTDjAwMDIgNDMzOTk4OTAzMR0wGwYDVQQDExRDZXJ0aW5vbWlzIC0g +Um9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBANTMCQosP5L2 +fxSeC5yaah1AMGT9qt8OHgZbn1CF6s2Nq0Nn3rD6foCWnoR4kkjW4znuzuRZWJfl +LieY6pOod5tK8O90gC3rMB+12ceAnGInkYjwSond3IjmFPnVAy//ldu9n+ws+hQV +WZUKxkd8aRi5pwP5ynapz8dvtF4F/u7BUrJ1Mofs7SlmO/NKFoL21prbcpjp3vDF +TKWrteoB4owuZH9kb/2jJZOLyKIOSY008B/sWEUuNKqEUL3nskoTuLAPrjhdsKkb +5nPJWqHZZkCqqU2mNAKthH6yI8H7KsZn9DS2sJVqM09xRLWtwHkziOC/7aOgFLSc +CbAK42C++PhmiM1b8XcF4LVzbsF9Ri6OSyemzTUK/eVNfaoqoynHWmgE6OXWk6Ri +wsXm9E/G+Z8ajYJJGYrKWUM66A0ywfRMEwNvbqY/kXPLynNvEiCL7sCCeN5LLsJJ +wx3tFvYk9CcbXFcx3FXuqB5vbKziRcxXV4p1VxngtViZSTYxPDMBbRZKzbgqg4SG +m/lg0h9tkQPTYKbVPZrdd5A9NaSfD171UkRpucC63M9933zZxKyGIjK8e2uR73r4 +F2iw4lNVYC2vPsKD2NkJK/DAZNuHi5HMkesE/Xa0lZrmFAYb1TQdvtj/dBxThZng +WVJKYe2InmtJiUZ+IFrZ50rlau7SZRFDAgMBAAGjYzBhMA4GA1UdDwEB/wQEAwIB +BjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvkUz1pcMw6C8I6tNxIqSSaHh0 +2TAfBgNVHSMEGDAWgBTvkUz1pcMw6C8I6tNxIqSSaHh02TANBgkqhkiG9w0BAQsF +AAOCAgEAfj1U2iJdGlg+O1QnurrMyOMaauo++RLrVl89UM7g6kgmJs95Vn6RHJk/ +0KGRHCwPT5iVWVO90CLYiF2cN/z7ZMF4jIuaYAnq1fohX9B0ZedQxb8uuQsLrbWw +F6YSjNRieOpWauwK0kDDPAUwPk2Ut59KA9N9J0u2/kTO+hkzGm2kQtHdzMjI1xZS +g081lLMSVX3l4kLr5JyTCcBMWwerx20RoFAXlCOotQqSD7J6wWAsOMwaplv/8gzj +qh8c3LigkyfeY+N/IZ865Z764BNqdeuWXGKRlI5nU7aJ+BIJy29SWwNyhlCVCNSN +h4YVH5Uk2KRvms6knZtt0rJ2BobGVgjF6wnaNsIbW0G+YSrjcOa4pvi2WsS9Iff/ +ql+hbHY5ZtbqTFXhADObE5hjyW/QASAJN1LnDE8+zbz1X5YnpyACleAu6AdBBR8V +btaw5BngDwKTACdyxYvRVB9dSsNAl35VpnzBMwQUAR1JIGkLGZOdblgi90AMRgwj +Y/M50n92Uaf0yKHxDHYiI0ZSKS3io0EHVmmY0gUJvGnHWmHNj4FgFU2A3ZDifcRQ +8ow7bkrHxuaAKzyBvBGAFhAn1/DNP3nMcyrDflOR1m749fPH0FFNjkulW+YZFzvW +gQncItzujrnEj1PhZ7szuIgVRs/taTX/dQ1G885x4cVrhkIGuUE= +-----END CERTIFICATE----- +# Issuer: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Subject: CN=Entrust.net Secure Server Certification Authority O=Entrust.net OU=www.entrust.net/CPS incorp. by ref. (limits liab.)/(c) 1999 Entrust.net Limited +# Label: "Entrust.net Secure Server CA" +# Serial: 927650371 +# MD5 Fingerprint: df:f2:80:73:cc:f1:e6:61:73:fc:f5:42:e9:c5:7c:ee +# SHA1 Fingerprint: 99:a6:9b:e6:1a:fe:88:6b:4d:2b:82:00:7c:b8:54:fc:31:7e:15:39 +# SHA256 Fingerprint: 62:f2:40:27:8c:56:4c:4d:d8:bf:7d:9d:4f:6f:36:6e:a8:94:d2:2f:5f:34:d9:89:a9:83:ac:ec:2f:ff:ed:50 +-----BEGIN CERTIFICATE----- +MIIE2DCCBEGgAwIBAgIEN0rSQzANBgkqhkiG9w0BAQUFADCBwzELMAkGA1UEBhMC +VVMxFDASBgNVBAoTC0VudHJ1c3QubmV0MTswOQYDVQQLEzJ3d3cuZW50cnVzdC5u +ZXQvQ1BTIGluY29ycC4gYnkgcmVmLiAobGltaXRzIGxpYWIuKTElMCMGA1UECxMc +KGMpIDE5OTkgRW50cnVzdC5uZXQgTGltaXRlZDE6MDgGA1UEAxMxRW50cnVzdC5u +ZXQgU2VjdXJlIFNlcnZlciBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTAeFw05OTA1 +MjUxNjA5NDBaFw0xOTA1MjUxNjM5NDBaMIHDMQswCQYDVQQGEwJVUzEUMBIGA1UE +ChMLRW50cnVzdC5uZXQxOzA5BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5j +b3JwLiBieSByZWYuIChsaW1pdHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBF +bnRydXN0Lm5ldCBMaW1pdGVkMTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUg +U2VydmVyIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGdMA0GCSqGSIb3DQEBAQUA +A4GLADCBhwKBgQDNKIM0VBuJ8w+vN5Ex/68xYMmo6LIQaO2f55M28Qpku0f1BBc/ +I0dNxScZgSYMVHINiC3ZH5oSn7yzcdOAGT9HZnuMNSjSuQrfJNqc1lB5gXpa0zf3 +wkrYKZImZNHkmGw6AIr1NJtl+O3jEP/9uElY3KDegjlrgbEWGWG5VLbmQwIBA6OC +AdcwggHTMBEGCWCGSAGG+EIBAQQEAwIABzCCARkGA1UdHwSCARAwggEMMIHeoIHb +oIHYpIHVMIHSMQswCQYDVQQGEwJVUzEUMBIGA1UEChMLRW50cnVzdC5uZXQxOzA5 +BgNVBAsTMnd3dy5lbnRydXN0Lm5ldC9DUFMgaW5jb3JwLiBieSByZWYuIChsaW1p +dHMgbGlhYi4pMSUwIwYDVQQLExwoYykgMTk5OSBFbnRydXN0Lm5ldCBMaW1pdGVk +MTowOAYDVQQDEzFFbnRydXN0Lm5ldCBTZWN1cmUgU2VydmVyIENlcnRpZmljYXRp +b24gQXV0aG9yaXR5MQ0wCwYDVQQDEwRDUkwxMCmgJ6AlhiNodHRwOi8vd3d3LmVu +dHJ1c3QubmV0L0NSTC9uZXQxLmNybDArBgNVHRAEJDAigA8xOTk5MDUyNTE2MDk0 +MFqBDzIwMTkwNTI1MTYwOTQwWjALBgNVHQ8EBAMCAQYwHwYDVR0jBBgwFoAU8Bdi +E1U9s/8KAGv7UISX8+1i0BowHQYDVR0OBBYEFPAXYhNVPbP/CgBr+1CEl/PtYtAa +MAwGA1UdEwQFMAMBAf8wGQYJKoZIhvZ9B0EABAwwChsEVjQuMAMCBJAwDQYJKoZI +hvcNAQEFBQADgYEAkNwwAvpkdMKnCqV8IY00F6j7Rw7/JXyNEwr75Ji174z4xRAN +95K+8cPV1ZVqBLssziY2ZcgxxufuP+NXdYR6Ee9GTxj005i7qIcyunL2POI9n9cd +2cNgQ4xYDiKWL2KjLB+6rQXvqzJ4h6BUcxm1XAX5Uj5tLUUL9wqT6u0G+bI= +-----END CERTIFICATE----- + +# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority +# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 2 Policy Validation Authority +# Label: "ValiCert Class 2 VA" +# Serial: 1 +# MD5 Fingerprint: a9:23:75:9b:ba:49:36:6e:31:c2:db:f2:e7:66:ba:87 +# SHA1 Fingerprint: 31:7a:2a:d0:7f:2b:33:5e:f5:a1:c3:4e:4b:57:e8:b7:d8:f1:fc:a6 +# SHA256 Fingerprint: 58:d0:17:27:9c:d4:dc:63:ab:dd:b1:96:a6:c9:90:6c:30:c4:e0:87:83:ea:e8:c1:60:99:54:d6:93:55:59:6b +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMTk1NFoXDTE5MDYy +NjAwMTk1NFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDIgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDOOnHK5avIWZJV16vY +dA757tn2VUdZZUcOBVXc65g2PFxTXdMwzzjsvUGJ7SVCCSRrCl6zfN1SLUzm1NZ9 +WlmpZdRJEy0kTRxQb7XBhVQ7/nHk01xC+YDgkRoKWzk2Z/M/VXwbP7RfZHM047QS +v4dk+NoS/zcnwbNDu+97bi5p9wIDAQABMA0GCSqGSIb3DQEBBQUAA4GBADt/UG9v +UJSZSWI4OB9L+KXIPqeCgfYrx+jFzug6EILLGACOTb2oWH+heQC1u+mNr0HZDzTu +IYEZoDJJKPTEjlbVUjP9UNV+mWwD5MlM/Mtsq2azSiGM5bUMMj4QssxsodyamEwC +W/POuZ6lcg5Ktz885hZo+L7tdEy8W9ViH0Pd +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok +# Subject: CN=NetLock Expressz (Class C) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok +# Label: "NetLock Express (Class C) Root" +# Serial: 104 +# MD5 Fingerprint: 4f:eb:f1:f0:70:c2:80:63:5d:58:9f:da:12:3c:a9:c4 +# SHA1 Fingerprint: e3:92:51:2f:0a:cf:f5:05:df:f6:de:06:7f:75:37:e1:65:ea:57:4b +# SHA256 Fingerprint: 0b:5e:ed:4e:84:64:03:cf:55:e0:65:84:84:40:ed:2a:82:75:8b:f5:b9:aa:1f:25:3d:46:13:cf:a0:80:ff:3f +-----BEGIN CERTIFICATE----- +MIIFTzCCBLigAwIBAgIBaDANBgkqhkiG9w0BAQQFADCBmzELMAkGA1UEBhMCSFUx +ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 +b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTQwMgYDVQQD +EytOZXRMb2NrIEV4cHJlc3N6IChDbGFzcyBDKSBUYW51c2l0dmFueWtpYWRvMB4X +DTk5MDIyNTE0MDgxMVoXDTE5MDIyMDE0MDgxMVowgZsxCzAJBgNVBAYTAkhVMREw +DwYDVQQHEwhCdWRhcGVzdDEnMCUGA1UEChMeTmV0TG9jayBIYWxvemF0Yml6dG9u +c2FnaSBLZnQuMRowGAYDVQQLExFUYW51c2l0dmFueWtpYWRvazE0MDIGA1UEAxMr +TmV0TG9jayBFeHByZXNzeiAoQ2xhc3MgQykgVGFudXNpdHZhbnlraWFkbzCBnzAN +BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA6+ywbGGKIyWvYCDj2Z/8kwvbXY2wobNA +OoLO/XXgeDIDhlqGlZHtU/qdQPzm6N3ZW3oDvV3zOwzDUXmbrVWg6dADEK8KuhRC +2VImESLH0iDMgqSaqf64gXadarfSNnU+sYYJ9m5tfk63euyucYT2BDMIJTLrdKwW +RMbkQJMdf60CAwEAAaOCAp8wggKbMBIGA1UdEwEB/wQIMAYBAf8CAQQwDgYDVR0P +AQH/BAQDAgAGMBEGCWCGSAGG+EIBAQQEAwIABzCCAmAGCWCGSAGG+EIBDQSCAlEW +ggJNRklHWUVMRU0hIEV6ZW4gdGFudXNpdHZhbnkgYSBOZXRMb2NrIEtmdC4gQWx0 +YWxhbm9zIFN6b2xnYWx0YXRhc2kgRmVsdGV0ZWxlaWJlbiBsZWlydCBlbGphcmFz +b2sgYWxhcGphbiBrZXN6dWx0LiBBIGhpdGVsZXNpdGVzIGZvbHlhbWF0YXQgYSBO +ZXRMb2NrIEtmdC4gdGVybWVrZmVsZWxvc3NlZy1iaXp0b3NpdGFzYSB2ZWRpLiBB +IGRpZ2l0YWxpcyBhbGFpcmFzIGVsZm9nYWRhc2FuYWsgZmVsdGV0ZWxlIGF6IGVs +b2lydCBlbGxlbm9yemVzaSBlbGphcmFzIG1lZ3RldGVsZS4gQXogZWxqYXJhcyBs +ZWlyYXNhIG1lZ3RhbGFsaGF0byBhIE5ldExvY2sgS2Z0LiBJbnRlcm5ldCBob25s +YXBqYW4gYSBodHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIGNpbWVuIHZhZ3kg +a2VyaGV0byBheiBlbGxlbm9yemVzQG5ldGxvY2submV0IGUtbWFpbCBjaW1lbi4g +SU1QT1JUQU5UISBUaGUgaXNzdWFuY2UgYW5kIHRoZSB1c2Ugb2YgdGhpcyBjZXJ0 +aWZpY2F0ZSBpcyBzdWJqZWN0IHRvIHRoZSBOZXRMb2NrIENQUyBhdmFpbGFibGUg +YXQgaHR0cHM6Ly93d3cubmV0bG9jay5uZXQvZG9jcyBvciBieSBlLW1haWwgYXQg +Y3BzQG5ldGxvY2submV0LjANBgkqhkiG9w0BAQQFAAOBgQAQrX/XDDKACtiG8XmY +ta3UzbM2xJZIwVzNmtkFLp++UOv0JhQQLdRmF/iewSf98e3ke0ugbLWrmldwpu2g +pO0u9f38vf5NNwgMvOOWgyL1SRt/Syu0VMGAfJlOHdCM7tCs5ZL6dVb+ZKATj7i4 +Fp1hBWeAyNDYpQcCNJgEjTME1A== +-----END CERTIFICATE----- + +# Issuer: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok +# Subject: CN=NetLock Uzleti (Class B) Tanusitvanykiado O=NetLock Halozatbiztonsagi Kft. OU=Tanusitvanykiadok +# Label: "NetLock Business (Class B) Root" +# Serial: 105 +# MD5 Fingerprint: 39:16:aa:b9:6a:41:e1:14:69:df:9e:6c:3b:72:dc:b6 +# SHA1 Fingerprint: 87:9f:4b:ee:05:df:98:58:3b:e3:60:d6:33:e7:0d:3f:fe:98:71:af +# SHA256 Fingerprint: 39:df:7b:68:2b:7b:93:8f:84:71:54:81:cc:de:8d:60:d8:f2:2e:c5:98:87:7d:0a:aa:c1:2b:59:18:2b:03:12 +-----BEGIN CERTIFICATE----- +MIIFSzCCBLSgAwIBAgIBaTANBgkqhkiG9w0BAQQFADCBmTELMAkGA1UEBhMCSFUx +ETAPBgNVBAcTCEJ1ZGFwZXN0MScwJQYDVQQKEx5OZXRMb2NrIEhhbG96YXRiaXp0 +b25zYWdpIEtmdC4xGjAYBgNVBAsTEVRhbnVzaXR2YW55a2lhZG9rMTIwMAYDVQQD +EylOZXRMb2NrIFV6bGV0aSAoQ2xhc3MgQikgVGFudXNpdHZhbnlraWFkbzAeFw05 +OTAyMjUxNDEwMjJaFw0xOTAyMjAxNDEwMjJaMIGZMQswCQYDVQQGEwJIVTERMA8G +A1UEBxMIQnVkYXBlc3QxJzAlBgNVBAoTHk5ldExvY2sgSGFsb3phdGJpenRvbnNh +Z2kgS2Z0LjEaMBgGA1UECxMRVGFudXNpdHZhbnlraWFkb2sxMjAwBgNVBAMTKU5l +dExvY2sgVXpsZXRpIChDbGFzcyBCKSBUYW51c2l0dmFueWtpYWRvMIGfMA0GCSqG +SIb3DQEBAQUAA4GNADCBiQKBgQCx6gTsIKAjwo84YM/HRrPVG/77uZmeBNwcf4xK +gZjupNTKihe5In+DCnVMm8Bp2GQ5o+2So/1bXHQawEfKOml2mrriRBf8TKPV/riX +iK+IA4kfpPIEPsgHC+b5sy96YhQJRhTKZPWLgLViqNhr1nGTLbO/CVRY7QbrqHvc +Q7GhaQIDAQABo4ICnzCCApswEgYDVR0TAQH/BAgwBgEB/wIBBDAOBgNVHQ8BAf8E +BAMCAAYwEQYJYIZIAYb4QgEBBAQDAgAHMIICYAYJYIZIAYb4QgENBIICURaCAk1G +SUdZRUxFTSEgRXplbiB0YW51c2l0dmFueSBhIE5ldExvY2sgS2Z0LiBBbHRhbGFu +b3MgU3pvbGdhbHRhdGFzaSBGZWx0ZXRlbGVpYmVuIGxlaXJ0IGVsamFyYXNvayBh +bGFwamFuIGtlc3p1bHQuIEEgaGl0ZWxlc2l0ZXMgZm9seWFtYXRhdCBhIE5ldExv +Y2sgS2Z0LiB0ZXJtZWtmZWxlbG9zc2VnLWJpenRvc2l0YXNhIHZlZGkuIEEgZGln +aXRhbGlzIGFsYWlyYXMgZWxmb2dhZGFzYW5hayBmZWx0ZXRlbGUgYXogZWxvaXJ0 +IGVsbGVub3J6ZXNpIGVsamFyYXMgbWVndGV0ZWxlLiBBeiBlbGphcmFzIGxlaXJh +c2EgbWVndGFsYWxoYXRvIGEgTmV0TG9jayBLZnQuIEludGVybmV0IGhvbmxhcGph +biBhIGh0dHBzOi8vd3d3Lm5ldGxvY2submV0L2RvY3MgY2ltZW4gdmFneSBrZXJo +ZXRvIGF6IGVsbGVub3J6ZXNAbmV0bG9jay5uZXQgZS1tYWlsIGNpbWVuLiBJTVBP +UlRBTlQhIFRoZSBpc3N1YW5jZSBhbmQgdGhlIHVzZSBvZiB0aGlzIGNlcnRpZmlj +YXRlIGlzIHN1YmplY3QgdG8gdGhlIE5ldExvY2sgQ1BTIGF2YWlsYWJsZSBhdCBo +dHRwczovL3d3dy5uZXRsb2NrLm5ldC9kb2NzIG9yIGJ5IGUtbWFpbCBhdCBjcHNA +bmV0bG9jay5uZXQuMA0GCSqGSIb3DQEBBAUAA4GBAATbrowXr/gOkDFOzT4JwG06 +sPgzTEdM43WIEJessDgVkcYplswhwG08pXTP2IKlOcNl40JwuyKQ433bNXbhoLXa +n3BukxowOR0w2y7jfLKRstE3Kfq51hdcR0/jHTjrn9V7lagonhVK0dHQKwCXoOKS +NitjrFgBazMpUIaD8QFI +-----END CERTIFICATE----- + +# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority +# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 3 Policy Validation Authority +# Label: "RSA Root Certificate 1" +# Serial: 1 +# MD5 Fingerprint: a2:6f:53:b7:ee:40:db:4a:68:e7:fa:18:d9:10:4b:72 +# SHA1 Fingerprint: 69:bd:8c:f4:9c:d3:00:fb:59:2e:17:93:ca:55:6a:f3:ec:aa:35:fb +# SHA256 Fingerprint: bc:23:f9:8a:31:3c:b9:2d:e3:bb:fc:3a:5a:9f:44:61:ac:39:49:4c:4a:e1:5a:9e:9d:f1:31:e9:9b:73:01:9a +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNjAwMjIzM1oXDTE5MDYy +NjAwMjIzM1owgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDMgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjmFGWHOjVsQaBalfD +cnWTq8+epvzzFlLWLU2fNUSoLgRNB0mKOCn1dzfnt6td3zZxFJmP3MKS8edgkpfs +2Ejcv8ECIMYkpChMMFp2bbFc893enhBxoYjHW5tBbcqwuI4V7q0zK89HBFx1cQqY +JJgpp0lZpd34t0NiYfPT4tBVPwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFa7AliE +Zwgs3x/be0kz9dNnnfS0ChCzycUs4pJqcXgn8nCDQtM+z6lU9PHYkhaM0QTLS6vJ +n0WuPIqpsHEzXcjFV9+vqDWzf4mH6eglkrh/hXqu1rweN1gqZ8mRzyqBPu3GOd/A +PhmcGcwTTYJBtYze4D1gCCAPRX5ron+jjBXu +-----END CERTIFICATE----- + +# Issuer: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority +# Subject: CN=http://www.valicert.com/ O=ValiCert, Inc. OU=ValiCert Class 1 Policy Validation Authority +# Label: "ValiCert Class 1 VA" +# Serial: 1 +# MD5 Fingerprint: 65:58:ab:15:ad:57:6c:1e:a8:a7:b5:69:ac:bf:ff:eb +# SHA1 Fingerprint: e5:df:74:3c:b6:01:c4:9b:98:43:dc:ab:8c:e8:6a:81:10:9f:e4:8e +# SHA256 Fingerprint: f4:c1:49:55:1a:30:13:a3:5b:c7:bf:fe:17:a7:f3:44:9b:c1:ab:5b:5a:0a:e7:4b:06:c2:3b:90:00:4c:01:04 +-----BEGIN CERTIFICATE----- +MIIC5zCCAlACAQEwDQYJKoZIhvcNAQEFBQAwgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0 +IFZhbGlkYXRpb24gTmV0d29yazEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAz +BgNVBAsTLFZhbGlDZXJ0IENsYXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9y +aXR5MSEwHwYDVQQDExhodHRwOi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG +9w0BCQEWEWluZm9AdmFsaWNlcnQuY29tMB4XDTk5MDYyNTIyMjM0OFoXDTE5MDYy +NTIyMjM0OFowgbsxJDAiBgNVBAcTG1ZhbGlDZXJ0IFZhbGlkYXRpb24gTmV0d29y +azEXMBUGA1UEChMOVmFsaUNlcnQsIEluYy4xNTAzBgNVBAsTLFZhbGlDZXJ0IENs +YXNzIDEgUG9saWN5IFZhbGlkYXRpb24gQXV0aG9yaXR5MSEwHwYDVQQDExhodHRw +Oi8vd3d3LnZhbGljZXJ0LmNvbS8xIDAeBgkqhkiG9w0BCQEWEWluZm9AdmFsaWNl +cnQuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYWYJ6ibiWuqYvaG9Y +LqdUHAZu9OqNSLwxlBfw8068srg1knaw0KWlAdcAAxIiGQj4/xEjm84H9b9pGib+ +TunRf50sQB1ZaG6m+FiwnRqP0z/x3BkGgagO4DrdyFNFCQbmD3DD+kCmDuJWBQ8Y +TfwggtFzVXSNdnKgHZ0dwN0/cQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAFBoPUn0 +LBwGlN+VYH+Wexf+T3GtZMjdd9LvWVXoP+iOBSoh8gfStadS/pyxtuJbdxdA6nLW +I8sogTLDAHkY7FkXicnGah5xyf23dKUlRWnFSKsZ4UWKJWsZ7uW7EvV/96aNUcPw +nXS3qT6gpf+2SQMT2iLM7XGCK5nPOrf1LXLI +-----END CERTIFICATE----- + +# Issuer: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. +# Subject: CN=Equifax Secure eBusiness CA-1 O=Equifax Secure Inc. +# Label: "Equifax Secure eBusiness CA 1" +# Serial: 4 +# MD5 Fingerprint: 64:9c:ef:2e:44:fc:c6:8f:52:07:d0:51:73:8f:cb:3d +# SHA1 Fingerprint: da:40:18:8b:91:89:a3:ed:ee:ae:da:97:fe:2f:9d:f5:b7:d1:8a:41 +# SHA256 Fingerprint: cf:56:ff:46:a4:a1:86:10:9d:d9:65:84:b5:ee:b5:8a:51:0c:42:75:b0:e5:f9:4f:40:bb:ae:86:5e:19:f6:73 +-----BEGIN CERTIFICATE----- +MIICgjCCAeugAwIBAgIBBDANBgkqhkiG9w0BAQQFADBTMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEmMCQGA1UEAxMdRXF1aWZheCBT +ZWN1cmUgZUJ1c2luZXNzIENBLTEwHhcNOTkwNjIxMDQwMDAwWhcNMjAwNjIxMDQw +MDAwWjBTMQswCQYDVQQGEwJVUzEcMBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5j +LjEmMCQGA1UEAxMdRXF1aWZheCBTZWN1cmUgZUJ1c2luZXNzIENBLTEwgZ8wDQYJ +KoZIhvcNAQEBBQADgY0AMIGJAoGBAM4vGbwXt3fek6lfWg0XTzQaDJj0ItlZ1MRo +RvC0NcWFAyDGr0WlIVFFQesWWDYyb+JQYmT5/VGcqiTZ9J2DKocKIdMSODRsjQBu +WqDZQu4aIZX5UkxVWsUPOE9G+m34LjXWHXzr4vCwdYDIqROsvojvOm6rXyo4YgKw +Env+j6YDAgMBAAGjZjBkMBEGCWCGSAGG+EIBAQQEAwIABzAPBgNVHRMBAf8EBTAD +AQH/MB8GA1UdIwQYMBaAFEp4MlIR21kWNl7fwRQ2QGpHfEyhMB0GA1UdDgQWBBRK +eDJSEdtZFjZe38EUNkBqR3xMoTANBgkqhkiG9w0BAQQFAAOBgQB1W6ibAxHm6VZM +zfmpTMANmvPMZWnmJXbMWbfWVMMdzZmsGd20hdXgPfxiIKeES1hl8eL5lSE/9dR+ +WB5Hh1Q+WKG1tfgq73HnvMP2sUlG4tega+VWeponmHxGYhTnyfxuAxJ5gDgdSIKN +/Bf+KpYrtWKmpj29f5JZzVoqgrI3eQ== +-----END CERTIFICATE----- + +# Issuer: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. +# Subject: CN=Equifax Secure Global eBusiness CA-1 O=Equifax Secure Inc. +# Label: "Equifax Secure Global eBusiness CA" +# Serial: 1 +# MD5 Fingerprint: 8f:5d:77:06:27:c4:98:3c:5b:93:78:e7:d7:7d:9b:cc +# SHA1 Fingerprint: 7e:78:4a:10:1c:82:65:cc:2d:e1:f1:6d:47:b4:40:ca:d9:0a:19:45 +# SHA256 Fingerprint: 5f:0b:62:ea:b5:e3:53:ea:65:21:65:16:58:fb:b6:53:59:f4:43:28:0a:4a:fb:d1:04:d7:7d:10:f9:f0:4c:07 +-----BEGIN CERTIFICATE----- +MIICkDCCAfmgAwIBAgIBATANBgkqhkiG9w0BAQQFADBaMQswCQYDVQQGEwJVUzEc +MBoGA1UEChMTRXF1aWZheCBTZWN1cmUgSW5jLjEtMCsGA1UEAxMkRXF1aWZheCBT +ZWN1cmUgR2xvYmFsIGVCdXNpbmVzcyBDQS0xMB4XDTk5MDYyMTA0MDAwMFoXDTIw +MDYyMTA0MDAwMFowWjELMAkGA1UEBhMCVVMxHDAaBgNVBAoTE0VxdWlmYXggU2Vj +dXJlIEluYy4xLTArBgNVBAMTJEVxdWlmYXggU2VjdXJlIEdsb2JhbCBlQnVzaW5l +c3MgQ0EtMTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuucXkAJlsTRVPEnC +UdXfp9E3j9HngXNBUmCbnaEXJnitx7HoJpQytd4zjTov2/KaelpzmKNc6fuKcxtc +58O/gGzNqfTWK8D3+ZmqY6KxRwIP1ORROhI8bIpaVIRw28HFkM9yRcuoWcDNM50/ +o5brhTMhHD4ePmBudpxnhcXIw2ECAwEAAaNmMGQwEQYJYIZIAYb4QgEBBAQDAgAH +MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUvqigdHJQa0S3ySPY+6j/s1dr +aGwwHQYDVR0OBBYEFL6ooHRyUGtEt8kj2Puo/7NXa2hsMA0GCSqGSIb3DQEBBAUA +A4GBADDiAVGqx+pf2rnQZQ8w1j7aDRRJbpGTJxQx78T3LUX47Me/okENI7SS+RkA +Z70Br83gcfxaz2TE4JaY0KNA4gGK7ycH8WUBikQtBmV1UsCGECAhX2xrD2yuCRyv +8qIYNMR1pHMc8Y3c7635s3a0kr/clRAevsvIO1qEYBlWlKlV +-----END CERTIFICATE----- + +# Issuer: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division +# Subject: CN=Thawte Premium Server CA O=Thawte Consulting cc OU=Certification Services Division +# Label: "Thawte Premium Server CA" +# Serial: 1 +# MD5 Fingerprint: 06:9f:69:79:16:66:90:02:1b:8c:8c:a2:c3:07:6f:3a +# SHA1 Fingerprint: 62:7f:8d:78:27:65:63:99:d2:7d:7f:90:44:c9:fe:b3:f3:3e:fa:9a +# SHA256 Fingerprint: ab:70:36:36:5c:71:54:aa:29:c2:c2:9f:5d:41:91:16:3b:16:2a:22:25:01:13:57:d5:6d:07:ff:a7:bc:1f:72 +-----BEGIN CERTIFICATE----- +MIIDJzCCApCgAwIBAgIBATANBgkqhkiG9w0BAQQFADCBzjELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD +VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcyBEaXZpc2lvbjEhMB8GA1UEAxMYVGhhd3RlIFByZW1pdW0gU2Vy +dmVyIENBMSgwJgYJKoZIhvcNAQkBFhlwcmVtaXVtLXNlcnZlckB0aGF3dGUuY29t +MB4XDTk2MDgwMTAwMDAwMFoXDTIwMTIzMTIzNTk1OVowgc4xCzAJBgNVBAYTAlpB +MRUwEwYDVQQIEwxXZXN0ZXJuIENhcGUxEjAQBgNVBAcTCUNhcGUgVG93bjEdMBsG +A1UEChMUVGhhd3RlIENvbnN1bHRpbmcgY2MxKDAmBgNVBAsTH0NlcnRpZmljYXRp +b24gU2VydmljZXMgRGl2aXNpb24xITAfBgNVBAMTGFRoYXd0ZSBQcmVtaXVtIFNl +cnZlciBDQTEoMCYGCSqGSIb3DQEJARYZcHJlbWl1bS1zZXJ2ZXJAdGhhd3RlLmNv +bTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA0jY2aovXwlue2oFBYo847kkE +VdbQ7xwblRZH7xhINTpS9CtqBo87L+pW46+GjZ4X9560ZXUCTe/LCaIhUdib0GfQ +ug2SBhRz1JPLlyoAnFxODLz6FVL88kRu2hFKbgifLy3j+ao6hnO2RlNYyIkFvYMR +uHM/qgeN9EJN50CdHDcCAwEAAaMTMBEwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG +9w0BAQQFAAOBgQAmSCwWwlj66BZ0DKqqX1Q/8tfJeGBeXm43YyJ3Nn6yF8Q0ufUI +hfzJATj/Tb7yFkJD57taRvvBxhEf8UqwKEbJw8RCfbz6q1lu1bdRiBHjpIUZa4JM +pAwSremkrj/xw0llmozFyD4lt5SZu5IycQfwhl7tUCemDaYj+bvLpgcUQg== +-----END CERTIFICATE----- + +# Issuer: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division +# Subject: CN=Thawte Server CA O=Thawte Consulting cc OU=Certification Services Division +# Label: "Thawte Server CA" +# Serial: 1 +# MD5 Fingerprint: c5:70:c4:a2:ed:53:78:0c:c8:10:53:81:64:cb:d0:1d +# SHA1 Fingerprint: 23:e5:94:94:51:95:f2:41:48:03:b4:d5:64:d2:a3:a3:f5:d8:8b:8c +# SHA256 Fingerprint: b4:41:0b:73:e2:e6:ea:ca:47:fb:c4:2f:8f:a4:01:8a:f4:38:1d:c5:4c:fa:a8:44:50:46:1e:ed:09:45:4d:e9 +-----BEGIN CERTIFICATE----- +MIIDEzCCAnygAwIBAgIBATANBgkqhkiG9w0BAQQFADCBxDELMAkGA1UEBhMCWkEx +FTATBgNVBAgTDFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYD +VQQKExRUaGF3dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlv +biBTZXJ2aWNlcyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEm +MCQGCSqGSIb3DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wHhcNOTYwODAx +MDAwMDAwWhcNMjAxMjMxMjM1OTU5WjCBxDELMAkGA1UEBhMCWkExFTATBgNVBAgT +DFdlc3Rlcm4gQ2FwZTESMBAGA1UEBxMJQ2FwZSBUb3duMR0wGwYDVQQKExRUaGF3 +dGUgQ29uc3VsdGluZyBjYzEoMCYGA1UECxMfQ2VydGlmaWNhdGlvbiBTZXJ2aWNl +cyBEaXZpc2lvbjEZMBcGA1UEAxMQVGhhd3RlIFNlcnZlciBDQTEmMCQGCSqGSIb3 +DQEJARYXc2VydmVyLWNlcnRzQHRoYXd0ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQAD +gY0AMIGJAoGBANOkUG7I/1Zr5s9dtuoMaHVHoqrC2oQl/Kj0R1HahbUgdJSGHg91 +yekIYfUGbTBuFRkC6VLAYttNmZ7iagxEOM3+vuNkCXDF/rFrKbYvScg71CcEJRCX +L+eQbcAoQpnXTEPew/UhbVSfXcNY4cDk2VuwuNy0e982OsK1ZiIS1ocNAgMBAAGj +EzARMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEEBQADgYEAB/pMaVz7lcxG +7oWDTSEwjsrZqG9JGubaUeNgcGyEYRGhGshIPllDfU+VPaGLtwtimHp1it2ITk6e +QNuozDJ0uW8NxuOzRAvZim+aKZuZGCg70eNAKJpaPNW15yAbi8qkq43pUdniTCxZ +qdq5snUb9kLy78fyGPmJvKP/iiMucEc= +-----END CERTIFICATE----- + +# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority +# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority +# Label: "Verisign Class 3 Public Primary Certification Authority" +# Serial: 149843929435818692848040365716851702463 +# MD5 Fingerprint: 10:fc:63:5d:f6:26:3e:0d:f3:25:be:5f:79:cd:67:67 +# SHA1 Fingerprint: 74:2c:31:92:e6:07:e4:24:eb:45:49:54:2b:e1:bb:c5:3e:61:74:e2 +# SHA256 Fingerprint: e7:68:56:34:ef:ac:f6:9a:ce:93:9a:6b:25:5b:7b:4f:ab:ef:42:93:5b:50:a2:65:ac:b5:cb:60:27:e4:4e:70 +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEHC65B0Q2Sk0tjjKewPMur8wDQYJKoZIhvcNAQECBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTI4MDgwMTIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE +BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is +I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G +CSqGSIb3DQEBAgUAA4GBALtMEivPLCYATxQT3ab7/AoRhIzzKBxnki98tsX63/Do +lbwdj2wsqFHMc9ikwFPwTtYmwHYBV4GSXiHx0bH/59AhWM1pF+NEHJwZRDmJXNyc +AA9WjQKZ7aKQRUzkuxCkPfAyAw7xzvjoyVGM5mKf5p/AfbdynMk2OmufTqj/ZA1k +-----END CERTIFICATE----- + +# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority +# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority +# Label: "Verisign Class 3 Public Primary Certification Authority" +# Serial: 80507572722862485515306429940691309246 +# MD5 Fingerprint: ef:5a:f1:33:ef:f1:cd:bb:51:02:ee:12:14:4b:96:c4 +# SHA1 Fingerprint: a1:db:63:93:91:6f:17:e4:18:55:09:40:04:15:c7:02:40:b0:ae:6b +# SHA256 Fingerprint: a4:b6:b3:99:6f:c2:f3:06:b3:fd:86:81:bd:63:41:3d:8c:50:09:cc:4f:a3:29:c2:cc:f0:e2:fa:1b:14:03:05 +-----BEGIN CERTIFICATE----- +MIICPDCCAaUCEDyRMcsf9tAbDpq40ES/Er4wDQYJKoZIhvcNAQEFBQAwXzELMAkG +A1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFz +cyAzIFB1YmxpYyBQcmltYXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MB4XDTk2 +MDEyOTAwMDAwMFoXDTI4MDgwMjIzNTk1OVowXzELMAkGA1UEBhMCVVMxFzAVBgNV +BAoTDlZlcmlTaWduLCBJbmMuMTcwNQYDVQQLEy5DbGFzcyAzIFB1YmxpYyBQcmlt +YXJ5IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MIGfMA0GCSqGSIb3DQEBAQUAA4GN +ADCBiQKBgQDJXFme8huKARS0EN8EQNvjV69qRUCPhAwL0TPZ2RHP7gJYHyX3KqhE +BarsAx94f56TuZoAqiN91qyFomNFx3InzPRMxnVx0jnvT0Lwdd8KkMaOIG+YD/is +I19wKTakyYbnsZogy1Olhec9vn2a/iRFM9x2Fe0PonFkTGUugWhFpwIDAQABMA0G +CSqGSIb3DQEBBQUAA4GBABByUqkFFBkyCEHwxWsKzH4PIRnN5GfcX6kb5sroc50i +2JhucwNhkcV8sEVAbkSdjbCxlnRhLQ2pRdKkkirWmnWXbj9T/UWZYB2oK0z5XqcJ +2HUw19JlYD1n1khVdWk/kfVIC0dpImmClr7JyDiGSnoscxlIaU5rfGW/D/xwzoiQ +-----END CERTIFICATE----- + +# Issuer: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network +# Subject: O=VeriSign, Inc. OU=Class 3 Public Primary Certification Authority - G2/(c) 1998 VeriSign, Inc. - For authorized use only/VeriSign Trust Network +# Label: "Verisign Class 3 Public Primary Certification Authority - G2" +# Serial: 167285380242319648451154478808036881606 +# MD5 Fingerprint: a2:33:9b:4c:74:78:73:d4:6c:e7:c1:f3:8d:cb:5c:e9 +# SHA1 Fingerprint: 85:37:1c:a6:e5:50:14:3d:ce:28:03:47:1b:de:3a:09:e8:f8:77:0f +# SHA256 Fingerprint: 83:ce:3c:12:29:68:8a:59:3d:48:5f:81:97:3c:0f:91:95:43:1e:da:37:cc:5e:36:43:0e:79:c7:a8:88:63:8b +-----BEGIN CERTIFICATE----- +MIIDAjCCAmsCEH3Z/gfPqB63EHln+6eJNMYwDQYJKoZIhvcNAQEFBQAwgcExCzAJ +BgNVBAYTAlVTMRcwFQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xh +c3MgMyBQdWJsaWMgUHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcy +MTowOAYDVQQLEzEoYykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3Jp +emVkIHVzZSBvbmx5MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMB4X +DTk4MDUxODAwMDAwMFoXDTI4MDgwMTIzNTk1OVowgcExCzAJBgNVBAYTAlVTMRcw +FQYDVQQKEw5WZXJpU2lnbiwgSW5jLjE8MDoGA1UECxMzQ2xhc3MgMyBQdWJsaWMg +UHJpbWFyeSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eSAtIEcyMTowOAYDVQQLEzEo +YykgMTk5OCBWZXJpU2lnbiwgSW5jLiAtIEZvciBhdXRob3JpemVkIHVzZSBvbmx5 +MR8wHQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMIGfMA0GCSqGSIb3DQEB +AQUAA4GNADCBiQKBgQDMXtERXVxp0KvTuWpMmR9ZmDCOFoUgRm1HP9SFIIThbbP4 +pO0M8RcPO/mn+SXXwc+EY/J8Y8+iR/LGWzOOZEAEaMGAuWQcRXfH2G71lSk8UOg0 +13gfqLptQ5GVj0VXXn7F+8qkBOvqlzdUMG+7AUcyM83cV5tkaWH4mx0ciU9cZwID +AQABMA0GCSqGSIb3DQEBBQUAA4GBAFFNzb5cy5gZnBWyATl4Lk0PZ3BwmcYQWpSk +U01UbSuvDV1Ai2TT1+7eVmGSX6bEHRBhNtMsJzzoKQm5EWR0zLVznxxIqbxhAe7i +F6YM40AIOw7n60RzKprxaZLvcRTDOaxxp5EJb+RxBrO6WVcmeQD2+A2iMzAo1KpY +oJ2daZH9 +-----END CERTIFICATE----- + +# Issuer: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. +# Subject: CN=GTE CyberTrust Global Root O=GTE Corporation OU=GTE CyberTrust Solutions, Inc. +# Label: "GTE CyberTrust Global Root" +# Serial: 421 +# MD5 Fingerprint: ca:3d:d3:68:f1:03:5c:d0:32:fa:b8:2b:59:e8:5a:db +# SHA1 Fingerprint: 97:81:79:50:d8:1c:96:70:cc:34:d8:09:cf:79:44:31:36:7e:f4:74 +# SHA256 Fingerprint: a5:31:25:18:8d:21:10:aa:96:4b:02:c7:b7:c6:da:32:03:17:08:94:e5:fb:71:ff:fb:66:67:d5:e6:81:0a:36 +-----BEGIN CERTIFICATE----- +MIICWjCCAcMCAgGlMA0GCSqGSIb3DQEBBAUAMHUxCzAJBgNVBAYTAlVTMRgwFgYD +VQQKEw9HVEUgQ29ycG9yYXRpb24xJzAlBgNVBAsTHkdURSBDeWJlclRydXN0IFNv +bHV0aW9ucywgSW5jLjEjMCEGA1UEAxMaR1RFIEN5YmVyVHJ1c3QgR2xvYmFsIFJv +b3QwHhcNOTgwODEzMDAyOTAwWhcNMTgwODEzMjM1OTAwWjB1MQswCQYDVQQGEwJV +UzEYMBYGA1UEChMPR1RFIENvcnBvcmF0aW9uMScwJQYDVQQLEx5HVEUgQ3liZXJU +cnVzdCBTb2x1dGlvbnMsIEluYy4xIzAhBgNVBAMTGkdURSBDeWJlclRydXN0IEds +b2JhbCBSb290MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCVD6C28FCc6HrH +iM3dFw4usJTQGz0O9pTAipTHBsiQl8i4ZBp6fmw8U+E3KHNgf7KXUwefU/ltWJTS +r41tiGeA5u2ylc9yMcqlHHK6XALnZELn+aks1joNrI1CqiQBOeacPwGFVw1Yh0X4 +04Wqk2kmhXBIgD8SFcd5tB8FLztimQIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAG3r +GwnpXtlR22ciYaQqPEh346B8pt5zohQDhT37qw4wxYMWM4ETCJ57NE7fQMh017l9 +3PR2VX2bY1QY6fDq81yx2YtCHrnAlU66+tXifPVoYb+O7AWXX1uw16OFNMQkpw0P +lZPvy5TYnh+dXIVtx6quTx8itc2VrbqnzPmrC3p/ +-----END CERTIFICATE----- From f7be4e5f28335cc2c6d31be716c0a883b64b2a71 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 8 Oct 2015 14:05:17 +0100 Subject: [PATCH 492/545] NewConnectionErrors aren't timeouts. This is a compatibility change, and should be removed in 2.8.0 --- requests/adapters.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/requests/adapters.py b/requests/adapters.py index dbeb8cf1..7682db0a 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -24,6 +24,7 @@ from .packages.urllib3.exceptions import ClosedPoolError from .packages.urllib3.exceptions import ConnectTimeoutError from .packages.urllib3.exceptions import HTTPError as _HTTPError 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 ProtocolError from .packages.urllib3.exceptions import ReadTimeoutError @@ -412,7 +413,9 @@ class HTTPAdapter(BaseAdapter): except MaxRetryError as e: 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): raise RetryError(e, request=request) From 3948a9562db886e7ead6a48dbcbada34fbeb0838 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 8 Oct 2015 14:53:35 +0100 Subject: [PATCH 493/545] Use general null check for JSON --- requests/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 3b8750fc..da6dccdc 100644 --- a/requests/models.py +++ b/requests/models.py @@ -414,7 +414,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): content_type = None length = None - if data == {} and json is not None: + if not data and json is not None: content_type = 'application/json' body = complexjson.dumps(json) From b071c4955682275a699ad7b3ac3e8dd945fd6761 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 12 Oct 2015 10:48:57 +0100 Subject: [PATCH 494/545] Failing test for issue #2818 --- test_requests.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test_requests.py b/test_requests.py index 6279f64e..c5ac4f8d 100755 --- a/test_requests.py +++ b/test_requests.py @@ -568,6 +568,17 @@ class RequestsTestCase(unittest.TestCase): method=u('POST'), url=httpbin('post'), files=files) assert r.status_code == 200 + def test_unicode_method_name_with_request_object(self): + files = {'file': open('test_requests.py', 'rb')} + s = requests.Session() + req = requests.Request(u("POST"), httpbin('post'), files=files) + prep = s.prepare_request(req) + assert isinstance(prep.method, builtin_str) + assert prep.method == "POST" + + resp = s.send(prep) + assert resp.status_code == 200 + def test_custom_content_type(self): r = requests.post( httpbin('post'), From 2df3c7c75dd4e3c825421da3aaa4b08bd91d4afb Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Mon, 12 Oct 2015 10:50:37 +0100 Subject: [PATCH 495/545] Move to_native_string for methods to prepare() --- requests/models.py | 2 +- requests/sessions.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/requests/models.py b/requests/models.py index da6dccdc..2727bee9 100644 --- a/requests/models.py +++ b/requests/models.py @@ -319,7 +319,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): """Prepares the given HTTP method.""" self.method = method 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): """Prepares the given HTTP URL.""" diff --git a/requests/sessions.py b/requests/sessions.py index 496fdb2e..ad63902a 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -438,9 +438,6 @@ class Session(SessionRedirectMixin): :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. """ - - method = to_native_string(method) - # Create the Request. req = Request( method = method.upper(), From 18a6b601100db978f3a6e191816456e75bc47e0f Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Tue, 13 Oct 2015 13:55:09 +0100 Subject: [PATCH 496/545] v2.8.1 --- HISTORY.rst | 14 ++++++++++++++ requests/__init__.py | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 4ac66ce0..02593a34 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,20 @@ Release History --------------- +2.8.1 (2015-10-13) +++++++++++++++++++ + +**Bugfixes** + +- Update certificate bundle to match ``certifi`` 2015.9.6.2's weak certificate + bundle. +- Fix a bug in 2.8.0 where requests would raise ``ConnectTimeout`` instead of + ``ConnectionError`` +- When using the PreparedRequest flow, requests will now correctly respect the + ``json`` parameter. Broken in 2.8.0. +- When using the PreparedRequest flow, requests will now correctly handle a + Unicode-string method name on Python 2. Broken in 2.8.0. + 2.8.0 (2015-10-05) ++++++++++++++++++ diff --git a/requests/__init__.py b/requests/__init__.py index 546ee2bf..3d8188a7 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.8.0' -__build__ = 0x020800 +__version__ = '2.8.1' +__build__ = 0x020801 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2015 Kenneth Reitz' From 7c55446b7ec0978d38fceda5ad4bc45e5b26f1f1 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 14 Oct 2015 13:23:19 +0100 Subject: [PATCH 497/545] Add warnings about text/binary mode files. --- docs/user/advanced.rst | 20 +++++++++++++++++++- docs/user/quickstart.rst | 8 ++++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 175fc9b7..62959a93 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -315,6 +315,15 @@ file-like object for your body:: with open('massive-body', 'rb') as f: requests.post('http://some.url/streamed', data=f) +.. warning:: It is strongly recommended that you open files in `binary mode`_. + This is because Requests may attempt to provide the + ``Content-Length`` header for you, and if it does this value will + be set to the number of *bytes* in the file. Errors may occur if + you open the file in *text mode*. + +.. _binary mode: https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files + + .. _chunk-encoding: Chunk-Encoded Requests @@ -362,6 +371,15 @@ To do that, just set files to a list of tuples of (form_field_name, file_info): ... } +.. warning:: It is strongly recommended that you open files in `binary mode`_. + This is because Requests may attempt to provide the + ``Content-Length`` header for you, and if it does this value will + be set to the number of *bytes* in the file. Errors may occur if + you open the file in *text mode*. + +.. _binary mode: https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files + + .. _event-hooks: Event Hooks @@ -519,7 +537,7 @@ any request to the given scheme and exact hostname. } Note that proxy URLs must include the scheme. - + .. _compliance: Compliance diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index d08561d2..75a22ce0 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -302,6 +302,14 @@ support this, but there is a separate package which does - For sending multiple files in one request refer to the :ref:`advanced ` section. +.. warning:: It is strongly recommended that you open files in `binary mode`_. + This is because Requests may attempt to provide the + ``Content-Length`` header for you, and if it does this value will + be set to the number of *bytes* in the file. Errors may occur if + you open the file in *text mode*. + +.. _binary mode: https://docs.python.org/2/tutorial/inputoutput.html#reading-and-writing-files + Response Status Codes --------------------- From d8f28f4bca40715a3b2929ae639868f235bcaa38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20M=C3=BCller?= Date: Thu, 15 Oct 2015 14:32:35 +0200 Subject: [PATCH 498/545] Fixed usage example in sessions.py --- requests/sessions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index ad63902a..12879a5b 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -273,13 +273,13 @@ class Session(SessionRedirectMixin): >>> import requests >>> s = requests.Session() >>> s.get('http://httpbin.org/get') - 200 + Or as a context manager:: >>> with requests.Session() as s: >>> s.get('http://httpbin.org/get') - 200 + """ __attrs__ = [ From 8f591682e6a901baf0cc8a0393b5930252f0318a Mon Sep 17 00:00:00 2001 From: Dimitris Bliablias Date: Thu, 15 Oct 2015 16:26:08 +0300 Subject: [PATCH 499/545] Use buffering for HTTP responses on chunked requests For non-chunked requests, the request is performed using the 'urlopen' method that underlying uses buffering for the HTTP responses (for Python 2.7+ versions). For chunked requests though, the request is made via a different code path and so the 'getresponse' method is called without using buffering. So, the response is consumed per single byte via a recv() call on the underlying socket. This patch, fixes that issue to mimic the non-chucked request behavior. --- requests/adapters.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/requests/adapters.py b/requests/adapters.py index 7682db0a..c69c082e 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -394,7 +394,15 @@ class HTTPAdapter(BaseAdapter): low_conn.send(b'\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 = conn.getresponse(buffering=True) + except TypeError: + # For compatibility with Python 2.6 versions and back + r = conn.getresponse() + resp = HTTPResponse.from_httplib( r, pool=conn, From b675377dc2a0b5552877dce523d375fc4cb17b76 Mon Sep 17 00:00:00 2001 From: shagun Sodhani Date: Fri, 16 Oct 2015 17:01:36 +0530 Subject: [PATCH 500/545] added fix for #2826 --- AUTHORS.rst | 1 + docs/user/quickstart.rst | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index a765a19c..b0594a6b 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -160,3 +160,4 @@ Patches and Suggestions - Paul van der Linden (`@pvanderlinden `_) - Colin Dickson (`@colindickson `_) - Smiley Barry (`@smiley `_) +- Shagun Sodhani (`@shagunsodhani `_) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index d08561d2..096693fb 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -37,12 +37,12 @@ get all the information we need from this object. Requests' simple API means that all forms of HTTP request are as obvious. For example, this is how you make an HTTP POST request:: - >>> r = requests.post("http://httpbin.org/post") + >>> r = requests.post("http://httpbin.org/post", data = {"key":"value"}) Nice, right? What about the other HTTP request types: PUT, DELETE, HEAD and OPTIONS? These are all just as simple:: - >>> r = requests.put("http://httpbin.org/put") + >>> r = requests.put("http://httpbin.org/put", data = {"key":"value"}) >>> r = requests.delete("http://httpbin.org/delete") >>> r = requests.head("http://httpbin.org/get") >>> r = requests.options("http://httpbin.org/get") From edc68a0ac899785cd89a726e3da258e880d641dd Mon Sep 17 00:00:00 2001 From: Alex Khomchenko Date: Fri, 23 Oct 2015 15:22:36 +0300 Subject: [PATCH 501/545] fix issue #2844 --- requests/models.py | 2 +- test_requests.py | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 2727bee9..639565cf 100644 --- a/requests/models.py +++ b/requests/models.py @@ -81,7 +81,7 @@ class RequestEncodingMixin(object): """ if isinstance(data, (str, bytes)): - return data + return to_native_string(data) elif hasattr(data, 'read'): return data elif hasattr(data, '__iter__'): diff --git a/test_requests.py b/test_requests.py index c5ac4f8d..7b23e594 100755 --- a/test_requests.py +++ b/test_requests.py @@ -139,6 +139,11 @@ class RequestsTestCase(unittest.TestCase): prep = session.prepare_request(request) assert prep.url == 'http://example.com/?z=1&a=1&k=1&d=1' + def test_params_bytes_are_encoded(self): + request = requests.Request('GET', 'http://example.com', + params=b'test=foo').prepare() + assert request.url == 'http://example.com/?test=foo' + def test_mixed_case_scheme_acceptable(self): s = requests.Session() s.proxies = getproxies() From f1fd11e54faef9045833432c8505b6681d7526bc Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 24 Oct 2015 09:29:23 +0100 Subject: [PATCH 502/545] Emit warnings when using text mode files. --- requests/__init__.py | 8 +++++++- requests/exceptions.py | 15 +++++++++++++++ requests/utils.py | 19 +++++++++++++++++-- 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/requests/__init__.py b/requests/__init__.py index 3d8188a7..f7924dc8 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -62,7 +62,8 @@ from .sessions import session, Session from .status_codes import codes from .exceptions import ( RequestException, Timeout, URLRequired, - TooManyRedirects, HTTPError, ConnectionError + TooManyRedirects, HTTPError, ConnectionError, + FileModeWarning, ) # Set default logging handler to avoid "No handler found" warnings. @@ -75,3 +76,8 @@ except ImportError: pass logging.getLogger(__name__).addHandler(NullHandler()) + +import warnings + +# FileModeWarnings go off per the default. +warnings.simplefilter('default', FileModeWarning, append=True) diff --git a/requests/exceptions.py b/requests/exceptions.py index 89135a80..ba0b910e 100644 --- a/requests/exceptions.py +++ b/requests/exceptions.py @@ -97,3 +97,18 @@ class StreamConsumedError(RequestException, TypeError): class RetryError(RequestException): """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 diff --git a/requests/utils.py b/requests/utils.py index 4a8c6d7e..5975d0f1 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -29,7 +29,7 @@ from .compat import (quote, urlparse, bytes, str, OrderedDict, unquote, is_py2, basestring) from .cookies import RequestsCookieJar, cookiejar_from_dict from .structures import CaseInsensitiveDict -from .exceptions import InvalidURL +from .exceptions import InvalidURL, FileModeWarning _hush_pyflakes = (RequestsCookieJar,) @@ -60,7 +60,22 @@ def super_len(o): except io.UnsupportedOperation: pass else: - return os.fstat(fileno).st_size + filesize = os.fstat(fileno).st_size + + # Having used fstat to determine the file length, we need to + # confirm that this file was opened up in binary mode. + 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 + ) + + return filesize if hasattr(o, 'getvalue'): # e.g. BytesIO, cStringIO.StringIO From 7dd94941c84d28c329db1d7739ea32f8d41a131e Mon Sep 17 00:00:00 2001 From: Ann Paul Date: Mon, 26 Oct 2015 08:42:37 -0700 Subject: [PATCH 503/545] Add 511 Network Authentication Required status code to dict of status codes --- requests/status_codes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/requests/status_codes.py b/requests/status_codes.py index 1db7fc04..a852574a 100644 --- a/requests/status_codes.py +++ b/requests/status_codes.py @@ -78,6 +78,7 @@ _codes = { 507: ('insufficient_storage',), 509: ('bandwidth_limit_exceeded', 'bandwidth'), 510: ('not_extended',), + 511: ('network_authentication_required', 'network_auth', 'network_authentication'), } codes = LookupDict(name='status_codes') From 4dffe5efaa73c1ae4a082442e201f413b5e5bea7 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Fri, 16 Oct 2015 22:01:47 +0100 Subject: [PATCH 504/545] Update trove classifiers. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index f98f5281..745531bf 100755 --- a/setup.py +++ b/setup.py @@ -62,11 +62,11 @@ setup( 'Natural Language :: English', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python', - 'Programming Language :: Python :: 2.6', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4' + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', ), extras_require={ 'security': ['pyOpenSSL', 'ndg-httpsclient', 'pyasn1'], From 0352ec0bd3aabb131b0cbb649f2062d59fc8f4a1 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 5 Nov 2015 13:21:21 +0000 Subject: [PATCH 505/545] Add support for a directory of CAs --- HISTORY.rst | 8 ++++++++ docs/user/advanced.rst | 2 +- requests/adapters.py | 8 +++++++- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 02593a34..34226f49 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,6 +3,14 @@ Release History --------------- +dev (XXXX) +++++++++++ + +**Minor Improvements** (Backwards compatible) + +- The ``verify`` keyword argument now supports being passed a path to a + directory of CA certificates, not just a single-file bundle. + 2.8.1 (2015-10-13) ++++++++++++++++++ diff --git a/docs/user/advanced.rst b/docs/user/advanced.rst index 62959a93..f0d2ffd9 100644 --- a/docs/user/advanced.rst +++ b/docs/user/advanced.rst @@ -201,7 +201,7 @@ I don't have SSL setup on this domain, so it fails. Excellent. GitHub does thoug >>> requests.get('https://github.com', verify=True) -You can pass ``verify`` the path to a CA_BUNDLE file with certificates of trusted CAs:: +You can pass ``verify`` the path to a CA_BUNDLE file or directory with certificates of trusted CAs:: >>> requests.get('https://github.com', verify='/path/to/certfile') diff --git a/requests/adapters.py b/requests/adapters.py index c69c082e..44f5064a 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -8,6 +8,7 @@ This module contains the transport adapters that Requests uses to define and maintain connections. """ +import os.path import socket from .models import Response @@ -185,10 +186,15 @@ class HTTPAdapter(BaseAdapter): raise Exception("Could not find a suitable SSL CA certificate bundle.") 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: conn.cert_reqs = 'CERT_NONE' conn.ca_certs = None + conn.ca_cert_dir = None if cert: if not isinstance(cert, basestring): From de53523befec064909fe0d2436f94772fc28b6f6 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 22 Oct 2015 12:17:13 +0100 Subject: [PATCH 506/545] Add pytest-httpbin requirement. --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 1f5297db..75391678 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,5 @@ py==1.4.30 pytest==2.8.1 pytest-cov==2.1.0 +pytest-httpbin==0.0.7 wheel From e5053cd32269ce9122ab7643e112e21518005b63 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 22 Oct 2015 12:20:29 +0100 Subject: [PATCH 507/545] Define some httpbin fixtures. --- test_requests.py | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/test_requests.py b/test_requests.py index 7b23e594..456dfe42 100755 --- a/test_requests.py +++ b/test_requests.py @@ -48,18 +48,31 @@ else: return s.decode('unicode-escape') +@pytest.fixture +def httpbin(httpbin): + # Issue #1483: Make sure the URL always has a trailing slash + httpbin_url = httpbin.url.rstrip('/') + '/' + + def inner(*suffix): + return urljoin(httpbin_url, '/'.join(suffix)) + + return inner + + +@pytest.fixture +def httpsbin_url(httpbin_secure): + # Issue #1483: Make sure the URL always has a trailing slash + httpbin_url = httpbin_secure.url.rstrip('/') + '/' + + def inner(*suffix): + return urljoin(httpbin_url, '/'.join(suffix)) + + return inner + + # Requests to this URL should always fail with a connection timeout (nothing # listening on that port) TARPIT = "http://10.255.255.1" -HTTPBIN = os.environ.get('HTTPBIN_URL', 'http://httpbin.org/') -# Issue #1483: Make sure the URL always has a trailing slash -HTTPBIN = HTTPBIN.rstrip('/') + '/' - - -def httpbin(*suffix): - """Returns url for HTTPBIN resource.""" - return urljoin(HTTPBIN, '/'.join(suffix)) - class RequestsTestCase(unittest.TestCase): From a56db9ecc4456c992372f13d979e526bca251183 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 22 Oct 2015 12:20:57 +0100 Subject: [PATCH 508/545] Stop inheriting from unittest.TestCase --- test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index 456dfe42..bed50888 100755 --- a/test_requests.py +++ b/test_requests.py @@ -74,7 +74,7 @@ def httpsbin_url(httpbin_secure): # listening on that port) TARPIT = "http://10.255.255.1" -class RequestsTestCase(unittest.TestCase): +class TestRequests(object): _multiprocess_can_split_ = True From f52287d9992f0cff892ab5b55ed12803791a78a7 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 22 Oct 2015 12:21:36 +0100 Subject: [PATCH 509/545] Stop using HTTP and HTTPS schemes in one test. --- test_requests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test_requests.py b/test_requests.py index bed50888..d1c6aa4d 100755 --- a/test_requests.py +++ b/test_requests.py @@ -161,8 +161,7 @@ class TestRequests(object): s = requests.Session() s.proxies = getproxies() parts = urlparse(httpbin('get')) - schemes = ['http://', 'HTTP://', 'hTTp://', 'HttP://', - 'https://', 'HTTPS://', 'hTTps://', 'HttPs://'] + schemes = ['http://', 'HTTP://', 'hTTp://', 'HttP://'] for scheme in schemes: url = scheme + parts.netloc + parts.path r = requests.Request('GET', url) From 9b63f9cd37d19f2d4bbce42caec112ad0606d8dd Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 22 Oct 2015 12:22:28 +0100 Subject: [PATCH 510/545] Make sure we unapply this patch. --- test_requests.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/test_requests.py b/test_requests.py index d1c6aa4d..cb25555a 100755 --- a/test_requests.py +++ b/test_requests.py @@ -353,28 +353,33 @@ class TestRequests(object): wrong_auth = ('wronguser', 'wrongpass') url = httpbin('basic-auth', 'user', 'pass') - def get_netrc_auth_mock(url): - return auth - requests.sessions.get_netrc_auth = get_netrc_auth_mock + old_auth = requests.sessions.get_netrc_auth - # Should use netrc and work. - r = requests.get(url) - assert r.status_code == 200 + try: + def get_netrc_auth_mock(url): + return auth + requests.sessions.get_netrc_auth = get_netrc_auth_mock - # Given auth should override and fail. - r = requests.get(url, auth=wrong_auth) - assert r.status_code == 401 + # Should use netrc and work. + r = requests.get(url) + assert r.status_code == 200 - s = requests.session() + # Given auth should override and fail. + r = requests.get(url, auth=wrong_auth) + assert r.status_code == 401 - # Should use netrc and work. - r = s.get(url) - assert r.status_code == 200 + s = requests.session() - # Given auth should override and fail. - s.auth = wrong_auth - r = s.get(url) - assert r.status_code == 401 + # Should use netrc and work. + r = s.get(url) + assert r.status_code == 200 + + # Given auth should override and fail. + s.auth = wrong_auth + r = s.get(url) + assert r.status_code == 401 + finally: + requests.sessions.get_netrc_auth = old_auth def test_DIGEST_HTTP_200_OK_GET(self): From 3bc189b0afba0d6ea8c65f0e99a78196a4a6d8b6 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 22 Oct 2015 12:24:34 +0100 Subject: [PATCH 511/545] Inject and use httpbin fixture everywhere. --- test_requests.py | 180 +++++++++++++++++++++++------------------------ 1 file changed, 90 insertions(+), 90 deletions(-) diff --git a/test_requests.py b/test_requests.py index cb25555a..6caaef0d 100755 --- a/test_requests.py +++ b/test_requests.py @@ -118,13 +118,13 @@ class TestRequests(object): assert pr.url == req.url assert pr.body == 'life=42' - def test_no_content_length(self): + def test_no_content_length(self, httpbin): get_req = requests.Request('GET', httpbin('get')).prepare() assert 'Content-Length' not in get_req.headers head_req = requests.Request('HEAD', httpbin('head')).prepare() assert 'Content-Length' not in head_req.headers - def test_override_content_length(self): + def test_override_content_length(self, httpbin): headers = { 'Content-Length': 'not zero' } @@ -157,7 +157,7 @@ class TestRequests(object): params=b'test=foo').prepare() assert request.url == 'http://example.com/?test=foo' - def test_mixed_case_scheme_acceptable(self): + def test_mixed_case_scheme_acceptable(self, httpbin): s = requests.Session() s.proxies = getproxies() parts = urlparse(httpbin('get')) @@ -168,7 +168,7 @@ class TestRequests(object): r = s.send(r.prepare()) assert r.status_code == 200, 'failed for scheme {0}'.format(scheme) - def test_HTTP_200_OK_GET_ALTERNATIVE(self): + def test_HTTP_200_OK_GET_ALTERNATIVE(self, httpbin): r = requests.Request('GET', httpbin('get')) s = requests.Session() s.proxies = getproxies() @@ -177,7 +177,7 @@ class TestRequests(object): assert r.status_code == 200 - def test_HTTP_302_ALLOW_REDIRECT_GET(self): + def test_HTTP_302_ALLOW_REDIRECT_GET(self, httpbin): r = requests.get(httpbin('redirect', '1')) assert r.status_code == 200 assert r.history[0].status_code == 302 @@ -187,7 +187,7 @@ class TestRequests(object): # r = requests.post(httpbin('status', '302'), data={'some': 'data'}) # self.assertEqual(r.status_code, 200) - def test_HTTP_200_OK_GET_WITH_PARAMS(self): + def test_HTTP_200_OK_GET_WITH_PARAMS(self, httpbin): heads = {'User-agent': 'Mozilla/5.0'} r = requests.get(httpbin('user-agent'), headers=heads) @@ -195,25 +195,25 @@ class TestRequests(object): assert heads['User-agent'] in r.text assert r.status_code == 200 - def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self): + def test_HTTP_200_OK_GET_WITH_MIXED_PARAMS(self, httpbin): heads = {'User-agent': 'Mozilla/5.0'} r = requests.get(httpbin('get') + '?test=true', params={'q': 'test'}, headers=heads) assert r.status_code == 200 - def test_set_cookie_on_301(self): + def test_set_cookie_on_301(self, httpbin): s = requests.session() url = httpbin('cookies/set?foo=bar') s.get(url) assert s.cookies['foo'] == 'bar' - def test_cookie_sent_on_redirect(self): + def test_cookie_sent_on_redirect(self, httpbin): s = requests.session() s.get(httpbin('cookies/set?foo=bar')) r = s.get(httpbin('redirect/1')) # redirects to httpbin('get') assert 'Cookie' in r.json()['headers'] - def test_cookie_removed_on_expire(self): + def test_cookie_removed_on_expire(self, httpbin): s = requests.session() s.get(httpbin('cookies/set?foo=bar')) assert s.cookies['foo'] == 'bar' @@ -226,18 +226,18 @@ class TestRequests(object): ) assert 'foo' not in s.cookies - def test_cookie_quote_wrapped(self): + def test_cookie_quote_wrapped(self, httpbin): s = requests.session() s.get(httpbin('cookies/set?foo="bar:baz"')) assert s.cookies['foo'] == '"bar:baz"' - def test_cookie_persists_via_api(self): + def test_cookie_persists_via_api(self, httpbin): s = requests.session() r = s.get(httpbin('redirect/1'), cookies={'foo': 'bar'}) assert 'foo' in r.request.headers['Cookie'] assert 'foo' in r.history[0].request.headers['Cookie'] - def test_request_cookie_overrides_session_cookie(self): + def test_request_cookie_overrides_session_cookie(self, httpbin): s = requests.session() s.cookies['foo'] = 'bar' r = s.get(httpbin('cookies'), cookies={'foo': 'baz'}) @@ -245,13 +245,13 @@ class TestRequests(object): # Session cookie should not be modified assert s.cookies['foo'] == 'bar' - def test_request_cookies_not_persisted(self): + def test_request_cookies_not_persisted(self, httpbin): s = requests.session() s.get(httpbin('cookies'), cookies={'foo': 'baz'}) # Sending a request with cookies should not add cookies to the session assert not s.cookies - def test_generic_cookiejar_works(self): + def test_generic_cookiejar_works(self, httpbin): cj = cookielib.CookieJar() cookiejar_from_dict({'foo': 'bar'}, cj) s = requests.session() @@ -262,7 +262,7 @@ class TestRequests(object): # Make sure the session cj is still the custom one assert s.cookies is cj - def test_param_cookiejar_works(self): + def test_param_cookiejar_works(self, httpbin): cj = cookielib.CookieJar() cookiejar_from_dict({'foo': 'bar'}, cj) s = requests.session() @@ -270,13 +270,13 @@ class TestRequests(object): # Make sure the cookie was sent assert r.json()['cookies']['foo'] == 'bar' - def test_requests_in_history_are_not_overridden(self): + def test_requests_in_history_are_not_overridden(self, httpbin): resp = requests.get(httpbin('redirect/3')) urls = [r.url for r in resp.history] req_urls = [r.request.url for r in resp.history] assert urls == req_urls - def test_history_is_always_a_list(self): + def test_history_is_always_a_list(self, httpbin): """ Show that even with redirects, Response.history is always a list. """ @@ -286,7 +286,7 @@ class TestRequests(object): assert isinstance(resp.history, list) assert not isinstance(resp.history, tuple) - def test_headers_on_session_with_None_are_not_sent(self): + def test_headers_on_session_with_None_are_not_sent(self, httpbin): """Do not send headers in Session.headers with None values.""" ses = requests.Session() ses.headers['Accept-Encoding'] = None @@ -294,7 +294,7 @@ class TestRequests(object): prep = ses.prepare_request(req) assert 'Accept-Encoding' not in prep.headers - def test_user_agent_transfers(self): + def test_user_agent_transfers(self, httpbin): heads = { 'User-agent': 'Mozilla/5.0 (github.com/kennethreitz/requests)' @@ -310,15 +310,15 @@ class TestRequests(object): r = requests.get(httpbin('user-agent'), headers=heads) assert heads['user-agent'] in r.text - def test_HTTP_200_OK_HEAD(self): + def test_HTTP_200_OK_HEAD(self, httpbin): r = requests.head(httpbin('get')) assert r.status_code == 200 - def test_HTTP_200_OK_PUT(self): + def test_HTTP_200_OK_PUT(self, httpbin): r = requests.put(httpbin('put')) assert r.status_code == 200 - def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self): + def test_BASICAUTH_TUPLE_HTTP_200_OK_GET(self, httpbin): auth = ('user', 'pass') url = httpbin('basic-auth', 'user', 'pass') @@ -348,7 +348,7 @@ class TestRequests(object): with pytest.raises(InvalidURL): requests.get("http://fe80::5054:ff:fe5a:fc0") - def test_basicauth_with_netrc(self): + def test_basicauth_with_netrc(self, httpbin): auth = ('user', 'pass') wrong_auth = ('wronguser', 'wrongpass') url = httpbin('basic-auth', 'user', 'pass') @@ -381,7 +381,7 @@ class TestRequests(object): finally: requests.sessions.get_netrc_auth = old_auth - def test_DIGEST_HTTP_200_OK_GET(self): + def test_DIGEST_HTTP_200_OK_GET(self, httpbin): auth = HTTPDigestAuth('user', 'pass') url = httpbin('digest-auth', 'auth', 'user', 'pass') @@ -397,7 +397,7 @@ class TestRequests(object): r = s.get(url) assert r.status_code == 200 - def test_DIGEST_AUTH_RETURNS_COOKIE(self): + def test_DIGEST_AUTH_RETURNS_COOKIE(self, httpbin): url = httpbin('digest-auth', 'auth', 'user', 'pass') auth = HTTPDigestAuth('user', 'pass') r = requests.get(url) @@ -406,14 +406,14 @@ class TestRequests(object): r = requests.get(url, auth=auth) assert r.status_code == 200 - def test_DIGEST_AUTH_SETS_SESSION_COOKIES(self): + def test_DIGEST_AUTH_SETS_SESSION_COOKIES(self, httpbin): url = httpbin('digest-auth', 'auth', 'user', 'pass') auth = HTTPDigestAuth('user', 'pass') s = requests.Session() s.get(url, auth=auth) assert s.cookies['fake'] == 'fake_value' - def test_DIGEST_STREAM(self): + def test_DIGEST_STREAM(self, httpbin): auth = HTTPDigestAuth('user', 'pass') url = httpbin('digest-auth', 'auth', 'user', 'pass') @@ -424,7 +424,7 @@ class TestRequests(object): r = requests.get(url, auth=auth, stream=False) assert r.raw.read() == b'' - def test_DIGESTAUTH_WRONG_HTTP_401_GET(self): + def test_DIGESTAUTH_WRONG_HTTP_401_GET(self, httpbin): auth = HTTPDigestAuth('user', 'wrongpass') url = httpbin('digest-auth', 'auth', 'user', 'pass') @@ -440,7 +440,7 @@ class TestRequests(object): r = s.get(url) assert r.status_code == 401 - def test_DIGESTAUTH_QUOTES_QOP_VALUE(self): + def test_DIGESTAUTH_QUOTES_QOP_VALUE(self, httpbin): auth = HTTPDigestAuth('user', 'pass') url = httpbin('digest-auth', 'auth', 'user', 'pass') @@ -448,7 +448,7 @@ class TestRequests(object): r = requests.get(url, auth=auth) assert '"auth"' in r.request.headers['Authorization'] - def test_DIGESTAUTH_THREADED(self): + def test_DIGESTAUTH_THREADED(self, httpbin): auth = HTTPDigestAuth('user', 'pass') url = httpbin('digest-auth', 'auth', 'user', 'pass') @@ -463,7 +463,7 @@ class TestRequests(object): pool = ThreadPool(processes=50) pool.map(do_request, range(100)) - def test_POSTBIN_GET_POST_FILES(self): + def test_POSTBIN_GET_POST_FILES(self, httpbin): url = httpbin('post') post1 = requests.post(url).raise_for_status() @@ -481,7 +481,7 @@ class TestRequests(object): with pytest.raises(ValueError): requests.post(url, files=['bad file data']) - def test_POSTBIN_GET_POST_FILES_WITH_DATA(self): + def test_POSTBIN_GET_POST_FILES_WITH_DATA(self, httpbin): url = httpbin('post') post1 = requests.post(url).raise_for_status() @@ -500,17 +500,17 @@ class TestRequests(object): with pytest.raises(ValueError): requests.post(url, files=['bad file data']) - def test_conflicting_post_params(self): + def test_conflicting_post_params(self, httpbin): url = httpbin('post') with open('requirements.txt') as f: pytest.raises(ValueError, "requests.post(url, data='[{\"some\": \"data\"}]', files={'some': f})") pytest.raises(ValueError, "requests.post(url, data=u('[{\"some\": \"data\"}]'), files={'some': f})") - def test_request_ok_set(self): + def test_request_ok_set(self, httpbin): r = requests.get(httpbin('status', '404')) assert not r.ok - def test_status_raising(self): + def test_status_raising(self, httpbin): r = requests.get(httpbin('status', '404')) with pytest.raises(requests.exceptions.HTTPError): r.raise_for_status() @@ -518,11 +518,11 @@ class TestRequests(object): r = requests.get(httpbin('status', '500')) assert not r.ok - def test_decompress_gzip(self): + def test_decompress_gzip(self, httpbin): r = requests.get(httpbin('gzip')) r.content.decode('ascii') - def test_unicode_get(self): + def test_unicode_get(self, httpbin): url = httpbin('/get') requests.get(url, params={'foo': 'føø'}) requests.get(url, params={'føø': 'føø'}) @@ -530,29 +530,29 @@ class TestRequests(object): requests.get(url, params={'foo': 'foo'}) requests.get(httpbin('ø'), params={'foo': 'foo'}) - def test_unicode_header_name(self): + def test_unicode_header_name(self, httpbin): requests.put( httpbin('put'), headers={str('Content-Type'): 'application/octet-stream'}, data='\xff') # compat.str is unicode. - def test_pyopenssl_redirect(self): - requests.get('https://httpbin.org/status/301') + def test_pyopenssl_redirect(self, httpsbin_url, httpbin_ca_bundle): + requests.get(httpsbin_url('status', '301'), verify=httpbin_ca_bundle) - def test_urlencoded_get_query_multivalued_param(self): + def test_urlencoded_get_query_multivalued_param(self, httpbin): r = requests.get(httpbin('get'), params=dict(test=['foo', 'baz'])) assert r.status_code == 200 assert r.url == httpbin('get?test=foo&test=baz') - def test_different_encodings_dont_break_post(self): + def test_different_encodings_dont_break_post(self, httpbin): r = requests.post(httpbin('post'), data={'stuff': json.dumps({'a': 123})}, params={'blah': 'asdf1234'}, files={'file': ('test_requests.py', open(__file__, 'rb'))}) assert r.status_code == 200 - def test_unicode_multipart_post(self): + def test_unicode_multipart_post(self, httpbin): r = requests.post(httpbin('post'), data={'stuff': u('ëlïxr')}, files={'file': ('test_requests.py', open(__file__, 'rb'))}) @@ -573,7 +573,7 @@ class TestRequests(object): files={'file': ('test_requests.py', open(__file__, 'rb'))}) assert r.status_code == 200 - def test_unicode_multipart_post_fieldnames(self): + def test_unicode_multipart_post_fieldnames(self, httpbin): filename = os.path.splitext(__file__)[0] + '.py' r = requests.Request(method='POST', url=httpbin('post'), @@ -584,13 +584,13 @@ class TestRequests(object): assert b'name="stuff"' in prep.body assert b'name="b\'stuff\'"' not in prep.body - def test_unicode_method_name(self): + def test_unicode_method_name(self, httpbin): files = {'file': open('test_requests.py', 'rb')} r = requests.request( method=u('POST'), url=httpbin('post'), files=files) assert r.status_code == 200 - def test_unicode_method_name_with_request_object(self): + def test_unicode_method_name_with_request_object(self, httpbin): files = {'file': open('test_requests.py', 'rb')} s = requests.Session() req = requests.Request(u("POST"), httpbin('post'), files=files) @@ -601,7 +601,7 @@ class TestRequests(object): resp = s.send(prep) assert resp.status_code == 200 - def test_custom_content_type(self): + def test_custom_content_type(self, httpbin): r = requests.post( httpbin('post'), data={'stuff': json.dumps({'a': 123})}, @@ -611,38 +611,38 @@ class TestRequests(object): assert r.status_code == 200 assert b"text/py-content-type" in r.request.body - def test_hook_receives_request_arguments(self): + def test_hook_receives_request_arguments(self, httpbin): def hook(resp, **kwargs): assert resp is not None assert kwargs != {} - requests.Request('GET', HTTPBIN, hooks={'response': hook}) + requests.Request('GET', httpbin(), hooks={'response': hook}) - def test_session_hooks_are_used_with_no_request_hooks(self): + def test_session_hooks_are_used_with_no_request_hooks(self, httpbin): hook = lambda x, *args, **kwargs: x s = requests.Session() s.hooks['response'].append(hook) - r = requests.Request('GET', HTTPBIN) + r = requests.Request('GET', httpbin()) prep = s.prepare_request(r) assert prep.hooks['response'] != [] assert prep.hooks['response'] == [hook] - def test_session_hooks_are_overriden_by_request_hooks(self): + def test_session_hooks_are_overriden_by_request_hooks(self, httpbin): hook1 = lambda x, *args, **kwargs: x hook2 = lambda x, *args, **kwargs: x assert hook1 is not hook2 s = requests.Session() s.hooks['response'].append(hook2) - r = requests.Request('GET', HTTPBIN, hooks={'response': [hook1]}) + r = requests.Request('GET', httpbin(), hooks={'response': [hook1]}) prep = s.prepare_request(r) assert prep.hooks['response'] == [hook1] - def test_prepared_request_hook(self): + def test_prepared_request_hook(self, httpbin): def hook(resp, **kwargs): resp.hook_working = True return resp - req = requests.Request('GET', HTTPBIN, hooks={'response': hook}) + req = requests.Request('GET', httpbin(), hooks={'response': hook}) prep = req.prepare() s = requests.Session() @@ -651,7 +651,7 @@ class TestRequests(object): assert hasattr(resp, 'hook_working') - def test_prepared_from_session(self): + def test_prepared_from_session(self, httpbin): class DummyAuth(requests.auth.AuthBase): def __call__(self, r): r.headers['Dummy-Auth-Test'] = 'dummy-auth-test-ok' @@ -804,7 +804,7 @@ class TestRequests(object): # make sure one can use items multiple times assert list(items) == list(items) - def test_time_elapsed_blank(self): + def test_time_elapsed_blank(self, httpbin): r = requests.get(httpbin('get')) td = r.elapsed total_seconds = ((td.microseconds + (td.seconds + td.days * 24 * 3600) @@ -843,7 +843,7 @@ class TestRequests(object): chunks = r.iter_content(decode_unicode=True) assert all(isinstance(chunk, str) for chunk in chunks) - def test_request_and_response_are_pickleable(self): + def test_request_and_response_are_pickleable(self, httpbin): r = requests.get(httpbin('get')) # verify we can pickle the original request @@ -875,8 +875,8 @@ class TestRequests(object): url = 'http://user:pass%23pass@complex.url.com/path?query=yes' assert ('user', 'pass#pass') == requests.utils.get_auth_from_url(url) - def test_cannot_send_unprepared_requests(self): - r = requests.Request(url=HTTPBIN) + def test_cannot_send_unprepared_requests(self, httpbin): + r = requests.Request(url=httpbin()) with pytest.raises(ValueError): requests.Session().send(r) @@ -890,7 +890,7 @@ class TestRequests(object): assert str(error) == 'message' assert error.response == response - def test_session_pickling(self): + def test_session_pickling(self, httpbin): r = requests.Request('GET', httpbin('get')) s = requests.Session() @@ -900,7 +900,7 @@ class TestRequests(object): r = s.send(r.prepare()) assert r.status_code == 200 - def test_fixes_1329(self): + def test_fixes_1329(self, httpbin): """ Ensure that header updates are done case-insensitively. """ @@ -913,7 +913,7 @@ class TestRequests(object): assert headers['Accept'] == 'application/json' assert headers['ACCEPT'] == 'application/json' - def test_uppercase_scheme_redirect(self): + def test_uppercase_scheme_redirect(self, httpbin): parts = urlparse(httpbin('html')) url = "HTTP://" + parts.netloc + parts.path r = requests.get(httpbin('redirect-to'), params={'url': url}) @@ -958,14 +958,14 @@ class TestRequests(object): assert 'http://' in s2.adapters assert 'https://' in s2.adapters - def test_header_remove_is_case_insensitive(self): + def test_header_remove_is_case_insensitive(self, httpbin): # From issue #1321 s = requests.Session() s.headers['foo'] = 'bar' r = s.get(httpbin('get'), headers={'FOO': None}) assert 'foo' not in r.request.headers - def test_params_are_merged_case_sensitive(self): + def test_params_are_merged_case_sensitive(self, httpbin): s = requests.Session() s.params['foo'] = 'bar' r = s.get(httpbin('get'), params={'FOO': 'bar'}) @@ -980,7 +980,7 @@ class TestRequests(object): r = requests.Request('GET', url).prepare() assert r.url == url - def test_header_keys_are_native(self): + def test_header_keys_are_native(self, httpbin): headers = {u('unicode'): 'blah', 'byte'.encode('ascii'): 'blah'} r = requests.Request('GET', httpbin('get'), headers=headers) p = r.prepare() @@ -990,7 +990,7 @@ class TestRequests(object): assert 'unicode' in p.headers.keys() assert 'byte' in p.headers.keys() - def test_can_send_nonstring_objects_with_files(self): + def test_can_send_nonstring_objects_with_files(self, httpbin): data = {'a': 0.0} files = {'b': 'foo'} r = requests.Request('POST', httpbin('post'), data=data, files=files) @@ -998,7 +998,7 @@ class TestRequests(object): assert 'multipart/form-data' in p.headers['Content-Type'] - def test_can_send_bytes_bytearray_objects_with_files(self): + def test_can_send_bytes_bytearray_objects_with_files(self, httpbin): # Test bytes: data = {'a': 'this is a string'} files = {'b': b'foo'} @@ -1011,7 +1011,7 @@ class TestRequests(object): p = r.prepare() assert 'multipart/form-data' in p.headers['Content-Type'] - def test_can_send_file_object_with_non_string_filename(self): + def test_can_send_file_object_with_non_string_filename(self, httpbin): f = io.BytesIO() f.name = 2 r = requests.Request('POST', httpbin('post'), files={'f': f}) @@ -1019,7 +1019,7 @@ class TestRequests(object): assert 'multipart/form-data' in p.headers['Content-Type'] - def test_autoset_header_values_are_native(self): + def test_autoset_header_values_are_native(self, httpbin): data = 'this is a string' length = '16' req = requests.Request('POST', httpbin('post'), data=data) @@ -1038,7 +1038,7 @@ class TestRequests(object): preq = req.prepare() assert test_url == preq.url - def test_auth_is_stripped_on_redirect_off_host(self): + def test_auth_is_stripped_on_redirect_off_host(self, httpbin): r = requests.get( httpbin('redirect-to'), params={'url': 'http://www.google.co.uk'}, @@ -1047,14 +1047,14 @@ class TestRequests(object): assert r.history[0].request.headers['Authorization'] assert not r.request.headers.get('Authorization', '') - def test_auth_is_retained_for_redirect_on_host(self): + def test_auth_is_retained_for_redirect_on_host(self, httpbin): r = requests.get(httpbin('redirect/1'), auth=('user', 'pass')) h1 = r.history[0].request.headers['Authorization'] h2 = r.request.headers['Authorization'] assert h1 == h2 - def test_manual_redirect_with_partial_body_read(self): + def test_manual_redirect_with_partial_body_read(self, httpbin): s = requests.Session() r1 = s.get(httpbin('redirect/2'), allow_redirects=False, stream=True) assert r1.is_redirect @@ -1087,7 +1087,7 @@ class TestRequests(object): adapter.build_response = build_response - def test_redirect_with_wrong_gzipped_header(self): + def test_redirect_with_wrong_gzipped_header(self, httpbin): s = requests.Session() url = httpbin('redirect/1') self._patch_adapter_gzipped_redirect(s, url) @@ -1098,7 +1098,7 @@ class TestRequests(object): assert isinstance(s, builtin_str) assert s == "Basic dGVzdDp0ZXN0" - def test_requests_history_is_saved(self): + def test_requests_history_is_saved(self, httpbin): r = requests.get(httpbin('redirect/5')) total = r.history[-1].history i = 0 @@ -1106,7 +1106,7 @@ class TestRequests(object): assert item.history == total[0:i] i = i + 1 - def test_json_param_post_content_type_works(self): + def test_json_param_post_content_type_works(self, httpbin): r = requests.post( httpbin('post'), json={'life': 42} @@ -1115,14 +1115,14 @@ class TestRequests(object): assert 'application/json' in r.request.headers['Content-Type'] assert {'life': 42} == r.json()['json'] - def test_json_param_post_should_not_override_data_param(self): - r = requests.Request(method='POST', url='http://httpbin.org/post', + def test_json_param_post_should_not_override_data_param(self, httpbin): + r = requests.Request(method='POST', url=httpbin('post'), data={'stuff': 'elixr'}, json={'music': 'flute'}) prep = r.prepare() assert 'stuff=elixr' == prep.body - def test_response_iter_lines(self): + def test_response_iter_lines(self, httpbin): r = requests.get(httpbin('stream/4'), stream=True) assert r.status_code == 200 @@ -1130,7 +1130,7 @@ class TestRequests(object): next(it) assert len(list(it)) == 3 - def test_unconsumed_session_response_closes_connection(self): + def test_unconsumed_session_response_closes_connection(self, httpbin): s = requests.session() with contextlib.closing(s.get(httpbin('stream/4'), stream=True)) as response: @@ -1140,7 +1140,7 @@ class TestRequests(object): self.assertTrue(response.raw.closed) @pytest.mark.xfail - def test_response_iter_lines_reentrant(self): + def test_response_iter_lines_reentrant(self, httpbin): """Response.iter_lines() is not reentrant safe""" r = requests.get(httpbin('stream/4'), stream=True) assert r.status_code == 200 @@ -1534,13 +1534,13 @@ class TestMorselToCookieMaxAge(unittest.TestCase): class TestTimeout: - def test_stream_timeout(self): + def test_stream_timeout(self, httpbin): try: requests.get(httpbin('delay/10'), timeout=2.0) except requests.exceptions.Timeout as e: assert 'Read timed out' in e.args[0].args[0] - def test_invalid_timeout(self): + def test_invalid_timeout(self, httpbin): with pytest.raises(ValueError) as e: requests.get(httpbin('get'), timeout=(3, 4, 5)) assert '(connect, read)' in str(e) @@ -1549,7 +1549,7 @@ class TestTimeout: requests.get(httpbin('get'), timeout="foo") assert 'must be an int or float' in str(e) - def test_none_timeout(self): + def test_none_timeout(self, httpbin): """ Check that you can set None as a valid timeout value. To actually test this behavior, we'd want to check that setting the @@ -1561,7 +1561,7 @@ class TestTimeout: r = requests.get(httpbin('get'), timeout=None) assert r.status_code == 200 - def test_read_timeout(self): + def test_read_timeout(self, httpbin): try: requests.get(httpbin('delay/10'), timeout=(None, 0.1)) assert False, "The recv() request should time out." @@ -1583,7 +1583,7 @@ class TestTimeout: except ConnectTimeout: pass - def test_encoded_methods(self): + def test_encoded_methods(self, httpbin): """See: https://github.com/kennethreitz/requests/issues/2316""" r = requests.request(b'GET', httpbin('get')) assert r.ok @@ -1634,7 +1634,7 @@ class TestRedirects: 'proxies': {}, } - def test_requests_are_updated_each_time(self): + def test_requests_are_updated_each_time(self, httpbin): session = RedirectSession([303, 307]) prep = requests.Request('POST', httpbin('post')).prepare() r0 = session.send(prep) @@ -1716,7 +1716,7 @@ def test_prepare_unicode_url(): assert_copy(p, p.copy()) -def test_urllib3_retries(): +def test_urllib3_retries(httpbin): from requests.packages.urllib3.util import Retry s = requests.Session() s.mount('http://', HTTPAdapter(max_retries=Retry( @@ -1727,7 +1727,7 @@ def test_urllib3_retries(): s.get(httpbin('status/500')) -def test_urllib3_pool_connection_closed(): +def test_urllib3_pool_connection_closed(httpbin): s = requests.Session() s.mount('http://', HTTPAdapter(pool_connections=0, pool_maxsize=0)) From a8fdba15b12550a9bb08b1fa1e4d78b5d9508c1f Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 22 Oct 2015 12:25:05 +0100 Subject: [PATCH 512/545] Use py.test style assertions --- test_requests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_requests.py b/test_requests.py index 6caaef0d..3c8259ef 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1136,8 +1136,8 @@ class TestRequests(object): with contextlib.closing(s.get(httpbin('stream/4'), stream=True)) as response: pass - self.assertFalse(response._content_consumed) - self.assertTrue(response.raw.closed) + assert not response._content_consumed is False + assert response.raw.closed @pytest.mark.xfail def test_response_iter_lines_reentrant(self, httpbin): From 70d7b134c806cf75c5c4343f9cefa92f9345635a Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 22 Oct 2015 12:25:15 +0100 Subject: [PATCH 513/545] Stop being so specific when testing error strings. --- test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index 3c8259ef..27b8fcde 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1734,7 +1734,7 @@ def test_urllib3_pool_connection_closed(httpbin): try: s.get(httpbin('status/200')) except ConnectionError as e: - assert u"HTTPConnectionPool(host='httpbin.org', port=80): Pool is closed." in str(e) + assert u"Pool is closed." in str(e) def test_vendor_aliases(): from requests.packages import urllib3 From be564e500cdebff5c0325808adb9ff33610b7c78 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Fri, 23 Oct 2015 17:28:53 +0100 Subject: [PATCH 514/545] Fixup assertion broken in rewrite to py.test Broke it in 0364a8ca7fc67b89f3e54d50fa3936d65e979b96 --- test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index 27b8fcde..38eedb8e 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1136,7 +1136,7 @@ class TestRequests(object): with contextlib.closing(s.get(httpbin('stream/4'), stream=True)) as response: pass - assert not response._content_consumed is False + assert response._content_consumed is False assert response.raw.closed @pytest.mark.xfail From 15cf19431a99a5334458b9478223cec63a494549 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Fri, 23 Oct 2015 17:30:58 +0100 Subject: [PATCH 515/545] Pin httpbin version. --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 75391678..ad5da761 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ py==1.4.30 pytest==2.8.1 pytest-cov==2.1.0 pytest-httpbin==0.0.7 +httpbin==0.4.0 wheel From 7c91e27d0142c177add6a1f726712f58914d4f52 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 5 Nov 2015 10:37:53 +0000 Subject: [PATCH 516/545] Remove threaded test --- test_requests.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/test_requests.py b/test_requests.py index 38eedb8e..24284c04 100755 --- a/test_requests.py +++ b/test_requests.py @@ -448,21 +448,6 @@ class TestRequests(object): r = requests.get(url, auth=auth) assert '"auth"' in r.request.headers['Authorization'] - def test_DIGESTAUTH_THREADED(self, httpbin): - - auth = HTTPDigestAuth('user', 'pass') - url = httpbin('digest-auth', 'auth', 'user', 'pass') - session = requests.Session() - session.auth=auth - - def do_request(i): - r = session.get(url) - assert '"auth"' in r.request.headers['Authorization'] - return 1 - if ThreadPool is not None: - pool = ThreadPool(processes=50) - pool.map(do_request, range(100)) - def test_POSTBIN_GET_POST_FILES(self, httpbin): url = httpbin('post') From 43b90d7f72c4793aa538dd7eac2ac126da2a3aaf Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 5 Nov 2015 13:47:04 +0000 Subject: [PATCH 517/545] Remove remaining test that contacts external server. --- test_requests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_requests.py b/test_requests.py index 24284c04..08d2f148 100755 --- a/test_requests.py +++ b/test_requests.py @@ -341,7 +341,7 @@ class TestRequests(object): def test_connection_error_invalid_port(self): """Connecting to an invalid port should raise a ConnectionError""" with pytest.raises(ConnectionError): - requests.get("http://httpbin.org:1", timeout=1) + requests.get("http://localhost:1", timeout=1) def test_LocationParseError(self): """Inputing a URL that cannot be parsed should raise an InvalidURL error""" From e67cd15bc81943cba10786cb3f5de0473d9e8550 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Thu, 5 Nov 2015 18:53:14 +0000 Subject: [PATCH 518/545] Fix breakage introduced by 8f591682 --- requests/adapters.py | 4 ++-- test_requests.py | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/requests/adapters.py b/requests/adapters.py index c69c082e..fbe6b661 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -398,10 +398,10 @@ class HTTPAdapter(BaseAdapter): try: # For Python 2.7+ versions, use buffering of HTTP # responses - r = conn.getresponse(buffering=True) + r = low_conn.getresponse(buffering=True) except TypeError: # For compatibility with Python 2.6 versions and back - r = conn.getresponse() + r = low_conn.getresponse() resp = HTTPResponse.from_httplib( r, diff --git a/test_requests.py b/test_requests.py index 08d2f148..3a8e4b05 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1133,6 +1133,13 @@ class TestRequests(object): next(r.iter_lines()) assert len(list(r.iter_lines())) == 3 + def test_chunked_upload(self, httpbin): + """Can safely send generators.""" + data = (c for c in 'abc') + r = requests.post(httpbin('post'), data=data, stream=True) + assert r.status_code == 200 + assert r.request.headers['Transfer-Encoding'] == 'chunked' + class TestContentEncodingDetection(unittest.TestCase): From 1ced0040bdc7da87b9fb05c57fc2443be012caf9 Mon Sep 17 00:00:00 2001 From: Artur Siekielski Date: Fri, 6 Nov 2015 21:54:43 +0100 Subject: [PATCH 519/545] Assure session is closed on exception. --- requests/api.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/requests/api.py b/requests/api.py index 72a777b2..09ec731b 100644 --- a/requests/api.py +++ b/requests/api.py @@ -46,13 +46,11 @@ def request(method, url, **kwargs): """ - session = sessions.Session() - response = session.request(method=method, url=url, **kwargs) - # By explicitly closing the session, we avoid leaving sockets open which - # can trigger a ResourceWarning in some cases, and look like a memory leak - # in others. - session.close() - return response + # By using the 'with' statement we are sure the session is closed, thus we + # avoid leaving sockets open which can trigger a ResourceWarning in some + # cases, and look like a memory leak in others. + with sessions.Session() as session: + return session.request(method=method, url=url, **kwargs) def get(url, params=None, **kwargs): From c1c164ba80a35e63e13f00d20bd32f2f08c4fc51 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 7 Nov 2015 14:08:15 +0000 Subject: [PATCH 520/545] Removed test of chunked encoding --- test_requests.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/test_requests.py b/test_requests.py index 3a8e4b05..08d2f148 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1133,13 +1133,6 @@ class TestRequests(object): next(r.iter_lines()) assert len(list(r.iter_lines())) == 3 - def test_chunked_upload(self, httpbin): - """Can safely send generators.""" - data = (c for c in 'abc') - r = requests.post(httpbin('post'), data=data, stream=True) - assert r.status_code == 200 - assert r.request.headers['Transfer-Encoding'] == 'chunked' - class TestContentEncodingDetection(unittest.TestCase): From a25a641fb479b17e582effa4142f21701a958805 Mon Sep 17 00:00:00 2001 From: vienno Date: Tue, 10 Nov 2015 12:36:36 +0100 Subject: [PATCH 521/545] unnest deep loop --- requests/cookies.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/requests/cookies.py b/requests/cookies.py index 88b478c7..9d9a941d 100644 --- a/requests/cookies.py +++ b/requests/cookies.py @@ -143,10 +143,11 @@ def remove_cookie_by_name(cookiejar, name, domain=None, path=None): """ clearables = [] for cookie in cookiejar: - if cookie.name == name: - if domain is None or domain == cookie.domain: - if path is None or path == cookie.path: - clearables.append((cookie.domain, cookie.path, cookie.name)) + if cookie.name != name: + continue + if domain is None or domain == cookie.domain: + if path is None or path == cookie.path: + clearables.append((cookie.domain, cookie.path, cookie.name)) for domain, path, name in clearables: cookiejar.clear(domain, path, name) From 89178ebcdca2d83f7fa43c766f44f4a60248400f Mon Sep 17 00:00:00 2001 From: vienno Date: Tue, 10 Nov 2015 13:47:40 +0100 Subject: [PATCH 522/545] Complete unnesting of if statements in cookie loop --- requests/cookies.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/requests/cookies.py b/requests/cookies.py index 9d9a941d..df78dfac 100644 --- a/requests/cookies.py +++ b/requests/cookies.py @@ -145,9 +145,11 @@ def remove_cookie_by_name(cookiejar, name, domain=None, path=None): for cookie in cookiejar: if cookie.name != name: continue - if domain is None or domain == cookie.domain: - if path is None or path == cookie.path: - clearables.append((cookie.domain, cookie.path, cookie.name)) + if domain and domain != cookie.domain: + continue + if path and path != cookie.path: + continue + clearables.append((cookie.domain, cookie.path, cookie.name)) for domain, path, name in clearables: cookiejar.clear(domain, path, name) From d849d6e90753cf76425d75fd3cb35631f1a5c5b4 Mon Sep 17 00:00:00 2001 From: vienno Date: Tue, 10 Nov 2015 13:58:09 +0100 Subject: [PATCH 523/545] Replace 'if x' with 'if x is not None' in cookie loop --- requests/cookies.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/cookies.py b/requests/cookies.py index df78dfac..d097d7db 100644 --- a/requests/cookies.py +++ b/requests/cookies.py @@ -145,9 +145,9 @@ def remove_cookie_by_name(cookiejar, name, domain=None, path=None): for cookie in cookiejar: if cookie.name != name: continue - if domain and domain != cookie.domain: + if domain is not None and domain != cookie.domain: continue - if path and path != cookie.path: + if path is not None and path != cookie.path: continue clearables.append((cookie.domain, cookie.path, cookie.name)) From cf9da5b52e5e3f97c1d87940f8d19c7c3383dcba Mon Sep 17 00:00:00 2001 From: vienno Date: Tue, 10 Nov 2015 14:05:24 +0100 Subject: [PATCH 524/545] add myself to authors --- AUTHORS.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index b0594a6b..805a05ef 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -161,3 +161,4 @@ Patches and Suggestions - Colin Dickson (`@colindickson `_) - Smiley Barry (`@smiley `_) - Shagun Sodhani (`@shagunsodhani `_) +- Robin Linderborg (`@robinlinderborg `_) From f835843f876c9757e2bc66bcba0459784a2a595a Mon Sep 17 00:00:00 2001 From: vienno Date: Tue, 10 Nov 2015 14:07:55 +0100 Subject: [PATCH 525/545] error in github bio link --- AUTHORS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 805a05ef..6a7f889b 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -161,4 +161,4 @@ Patches and Suggestions - Colin Dickson (`@colindickson `_) - Smiley Barry (`@smiley `_) - Shagun Sodhani (`@shagunsodhani `_) -- Robin Linderborg (`@robinlinderborg `_) +- Robin Linderborg (`@vienno `_) From 0a1aaca024f34cc3b2d411f56905f9cfb7a3003b Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 10 Nov 2015 21:15:29 -0600 Subject: [PATCH 526/545] Add failing test for #2872 --- test_requests.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test_requests.py b/test_requests.py index 08d2f148..e83cbc44 100755 --- a/test_requests.py +++ b/test_requests.py @@ -1351,6 +1351,13 @@ class UtilsTestCase(unittest.TestCase): assert super_len( cStringIO.StringIO('but some how, some way...')) == 25 + def test_super_len_correctly_calculates_len_of_partially_read_file(self): + """Ensure that we handle partially consumed file like objects.""" + from requests.utils import super_len + s = StringIO.StringIO() + s.write('foobarbogus') + assert super_len(s) == 0 + def test_get_environ_proxies_ip_ranges(self): """Ensures that IP addresses are correctly matches with ranges in no_proxy variable.""" @@ -1721,6 +1728,7 @@ def test_urllib3_pool_connection_closed(httpbin): except ConnectionError as e: assert u"Pool is closed." in str(e) + def test_vendor_aliases(): from requests.packages import urllib3 from requests.packages import chardet @@ -1728,5 +1736,6 @@ def test_vendor_aliases(): with pytest.raises(ImportError): from requests.packages import webbrowser + if __name__ == '__main__': unittest.main() From 5397ef71b37ebe1fdf8af95a9a3b561baffe9dd8 Mon Sep 17 00:00:00 2001 From: Ian Cordasco Date: Tue, 10 Nov 2015 21:22:24 -0600 Subject: [PATCH 527/545] Fix super_len for partially read files Closes #2872 --- requests/utils.py | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/requests/utils.py b/requests/utils.py index 5975d0f1..132cd2b5 100644 --- a/requests/utils.py +++ b/requests/utils.py @@ -48,19 +48,26 @@ def dict_to_sequence(d): def super_len(o): + total_length = 0 + current_position = 0 + if hasattr(o, '__len__'): - return len(o) + total_length = len(o) - if hasattr(o, 'len'): - return o.len + elif hasattr(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: fileno = o.fileno() except io.UnsupportedOperation: pass else: - filesize = os.fstat(fileno).st_size + total_length = os.fstat(fileno).st_size # Having used fstat to determine the file length, we need to # confirm that this file was opened up in binary mode. @@ -75,11 +82,10 @@ def super_len(o): FileModeWarning ) - return filesize + if hasattr(o, 'tell'): + current_position = o.tell() - if hasattr(o, 'getvalue'): - # e.g. BytesIO, cStringIO.StringIO - return len(o.getvalue()) + return max(0, total_length - current_position) def get_netrc_auth(url, raise_errors=False): From de2a5f43320288ed29023564574e299f2d254160 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 12 Nov 2015 16:54:47 +0300 Subject: [PATCH 528/545] Remove redundant json import. --- docs/user/quickstart.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/user/quickstart.rst b/docs/user/quickstart.rst index 5117ed0c..1ff66593 100644 --- a/docs/user/quickstart.rst +++ b/docs/user/quickstart.rst @@ -238,7 +238,6 @@ For example, the GitHub API v3 accepts JSON-Encoded POST/PATCH data:: Instead of encoding the ``dict`` yourself, you can also pass it directly using the ``json`` parameter (added in version 2.4.2) and it will be encoded automatically:: - >>> import json >>> url = 'https://api.github.com/some/endpoint' >>> payload = {'some': 'data'} From 6597b5383485c8ffd06f5593c338d4793b7b8e82 Mon Sep 17 00:00:00 2001 From: Philip Lorenz Date: Thu, 19 Nov 2015 20:05:03 +0100 Subject: [PATCH 529/545] Specify minimum pyOpenSSL version urllib3 requires "set_tlsext_host_name" which was only added in pyOpenSSL 0.13. As some distributions (e.g. Ubuntu 12.04) still ship an older version enforce the correct minimum version during installation. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 745531bf..b7ed12ba 100755 --- a/setup.py +++ b/setup.py @@ -69,6 +69,6 @@ setup( 'Programming Language :: Python :: 3.5', ), extras_require={ - 'security': ['pyOpenSSL', 'ndg-httpsclient', 'pyasn1'], + 'security': ['pyOpenSSL>=0.13', 'ndg-httpsclient', 'pyasn1'], }, ) From e5d6630888e530053ca3167363772fc0f54249d5 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Fri, 20 Nov 2015 11:29:35 +0000 Subject: [PATCH 530/545] Add basic CONTRIBUTING.md file --- CONTRIBUTING.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..6b70c4bf --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,53 @@ +# Contribution Guidelines + +Before opening any issues or proposing any pull requests, please do the +following: + +1. Read our [Contributor's Guide](http://docs.python-requests.org/en/latest/dev/contributing/). +2. Understand our [development philosophy](http://docs.python-requests.org/en/latest/dev/philosophy/). + +To get the greatest chance of helpful responses, please also observe the +following additional notes. + +## Questions + +The GitHub issue tracker is for *bug reports* and *feature requests*. Please do +not use it to ask questions about how to use Requests. These questions should +instead be directed to [Stack Overflow](https://stackoverflow.com/). Make sure +that your question is tagged with the `python-requests` tag when asking it on +Stack Overflow, to ensure that it is answered promptly and accurately. + +## Good Bug Reports + +Please be aware of the following things when filing bug reports: + +1. Avoid raising duplicate issues. *Please* use the GitHub issue search feature + to check whether your bug report or feature request has been mentioned in + the past. Duplicate bug reports and feature requests are a huge maintenance + burden on the limited resources of the project. If it is clear from your + report that you would have struggled to find the original, that's ok, but + if searching for a selection of words in your issue title would have found + the duplicate then the issue will likely be closed extremely abruptly. +2. When filing bug reports about exceptions or tracebacks, please include the + *complete* traceback. Partial tracebacks, or just the exception text, are + not helpful. Issues that do not contain complete tracebacks may be closed + without warning. +3. Make sure you provide a suitable amount of information to work with. This + means you should provide: + + - Guidance on **how to reproduce the issue**. Ideally, this should be a + *small* code sample that can be run immediately by the maintainers. + Failing that, let us know what you're doing, how often it happens, what + environment you're using, etc. Be thorough: it prevents us needing to ask + further questions. + - Tell us **what you expected to happen**. When we run your example code, + what are we expecting to happen? What does "success" look like for your + code? + - Tell us **what actually happens**. It's not helpful for you to say "it + doesn't work" or "it fails". Tell us *how* it fails: do you get an + exception? A hang? A non-200 status code? How was the actual result + different from your expected result? + + If you do not provide all of these things, it will take us much longer to + fix your problem. If we ask you to clarify these and you never respond, we + will close your issue without fixing it. From 085a8768a232187f12d9c195c431ea85c33fb4b8 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Fri, 20 Nov 2015 13:59:59 +0000 Subject: [PATCH 531/545] Where did you get Requests? --- CONTRIBUTING.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b70c4bf..57aef1e0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -47,6 +47,10 @@ Please be aware of the following things when filing bug reports: doesn't work" or "it fails". Tell us *how* it fails: do you get an exception? A hang? A non-200 status code? How was the actual result different from your expected result? + - Tell us **what version of Requests you're using**, and + **how you installed it**. Different versions of Requests behave + differently and have different bugs, and some distributors of Requests + ship patches on top of the code we supply. If you do not provide all of these things, it will take us much longer to fix your problem. If we ask you to clarify these and you never respond, we From 6f6b2fd1d5ca6b281a841e8a31e32eb6c34c429b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Braulio=20Valdivielso=20Mart=C3=ADnez?= Date: Mon, 23 Nov 2015 20:52:06 +0100 Subject: [PATCH 532/545] Set 'Transfer-Encoding: chunked' if data file length equals 0 --- requests/models.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requests/models.py b/requests/models.py index 639565cf..43a297e6 100644 --- a/requests/models.py +++ b/requests/models.py @@ -446,6 +446,8 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if data: body = self._encode_params(data) if isinstance(data, basestring) or hasattr(data, 'read'): + if hasattr(data, 'fileno') and length == 0: + self.headers['Transfer-Encoding'] = 'chunked' content_type = None else: content_type = 'application/x-www-form-urlencoded' From 4c82dbab6fc3ad4b66babb1b01d21a26db602579 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Braulio=20Valdivielso=20Mart=C3=ADnez?= Date: Tue, 24 Nov 2015 13:42:13 +0100 Subject: [PATCH 533/545] Fix 'Transfer-Encoding: chunked' change --- requests/models.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/requests/models.py b/requests/models.py index 43a297e6..81bd1a84 100644 --- a/requests/models.py +++ b/requests/models.py @@ -434,7 +434,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if files: raise NotImplementedError('Streamed bodies and files are mutually exclusive.') - if length is not None: + if length: self.headers['Content-Length'] = builtin_str(length) else: self.headers['Transfer-Encoding'] = 'chunked' @@ -446,8 +446,6 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if data: body = self._encode_params(data) if isinstance(data, basestring) or hasattr(data, 'read'): - if hasattr(data, 'fileno') and length == 0: - self.headers['Transfer-Encoding'] = 'chunked' content_type = None else: content_type = 'application/x-www-form-urlencoded' From 68db4132e36d23dc371219dd99ac51b1c1b1f852 Mon Sep 17 00:00:00 2001 From: Olle Lundberg Date: Wed, 25 Nov 2015 02:02:41 +0100 Subject: [PATCH 534/545] Update adapters.py Remove duplicate word. --- requests/adapters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/adapters.py b/requests/adapters.py index cecf6c2a..6266d5be 100644 --- a/requests/adapters.py +++ b/requests/adapters.py @@ -108,7 +108,7 @@ class HTTPAdapter(BaseAdapter): def __setstate__(self, state): # 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.config = {} From 27f5a15364f921fc314eadf3b0370dbfc02c3190 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Thu, 26 Nov 2015 09:34:01 -0500 Subject: [PATCH 535/545] Include a link to my GitHub profile. --- AUTHORS.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 6a7f889b..d4bfaf96 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -134,7 +134,7 @@ Patches and Suggestions - Kevin Burke - Flavio Curella - David Pursehouse (`@dpursehouse `_) -- Jon Parise +- Jon Parise (`@jparise `_) - Alexander Karpinsky (`@homm86 `_) - Marc Schlaich (`@schlamar `_) - Park Ilsu (`@daftshady `_) From 23d9b077d8b2db9d13c1edd97ffb65b0582631d0 Mon Sep 17 00:00:00 2001 From: Jakub Wilk Date: Tue, 1 Dec 2015 12:24:56 +0100 Subject: [PATCH 536/545] Fix typos --- HISTORY.rst | 6 +++--- requests/cookies.py | 2 +- requests/models.py | 4 ++-- requests/sessions.py | 4 ++-- test_requests.py | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 34226f49..7f20389e 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -50,7 +50,7 @@ dev (XXXX) - The ``json`` parameter to ``post()`` and friends will now only be used if neither ``data`` nor ``files`` are present, consistent with the documentation. -- We now ignore empty fields in the ``NO_PROXY`` enviroment variable. +- We now ignore empty fields in the ``NO_PROXY`` environment variable. - Fixed problem where ``httplib.BadStatusLine`` would get raised if combining ``stream=True`` with ``contextlib.closing``. - Prevented bugs where we would attempt to return the same connection back to @@ -533,7 +533,7 @@ This is not a backwards compatible change. - Improved mime-compatible JSON handling - Proxy fixes - Path hack fixes -- Case-Insensistive Content-Encoding headers +- Case-Insensitive Content-Encoding headers - Support for CJK parameters in form posts @@ -620,7 +620,7 @@ This is not a backwards compatible change. +++++++++++++++++++ - Removal of Requests.async in favor of `grequests `_ -- Allow disabling of cookie persistiance. +- Allow disabling of cookie persistence. - New implementation of safe_mode - cookies.get now supports default argument - Session cookies not saved when Session.request is called with return_response=False diff --git a/requests/cookies.py b/requests/cookies.py index d097d7db..d61ec2da 100644 --- a/requests/cookies.py +++ b/requests/cookies.py @@ -368,7 +368,7 @@ def _copy_cookie_jar(jar): return None if hasattr(jar, 'copy'): - # We're dealing with an instane of RequestsCookieJar + # We're dealing with an instance of RequestsCookieJar return jar.copy() # We're dealing with a generic CookieJar instance new_jar = copy.copy(jar) diff --git a/requests/models.py b/requests/models.py index 639565cf..b01d3487 100644 --- a/requests/models.py +++ b/requests/models.py @@ -324,7 +324,7 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): def prepare_url(self, url, params): """Prepares the given HTTP URL.""" #: 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'') #: on python 3.x. #: https://github.com/kennethreitz/requests/pull/2238 @@ -631,7 +631,7 @@ class Response(object): @property 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)) @property diff --git a/requests/sessions.py b/requests/sessions.py index 12879a5b..6d607459 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -325,7 +325,7 @@ class Session(SessionRedirectMixin): #: limit, a :class:`TooManyRedirects` exception is raised. self.max_redirects = DEFAULT_REDIRECT_LIMIT - #: Trust environement settings for proxy configuration, default + #: Trust environment settings for proxy configuration, default #: authentication and similar. self.trust_env = True @@ -634,7 +634,7 @@ class Session(SessionRedirectMixin): 'cert': cert} 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(): if url.lower().startswith(prefix): diff --git a/test_requests.py b/test_requests.py index e83cbc44..137edc08 100755 --- a/test_requests.py +++ b/test_requests.py @@ -612,7 +612,7 @@ class TestRequests(object): assert prep.hooks['response'] != [] assert prep.hooks['response'] == [hook] - def test_session_hooks_are_overriden_by_request_hooks(self, httpbin): + def test_session_hooks_are_overridden_by_request_hooks(self, httpbin): hook1 = lambda x, *args, **kwargs: x hook2 = lambda x, *args, **kwargs: x assert hook1 is not hook2 From 5c19d3e07b68145c7af9ad70dcaa2e48dd06b0b5 Mon Sep 17 00:00:00 2001 From: Matt Jordan Date: Sat, 5 Dec 2015 09:47:00 -0600 Subject: [PATCH 537/545] requests/auth: Handle an empty 'qop' attribute in a Authenticate challenge Some malfunctioning HTTP servers may return a qop directive with no token, as opposed to correctly omitting the qop directive completely. For example: header: WWW-Authenticate: Digest realm="foobar_api_auth", qop="", nonce="a12059eaaad0b86ece8f62f04cbafed6", algorithm="MD5", stale="false" Prior to this patch, requests would respond with a 'None' Authorization header. While the server is certainly incorrect, this patch updates requests to be more tolerant to this kind of shenaniganry. If we receive an empty string for the value of the qop attribute, we instead treat that as if the qop attribute was simply not provided. Closes #2916 --- requests/auth.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/auth.py b/requests/auth.py index 8c4e847f..2af55fb5 100644 --- a/requests/auth.py +++ b/requests/auth.py @@ -136,7 +136,7 @@ class HTTPDigestAuth(AuthBase): if _algorithm == 'MD5-SESS': HA1 = hash_utf8('%s:%s:%s' % (HA1, nonce, cnonce)) - if qop is None: + if not qop: respdig = KD(HA1, "%s:%s" % (nonce, HA2)) elif qop == 'auth' or 'auth' in qop.split(','): noncebit = "%s:%s:%s:%s:%s" % ( From 3ad66349ea651d13459d7cd2db1613c1b91b40e1 Mon Sep 17 00:00:00 2001 From: Brian Samek Date: Wed, 9 Dec 2015 11:21:21 -0500 Subject: [PATCH 538/545] Add hint to :param verify. It is not clear that :param verify defaults to True. The way the verify portion of the docstring is written it looks like it defaults to False, and you have to pass in True if you'd like the SSL cert to be verified, but the opposite is the case. --- requests/sessions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requests/sessions.py b/requests/sessions.py index 6d607459..a1d19686 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -434,7 +434,7 @@ class Session(SessionRedirectMixin): :param stream: (optional) whether to immediately download the response content. Defaults to ``False``. :param verify: (optional) if ``True``, 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). If Tuple, ('cert', 'key') pair. """ From 9a20afd6b5140da4b9f187a3c0c6ea7010baeee9 Mon Sep 17 00:00:00 2001 From: Brian Samek Date: Fri, 11 Dec 2015 13:32:09 -0500 Subject: [PATCH 539/545] Refactor default params Specified the default argument for params that have a default in the docstring so that the default is easier to see from the code. Modified the docstring in api.py to match the docstring in sessions.py. --- AUTHORS.rst | 1 + requests/api.py | 2 +- requests/sessions.py | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index d4bfaf96..7d0ac65a 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -162,3 +162,4 @@ Patches and Suggestions - Smiley Barry (`@smiley `_) - Shagun Sodhani (`@shagunsodhani `_) - Robin Linderborg (`@vienno `_) +- Brian Samek(`@bsamek ` object diff --git a/requests/sessions.py b/requests/sessions.py index a1d19686..79d52ee9 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -400,8 +400,8 @@ class Session(SessionRedirectMixin): allow_redirects=True, proxies=None, hooks=None, - stream=None, - verify=None, + stream=False, + verify=True, cert=None, json=None): """Constructs a :class:`Request `, prepares it and sends it. @@ -433,7 +433,7 @@ class Session(SessionRedirectMixin): hostname to the URL of the proxy. :param stream: (optional) whether to immediately download the response 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. Defaults to ``True``. :param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair. From 9aca57d4bf7fe7906dd7b6d9e7c4836b6f38735d Mon Sep 17 00:00:00 2001 From: Brian Samek Date: Fri, 11 Dec 2015 16:47:47 -0500 Subject: [PATCH 540/545] Revert changes to params This reverts commit 9a20afd6b5140da4b9f187a3c0c6ea7010baeee9. Docstring changes were desirable, but changes to params were not. --- requests/sessions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requests/sessions.py b/requests/sessions.py index 79d52ee9..9eaa36ae 100644 --- a/requests/sessions.py +++ b/requests/sessions.py @@ -400,8 +400,8 @@ class Session(SessionRedirectMixin): allow_redirects=True, proxies=None, hooks=None, - stream=False, - verify=True, + stream=None, + verify=None, cert=None, json=None): """Constructs a :class:`Request `, prepares it and sends it. From 3c0f0b9ab510d7ac6b556088d29fcc050c995357 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Tue, 15 Dec 2015 14:47:20 +0000 Subject: [PATCH 541/545] Update urllib3 to 1.13 --- requests/packages/urllib3/__init__.py | 32 +++++++++-- requests/packages/urllib3/_collections.py | 7 +-- requests/packages/urllib3/connection.py | 28 +++++----- requests/packages/urllib3/connectionpool.py | 34 ++++++------ .../packages/urllib3/contrib/appengine.py | 23 ++++---- requests/packages/urllib3/contrib/ntlmpool.py | 1 + .../packages/urllib3/contrib/pyopenssl.py | 35 ++++++------ requests/packages/urllib3/exceptions.py | 14 +++-- requests/packages/urllib3/fields.py | 1 + requests/packages/urllib3/filepost.py | 1 + .../packages/urllib3/packages/__init__.py | 1 + .../packages/ssl_match_hostname/.gitignore | 1 + requests/packages/urllib3/poolmanager.py | 3 +- requests/packages/urllib3/request.py | 4 +- requests/packages/urllib3/response.py | 53 ++++++++++++++----- requests/packages/urllib3/util/__init__.py | 20 +++++++ requests/packages/urllib3/util/connection.py | 1 + requests/packages/urllib3/util/request.py | 1 + requests/packages/urllib3/util/response.py | 3 +- requests/packages/urllib3/util/retry.py | 7 +-- requests/packages/urllib3/util/ssl_.py | 39 ++++++++++++-- requests/packages/urllib3/util/timeout.py | 8 +-- requests/packages/urllib3/util/url.py | 5 +- 23 files changed, 228 insertions(+), 94 deletions(-) create mode 100644 requests/packages/urllib3/packages/ssl_match_hostname/.gitignore diff --git a/requests/packages/urllib3/__init__.py b/requests/packages/urllib3/__init__.py index 86bb71d2..43c88b14 100644 --- a/requests/packages/urllib3/__init__.py +++ b/requests/packages/urllib3/__init__.py @@ -2,10 +2,8 @@ urllib3 - Thread-safe connection pooling and re-using. """ -__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' -__license__ = 'MIT' -__version__ = '1.12' - +from __future__ import absolute_import +import warnings from .connectionpool import ( HTTPConnectionPool, @@ -32,8 +30,30 @@ except ImportError: def emit(self, record): pass +__author__ = 'Andrey Petrov (andrey.petrov@shazow.net)' +__license__ = 'MIT' +__version__ = '1.13' + +__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()) + def add_stderr_logger(level=logging.DEBUG): """ Helper for quickly adding a StreamHandler to the logger. Useful for @@ -55,7 +75,6 @@ def add_stderr_logger(level=logging.DEBUG): del NullHandler -import warnings # SecurityWarning's always go off by default. warnings.simplefilter('always', exceptions.SecurityWarning, append=True) # SubjectAltNameWarning's should go off once per host @@ -63,6 +82,9 @@ warnings.simplefilter('default', exceptions.SubjectAltNameWarning) # 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) + def disable_warnings(category=exceptions.HTTPWarning): """ diff --git a/requests/packages/urllib3/_collections.py b/requests/packages/urllib3/_collections.py index b68b9a59..67f3ce99 100644 --- a/requests/packages/urllib3/_collections.py +++ b/requests/packages/urllib3/_collections.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from collections import Mapping, MutableMapping try: from threading import RLock @@ -167,7 +168,7 @@ class HTTPHeaderDict(MutableMapping): def __ne__(self, other): return not self.__eq__(other) - if not PY3: # Python 2 + if not PY3: # Python 2 iterkeys = MutableMapping.iterkeys itervalues = MutableMapping.itervalues @@ -234,7 +235,7 @@ class HTTPHeaderDict(MutableMapping): """ if len(args) > 1: raise TypeError("extend() takes at most 1 positional " - "arguments ({} given)".format(len(args))) + "arguments ({0} given)".format(len(args))) other = args[0] if len(args) >= 1 else () if isinstance(other, HTTPHeaderDict): @@ -304,7 +305,7 @@ class HTTPHeaderDict(MutableMapping): return list(self.iteritems()) @classmethod - def from_httplib(cls, message): # Python 2 + 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 diff --git a/requests/packages/urllib3/connection.py b/requests/packages/urllib3/connection.py index 3eab1e28..bb7f3e55 100644 --- a/requests/packages/urllib3/connection.py +++ b/requests/packages/urllib3/connection.py @@ -1,4 +1,6 @@ +from __future__ import absolute_import import datetime +import os import sys import socket from socket import error as SocketError, timeout as SocketTimeout @@ -6,18 +8,13 @@ import warnings from .packages import six 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: - from httplib import HTTPConnection as _HTTPConnection, HTTPException - - -class DummyConnection(object): - "Used to detect a failed ConnectionCls import." - pass - + from httplib import HTTPConnection as _HTTPConnection + from httplib import HTTPException # noqa: unused in this module try: # Compiled with SSL? - HTTPSConnection = DummyConnection import ssl BaseSSLError = ssl.SSLError except (ImportError, AttributeError): # Platform-specific: No SSL. @@ -61,6 +58,11 @@ port_by_scheme = { RECENT_DATE = datetime.date(2014, 1, 1) +class DummyConnection(object): + """Used to detect a failed ConnectionCls import.""" + pass + + class HTTPConnection(_HTTPConnection, object): """ Based on httplib.HTTPConnection but provides an extra constructor @@ -205,10 +207,10 @@ class VerifiedHTTPSConnection(HTTPSConnection): self.key_file = key_file self.cert_file = cert_file self.cert_reqs = cert_reqs - self.ca_certs = ca_certs - self.ca_cert_dir = ca_cert_dir self.assert_hostname = assert_hostname 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): # Add certificate verification @@ -265,8 +267,8 @@ class VerifiedHTTPSConnection(HTTPSConnection): ) match_hostname(cert, self.assert_hostname or hostname) - self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED - or self.assert_fingerprint is not None) + self.is_verified = (resolved_cert_reqs == ssl.CERT_REQUIRED or + self.assert_fingerprint is not None) if ssl: diff --git a/requests/packages/urllib3/connectionpool.py b/requests/packages/urllib3/connectionpool.py index b38ac68d..995b4167 100644 --- a/requests/packages/urllib3/connectionpool.py +++ b/requests/packages/urllib3/connectionpool.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import errno import logging import sys @@ -10,7 +11,8 @@ try: # Python 3 from queue import LifoQueue, Empty, Full except ImportError: 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 ( @@ -22,7 +24,6 @@ from .exceptions import ( LocationValueError, MaxRetryError, ProxyError, - ConnectTimeoutError, ReadTimeoutError, SSLError, TimeoutError, @@ -35,7 +36,7 @@ from .connection import ( port_by_scheme, DummyConnection, HTTPConnection, HTTPSConnection, VerifiedHTTPSConnection, - HTTPException, BaseSSLError, ConnectionError + HTTPException, BaseSSLError, ) from .request import RequestMethods from .response import HTTPResponse @@ -54,7 +55,7 @@ log = logging.getLogger(__name__) _Default = object() -## Pool objects +# Pool objects class ConnectionPool(object): """ Base class for all connection pools, such as @@ -68,8 +69,7 @@ class ConnectionPool(object): if not host: raise LocationValueError("No host specified.") - # httplib doesn't like it when we include brackets in ipv6 addresses - self.host = host.strip('[]') + self.host = host self.port = port def __str__(self): @@ -645,22 +645,24 @@ class HTTPConnectionPool(ConnectionPool, RequestMethods): return response log.info("Redirecting %s -> %s" % (url, redirect_location)) - return self.urlopen(method, redirect_location, body, headers, - retries=retries, redirect=redirect, - assert_same_host=assert_same_host, - timeout=timeout, pool_timeout=pool_timeout, - release_conn=release_conn, **response_kw) + return self.urlopen( + method, redirect_location, body, headers, + retries=retries, redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, pool_timeout=pool_timeout, + release_conn=release_conn, **response_kw) # Check if we should retry the HTTP response. if retries.is_forced_retry(method, status_code=response.status): retries = retries.increment(method, url, response=response, _pool=self) retries.sleep() log.info("Forced retry: %s" % url) - return self.urlopen(method, url, body, headers, - retries=retries, redirect=redirect, - assert_same_host=assert_same_host, - timeout=timeout, pool_timeout=pool_timeout, - release_conn=release_conn, **response_kw) + return self.urlopen( + method, url, body, headers, + retries=retries, redirect=redirect, + assert_same_host=assert_same_host, + timeout=timeout, pool_timeout=pool_timeout, + release_conn=release_conn, **response_kw) return response diff --git a/requests/packages/urllib3/contrib/appengine.py b/requests/packages/urllib3/contrib/appengine.py index ed9d8b81..884cdb22 100644 --- a/requests/packages/urllib3/contrib/appengine.py +++ b/requests/packages/urllib3/contrib/appengine.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import logging import os import warnings @@ -60,7 +61,7 @@ class AppEngineManager(RequestMethods): raise AppEnginePlatformError( "URLFetch is not available in this environment.") - if is_prod_appengine_v2(): + if is_prod_appengine_mvms(): raise AppEnginePlatformError( "Use normal urllib3.PoolManager instead of AppEngineManager" "on Managed VMs, as using URLFetch is not necessary in " @@ -108,14 +109,14 @@ class AppEngineManager(RequestMethods): raise TimeoutError(self, e) except urlfetch.InvalidURLError as e: - if 'too large' in e.message: + 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 e.message: + if 'Too many redirects' in str(e): raise MaxRetryError(self, url, reason=e) raise ProtocolError(e) @@ -155,7 +156,7 @@ class AppEngineManager(RequestMethods): def _urlfetch_response_to_http_response(self, urlfetch_resp, **response_kw): - if is_prod_appengine_v1(): + 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') @@ -176,7 +177,7 @@ class AppEngineManager(RequestMethods): if timeout is Timeout.DEFAULT_TIMEOUT: return 5 # 5s is the default timeout for URLFetch. if isinstance(timeout, Timeout): - if not timeout.read is timeout.connect: + if timeout.read is not timeout.connect: warnings.warn( "URLFetch does not support granular timeout settings, " "reverting to total timeout.", AppEnginePlatformWarning) @@ -199,12 +200,12 @@ class AppEngineManager(RequestMethods): def is_appengine(): return (is_local_appengine() or - is_prod_appengine_v1() or - is_prod_appengine_v2()) + is_prod_appengine() or + is_prod_appengine_mvms()) def is_appengine_sandbox(): - return is_appengine() and not is_prod_appengine_v2() + return is_appengine() and not is_prod_appengine_mvms() def is_local_appengine(): @@ -212,11 +213,11 @@ def is_local_appengine(): 'Development/' in os.environ['SERVER_SOFTWARE']) -def is_prod_appengine_v1(): +def is_prod_appengine(): return ('APPENGINE_RUNTIME' in os.environ and 'Google App Engine/' in os.environ['SERVER_SOFTWARE'] and - not is_prod_appengine_v2()) + not is_prod_appengine_mvms()) -def is_prod_appengine_v2(): +def is_prod_appengine_mvms(): return os.environ.get('GAE_VM', False) == 'true' diff --git a/requests/packages/urllib3/contrib/ntlmpool.py b/requests/packages/urllib3/contrib/ntlmpool.py index c6b266f5..c136a238 100644 --- a/requests/packages/urllib3/contrib/ntlmpool.py +++ b/requests/packages/urllib3/contrib/ntlmpool.py @@ -3,6 +3,7 @@ NTLM authenticating pool, contributed by erikcederstran Issue #10, see: http://code.google.com/p/urllib3/issues/detail?id=10 """ +from __future__ import absolute_import try: from http.client import HTTPSConnection diff --git a/requests/packages/urllib3/contrib/pyopenssl.py b/requests/packages/urllib3/contrib/pyopenssl.py index c20ae46d..5996153a 100644 --- a/requests/packages/urllib3/contrib/pyopenssl.py +++ b/requests/packages/urllib3/contrib/pyopenssl.py @@ -43,6 +43,7 @@ Module Variables .. _crime attack: https://en.wikipedia.org/wiki/CRIME_(security_exploit) ''' +from __future__ import absolute_import try: from ndg.httpsclient.ssl_peer_verification import SUBJ_ALT_NAME_SUPPORT @@ -53,7 +54,7 @@ except SyntaxError as e: import OpenSSL.SSL from pyasn1.codec.der import decoder as der_decoder from pyasn1.type import univ, constraint -from socket import _fileobject, timeout +from socket import _fileobject, timeout, error as SocketError import ssl import select @@ -71,6 +72,12 @@ _openssl_versions = { 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: _openssl_versions.update({ssl.PROTOCOL_SSLv3: OpenSSL.SSL.SSLv3_METHOD}) except AttributeError: @@ -79,8 +86,8 @@ except AttributeError: _openssl_verify = { ssl.CERT_NONE: OpenSSL.SSL.VERIFY_NONE, ssl.CERT_OPTIONAL: OpenSSL.SSL.VERIFY_PEER, - ssl.CERT_REQUIRED: OpenSSL.SSL.VERIFY_PEER - + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, + ssl.CERT_REQUIRED: + OpenSSL.SSL.VERIFY_PEER + OpenSSL.SSL.VERIFY_FAIL_IF_NO_PEER_CERT, } DEFAULT_SSL_CIPHER_LIST = util.ssl_.DEFAULT_CIPHERS @@ -88,12 +95,6 @@ DEFAULT_SSL_CIPHER_LIST = util.ssl_.DEFAULT_CIPHERS # OpenSSL will only write 16K at a time SSL_WRITE_BLOCKSIZE = 16384 -try: - _ = memoryview - has_memoryview = True -except NameError: - has_memoryview = False - orig_util_HAS_SNI = util.HAS_SNI orig_connection_ssl_wrap_socket = connection.ssl_wrap_socket @@ -112,7 +113,7 @@ def extract_from_urllib3(): util.HAS_SNI = orig_util_HAS_SNI -### 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): '''ASN.1 implementation for subjectAltNames support''' @@ -123,7 +124,7 @@ class SubjectAltName(BaseSubjectAltName): 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): # Search through extensions dns_name = [] @@ -181,7 +182,7 @@ class WrappedSocket(object): if self.suppress_ragged_eofs and e.args == (-1, 'Unexpected EOF'): return b'' else: - raise + raise SocketError(e) except OpenSSL.SSL.ZeroReturnError as e: if self.connection.get_shutdown() == OpenSSL.SSL.RECEIVED_SHUTDOWN: return b'' @@ -212,12 +213,9 @@ class WrappedSocket(object): continue def sendall(self, data): - if has_memoryview and not isinstance(data, memoryview): - data = memoryview(data) - total_sent = 0 while total_sent < len(data): - sent = self._send_until_done(data[total_sent:total_sent+SSL_WRITE_BLOCKSIZE]) + sent = self._send_until_done(data[total_sent:total_sent + SSL_WRITE_BLOCKSIZE]) total_sent += sent def shutdown(self): @@ -226,7 +224,10 @@ class WrappedSocket(object): def close(self): if self._makefile_refs < 1: - return self.connection.close() + try: + return self.connection.close() + except OpenSSL.SSL.Error: + return else: self._makefile_refs -= 1 diff --git a/requests/packages/urllib3/exceptions.py b/requests/packages/urllib3/exceptions.py index 9607d65f..8e07eb61 100644 --- a/requests/packages/urllib3/exceptions.py +++ b/requests/packages/urllib3/exceptions.py @@ -1,16 +1,17 @@ +from __future__ import absolute_import +# Base Exceptions -## Base Exceptions class HTTPError(Exception): "Base exception used by this module." pass + class HTTPWarning(Warning): "Base warning used by this module." pass - class PoolError(HTTPError): "Base exception for errors caused within a pool." def __init__(self, pool, message): @@ -57,7 +58,7 @@ class ProtocolError(HTTPError): ConnectionError = ProtocolError -## Leaf Exceptions +# Leaf Exceptions class MaxRetryError(RequestError): """Raised when the maximum number of retries is exceeded. @@ -112,10 +113,12 @@ class ConnectTimeoutError(TimeoutError): "Raised when a socket timeout occurs while connecting to a server" pass + class NewConnectionError(ConnectTimeoutError, PoolError): "Raised when we fail to establish a new connection. Usually ECONNREFUSED." pass + class EmptyPoolError(PoolError): "Raised when a pool runs out of connections and no more are allowed." pass @@ -172,6 +175,11 @@ class InsecurePlatformWarning(SecurityWarning): pass +class SNIMissingWarning(HTTPWarning): + "Warned when making a HTTPS request without SNI available." + pass + + class ResponseNotChunked(ProtocolError, ValueError): "Response needs to be chunked in order to read it as chunks." pass diff --git a/requests/packages/urllib3/fields.py b/requests/packages/urllib3/fields.py index c853f8d5..c7d48113 100644 --- a/requests/packages/urllib3/fields.py +++ b/requests/packages/urllib3/fields.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import email.utils import mimetypes diff --git a/requests/packages/urllib3/filepost.py b/requests/packages/urllib3/filepost.py index 0fbf488d..97a2843c 100644 --- a/requests/packages/urllib3/filepost.py +++ b/requests/packages/urllib3/filepost.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import codecs from uuid import uuid4 diff --git a/requests/packages/urllib3/packages/__init__.py b/requests/packages/urllib3/packages/__init__.py index 37e83515..170e974c 100644 --- a/requests/packages/urllib3/packages/__init__.py +++ b/requests/packages/urllib3/packages/__init__.py @@ -2,3 +2,4 @@ from __future__ import absolute_import from . import ssl_match_hostname +__all__ = ('ssl_match_hostname', ) diff --git a/requests/packages/urllib3/packages/ssl_match_hostname/.gitignore b/requests/packages/urllib3/packages/ssl_match_hostname/.gitignore new file mode 100644 index 00000000..0a764a4d --- /dev/null +++ b/requests/packages/urllib3/packages/ssl_match_hostname/.gitignore @@ -0,0 +1 @@ +env diff --git a/requests/packages/urllib3/poolmanager.py b/requests/packages/urllib3/poolmanager.py index 76b6a129..f13e673d 100644 --- a/requests/packages/urllib3/poolmanager.py +++ b/requests/packages/urllib3/poolmanager.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import logging try: # Python 3 @@ -25,7 +26,7 @@ pool_classes_by_scheme = { log = logging.getLogger(__name__) SSL_KEYWORDS = ('key_file', 'cert_file', 'cert_reqs', 'ca_certs', - 'ssl_version') + 'ssl_version', 'ca_cert_dir') class PoolManager(RequestMethods): diff --git a/requests/packages/urllib3/request.py b/requests/packages/urllib3/request.py index a1a12bc5..d5aa62d8 100644 --- a/requests/packages/urllib3/request.py +++ b/requests/packages/urllib3/request.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import try: from urllib.parse import urlencode except ImportError: @@ -133,7 +134,8 @@ class RequestMethods(object): if fields: 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: body, content_type = encode_multipart_formdata(fields, boundary=multipart_boundary) diff --git a/requests/packages/urllib3/response.py b/requests/packages/urllib3/response.py index 788eb6ca..8f2a1b5c 100644 --- a/requests/packages/urllib3/response.py +++ b/requests/packages/urllib3/response.py @@ -1,7 +1,9 @@ +from __future__ import absolute_import from contextlib import contextmanager import zlib import io from socket import timeout as SocketTimeout +from socket import error as SocketError from ._collections import HTTPHeaderDict from .exceptions import ( @@ -130,8 +132,8 @@ class HTTPResponse(io.IOBase): if "chunked" in encodings: self.chunked = True - # We certainly don't want to preload content when the response is chunked. - if not self.chunked and preload_content and not self._body: + # If requested, preload the body. + if preload_content and not self._body: self._body = self.read(decode_content=decode_content) def get_redirect_location(self): @@ -194,12 +196,22 @@ class HTTPResponse(io.IOBase): "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 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): """ @@ -227,15 +239,22 @@ class HTTPResponse(io.IOBase): raise ReadTimeoutError(self._pool, None, 'Read timed out.') - except HTTPException as e: + except (HTTPException, SocketError) as e: # This includes IncompleteRead. raise ProtocolError('Connection broken: %r' % e, e) + except Exception: # 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 and not self._original_response.isclosed(): 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 is not None: + self._connection.close() + raise finally: if self._original_response and self._original_response.isclosed(): @@ -301,7 +320,6 @@ class HTTPResponse(io.IOBase): return data - def stream(self, amt=2**16, decode_content=None): """ A generator wrapper for the read() method. A call will block until @@ -340,9 +358,9 @@ class HTTPResponse(io.IOBase): headers = r.msg if not isinstance(headers, HTTPHeaderDict): - if PY3: # Python 3 + if PY3: # Python 3 headers = HTTPHeaderDict(headers.items()) - else: # Python 2 + else: # Python 2 headers = HTTPHeaderDict.from_httplib(headers) # HTTPResponse objects in Python 3 don't have a .strict attribute @@ -454,7 +472,8 @@ class HTTPResponse(io.IOBase): 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. " + raise ResponseNotChunked( + "Response is not chunked. " "Header 'transfer-encoding: chunked' is missing.") # Don't bother reading the body of a HEAD request. @@ -468,8 +487,18 @@ class HTTPResponse(io.IOBase): if self.chunk_left == 0: break chunk = self._handle_chunk(amt) - yield self._decode(chunk, decode_content=decode_content, - flush_decoder=True) + 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: diff --git a/requests/packages/urllib3/util/__init__.py b/requests/packages/urllib3/util/__init__.py index 8becc814..c6c6243c 100644 --- a/requests/packages/urllib3/util/__init__.py +++ b/requests/packages/urllib3/util/__init__.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import # For backwards compatibility, provide imports that used to be here. from .connection import is_connection_dropped from .request import make_headers @@ -22,3 +23,22 @@ from .url import ( split_first, Url, ) + +__all__ = ( + 'HAS_SNI', + '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', +) diff --git a/requests/packages/urllib3/util/connection.py b/requests/packages/urllib3/util/connection.py index 4f2f0f18..01a4812f 100644 --- a/requests/packages/urllib3/util/connection.py +++ b/requests/packages/urllib3/util/connection.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import socket try: from select import poll, POLLIN diff --git a/requests/packages/urllib3/util/request.py b/requests/packages/urllib3/util/request.py index bc64f6b1..73779315 100644 --- a/requests/packages/urllib3/util/request.py +++ b/requests/packages/urllib3/util/request.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from base64 import b64encode from ..packages.six import b diff --git a/requests/packages/urllib3/util/response.py b/requests/packages/urllib3/util/response.py index 2c1de154..bc723272 100644 --- a/requests/packages/urllib3/util/response.py +++ b/requests/packages/urllib3/util/response.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from ..packages.six.moves import http_client as httplib from ..exceptions import HeaderParsingError @@ -44,7 +45,7 @@ def assert_header_parsing(headers): # 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 {}.'.format( + raise TypeError('expected httplib.Message, got {0}.'.format( type(headers))) defects = getattr(headers, 'defects', None) diff --git a/requests/packages/urllib3/util/retry.py b/requests/packages/urllib3/util/retry.py index 1fb1f23b..03a01249 100644 --- a/requests/packages/urllib3/util/retry.py +++ b/requests/packages/urllib3/util/retry.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import import time import logging @@ -126,7 +127,7 @@ class Retry(object): self.method_whitelist = method_whitelist self.backoff_factor = backoff_factor self.raise_on_redirect = raise_on_redirect - self._observed_errors = _observed_errors # TODO: use .history instead? + self._observed_errors = _observed_errors # TODO: use .history instead? def new(self, **kw): params = dict( @@ -206,7 +207,8 @@ class Retry(object): 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. :param response: A response object, or None, if the server did not @@ -274,7 +276,6 @@ class Retry(object): return new_retry - def __repr__(self): return ('{cls.__name__}(total={self.total}, connect={self.connect}, ' 'read={self.read}, redirect={self.redirect})').format( diff --git a/requests/packages/urllib3/util/ssl_.py b/requests/packages/urllib3/util/ssl_.py index 47b817e3..67f83441 100644 --- a/requests/packages/urllib3/util/ssl_.py +++ b/requests/packages/urllib3/util/ssl_.py @@ -1,7 +1,12 @@ +from __future__ import absolute_import +import errno +import warnings +import hmac + from binascii import hexlify, unhexlify from hashlib import md5, sha1, sha256 -from ..exceptions import SSLError, InsecurePlatformWarning +from ..exceptions import SSLError, InsecurePlatformWarning, SNIMissingWarning SSLContext = None @@ -15,8 +20,23 @@ HASHFUNC_MAP = { 64: sha256, } -import errno -import warnings + +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) + try: # Test for SSL features import ssl @@ -134,7 +154,7 @@ def assert_fingerprint(cert, fingerprint): cert_digest = hashfunc(cert).digest() - if cert_digest != fingerprint_bytes: + if not _const_compare_digest(cert_digest, fingerprint_bytes): raise SSLError('Fingerprints did not match. Expected "{0}", got "{1}".' .format(fingerprint, hexlify(cert_digest))) @@ -283,4 +303,15 @@ def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None, context.load_cert_chain(certfile, keyfile) if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI 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. For more ' + 'information, see ' + 'https://urllib3.readthedocs.org/en/latest/security.html' + '#snimissingwarning.', + SNIMissingWarning + ) return context.wrap_socket(sock) diff --git a/requests/packages/urllib3/util/timeout.py b/requests/packages/urllib3/util/timeout.py index ea7027f3..ff62f476 100644 --- a/requests/packages/urllib3/util/timeout.py +++ b/requests/packages/urllib3/util/timeout.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import # The default socket timeout, used by httplib to indicate that no timeout was # specified by the user from socket import _GLOBAL_DEFAULT_TIMEOUT @@ -9,6 +10,7 @@ from ..exceptions import TimeoutStateError # urllib3 _Default = object() + def current_time(): """ 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. """ if (self.total is not None and - self.total is not self.DEFAULT_TIMEOUT and - self._read is not None and - self._read is not self.DEFAULT_TIMEOUT): + self.total is not self.DEFAULT_TIMEOUT and + self._read is not None and + self._read is not self.DEFAULT_TIMEOUT): # In case the connect timeout has not yet been established. if self._start_connect is None: return self._read diff --git a/requests/packages/urllib3/util/url.py b/requests/packages/urllib3/util/url.py index e58050cd..e996204a 100644 --- a/requests/packages/urllib3/util/url.py +++ b/requests/packages/urllib3/util/url.py @@ -1,3 +1,4 @@ +from __future__ import absolute_import from collections import namedtuple from ..exceptions import LocationParseError @@ -85,6 +86,7 @@ class Url(namedtuple('Url', url_attrs)): def __str__(self): return self.url + def split_first(s, delims): """ Given a string and an iterable of delimiters, split on the first found @@ -115,7 +117,7 @@ def split_first(s, delims): if min_idx is None or min_idx < 0: 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): @@ -206,6 +208,7 @@ def parse_url(url): return Url(scheme, auth, host, port, path, query, fragment) + def get_host(url): """ Deprecated. Use :func:`.parse_url` instead. From 2f744b501058d45c36c71391d4a185cccf62f9e1 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Tue, 15 Dec 2015 14:59:14 +0000 Subject: [PATCH 542/545] Update 2.9.0 release log. --- HISTORY.rst | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/HISTORY.rst b/HISTORY.rst index 7f20389e..43892ccf 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -10,6 +10,28 @@ dev (XXXX) - The ``verify`` keyword argument now supports being passed a path to a directory of CA certificates, not just a single-file bundle. +- Warnings are now emitted when sending files opened in text mode. +- Added the 511 Network Authentication Required status code to the status code + registry. + +**Bugfixes** + +- For file-like objects that are not seeked to the very beginning, we now + send the content length for the number of bytes we will actually read, rather + than the total size of the file, allowing partial file uploads. +- When uploading file-like objects, if they are empty or have no obvious + content length we set ``Transfer-Encoding: chunked`` rather than + ``Content-Length: 0``. +- We correctly receive the response in buffered mode when uploading chunked + bodies. +- We now handle being passed a query string as a bytestring on Python 3, by + decoding it as UTF-8. +- Sessions are now closed in a timely manner when using the function API, + rather than leaking and waiting for the garbage collector to clean them up. +- Correctly handle digest auth headers with a malformed ``qop`` directive that + contains no token, by treating it the same as if no ``qop`` directive was + provided at all. +- Minor performance improvements when removing specific cookies by name. 2.8.1 (2015-10-13) ++++++++++++++++++ From 424fe263d8d80c9e8b90fccfc5b26d5bbc239681 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Tue, 15 Dec 2015 15:20:26 +0000 Subject: [PATCH 543/545] Suggested wording from @sigmavirus24 --- HISTORY.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 43892ccf..825cd274 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -26,8 +26,9 @@ dev (XXXX) bodies. - We now handle being passed a query string as a bytestring on Python 3, by decoding it as UTF-8. -- Sessions are now closed in a timely manner when using the function API, - rather than leaking and waiting for the garbage collector to clean them up. +- Sessions are now closed in all cases (exceptional and not) when using the + functional API rather than leaking and waiting for the garbage collector to + clean them up. - Correctly handle digest auth headers with a malformed ``qop`` directive that contains no token, by treating it the same as if no ``qop`` directive was provided at all. From 5f7a3a74aab1625c2bb65f643197ee885e3da576 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Tue, 15 Dec 2015 15:29:27 +0000 Subject: [PATCH 544/545] v2.9.0 --- HISTORY.rst | 4 ++-- requests/__init__.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/HISTORY.rst b/HISTORY.rst index 825cd274..d2bbdd91 100644 --- a/HISTORY.rst +++ b/HISTORY.rst @@ -3,8 +3,8 @@ Release History --------------- -dev (XXXX) -++++++++++ +2.9.0 (2015-12-15) +++++++++++++++++++ **Minor Improvements** (Backwards compatible) diff --git a/requests/__init__.py b/requests/__init__.py index f7924dc8..35987f95 100644 --- a/requests/__init__.py +++ b/requests/__init__.py @@ -42,8 +42,8 @@ is at . """ __title__ = 'requests' -__version__ = '2.8.1' -__build__ = 0x020801 +__version__ = '2.9.0' +__build__ = 0x020900 __author__ = 'Kenneth Reitz' __license__ = 'Apache 2.0' __copyright__ = 'Copyright 2015 Kenneth Reitz' From fc8fa1aa265bb14d59c68eb68a179bce17953967 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Wed, 16 Dec 2015 14:56:13 +0000 Subject: [PATCH 545/545] Fix regression from #2844 regarding binary bodies. --- requests/models.py | 5 ++++- test_requests.py | 5 +++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/requests/models.py b/requests/models.py index 9c624d3c..4bcbc548 100644 --- a/requests/models.py +++ b/requests/models.py @@ -81,7 +81,7 @@ class RequestEncodingMixin(object): """ if isinstance(data, (str, bytes)): - return to_native_string(data) + return data elif hasattr(data, 'read'): return data elif hasattr(data, '__iter__'): @@ -385,6 +385,9 @@ class PreparedRequest(RequestEncodingMixin, RequestHooksMixin): if isinstance(fragment, str): fragment = fragment.encode('utf-8') + if isinstance(params, (str, bytes)): + params = to_native_string(params) + enc_params = self._encode_params(params) if enc_params: if query: diff --git a/test_requests.py b/test_requests.py index 137edc08..07952418 100755 --- a/test_requests.py +++ b/test_requests.py @@ -157,6 +157,11 @@ class TestRequests(object): params=b'test=foo').prepare() assert request.url == 'http://example.com/?test=foo' + def test_binary_put(self): + request = requests.Request('PUT', 'http://example.com', + data=u"ööö".encode("utf-8")).prepare() + assert isinstance(request.body, bytes) + def test_mixed_case_scheme_acceptable(self, httpbin): s = requests.Session() s.proxies = getproxies()