courrier preview
This commit is contained in:
parent
cdd5bd98bc
commit
1dbf66cdec
@ -718,9 +718,32 @@ export async function getEmailContent(
|
|||||||
// Convert flags from Set to boolean checks
|
// Convert flags from Set to boolean checks
|
||||||
const flagsArray = Array.from(flags as Set<string>);
|
const flagsArray = Array.from(flags as Set<string>);
|
||||||
|
|
||||||
// Preserve the raw HTML exactly as it was in the original email
|
// Process the raw HTML with CID attachments
|
||||||
const rawHtml = parsedEmail.html || '';
|
const rawHtml = parsedEmail.html || '';
|
||||||
|
|
||||||
|
// Import processHtmlContent if needed
|
||||||
|
const { processHtmlContent } = await import('../utils/email-content');
|
||||||
|
|
||||||
|
// Process HTML content with attachments for CID image handling
|
||||||
|
let processedHtml = rawHtml;
|
||||||
|
let direction = 'ltr';
|
||||||
|
|
||||||
|
if (rawHtml) {
|
||||||
|
const processed = processHtmlContent(rawHtml, {
|
||||||
|
sanitize: true,
|
||||||
|
blockExternalContent: false,
|
||||||
|
attachments: parsedEmail.attachments?.map(att => ({
|
||||||
|
filename: att.filename || 'attachment',
|
||||||
|
contentType: att.contentType,
|
||||||
|
content: att.content?.toString('base64'), // Convert Buffer to base64 string
|
||||||
|
contentId: att.contentId
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
processedHtml = processed.sanitizedContent;
|
||||||
|
direction = processed.direction;
|
||||||
|
}
|
||||||
|
|
||||||
const email: EmailMessage = {
|
const email: EmailMessage = {
|
||||||
id: emailId,
|
id: emailId,
|
||||||
messageId: envelope.messageId,
|
messageId: envelope.messageId,
|
||||||
@ -753,13 +776,15 @@ export async function getEmailContent(
|
|||||||
attachments: parsedEmail.attachments?.map(att => ({
|
attachments: parsedEmail.attachments?.map(att => ({
|
||||||
filename: att.filename || 'attachment',
|
filename: att.filename || 'attachment',
|
||||||
contentType: att.contentType,
|
contentType: att.contentType,
|
||||||
|
contentId: att.contentId,
|
||||||
|
content: att.content?.toString('base64'),
|
||||||
size: att.size || 0
|
size: att.size || 0
|
||||||
})),
|
})),
|
||||||
content: {
|
content: {
|
||||||
text: parsedEmail.text || '',
|
text: parsedEmail.text || '',
|
||||||
html: rawHtml || '',
|
html: processedHtml || '',
|
||||||
isHtml: !!rawHtml,
|
isHtml: !!processedHtml,
|
||||||
direction: 'ltr' // Default to left-to-right
|
direction
|
||||||
},
|
},
|
||||||
folder: normalizedFolder,
|
folder: normalizedFolder,
|
||||||
contentFetched: true,
|
contentFetched: true,
|
||||||
|
|||||||
@ -13,20 +13,23 @@
|
|||||||
import { sanitizeHtml } from './dom-purify-config';
|
import { sanitizeHtml } from './dom-purify-config';
|
||||||
import { detectTextDirection } from './text-direction';
|
import { detectTextDirection } from './text-direction';
|
||||||
import { EmailContent } from '@/types/email';
|
import { EmailContent } from '@/types/email';
|
||||||
|
import { processCidReferences } from './email-utils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract content from various possible email formats
|
* Extract content from various possible email formats
|
||||||
* Centralized implementation to reduce duplication across the codebase
|
* Centralized implementation to reduce duplication across the codebase
|
||||||
*/
|
*/
|
||||||
export function extractEmailContent(email: any): { text: string; html: string } {
|
export function extractEmailContent(email: any): { text: string; html: string; isHtml: boolean; direction: 'ltr' | 'rtl'; } {
|
||||||
// Default empty values
|
// Default empty values
|
||||||
let textContent = '';
|
let textContent = '';
|
||||||
let htmlContent = '';
|
let htmlContent = '';
|
||||||
|
let isHtml = false;
|
||||||
|
let direction: 'ltr' | 'rtl' = 'ltr';
|
||||||
|
|
||||||
// Early exit if no email
|
// Early exit if no email
|
||||||
if (!email) {
|
if (!email) {
|
||||||
console.log('extractEmailContent: No email provided');
|
console.log('extractEmailContent: No email provided');
|
||||||
return { text: '', html: '' };
|
return { text: '', html: '', isHtml: false, direction: 'ltr' };
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -35,6 +38,8 @@ export function extractEmailContent(email: any): { text: string; html: string }
|
|||||||
// Standard format with content object
|
// Standard format with content object
|
||||||
textContent = email.content.text || '';
|
textContent = email.content.text || '';
|
||||||
htmlContent = email.content.html || '';
|
htmlContent = email.content.html || '';
|
||||||
|
isHtml = email.content.isHtml || !!htmlContent;
|
||||||
|
direction = email.content.direction || 'ltr';
|
||||||
|
|
||||||
// Handle complex email formats where content might be nested
|
// Handle complex email formats where content might be nested
|
||||||
if (!textContent && !htmlContent) {
|
if (!textContent && !htmlContent) {
|
||||||
@ -44,13 +49,17 @@ export function extractEmailContent(email: any): { text: string; html: string }
|
|||||||
// Determine if body is HTML or text
|
// Determine if body is HTML or text
|
||||||
if (isHtmlContent(email.content.body)) {
|
if (isHtmlContent(email.content.body)) {
|
||||||
htmlContent = email.content.body;
|
htmlContent = email.content.body;
|
||||||
|
isHtml = true;
|
||||||
} else {
|
} else {
|
||||||
textContent = email.content.body;
|
textContent = email.content.body;
|
||||||
|
isHtml = false;
|
||||||
}
|
}
|
||||||
} else if (typeof email.content.body === 'object' && email.content.body) {
|
} else if (typeof email.content.body === 'object' && email.content.body) {
|
||||||
// Some email formats nest content inside body
|
// Some email formats nest content inside body
|
||||||
htmlContent = email.content.body.html || '';
|
htmlContent = email.content.body.html || '';
|
||||||
textContent = email.content.body.text || '';
|
textContent = email.content.body.text || '';
|
||||||
|
isHtml = email.content.body.isHtml || !!htmlContent;
|
||||||
|
direction = email.content.body.direction || 'ltr';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,8 +69,10 @@ export function extractEmailContent(email: any): { text: string; html: string }
|
|||||||
// Check if data looks like HTML
|
// Check if data looks like HTML
|
||||||
if (isHtmlContent(email.content.data)) {
|
if (isHtmlContent(email.content.data)) {
|
||||||
htmlContent = email.content.data;
|
htmlContent = email.content.data;
|
||||||
|
isHtml = true;
|
||||||
} else {
|
} else {
|
||||||
textContent = email.content.data;
|
textContent = email.content.data;
|
||||||
|
isHtml = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -70,19 +81,25 @@ export function extractEmailContent(email: any): { text: string; html: string }
|
|||||||
// Check if content is likely HTML
|
// Check if content is likely HTML
|
||||||
if (isHtmlContent(email.content)) {
|
if (isHtmlContent(email.content)) {
|
||||||
htmlContent = email.content;
|
htmlContent = email.content;
|
||||||
|
isHtml = true;
|
||||||
} else {
|
} else {
|
||||||
textContent = email.content;
|
textContent = email.content;
|
||||||
|
isHtml = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Check other common properties
|
// Check other common properties
|
||||||
htmlContent = email.html || '';
|
htmlContent = email.html || '';
|
||||||
textContent = email.text || '';
|
textContent = email.text || '';
|
||||||
|
isHtml = email.isHtml || !!htmlContent;
|
||||||
|
direction = email.direction || 'ltr';
|
||||||
|
|
||||||
// If still no content, check for less common properties
|
// If still no content, check for less common properties
|
||||||
if (!htmlContent && !textContent) {
|
if (!htmlContent && !textContent) {
|
||||||
// Try additional properties that some email clients use
|
// Try additional properties that some email clients use
|
||||||
htmlContent = email.body?.html || email.bodyHtml || email.htmlBody || '';
|
htmlContent = email.body?.html || email.bodyHtml || email.htmlBody || '';
|
||||||
textContent = email.body?.text || email.bodyText || email.plainText || '';
|
textContent = email.body?.text || email.bodyText || email.plainText || '';
|
||||||
|
isHtml = email.body?.isHtml || !!htmlContent;
|
||||||
|
direction = email.body?.direction || 'ltr';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -99,10 +116,12 @@ export function extractEmailContent(email: any): { text: string; html: string }
|
|||||||
hasHtml: !!htmlContent,
|
hasHtml: !!htmlContent,
|
||||||
htmlLength: htmlContent?.length || 0,
|
htmlLength: htmlContent?.length || 0,
|
||||||
hasText: !!textContent,
|
hasText: !!textContent,
|
||||||
textLength: textContent?.length || 0
|
textLength: textContent?.length || 0,
|
||||||
|
isHtml,
|
||||||
|
direction
|
||||||
});
|
});
|
||||||
|
|
||||||
return { text: textContent, html: htmlContent };
|
return { text: textContent, html: htmlContent, isHtml, direction };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -160,28 +179,30 @@ export function formatEmailContent(email: any): string {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Extract content from email
|
// Extract content from email
|
||||||
const { text, html } = extractEmailContent(email);
|
const { text, html, isHtml, direction } = extractEmailContent(email);
|
||||||
|
|
||||||
console.log('formatEmailContent processing:', {
|
console.log('formatEmailContent processing:', {
|
||||||
hasHtml: !!html,
|
hasHtml: !!html,
|
||||||
htmlLength: html?.length || 0,
|
htmlLength: html?.length || 0,
|
||||||
hasText: !!text,
|
hasText: !!text,
|
||||||
textLength: text?.length || 0,
|
textLength: text?.length || 0,
|
||||||
emailType: typeof email === 'string' ? 'string' : 'object'
|
emailType: typeof email === 'string' ? 'string' : 'object',
|
||||||
|
isHtml,
|
||||||
|
direction
|
||||||
});
|
});
|
||||||
|
|
||||||
// If we have HTML content, sanitize and standardize it
|
// If we have HTML content, sanitize and standardize it
|
||||||
if (html) {
|
if (html) {
|
||||||
// Process HTML content
|
// Process HTML content
|
||||||
let processedHtml = processHtmlContent(html, text);
|
const processed = processHtmlContent(html, { sanitize: true });
|
||||||
|
|
||||||
console.log('HTML content processed:', {
|
console.log('HTML content processed:', {
|
||||||
processedLength: processedHtml?.length || 0,
|
processedLength: processed.sanitizedContent?.length || 0,
|
||||||
isEmpty: !processedHtml || processedHtml.trim().length === 0
|
isEmpty: !processed.sanitizedContent || processed.sanitizedContent.trim().length === 0
|
||||||
});
|
});
|
||||||
|
|
||||||
// Apply styling
|
// Apply styling
|
||||||
return `<div class="email-content" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; max-width: 100%; overflow-x: auto; overflow-wrap: break-word; word-wrap: break-word;" dir="${detectTextDirection(text)}">${processedHtml}</div>`;
|
return `<div class="email-content" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; line-height: 1.6; color: #333; max-width: 100%; overflow-x: auto; overflow-wrap: break-word; word-wrap: break-word;" dir="${processed.direction}">${processed.sanitizedContent}</div>`;
|
||||||
}
|
}
|
||||||
// If we only have text content, format it properly
|
// If we only have text content, format it properly
|
||||||
else if (text) {
|
else if (text) {
|
||||||
@ -198,164 +219,102 @@ export function formatEmailContent(email: any): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process HTML content to fix common email rendering issues
|
* Process HTML content to ensure safe rendering and proper formatting
|
||||||
*/
|
*/
|
||||||
export function processHtmlContent(htmlContent: string, textContent?: string): string {
|
export function processHtmlContent(
|
||||||
if (!htmlContent) return '';
|
htmlContent: string,
|
||||||
|
options?: {
|
||||||
|
sanitize?: boolean;
|
||||||
|
blockExternalContent?: boolean;
|
||||||
|
attachments?: Array<{
|
||||||
|
filename?: string;
|
||||||
|
name?: string;
|
||||||
|
contentType?: string;
|
||||||
|
content?: string;
|
||||||
|
contentId?: string;
|
||||||
|
}>;
|
||||||
|
} | string // Support for legacy textContent parameter
|
||||||
|
): {
|
||||||
|
sanitizedContent: string;
|
||||||
|
hasImages: boolean;
|
||||||
|
hasExternalContent: boolean;
|
||||||
|
direction: 'ltr' | 'rtl';
|
||||||
|
} {
|
||||||
|
// Handle legacy string parameter (textContent)
|
||||||
|
if (typeof options === 'string') {
|
||||||
|
options = { sanitize: true };
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
console.log('Processing HTML content:', {
|
||||||
console.log('processHtmlContent input:', {
|
contentLength: htmlContent?.length || 0,
|
||||||
length: htmlContent.length,
|
startsWithHtml: htmlContent?.startsWith('<html'),
|
||||||
startsWithHtml: htmlContent.trim().startsWith('<html'),
|
startsWithDiv: htmlContent?.startsWith('<div'),
|
||||||
startsWithDiv: htmlContent.trim().startsWith('<div'),
|
containsForwardedMessage: htmlContent?.includes('---------- Forwarded message ----------'),
|
||||||
hasBody: htmlContent.includes('<body'),
|
containsQuoteHeader: htmlContent?.includes('<div class="gmail_quote"'),
|
||||||
containsForwardedMessage: htmlContent.includes('---------- Forwarded message ----------'),
|
sanitize: options?.sanitize,
|
||||||
containsQuoteHeader: htmlContent.includes('wrote:'),
|
blockExternalContent: options?.blockExternalContent,
|
||||||
hasBlockquote: htmlContent.includes('<blockquote'),
|
hasAttachments: options?.attachments?.length || 0
|
||||||
hasTable: htmlContent.includes('<table')
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check for browser environment (DOMParser is browser-only)
|
if (!htmlContent) {
|
||||||
const hasHtmlTag = htmlContent.includes('<html');
|
return {
|
||||||
const hasBodyTag = htmlContent.includes('<body');
|
sanitizedContent: '',
|
||||||
|
hasImages: false,
|
||||||
|
hasExternalContent: false,
|
||||||
|
direction: 'ltr',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// Preserve original HTML for debugging
|
// Store the original content for comparison
|
||||||
let originalHtml = htmlContent;
|
const originalContent = htmlContent;
|
||||||
|
|
||||||
|
// Process CID references before sanitization
|
||||||
|
if (options?.attachments?.length) {
|
||||||
|
console.log('Processing CID references in processHtmlContent');
|
||||||
|
htmlContent = processCidReferences(htmlContent, options.attachments);
|
||||||
|
}
|
||||||
|
|
||||||
// Extract body content if we have a complete HTML document and in browser environment
|
|
||||||
if (hasHtmlTag && hasBodyTag && typeof window !== 'undefined' && typeof DOMParser !== 'undefined') {
|
|
||||||
try {
|
try {
|
||||||
// Create a DOM parser to extract just the body content
|
// Apply sanitization by default unless explicitly turned off
|
||||||
const parser = new DOMParser();
|
let sanitizedContent = (options?.sanitize !== false) ? sanitizeHtml(htmlContent) : htmlContent;
|
||||||
const doc = parser.parseFromString(htmlContent, 'text/html');
|
|
||||||
const bodyContent = doc.body.innerHTML;
|
|
||||||
|
|
||||||
if (bodyContent) {
|
// Log content changes from sanitization
|
||||||
console.log('Extracted body content from HTML document, length:', bodyContent.length);
|
console.log('HTML sanitization results:', {
|
||||||
htmlContent = bodyContent;
|
originalLength: originalContent.length,
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error extracting body content:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the centralized sanitizeHtml function
|
|
||||||
let sanitizedContent = sanitizeHtml(htmlContent);
|
|
||||||
|
|
||||||
console.log('After sanitizeHtml:', {
|
|
||||||
originalLength: originalHtml.length,
|
|
||||||
sanitizedLength: sanitizedContent.length,
|
sanitizedLength: sanitizedContent.length,
|
||||||
difference: originalHtml.length - sanitizedContent.length,
|
difference: originalContent.length - sanitizedContent.length,
|
||||||
percentRemoved: ((originalHtml.length - sanitizedContent.length) / originalHtml.length * 100).toFixed(2) + '%',
|
percentRemoved: ((originalContent.length - sanitizedContent.length) / originalContent.length * 100).toFixed(2) + '%',
|
||||||
containsForwardedMessage: sanitizedContent.includes('---------- Forwarded message ----------'),
|
isEmpty: !sanitizedContent || sanitizedContent.trim().length === 0
|
||||||
hasTable: sanitizedContent.includes('<table'),
|
|
||||||
hasBlockquote: sanitizedContent.includes('<blockquote')
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fix URL encoding issues and clean up content
|
|
||||||
try {
|
|
||||||
if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
|
||||||
// Temporary element to manipulate the HTML
|
|
||||||
const tempDiv = document.createElement('div');
|
|
||||||
tempDiv.innerHTML = sanitizedContent;
|
|
||||||
|
|
||||||
// Fix all links that might have been double-encoded
|
|
||||||
const links = tempDiv.querySelectorAll('a');
|
|
||||||
links.forEach(link => {
|
|
||||||
const href = link.getAttribute('href');
|
|
||||||
if (href && href.includes('%')) {
|
|
||||||
try {
|
|
||||||
// Try to decode URLs that might have been double-encoded
|
|
||||||
const decodedHref = decodeURIComponent(href);
|
|
||||||
link.setAttribute('href', decodedHref);
|
|
||||||
} catch (e) {
|
|
||||||
// If decoding fails, keep the original
|
|
||||||
console.warn('Failed to decode href:', href);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Fix image URLs - preserve cid: URLs for email attachments
|
|
||||||
const images = tempDiv.querySelectorAll('img');
|
|
||||||
images.forEach(img => {
|
|
||||||
const src = img.getAttribute('src');
|
|
||||||
if (src) {
|
|
||||||
// Don't modify cid: URLs as they are handled specially in email clients
|
|
||||||
if (src.startsWith('cid:')) {
|
|
||||||
// Keep cid: URLs as they are
|
|
||||||
console.log('Preserving CID reference:', src);
|
|
||||||
}
|
|
||||||
// Fix http:// URLs to https:// for security
|
|
||||||
else if (src.startsWith('http://')) {
|
|
||||||
img.setAttribute('src', src.replace('http://', 'https://'));
|
|
||||||
}
|
|
||||||
// Handle relative URLs that might be broken
|
|
||||||
else if (!src.startsWith('https://') && !src.startsWith('data:')) {
|
|
||||||
if (src.startsWith('/')) {
|
|
||||||
img.setAttribute('src', `https://example.com${src}`);
|
|
||||||
} else {
|
|
||||||
img.setAttribute('src', `https://example.com/${src}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clean up excessive whitespace and empty elements
|
|
||||||
// Find all text nodes and normalize whitespace
|
|
||||||
const walker = document.createTreeWalker(
|
|
||||||
tempDiv,
|
|
||||||
NodeFilter.SHOW_TEXT,
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
const textNodes = [];
|
|
||||||
while (walker.nextNode()) {
|
|
||||||
textNodes.push(walker.currentNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process text nodes to normalize whitespace
|
|
||||||
textNodes.forEach(node => {
|
|
||||||
if (node.nodeValue) {
|
|
||||||
// Replace sequences of whitespace with a single space
|
|
||||||
node.nodeValue = node.nodeValue.replace(/\s+/g, ' ').trim();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove empty paragraphs and divs that contain only whitespace
|
|
||||||
const emptyElements = tempDiv.querySelectorAll('p, div, span');
|
|
||||||
emptyElements.forEach(el => {
|
|
||||||
if (el.innerHTML.trim() === '' || el.innerHTML === ' ') {
|
|
||||||
el.parentNode?.removeChild(el);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Remove excessive consecutive <br> tags (more than 2)
|
|
||||||
let html = tempDiv.innerHTML;
|
|
||||||
html = html.replace(/(<br\s*\/?>\s*){3,}/gi, '<br><br>');
|
|
||||||
tempDiv.innerHTML = html;
|
|
||||||
|
|
||||||
// Get the fixed HTML
|
|
||||||
sanitizedContent = tempDiv.innerHTML;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error('Error fixing content:', e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fix common email client quirks without breaking cid: URLs
|
// Fix common email client quirks without breaking cid: URLs
|
||||||
return sanitizedContent
|
sanitizedContent = sanitizedContent
|
||||||
// Fix for Outlook WebVML content
|
// Fix for Outlook WebVML content
|
||||||
.replace(/<!--\[if\s+gte\s+mso/g, '<!--[if gte mso')
|
.replace(/<!--\[if\s+gte\s+mso/g, '<!--[if gte mso')
|
||||||
// Fix for broken image paths WITHOUT replacing cid: URLs
|
// Fix for broken image paths starting with // (add https:)
|
||||||
.replace(/(src|background)="(?!(?:https?:|data:|cid:))/gi, '$1="https://')
|
.replace(/src="\/\//g, 'src="https://')
|
||||||
// Fix for base64 images that might be broken across lines
|
// Handle mixed content issues by converting http:// to https://
|
||||||
.replace(/src="data:image\/[^;]+;base64,\s*([^"]+)\s*"/gi, (match, p1) => {
|
.replace(/src="http:\/\//g, 'src="https://')
|
||||||
return `src="data:image/png;base64,${p1.replace(/\s+/g, '')}"`;
|
// Fix email signature line breaks
|
||||||
})
|
.replace(/--<br>/g, '<hr style="border-top: 1px solid #ccc; margin: 15px 0;">')
|
||||||
|
.replace(/-- <br>/g, '<hr style="border-top: 1px solid #ccc; margin: 15px 0;">')
|
||||||
// Remove excessive whitespace from the HTML string itself
|
// Remove excessive whitespace from the HTML string itself
|
||||||
.replace(/>\s+</g, '> <');
|
.replace(/>\s+</g, '> <');
|
||||||
|
|
||||||
|
return {
|
||||||
|
sanitizedContent,
|
||||||
|
hasImages: sanitizedContent.includes('<img'),
|
||||||
|
hasExternalContent: sanitizedContent.includes('https://'),
|
||||||
|
direction: detectTextDirection(sanitizedContent)
|
||||||
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error processing HTML content:', error);
|
console.error('Error processing HTML content:', error);
|
||||||
return htmlContent;
|
return {
|
||||||
|
sanitizedContent: htmlContent,
|
||||||
|
hasImages: false,
|
||||||
|
hasExternalContent: false,
|
||||||
|
direction: 'ltr',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -381,6 +381,98 @@ export function formatReplyEmail(originalEmail: EmailMessage | LegacyEmailMessag
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process and replace CID references with base64 data URLs using the email's attachments.
|
||||||
|
* This function should be called before sanitizing the content.
|
||||||
|
*/
|
||||||
|
export function processCidReferences(htmlContent: string, attachments?: Array<{
|
||||||
|
filename?: string;
|
||||||
|
name?: string;
|
||||||
|
contentType?: string;
|
||||||
|
content?: string;
|
||||||
|
contentId?: string;
|
||||||
|
}>): string {
|
||||||
|
if (!htmlContent || !attachments || !attachments.length) {
|
||||||
|
return htmlContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Processing CID references with ${attachments.length} attachments available`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Create a map of content IDs to their attachment data
|
||||||
|
const cidMap = new Map();
|
||||||
|
attachments.forEach(att => {
|
||||||
|
if (att.contentId) {
|
||||||
|
// Content ID sometimes has <> brackets which need to be removed
|
||||||
|
const cleanCid = att.contentId.replace(/[<>]/g, '');
|
||||||
|
cidMap.set(cleanCid, {
|
||||||
|
contentType: att.contentType || 'application/octet-stream',
|
||||||
|
content: att.content
|
||||||
|
});
|
||||||
|
console.log(`Mapped CID: ${cleanCid} to attachment of type ${att.contentType || 'unknown'}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// If we have no content IDs mapped, return original content
|
||||||
|
if (cidMap.size === 0) {
|
||||||
|
console.log('No CID references found in attachments');
|
||||||
|
return htmlContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we're in a browser environment
|
||||||
|
if (typeof document === 'undefined') {
|
||||||
|
console.log('Not in browser environment, skipping CID processing');
|
||||||
|
return htmlContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the HTML content and replace CID references
|
||||||
|
const tempDiv = document.createElement('div');
|
||||||
|
tempDiv.innerHTML = htmlContent;
|
||||||
|
|
||||||
|
// Find all images with CID sources
|
||||||
|
const imgElements = tempDiv.querySelectorAll('img[src^="cid:"]');
|
||||||
|
|
||||||
|
console.log(`Found ${imgElements.length} img elements with CID references`);
|
||||||
|
|
||||||
|
if (imgElements.length === 0) {
|
||||||
|
return htmlContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each image with a CID reference
|
||||||
|
let replacedCount = 0;
|
||||||
|
imgElements.forEach(img => {
|
||||||
|
const src = img.getAttribute('src');
|
||||||
|
if (!src || !src.startsWith('cid:')) return;
|
||||||
|
|
||||||
|
// Extract the content ID from the src
|
||||||
|
const cid = src.substring(4); // Remove 'cid:' prefix
|
||||||
|
|
||||||
|
// Find the matching attachment
|
||||||
|
const attachment = cidMap.get(cid);
|
||||||
|
|
||||||
|
if (attachment && attachment.content) {
|
||||||
|
// Convert the attachment content to a data URL
|
||||||
|
const dataUrl = `data:${attachment.contentType};base64,${attachment.content}`;
|
||||||
|
|
||||||
|
// Replace the CID reference with the data URL
|
||||||
|
img.setAttribute('src', dataUrl);
|
||||||
|
replacedCount++;
|
||||||
|
console.log(`Replaced CID ${cid} with data URL`);
|
||||||
|
} else {
|
||||||
|
console.log(`No matching attachment found for CID: ${cid}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`Replaced ${replacedCount} CID references with data URLs`);
|
||||||
|
|
||||||
|
// Return the updated HTML content
|
||||||
|
return tempDiv.innerHTML;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error processing CID references:', error);
|
||||||
|
return htmlContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format email for forwarding
|
* Format email for forwarding
|
||||||
*/
|
*/
|
||||||
@ -453,6 +545,12 @@ export function formatForwardedEmail(originalEmail: EmailMessage | LegacyEmailMe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process embedded images with CID references
|
||||||
|
if (htmlContent && email.attachments && email.attachments.length > 0) {
|
||||||
|
console.log('Processing CID references before sanitization');
|
||||||
|
htmlContent = processCidReferences(htmlContent, email.attachments);
|
||||||
|
}
|
||||||
|
|
||||||
// Create the forwarded email HTML content
|
// Create the forwarded email HTML content
|
||||||
if (htmlContent) {
|
if (htmlContent) {
|
||||||
console.log('Formatting HTML forward, original content length:', htmlContent.length);
|
console.log('Formatting HTML forward, original content length:', htmlContent.length);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user