courrier preview
This commit is contained in:
parent
d582a2ba30
commit
0db546ad79
@ -28,12 +28,27 @@ function cleanupTableStructures(htmlContent: string): string {
|
||||
// Find tables with problematic nested structures
|
||||
const tables = tempDiv.querySelectorAll('table');
|
||||
|
||||
// Simple approach: if we have tables in forwarded content, convert them to divs
|
||||
// to prevent quill-better-table from trying to interpret them
|
||||
if (tables.length > 0 && htmlContent.includes('---------- Forwarded message ----------')) {
|
||||
console.log(`Converting ${tables.length} tables in forwarded email to prevent Quill errors`);
|
||||
// Check if content looks like a forwarded email or reply content
|
||||
const isForwardedEmail =
|
||||
htmlContent.includes('---------- Forwarded message ----------') ||
|
||||
htmlContent.includes('Forwarded message') ||
|
||||
htmlContent.includes('forwarded message') ||
|
||||
(htmlContent.includes('From:') && htmlContent.includes('Date:') && htmlContent.includes('Subject:'));
|
||||
|
||||
// Check if content has complex tables that might cause issues
|
||||
const hasComplexTables = tables.length > 0 &&
|
||||
(isForwardedEmail || htmlContent.includes('gmail_quote') ||
|
||||
htmlContent.includes('blockquote') || htmlContent.includes('wrote:'));
|
||||
|
||||
if (hasComplexTables) {
|
||||
console.log(`Converting ${tables.length} tables in complex email content to prevent Quill errors`);
|
||||
|
||||
tables.forEach(table => {
|
||||
// Skip simple tables that are likely to work fine with Quill
|
||||
if (table.rows.length <= 1 && table.querySelectorAll('td, th').length <= 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a replacement div
|
||||
const replacementDiv = document.createElement('div');
|
||||
replacementDiv.className = 'converted-table';
|
||||
|
||||
@ -11,64 +11,90 @@ 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',
|
||||
'font' // Allow legacy font tag often found in emails
|
||||
],
|
||||
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-*',
|
||||
'start', 'type', 'value', 'cite', 'datetime', 'wrap', 'summary'
|
||||
],
|
||||
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,
|
||||
USE_PROFILES: { html: true } // Use HTML profile for more permissive sanitization for emails
|
||||
});
|
||||
/**
|
||||
* Configure DOMPurify with safe defaults for email content
|
||||
* This balances security with the need to display rich email content
|
||||
*/
|
||||
export function configureDOMPurify() {
|
||||
// Enhanced configuration for email content
|
||||
DOMPurify.setConfig({
|
||||
ADD_TAGS: [
|
||||
// SVG elements for simple charts/logos that might be in emails
|
||||
'svg', 'path', 'g', 'circle', 'rect', 'line', 'polygon', 'ellipse',
|
||||
// Common email-specific elements
|
||||
'o:p', 'font',
|
||||
// Allow comments for conditional HTML in emails
|
||||
'!--...--'
|
||||
],
|
||||
ADD_ATTR: [
|
||||
// SVG attributes
|
||||
'viewbox', 'd', 'cx', 'cy', 'r', 'fill', 'stroke', 'stroke-width', 'x', 'y', 'width', 'height',
|
||||
// Additional HTML attributes commonly used in emails
|
||||
'align', 'valign', 'bgcolor', 'color', 'cellpadding', 'cellspacing', 'colspan', 'rowspan',
|
||||
'face', 'size', 'direction', 'role', 'aria-label', 'aria-hidden',
|
||||
// List attributes
|
||||
'start', 'type', 'value',
|
||||
// Table attributes and styles
|
||||
'border', 'frame', 'rules', 'summary', 'headers', 'scope', 'abbr',
|
||||
// Blockquote attributes
|
||||
'cite', 'datetime',
|
||||
// Form elements attributes (read-only)
|
||||
'readonly', 'disabled', 'selected', 'checked', 'multiple', 'wrap'
|
||||
],
|
||||
FORBID_TAGS: [
|
||||
// Remove dangerous tags
|
||||
'script', 'object', 'iframe', 'embed', 'applet', 'meta', 'link',
|
||||
// Form elements that could be used for phishing
|
||||
'form', 'button', 'input', 'textarea', 'select', 'option'
|
||||
],
|
||||
FORBID_ATTR: [
|
||||
// Remove JavaScript and dangerous attributes
|
||||
'onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout', 'onmouseenter', 'onmouseleave',
|
||||
'onkeydown', 'onkeypress', 'onkeyup', 'onchange', 'onsubmit', 'onreset', 'onselect', 'onblur',
|
||||
'onfocus', 'onscroll', 'onbeforeunload', 'onunload', 'onhashchange', 'onpopstate', 'onpageshow',
|
||||
'onpagehide', 'onabort', 'oncanplay', 'oncanplaythrough', 'ondurationchange', 'onemptied',
|
||||
'onended', 'onloadeddata', 'onloadedmetadata', 'onloadstart', 'onpause', 'onplay', 'onplaying',
|
||||
'onprogress', 'onratechange', 'onseeked', 'onseeking', 'onstalled', 'onsuspend', 'ontimeupdate',
|
||||
'onvolumechange', 'onwaiting', 'animationend', 'animationiteration', 'animationstart',
|
||||
// Dangerous attributes
|
||||
'formaction', 'xlink:href'
|
||||
],
|
||||
ALLOW_DATA_ATTR: false, // Disable data-* attributes which can be used for XSS
|
||||
WHOLE_DOCUMENT: false, // Don't parse the entire document - just fragments
|
||||
SANITIZE_DOM: true, // Sanitize the DOM to prevent XSS
|
||||
KEEP_CONTENT: true, // Keep content of elements that are removed
|
||||
RETURN_DOM: false, // Return a DOM object rather than HTML string
|
||||
RETURN_DOM_FRAGMENT: false, // Return a DocumentFragment rather than HTML string
|
||||
FORCE_BODY: false, // Add a <body> tag if one doesn't exist
|
||||
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp|data|irc):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
|
||||
ALLOW_UNKNOWN_PROTOCOLS: true, // Some email clients use custom protocols for images/attachments
|
||||
USE_PROFILES: { html: true } // Use the HTML profile for more permissive sanitization
|
||||
});
|
||||
|
||||
return DOMPurify;
|
||||
}
|
||||
|
||||
// Singleton instance of configured DOMPurify for the app
|
||||
export const purify = configureDOMPurify();
|
||||
|
||||
/**
|
||||
* Sanitizes HTML content with the centralized DOMPurify configuration
|
||||
* @param html HTML content to sanitize
|
||||
* @returns Sanitized HTML
|
||||
* Sanitize HTML content using our email-specific configuration
|
||||
*/
|
||||
export function sanitizeHtml(html: string): string {
|
||||
if (!html) return '';
|
||||
export function sanitizeHtml(content: string): string {
|
||||
if (!content) return '';
|
||||
|
||||
try {
|
||||
// Use DOMPurify with our central configuration
|
||||
const clean = DOMPurify.sanitize(html, {
|
||||
ADD_ATTR: ['style', 'class', 'id', 'align', 'valign', 'colspan', 'rowspan', 'cellspacing', 'cellpadding', 'bgcolor']
|
||||
// Sanitize with our configured instance
|
||||
return purify.sanitize(content, {
|
||||
ADD_TAGS: ['style'], // Allow internal styles temporarily for cleaning
|
||||
ADD_ATTR: ['style', 'class'] // Allow style and class attributes
|
||||
});
|
||||
|
||||
// Fix common email rendering issues
|
||||
const fixedHtml = clean
|
||||
// Fix for Outlook WebVML content
|
||||
.replace(/<!--\[if\s+gte\s+mso/g, '<!--[if gte mso')
|
||||
// Fix for broken image paths that might be relative
|
||||
.replace(/(src|background)="(?!http|data|https|cid)/gi, '$1="https://');
|
||||
|
||||
return fixedHtml;
|
||||
} catch (e) {
|
||||
console.error('Error sanitizing HTML:', e);
|
||||
// Fall back to a basic sanitization approach
|
||||
return html
|
||||
} catch (error) {
|
||||
console.error('Failed to sanitize HTML content:', error);
|
||||
// Fallback to basic sanitization
|
||||
return content
|
||||
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
||||
.replace(/on\w+="[^"]*"/g, '')
|
||||
.replace(/(javascript|jscript|vbscript|mocha):/gi, 'removed:');
|
||||
.replace(/javascript:/gi, 'blocked:');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -287,6 +287,23 @@ export function processHtmlContent(
|
||||
isEmpty: !sanitizedContent || sanitizedContent.trim().length === 0
|
||||
});
|
||||
|
||||
// Check if content is a forwarded message to ensure special handling for tables
|
||||
const isForwardedEmail =
|
||||
sanitizedContent.includes('---------- Forwarded message ----------') ||
|
||||
sanitizedContent.includes('Forwarded message') ||
|
||||
(sanitizedContent.includes('From:') && sanitizedContent.includes('Date:') &&
|
||||
sanitizedContent.includes('Subject:') && sanitizedContent.includes('To:'));
|
||||
|
||||
// Special processing for forwarded email styling
|
||||
if (isForwardedEmail) {
|
||||
console.log('Detected forwarded email content, preserving table structure');
|
||||
// Make sure we're not removing important table structures
|
||||
sanitizedContent = sanitizedContent
|
||||
// Preserve table styling for email headers
|
||||
.replace(/<table([^>]*)>/g, '<table$1 style="margin: 10px 0; border-collapse: collapse; font-size: 13px; color: #333;">')
|
||||
.replace(/<td([^>]*)>/g, '<td$1 style="padding: 3px 5px; vertical-align: top;">');
|
||||
}
|
||||
|
||||
// Fix common email client quirks without breaking cid: URLs
|
||||
sanitizedContent = sanitizedContent
|
||||
// Fix for Outlook WebVML content
|
||||
|
||||
@ -590,9 +590,9 @@ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMe
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div style="padding: 10px 0; border-top: 1px solid #ddd;">
|
||||
<blockquote style="margin: 0; padding-left: 10px; border-left: 3px solid #ddd; color: #505050; background-color: #f9f9f9; padding: 10px;">
|
||||
${sanitizedOriginalContent}
|
||||
</div>
|
||||
</blockquote>
|
||||
`;
|
||||
|
||||
// Now we have the full forwarded email structure without sanitizing it again
|
||||
|
||||
Loading…
Reference in New Issue
Block a user