/** * Unified Email Utilities * * This file contains all email-related utility functions: * - Content normalization * - Content sanitization * - Email formatting (replies, forwards) * - Text direction detection */ import DOMPurify from 'isomorphic-dompurify'; import { EmailMessage, EmailContent, EmailAddress, LegacyEmailMessage } from '@/types/email'; import { adaptLegacyEmail } from '@/lib/utils/email-adapters'; import { decodeInfomaniakEmail, adaptMimeEmail, isMimeFormat } from './email-mime-decoder'; // Reset any existing hooks to start clean DOMPurify.removeAllHooks(); // Configure DOMPurify for auto text direction DOMPurify.addHook('afterSanitizeAttributes', function(node) { if (node instanceof HTMLElement) { // Only set direction if not already specified if (!node.hasAttribute('dir')) { // Add dir attribute only if not present node.setAttribute('dir', 'auto'); } } }); // Configure DOMPurify to preserve direction attributes DOMPurify.setConfig({ ADD_ATTR: ['dir'], ALLOWED_ATTR: ['style', 'class', 'id', 'dir'] }); /** * Detect if text contains RTL characters */ export function detectTextDirection(text: string): 'ltr' | 'rtl' { // Pattern for RTL characters (Arabic, Hebrew, etc.) const rtlLangPattern = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/; return rtlLangPattern.test(text) ? 'rtl' : 'ltr'; } /** * Format email addresses for display * Can handle both array of EmailAddress objects or a string */ export function formatEmailAddresses(addresses: EmailAddress[] | string | undefined): string { if (!addresses) return ''; // If already a string, return as is if (typeof addresses === 'string') { return addresses; } // If array, format each address if (Array.isArray(addresses) && addresses.length > 0) { return addresses.map(addr => addr.name && addr.name !== addr.address ? `${addr.name} <${addr.address}>` : addr.address ).join(', '); } return ''; } /** * Format date for display */ export function formatEmailDate(date: Date | string | undefined): string { if (!date) return ''; try { const dateObj = typeof date === 'string' ? new Date(date) : date; return dateObj.toLocaleString('en-US', { weekday: 'short', year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); } catch (e) { return typeof date === 'string' ? date : date.toString(); } } /** * Sanitize HTML content before processing or displaying * Uses email industry standards for proper, consistent, and secure rendering */ export function sanitizeHtml(html: string): string { if (!html) return ''; try { // Use DOMPurify with comprehensive email HTML standards const clean = DOMPurify.sanitize(html, { ADD_TAGS: [ 'html', 'head', 'body', 'style', 'link', 'meta', 'title', 'table', 'caption', 'col', 'colgroup', 'thead', 'tbody', 'tfoot', 'tr', 'td', 'th', 'div', 'span', 'img', 'br', 'hr', 'section', 'article', 'header', 'footer', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'p', 'blockquote', 'pre', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd', 'a', 'b', 'i', 'u', 'em', 'strong', 'del', 'ins', 'mark', 'small', 'sub', 'sup', 'q', 'abbr' ], ADD_ATTR: [ 'style', 'class', 'id', 'name', 'href', 'src', 'alt', 'title', 'width', 'height', 'border', 'cellspacing', 'cellpadding', 'bgcolor', 'background', 'color', 'align', 'valign', 'dir', 'lang', 'target', 'rel', 'charset', 'media', 'colspan', 'rowspan', 'scope', 'span', 'size', 'face', 'hspace', 'vspace', 'data-*' ], KEEP_CONTENT: true, WHOLE_DOCUMENT: false, ALLOW_DATA_ATTR: true, ALLOW_UNKNOWN_PROTOCOLS: true, // Needed for some email clients FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form', 'input', 'button', 'select', 'textarea'], FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout'], FORCE_BODY: false }); // Fix common email rendering issues return clean // Fix for Outlook WebVML content .replace(/