From f4990623da1f77bf3425527a0fb575bcccc02dbe Mon Sep 17 00:00:00 2001 From: alma Date: Thu, 24 Apr 2025 16:04:46 +0200 Subject: [PATCH] mime change --- app/courrier/page.tsx | 419 +++++++------------------------------ lib/mail-parser-wrapper.ts | 71 +++++++ 2 files changed, 145 insertions(+), 345 deletions(-) create mode 100644 lib/mail-parser-wrapper.ts diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index 5f25efce..f5d020e0 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -40,6 +40,8 @@ import { } from '@/lib/infomaniak-mime-decoder'; import DOMPurify from 'isomorphic-dompurify'; import ComposeEmail from '@/components/ComposeEmail'; +import { decodeEmail } from '@/lib/mail-parser-wrapper'; +import { Attachment as MailParserAttachment } from 'mailparser'; export interface Account { id: number; @@ -108,188 +110,67 @@ function splitEmailHeadersAndBody(emailBody: string): { headers: string; body: s }; } -function renderEmailContent(email: Email) { +async function renderEmailContent(email: Email) { if (!email.body) { console.warn('No email body provided'); return null; } try { - // Split email into headers and body - const [headersPart, ...bodyParts] = email.body.split('\r\n\r\n'); - if (!headersPart || bodyParts.length === 0) { - throw new Error('Invalid email format: missing headers or body'); + const decoded = await decodeEmail(email.body); + + // Prefer HTML content if available + if (decoded.html) { + return ( +
+
+ {decoded.attachments.length > 0 && renderAttachments(decoded.attachments)} +
+ ); } - const body = bodyParts.join('\r\n\r\n'); - - // Parse headers using Infomaniak MIME decoder - const headerInfo = parseEmailHeaders(headersPart); - const boundary = extractBoundary(headersPart); - - // If it's a multipart email - if (boundary) { - try { - const parts = body.split(`--${boundary}`); - let htmlContent = ''; - let textContent = ''; - let attachments: { filename: string; content: string }[] = []; - - for (const part of parts) { - if (!part.trim()) continue; - - const [partHeaders, ...partBodyParts] = part.split('\r\n\r\n'); - if (!partHeaders || partBodyParts.length === 0) continue; - - const partBody = partBodyParts.join('\r\n\r\n'); - const contentType = extractHeader(partHeaders, 'Content-Type').toLowerCase(); - const encoding = extractHeader(partHeaders, 'Content-Transfer-Encoding').toLowerCase(); - const charset = extractHeader(partHeaders, 'charset') || 'utf-8'; - - try { - let decodedContent = ''; - if (encoding === 'base64') { - decodedContent = decodeBase64(partBody, charset); - } else if (encoding === 'quoted-printable') { - decodedContent = decodeQuotedPrintable(partBody, charset); - } else { - decodedContent = convertCharset(partBody, charset); - } - - if (contentType.includes('text/html')) { - // For HTML content, we want to preserve the HTML structure - // Only clean up problematic elements while keeping the formatting - htmlContent = decodedContent - .replace(/]*>[\s\S]*?<\/style>/gi, '') - .replace(/]*>[\s\S]*?<\/script>/gi, '') - .replace(/]*>/gi, '') - .replace(/]*>/gi, '') - .replace(/]*>/gi, '') - .replace(/]*>[\s\S]*?<\/title>/gi, '') - .replace(/]*>[\s\S]*?<\/head>/gi, '') - .replace(/]*>/gi, '') - .replace(/<\/body>/gi, '') - .replace(/]*>/gi, '') - .replace(/<\/html>/gi, ''); - } else if (contentType.includes('text/plain')) { - textContent = decodedContent; - } else if (contentType.includes('attachment') || extractHeader(partHeaders, 'Content-Disposition').includes('attachment')) { - attachments.push({ - filename: extractFilename(partHeaders) || 'unnamed_attachment', - content: decodedContent - }); - } - } catch (partError) { - console.error('Error processing email part:', partError); - continue; - } - } - - // Prefer HTML content if available - if (htmlContent) { - return ( -
-
- {attachments.length > 0 && renderAttachments(attachments)} -
- ); - } - - // Fall back to text content - if (textContent) { - return ( -
-
- {textContent.split('\n').map((line: string, i: number) => ( -

{line}

- ))} -
- {attachments.length > 0 && renderAttachments(attachments)} -
- ); - } - } catch (multipartError) { - console.error('Error processing multipart email:', multipartError); - throw new Error('Failed to process multipart email'); - } - } - - // If it's a simple email, try to detect content type and decode - const contentType = extractHeader(headersPart, 'Content-Type').toLowerCase(); - const encoding = extractHeader(headersPart, 'Content-Transfer-Encoding').toLowerCase(); - const charset = extractHeader(headersPart, 'charset') || 'utf-8'; - - try { - let decodedBody = ''; - if (encoding === 'base64') { - decodedBody = decodeBase64(body, charset); - } else if (encoding === 'quoted-printable') { - decodedBody = decodeQuotedPrintable(body, charset); - } else { - decodedBody = convertCharset(body, charset); - } - - if (contentType.includes('text/html')) { - // For HTML content, preserve the HTML structure - const cleanedHtml = decodedBody - .replace(/]*>[\s\S]*?<\/style>/gi, '') - .replace(/]*>[\s\S]*?<\/script>/gi, '') - .replace(/]*>/gi, '') - .replace(/]*>/gi, '') - .replace(/]*>/gi, '') - .replace(/]*>[\s\S]*?<\/title>/gi, '') - .replace(/]*>[\s\S]*?<\/head>/gi, '') - .replace(/]*>/gi, '') - .replace(/<\/body>/gi, '') - .replace(/]*>/gi, '') - .replace(/<\/html>/gi, ''); - - return ( -
-
-
- ); - } else { - return ( -
+ // Fall back to text content + if (decoded.text) { + return ( +
- {decodedBody.split('\n').map((line: string, i: number) => ( + {decoded.text.split('\n').map((line: string, i: number) => (

{line}

))}
-
- ); - } - } catch (decodeError) { - console.error('Error decoding email body:', decodeError); - throw new Error('Failed to decode email body'); + {decoded.attachments.length > 0 && renderAttachments(decoded.attachments)} +
+ ); } + + return null; } catch (error) { console.error('Error rendering email content:', error); return ( -
-
Error displaying email content: {error instanceof Error ? error.message : 'Unknown error'}
-
-          {email.body}
-        
+
+ Error rendering email content
); } } -// Helper function to render attachments -function renderAttachments(attachments: { filename: string; content: string }[]) { +function renderAttachments(attachments: MailParserAttachment[]) { + if (!attachments.length) return null; + return ( -
-

