mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 16:13:15 +00:00
91 lines
3.7 KiB
TypeScript
91 lines
3.7 KiB
TypeScript
/**
|
|
* Which attachment parts belong in the attachment list, and which are really
|
|
* pieces of the rendered body.
|
|
*
|
|
* A message body embeds images by `cid:` reference. Well-behaved senders mark
|
|
* those parts `Content-Disposition: inline` with an `image/*` type, and the
|
|
* "hide inline images" setting keeps them out of the attachment list. Sloppy
|
|
* senders (Foxmail, various order/invoice systems - #543's pattern) ship the
|
|
* very same parts as `application/octet-stream` with no disposition and no
|
|
* name: the body still renders them, but the declaration-based check let them
|
|
* through and they showed up as nameless "Attachment" chips next to the real
|
|
* files. The reference scan below closes that gap for the viewers the way the
|
|
* composer's forward path already handles it.
|
|
*/
|
|
|
|
/**
|
|
* The exact pattern the viewers rewrite to blob URLs (rewriteCidReferences),
|
|
* so "referenced" here means "rendered" and a chip is only ever hidden for a
|
|
* part the body shows. A reference ends at `<`/`>` too: an unquoted
|
|
* `<img src=cid:logo>` would otherwise take the rest of the markup with it.
|
|
*/
|
|
const CID_REFERENCE = /\bcid:([^"'\s)<>]+)/gi;
|
|
|
|
/**
|
|
* Swaps every `cid:` reference in a raw HTML body for the URL fetched for that
|
|
* part, or `fallback` when there is none.
|
|
*/
|
|
export function rewriteCidReferences(html: string, urls: Readonly<Record<string, string>>, fallback: string): string {
|
|
return html.replace(CID_REFERENCE, (_match, cid: string) =>
|
|
(Object.prototype.hasOwnProperty.call(urls, cid) ? urls[cid] : fallback));
|
|
}
|
|
|
|
const EMPTY: ReadonlySet<string> = new Set();
|
|
|
|
/**
|
|
* Strips the RFC angle brackets a Content-ID header may still carry (e.g.
|
|
* `<logo123@sender>`). Body `cid:` references never have them, so every
|
|
* comparison between an attachment's `cid`/`contentId` and a body reference
|
|
* must normalize through this first.
|
|
*/
|
|
export function normalizeCid(cid: string): string {
|
|
return cid.replace(/^<|>$/g, '');
|
|
}
|
|
|
|
/**
|
|
* Content-IDs a raw HTML body references via `cid:` URLs (img src, table
|
|
* background, style url(...) - anything), exactly as written. `null` (the
|
|
* message renders as plain text) yields an empty set: nothing is embedded, so
|
|
* nothing must be hidden.
|
|
*/
|
|
export function collectReferencedCids(html: string | null | undefined): ReadonlySet<string> {
|
|
if (!html || html.indexOf('cid:') === -1) return EMPTY;
|
|
const cids = new Set<string>();
|
|
for (const match of html.matchAll(CID_REFERENCE)) {
|
|
cids.add(match[1]);
|
|
}
|
|
return cids;
|
|
}
|
|
|
|
export interface AttachmentPartLike {
|
|
cid?: string | null;
|
|
type?: string | null;
|
|
disposition?: string | null;
|
|
}
|
|
|
|
/**
|
|
* True when the part is embedded in the rendered body and should stay out of
|
|
* the attachment list while "hide inline images" is on.
|
|
*
|
|
* - Declared inline images hide as before, referenced or not.
|
|
* - An explicit `attachment` disposition always keeps its chip - the sender
|
|
* asked for a download, even if the body also references the part.
|
|
* - Otherwise a part hides only when the body actually references it AND it
|
|
* is an image or generically typed (`application/octet-stream`, no type):
|
|
* a referenced part with a real non-image type keeps its chip, because that
|
|
* chip is its only download.
|
|
*/
|
|
export function isEmbeddedInBody(
|
|
att: AttachmentPartLike,
|
|
referencedCids: ReadonlySet<string>,
|
|
): boolean {
|
|
if (!att.cid) return false;
|
|
const type = (att.type || '').toLowerCase();
|
|
const isImage = type.startsWith('image/');
|
|
if (att.disposition === 'inline' && isImage) return true;
|
|
if (att.disposition === 'attachment') return false;
|
|
const genericType = !type || type === 'application/octet-stream';
|
|
if (!isImage && !genericType) return false;
|
|
return referencedCids.has(normalizeCid(att.cid));
|
|
}
|