diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx
index 50f07e69..5cd64c11 100644
--- a/app/courrier/page.tsx
+++ b/app/courrier/page.tsx
@@ -63,6 +63,7 @@ interface Email {
cc?: string;
bcc?: string;
flags?: string[];
+ raw: string;
}
interface Attachment {
@@ -327,64 +328,81 @@ const initialSidebarItems = [
];
function getReplyBody(email: Email, type: 'reply' | 'reply-all' | 'forward'): string {
- const { headers, body } = splitEmailHeadersAndBody(email.body);
+ if (!email.raw) return '';
+
+ const { headers, body } = splitEmailHeadersAndBody(email.raw);
const { contentType, encoding, charset } = parseEmailHeaders(headers);
- const isHtml = contentType.includes('text/html');
- const isMultipart = contentType.includes('multipart');
- const isQuotedPrintable = encoding === 'quoted-printable';
-
- let content = body;
- if (isQuotedPrintable) {
- content = decodeQuotedPrintable(content, charset);
- }
-
- if (isMultipart) {
- const parts = content.split('--boundary');
- content = parts.find((part: string) => part.includes('text/html')) || parts.find((part: string) => part.includes('text/plain')) || '';
- const partHeaders = content.split('\n\n')[0];
- const partContent = content.split('\n\n').slice(1).join('\n\n');
- const { contentType: partContentType, encoding: partEncoding, charset: partCharset } = parseEmailHeaders(partHeaders);
- content = partContent;
- if (partEncoding === 'quoted-printable') {
- content = decodeQuotedPrintable(content, partCharset);
+
+ let content = '';
+
+ if (contentType.includes('multipart/')) {
+ const boundary = contentType.match(/boundary="([^"]+)"/)?.[1];
+ if (boundary) {
+ const parts = body.split('--' + boundary).filter(part => part.trim());
+
+ // Find HTML part first, fallback to text part
+ const htmlPart = parts.find(part => part.toLowerCase().includes('content-type: text/html'));
+ const textPart = parts.find(part => part.toLowerCase().includes('content-type: text/plain'));
+
+ const selectedPart = htmlPart || textPart;
+ if (selectedPart) {
+ const partHeaders = selectedPart.split('\r\n\r\n')[0];
+ const partBody = selectedPart.split('\r\n\r\n').slice(1).join('\r\n\r\n');
+ const { encoding: partEncoding } = parseEmailHeaders(partHeaders);
+
+ content = partEncoding === 'quoted-printable'
+ ? decodeQuotedPrintable(partBody, charset)
+ : partBody;
+ }
}
- }
-
- if (isHtml) {
- // Preserve HTML structure while cleaning potentially dangerous elements
- content = cleanHtml(content);
} else {
- // Convert plain text to HTML while preserving formatting
- content = content
- .replace(/\n/g, '
')
- .replace(/\t/g, ' ')
- .replace(/ /g, ' ');
+ content = encoding === 'quoted-printable'
+ ? decodeQuotedPrintable(body, charset)
+ : body;
}
-
+
+ // Convert plain text to HTML if needed
+ if (!contentType.includes('text/html')) {
+ content = content
+ .split('\n')
+ .map(line => `
${line}
`) + .join(''); + } + + // Sanitize HTML content + content = DOMPurify.sanitize(content, { + ALLOWED_TAGS: [ + 'p', 'br', 'div', 'span', 'b', 'i', 'u', 'strong', 'em', + 'blockquote', 'ul', 'ol', 'li', 'a', 'h1', 'h2', 'h3', 'h4', + 'table', 'thead', 'tbody', 'tr', 'td', 'th' + ], + ALLOWED_ATTR: ['href', 'style', 'class'], + }); + + const date = new Date(email.date).toLocaleString(); + if (type === 'forward') { return ` - +