comparison planemo/lib/python3.7/site-packages/urllib3/connection.py @ 1:56ad4e20f292 draft

"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
author guerler
date Fri, 31 Jul 2020 00:32:28 -0400
parents
children
comparison
equal deleted inserted replaced
0:d30785e31577 1:56ad4e20f292
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 to_send = bytearray(len_str.encode())
229 to_send += b"\r\n"
230 to_send += chunk
231 to_send += b"\r\n"
232 self.send(to_send)
233
234 # After the if clause, to always have a closed body
235 self.send(b"0\r\n\r\n")
236
237
238 class HTTPSConnection(HTTPConnection):
239 default_port = port_by_scheme["https"]
240
241 cert_reqs = None
242 ca_certs = None
243 ca_cert_dir = None
244 ca_cert_data = None
245 ssl_version = None
246 assert_fingerprint = None
247
248 def __init__(
249 self,
250 host,
251 port=None,
252 key_file=None,
253 cert_file=None,
254 key_password=None,
255 strict=None,
256 timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
257 ssl_context=None,
258 server_hostname=None,
259 **kw
260 ):
261
262 HTTPConnection.__init__(self, host, port, strict=strict, timeout=timeout, **kw)
263
264 self.key_file = key_file
265 self.cert_file = cert_file
266 self.key_password = key_password
267 self.ssl_context = ssl_context
268 self.server_hostname = server_hostname
269
270 # Required property for Google AppEngine 1.9.0 which otherwise causes
271 # HTTPS requests to go out as HTTP. (See Issue #356)
272 self._protocol = "https"
273
274 def set_cert(
275 self,
276 key_file=None,
277 cert_file=None,
278 cert_reqs=None,
279 key_password=None,
280 ca_certs=None,
281 assert_hostname=None,
282 assert_fingerprint=None,
283 ca_cert_dir=None,
284 ca_cert_data=None,
285 ):
286 """
287 This method should only be called once, before the connection is used.
288 """
289 # If cert_reqs is not provided we'll assume CERT_REQUIRED unless we also
290 # have an SSLContext object in which case we'll use its verify_mode.
291 if cert_reqs is None:
292 if self.ssl_context is not None:
293 cert_reqs = self.ssl_context.verify_mode
294 else:
295 cert_reqs = resolve_cert_reqs(None)
296
297 self.key_file = key_file
298 self.cert_file = cert_file
299 self.cert_reqs = cert_reqs
300 self.key_password = key_password
301 self.assert_hostname = assert_hostname
302 self.assert_fingerprint = assert_fingerprint
303 self.ca_certs = ca_certs and os.path.expanduser(ca_certs)
304 self.ca_cert_dir = ca_cert_dir and os.path.expanduser(ca_cert_dir)
305 self.ca_cert_data = ca_cert_data
306
307 def connect(self):
308 # Add certificate verification
309 conn = self._new_conn()
310 hostname = self.host
311
312 # Google App Engine's httplib does not define _tunnel_host
313 if getattr(self, "_tunnel_host", None):
314 self.sock = conn
315 # Calls self._set_hostport(), so self.host is
316 # self._tunnel_host below.
317 self._tunnel()
318 # Mark this connection as not reusable
319 self.auto_open = 0
320
321 # Override the host with the one we're requesting data from.
322 hostname = self._tunnel_host
323
324 server_hostname = hostname
325 if self.server_hostname is not None:
326 server_hostname = self.server_hostname
327
328 is_time_off = datetime.date.today() < RECENT_DATE
329 if is_time_off:
330 warnings.warn(
331 (
332 "System time is way off (before {0}). This will probably "
333 "lead to SSL verification errors"
334 ).format(RECENT_DATE),
335 SystemTimeWarning,
336 )
337
338 # Wrap socket using verification with the root certs in
339 # trusted_root_certs
340 default_ssl_context = False
341 if self.ssl_context is None:
342 default_ssl_context = True
343 self.ssl_context = create_urllib3_context(
344 ssl_version=resolve_ssl_version(self.ssl_version),
345 cert_reqs=resolve_cert_reqs(self.cert_reqs),
346 )
347
348 context = self.ssl_context
349 context.verify_mode = resolve_cert_reqs(self.cert_reqs)
350
351 # Try to load OS default certs if none are given.
352 # Works well on Windows (requires Python3.4+)
353 if (
354 not self.ca_certs
355 and not self.ca_cert_dir
356 and not self.ca_cert_data
357 and default_ssl_context
358 and hasattr(context, "load_default_certs")
359 ):
360 context.load_default_certs()
361
362 self.sock = ssl_wrap_socket(
363 sock=conn,
364 keyfile=self.key_file,
365 certfile=self.cert_file,
366 key_password=self.key_password,
367 ca_certs=self.ca_certs,
368 ca_cert_dir=self.ca_cert_dir,
369 ca_cert_data=self.ca_cert_data,
370 server_hostname=server_hostname,
371 ssl_context=context,
372 )
373
374 if self.assert_fingerprint:
375 assert_fingerprint(
376 self.sock.getpeercert(binary_form=True), self.assert_fingerprint
377 )
378 elif (
379 context.verify_mode != ssl.CERT_NONE
380 and not getattr(context, "check_hostname", False)
381 and self.assert_hostname is not False
382 ):
383 # While urllib3 attempts to always turn off hostname matching from
384 # the TLS library, this cannot always be done. So we check whether
385 # the TLS Library still thinks it's matching hostnames.
386 cert = self.sock.getpeercert()
387 if not cert.get("subjectAltName", ()):
388 warnings.warn(
389 (
390 "Certificate for {0} has no `subjectAltName`, falling back to check for a "
391 "`commonName` for now. This feature is being removed by major browsers and "
392 "deprecated by RFC 2818. (See https://github.com/urllib3/urllib3/issues/497 "
393 "for details.)".format(hostname)
394 ),
395 SubjectAltNameWarning,
396 )
397 _match_hostname(cert, self.assert_hostname or server_hostname)
398
399 self.is_verified = (
400 context.verify_mode == ssl.CERT_REQUIRED
401 or self.assert_fingerprint is not None
402 )
403
404
405 def _match_hostname(cert, asserted_hostname):
406 try:
407 match_hostname(cert, asserted_hostname)
408 except CertificateError as e:
409 log.warning(
410 "Certificate did not match expected hostname: %s. Certificate: %s",
411 asserted_hostname,
412 cert,
413 )
414 # Add cert to exception and reraise so client code can inspect
415 # the cert when catching the exception, if they want to
416 e._peer_cert = cert
417 raise
418
419
420 if not ssl:
421 HTTPSConnection = DummyConnection # noqa: F811
422
423
424 VerifiedHTTPSConnection = HTTPSConnection