tutil.parse_URI: more granular exceptions - electrum - Electrum Bitcoin wallet HTML git clone https://git.parazyd.org/electrum DIR Log DIR Files DIR Refs DIR Submodules --- DIR commit 158090bf8bba4d317ede7b8c07bdb9a140d85797 DIR parent a591ccf9b1ca079b7e46491071c9101cd8378abf HTML Author: SomberNight <somber.night@protonmail.com> Date: Sat, 25 May 2019 04:55:36 +0200 util.parse_URI: more granular exceptions related: #5376 first report in #5376 was generated with these changes; before, the exception was caught and a toast displayed "Not a Bitcoin URI" Diffstat: M electrum/gui/kivy/main_window.py | 2 ++ M electrum/gui/kivy/uix/screens.py | 9 ++++----- M electrum/gui/qt/main_window.py | 7 ++++--- M electrum/util.py | 53 +++++++++++++++++++++---------- 4 files changed, 46 insertions(+), 25 deletions(-) --- DIR diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py t@@ -454,6 +454,8 @@ class ElectrumWindow(App): String = autoclass("java.lang.String") contents = intent.getStringExtra(String("text")) on_complete(contents) + except Exception as e: # exc would otherwise get lost + send_exception_to_crash_reporter(e) finally: activity.unbind(on_activity_result=on_qr_result) activity.bind(on_activity_result=on_qr_result) DIR diff --git a/electrum/gui/kivy/uix/screens.py b/electrum/gui/kivy/uix/screens.py t@@ -21,7 +21,7 @@ from kivy.utils import platform from electrum.util import profiler, parse_URI, format_time, InvalidPassword, NotEnoughFunds, Fiat from electrum import bitcoin from electrum.transaction import TxOutput -from electrum.util import send_exception_to_crash_reporter +from electrum.util import send_exception_to_crash_reporter, parse_URI, InvalidBitcoinURI from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED from electrum.plugin import run_hook from electrum.wallet import InternalAddressCorruption t@@ -174,11 +174,10 @@ class SendScreen(CScreen): if not self.app.wallet: self.payment_request_queued = text return - import electrum try: - uri = electrum.util.parse_URI(text, self.app.on_pr) - except: - self.app.show_info(_("Not a Bitcoin URI")) + uri = parse_URI(text, self.app.on_pr) + except InvalidBitcoinURI as e: + self.app.show_info(_("Error parsing URI") + f":\n{e}") return amount = uri.get('amount') self.screen.address = uri.get('address', '') DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py t@@ -60,7 +60,8 @@ from electrum.util import (format_time, format_satoshis, format_fee_satoshis, base_units, base_units_list, base_unit_name_to_decimal_point, decimal_point_to_base_unit_name, quantize_feerate, UnknownBaseUnit, DECIMAL_POINT_DEFAULT, UserFacingException, - get_new_wallet_name, send_exception_to_crash_reporter) + get_new_wallet_name, send_exception_to_crash_reporter, + InvalidBitcoinURI) from electrum.transaction import Transaction, TxOutput from electrum.address_synchronizer import AddTransactionException from electrum.wallet import (Multisig_Wallet, CannotBumpFee, Abstract_Wallet, t@@ -1844,8 +1845,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger): return try: out = util.parse_URI(URI, self.on_pr) - except BaseException as e: - self.show_error(_('Invalid bitcoin URI:') + '\n' + str(e)) + except InvalidBitcoinURI as e: + self.show_error(_("Error parsing URI") + f":\n{e}") return self.show_send_tab() r = out.get('r') DIR diff --git a/electrum/util.py b/electrum/util.py t@@ -720,18 +720,25 @@ def block_explorer_URL(config: 'SimpleConfig', kind: str, item: str) -> Optional #_ud = re.compile('%([0-9a-hA-H]{2})', re.MULTILINE) #urldecode = lambda x: _ud.sub(lambda m: chr(int(m.group(1), 16)), x) -def parse_URI(uri: str, on_pr: Callable=None) -> dict: +class InvalidBitcoinURI(Exception): pass + + +def parse_URI(uri: str, on_pr: Callable = None, *, loop=None) -> dict: + """Raises InvalidBitcoinURI on malformed URI.""" from . import bitcoin from .bitcoin import COIN + if not isinstance(uri, str): + raise InvalidBitcoinURI(f"expected string, not {repr(uri)}") + if ':' not in uri: if not bitcoin.is_address(uri): - raise Exception("Not a bitcoin address") + raise InvalidBitcoinURI("Not a bitcoin address") return {'address': uri} u = urllib.parse.urlparse(uri) if u.scheme != 'bitcoin': - raise Exception("Not a bitcoin URI") + raise InvalidBitcoinURI("Not a bitcoin URI") address = u.path # python for android fails to parse query t@@ -742,32 +749,44 @@ def parse_URI(uri: str, on_pr: Callable=None) -> dict: pq = urllib.parse.parse_qs(u.query) for k, v in pq.items(): - if len(v)!=1: - raise Exception('Duplicate Key', k) + if len(v) != 1: + raise InvalidBitcoinURI(f'Duplicate Key: {repr(k)}') out = {k: v[0] for k, v in pq.items()} if address: if not bitcoin.is_address(address): - raise Exception("Invalid bitcoin address:" + address) + raise InvalidBitcoinURI(f"Invalid bitcoin address: {address}") out['address'] = address if 'amount' in out: am = out['amount'] - m = re.match(r'([0-9.]+)X([0-9])', am) - if m: - k = int(m.group(2)) - 8 - amount = Decimal(m.group(1)) * pow( Decimal(10) , k) - else: - amount = Decimal(am) * COIN - out['amount'] = int(amount) + try: + m = re.match(r'([0-9.]+)X([0-9])', am) + if m: + k = int(m.group(2)) - 8 + amount = Decimal(m.group(1)) * pow( Decimal(10) , k) + else: + amount = Decimal(am) * COIN + out['amount'] = int(amount) + except Exception as e: + raise InvalidBitcoinURI(f"failed to parse 'amount' field: {repr(e)}") from e if 'message' in out: out['message'] = out['message'] out['memo'] = out['message'] if 'time' in out: - out['time'] = int(out['time']) + try: + out['time'] = int(out['time']) + except Exception as e: + raise InvalidBitcoinURI(f"failed to parse 'time' field: {repr(e)}") from e if 'exp' in out: - out['exp'] = int(out['exp']) + try: + out['exp'] = int(out['exp']) + except Exception as e: + raise InvalidBitcoinURI(f"failed to parse 'exp' field: {repr(e)}") from e if 'sig' in out: - out['sig'] = bh2u(bitcoin.base_decode(out['sig'], None, base=58)) + try: + out['sig'] = bh2u(bitcoin.base_decode(out['sig'], None, base=58)) + except Exception as e: + raise InvalidBitcoinURI(f"failed to parse 'sig' field: {repr(e)}") from e r = out.get('r') sig = out.get('sig') t@@ -782,7 +801,7 @@ def parse_URI(uri: str, on_pr: Callable=None) -> dict: request = await pr.get_payment_request(r) if on_pr: on_pr(request) - loop = asyncio.get_event_loop() + loop = loop or asyncio.get_event_loop() asyncio.run_coroutine_threadsafe(get_payment_request(), loop) return out