Improved string sanitization, added dynamic timeout calculations, improved state persistence, fixed .allowed not being respected for downloadable files, by Zenith

This commit is contained in:
Mark Qvist 2026-09-02 22:48:31 +02:00
parent 3eb410afdd
commit 54491d8f2b
14 changed files with 139 additions and 99 deletions

View file

@ -873,7 +873,7 @@ class ConversationMessage:
name = str(name) if name is not None else ""
except Exception:
name = ""
name = re.sub(r"[\x00-\x1f\x7f]", "", name)
name = re.sub(r"[\x00-\x1f\x7f-\x9f]", "", name)
parts = re.split(r"[/\\]", name)
name = parts[-1] if parts else ""
if ":" in name:

View file

@ -9,6 +9,7 @@ import RNS.vendor.umsgpack as msgpack
class Node:
JOB_INTERVAL = 5
SETTINGS_FLUSH_INTERVAL = 60
START_ANNOUNCE_DELAY = 6
def __init__(self, app):
@ -24,6 +25,8 @@ class Node:
self.file_refresh_interval = self.app.file_refresh_interval
self.job_interval = Node.JOB_INTERVAL
self.should_run_jobs = True
self.settings_dirty = False
self.last_settings_flush = time.time()
self.app_data = None
self.name = self.app.node_name
@ -100,7 +103,8 @@ class Node:
directories = [file for file in os.listdir(base_path) if os.path.isdir(os.path.join(base_path, file)) and file[:1] != "."]
for file in files:
self.servedfiles.append(base_path+"/"+file)
if not file.endswith(".allowed"):
self.servedfiles.append(base_path+"/"+file)
for directory in directories:
self.scan_files(base_path+"/"+directory)
@ -109,51 +113,13 @@ class Node:
RNS.log("Page request "+RNS.prettyhexrep(request_id)+" for: "+str(path), RNS.LOG_VERBOSE)
try:
self.app.peer_settings["served_page_requests"] += 1
self.app.save_peer_settings()
self.settings_dirty = True
except Exception as e:
RNS.log("Could not increase served page request count", RNS.LOG_ERROR)
file_path = path.replace("/page", self.app.pagespath, 1)
allowed_path = file_path+".allowed"
request_allowed = False
if os.path.isfile(allowed_path):
allowed_list = []
try:
if os.access(allowed_path, os.X_OK):
allowed_result = subprocess.run([allowed_path], stdout=subprocess.PIPE)
allowed_input = allowed_result.stdout
else:
with open(allowed_path, "rb") as fh:
allowed_input = fh.read()
allowed_hash_strs = allowed_input.splitlines()
for hash_str in allowed_hash_strs:
if len(hash_str) == RNS.Identity.TRUNCATED_HASHLENGTH//8*2:
try:
allowed_hash = bytes.fromhex(hash_str.decode("utf-8"))
allowed_list.append(allowed_hash)
except Exception as e:
RNS.log("Could not decode RNS Identity hash from: "+str(hash_str), RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
except Exception as e:
RNS.log("Error while fetching list of allowed identities for request: "+str(e), RNS.LOG_ERROR)
if hasattr(remote_identity, "hash") and remote_identity.hash in allowed_list:
request_allowed = True
else:
request_allowed = False
RNS.log("Denying request, remote identity was not in list of allowed identities", RNS.LOG_VERBOSE)
else:
request_allowed = True
request_allowed = self.request_allowed(file_path, remote_identity)
try:
if request_allowed:
@ -187,18 +153,52 @@ class Node:
RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
return None
def request_allowed(self, file_path, remote_identity):
allowed_path = file_path+".allowed"
if not os.path.isfile(allowed_path):
return True
allowed_list = []
try:
if os.access(allowed_path, os.X_OK):
allowed_input = subprocess.run([allowed_path], stdout=subprocess.PIPE).stdout
else:
with open(allowed_path, "rb") as fh:
allowed_input = fh.read()
for hash_str in allowed_input.splitlines():
if len(hash_str) == RNS.Identity.TRUNCATED_HASHLENGTH//8*2:
try:
allowed_list.append(bytes.fromhex(hash_str.decode("utf-8")))
except Exception as e:
RNS.log("Could not decode RNS Identity hash from: "+str(hash_str), RNS.LOG_DEBUG)
RNS.log("The contained exception was: "+str(e), RNS.LOG_DEBUG)
except Exception as e:
RNS.log("Error while fetching list of allowed identities for request: "+str(e), RNS.LOG_ERROR)
if hasattr(remote_identity, "hash") and remote_identity.hash in allowed_list:
return True
RNS.log("Denying request, remote identity was not in list of allowed identities", RNS.LOG_VERBOSE)
return False
# TODO: Improve file handling, this will be slow for large files
def serve_file(self, path, data, request_id, remote_identity, requested_at):
RNS.log("File request "+RNS.prettyhexrep(request_id)+" for: "+str(path), RNS.LOG_VERBOSE)
try:
self.app.peer_settings["served_file_requests"] += 1
self.app.save_peer_settings()
self.settings_dirty = True
except Exception as e:
RNS.log("Could not increase served file request count", RNS.LOG_ERROR)
file_path = path.replace("/file", self.app.filespath, 1)
file_name = path.replace("/file/", "", 1)
if not self.request_allowed(file_path, remote_identity):
RNS.log("Request denied", RNS.LOG_VERBOSE)
return DEFAULT_NOTALLOWED.encode("utf-8")
try:
RNS.log("Serving file: "+file_path, RNS.LOG_VERBOSE)
return [open(file_path, "rb"), {"name": file_name.encode("utf-8")}]
@ -236,13 +236,19 @@ class Node:
self.register_files()
self.last_file_refresh = time.time()
if self.settings_dirty and now > self.last_settings_flush + Node.SETTINGS_FLUSH_INTERVAL:
self.settings_dirty = False
self.last_settings_flush = now
try: self.app.save_peer_settings()
except Exception as e: RNS.log("Could not save peer settings: "+str(e), RNS.LOG_ERROR)
time.sleep(self.job_interval)
def peer_connected(self, link):
RNS.log("Peer connected to "+str(self.destination), RNS.LOG_VERBOSE)
try:
self.app.peer_settings["node_connects"] += 1
self.app.save_peer_settings()
self.settings_dirty = True
except Exception as e:
RNS.log("Could not increase node connection count", RNS.LOG_ERROR)

View file

@ -8,6 +8,7 @@ import threading
import traceback
import subprocess
import contextlib
import tempfile
import RNS
import LXMF
@ -129,6 +130,7 @@ class NomadNetworkApp:
self.firstrun = False
self.should_run_jobs = True
self.job_interval = 5
self.peer_settings_lock = threading.Lock()
self.defer_jobs = 90
self.page_refresh_interval = 0
self.file_refresh_interval = 0
@ -249,11 +251,13 @@ class NomadNetworkApp:
RNS.log("The contained exception was: %s" % (str(e)), RNS.LOG_ERROR)
nomadnet.panic()
if os.path.isfile(self.peersettingspath):
self.peer_settings = None
self.peer_settings_notice = None
for path in (self.peersettingspath, self.peersettingspath+".bak"):
if self.peer_settings != None or not os.path.isfile(path): continue
try:
file = open(self.peersettingspath, "rb")
self.peer_settings = msgpack.unpackb(file.read())
file.close()
with open(path, "rb") as file:
self.peer_settings = msgpack.unpackb(file.read())
if not "node_last_announce" in self.peer_settings:
self.peer_settings["node_last_announce"] = None
@ -274,17 +278,21 @@ class NomadNetworkApp:
self.peer_settings["served_file_requests"] = 0
self.peer_settings["announce_interval"] = self.announce_interval
if path != self.peersettingspath:
self.peer_settings_notice = "Peer settings were restored from the last backup.\nCheck your display name and propagation node."
except Exception as e:
RNS.logdest = RNS.LOG_STDOUT
RNS.log(f"Could not load local peer settings from {self.peersettingspath}", RNS.LOG_ERROR)
RNS.log(f"Could not load local peer settings from {path}", RNS.LOG_ERROR)
RNS.log(f"The contained exception was: {e}", RNS.LOG_ERROR)
RNS.log(f"This likely means that the peer settings file has become corrupt.", RNS.LOG_ERROR)
RNS.log(f"You can try deleting the file at {self.peersettingspath} and restarting nomadnet.", RNS.LOG_ERROR)
nomadnet.panic()
else:
RNS.log(f"Moving the corrupt file to {path}.corrupt", RNS.LOG_ERROR)
try: os.replace(path, path+".corrupt")
except Exception: pass
self.peer_settings = None
self.peer_settings_notice = "Peer settings were corrupt and have been reset.\nCheck your display name and propagation node."
if self.peer_settings == None:
try:
RNS.log("No peer settings file found, creating new...")
RNS.log("Creating new peer settings file...")
self.peer_settings = {
"display_name": "Anonymous Peer",
"announce_interval": self.announce_interval,
@ -650,9 +658,16 @@ class NomadNetworkApp:
return self.message_router.get_outbound_propagation_node()
def save_peer_settings(self):
tmp_path = f"{self.peersettingspath}.tmp"
with open(tmp_path, "wb") as file: file.write(msgpack.packb(self.peer_settings))
os.replace(tmp_path, self.peersettingspath)
with self.peer_settings_lock:
fd, tmp_path = tempfile.mkstemp(dir=os.path.dirname(self.peersettingspath), prefix=".peersettings.")
try:
with os.fdopen(fd, "wb") as file: file.write(msgpack.packb(self.peer_settings))
if os.path.isfile(self.peersettingspath): os.replace(self.peersettingspath, self.peersettingspath+".bak")
os.replace(tmp_path, self.peersettingspath)
except Exception:
try: os.unlink(tmp_path)
except Exception: pass
raise
def lxmf_delivery(self, message):
time_string = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(message.timestamp))

View file

@ -8,6 +8,7 @@ from collections import deque
import RNS
from nomadnet.vendor import cbor
from nomadnet.util import strip_modifiers
HISTORY_DIR_NAME = "rrc_history"
@ -192,7 +193,7 @@ class RRCMessage:
self.room = room
self.src = src
self.nick = nick
self.text = text
self.text = strip_modifiers(text)
self.ts = ts
self.mention = False
@ -355,10 +356,11 @@ class RRCHub:
def _connect_worker(self):
try:
timeout_s = 20.0
path_timeout = self.manager.app.rns.get_medium_path_timeout()
timeout_s = max(20.0, path_timeout)
if not RNS.Transport.has_path(self.hub_hash):
RNS.Transport.request_path(self.hub_hash)
deadline = time.monotonic() + min(5.0, timeout_s)
deadline = time.monotonic() + max(5.0, path_timeout)
while time.monotonic() < deadline:
if RNS.Transport.has_path(self.hub_hash):
break
@ -429,7 +431,7 @@ class RRCHub:
except Exception as e:
self._log("HELLO send failed: "+str(e), RNS.LOG_ERROR)
attempts += 1
self._stop_hello.wait(timeout=3.0)
self._stop_hello.wait(timeout=max(3.0, (cur_link.rtt or 0)*cur_link.traffic_timeout_factor))
if not self.welcomed and not self._stop_hello.is_set():
self._set_status(RRCHub.STATUS_FAILED, "WELCOME timeout")
try:
@ -847,7 +849,7 @@ class RRCHub:
parsed = _parse_room_list_notice(text)
if parsed is not None:
with self._lock:
self.available_rooms = parsed
self.available_rooms = {strip_modifiers(name): strip_modifiers(topic) for name, topic in parsed.items()}
silent = self._silent_list_pending > 0
if silent:
self._silent_list_pending -= 1
@ -925,7 +927,7 @@ class RRCHub:
if isinstance(body, dict):
hub_name = body.get(B_WELCOME_HUB)
if isinstance(hub_name, str):
self.hub_name = hub_name
self.hub_name = strip_modifiers(hub_name)
ver = body.get(B_WELCOME_VER)
if isinstance(ver, str):
self.hub_version = ver
@ -1135,7 +1137,7 @@ class RRCHub:
room_n = room.strip().lower() if isinstance(room, str) else None
if room_n is None and isinstance(body, str) and body.strip():
with self._lock:
self.motd = body
self.motd = strip_modifiers(body)
self.manager._notify_change(self)
msg = RRCMessage(
"notice",
@ -1273,7 +1275,7 @@ class RRCHub:
return
if kind == RES_KIND_MOTD:
with self._lock:
self.motd = text
self.motd = strip_modifiers(text)
self.manager._notify_change(self)
elif self._process_notice_text(text):
return

View file

@ -279,6 +279,7 @@ class TextUI:
self.set_colormode(colormode)
self.main_display.start()
if intro_timeout <= 0 and self.app.peer_settings_notice: self.main_display.show_notice(self.app.peer_settings_notice)
self.loop.run()
def set_colormode(self, colormode):
@ -297,3 +298,4 @@ class TextUI:
def display_main(self, loop, user_data):
self.loop.widget = self.main_display.widget
if self.app.peer_settings_notice: self.main_display.show_notice(self.app.peer_settings_notice)

View file

@ -11,6 +11,7 @@ import threading
from threading import Lock
from .MicronParser import markup_to_attrmaps, make_style, default_state
from nomadnet.Directory import DirectoryEntry
from nomadnet.Conversation import ConversationMessage
from nomadnet.vendor.Scrollable import *
from nomadnet.util import strip_modifiers
from nomadnet.util import sanitize_name
@ -825,10 +826,9 @@ class Browser:
partial["pr_throttle"] = time.time()+15
RNS.log(f"Requesting path for partial: {partial_destination_hash} / {path}", RNS.LOG_EXTREME)
RNS.Transport.request_path(partial_destination_hash)
pr_time = time.time()+RNS.Transport.first_hop_timeout(partial_destination_hash)
pr_time = time.time()+self.path_timeout(partial_destination_hash)
while not RNS.Transport.has_path(partial_destination_hash):
now = time.time()
if now > pr_time+self.timeout: return
if time.time() > pr_time: return
time.sleep(0.25)
for pid in self.page_partials:
@ -853,7 +853,7 @@ class Browser:
partial["link"] = None
partial["link"] = RNS.Link(destination, established_callback = established, closed_callback = closed)
timeout = time.time()+self.timeout
timeout = time.time()+max(self.timeout, partial["link"].establishment_timeout)
while partial["link"].status != RNS.Link.ACTIVE and time.time() < timeout: time.sleep(0.1)
if partial["link"] and partial["link"].status == RNS.Link.ACTIVE and partial["request_id"] == None:
@ -1065,6 +1065,9 @@ class Browser:
def set_timeout(self, timeout):
self.timeout = timeout
def path_timeout(self, destination_hash):
return max(self.timeout + self.app.rns.get_first_hop_timeout(destination_hash), self.app.rns.get_medium_path_timeout())
def download_local_file(self, path):
try:
file_path = self.app.filespath+path.replace("/file", "", 1)
@ -1110,10 +1113,9 @@ class Browser:
self.status = Browser.PATH_REQUESTED
self.update_display()
pr_time = time.time()+RNS.Transport.first_hop_timeout(self.destination_hash)
pr_time = time.time()+self.path_timeout(self.destination_hash)
while not RNS.Transport.has_path(self.destination_hash):
now = time.time()
if now > pr_time+self.timeout:
if time.time() > pr_time:
self.request_timeout()
return
@ -1493,10 +1495,9 @@ class Browser:
self.status = Browser.PATH_REQUESTED
self.update_display()
pr_time = time.time()+RNS.Transport.first_hop_timeout(self.destination_hash)
pr_time = time.time()+self.path_timeout(self.destination_hash)
while not RNS.Transport.has_path(self.destination_hash):
now = time.time()
if now > pr_time+self.timeout:
if time.time() > pr_time:
self.request_timeout()
return
@ -1738,9 +1739,13 @@ class Browser:
def file_received(self, request_receipt):
try:
if type(request_receipt.response) == io.BufferedReader:
if isinstance(request_receipt.response, bytes):
markup = request_receipt.response.decode("utf-8")
self.attr_maps = markup_to_attrmaps(strip_modifiers(markup), url_delegate=self, fg_color=self.page_foreground_color, bg_color=self.page_background_color)
elif type(request_receipt.response) == io.BufferedReader:
if request_receipt.metadata != None:
file_name = os.path.basename(request_receipt.metadata["name"].decode("utf-8"))
file_name = ConversationMessage.safe_attachment_name(request_receipt.metadata["name"])
file_handle = request_receipt.response
file_destination = self.app.downloads_path+"/"+file_name
@ -1759,7 +1764,7 @@ class Browser:
else:
file_name = request_receipt.response[0]
file_data = request_receipt.response[1]
file_destination_name = os.path.basename(file_name)
file_destination_name = ConversationMessage.safe_attachment_name(file_name)
file_destination = self.app.downloads_path+"/"+file_destination_name
counter = 0

View file

@ -2809,7 +2809,7 @@ class LXMessageWidget(urwid.WidgetWrap):
title_string = g["warning"]+" "+g["arrow_l"]+" "+message.get_signature_description() + "\n " + title_string
if message.get_title() != "":
title_string += " | " + message.get_title()
title_string += " | " + strip_modifiers(message.get_title())
inbound_untrusted = False
if not is_outbound and msg_source_hash is not None:

View file

@ -953,18 +953,18 @@ class PageEditorDisplay():
common, restricted, total = self._folder_permissions(path)
content = common or ""
if not total:
state = "This folder contains no pages."
state = "This folder contains no files."
elif not restricted:
state = "All %d pages here are public." % total
state = "All %d files here are public." % total
elif common is not None:
state = "All %d pages here share these entries." % total
state = "All %d files here share these entries." % total
else:
state = "Entries differ across pages (%d of %d restricted)." % (restricted, total)
state = "Entries differ across files (%d of %d restricted)." % (restricted, total)
note = ("Allowed identity hashes per line.\n"
+ state + "\nSaving applies these entries to every page in this folder and its subfolders.")
+ state + "\nSaving applies these entries to every file in this folder and its subfolders.")
else:
content = self._read_allowed(path)
note = "Allowed identity hashes per line. Empty = public.\nApplies to this page."
note = "Allowed identity hashes per line. Empty = public.\nApplies to this page or file."
title = "Permissions: " + os.path.basename(path) + ("/" if is_dir else "")
self.show_dialog(PermissionsDialog(title, note, content,

View file

@ -498,9 +498,9 @@ By default, you can find the examples in `!~/.nomadnetwork/examples`!. If you bu
>>Authenticating Users
Sometimes, you don't want everyone to be able to view certain pages or execute certain scripts. In such cases, you can use `*authentication`* to control who gets to run certain requests.
Sometimes, you don't want everyone to be able to view certain pages, download certain files or execute certain scripts. In such cases, you can use `*authentication`* to control who gets to run certain requests.
To enable authentication for any page, simply add a new file to your pages directory with ".allowed" added to the file-name of the page. If your page is named "secret_page.mu", just add a file named "secret_page.mu.allowed".
To enable authentication for any page or file, simply add a new file next to it with ".allowed" added to its file-name. If your page is named "secret_page.mu", just add a file named "secret_page.mu.allowed". The same works for files in your files directory, and the ".allowed" file itself is never served.
For each user allowed to access the page, add a line to this file, containing the hash of that users primary identity. Users can find their own identity hash in the `![ Network ]`! part of the program, under `!Local Peer Info`!. If you want to allow access for three different users, your file would look like this:

View file

@ -4,6 +4,7 @@ import itertools
import mmap
import urwid
import nomadnet
from nomadnet.util import strip_modifiers
class LogDisplayShortcuts():
@ -68,7 +69,7 @@ class LogTerminal(urwid.WidgetWrap):
class LogTail(urwid.WidgetWrap):
def __init__(self, app):
self.app = app
self.log_tail = urwid.Text(tail(self.app.logfilepath, 50))
self.log_tail = urwid.Text(strip_modifiers(tail(self.app.logfilepath, 50)))
self.log = urwid.Scrollable(self.log_tail)
self.log.set_scrollpos(-1)
self.log_scrollbar = urwid.ScrollBar(self.log)

View file

@ -249,6 +249,12 @@ class MainDisplay():
valign="middle", height=8, min_width=20,
)
def show_notice(self, text):
def close(button=None): self.app.ui.loop.widget = self.frame
pile = urwid.Pile([urwid.Text(text, align="center"), urwid.Divider(), urwid.Button("OK", on_press=close)])
dialog = QuitDialogLineBox(urwid.Filler(pile, urwid.TOP), title="Notice", on_cancel=close)
self.app.ui.loop.widget = urwid.Overlay(dialog, self.frame, align="center", width=64, valign="middle", height=9, min_width=20)
def do_quit(self, sender=None):
logterm_pid = None
if True or RNS.vendor.platformutils.is_android():

View file

@ -8,6 +8,7 @@ from urwid.util import is_mouse_press
from urwid.text_layout import calc_coords
from .ReadlineEdit import ReadlineEdit
from RNS.Utilities.rngit.util import MarkdownToMicron
from nomadnet.util import STRIP_CONTROL_RE
DEFAULT_FG_DARK = "ddd"
DEFAULT_FG_LIGHT = "222"
@ -101,6 +102,7 @@ def ensure_selected_styles():
else: SELECTED_STYLES = STYLES_LIGHT
def markup_to_attrmaps(markup, url_delegate = None, fg_color=None, bg_color=None, link_class=None, anchors=None):
markup = STRIP_CONTROL_RE.sub("", markup)
global LINK_CLASS
if link_class: LINK_CLASS = link_class
else: LINK_CLASS = LinkableText

View file

@ -419,7 +419,7 @@ class ProbeView:
def _timeout(self):
try:
return DEFAULT_PROBE_TIMEOUT + self.app.rns.get_first_hop_timeout(self._dest_hash)
return max(DEFAULT_PROBE_TIMEOUT + self.app.rns.get_first_hop_timeout(self._dest_hash), self.app.rns.get_medium_path_timeout())
except Exception:
return DEFAULT_PROBE_TIMEOUT
@ -639,7 +639,7 @@ class PathView:
def _timeout(self):
try:
return DEFAULT_PROBE_TIMEOUT + self.app.rns.get_first_hop_timeout(self._dest_hash)
return max(DEFAULT_PROBE_TIMEOUT + self.app.rns.get_first_hop_timeout(self._dest_hash), self.app.rns.get_medium_path_timeout())
except Exception:
return DEFAULT_PROBE_TIMEOUT
@ -830,7 +830,7 @@ class IdentityView:
self.lookup_button.original_widget.set_label("Looking up…")
self._log("Identity unknown, requesting from the network …")
try:
self._deadline = time.time() + DEFAULT_PROBE_TIMEOUT + self.app.rns.get_first_hop_timeout(dest_hash)
self._deadline = time.time() + max(DEFAULT_PROBE_TIMEOUT + self.app.rns.get_first_hop_timeout(dest_hash), self.app.rns.get_medium_path_timeout())
except Exception:
self._deadline = time.time() + DEFAULT_PROBE_TIMEOUT
self.app.ui.loop.set_alarm_in(0.25, self._await_identity)
@ -975,7 +975,7 @@ class SendFileView:
def _timeout(self):
try:
return DEFAULT_PROBE_TIMEOUT + self.app.rns.get_first_hop_timeout(self._dest_hash)
return max(DEFAULT_PROBE_TIMEOUT + self.app.rns.get_first_hop_timeout(self._dest_hash), self.app.rns.get_medium_path_timeout())
except Exception:
return DEFAULT_PROBE_TIMEOUT
@ -1040,7 +1040,7 @@ class SendFileView:
self._log("Could not establish link: %s" % str(e), "error_text")
self._finish()
return
self._link_deadline = time.time() + self._timeout()
self._link_deadline = time.time() + max(self._timeout(), self._link.establishment_timeout)
self.app.ui.loop.set_alarm_in(0.5, self._check_link)
def _check_link(self, loop, user_data):
@ -1402,7 +1402,7 @@ class SpeedTestView:
return
self._incoming_link = link
link.set_remote_identified_callback(self._on_remote_identified)
self.app.ui.loop.set_alarm_in(8, lambda l, u: self._incoming_timeout(link))
self.app.ui.loop.set_alarm_in(max(8, self.app.rns.get_medium_path_timeout()), lambda l, u: self._incoming_timeout(link))
def _incoming_timeout(self, link):
if self._recv_link is link:
@ -1565,7 +1565,7 @@ class SpeedTestView:
def _timeout(self):
try:
return DEFAULT_PROBE_TIMEOUT + self.app.rns.get_first_hop_timeout(self._dest_hash)
return max(DEFAULT_PROBE_TIMEOUT + self.app.rns.get_first_hop_timeout(self._dest_hash), self.app.rns.get_medium_path_timeout())
except Exception:
return DEFAULT_PROBE_TIMEOUT
@ -1594,7 +1594,7 @@ class SpeedTestView:
self._log("Could not establish link: %s" % str(e), "error_text")
self._finish()
return
self._link_deadline = time.time() + self._timeout()
self._link_deadline = time.time() + max(self._timeout(), self._client_link.establishment_timeout)
self.app.ui.loop.set_alarm_in(0.5, self._check_link)
def _check_link(self, loop, user_data):

View file

@ -121,8 +121,9 @@ def strip_modifiers(text):
stripped = re.sub(r'[\U0001F3FB-\U0001F3FF]', '', stripped, flags=re.UNICODE)
stripped = re.sub(r'[\u200D\u200C]', '', stripped)
stripped = re.sub(r'\r\n?', '\n', stripped)
return stripped.strip().replace("\x00", "")
stripped = STRIP_CONTROL_RE.sub('', stripped)
return stripped.strip()
def sanitize_name(name):
if name is None: return None