courrier clean 2$
This commit is contained in:
parent
5686b9fb7d
commit
26e72f4f73
@ -1,7 +1,6 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { simpleParser } from 'mailparser';
|
||||
import * as DOMPurify from 'isomorphic-dompurify';
|
||||
import { cleanHtml as cleanHtmlCentralized } from '@/lib/mail-parser-wrapper';
|
||||
import { parseEmail } from '@/lib/server/email-parser';
|
||||
import { sanitizeHtml } from '@/lib/utils/email-formatter';
|
||||
|
||||
interface EmailAddress {
|
||||
name?: string;
|
||||
@ -45,68 +44,29 @@ function getEmailAddresses(addresses: any): EmailAddress[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This function is deprecated and will be removed in future versions.
|
||||
* Use the cleanHtml function from '@/lib/mail-parser-wrapper' instead.
|
||||
* Maintained for backward compatibility only.
|
||||
*/
|
||||
function processHtml(html: string): string {
|
||||
if (!html) return '';
|
||||
|
||||
// Delegate to the centralized implementation
|
||||
return cleanHtmlCentralized(html, {
|
||||
preserveStyles: true,
|
||||
scopeStyles: true,
|
||||
addWrapper: true
|
||||
});
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const { email } = await req.json();
|
||||
const body = await request.json();
|
||||
|
||||
if (!email || typeof email !== 'string') {
|
||||
if (!body.email) {
|
||||
return NextResponse.json(
|
||||
{ error: 'Invalid email content' },
|
||||
{ error: 'Missing email content' },
|
||||
{ status: 400 }
|
||||
);
|
||||
}
|
||||
|
||||
const parsedEmail = await parseEmail(body.email);
|
||||
|
||||
const parsed = await simpleParser(email);
|
||||
// Process HTML content if available
|
||||
if (parsedEmail.html) {
|
||||
parsedEmail.html = sanitizeHtml(parsedEmail.html);
|
||||
}
|
||||
|
||||
// Process the HTML content to make it safe and displayable
|
||||
const html = parsed.html
|
||||
? processHtml(parsed.html.toString())
|
||||
: undefined;
|
||||
|
||||
const text = parsed.text
|
||||
? parsed.text.toString()
|
||||
: undefined;
|
||||
|
||||
// Extract attachments info if available
|
||||
const attachments = parsed.attachments?.map(attachment => ({
|
||||
filename: attachment.filename,
|
||||
contentType: attachment.contentType,
|
||||
contentDisposition: attachment.contentDisposition,
|
||||
size: attachment.size
|
||||
})) || [];
|
||||
|
||||
// Return all parsed email details
|
||||
return NextResponse.json({
|
||||
subject: parsed.subject,
|
||||
from: getEmailAddresses(parsed.from),
|
||||
to: getEmailAddresses(parsed.to),
|
||||
cc: getEmailAddresses(parsed.cc),
|
||||
bcc: getEmailAddresses(parsed.bcc),
|
||||
date: parsed.date,
|
||||
html,
|
||||
text,
|
||||
attachments
|
||||
});
|
||||
return NextResponse.json(parsedEmail);
|
||||
} catch (error) {
|
||||
console.error('Error parsing email:', error);
|
||||
return NextResponse.json(
|
||||
{ error: 'Failed to parse email content' },
|
||||
{ error: 'Failed to parse email', details: error instanceof Error ? error.message : String(error) },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,3 +1,10 @@
|
||||
/**
|
||||
* @deprecated This entire file is deprecated and will be removed in future versions.
|
||||
* Use the parseEmail function from lib/server/email-parser.ts and
|
||||
* sanitizeHtml from lib/utils/email-formatter.ts instead.
|
||||
* This file is maintained only for backward compatibility.
|
||||
*/
|
||||
|
||||
interface EmailHeaders {
|
||||
from: string;
|
||||
subject: string;
|
||||
@ -5,7 +12,12 @@ interface EmailHeaders {
|
||||
to?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use parseEmail from lib/server/email-parser.ts instead.
|
||||
*/
|
||||
export function parseEmailHeaders(headerContent: string): EmailHeaders {
|
||||
console.warn('parseEmailHeaders is deprecated. Use parseEmail from lib/server/email-parser.ts instead.');
|
||||
|
||||
const headers: { [key: string]: string } = {};
|
||||
let currentHeader = '';
|
||||
let currentValue = '';
|
||||
@ -48,7 +60,12 @@ export function parseEmailHeaders(headerContent: string): EmailHeaders {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use sanitizeHtml from lib/utils/email-formatter.ts instead.
|
||||
*/
|
||||
export function decodeEmailBody(content: string, contentType: string): string {
|
||||
console.warn('decodeEmailBody is deprecated. Use sanitizeHtml from lib/utils/email-formatter.ts instead.');
|
||||
|
||||
try {
|
||||
// Remove email client-specific markers
|
||||
content = content.replace(/\r\n/g, '\n')
|
||||
@ -68,7 +85,12 @@ export function decodeEmailBody(content: string, contentType: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use sanitizeHtml from lib/utils/email-formatter.ts instead.
|
||||
*/
|
||||
function extractTextFromHtml(html: string): string {
|
||||
console.warn('extractTextFromHtml is deprecated. Use sanitizeHtml from lib/utils/email-formatter.ts instead.');
|
||||
|
||||
// Remove scripts and style tags
|
||||
html = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
||||
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
'use client';
|
||||
|
||||
import DOMPurify from 'dompurify';
|
||||
import { sanitizeHtml } from '@/lib/utils/email-formatter';
|
||||
|
||||
export interface ParsedEmail {
|
||||
subject: string | null;
|
||||
@ -99,119 +100,4 @@ export async function decodeEmail(emailContent: string): Promise<ParsedEmail> {
|
||||
headers: {}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans HTML content by removing potentially harmful elements while preserving styling.
|
||||
* This is the centralized HTML sanitization function to be used across the application.
|
||||
*
|
||||
* Key features:
|
||||
* - Safely removes scripts, iframes, and other potentially harmful elements
|
||||
* - Optionally preserves and scopes CSS styles to prevent them from affecting the rest of the page
|
||||
* - Fixes self-closing tags that might break in React or contentEditable contexts
|
||||
* - Can add a wrapper div with isolation for additional safety
|
||||
*
|
||||
* @param html HTML content to sanitize
|
||||
* @param options Configuration options:
|
||||
* - preserveStyles: Whether to keep <style> tags (default: true)
|
||||
* - scopeStyles: Whether to scope CSS to prevent leakage into the rest of the page (default: true)
|
||||
* - addWrapper: Whether to add a container div with CSS isolation (default: true)
|
||||
* @returns Sanitized HTML that can be safely inserted into the document
|
||||
*/
|
||||
export function cleanHtml(html: string, options: {
|
||||
preserveStyles?: boolean;
|
||||
scopeStyles?: boolean;
|
||||
addWrapper?: boolean;
|
||||
} = {}): string {
|
||||
if (!html) return '';
|
||||
|
||||
try {
|
||||
const defaultOptions = {
|
||||
preserveStyles: true,
|
||||
scopeStyles: true,
|
||||
addWrapper: true,
|
||||
...options
|
||||
};
|
||||
|
||||
// Extract style tags if we're preserving them
|
||||
const styleTagsContent: string[] = [];
|
||||
let processedHtml = html;
|
||||
|
||||
if (defaultOptions.preserveStyles) {
|
||||
processedHtml = html.replace(/<style[^>]*>([\s\S]*?)<\/style>/gi, (match, styleContent) => {
|
||||
styleTagsContent.push(styleContent);
|
||||
return ''; // Remove style tags temporarily
|
||||
});
|
||||
}
|
||||
|
||||
// Process the HTML content
|
||||
processedHtml = processedHtml
|
||||
// Remove potentially harmful elements and attributes
|
||||
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
||||
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
|
||||
.replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, '')
|
||||
.replace(/<embed\b[^<]*(?:(?!<\/embed>)<[^<]*)*<\/embed>/gi, '')
|
||||
.replace(/<form\b[^<]*(?:(?!<\/form>)<[^<]*)*<\/form>/gi, '')
|
||||
.replace(/on\w+="[^"]*"/gi, '') // Remove inline event handlers (onclick, onload, etc.)
|
||||
.replace(/on\w+='[^']*'/gi, '')
|
||||
// Fix self-closing tags that might break React
|
||||
.replace(/<(br|hr|img|input|link|meta|area|base|col|embed|keygen|param|source|track|wbr)([^>]*)>/gi, '<$1$2 />');
|
||||
|
||||
// If we're scoping styles, prefix classes
|
||||
if (defaultOptions.scopeStyles) {
|
||||
processedHtml = processedHtml.replace(/class=["']([^"']*)["']/gi, (match, classContent) => {
|
||||
const classes = classContent.split(/\s+/).map((cls: string) => `email-forwarded-${cls}`).join(' ');
|
||||
return `class="${classes}"`;
|
||||
});
|
||||
}
|
||||
|
||||
// Add scoped styles if needed
|
||||
if (defaultOptions.preserveStyles && styleTagsContent.length > 0 && defaultOptions.scopeStyles) {
|
||||
// Create a modified version of the styles that scope them to our container
|
||||
const scopedStyles = styleTagsContent.map(style => {
|
||||
// Replace CSS selectors to be scoped to our container
|
||||
return style
|
||||
// Add scope to class selectors
|
||||
.replace(/(\.[a-zA-Z0-9_-]+)/g, '.email-forwarded-$1')
|
||||
// Add scope to ID selectors
|
||||
.replace(/(#[a-zA-Z0-9_-]+)/g, '#email-forwarded-$1')
|
||||
// Fix any CSS that might break out
|
||||
.replace(/@import/g, '/* @import */')
|
||||
.replace(/@media/g, '/* @media */');
|
||||
}).join('\n');
|
||||
|
||||
// Add the styles back in a scoped way
|
||||
if (defaultOptions.addWrapper) {
|
||||
return `
|
||||
<div class="email-forwarded-content" style="position: relative; overflow: auto; max-width: 100%;">
|
||||
<style type="text/css">
|
||||
/* Scoped styles for forwarded email */
|
||||
.email-forwarded-content {
|
||||
/* Base containment */
|
||||
font-family: Arial, sans-serif;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
/* Original email styles (scoped) */
|
||||
${scopedStyles}
|
||||
</style>
|
||||
${processedHtml}
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
return `<style type="text/css">${scopedStyles}</style>${processedHtml}`;
|
||||
}
|
||||
}
|
||||
|
||||
// Just wrap the content if needed
|
||||
if (defaultOptions.addWrapper) {
|
||||
return `<div class="email-forwarded-content" style="position: relative; overflow: auto; max-width: 100%;">${processedHtml}</div>`;
|
||||
}
|
||||
|
||||
return processedHtml;
|
||||
} catch (error) {
|
||||
console.error('Error cleaning HTML:', error);
|
||||
// Return something safe in case of error
|
||||
return `<div style="color: #666; font-style: italic;">Error processing HTML content</div>`;
|
||||
}
|
||||
}
|
||||
@ -1,22 +1,20 @@
|
||||
import { sanitizeHtml } from '@/lib/utils/email-formatter';
|
||||
import { simpleParser } from 'mailparser';
|
||||
import { cleanHtml as cleanHtmlCentralized } from '@/lib/mail-parser-wrapper';
|
||||
|
||||
/**
|
||||
* @deprecated This function is deprecated and will be removed in future versions.
|
||||
* Use the centralized cleanHtml function from '@/lib/mail-parser-wrapper.ts instead.
|
||||
* This is maintained only for backward compatibility.
|
||||
*/
|
||||
export function cleanHtml(html: string): string {
|
||||
console.warn('The cleanHtml function in lib/server/email-parser.ts is deprecated. Use the one in lib/mail-parser-wrapper.ts');
|
||||
return cleanHtmlCentralized(html, { preserveStyles: true, scopeStyles: false });
|
||||
}
|
||||
|
||||
function getAddressText(address: any): string | null {
|
||||
if (!address) return null;
|
||||
if (Array.isArray(address)) {
|
||||
return address.map(addr => addr.value?.[0]?.address || '').filter(Boolean).join(', ');
|
||||
function getAddressText(addresses: any): string | null {
|
||||
if (!addresses) return null;
|
||||
|
||||
try {
|
||||
if (Array.isArray(addresses)) {
|
||||
return addresses.map(a => a.address || '').join(', ');
|
||||
} else if (typeof addresses === 'object') {
|
||||
return addresses.address || null;
|
||||
}
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Error formatting addresses:', error);
|
||||
return null;
|
||||
}
|
||||
return address.value?.[0]?.address || null;
|
||||
}
|
||||
|
||||
export async function parseEmail(emailContent: string) {
|
||||
@ -30,10 +28,7 @@ export async function parseEmail(emailContent: string) {
|
||||
cc: getAddressText(parsed.cc),
|
||||
bcc: getAddressText(parsed.bcc),
|
||||
date: parsed.date || null,
|
||||
html: parsed.html ? cleanHtmlCentralized(parsed.html as string, {
|
||||
preserveStyles: true,
|
||||
scopeStyles: false
|
||||
}) : null,
|
||||
html: parsed.html ? sanitizeHtml(parsed.html as string) : null,
|
||||
text: parsed.text || null,
|
||||
attachments: parsed.attachments || [],
|
||||
headers: Object.fromEntries(parsed.headers)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user