1
0

validate and deduplicate relay config input in qt gui

Adds validation and deduplication of the relay urls entered in the QT
settings dialog. This is supposed to prevent malformed or duplicated
relay entries.
Also resets the relays to the default value if no (valid) url
is entered. This prevents the user from getting stuck without relays
(otherwise the user would have to research for relay urls manually if
they don't know any).
This commit is contained in:
f321x
2025-04-30 14:15:01 +02:00
parent 4a8590077e
commit ee7d2ee17d
2 changed files with 34 additions and 5 deletions

View File

@@ -24,7 +24,7 @@
# SOFTWARE. # SOFTWARE.
import ast import ast
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, Dict
from PyQt6.QtCore import Qt from PyQt6.QtCore import Qt
from PyQt6.QtWidgets import (QComboBox, QTabWidget, QDialog, QSpinBox, QCheckBox, QLabel, from PyQt6.QtWidgets import (QComboBox, QTabWidget, QDialog, QSpinBox, QCheckBox, QLabel,
@@ -32,7 +32,7 @@ from PyQt6.QtWidgets import (QComboBox, QTabWidget, QDialog, QSpinBox, QCheckB
from electrum.i18n import _, languages from electrum.i18n import _, languages
from electrum import util from electrum import util
from electrum.util import base_units_list, event_listener from electrum.util import base_units_list, event_listener, is_valid_websocket_url
from electrum.gui import messages from electrum.gui import messages
@@ -181,7 +181,16 @@ class SettingsDialog(QDialog, QtEventListener):
self.nostr_relays_e = QLineEdit(nostr_relays) self.nostr_relays_e = QLineEdit(nostr_relays)
def on_nostr_edit(): def on_nostr_edit():
self.config.NOSTR_RELAYS = str(self.nostr_relays_e.text()) relays: Dict[str, None] = dict() # dicts keep insertion order
for url in self.nostr_relays_e.text().split(','):
url = url.strip()
if url and is_valid_websocket_url(url):
relays[url] = None
if relays.keys():
self.config.NOSTR_RELAYS = ",".join(relays.keys())
else: # if no valid relays are given, assign default relays from config
self.config.NOSTR_RELAYS = None
self.nostr_relays_e.setText(self.config.NOSTR_RELAYS)
self.nostr_relays_e.editingFinished.connect(on_nostr_edit) self.nostr_relays_e.editingFinished.connect(on_nostr_edit)
msat_cb = checkbox_from_configvar(self.config.cv.BTC_AMOUNTS_PREC_POST_SAT) msat_cb = checkbox_from_configvar(self.config.cv.BTC_AMOUNTS_PREC_POST_SAT)

View File

@@ -31,14 +31,13 @@ from typing import (NamedTuple, Union, TYPE_CHECKING, Tuple, Optional, Callable,
from datetime import datetime, timezone, timedelta from datetime import datetime, timezone, timedelta
import decimal import decimal
from decimal import Decimal from decimal import Decimal
import urllib from urllib.parse import urlparse
import threading import threading
import hmac import hmac
import hashlib import hashlib
import stat import stat
import locale import locale
import asyncio import asyncio
import urllib.request, urllib.parse, urllib.error
import builtins import builtins
import json import json
import time import time
@@ -699,6 +698,27 @@ def is_valid_email(s):
regexp = r"[^@]+@[^@]+\.[^@]+" regexp = r"[^@]+@[^@]+\.[^@]+"
return re.match(regexp, s) is not None return re.match(regexp, s) is not None
def is_valid_websocket_url(url: str) -> bool:
"""
uses this django url validation regex:
https://github.com/django/django/blob/2c6906a0c4673a7685817156576724aba13ad893/django/core/validators.py#L45C1-L52C43
Note: this is not perfect, urls and their parsing can get very complex (see recent django code).
however its sufficient for catching weird user input in the gui dialog
"""
# stores the compiled regex in the function object itself to avoid recompiling it every call
if not hasattr(is_valid_websocket_url, "regex"):
is_valid_websocket_url.regex = re.compile(
r'^(?:ws|wss)://' # ws:// or wss://
r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
r'localhost|' # localhost...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
try:
return re.match(is_valid_websocket_url.regex, url) is not None
except Exception:
return False
def is_hash256_str(text: Any) -> bool: def is_hash256_str(text: Any) -> bool:
if not isinstance(text, str): return False if not isinstance(text, str): return False