Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
35 lines
1.4 KiB
TypeScript
35 lines
1.4 KiB
TypeScript
import { sanitizeEmailHtml, blockRemoteImages, unblockRemoteImages } from "./sanitizer";
|
|
import { resolveInlineImages, buildReaderDocument, type InlineImages } from "./emailBody";
|
|
|
|
/** What it takes to draw a message's body. */
|
|
export interface RenderableBody {
|
|
html: string;
|
|
inline_images?: InlineImages;
|
|
}
|
|
|
|
/**
|
|
* The document the reader frame renders, or null for a message with no HTML.
|
|
*
|
|
* Order matters: sanitise first so nothing downstream handles untrusted
|
|
* markup, then resolve the message's own inline parts, then block or restore
|
|
* remote sources - which is the only part the reader chooses.
|
|
*/
|
|
export function messageDocument(message: RenderableBody, showImages: boolean): string | null {
|
|
if (!message.html) return null;
|
|
const sanitized = sanitizeEmailHtml(message.html);
|
|
if (!sanitized) return null;
|
|
const withInline = resolveInlineImages(sanitized, message.inline_images ?? {});
|
|
const body = showImages ? unblockRemoteImages(withInline) : blockRemoteImages(withInline);
|
|
return buildReaderDocument(body);
|
|
}
|
|
|
|
/**
|
|
* Whether anything was actually held back, so the privacy bar is not shown over
|
|
* a message that has no remote content at all.
|
|
*/
|
|
export function hasBlockedRemoteContent(message: RenderableBody): boolean {
|
|
if (!message.html) return false;
|
|
const sanitized = resolveInlineImages(sanitizeEmailHtml(message.html), message.inline_images ?? {});
|
|
return blockRemoteImages(sanitized) !== sanitized;
|
|
}
|