diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx index 322450cf..36c2a53d 100644 --- a/app/courrier/page.tsx +++ b/app/courrier/page.tsx @@ -98,115 +98,160 @@ interface ParsedEmailMetadata { } function renderEmailContent(email: Email) { - if (!email.body) return null; + 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 body = bodyParts.join('\r\n\r\n'); - // Parse headers using our MIME decoder + // Parse headers using Infomaniak MIME decoder const headerInfo = parseEmailHeaders(headersPart); const boundary = extractBoundary(headersPart); // If it's a multipart email if (boundary) { - const parts = body.split(`--${boundary}`); - let htmlContent = ''; - let textContent = ''; - let attachments: { filename: string; content: string }[] = []; + 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'); - const partBody = partBodyParts.join('\r\n\r\n'); - const partHeaderInfo = parseEmailHeaders(partHeaders); - - if (partHeaderInfo.contentType.includes('text/html')) { - htmlContent = decodeQuotedPrintable(partBody, partHeaderInfo.charset); - } else if (partHeaderInfo.contentType.includes('text/plain')) { - textContent = decodeQuotedPrintable(partBody, partHeaderInfo.charset); - } else if (partHeaderInfo.contentType.includes('attachment')) { - attachments.push({ - filename: extractFilename(partHeaders), - content: decodeBase64(partBody, partHeaderInfo.charset) - }); + 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); + const contentType = extractHeader(partHeaders, 'Content-Type').toLowerCase(); + const encoding = extractHeader(partHeaders, 'Content-Transfer-Encoding').toLowerCase(); + const charset = extractHeader(partHeaders, 'charset') || 'utf-8'; + + try { + if (contentType.includes('text/html')) { + const decodedContent = encoding === 'base64' + ? decodeBase64(partBody, charset) + : decodeQuotedPrintable(partBody, charset); + htmlContent = cleanHtml(decodedContent); + } else if (contentType.includes('text/plain')) { + const decodedContent = encoding === 'base64' + ? decodeBase64(partBody, charset) + : decodeQuotedPrintable(partBody, charset); + textContent = decodedContent; + } else if (contentType.includes('attachment') || extractHeader(partHeaders, 'Content-Disposition').includes('attachment')) { + attachments.push({ + filename: extractFilename(partHeaders) || 'unnamed_attachment', + content: encoding === 'base64' + ? decodeBase64(partBody, charset) + : decodeQuotedPrintable(partBody, charset) + }); + } + } catch (partError) { + console.error('Error processing email part:', partError); + continue; + } } - } - // Prefer HTML content if available - if (htmlContent) { - return ( -
-
- {attachments.length > 0 && ( -
-

Attachments:

-
    - {attachments.map((attachment, index) => ( -
  • - - {attachment.filename} -
  • - ))} -
-
- )} -
- ); - } - - // Fall back to text content - if (textContent) { - return ( -
-
- {textContent.split('\n').map((line: string, i: number) => ( -

{line}

- ))} + // Prefer HTML content if available + if (htmlContent) { + return ( +
+
+ {attachments.length > 0 && renderAttachments(attachments)}
- {attachments.length > 0 && ( -
-

Attachments:

-
    - {attachments.map((attachment, index) => ( -
  • - - {attachment.filename} -
  • - ))} -
+ ); + } + + // 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 decode it - const decodedBody = decodeQuotedPrintable(body, headerInfo.charset); - const cleanedContent = cleanHtml(decodedBody); + // 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'; - return ( -
-
- {cleanedContent.split('\n').map((line: string, i: number) => ( -

{line}

- ))} -
-
- ); + try { + const decodedBody = encoding === 'base64' + ? decodeBase64(body, charset) + : decodeQuotedPrintable(body, charset); + + if (contentType.includes('text/html')) { + const cleanedContent = cleanHtml(decodedBody); + return ( +
+
+
+ ); + } else { + return ( +
+
+ {decodedBody.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'); + } } catch (error) { console.error('Error rendering email content:', error); return (
-
{email.body}
+
Error displaying email content: {error instanceof Error ? error.message : 'Unknown error'}
+
+          {email.body}
+        
); } } +// Helper function to render attachments +function renderAttachments(attachments: { filename: string; content: string }[]) { + return ( +
+

Attachments:

+
    + {attachments.map((attachment, index) => ( +
  • + + {attachment.filename} +
  • + ))} +
+
+ ); +} + // Define the exact folder names from IMAP type MailFolder = string;