import DOMPurify, { type Config as DOMPurifyConfig } from 'dompurify' /** * Options de sanitization du corps HTML d'un mail. */ export type SanitizeMailHtmlOptions = { /** * Si true, les images distantes (http/https) sont affichées directement. * Par défaut false — les images distantes sont remplacées par un placeholder * cliquable pour éviter le tracking par pixel. */ allowImages?: boolean } /** * Configuration DOMPurify bloquante pour les corps de mail. * - Bloque les balises dangereuses : script, iframe, object, embed, style, link, meta, form, input * - Bloque les attributs événements (on*) et les URI javascript: * - Autorise les URI data: uniquement pour les images (PNG/JPEG/GIF/WEBP) — images inline CID */ const DOMPURIFY_CONFIG: DOMPurifyConfig = { FORBID_TAGS: [ 'script', 'iframe', 'object', 'embed', 'style', 'link', 'meta', 'form', 'input', 'button', 'textarea', 'select', 'base', 'applet', ], FORBID_ATTR: [ 'onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout', 'onmouseenter', 'onmouseleave', 'onfocus', 'onblur', 'onchange', 'onsubmit', 'onreset', 'onkeydown', 'onkeyup', 'onkeypress', 'ondblclick', 'oncontextmenu', 'onwheel', 'ondrag', 'ondrop', 'oncopy', 'oncut', 'onpaste', 'action', 'formaction', 'xlink:href', ], ALLOWED_URI_REGEXP: /^(?:https?|mailto|tel|cid|data:image\/(?:png|jpeg|gif|webp)(?:;base64,)?)(?::|$)/i, FORCE_BODY: true, WHOLE_DOCUMENT: false, } /** * Remplace les balises avec src http(s):// par un bouton placeholder. * Le src original est stocké en data-mail-image-src pour permettre l'affichage * à la demande de l'utilisateur (Phase 5 — MailMessageViewer). */ function replaceRemoteImages(html: string): string { // Utiliser un DOMParser côté client uniquement (SSR-safe : le guard process.client // est géré par l'appelant dans un composant Vue — ce helper ne tourne que client-side) const parser = new DOMParser() const doc = parser.parseFromString(html, 'text/html') const images = doc.querySelectorAll('img') images.forEach((img) => { const src = img.getAttribute('src') ?? '' const isRemote = /^https?:\/\//i.test(src) if (!isRemote) return // Remplacer par un span cliquable (pas de