mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-09-21 18:15:05 +00:00
* feat: add simplex-support-bot-light * feat: package simplex-support-bot-light for docker * docs: document simplex-support-bot-light * fix: make a missing config actionable and bounded * docs: note the attached compose Ctrl+C behaviour * refactor: use the library for startup and custom data * refactor: drop the display-name workaround * style: shorten the startup comment * build: build the core and the library into the image * docs: correct where the image type rule comes from * fix: log what the core said a command got wrong * build: run the container as the operator's uid * docs: keep the container uid in .env * docs: tighten the README
49 lines
1.8 KiB
Python
49 lines
1.8 KiB
Python
"""Sanitising peer-controlled text before it is rendered."""
|
||
|
||
from __future__ import annotations
|
||
|
||
import unicodedata
|
||
|
||
# mkValidName in src/Simplex/Chat/Library/Commands.hs caps a locally entered
|
||
# name at 50 characters. It is not applied to inbound profiles.
|
||
MAX_NAME = 50
|
||
|
||
UNNAMED = "(unnamed)"
|
||
|
||
# Characters that render as nothing but are neither whitespace nor a control
|
||
# category, so `str.split` and `str.isprintable` both let them through. A stock
|
||
# client accepts them in a profile name, which makes "ㅤㅤAlice" a
|
||
# working impersonation of "Alice".
|
||
# Separators the bot's own messages use. A customer chooses their display name,
|
||
# and the roster group is the operator's only record of who was added.
|
||
SEPARATORS = frozenset("→")
|
||
|
||
INVISIBLE = frozenset(
|
||
"ᅟᅠㅤᅠ" # Hangul fillers
|
||
"⠀" # Braille pattern blank
|
||
"឴឵" # Khmer inherent vowels
|
||
"" # word joiner, zero-width no-break space
|
||
)
|
||
|
||
|
||
def safe_name(name: str) -> str:
|
||
"""Collapse and truncate a display name for rendering.
|
||
|
||
The core does not sanitise inbound profiles: a peer's display name reaches
|
||
us verbatim and may contain newlines or run to kilobytes. Rendered as-is it
|
||
forges lines in the roster group and in the log, and can push a message past
|
||
the size the core will send.
|
||
"""
|
||
# NFKC folds compatibility forms, so a name cannot hide behind an exotic
|
||
# encoding of an ordinary character.
|
||
collapsed = " ".join(unicodedata.normalize("NFKC", name).split())
|
||
printable = "".join(
|
||
c for c in collapsed if c.isprintable() and c not in INVISIBLE and c not in SEPARATORS
|
||
)
|
||
stripped = printable.strip()
|
||
if not stripped:
|
||
return UNNAMED
|
||
if len(stripped) > MAX_NAME:
|
||
return stripped[: MAX_NAME - 1] + "…"
|
||
return stripped
|