comparison env/lib/python3.7/site-packages/urllib3/connection.py @ 0:26e78fe6e8c4 draft

"planemo upload commit c699937486c35866861690329de38ec1a5d9f783"
author shellac
date Sat, 02 May 2020 07:14:21 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:26e78fe6e8c4
1 from __future__ import absolute_import
2 import re
3 import datetime
4 import logging
5 import os
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
16 BaseSSLError = ssl.SSLError
17 except (ImportError, AttributeError): # Platform-specific: No SSL.
18 ssl = None
19
20 class BaseSSLError(BaseException):
21 pass
22
23
24 try:
25 # Python 3: not a no-op, we're adding this to the namespace so it can be imported.
26 ConnectionError = ConnectionError
27 except NameError:
28 # Python 2
29 class ConnectionError(Exception):
30 pass
31
32
33 from .exceptions import (
34 NewConnectionError,
35 ConnectTimeoutError,
36 SubjectAltNameWarning,
37 SystemTimeWarning,
38 )
39 from .packages.ssl_match_hostname import match_hostname, CertificateError
40
41 from .util.ssl_ import (
42 resolve_cert_reqs,
43 resolve_ssl_version,
44 assert_fingerprint,
45 create_urllib3_context,
46 ssl_wrap_socket,
47 )
48
49
50 from .util import connection
51
52 from ._collections import HTTPHeaderDict
53
54 log = logging.getLogger(__name__)
55
56 port_by_scheme = {"http": 80, "https": 443}
57
58 # When it comes time to update this value as a part of regular maintenance
59 # (ie test_recent_date is failing) update it to ~6 months before the current date.
60 RECENT_DATE = datetime.date(2019, 1, 1)
61
62 _CONTAINS_CONTROL_CHAR_RE = re.compile(r"[^-!#$%&'*+.^_`|~0-9a-zA-Z]")
63
64
65 class DummyConnection(object):
66 """Used to detect a failed ConnectionCls import."""
67
68 pass
69
70
71 class HTTPConnection(_HTTPConnection, object):
72 """
73 Based on httplib.HTTPConnection but provides an extra constructor
74 backwards-compatibility layer between older and newer Pythons.
75
76 Additional keyword parameters are used to configure attributes of the connection.
77 Accepted parameters include:
78
79 - ``strict``: See the documentation on :class:`urllib3.connectionpool.HTTPConnectionPool`
80 - ``source_address``: Set the source address for the current connection.
81 - ``socket_options``: Set specific options on the underlying socket. If not specified, then
82 defaults are loaded from ``HTTPConnection.default_socket_options`` which includes disabling
83 Nagle's algorithm (sets TCP_NODELAY to 1) unless the connection is behind a proxy.
84
85 For example, if you wish to enable TCP Keep Alive in addition to the defaults,
86 you might pass::
87
88 HTTPConnection.default_socket_options + [
89 (socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1),
90 ]
91
92 Or you may want to disable the defaults by passing an empty list (e.g., ``[]``).
93 """
94
95 default_port = port_by_scheme["http"]
96
97 #: Disable Nagle's algorithm by default.
98 #: ``[(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]``
99 default_socket_options = [(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)]
100
101 #: Whether this connection verifies the host's certificate.
102 is_verified = False
103
104 def __init__(self, *args, **kw):
105 if not six.PY2:
106 kw.pop("strict", None)
107
108 # Pre-set source_address.
109 self.source_address = kw.get("source_address")
110
111 #: The socket options provided by the user. If no options are
112 #: provided, we use the default options.
113 self.socket_options = kw.pop("socket_options", self.default_socket_options)
114
115 _HTTPConnection.__init__(self, *args, **kw)
116
117 @property
118 def host(self):
119 """
120 Getter method to remove any trailing dots that indicate the hostname is an FQDN.
121
122 In general, SSL certificates don't include the trailing dot indicating a
123 fully-qualified domain name, and thus, they don't validate properly when
124 checked against a domain name that includes the dot. In addition, some
125 servers may not expect to receive the trailing dot when provided.
126
127 However, the hostname with trailing dot is critical to DNS resolution; doing a
128 lookup with the trailing dot will properly only resolve the appropriate FQDN,
129 whereas a lookup without a trailing dot will search the system's search domain
130 list. Thus, it's important to keep the original host around for use only in
131 those cases where it's appropriate (i.e., when doing DNS lookup to establish the
132 actual TCP connection across which we're going to send HTTP requests).
133 """
134 return self._dns_host.rstrip(".")
135
136 @host.setter
137 def host(self, value):
138 """
139 Setter for the `host` property.
140
141 We assume that only urllib3 uses the _dns_host attribute; httplib itself
142 only uses `host`, and it seems reasonable that other libraries follow suit.
143 """
144 self._dns_host = value
145
146 def _new_conn(self):
147 """ Establish a socket connection and set nodelay settings on it.
148
149 :return: New socket connection.
150 """
151 extra_kw = {}
152 if self.source_address:
153 extra_kw["source_address"] = self.source_address
154
155 if self.socket_options:
156 extra_kw["socket_options"] = self.socket_options
157
158 try:
159 conn = connection.create_connection(
160 (self._dns_host, self.port), self.timeout, **extra_kw
161 )
162
163 except SocketTimeout:
164 raise ConnectTimeoutError(
165 self,
166 "Connection to %s timed out. (connect timeout=%s)"
167 % (self.host, self.timeout),
168 )
169
170 except SocketError as e:
171 raise NewConnectionError(
172 self, "Failed to establish a new connection: %s" % e
173 )
174
175 return conn
176
177 def _prepare_conn(self, conn):
178 self.sock = conn
179 # Google App Engine's httplib does not define _tunnel_host
180 if getattr(self, "_tunnel_host", None):
181 # TODO: Fix tunnel so it doesn't depend on self.sock state.
182 self._tunnel()
183 # Mark this connection as not reusable
184 self.auto_open = 0
185
186 def connect(self):
187 conn = self._new_conn()
188 self._prepare_conn(conn)
189
190 def putrequest(self, method, url, *args, **kwargs):
191 """Send a request to the server"""
192 match = _CONTAINS_CONTROL_CHAR_RE.search(method)
193 if match:
194 raise ValueError(
195 "Method cannot contain non-token characters %r (found at least %r)"
196 % (method, match.group())
197 )
198
199 return _HTTPConnection.putrequest(self, method, url, *args, **kwargs)
200
201 def request_chunked(self, method, url, body=None, headers=None):
202 """
203 Alternative to the common request method, which sends the
204 body with chunked encoding and not as one block
205 """
206 headers = HTTPHeaderDict(headers if headers is not None else {})
207 skip_accept_encoding = "accept-encoding" in headers
208 skip_host = "host" in headers
209 self.putrequest(
210 method, url, skip_accept_encoding=skip_accept_encoding, skip_host=skip_host
211 )
212 for header, value in headers.items():
213 self.putheader(header, value)
214 if "transfer-encoding" not in headers:
215 self.putheader("Transfer-Encoding", "chunked")
216 self.endheaders()
217
218 if body is not None:
219 stringish_types = six.string_types + (bytes,)
220 if isinstance(body, stringish_types):
221 body = (body,)
222 for chunk in body:
223 if not chunk:
224 continue
225 if not isinstance(chunk, bytes):
226 chunk = chunk.encode("utf8")
227 len_str = hex(len(chunk))[2:]
228 self.send(len_str.encode("utf-8"))
229 self.send(b"\r\n")
230 self.send(chunk)
231 self.send(b"\r\n")
232
233 # After the if clause, to always have a closed body
234 self.send(b"0\r\n\r\n")
235
236
237 class HTTPSConnection(HTTPConnection):
238 default_port = port_by_scheme["https"]
239
240 cert_reqs = None
241 ca_certs = None
242 ca_cert_dir = None
243 ca_cert_data = None
244 ssl_version = None
245 assert_fingerprint = None
246
247 def __init__(
248 self,
249 host,
250 port=None,
251 key_file=None,
252 cert_file=None,
253 key_password=None,
254 strict=None,
255 timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
256 ssl_context=None,
257 server_hostname=None,
258 **kw
259 ):
260
261 HTTPConnection.__init__(self, host, port, strict=strict, timeout=timeout, **kw)
262
263 self.key_file = key_file
264 self.cert_file = cert_file
265 self.key_password = key_password
266 self.ssl_context = ssl_context
267 self.server_hostname = server_hostname
268
269 # Required property for Google AppEngine 1.9.0 which otherwise causes
270 # HTTPS requests to go out as HTTP. (See Issue #356)
271 self._protocol = "https"
272
273 def set_cert(
274 self,
275 key_file=None,
276 cert_file=None,
277 cert_reqs=None,
278 key_password=None,
279 ca_certs=None,
280 assert_hostname=None,
281 assert_fingerprint=None,
282 ca_cert_dir=None,
283 ca_cert_data=None,
284 ):
285 """
286 This method should only be called once, before the connection is used.
287 """
288 # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also
289 # have an SSLContext object in which case we'll use its verify_mode.
290 if cert_reqs is None:
291 if self.ssl_context is not None:
292 cert_reqs = self.ssl_context.verify_mode
293 else:
294 cert_reqs = resolve_cert_reqs(None)
295
296 self.key_file = key_file
297 self.cert_file = cert_file
298 self.cert_reqs = cert_reqs
299 self.key_password = key_password
300 self.assert_hostname = assert_hostname
301 self.assert_fingerprint = assert_fingerprint
302 self.ca_certs = ca_certs and os.path.expanduser(ca_certs)
303 self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir)
304 self.ca_cert_data = ca_cert_data
305
306 def connect(self):
307 # Add certificate verification
308 conn = self._new_conn()
309 hostname = self.host
310
311 # Google App Engine's httplib does not define _tunnel_host
312 if getattr(self, "_tunnel_host", None):
313 self.sock = conn
314 # Calls self._set_hostport(), so self.host is
315 # self._tunnel_host below.
316 self._tunnel()
317 # Mark this connection as not reusable
318 self.auto_open = 0
319
320 # Override the host with the one we're requesting data from.
321 hostname = self._tunnel_host
322
323 server_hostname = hostname
324 if self.server_hostname is not None:
325 server_hostname = self.server_hostname
326
327 is_time_off = datetime.date.today() < RECENT_DATE
328 if is_time_off:
329 warnings.warn(
330 (
331 "System time is way off (before {0}). This will probably "
332 "lead to SSL verification errors"
333 ).format(RECENT_DATE),
334 SystemTimeWarning,
335 )
336
337 # Wrap socket using verification with the root certs in
338 # trusted_root_certs
339 default_ssl_context = False
340 if self.ssl_context is None:
341 default_ssl_context = True
342 self.ssl_context = create_urllib3_context(
343 ssl_version=resolve_ssl_version(self.ssl_version),
344 cert_reqs=resolve_cert_reqs(self.cert_reqs),
345 )
346
347 context = self.ssl_context
348 context.verify_mode = resolve_cert_reqs(self.cert_reqs)
349
350 # Try to load OS default certs if none are given.
351 # Works well on Windows (requires Python3.4+)
352 if (
353 not self.ca_certs
354 and not self.ca_cert_dir
355 and not self.ca_cert_data
356 and default_ssl_context
357 and hasattr(context, "load_default_certs")
358 ):
359 context.load_default_certs()
360
361 self.sock = ssl_wrap_socket(
362 sock=conn,
363 keyfile=self.key_file,
364 certfile=self.cert_file,
365 key_password=self.key_password,
366 ca_certs=self.ca_certs,
367 ca_cert_dir=self.ca_cert_dir,
368 ca_cert_data=self.ca_cert_data,
369 server_hostname=server_hostname,
370 ssl_context=context,
371 )
372
373 if self.assert_fingerprint:
374 assert_fingerprint(
375 self.sock.getpeercert(binary_form=True), self.assert_fingerprint
376 )
377 elif (
378 context.verify_mode != ssl.CERT_NONE
379 and not getattr(context, "check_hostname", False)
380 and self.assert_hostname is not False
381 ):
382 # While urllib3 attempts to always turn off hostname matching from
383 # the TLS library, this cannot always be done. So we check whether
384 # the TLS Library still thinks it's matching hostnames.
385 cert = self.sock.getpeercert()
386 if not cert.get("subjectAltName", ()):
387 warnings.warn(
388 (
389 "Certificate for {0} has no `subjectAltName`, falling back to check for a "
390 "`commonName` for now. This feature is being removed by major browsers and "
391 "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 "
392 "for details.)".format(hostname)
393 ),
394 SubjectAltNameWarning,
395 )
396 _match_hostname(cert, self.assert_hostname or server_hostname)
397
398 self.is_verified = (
399 context.verify_mode == ssl.CERT_REQUIRED
400 or self.assert_fingerprint is not None
401 )
402
403
404 def _match_hostname(cert, asserted_hostname):
405 try:
406 match_hostname(cert, asserted_hostname)
407 except CertificateError as e:
408 log.warning(
409 "Certificate did not match expected hostname: %s. Certificate: %s",
410 asserted_hostname,
411 cert,
412 )
413 # Add cert to exception and reraise so client code can inspect
414 # the cert when catching the exception, if they want to
415 e._peer_cert = cert
416 raise
417
418
419 if not ssl:
420 HTTPSConnection = DummyConnection # noqa: F811
421
422
423 VerifiedHTTPSConnection = HTTPSConnection