URI: 
       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