Fix data-URI screenshots not opening on click (Chrome blocks data: URL navigation)
The click-to-zoom feature from the previous commit worked for cid:-referenced images but silently failed for images embedded directly as a data: URI — confirmed with a real Playwright click: Chrome refuses to navigate a tab (even a new one, even from a direct user click) to a data: URL, so <a href="data:...' target="_blank"> just does nothing. The cursor still showed zoom-in on hover since that's plain CSS, which is exactly the "лупа появляется, но не нажимается" symptom reported. Fix: imap.ts now extracts every data:image src out of an inbound email's HTML into a real attachment file (deduping identical images embedded more than once), the same way cid: images already were, and rewrites the HTML to point at that attachment's normal /api/attachments/... URL instead. That also shrinks messages.body_html (data URIs can be hundreds of KB sitting in a DB column) and gets data-URI images the same "no duplicate chip below" treatment cid: images already had. attachments.isInline is now its own real column (backfilled from the existing content_id-based cases) instead of being derived from content_id, since a data-URI-derived attachment is inline but was never cid-referenced. sanitizeEmailHtml's link-wrapping step now skips any residual data: src defensively (unwrapped-but-visible beats a link that looks clickable but isn't). Verified against the real deployment with actual browser clicks (Playwright): both a data-URI image and a cid: image now open their full-resolution attachment in a new tab; before this fix the data-URI one silently did nothing. Added a vitest.config.ts (needed for the new test file's @/ import aliases) and unit tests for the extraction/dedup logic. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GteWhnWKTmnXcsd5jx6H7u
This commit is contained in:
1 parent
1a09903326
commit
95522fdedd
10 files changed
+1228
-20
No files matched your search
@@ -68,6 +68,14 @@ export function sanitizeEmailHtml(html: string): string {
|
||||
* Runs after sanitizeHtml, on already-sanitized output — the href is just
|
||||
* the img's own already-scheme-validated src, so this can't reintroduce
|
||||
* anything sanitizeHtml would have stripped.
|
||||
*
|
||||
* data: URIs are skipped here — imap.ts extracts every data:image src into
|
||||
* a real attachment (and a real /api/attachments/... URL) before this ever
|
||||
* runs, specifically because Chrome silently refuses to navigate a tab to a
|
||||
* data: URL, so a link built from one would look clickable but do nothing.
|
||||
* Anything still starting with "data:" at this point is a residual case
|
||||
* that extraction didn't catch — better left unwrapped (a plain image) than
|
||||
* wrapped in a link that appears to work but doesn't.
|
||||
*/
|
||||
function wrapBareImagesInLinks(html: string): string {
|
||||
const $ = cheerio.load(html, null, false);
|
||||
@@ -75,7 +83,7 @@ function wrapBareImagesInLinks(html: string): string {
|
||||
const $img = $(el);
|
||||
if ($img.closest("a").length > 0) return;
|
||||
const src = $img.attr("src");
|
||||
if (!src) return;
|
||||
if (!src || src.startsWith("data:")) return;
|
||||
// Built via .attr(), not string-interpolated HTML — src is
|
||||
// attacker-influenced (an already scheme-validated but otherwise
|
||||
// arbitrary data:/http(s) URL), and interpolating it into an HTML
|
||||
|
||||
Reference in new issue
Block a user