OLD | NEW |
(Empty) | |
| 1 from __future__ import absolute_import |
| 2 import datetime |
| 3 import logging |
| 4 import os |
| 5 import sys |
| 6 import socket |
| 7 from socket import error as SocketError, timeout as SocketTimeout |
| 8 import warnings |
| 9 from .packages import six |
| 10 from .packages.six.moves.http_client import HTTPConnection as _HTTPConnection |
| 11 from .packages.six.moves.http_client import HTTPException # noqa: F401 |
| 12 |
| 13 try: # Compiled with SSL? |
| 14 import ssl |
| 15 BaseSSLError = ssl.SSLError |
| 16 except (ImportError, AttributeError): # Platform-specific: No SSL. |
| 17 ssl = None |
| 18 |
| 19 class BaseSSLError(BaseException): |
| 20 pass |
| 21 |
| 22 |
| 23 try: # Python 3: |
| 24 # Not a no-op, we're adding this to the namespace so it can be imported. |
| 25 ConnectionError = ConnectionError |
| 26 except NameError: # Python 2: |
| 27 class ConnectionError(Exception): |
| 28 pass |
| 29 |
| 30 |
| 31 from .exceptions import ( |
| 32 NewConnectionError, |
| 33 ConnectTimeoutError, |
| 34 SubjectAltNameWarning, |
| 35 SystemTimeWarning, |
| 36 ) |
| 37 from .packages.ssl_match_hostname import match_hostname, CertificateError |
| 38 |
| 39 from .util.ssl_ import ( |
| 40 resolve_cert_reqs, |
| 41 resolve_ssl_version, |
| 42 assert_fingerprint, |
| 43 create_urllib3_context, |
| 44 ssl_wrap_socket |
| 45 ) |
| 46 |
| 47 |
| 48 from .util import connection |
| 49 |
| 50 from ._collections import HTTPHeaderDict |
| 51 |
| 52 log = logging.getLogger(__name__) |
| 53 |
| 54 port_by_scheme = { |
| 55 'http': 80, |
| 56 'https': 443, |
| 57 } |
| 58 |
| 59 # When updating RECENT_DATE, move it to |
| 60 # within two years of the current date, and no |
| 61 # earlier than 6 months ago. |
| 62 RECENT_DATE = datetime.date(2016, 1, 1) |
| 63 |
| 64 |
| 65 class DummyConnection(object): |
| 66 """Used to detect a failed ConnectionCls import.""" |
| 67 pass |
| 68 |
| 69 |
| 70 class HTTPConnection(_HTTPConnection, object): |
| 71 """ |
| 72 Based on httplib.HTTPConnection but provides an extra constructor |
| 73 backwards-compatibility layer between older and newer Pythons. |
| 74 |
| 75 Additional keyword parameters are used to configure attributes of the connec
tion. |
| 76 Accepted parameters include: |
| 77 |
| 78 - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTP
ConnectionPool` |
| 79 - ``source_address``: Set the source address for the current connection. |
| 80 |
| 81 .. note:: This is ignored for Python 2.6. It is only applied for 2.7 and
3.x |
| 82 |
| 83 - ``socket_options``: Set specific options on the underlying socket. If no
t specified, then |
| 84 defaults are loaded from ``HTTPConnection.default_socket_options`` which
includes disabling |
| 85 Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behin
d a proxy. |
| 86 |
| 87 For example, if you wish to enable TCP Keep Alive in addition to the def
aults, |
| 88 you might pass:: |
| 89 |
| 90 HTTPConnection.default_socket_options + [ |
| 91 (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1), |
| 92 ] |
| 93 |
| 94 Or you may want to disable the defaults by passing an empty list (e.g.,
``[]``). |
| 95 """ |
| 96 |
| 97 default_port = port_by_scheme['http'] |
| 98 |
| 99 #: Disable Nagle's algorithm by default. |
| 100 #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]`` |
| 101 default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)] |
| 102 |
| 103 #: Whether this connection verifies the host's certificate. |
| 104 is_verified = False |
| 105 |
| 106 def __init__(self, *args, **kw): |
| 107 if six.PY3: # Python 3 |
| 108 kw.pop('strict', None) |
| 109 |
| 110 # Pre-set source_address in case we have an older Python like 2.6. |
| 111 self.source_address = kw.get('source_address') |
| 112 |
| 113 if sys.version_info < (2, 7): # Python 2.6 |
| 114 # _HTTPConnection on Python 2.6 will balk at this keyword arg, but |
| 115 # not newer versions. We can still use it when creating a |
| 116 # connection though, so we pop it *after* we have saved it as |
| 117 # self.source_address. |
| 118 kw.pop('source_address', None) |
| 119 |
| 120 #: The socket options provided by the user. If no options are |
| 121 #: provided, we use the default options. |
| 122 self.socket_options = kw.pop('socket_options', self.default_socket_optio
ns) |
| 123 |
| 124 # Superclass also sets self.source_address in Python 2.7+. |
| 125 _HTTPConnection.__init__(self, *args, **kw) |
| 126 |
| 127 def _new_conn(self): |
| 128 """ Establish a socket connection and set nodelay settings on it. |
| 129 |
| 130 :return: New socket connection. |
| 131 """ |
| 132 extra_kw = {} |
| 133 if self.source_address: |
| 134 extra_kw['source_address'] = self.source_address |
| 135 |
| 136 if self.socket_options: |
| 137 extra_kw['socket_options'] = self.socket_options |
| 138 |
| 139 try: |
| 140 conn = connection.create_connection( |
| 141 (self.host, self.port), self.timeout, **extra_kw) |
| 142 |
| 143 except SocketTimeout as e: |
| 144 raise ConnectTimeoutError( |
| 145 self, "Connection to %s timed out. (connect timeout=%s)" % |
| 146 (self.host, self.timeout)) |
| 147 |
| 148 except SocketError as e: |
| 149 raise NewConnectionError( |
| 150 self, "Failed to establish a new connection: %s" % e) |
| 151 |
| 152 return conn |
| 153 |
| 154 def _prepare_conn(self, conn): |
| 155 self.sock = conn |
| 156 # the _tunnel_host attribute was added in python 2.6.3 (via |
| 157 # http://hg.python.org/cpython/rev/0f57b30a152f) so pythons 2.6(0-2) do |
| 158 # not have them. |
| 159 if getattr(self, '_tunnel_host', None): |
| 160 # TODO: Fix tunnel so it doesn't depend on self.sock state. |
| 161 self._tunnel() |
| 162 # Mark this connection as not reusable |
| 163 self.auto_open = 0 |
| 164 |
| 165 def connect(self): |
| 166 conn = self._new_conn() |
| 167 self._prepare_conn(conn) |
| 168 |
| 169 def request_chunked(self, method, url, body=None, headers=None): |
| 170 """ |
| 171 Alternative to the common request method, which sends the |
| 172 body with chunked encoding and not as one block |
| 173 """ |
| 174 headers = HTTPHeaderDict(headers if headers is not None else {}) |
| 175 skip_accept_encoding = 'accept-encoding' in headers |
| 176 skip_host = 'host' in headers |
| 177 self.putrequest( |
| 178 method, |
| 179 url, |
| 180 skip_accept_encoding=skip_accept_encoding, |
| 181 skip_host=skip_host |
| 182 ) |
| 183 for header, value in headers.items(): |
| 184 self.putheader(header, value) |
| 185 if 'transfer-encoding' not in headers: |
| 186 self.putheader('Transfer-Encoding', 'chunked') |
| 187 self.endheaders() |
| 188 |
| 189 if body is not None: |
| 190 stringish_types = six.string_types + (six.binary_type,) |
| 191 if isinstance(body, stringish_types): |
| 192 body = (body,) |
| 193 for chunk in body: |
| 194 if not chunk: |
| 195 continue |
| 196 if not isinstance(chunk, six.binary_type): |
| 197 chunk = chunk.encode('utf8') |
| 198 len_str = hex(len(chunk))[2:] |
| 199 self.send(len_str.encode('utf-8')) |
| 200 self.send(b'\r\n') |
| 201 self.send(chunk) |
| 202 self.send(b'\r\n') |
| 203 |
| 204 # After the if clause, to always have a closed body |
| 205 self.send(b'0\r\n\r\n') |
| 206 |
| 207 |
| 208 class HTTPSConnection(HTTPConnection): |
| 209 default_port = port_by_scheme['https'] |
| 210 |
| 211 ssl_version = None |
| 212 |
| 213 def __init__(self, host, port=None, key_file=None, cert_file=None, |
| 214 strict=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT, |
| 215 ssl_context=None, **kw): |
| 216 |
| 217 HTTPConnection.__init__(self, host, port, strict=strict, |
| 218 timeout=timeout, **kw) |
| 219 |
| 220 self.key_file = key_file |
| 221 self.cert_file = cert_file |
| 222 self.ssl_context = ssl_context |
| 223 |
| 224 # Required property for Google AppEngine 1.9.0 which otherwise causes |
| 225 # HTTPS requests to go out as HTTP. (See Issue #356) |
| 226 self._protocol = 'https' |
| 227 |
| 228 def connect(self): |
| 229 conn = self._new_conn() |
| 230 self._prepare_conn(conn) |
| 231 |
| 232 if self.ssl_context is None: |
| 233 self.ssl_context = create_urllib3_context( |
| 234 ssl_version=resolve_ssl_version(None), |
| 235 cert_reqs=resolve_cert_reqs(None), |
| 236 ) |
| 237 |
| 238 self.sock = ssl_wrap_socket( |
| 239 sock=conn, |
| 240 keyfile=self.key_file, |
| 241 certfile=self.cert_file, |
| 242 ssl_context=self.ssl_context, |
| 243 ) |
| 244 |
| 245 |
| 246 class VerifiedHTTPSConnection(HTTPSConnection): |
| 247 """ |
| 248 Based on httplib.HTTPSConnection but wraps the socket with |
| 249 SSL certification. |
| 250 """ |
| 251 cert_reqs = None |
| 252 ca_certs = None |
| 253 ca_cert_dir = None |
| 254 ssl_version = None |
| 255 assert_fingerprint = None |
| 256 |
| 257 def set_cert(self, key_file=None, cert_file=None, |
| 258 cert_reqs=None, ca_certs=None, |
| 259 assert_hostname=None, assert_fingerprint=None, |
| 260 ca_cert_dir=None): |
| 261 """ |
| 262 This method should only be called once, before the connection is used. |
| 263 """ |
| 264 # If cert_reqs is not provided, we can try to guess. If the user gave |
| 265 # us a cert database, we assume they want to use it: otherwise, if |
| 266 # they gave us an SSL Context object we should use whatever is set for |
| 267 # it. |
| 268 if cert_reqs is None: |
| 269 if ca_certs or ca_cert_dir: |
| 270 cert_reqs = 'CERT_REQUIRED' |
| 271 elif self.ssl_context is not None: |
| 272 cert_reqs = self.ssl_context.verify_mode |
| 273 |
| 274 self.key_file = key_file |
| 275 self.cert_file = cert_file |
| 276 self.cert_reqs = cert_reqs |
| 277 self.assert_hostname = assert_hostname |
| 278 self.assert_fingerprint = assert_fingerprint |
| 279 self.ca_certs = ca_certs and os.path.expanduser(ca_certs) |
| 280 self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir) |
| 281 |
| 282 def connect(self): |
| 283 # Add certificate verification |
| 284 conn = self._new_conn() |
| 285 |
| 286 hostname = self.host |
| 287 if getattr(self, '_tunnel_host', None): |
| 288 # _tunnel_host was added in Python 2.6.3 |
| 289 # (See: http://hg.python.org/cpython/rev/0f57b30a152f) |
| 290 |
| 291 self.sock = conn |
| 292 # Calls self._set_hostport(), so self.host is |
| 293 # self._tunnel_host below. |
| 294 self._tunnel() |
| 295 # Mark this connection as not reusable |
| 296 self.auto_open = 0 |
| 297 |
| 298 # Override the host with the one we're requesting data from. |
| 299 hostname = self._tunnel_host |
| 300 |
| 301 is_time_off = datetime.date.today() < RECENT_DATE |
| 302 if is_time_off: |
| 303 warnings.warn(( |
| 304 'System time is way off (before {0}). This will probably ' |
| 305 'lead to SSL verification errors').format(RECENT_DATE), |
| 306 SystemTimeWarning |
| 307 ) |
| 308 |
| 309 # Wrap socket using verification with the root certs in |
| 310 # trusted_root_certs |
| 311 if self.ssl_context is None: |
| 312 self.ssl_context = create_urllib3_context( |
| 313 ssl_version=resolve_ssl_version(self.ssl_version), |
| 314 cert_reqs=resolve_cert_reqs(self.cert_reqs), |
| 315 ) |
| 316 |
| 317 context = self.ssl_context |
| 318 context.verify_mode = resolve_cert_reqs(self.cert_reqs) |
| 319 self.sock = ssl_wrap_socket( |
| 320 sock=conn, |
| 321 keyfile=self.key_file, |
| 322 certfile=self.cert_file, |
| 323 ca_certs=self.ca_certs, |
| 324 ca_cert_dir=self.ca_cert_dir, |
| 325 server_hostname=hostname, |
| 326 ssl_context=context) |
| 327 |
| 328 if self.assert_fingerprint: |
| 329 assert_fingerprint(self.sock.getpeercert(binary_form=True), |
| 330 self.assert_fingerprint) |
| 331 elif context.verify_mode != ssl.CERT_NONE \ |
| 332 and self.assert_hostname is not False: |
| 333 cert = self.sock.getpeercert() |
| 334 if not cert.get('subjectAltName', ()): |
| 335 warnings.warn(( |
| 336 'Certificate for {0} has no `subjectAltName`, falling back t
o check for a ' |
| 337 '`commonName` for now. This feature is being removed by majo
r browsers and ' |
| 338 'deprecated by RFC 2818. (See https://github.com/shazow/urll
ib3/issues/497 ' |
| 339 'for details.)'.format(hostname)), |
| 340 SubjectAltNameWarning |
| 341 ) |
| 342 _match_hostname(cert, self.assert_hostname or hostname) |
| 343 |
| 344 self.is_verified = ( |
| 345 context.verify_mode == ssl.CERT_REQUIRED or |
| 346 self.assert_fingerprint is not None |
| 347 ) |
| 348 |
| 349 |
| 350 def _match_hostname(cert, asserted_hostname): |
| 351 try: |
| 352 match_hostname(cert, asserted_hostname) |
| 353 except CertificateError as e: |
| 354 log.error( |
| 355 'Certificate did not match expected hostname: %s. ' |
| 356 'Certificate: %s', asserted_hostname, cert |
| 357 ) |
| 358 # Add cert to exception and reraise so client code can inspect |
| 359 # the cert when catching the exception, if they want to |
| 360 e._peer_cert = cert |
| 361 raise |
| 362 |
| 363 |
| 364 if ssl: |
| 365 # Make a copy for testing. |
| 366 UnverifiedHTTPSConnection = HTTPSConnection |
| 367 HTTPSConnection = VerifiedHTTPSConnection |
| 368 else: |
| 369 HTTPSConnection = DummyConnection |
OLD | NEW |