"""Text hygiene: everything user- or web-supplied is reduced to plain text before it is stored. Rendering escapes again (defence in depth).""" from __future__ import annotations import html import re import unicodedata import urllib.parse from html.parser import HTMLParser _CTRL = re.compile(r"[\x00-\x08\x0b\x0c\x0e-\x1f\x7f\u200b-\u200f\u202a-\u202e\u2066-\u2069\ufeff]") URL_RE = re.compile(r"https?://[^\s<>\"'`]+", re.I) def plain(s, limit: int) -> str: """Unicode-normalised plain text: no tags, no control/bidi characters, collapsed spaces.""" if not isinstance(s, str): return "" s = unicodedata.normalize("NFKC", s) s = strip_tags(s) if "<" in s else s s = html.unescape(s) s = re.sub(r"<[^>]*>?", "", s) # anything tag-like left after unescape s = _CTRL.sub("", s.encode("utf-8", "ignore").decode("utf-8")) s = re.sub(r"\s+", " ", s).strip() return s[:limit].rstrip() class _Strip(HTMLParser): SKIP = {"script", "style", "noscript", "template", "svg", "iframe", "head", "nav", "footer", "aside", "form"} def __init__(self): super().__init__(convert_charrefs=True) self.out: list[str] = [] self.paras: list[str] = [] self._buf: list[str] = [] self.depth = 0 self.title = "" self._in_title = False self.meta: dict[str, str] = {} def handle_starttag(self, tag, attrs): if tag in self.SKIP: self.depth += 1 if tag == "title": self._in_title = True if tag == "meta": a = dict(attrs) k = (a.get("property") or a.get("name") or "").lower() if k in ("og:title", "og:description", "description", "og:site_name", "article:published_time") and a.get("content"): self.meta.setdefault(k, a["content"]) if tag in ("p", "br", "div", "li", "h1", "h2", "h3"): self._flush() def handle_endtag(self, tag): if tag in self.SKIP and self.depth: self.depth -= 1 if tag == "title": self._in_title = False if tag in ("p", "li", "div", "h1", "h2", "h3"): self._flush() def _flush(self): t = " ".join("".join(self._buf).split()) if len(t) > 60: self.paras.append(t) self._buf = [] def handle_data(self, data): if self._in_title: self.title += data if not self.depth: self.out.append(data) self._buf.append(data) def strip_tags(s: str) -> str: p = _Strip() try: p.feed(s) p.close() except Exception: # noqa: BLE001 - malformed markup: fall back to regex return re.sub(r"<[^>]*>", " ", s) return " ".join("".join(p.out).split()) def page_info(body: bytes, ctype: str) -> dict: """Title, description and the first readable paragraphs of an HTML page (for summarising).""" m = re.search(r"charset=([\w-]+)", ctype or "") try: s = body.decode(m.group(1) if m else "utf-8", "replace") except LookupError: s = body.decode("utf-8", "replace") p = _Strip() try: p.feed(s) p.close() except Exception: # noqa: BLE001 pass p._flush() return {"title": plain(p.meta.get("og:title") or p.title, 200), "description": plain(p.meta.get("og:description") or p.meta.get("description"), 500), "site": plain(p.meta.get("og:site_name"), 80), "published": plain(p.meta.get("article:published_time"), 40), "text": plain(" ".join(p.paras), 4000)} def safe_url(u) -> str: """Only absolute http(s) URLs without credentials survive; anything else becomes ''.""" if not isinstance(u, str): return "" u = u.strip() if len(u) > 2000 or _CTRL.search(u) or any(c in u for c in " <>\"'`\\"): return "" p = urllib.parse.urlsplit(u) if p.scheme.lower() not in ("http", "https") or not p.hostname or p.username or p.password: return "" return urllib.parse.urlunsplit((p.scheme.lower(), p.netloc.lower(), p.path, p.query, "")) def domain(u: str) -> str: h = (urllib.parse.urlsplit(u).hostname or "").lower() return h[4:] if h.startswith("www.") else h