Skip to content

Commit

Permalink
util.parse_URI: more granular exceptions
Browse files Browse the repository at this point in the history
related: #5376

first report in #5376 was generated with these changes;
before, the exception was caught and a toast displayed "Not a Bitcoin URI"
  • Loading branch information
SomberNight committed May 25, 2019
1 parent a591ccf commit 158090b
Show file tree
Hide file tree
Showing 4 changed files with 46 additions and 25 deletions.
2 changes: 2 additions & 0 deletions electrum/gui/kivy/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,8 @@ def on_qr_result(requestCode, resultCode, intent):
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)
Expand Down
9 changes: 4 additions & 5 deletions electrum/gui/kivy/uix/screens.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
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
Expand Down Expand Up @@ -174,11 +174,10 @@ def set_URI(self, text):
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', '')
Expand Down
7 changes: 4 additions & 3 deletions electrum/gui/qt/main_window.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@
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,
Expand Down Expand Up @@ -1844,8 +1845,8 @@ def pay_to_URI(self, URI):
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')
Expand Down
53 changes: 36 additions & 17 deletions electrum/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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')
Expand All @@ -782,7 +801,7 @@ async def get_payment_request():
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
Expand Down

0 comments on commit 158090b

Please sign in to comment.