11import { convert } from "html-to-text" ;
2+ import { HTML_PROCESSING } from "@/config/constants" ;
23
34/**
45 * Safely get the domain from an email address
@@ -9,14 +10,45 @@ export function getDomain(email: string): string {
910}
1011
1112/**
12- * Convert HTML to plain text
13+ * Sanitize HTML content by removing potentially dangerous elements
14+ */
15+ function sanitizeHtml ( html : string ) : string {
16+ // Basic HTML sanitization - remove script tags and event handlers
17+ return html
18+ . replace ( / < s c r i p t \b [ ^ < ] * (?: (? ! < \/ s c r i p t > ) < [ ^ < ] * ) * < \/ s c r i p t > / gi, "" )
19+ . replace ( / o n \w + \s * = \s * [ " ' ] [ ^ " ' ] * [ " ' ] / gi, "" )
20+ . replace ( / j a v a s c r i p t : / gi, "" )
21+ . replace ( / < i f r a m e \b [ ^ > ] * > / gi, "" )
22+ . replace ( / < o b j e c t \b [ ^ > ] * > / gi, "" )
23+ . replace ( / < e m b e d \b [ ^ > ] * > / gi, "" ) ;
24+ }
25+
26+ /**
27+ * Convert HTML to plain text with size limits and error handling
1328 */
1429function htmlToText ( html : string ) : string | null {
15- const text = convert ( html , {
16- wordwrap : 130 ,
17- } ) ;
30+ try {
31+ // Check size limit before processing
32+ if ( Buffer . byteLength ( html , "utf8" ) > HTML_PROCESSING . MAX_CONVERSION_SIZE ) {
33+ console . warn ( "HTML content too large for conversion, truncating" ) ;
34+ html = html . substring ( 0 , HTML_PROCESSING . MAX_CONVERSION_SIZE ) ;
35+ }
1836
19- return text . trim ( ) === "" ? null : text ;
37+ const text = convert ( html , {
38+ wordwrap : HTML_PROCESSING . WORDWRAP_LENGTH ,
39+ selectors : [
40+ // Remove potentially dangerous content
41+ { selector : "script" , format : "skip" } ,
42+ { selector : "style" , format : "skip" } ,
43+ { selector : "iframe" , format : "skip" } ,
44+ ] ,
45+ } ) ;
46+
47+ return text . trim ( ) === "" ? null : text ;
48+ } catch ( error ) {
49+ console . error ( "Failed to convert HTML to text:" , error ) ;
50+ return null ;
51+ }
2052}
2153
2254/**
@@ -31,7 +63,7 @@ function textToHtmlTemplate(text: string): string | null {
3163}
3264
3365/**
34- * Process email content
66+ * Process email content with sanitization and size validation
3567 */
3668export function processEmailContent (
3769 html : string | null ,
@@ -40,18 +72,21 @@ export function processEmailContent(
4072 htmlContent : string | null ;
4173 textContent : string | null ;
4274} {
43- // Both exist - return as-is
44- if ( html && text ) {
45- return { htmlContent : html , textContent : text } ;
75+ // Sanitize HTML content if present
76+ const sanitizedHtml = html ? sanitizeHtml ( html ) : null ;
77+
78+ // Both exist - return sanitized HTML and original text
79+ if ( sanitizedHtml && text ) {
80+ return { htmlContent : sanitizedHtml , textContent : text } ;
4681 }
4782
48- // Only HTML exists - generate text
49- if ( html && ! text ) {
50- return { htmlContent : html , textContent : htmlToText ( html ) } ;
83+ // Only HTML exists - generate text from sanitized HTML
84+ if ( sanitizedHtml && ! text ) {
85+ return { htmlContent : sanitizedHtml , textContent : htmlToText ( sanitizedHtml ) } ;
5186 }
5287
53- // Only text exists - generate HTML
54- if ( ! html && text ) {
88+ // Only text exists - generate HTML template
89+ if ( ! sanitizedHtml && text ) {
5590 return { htmlContent : textToHtmlTemplate ( text ) , textContent : text } ;
5691 }
5792
0 commit comments