From 86581cea72e8f31617b0b5abca6a994c397f39cd Mon Sep 17 00:00:00 2001 From: alma Date: Thu, 1 May 2025 10:31:52 +0200 Subject: [PATCH] courrier preview --- app/api/parse-email/route.ts | 2 +- components/email/EmailContent.tsx | 2 +- components/email/EmailContentDisplay.tsx | 17 +- components/ui/rich-text-editor.tsx | 12 +- lib/server/email-parser.ts | 2 +- lib/utils/dom-purify-config.ts | 76 +++++ lib/utils/email-formatter.ts | 366 +++-------------------- lib/utils/email-utils.ts | 280 +++++++---------- 8 files changed, 248 insertions(+), 509 deletions(-) create mode 100644 lib/utils/dom-purify-config.ts diff --git a/app/api/parse-email/route.ts b/app/api/parse-email/route.ts index 7af4e6a1..992f081c 100644 --- a/app/api/parse-email/route.ts +++ b/app/api/parse-email/route.ts @@ -1,6 +1,6 @@ import { NextRequest, NextResponse } from 'next/server'; import { parseEmail } from '@/lib/server/email-parser'; -import { sanitizeHtml } from '@/lib/utils/email-formatter'; +import { sanitizeHtml } from '@/lib/utils/dom-purify-config'; interface EmailAddress { name?: string; diff --git a/components/email/EmailContent.tsx b/components/email/EmailContent.tsx index f6341bbf..4e15d4b9 100644 --- a/components/email/EmailContent.tsx +++ b/components/email/EmailContent.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import { Loader2, Paperclip, Download } from 'lucide-react'; -import { sanitizeHtml } from '@/lib/utils/email-formatter'; +import { sanitizeHtml } from '@/lib/utils/dom-purify-config'; import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'; import { Avatar, AvatarFallback } from '@/components/ui/avatar'; diff --git a/components/email/EmailContentDisplay.tsx b/components/email/EmailContentDisplay.tsx index 7f4e9c4d..e23c9506 100644 --- a/components/email/EmailContentDisplay.tsx +++ b/components/email/EmailContentDisplay.tsx @@ -3,10 +3,7 @@ import React, { useMemo } from 'react'; import { EmailContent } from '@/types/email'; import { detectTextDirection, applyTextDirection } from '@/lib/utils/text-direction'; -import DOMPurify from 'isomorphic-dompurify'; - -// Configure DOMPurify to not interfere with our direction handling -DOMPurify.removeHooks('afterSanitizeAttributes'); +import { sanitizeHtml, getDOMPurify } from '@/lib/utils/dom-purify-config'; interface EmailContentDisplayProps { content: EmailContent | null | undefined; @@ -99,15 +96,11 @@ const EmailContentDisplay: React.FC = ({ // Sanitize HTML content and apply proper direction const sanitizedHTML = useMemo(() => { - // Use DOMPurify with explicit config to avoid global configuration issues - const clean = DOMPurify.sanitize(processedHTML, { - ADD_ATTR: ['dir'], // Ensure dir attributes are preserved - ALLOWED_ATTR: ['style', 'class', 'id', 'dir'], // Allow direction attributes - FORBID_TAGS: ['script', 'iframe', 'object'] // Only strip dangerous elements - }); + // First sanitize the HTML with our centralized utility + const cleanHtml = sanitizeHtml(processedHTML); - // Apply text direction consistently using our utility - return applyTextDirection(clean, safeContent.text); + // Then apply text direction consistently + return applyTextDirection(cleanHtml, safeContent.text); }, [processedHTML, safeContent.text]); return ( diff --git a/components/ui/rich-text-editor.tsx b/components/ui/rich-text-editor.tsx index bfbb45dc..309cd233 100644 --- a/components/ui/rich-text-editor.tsx +++ b/components/ui/rich-text-editor.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useRef, forwardRef, useImperativeHandle } from 'react'; import { detectTextDirection } from '@/lib/utils/text-direction'; -import DOMPurify from 'isomorphic-dompurify'; +import { getDOMPurify } from '@/lib/utils/dom-purify-config'; interface RichTextEditorProps { /** Initial HTML content */ @@ -27,9 +27,8 @@ interface RichTextEditorProps { initialDirection?: 'ltr' | 'rtl'; } -// Before using DOMPurify, configure it to not add dir attributes automatically -// This will prevent conflicts with our explicit direction handling -DOMPurify.removeHooks('afterSanitizeAttributes'); +// Get DOMPurify from our centralized configuration +const DOMPurify = getDOMPurify(); /** * Unified rich text editor component with proper RTL support @@ -55,10 +54,9 @@ const RichTextEditor = forwardRef(({ // Initialize editor with clean content useEffect(() => { if (internalEditorRef.current) { - // Clean the initial content but preserve existing dir attributes + // Clean the initial content using our centralized config const cleanContent = DOMPurify.sanitize(initialContent, { - ADD_ATTR: ['dir'], // Ensure dir attributes are preserved - FORBID_ATTR: ['onerror', 'onload', 'onclick'] // Only strip dangerous attributes + ADD_ATTR: ['dir'] // Ensure dir attributes are preserved }); internalEditorRef.current.innerHTML = cleanContent; diff --git a/lib/server/email-parser.ts b/lib/server/email-parser.ts index c79a50f8..1b32eba6 100644 --- a/lib/server/email-parser.ts +++ b/lib/server/email-parser.ts @@ -1,4 +1,4 @@ -import { sanitizeHtml } from '@/lib/utils/email-formatter'; +import { sanitizeHtml } from '@/lib/utils/dom-purify-config'; import { simpleParser } from 'mailparser'; function getAddressText(addresses: any): string | null { diff --git a/lib/utils/dom-purify-config.ts b/lib/utils/dom-purify-config.ts new file mode 100644 index 00000000..a2131936 --- /dev/null +++ b/lib/utils/dom-purify-config.ts @@ -0,0 +1,76 @@ +/** + * CENTRALIZED DOMPURIFY CONFIGURATION + * + * This file provides a consistent, centralized configuration for DOMPurify + * used throughout the application. All components that need to sanitize HTML + * should import from this file instead of configuring DOMPurify directly. + */ + +import DOMPurify from 'isomorphic-dompurify'; + +// Reset any existing hooks to start with a clean slate +DOMPurify.removeAllHooks(); + +// Configure DOMPurify with settings appropriate for email content +DOMPurify.setConfig({ + 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 +}); + +/** + * Sanitizes HTML content with the centralized DOMPurify configuration + * @param html HTML content to sanitize + * @returns Sanitized HTML + */ +export function sanitizeHtml(html: string): string { + if (!html) return ''; + + try { + // Use DOMPurify with our central configuration + const clean = DOMPurify.sanitize(html); + + // Fix common email rendering issues + const fixedHtml = clean + // Fix for Outlook WebVML content + .replace(/