Attachments:

-
    +
    +

    Attachments

    +
    {attachments.map((attachment, index) => ( -
  • - - {attachment.filename} -
  • +
    + + {attachment.filename || 'unnamed_attachment'} + + {attachment.size ? `(${Math.round(attachment.size / 1024)} KB)` : ''} + +
    ))} -
+
); } @@ -328,124 +209,43 @@ const initialSidebarItems = [ } ]; -function getReplyBody(email: Email, type: 'reply' | 'reply-all' | 'forward' = 'reply') { +async function getReplyBody(email: Email, type: 'reply' | 'reply-all' | 'forward' = 'reply') { if (!email.body) return ''; - let content = ''; - let headers = ''; - let body = ''; - - // Split headers and body - const parts = email.body.split('\r\n\r\n'); - if (parts.length > 1) { - headers = parts[0]; - body = parts.slice(1).join('\r\n\r\n'); - } else { - body = email.body; - } - - // Parse headers - const headerInfo = parseEmailHeaders(headers); - const boundary = extractBoundary(headers); - - // Handle multipart emails - if (boundary) { - const parts = body.split(`--${boundary}`); - for (const part of parts) { - if (!part.trim()) continue; - - const [partHeaders, ...partBodyParts] = part.split('\r\n\r\n'); - if (!partHeaders || partBodyParts.length === 0) continue; - - const partBody = partBodyParts.join('\r\n\r\n'); - const partHeaderInfo = parseEmailHeaders(partHeaders); - - try { - let decodedContent = ''; - if (partHeaderInfo.encoding === 'base64') { - decodedContent = decodeBase64(partBody, partHeaderInfo.charset); - } else if (partHeaderInfo.encoding === 'quoted-printable') { - decodedContent = decodeQuotedPrintable(partBody, partHeaderInfo.charset); - } else { - decodedContent = convertCharset(partBody, partHeaderInfo.charset); - } - - // Preserve the original MIME structure - if (partHeaderInfo.contentType.includes('text/html')) { - content = ` - - `; - break; - } else if (partHeaderInfo.contentType.includes('text/plain') && !content) { - content = ` - - `; - } - } catch (error) { - console.error('Error decoding email part:', error); - } - } - } else { - // Handle simple email - try { - let decodedBody = ''; - if (headerInfo.encoding === 'base64') { - decodedBody = decodeBase64(body, headerInfo.charset); - } else if (headerInfo.encoding === 'quoted-printable') { - decodedBody = decodeQuotedPrintable(body, headerInfo.charset); - } else { - decodedBody = convertCharset(body, headerInfo.charset); - } - - content = ` -