Privacy
This commit is contained in:
parent
c53e2a3319
commit
43c5a948e8
@ -193,6 +193,8 @@ The CLIENT pipeline (`ContentRenderer.js`, `dp-content`/`dp-title`) is retained
|
||||
|
||||
**Emoji shortcodes are the full GitHub/Discord `:name:` set** (~4869 names), generated once from the `emoji` library by `rendering.py` `build_emoji_shortcodes()`; the frontend gets the identical map via the generated `static/js/emoji-shortcodes.js` (regenerate with `devplace emoji-sync` after bumping the dependency, never hand-edit).
|
||||
|
||||
**Email anonymization:** both `_render_content` and `_render_title` mask email addresses to prevent doxing. `_mask_emails` in `rendering.py` runs on rendered text nodes only (inside `_transform_text`, `_MediaProcessor`, and `_InlineFilter` - never before mistune, or the `*` mask characters would be parsed as emphasis) and stars ~80% of the local part (keeps a leading 20%, minimum one visible char). Addresses on `molodetz.nl` (and its subdomains) are exempt and render verbatim.
|
||||
|
||||
**Em-dash normalization:** `_normalize_dashes` in `rendering.py` replaces all forms (em dash `\u2014`, en dash `\u2013`, and their HTML entities) with a hyphen BEFORE mistune processes the text. Runs inside `_render_content`/`_render_title` which are `@lru_cache`d, so each unique text is normalized once. No per-template overhead.
|
||||
|
||||
### Auth
|
||||
|
||||
@ -69,6 +69,9 @@ _YOUTUBE_ALLOW = (
|
||||
"gyroscope; picture-in-picture"
|
||||
)
|
||||
|
||||
_EMAIL_RE = re.compile(r"\b[A-Za-z0-9._%+\-]+@[A-Za-z0-9.\-]+\.[A-Za-z]{2,}\b")
|
||||
_EMAIL_KEEP_DOMAIN = "molodetz.nl"
|
||||
|
||||
_MEDIA_SKIP_TAGS = {"a", "code", "pre"}
|
||||
_TITLE_INLINE_TAGS = {
|
||||
"b", "strong", "i", "em", "code", "del", "s", "mark", "sub", "sup", "span", "br",
|
||||
@ -153,12 +156,26 @@ def _embed_url(url: str) -> str:
|
||||
)
|
||||
|
||||
|
||||
def _mask_email(match: re.Match) -> str:
|
||||
email = match.group(0)
|
||||
local, _, domain = email.partition("@")
|
||||
lowered = domain.lower()
|
||||
if lowered == _EMAIL_KEEP_DOMAIN or lowered.endswith("." + _EMAIL_KEEP_DOMAIN):
|
||||
return email
|
||||
reveal = max(1, len(local) - round(len(local) * 0.8))
|
||||
return f"{local[:reveal]}{'*' * (len(local) - reveal)}@{domain}"
|
||||
|
||||
|
||||
def _mask_emails(text: str) -> str:
|
||||
return _EMAIL_RE.sub(_mask_email, text)
|
||||
|
||||
|
||||
def _transform_text(text: str) -> str:
|
||||
out: list[str] = []
|
||||
pos = 0
|
||||
for match in _TOKEN_RE.finditer(text):
|
||||
if match.start() > pos:
|
||||
out.append(html.escape(text[pos:match.start()]))
|
||||
out.append(html.escape(_mask_emails(text[pos:match.start()])))
|
||||
if match.group("url"):
|
||||
out.append(_embed_url(match.group("url")))
|
||||
else:
|
||||
@ -169,7 +186,7 @@ def _transform_text(text: str) -> str:
|
||||
)
|
||||
pos = match.end()
|
||||
if pos < len(text):
|
||||
out.append(html.escape(text[pos:]))
|
||||
out.append(html.escape(_mask_emails(text[pos:])))
|
||||
return "".join(out)
|
||||
|
||||
|
||||
@ -205,7 +222,7 @@ class _MediaProcessor(HTMLParser):
|
||||
|
||||
def handle_data(self, data: str) -> None:
|
||||
if self._skip_depth > 0:
|
||||
self._out.append(html.escape(data))
|
||||
self._out.append(html.escape(_mask_emails(data)))
|
||||
else:
|
||||
self._out.append(_transform_text(data))
|
||||
|
||||
@ -231,7 +248,7 @@ class _InlineFilter(HTMLParser):
|
||||
self._out.append(f"</{tag}>")
|
||||
|
||||
def handle_data(self, data: str) -> None:
|
||||
self._out.append(html.escape(data))
|
||||
self._out.append(html.escape(_mask_emails(data)))
|
||||
|
||||
def result(self) -> str:
|
||||
return "".join(self._out).strip()
|
||||
|
||||
Loading…
Reference in New Issue
Block a user