@@ -193,91 +192,83 @@ export default function ComposeEmail({
`;
- // Process the original content
- let originalContent = '';
+ // Default content is a clear "no content" message
+ let contentHtml = '
No content available in original email
';
- // First try to use the API to parse and sanitize the email content
- try {
- // Use server-side parsing via fetch API to properly handle complex emails
- const response = await fetch('/api/parse-email', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({
- email: initialEmail.content || initialEmail.html || initialEmail.text || ''
- }),
- });
-
- if (response.ok) {
- const parsedEmail = await response.json();
-
- if (parsedEmail.html && parsedEmail.html.trim()) {
- console.log('Using parsed HTML content for forward');
-
- // Create an iframe-like containment for the email content
- // This prevents CSS from the original email leaking into our compose view
- originalContent = `
-
- ${parsedEmail.html}
-
- `;
- } else if (parsedEmail.text && parsedEmail.text.trim()) {
- console.log('Using parsed text content for forward');
- originalContent = `
${parsedEmail.text}
`;
- } else {
- console.log('No content available from parser');
- originalContent = '
No content available
';
- }
- } else {
- throw new Error('Failed to parse email content');
- }
- } catch (parseError) {
- console.error('Error parsing email content:', parseError);
-
- // Fall back to direct content handling if API parsing fails
- if (initialEmail.html && initialEmail.html.trim()) {
- console.log('Falling back to HTML content for forward');
- // Use DOMPurify to sanitize HTML and remove dangerous elements
- originalContent = DOMPurify.sanitize(initialEmail.html, {
- ADD_TAGS: ['style', 'div', 'span', 'p', 'br', 'hr', 'h1', 'h2', 'h3', 'img', 'table', 'tr', 'td', 'th'],
- ADD_ATTR: ['style', 'class', 'id', 'src', 'alt', 'href', 'target'],
- FORBID_TAGS: ['script', 'iframe', 'object', 'embed'],
- FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover']
+ // Check if we have content to forward
+ if (initialEmail.content || initialEmail.html || initialEmail.text) {
+ try {
+ // Use the parse-email API endpoint which centralizes our email parsing logic
+ const response = await fetch('/api/parse-email', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ email: initialEmail.content || initialEmail.html || initialEmail.text || ''
+ }),
});
- } else if (initialEmail.content && initialEmail.content.trim()) {
- console.log('Falling back to content field for forward');
- originalContent = DOMPurify.sanitize(initialEmail.content);
- } else if (initialEmail.text && initialEmail.text.trim()) {
- console.log('Falling back to text content for forward');
- originalContent = `
${initialEmail.text}
`;
- } else {
- console.log('No content available for forward');
- originalContent = '
No content available
';
+
+ if (response.ok) {
+ const parsedEmail = await response.json();
+
+ // Use the parsed HTML content if available
+ if (parsedEmail.html) {
+ contentHtml = parsedEmail.html;
+ console.log('Successfully parsed HTML content');
+ } else if (parsedEmail.text) {
+ // Text-only content is wrapped in pre-formatted styling
+ contentHtml = `
${parsedEmail.text}
`;
+ console.log('Using text content');
+ } else {
+ console.warn('API returned success but no content');
+ }
+ } else {
+ console.error('API returned error:', await response.text());
+ throw new Error('API call failed');
+ }
+ } catch (error) {
+ console.error('Error parsing email:', error);
+
+ // Fallback processing - using our cleanHtml utility directly
+ if (initialEmail.html) {
+ // Import the cleanHtml function dynamically if needed
+ const { cleanHtml } = await import('@/lib/mail-parser-wrapper');
+ contentHtml = cleanHtml(initialEmail.html, {
+ preserveStyles: true,
+ scopeStyles: true,
+ addWrapper: true
+ });
+ console.log('Using direct HTML cleaning fallback');
+ } else if (initialEmail.content) {
+ contentHtml = DOMPurify.sanitize(initialEmail.content, {
+ ADD_TAGS: ['style'],
+ FORBID_TAGS: ['script', 'iframe']
+ });
+ console.log('Using DOMPurify sanitized content');
+ } else if (initialEmail.text) {
+ contentHtml = `
${initialEmail.text}
`;
+ console.log('Using plain text fallback');
+ }
}
+ } else {
+ console.warn('No email content available for forwarding');
}
- // Preserve all original structure by wrapping, not modifying the original content
- // Important: We add a style scope to prevent CSS leakage
+ // Combine the header and content - using containment
const forwardedContent = `
${headerHtml}
-
-
-
-
- ${originalContent}
+
-
`;
console.log('Setting body with forwarded content');
setBody(forwardedContent);
-
} catch (error) {
console.error('Error initializing forwarded email:', error);
- // Even in error case, provide a usable template with empty values
+ // Provide a minimal template even in error case
setBody(`
@@ -288,7 +279,7 @@ export default function ComposeEmail({
To: ${initialEmail.to ? formatRecipients(initialEmail.to) : ''}
-
+
Error loading original message content. The original message may still be viewable in your inbox.
`);
diff --git a/lib/mail-parser-wrapper.ts b/lib/mail-parser-wrapper.ts
index 6f23bd95..383f388e 100644
--- a/lib/mail-parser-wrapper.ts
+++ b/lib/mail-parser-wrapper.ts
@@ -101,23 +101,107 @@ export async function decodeEmail(emailContent: string): Promise
{
}
}
-export function cleanHtml(html: string): string {
+/**
+ * Cleans HTML content by removing potentially harmful elements while preserving styling.
+ * This is the centralized HTML sanitization function to be used across the application.
+ * @param html HTML content to sanitize
+ * @param options Optional configuration
+ * @returns Sanitized HTML
+ */
+export function cleanHtml(html: string, options: {
+ preserveStyles?: boolean;
+ scopeStyles?: boolean;
+ addWrapper?: boolean;
+} = {}): string {
+ if (!html) return '';
+
try {
- // Enhanced configuration to preserve more HTML elements for complex emails
- return DOMPurify.sanitize(html, {
- ADD_TAGS: ['style', 'meta', 'link', 'table', 'thead', 'tbody', 'tr', 'td', 'th', 'hr', 'font', 'div', 'span', 'a', 'img', 'b', 'strong', 'i', 'em', 'u', 'br', 'p', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'pre', 'code', 'center', 'section', 'header', 'footer', 'article', 'nav', 'keyframes'],
- ADD_ATTR: ['*', 'colspan', 'rowspan', 'cellpadding', 'cellspacing', 'border', 'bgcolor', 'width', 'height', 'align', 'valign', 'class', 'id', 'style', 'color', 'face', 'size', 'background', 'src', 'href', 'target', 'rel', 'alt', 'title', 'name', 'animation', 'animation-name', 'animation-duration', 'animation-fill-mode'],
- ALLOW_UNKNOWN_PROTOCOLS: true,
- WHOLE_DOCUMENT: true,
- KEEP_CONTENT: true,
- RETURN_DOM: false,
- FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form', 'input', 'button', 'select', 'option', 'textarea', 'canvas', 'video', 'audio'],
- FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout', 'onchange', 'onsubmit'],
- USE_PROFILES: { html: true, svg: false, svgFilters: false, mathMl: false },
- FORCE_BODY: true
- });
+ const defaultOptions = {
+ preserveStyles: true,
+ scopeStyles: true,
+ addWrapper: true,
+ ...options
+ };
+
+ // Extract style tags if we're preserving them
+ const styleTagsContent: string[] = [];
+ let processedHtml = html;
+
+ if (defaultOptions.preserveStyles) {
+ processedHtml = html.replace(/
+ ${processedHtml}
+
+ `;
+ } else {
+ return `${processedHtml}`;
+ }
+ }
+
+ // Just wrap the content if needed
+ if (defaultOptions.addWrapper) {
+ return `
${processedHtml}
`;
+ }
+
+ return processedHtml;
} catch (error) {
console.error('Error cleaning HTML:', error);
- return html;
+ // Return something safe in case of error
+ return `
Error processing HTML content
`;
}
}
\ No newline at end of file
diff --git a/lib/server/email-parser.ts b/lib/server/email-parser.ts
index 740633ef..04b50814 100644
--- a/lib/server/email-parser.ts
+++ b/lib/server/email-parser.ts
@@ -1,20 +1,10 @@
import { simpleParser } from 'mailparser';
+import { cleanHtml as cleanHtmlCentralized } from '@/lib/mail-parser-wrapper';
+// This function is now deprecated in favor of the centralized cleanHtml in mail-parser-wrapper.ts
+// It's kept here temporarily for backward compatibility
export function cleanHtml(html: string): string {
- try {
- // More permissive cleaning that preserves styling but removes potentially harmful elements
- return html
- .replace(/