feat: replies mirror the thread view mode (html vs plaintext) #26

Closed
clanker wants to merge 2 commits from pr/html-replies into master
Member

Replies mirror the thread view mode (HTML ↔ plaintext) + renderable-fragment fix.

Replying/forwarding composes in the format you were viewing:

  • HTML view → rich-text reply: attribution + the original's renderable body inside a blockquote
  • Plaintext view → the existing > quoted text, unchanged

Fix for full-document mail (the reported issue): marketing emails are full documents (<html><head><meta> + <style>). Chromium renders them in the view, but Qt's compose editor can't — head/style were leaking as raw text. Now the quote:

  • extracts the <body> inner HTML (html_utils.html_fragment), dropping head/style/script wrappers
  • bleach-sanitises with a Qt-friendly tag set (blocks, tables, lists, links — what QTextEdit can actually render) and strips the rest instead of escaping it, so <h1>/<p> render as rich text rather than &lt;h1&gt; text
  • a malicious <script> is dropped entirely from the outgoing mail

Plumbing: the view's html_mode travels open_compose(mode, msg, html=…) → ComposeSeed.body_html → RichTextEditor.setHtml, with plaintext fallback for bodyless-HTML mail. List replies use default_to_html. Signatures render at top in both modes.

Tests: +8 (HTML seeds, full-document fragment regression ×2 — seed and compose-panel — sanitisation, signature, fallback, plain-mode unchanged). Suite: 151 passed, mypy 0.

Eyeball: reply to a marketing email in HTML view — the compose editor should show the body rendered (headings, text) with no <style>/charset leakage; send it and check your Sent folder.

**Replies mirror the thread view mode (HTML ↔ plaintext) + renderable-fragment fix.** Replying/forwarding composes in the format you were viewing: - **HTML view** → rich-text reply: attribution + the original's **renderable body** inside a blockquote - **Plaintext view** → the existing `> ` quoted text, unchanged **Fix for full-document mail** (the reported issue): marketing emails are full documents (`<html><head><meta>` + `<style>`). Chromium renders them in the view, but Qt's compose editor can't — head/style were leaking as raw text. Now the quote: - extracts the `<body>` inner HTML (`html_utils.html_fragment`), dropping head/style/script wrappers - bleach-sanitises with a **Qt-friendly tag set** (blocks, tables, lists, links — what `QTextEdit` can actually render) and **strips** the rest instead of escaping it, so `<h1>`/`<p>` render as rich text rather than `&lt;h1&gt;` text - a malicious `<script>` is dropped entirely from the outgoing mail Plumbing: the view's `html_mode` travels `open_compose(mode, msg, html=…)` → `ComposeSeed.body_html` → `RichTextEditor.setHtml`, with plaintext fallback for bodyless-HTML mail. List replies use `default_to_html`. Signatures render at top in both modes. Tests: **+8** (HTML seeds, full-document fragment regression ×2 — seed and compose-panel — sanitisation, signature, fallback, plain-mode unchanged). Suite: **151 passed**, mypy **0**. Eyeball: reply to a marketing email in HTML view — the compose editor should show the body rendered (headings, text) with no `<style>`/`charset` leakage; send it and check your Sent folder.
Replying/forwarding now composes in the same format you were viewing:

- Thread preview in HTML mode (H toggled) -> rich-text reply/forward:
  attribution + the original HTML inside a blockquote, bleach-sanitised
  so a malicious message can't smuggle a live script into the outgoing
  mail (escaped, verified end-to-end).
- Plaintext view -> the existing '> ' quoted text reply, unchanged.

The view's html_mode travels via open_compose(mode, msg, html=...) ->
ComposeSeed.body_html -> RichTextEditor.setHtml (falling back to the
plaintext quote when the message has no HTML body). List replies use
settings.default_to_html. Signature block renders at the top in both.

Tests: +6 — html reply/forward seeds (quote, sanitisation, sig,
fallback), plain-mode unchanged, and ComposePanel integration (editor
content + sanitised outgoing body_html). 148 passed, mypy 0.
Full-document marketing mail (<html><head><meta> + <style>) leaked its
raw markup into HTML replies — Qt's compose editor can't render head/
style, so the CSS showed as text. Now:

- html_utils.html_fragment(): extracts the <body> inner HTML, drops
  head/style/script wrappers.
- html_utils.clean_qt_fragment(): bleach sanitises with the tag set Qt
  can actually render (blocks, tables, lists) and STRIPS the rest
  instead of escaping it — so <h1>/<p> render as rich text instead of
  showing as &lt;h1&gt;.
- quote_body_html / forwarded_html use fragment+qt-cleaner.

Tests: +3 (full-document seed: no head/style leak, body rendered;
compose panel renders the body as rich text; script block dropped
entirely). 151 passed, mypy 0.
clanker closed this pull request 2026-08-14 22:05:28 +00:00

Pull request closed

Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Home/lazarus!26
No description provided.