Messages size at once; quoted inline images travel inside the reply

The message frame is set up as soon as its document is parsed instead of at
its load event, which waits for every image (remote ones included): it no
longer sits at its first height until the slowest image arrives.

A reply quoted an original's inline (cid:) images as this server's own URLs,
and they were sent like that — broken for the recipient, and mangled copies
("http:///api/…") made browsers look up a host named "api". Outgoing mail
(and the IMAP Drafts copy) now carries them as inline parts with new
Content-IDs; one that can no longer be had is left out. Shown messages load
such URLs from this server (sanitizer v5).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-08 20:20:21 +02:00
co-authored by Claude Opus 5.5
parent d0caf76b03
commit 0514053252
6 changed files with 317 additions and 7 deletions
+13 -1
View File
@@ -17,7 +17,12 @@ import nh3
# Bump when the sanitization policy changes; body_parts.sanitizer_v records it so a
# render re-sanitizes stale content from the raw blob (SPEC §10.4).
SANITIZER_VERSION = 4
# v5: this server's own inline-image URLs quoted in a message load from here.
SANITIZER_VERSION = 5
# This server's inline-image URL, however it ended up written: "/api/…",
# "https://host/api/…", or mangled to "http:///api/…" by an earlier copy.
INTERNAL_CID = re.compile(r"/api/messages/([0-9a-fA-F-]{36})/cid/([^\"'?#\s<>]+)")
_ALLOWED_TAGS: set[str] = {
# structure
@@ -202,6 +207,13 @@ def sanitize_html(
if lowered.startswith("cid:"):
content_id = value.strip()[4:]
return f"/api/messages/{message_id}/cid/{content_id}"
# This server's own inline-image URL, quoted from another message
# (by an earlier version, or another of the user's own mails) —
# possibly mangled to "http:///api/…", which a browser takes for a
# host named "api": load it from here, never look that host up.
internal = INTERNAL_CID.search(value)
if internal is not None and not lowered.startswith("data:"):
return f"/api/messages/{internal.group(1)}/cid/{internal.group(2)}"
if lowered.startswith(("http:", "https:")):
state["has_remote"] = True
return value if allow_remote_images else None # block by default (FR-23)