From 975dc1e1f8f0a62ca83f2cc2e1abdce3febbdf7e Mon Sep 17 00:00:00 2001 From: alma Date: Thu, 1 May 2025 09:54:17 +0200 Subject: [PATCH] courrier preview --- app/api/parse-email/route.ts | 2 +- components/email/ComposeEmail.tsx | 2 +- components/email/EmailContent.tsx | 2 +- components/email/EmailContentDisplay.tsx | 13 +++- components/email/RichEmailEditor.tsx | 2 +- components/ui/rich-text-editor.tsx | 4 +- hooks/use-email-fetch.ts | 2 +- lib/server/email-parser.ts | 2 +- lib/utils/dom-sanitizer.ts | 83 ++++++++++++++++++++++++ lib/utils/email-adapters.ts | 2 +- lib/utils/email-content.ts | 78 ++++------------------ lib/utils/email-formatter.ts | 33 ++-------- lib/utils/email-utils.ts | 67 ++----------------- 13 files changed, 123 insertions(+), 169 deletions(-) create mode 100644 lib/utils/dom-sanitizer.ts diff --git a/app/api/parse-email/route.ts b/app/api/parse-email/route.ts index 7af4e6a1..c2f3a015 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-sanitizer'; interface EmailAddress { name?: string; diff --git a/components/email/ComposeEmail.tsx b/components/email/ComposeEmail.tsx index 41aaf28e..d4cabaf1 100644 --- a/components/email/ComposeEmail.tsx +++ b/components/email/ComposeEmail.tsx @@ -6,7 +6,7 @@ import { } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; -import DOMPurify from 'isomorphic-dompurify'; +import { sanitizeHtml } from '@/lib/utils/dom-sanitizer'; import { DropdownMenu, DropdownMenuContent, diff --git a/components/email/EmailContent.tsx b/components/email/EmailContent.tsx index f6341bbf..46d3e9b3 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-sanitizer'; 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 3c23aa89..61c0b0ab 100644 --- a/components/email/EmailContentDisplay.tsx +++ b/components/email/EmailContentDisplay.tsx @@ -3,7 +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'; +import { sanitizeHtml } from '@/lib/utils/dom-sanitizer'; interface EmailContentDisplayProps { content: EmailContent | null | undefined; @@ -96,7 +96,7 @@ const EmailContentDisplay: React.FC = ({ // Sanitize HTML content and apply proper direction const sanitizedHTML = useMemo(() => { - const clean = DOMPurify.sanitize(processedHTML); + const clean = sanitizeHtml(processedHTML); // Apply text direction consistently using our utility return applyTextDirection(clean, safeContent.text); @@ -124,11 +124,18 @@ const EmailContentDisplay: React.FC = ({ width: 100%; } + /* Enhanced RTL styling - handle direction at every level */ + [dir="rtl"] { + text-align: right; + } + + /* Ensure images don't overflow their containers */ .email-content-inner img { max-width: 100%; height: auto; } + /* Blockquote styling for LTR content */ .email-content-inner blockquote { margin: 10px 0; padding-left: 15px; @@ -138,7 +145,7 @@ const EmailContentDisplay: React.FC = ({ border-radius: 4px; } - /* RTL blockquote styling will be handled by the direction attribute now */ + /* Special styling for RTL blockquotes - handled via attribute selector */ [dir="rtl"] blockquote { padding-left: 0; padding-right: 15px; diff --git a/components/email/RichEmailEditor.tsx b/components/email/RichEmailEditor.tsx index 6cfb0693..3880e5ce 100644 --- a/components/email/RichEmailEditor.tsx +++ b/components/email/RichEmailEditor.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useRef, useState } from 'react'; import 'quill/dist/quill.snow.css'; -import { sanitizeHtml } from '@/lib/utils/email-utils'; +import { sanitizeHtml } from '@/lib/utils/dom-sanitizer'; interface RichEmailEditorProps { initialContent: string; diff --git a/components/ui/rich-text-editor.tsx b/components/ui/rich-text-editor.tsx index 6b2c755a..5e6e7aa7 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 { sanitizeHtml } from '@/lib/utils/dom-sanitizer'; interface RichTextEditorProps { /** Initial HTML content */ @@ -52,7 +52,7 @@ const RichTextEditor = forwardRef(({ useEffect(() => { if (internalEditorRef.current) { // Clean the initial content - const cleanContent = DOMPurify.sanitize(initialContent); + const cleanContent = sanitizeHtml(initialContent); internalEditorRef.current.innerHTML = cleanContent; // Set initial direction diff --git a/hooks/use-email-fetch.ts b/hooks/use-email-fetch.ts index 5e9c96e0..3a4cb2cf 100644 --- a/hooks/use-email-fetch.ts +++ b/hooks/use-email-fetch.ts @@ -2,7 +2,7 @@ import { useState, useEffect, useCallback, useRef } from 'react'; import { useToast } from './use-toast'; import { EmailMessage, EmailContent } from '@/types/email'; import { detectTextDirection } from '@/lib/utils/text-direction'; -import { sanitizeHtml } from '@/lib/utils/email-utils'; +import { sanitizeHtml } from '@/lib/utils/dom-sanitizer'; interface EmailFetchState { email: EmailMessage | null; diff --git a/lib/server/email-parser.ts b/lib/server/email-parser.ts index c79a50f8..305e7605 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-sanitizer'; import { simpleParser } from 'mailparser'; function getAddressText(addresses: any): string | null { diff --git a/lib/utils/dom-sanitizer.ts b/lib/utils/dom-sanitizer.ts new file mode 100644 index 00000000..cfd848b2 --- /dev/null +++ b/lib/utils/dom-sanitizer.ts @@ -0,0 +1,83 @@ +/** + * DOM Sanitizer + * + * Centralized DOMPurify configuration for consistent HTML sanitization + * throughout the application. This ensures all sanitized content follows + * the same rules for security and presentation. + */ + +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({ + // Allow these attributes on all elements + ADD_ATTR: [ + 'dir', // For text direction + 'lang', // For language specification + 'style', // For inline styles (carefully sanitized) + 'class', 'id', // For CSS targeting + 'title', // For tooltips + 'target', 'rel', // For links + 'colspan', 'rowspan', // For tables + 'width', 'height', 'align', 'valign', // Basic layout + 'alt', 'src', // For images + 'href', // For links + 'data-*' // For custom data attributes + ], + + // Allow these HTML tags + 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' + ], + + // Explicitly forbid these dangerous tags + FORBID_TAGS: [ + 'script', 'iframe', 'object', 'embed', 'form', + 'input', 'button', 'select', 'textarea' + ], + + // Explicitly forbid these dangerous attributes + FORBID_ATTR: [ + 'onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout', + 'onkeydown', 'onkeypress', 'onkeyup', 'onchange' + ], + + // Other configuration options + KEEP_CONTENT: true, // Keep content of removed tags + WHOLE_DOCUMENT: false, // Don't require a full HTML document + ALLOW_DATA_ATTR: true, // Allow data-* attributes + ALLOW_UNKNOWN_PROTOCOLS: true, // Allow protocols like cid: for email images + FORCE_BODY: false // Don't force content to be wrapped in +}); + +// Export a wrapped sanitizeHtml function that handles email-specific fixes +export function sanitizeHtml(html: string): string { + if (!html) return ''; + + try { + // Use DOMPurify with our configured settings + const clean = DOMPurify.sanitize(html); + + // Fix common email rendering issues + return clean + // Fix for Outlook WebVML content + .replace(/