retronews

a featureful fork of the luke8086/retronews hn+lobste.rs tui
Log | Files | Refs | README | LICENSE

commit df64ed1752e69b8a46c1fad85b898732b06d2138
parent 0a57a6665a732eaa6f82429acb453a5c48478668
Author: luke8086 <55237178+luke8086@users.noreply.github.com>
Date:   Wed, 14 Jun 2023 15:23:44 +0000

Improve code for sanitizing message text

Diffstat:
Mretronews.py | 29++++++++++++++++-------------
Mtests.py | 2+-
2 files changed, 17 insertions(+), 14 deletions(-)

diff --git a/retronews.py b/retronews.py @@ -23,6 +23,7 @@ import logging import os import re import sqlite3 +import unicodedata import urllib.request from datetime import datetime from functools import partial, reduce @@ -352,6 +353,18 @@ def wrap_paragraph(text: str) -> list[str]: return wrap(text, subsequent_indent=indent, break_on_hyphens=False, break_long_words=False) +def sanitize_text(text: Optional[str]) -> str: + # For safety, remove any control characters except for \n and \t + # At least on HN some messages contain \x00 characters + + text = text or "" + allowed_cc = set(("\n", "\t")) + chars = (c for c in text if c in allowed_cc or unicodedata.category(c) != "Cc") + text = "".join(chars) + + return text + + def parse_html(html: str) -> list[str]: # This parser works well for HN messages because their markup is simple, and it can do # some custom optimizations, like expanding ellipsis-shortened links, preserving quote @@ -655,13 +668,8 @@ def msg_flatten_thread(msg: Message, prefix: str = "", is_last_child: bool = Fal yield child -def msg_sanitize_lines(lines: list[str]) -> list[str]: - # Some HN messages include null characters, which crash ncurses - return [line.replace("\u0000", "") for line in lines] - - def msg_build_raw_lines(msg: Message) -> list[str]: - text = msg.body or "" + text = sanitize_text(msg.body) # Unescape selected entities for better readability repl = {"&#x2F;": "/", "&#x27;": "'", "&quot;": '"'} @@ -680,16 +688,11 @@ def msg_build_lines(msg: Message) -> list[str]: "", ] - lines += parse_html(msg.body or "") if not msg.is_deleted else ["<deleted>"] + lines += parse_html(sanitize_text(msg.body)) if not msg.is_deleted else ["<deleted>"] return lines -def msg_update_lines(msg: Message, raw_mode: bool = False) -> None: - msg.lines = msg_build_raw_lines(msg) if raw_mode else msg_build_lines(msg) - msg.lines = msg_sanitize_lines(msg.lines) - - def msg_unload(msg: Message) -> Message: msg.children = [] msg.body = None @@ -817,7 +820,7 @@ def app_refresh_message(app: AppState) -> None: # Converting html to lines lazily on render for easier debugging if (msg := app.selected_message) is not None: - msg_update_lines(msg, raw_mode=app.raw_mode) + msg.lines = msg_build_raw_lines(msg) if app.raw_mode else msg_build_lines(msg) def app_select_message(app: AppState, message: Optional[Message], show_pager: bool = False) -> None: diff --git a/tests.py b/tests.py @@ -16,7 +16,7 @@ class TestHtmlParser(unittest.TestCase): with open(html_path) as fp: html = fp.read() - actual = "\n".join(retronews.parse_html(html)).strip() + actual = "\n".join(retronews.parse_html(retronews.sanitize_text(html))).strip() if not os.path.exists(out_path): with open(out_path, "w") as fp: