diff --git a/lib/utils/email-formatter.ts b/lib/utils/email-formatter.ts index 2fbf2d07..f3c1b139 100644 --- a/lib/utils/email-formatter.ts +++ b/lib/utils/email-formatter.ts @@ -4,84 +4,50 @@ * This is the primary and only email formatting utility to be used. * All email formatting should go through here to ensure consistent * handling of text direction and HTML sanitization. + * + * IMPORTANT: This formatter is configured for English-only content + * and enforces left-to-right text direction. */ import DOMPurify from 'isomorphic-dompurify'; -// Configure DOMPurify to preserve direction attributes -DOMPurify.addHook('afterSanitizeAttributes', function(node) { - // Preserve direction attributes - if (node.hasAttribute('dir')) { - node.setAttribute('dir', node.getAttribute('dir') || 'ltr'); - } - // Preserve text-align in styles - if (node.hasAttribute('style')) { - const style = node.getAttribute('style') || ''; - if (style.includes('text-align')) { - // Keep existing alignment - } else if (node.hasAttribute('dir') && node.getAttribute('dir') === 'rtl') { - node.setAttribute('style', style + '; text-align: right;'); - } - } -}); - -// Configure DOMPurify to enforce LTR for English-only content -DOMPurify.addHook('afterSanitizeAttributes', function(node) { - // Always set direction to LTR for all elements - if (node.hasAttribute('dir')) { - node.setAttribute('dir', 'ltr'); - } - - // Ensure text alignment is left-aligned for all elements - if (node.hasAttribute('style')) { - let style = node.getAttribute('style') || ''; - - // Remove any right-to-left text alignment - if (style.includes('text-align: right') || style.includes('text-align:right')) { - style = style.replace(/text-align:\s*right\s*;?/gi, ''); - style = style.trim(); - // Add semicolon if needed - if (style && !style.endsWith(';')) { - style += ';'; - } - } - - // Add left alignment if not already specified - if (!style.includes('text-align:')) { - style += (style ? ' ' : '') + 'text-align: left;'; - } - - node.setAttribute('style', style); - } -}); - -// Clear existing hooks first -DOMPurify.removeHook('afterSanitizeAttributes'); +// Reset any existing hooks to start clean +DOMPurify.removeAllHooks(); // Configure DOMPurify for English-only content (always LTR) DOMPurify.addHook('afterSanitizeAttributes', function(node) { - // Always set direction to LTR for all elements - node.setAttribute('dir', 'ltr'); - - // Ensure text alignment is left-aligned for all elements - if (node.hasAttribute('style')) { - let style = node.getAttribute('style') || ''; + // Force LTR direction on all elements that can have a dir attribute + if (node instanceof HTMLElement) { + node.setAttribute('dir', 'ltr'); - // Remove any right-to-left text alignment - if (style.includes('text-align: right') || style.includes('text-align:right')) { - style = style.replace(/text-align:\s*right\s*;?/gi, ''); + // Handle style attribute + if (node.hasAttribute('style')) { + let style = node.getAttribute('style') || ''; + + // Remove any RTL-related styles + style = style.replace(/direction\s*:\s*rtl\s*;?/gi, ''); + style = style.replace(/text-align\s*:\s*right\s*;?/gi, ''); + style = style.replace(/unicode-bidi\s*:[^;]*;?/gi, ''); + + // Add explicit LTR styles style = style.trim(); + if (style && !style.endsWith(';')) style += ';'; + style += ' direction: ltr; text-align: left;'; + + node.setAttribute('style', style); + } else { + // If no style exists, add default LTR styles + node.setAttribute('style', 'direction: ltr; text-align: left;'); } - - // Add left alignment - style = (style ? style + '; ' : '') + 'text-align: left;'; - node.setAttribute('style', style); - } else { - // If no style exists, add default left alignment - node.setAttribute('style', 'text-align: left;'); } }); +// Configure DOMPurify to add certain attributes and forbid others +DOMPurify.setConfig({ + ADD_ATTR: ['dir'], + FORBID_ATTR: ['lang', 'bidi'] +}); + // Interface definitions export interface EmailAddress { name: string; @@ -151,13 +117,14 @@ export function formatEmailDate(date: Date | string | undefined): string { /** * Sanitize HTML content before processing or displaying + * This ensures the content is properly formatted for LTR display * @param content HTML content to sanitize - * @returns Sanitized HTML + * @returns Sanitized HTML with LTR formatting */ export function sanitizeHtml(content: string): string { if (!content) return ''; - // Sanitize the HTML using our configured DOMPurify + // Sanitize the HTML using our configured DOMPurify with LTR enforced return DOMPurify.sanitize(content); } @@ -180,7 +147,7 @@ export function formatForwardedEmail(email: EmailMessage): { const toString = formatEmailAddresses(email.to || []); const dateString = formatEmailDate(email.date); - // Get and sanitize original content + // Get and sanitize original content (sanitization enforces LTR) const originalContent = sanitizeHtml(email.content || email.html || email.text || ''); // Check if the content already has a forwarded message header @@ -188,7 +155,7 @@ export function formatForwardedEmail(email: EmailMessage): { // If there's already a forwarded message header, don't add another one if (hasExistingHeader) { - // Just wrap the content in appropriate styling without adding another header + // Just wrap the content without additional formatting const content = `