diff --git a/electrum/interface.py b/electrum/interface.py index 9edbd41dc..4e68141c1 100644 --- a/electrum/interface.py +++ b/electrum/interface.py @@ -292,6 +292,7 @@ class ServerAddr: @classmethod def from_str(cls, s: str) -> 'ServerAddr': + """Constructs a ServerAddr or raises ValueError.""" # host might be IPv6 address, hence do rsplit: host, port, protocol = str(s).rsplit(':', 2) return ServerAddr(host=host, port=port, protocol=protocol) @@ -299,20 +300,29 @@ class ServerAddr: @classmethod def from_str_with_inference(cls, s: str) -> Optional['ServerAddr']: """Construct ServerAddr from str, guessing missing details. + Does not raise - just returns None if guessing failed. Ongoing compatibility not guaranteed. """ if not s: return None + host = "" + if s[0] == "[" and "]" in s: # IPv6 address + host_end = s.index("]") + host = s[1:host_end] + s = s[host_end+1:] items = str(s).rsplit(':', 2) if len(items) < 2: return None # although maybe we could guess the port too? - host = items[0] + host = host or items[0] port = items[1] if len(items) >= 3: protocol = items[2] else: protocol = PREFERRED_NETWORK_PROTOCOL - return ServerAddr(host=host, port=port, protocol=protocol) + try: + return ServerAddr(host=host, port=port, protocol=protocol) + except ValueError: + return None def to_friendly_name(self) -> str: # note: this method is closely linked to from_str_with_inference diff --git a/electrum/tests/test_interface.py b/electrum/tests/test_interface.py new file mode 100644 index 000000000..84e223e09 --- /dev/null +++ b/electrum/tests/test_interface.py @@ -0,0 +1,48 @@ +from electrum.interface import ServerAddr + +from . import ElectrumTestCase + + +class TestServerAddr(ElectrumTestCase): + + def test_from_str(self): + self.assertEqual(ServerAddr(host="104.198.149.61", port=80, protocol="t"), + ServerAddr.from_str("104.198.149.61:80:t")) + self.assertEqual(ServerAddr(host="ecdsa.net", port=110, protocol="s"), + ServerAddr.from_str("ecdsa.net:110:s")) + self.assertEqual(ServerAddr(host="2400:6180:0:d1::86b:e001", port=50002, protocol="s"), + ServerAddr.from_str("[2400:6180:0:d1::86b:e001]:50002:s")) + self.assertEqual(ServerAddr(host="localhost", port=8080, protocol="s"), + ServerAddr.from_str("localhost:8080:s")) + + def test_from_str_with_inference(self): + self.assertEqual(None, ServerAddr.from_str_with_inference("104.198.149.61")) + self.assertEqual(None, ServerAddr.from_str_with_inference("ecdsa.net")) + self.assertEqual(None, ServerAddr.from_str_with_inference("2400:6180:0:d1::86b:e001")) + self.assertEqual(None, ServerAddr.from_str_with_inference("[2400:6180:0:d1::86b:e001]")) + + self.assertEqual(ServerAddr(host="104.198.149.61", port=80, protocol="s"), + ServerAddr.from_str_with_inference("104.198.149.61:80")) + self.assertEqual(ServerAddr(host="ecdsa.net", port=110, protocol="s"), + ServerAddr.from_str_with_inference("ecdsa.net:110")) + self.assertEqual(ServerAddr(host="2400:6180:0:d1::86b:e001", port=50002, protocol="s"), + ServerAddr.from_str_with_inference("[2400:6180:0:d1::86b:e001]:50002")) + + self.assertEqual(ServerAddr(host="104.198.149.61", port=80, protocol="t"), + ServerAddr.from_str_with_inference("104.198.149.61:80:t")) + self.assertEqual(ServerAddr(host="ecdsa.net", port=110, protocol="s"), + ServerAddr.from_str_with_inference("ecdsa.net:110:s")) + self.assertEqual(ServerAddr(host="2400:6180:0:d1::86b:e001", port=50002, protocol="s"), + ServerAddr.from_str_with_inference("[2400:6180:0:d1::86b:e001]:50002:s")) + + def test_to_friendly_name(self): + self.assertEqual("104.198.149.61:80:t", + ServerAddr(host="104.198.149.61", port=80, protocol="t").to_friendly_name()) + self.assertEqual("ecdsa.net:110", + ServerAddr(host="ecdsa.net", port=110, protocol="s").to_friendly_name()) + self.assertEqual("ecdsa.net:50001:t", + ServerAddr(host="ecdsa.net", port=50001, protocol="t").to_friendly_name()) + self.assertEqual("[2400:6180:0:d1::86b:e001]:50002", + ServerAddr(host="2400:6180:0:d1::86b:e001", port=50002, protocol="s").to_friendly_name()) + self.assertEqual("[2400:6180:0:d1::86b:e001]:50001:t", + ServerAddr(host="2400:6180:0:d1::86b:e001", port=50001, protocol="t").to_friendly_name())