courrier clean 2$
This commit is contained in:
parent
5686b9fb7d
commit
26e72f4f73
@ -1,7 +1,6 @@
|
|||||||
import { NextRequest, NextResponse } from 'next/server';
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
import { simpleParser } from 'mailparser';
|
import { parseEmail } from '@/lib/server/email-parser';
|
||||||
import * as DOMPurify from 'isomorphic-dompurify';
|
import { sanitizeHtml } from '@/lib/utils/email-formatter';
|
||||||
import { cleanHtml as cleanHtmlCentralized } from '@/lib/mail-parser-wrapper';
|
|
||||||
|
|
||||||
interface EmailAddress {
|
interface EmailAddress {
|
||||||
name?: string;
|
name?: string;
|
||||||
@ -45,68 +44,29 @@ function getEmailAddresses(addresses: any): EmailAddress[] {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export async function POST(request: NextRequest) {
|
||||||
* @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) {
|
|
||||||
try {
|
try {
|
||||||
const { email } = await req.json();
|
const body = await request.json();
|
||||||
|
|
||||||
if (!email || typeof email !== 'string') {
|
if (!body.email) {
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Invalid email content' },
|
{ error: 'Missing email content' },
|
||||||
{ status: 400 }
|
{ status: 400 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const parsed = await simpleParser(email);
|
const parsedEmail = await parseEmail(body.email);
|
||||||
|
|
||||||
// Process the HTML content to make it safe and displayable
|
// Process HTML content if available
|
||||||
const html = parsed.html
|
if (parsedEmail.html) {
|
||||||
? processHtml(parsed.html.toString())
|
parsedEmail.html = sanitizeHtml(parsedEmail.html);
|
||||||
: undefined;
|
}
|
||||||
|
|
||||||
const text = parsed.text
|
return NextResponse.json(parsedEmail);
|
||||||
? 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
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing email:', error);
|
console.error('Error parsing email:', error);
|
||||||
return NextResponse.json(
|
return NextResponse.json(
|
||||||
{ error: 'Failed to parse email content' },
|
{ error: 'Failed to parse email', details: error instanceof Error ? error.message : String(error) },
|
||||||
{ status: 500 }
|
{ 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 {
|
interface EmailHeaders {
|
||||||
from: string;
|
from: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
@ -5,7 +12,12 @@ interface EmailHeaders {
|
|||||||
to?: string;
|
to?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use parseEmail from lib/server/email-parser.ts instead.
|
||||||
|
*/
|
||||||
export function parseEmailHeaders(headerContent: string): EmailHeaders {
|
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 } = {};
|
const headers: { [key: string]: string } = {};
|
||||||
let currentHeader = '';
|
let currentHeader = '';
|
||||||
let currentValue = '';
|
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 {
|
export function decodeEmailBody(content: string, contentType: string): string {
|
||||||
|
console.warn('decodeEmailBody is deprecated. Use sanitizeHtml from lib/utils/email-formatter.ts instead.');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Remove email client-specific markers
|
// Remove email client-specific markers
|
||||||
content = content.replace(/\r\n/g, '\n')
|
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 {
|
function extractTextFromHtml(html: string): string {
|
||||||
|
console.warn('extractTextFromHtml is deprecated. Use sanitizeHtml from lib/utils/email-formatter.ts instead.');
|
||||||
|
|
||||||
// Remove scripts and style tags
|
// Remove scripts and style tags
|
||||||
html = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
html = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, '')
|
||||||
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
|
.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, '');
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
|
import { sanitizeHtml } from '@/lib/utils/email-formatter';
|
||||||
|
|
||||||
export interface ParsedEmail {
|
export interface ParsedEmail {
|
||||||
subject: string | null;
|
subject: string | null;
|
||||||
@ -100,118 +101,3 @@ export async function decodeEmail(emailContent: string): Promise<ParsedEmail> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 { simpleParser } from 'mailparser';
|
||||||
import { cleanHtml as cleanHtmlCentralized } from '@/lib/mail-parser-wrapper';
|
|
||||||
|
|
||||||
/**
|
function getAddressText(addresses: any): string | null {
|
||||||
* @deprecated This function is deprecated and will be removed in future versions.
|
if (!addresses) return null;
|
||||||
* 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 {
|
try {
|
||||||
if (!address) return null;
|
if (Array.isArray(addresses)) {
|
||||||
if (Array.isArray(address)) {
|
return addresses.map(a => a.address || '').join(', ');
|
||||||
return address.map(addr => addr.value?.[0]?.address || '').filter(Boolean).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) {
|
export async function parseEmail(emailContent: string) {
|
||||||
@ -30,10 +28,7 @@ export async function parseEmail(emailContent: string) {
|
|||||||
cc: getAddressText(parsed.cc),
|
cc: getAddressText(parsed.cc),
|
||||||
bcc: getAddressText(parsed.bcc),
|
bcc: getAddressText(parsed.bcc),
|
||||||
date: parsed.date || null,
|
date: parsed.date || null,
|
||||||
html: parsed.html ? cleanHtmlCentralized(parsed.html as string, {
|
html: parsed.html ? sanitizeHtml(parsed.html as string) : null,
|
||||||
preserveStyles: true,
|
|
||||||
scopeStyles: false
|
|
||||||
}) : null,
|
|
||||||
text: parsed.text || null,
|
text: parsed.text || null,
|
||||||
attachments: parsed.attachments || [],
|
attachments: parsed.attachments || [],
|
||||||
headers: Object.fromEntries(parsed.headers)
|
headers: Object.fromEntries(parsed.headers)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user