From ff2da7ceb9967ede1fc51266504265f34dce72b0 Mon Sep 17 00:00:00 2001 From: SomberNight Date: Thu, 16 Mar 2023 19:07:33 +0000 Subject: [PATCH] crash reporter: hardcode gui text. do not trust the server with it paranoia. --- electrum/base_crash_reporter.py | 30 +++++++++++++++++-- .../gui/kivy/uix/dialogs/crash_reporter.py | 14 ++++----- electrum/gui/qml/qeapp.py | 6 +++- electrum/gui/qt/exception_window.py | 11 +++---- 4 files changed, 45 insertions(+), 16 deletions(-) diff --git a/electrum/base_crash_reporter.py b/electrum/base_crash_reporter.py index b3441afc1..b2c95be80 100644 --- a/electrum/base_crash_reporter.py +++ b/electrum/base_crash_reporter.py @@ -25,6 +25,7 @@ import locale import traceback import sys import queue +from typing import NamedTuple, Optional from .version import ELECTRUM_VERSION from . import constants @@ -33,6 +34,12 @@ from .util import make_aiohttp_session from .logging import describe_os_version, Logger, get_git_version +class CrashReportResponse(NamedTuple): + status: Optional[str] + text: str + url: Optional[str] + + class BaseCrashReporter(Logger): report_server = "https://crashhub.electrum.org" config_key = "show_crash_reporter" @@ -63,16 +70,33 @@ class BaseCrashReporter(Logger): Logger.__init__(self) self.exc_args = (exctype, value, tb) - def send_report(self, asyncio_loop, proxy, endpoint="/crash", *, timeout=None): + def send_report(self, asyncio_loop, proxy, *, timeout=None) -> CrashReportResponse: + # FIXME the caller needs to catch generic "Exception", as this method does not have a well-defined API... if constants.net.GENESIS[-4:] not in ["4943", "e26f"] and ".electrum.org" in BaseCrashReporter.report_server: # Gah! Some kind of altcoin wants to send us crash reports. raise Exception(_("Missing report URL.")) report = self.get_traceback_info() report.update(self.get_additional_info()) report = json.dumps(report) - coro = self.do_post(proxy, BaseCrashReporter.report_server + endpoint, data=report) + coro = self.do_post(proxy, BaseCrashReporter.report_server + "/crash.json", data=report) response = asyncio.run_coroutine_threadsafe(coro, asyncio_loop).result(timeout) - return response + self.logger.info(f"Crash report sent. Got response [DO NOT TRUST THIS MESSAGE]: {response!r}") + response = json.loads(response) + assert isinstance(response, dict), type(response) + # sanitize URL + if location := response.get("location"): + assert isinstance(location, str) + base_issues_url = constants.GIT_REPO_ISSUES_URL + if not base_issues_url.endswith("/"): + base_issues_url = base_issues_url + "/" + if not location.startswith(base_issues_url): + location = None + ret = CrashReportResponse( + status=response.get("status"), + url=location, + text=_("Thanks for reporting this issue!"), + ) + return ret async def do_post(self, proxy, url, data): async with make_aiohttp_session(proxy) as session: diff --git a/electrum/gui/kivy/uix/dialogs/crash_reporter.py b/electrum/gui/kivy/uix/dialogs/crash_reporter.py index 49b23b78d..f5aa0a9da 100644 --- a/electrum/gui/kivy/uix/dialogs/crash_reporter.py +++ b/electrum/gui/kivy/uix/dialogs/crash_reporter.py @@ -121,16 +121,16 @@ class CrashReporter(BaseCrashReporter, Factory.Popup): loop = self.main_window.network.asyncio_loop proxy = self.main_window.network.proxy # FIXME network request in GUI thread... - response = json.loads(BaseCrashReporter.send_report(self, loop, proxy, - "/crash.json", timeout=10)) - except (ValueError, ClientError) as e: + response = BaseCrashReporter.send_report(self, loop, proxy, timeout=10) + except Exception as e: self.logger.warning(f"Error sending crash report. exc={e!r}") self.show_popup(_('Unable to send report'), _("Please check your network connection.")) else: - self.show_popup(_('Report sent'), response["text"]) - location = response["location"] - if location: - self.logger.info(f"Crash report sent. location={location!r}") + text = response.text + if response.url: + text += f" You can track further progress on GitHub." + self.show_popup(_('Report sent'), text) + if location := response.url: self.open_url(location) self.dismiss() diff --git a/electrum/gui/qml/qeapp.py b/electrum/gui/qml/qeapp.py index d151f1a27..8dc9077e3 100644 --- a/electrum/gui/qml/qeapp.py +++ b/electrum/gui/qml/qeapp.py @@ -247,13 +247,17 @@ class QEAppController(BaseCrashReporter, QObject): def report_task(): try: response = BaseCrashReporter.send_report(self, network.asyncio_loop, proxy) - self.sendingBugreportSuccess.emit(response) except Exception as e: self.logger.error('There was a problem with the automatic reporting', exc_info=e) self.sendingBugreportFailure.emit(_('There was a problem with the automatic reporting:') + '
' + repr(e)[:120] + '

' + _("Please report this issue manually") + f' on GitHub.') + else: + text = response.text + if response.url: + text += f" You can track further progress on GitHub." + self.sendingBugreportSuccess.emit(text) self.sendingBugreport.emit() threading.Thread(target=report_task).start() diff --git a/electrum/gui/qt/exception_window.py b/electrum/gui/qt/exception_window.py index 1a920aa15..d549d75d1 100644 --- a/electrum/gui/qt/exception_window.py +++ b/electrum/gui/qt/exception_window.py @@ -31,7 +31,7 @@ from PyQt5.QtWidgets import (QWidget, QLabel, QPushButton, QTextEdit, QMessageBox, QHBoxLayout, QVBoxLayout) from electrum.i18n import _ -from electrum.base_crash_reporter import BaseCrashReporter, EarlyExceptionsQueue +from electrum.base_crash_reporter import BaseCrashReporter, EarlyExceptionsQueue, CrashReportResponse from electrum.logging import Logger from electrum import constants from electrum.network import Network @@ -103,12 +103,13 @@ class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin, Logger): self.show() def send_report(self): - def on_success(response): - # note: 'response' coming from (remote) crash reporter server. - # It contains a URL to the GitHub issue, so we allow rich text. + def on_success(response: CrashReportResponse): + text = response.text + if response.url: + text += f" You can track further progress on GitHub." self.show_message(parent=self, title=_("Crash report"), - msg=response, + msg=text, rich_text=True) self.close() def on_failure(exc_info):