+++ /dev/null
-# -*- coding: utf-8 -*-
-"""
-Copyright © 2004-2019, CherryPy Team (team@cherrypy.org)
-
-All rights reserved.
-
-* * *
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-* Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-
-* 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.
-
-* Neither the name of CherryPy 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 HOLDER 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 pkg_resources import parse_version
-
-# The SSL code in CherryPy 3.5.0 is buggy. It was fixed long ago,
-# but 3.5.0 is still shipping in major linux distributions
-# (Fedora 27, Ubuntu Xenial), so we must monkey patch it to get SSL working.
-
-
-def patch_http_connection_init(v):
- # It was fixed in 3.7.0. Exact lower bound version is probably earlier,
- # but 3.5.0 is what this monkey patch is tested on.
- if parse_version("3.5.0") <= v < parse_version("3.7.0"):
- from cherrypy.wsgiserver.wsgiserver2 import CP_fileobject, HTTPConnection
-
- def fixed_init(hc_self, server, sock, makefile=CP_fileobject):
- hc_self.server = server
- hc_self.socket = sock
- hc_self.rfile = makefile(sock, "rb", hc_self.rbufsize)
- hc_self.wfile = makefile(sock, "wb", hc_self.wbufsize)
- hc_self.requests_seen = 0
-
- HTTPConnection.__init__ = fixed_init
-
-
-# When the CherryPy server in 3.2.2 (and later) starts it attempts to verify
-# that the ports its listening on are in fact bound. When using the any address
-# "::" it tries both ipv4 and ipv6, and in some environments (e.g. kubernetes)
-# ipv6 isn't yet configured / supported and CherryPy throws an uncaught
-# exception.
-def skip_wait_for_occupied_port(v):
- # the issue was fixed in 3.2.3. it's present in 3.2.2 (current version on
- # centos:7) and back to at least 3.0.0.
- if parse_version("3.1.2") <= v < parse_version("3.2.3"):
- # https://github.com/cherrypy/cherrypy/issues/1100
- from cherrypy.process import servers
- servers.wait_for_occupied_port = lambda host, port: None
-
-
-# cherrypy.wsgiserver was extracted wsgiserver into cheroot in cherrypy v9.0.0
-def patch_builtin_ssl_wrap(v, new_wrap):
- if v < parse_version("9.0.0"):
- from cherrypy.wsgiserver.ssl_builtin import BuiltinSSLAdapter as builtin_ssl
- else:
- from cheroot.ssl.builtin import BuiltinSSLAdapter as builtin_ssl # type: ignore
- builtin_ssl.wrap = new_wrap(builtin_ssl.wrap)
-
-
-def accept_exceptions_from_builtin_ssl(v):
- # the fix was included by cheroot v5.2.0, which was included by cherrypy
- # 10.2.0.
- if v < parse_version("10.2.0"):
- # see https://github.com/cherrypy/cheroot/pull/4
- import ssl
-
- def accept_ssl_errors(func):
- def wrapper(self, sock):
- try:
- return func(self, sock)
- except ssl.SSLError as e:
- if e.errno == ssl.SSL_ERROR_SSL:
- # Check if it's one of the known errors
- # Errors that are caught by PyOpenSSL, but thrown by
- # built-in ssl
- _block_errors = ('unknown protocol', 'unknown ca', 'unknown_ca',
- 'unknown error',
- 'https proxy request', 'inappropriate fallback',
- 'wrong version number',
- 'no shared cipher', 'certificate unknown',
- 'ccs received early',
- 'certificate verify failed', # client cert w/o trusted CA
- 'version too low', # caused by SSL3 connections
- 'unsupported protocol', # caused by TLS1 connections
- 'sslv3 alert bad certificate')
- for error_text in _block_errors:
- if error_text in e.args[1].lower():
- # Accepted error, let's pass
- return None, {}
- raise
- return wrapper
- patch_builtin_ssl_wrap(v, accept_ssl_errors)
-
-
-def accept_socket_error_0(v):
- # see https://github.com/cherrypy/cherrypy/issues/1618
- try:
- import cheroot
- cheroot_version = parse_version(cheroot.__version__)
- except ImportError:
- pass
-
- if v < parse_version("9.0.0") or cheroot_version < parse_version("6.5.5"):
- generic_socket_error = OSError
-
- def accept_socket_error_0(func):
- def wrapper(self, sock):
- try:
- return func(self, sock)
- except generic_socket_error as e:
- """It is unclear why exactly this happens.
-
- It's reproducible only with openssl>1.0 and stdlib ``ssl`` wrapper.
- In CherryPy it's triggered by Checker plugin, which connects
- to the app listening to the socket port in TLS mode via plain
- HTTP during startup (from the same process).
-
- Ref: https://github.com/cherrypy/cherrypy/issues/1618
- """
- import ssl
- is_error0 = e.args == (0, 'Error')
- IS_ABOVE_OPENSSL10 = ssl.OPENSSL_VERSION_INFO >= (1, 1)
- del ssl
- if is_error0 and IS_ABOVE_OPENSSL10:
- return None, {}
- raise
- return wrapper
- patch_builtin_ssl_wrap(v, accept_socket_error_0)
-
-
-def patch_request_unique_id(v):
- """
- Older versions of cherrypy don't include request.unique_id field (a lazily
- calculated UUID4).
-
- Monkey-patching is preferred over alternatives as inheritance, as it'd break
- type checks (cherrypy/lib/cgtools.py: `isinstance(obj, _cprequest.Request)`)
- """
- if v < parse_version('11.1.0'):
- import uuid
- from functools import update_wrapper
-
- from cherrypy._cprequest import Request
-
- class LazyUUID4(object):
- def __str__(self):
- """Return UUID4 and keep it for future calls."""
- return str(self.uuid4)
-
- @property
- def uuid4(self):
- """Provide unique id on per-request basis using UUID4.
- It's evaluated lazily on render.
- """
- try:
- self._uuid4 # type: ignore
- except AttributeError:
- # evaluate on first access
- self._uuid4 = uuid.uuid4()
-
- return self._uuid4
-
- old_init = Request.__init__
-
- def init_with_unique_id(self, *args, **kwargs):
- old_init(self, *args, **kwargs)
- self.unique_id = LazyUUID4()
-
- Request.__init__ = update_wrapper(init_with_unique_id, old_init)
-
-
-def patch_cherrypy(v):
- ver = parse_version(v)
- patch_http_connection_init(ver)
- skip_wait_for_occupied_port(ver)
- accept_exceptions_from_builtin_ssl(ver)
- accept_socket_error_0(ver)
- patch_request_unique_id(ver)