diff --git a/funkwhale_cli/cli.py b/funkwhale_cli/cli.py index 4465c1fd50b4d442817686fc550be1a789d8ff70..60b6a7214d34c0fcf98df779f37cf19c76c4d4e3 100644 --- a/funkwhale_cli/cli.py +++ b/funkwhale_cli/cli.py @@ -7,6 +7,8 @@ import datetime import dotenv import functools import keyring +import ssl +import sys # importing the backends explicitely is required for PyInstaller to work import keyring.backends.kwallet @@ -35,6 +37,59 @@ click_log.basic_config(logs.logger) NOOP = object() +SSL_PROTOCOLS = (asyncio.sslproto.SSLProtocol,) +try: + import uvloop.loop +except ImportError: + pass +else: + SSL_PROTOCOLS = (*SSL_PROTOCOLS, uvloop.loop.SSLProtocol) + +def ignore_aiohttp_ssl_eror(loop): + """Ignore aiohttp #3535 / cpython #13548 issue with SSL data after close + + There is an issue in Python 3.7 up to 3.7.3 that over-reports a + ssl.SSLError fatal error (ssl.SSLError: [SSL: KRB5_S_INIT] application data + after close notify (_ssl.c:2609)) after we are already done with the + connection. See GitHub issues aio-libs/aiohttp#3535 and + python/cpython#13548. + + Given a loop, this sets up an exception handler that ignores this specific + exception, but passes everything else on to the previous exception handler + this one replaces. + + Checks for fixed Python versions, disabling itself when running on 3.7.4+ + or 3.8. + + """ + if sys.version_info >= (3, 7, 4): + return + + orig_handler = loop.get_exception_handler() + + def ignore_ssl_error(loop, context): + if context.get("message") in { + "SSL error in data received", + "Fatal error on transport", + }: + # validate we have the right exception, transport and protocol + exception = context.get('exception') + protocol = context.get('protocol') + if ( + isinstance(exception, ssl.SSLError) + and exception.reason == 'KRB5_S_INIT' + and isinstance(protocol, SSL_PROTOCOLS) + ): + if loop.get_debug(): + asyncio.log.logger.debug('Ignoring asyncio SSL KRB5_S_INIT error') + return + if orig_handler is not None: + orig_handler(loop, context) + else: + loop.default_exception_handler(context) + + loop.set_exception_handler(ignore_ssl_error) + def URL(v): if v is NOOP: @@ -66,6 +121,7 @@ def env_file(v): def async_command(f): def wrapper(*args, **kwargs): loop = asyncio.get_event_loop() + ignore_aiohttp_ssl_eror(loop) _async_reraise = kwargs.pop("_async_reraise", False) try: return loop.run_until_complete(f(*args, **kwargs))