Initial commit
This commit is contained in:
160
src/utile/emailSanitizer.ts
Normal file
160
src/utile/emailSanitizer.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import DOMPurify from 'dompurify'
|
||||
|
||||
export enum SecurityLevel {
|
||||
STRICT = 'strict',
|
||||
MODERATE = 'moderate',
|
||||
RELAXED = 'relaxed'
|
||||
}
|
||||
|
||||
export interface SanitizationOptions {
|
||||
securityLevel?: SecurityLevel
|
||||
allowImages?: boolean
|
||||
allowExternalLinks?: boolean
|
||||
allowStyles?: boolean
|
||||
}
|
||||
|
||||
type SanitizerConfig = {
|
||||
ALLOWED_TAGS?: string[]
|
||||
ALLOWED_ATTR?: string[]
|
||||
ALLOWED_URI_REGEXP?: RegExp
|
||||
ALLOW_DATA_ATTR?: boolean
|
||||
FORBID_TAGS?: string[]
|
||||
FORBID_ATTR?: string[]
|
||||
KEEP_CONTENT?: boolean
|
||||
RETURN_TRUSTED_TYPE?: boolean
|
||||
}
|
||||
|
||||
export class EmailSanitizer {
|
||||
private static readonly STRICT_CONFIG: SanitizerConfig = {
|
||||
ALLOWED_TAGS: [
|
||||
'p', 'br', 'strong', 'em', 'u', 'ul', 'ol', 'li', 'blockquote',
|
||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'span', 'b', 'i'
|
||||
],
|
||||
ALLOWED_ATTR: ['title'],
|
||||
ALLOW_DATA_ATTR: false,
|
||||
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form', 'input', 'button', 'img', 'style', 'link', 'base', 'meta', 'svg', 'math'],
|
||||
FORBID_ATTR: ['style', 'onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout', 'onmousemove', 'onmouseenter', 'onmouseleave'],
|
||||
KEEP_CONTENT: true,
|
||||
RETURN_TRUSTED_TYPE: false
|
||||
}
|
||||
|
||||
private static readonly MODERATE_CONFIG: SanitizerConfig = {
|
||||
ALLOWED_TAGS: [
|
||||
'p', 'br', 'strong', 'em', 'u', 'a', 'ul', 'ol', 'li', 'blockquote',
|
||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'span', 'table', 'thead',
|
||||
'tbody', 'tr', 'td', 'th', 'b', 'i', 'hr', 'pre', 'code', 'center',
|
||||
'font', 'small', 'big', 'sub', 'sup'
|
||||
],
|
||||
ALLOWED_ATTR: [
|
||||
'href', 'title', 'class', 'colspan', 'rowspan', 'align', 'valign',
|
||||
'style', 'color', 'bgcolor', 'width', 'height', 'border', 'cellpadding',
|
||||
'cellspacing', 'size', 'face'
|
||||
],
|
||||
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
|
||||
ALLOW_DATA_ATTR: false,
|
||||
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form', 'input', 'button', 'img', 'style', 'link', 'base', 'meta'],
|
||||
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout', 'onmousemove', 'onmouseenter', 'onmouseleave'],
|
||||
KEEP_CONTENT: true,
|
||||
RETURN_TRUSTED_TYPE: false
|
||||
}
|
||||
|
||||
private static readonly RELAXED_CONFIG: SanitizerConfig = {
|
||||
ALLOWED_TAGS: [
|
||||
'p', 'br', 'strong', 'em', 'u', 'a', 'ul', 'ol', 'li', 'blockquote',
|
||||
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'div', 'span', 'table', 'thead',
|
||||
'tbody', 'tr', 'td', 'th', 'b', 'i', 'hr', 'pre', 'code', 'center',
|
||||
'font', 'small', 'big', 'sub', 'sup', 'img', 'picture', 'source'
|
||||
],
|
||||
ALLOWED_ATTR: [
|
||||
'href', 'title', 'class', 'colspan', 'rowspan', 'align', 'valign',
|
||||
'style', 'color', 'bgcolor', 'width', 'height', 'border', 'cellpadding',
|
||||
'cellspacing', 'size', 'face', 'src', 'alt', 'srcset', 'loading'
|
||||
],
|
||||
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|data|cid):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i,
|
||||
ALLOW_DATA_ATTR: false,
|
||||
FORBID_TAGS: ['script', 'iframe', 'object', 'embed', 'form', 'input', 'button', 'style', 'link', 'base', 'meta'],
|
||||
FORBID_ATTR: ['onerror', 'onload', 'onclick', 'onmouseover', 'onmouseout', 'onmousemove', 'onmouseenter', 'onmouseleave'],
|
||||
KEEP_CONTENT: true,
|
||||
RETURN_TRUSTED_TYPE: false
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize HTML email content based on security level and options
|
||||
*/
|
||||
static sanitize(html: string, options: SanitizationOptions = {}): string {
|
||||
const {
|
||||
securityLevel = SecurityLevel.MODERATE,
|
||||
allowImages = false,
|
||||
allowExternalLinks = true,
|
||||
allowStyles = true
|
||||
} = options
|
||||
|
||||
// Get base configuration
|
||||
let config: SanitizerConfig
|
||||
switch (securityLevel) {
|
||||
case SecurityLevel.STRICT:
|
||||
config = { ...this.STRICT_CONFIG }
|
||||
break
|
||||
case SecurityLevel.RELAXED:
|
||||
config = { ...this.RELAXED_CONFIG }
|
||||
break
|
||||
case SecurityLevel.MODERATE:
|
||||
default:
|
||||
config = { ...this.MODERATE_CONFIG }
|
||||
}
|
||||
|
||||
// Adjust configuration based on options
|
||||
if (allowImages && config.ALLOWED_TAGS && config.ALLOWED_ATTR) {
|
||||
// Add image tags
|
||||
if (!config.ALLOWED_TAGS.includes('img')) {
|
||||
config.ALLOWED_TAGS.push('img', 'picture', 'source')
|
||||
}
|
||||
|
||||
// Add image attributes
|
||||
const imageAttrs = ['src', 'alt', 'srcset', 'loading']
|
||||
imageAttrs.forEach(attr => {
|
||||
if (!config.ALLOWED_ATTR!.includes(attr)) {
|
||||
config.ALLOWED_ATTR!.push(attr)
|
||||
}
|
||||
})
|
||||
|
||||
// Allow data URIs and CID references
|
||||
config.ALLOWED_URI_REGEXP = /^(?:(?:(?:f|ht)tps?|mailto|tel|data|cid):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i
|
||||
|
||||
// Remove img from forbidden tags
|
||||
if (config.FORBID_TAGS) {
|
||||
config.FORBID_TAGS = config.FORBID_TAGS.filter((tag: string) => tag !== 'img')
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowExternalLinks && config.ALLOWED_TAGS) {
|
||||
// Remove anchor tags
|
||||
config.ALLOWED_TAGS = config.ALLOWED_TAGS.filter((tag: string) => tag !== 'a')
|
||||
}
|
||||
|
||||
if (!allowStyles && config.ALLOWED_ATTR) {
|
||||
// Remove style-related attributes
|
||||
config.ALLOWED_ATTR = config.ALLOWED_ATTR.filter(
|
||||
(attr: string) => !['style', 'color', 'bgcolor'].includes(attr)
|
||||
)
|
||||
}
|
||||
|
||||
return DOMPurify.sanitize(html, config) as string
|
||||
}
|
||||
|
||||
/**
|
||||
* Get security level description
|
||||
*/
|
||||
static getSecurityLevelDescription(level: SecurityLevel): string {
|
||||
switch (level) {
|
||||
case SecurityLevel.STRICT:
|
||||
return 'Maximum security - Only basic text formatting allowed'
|
||||
case SecurityLevel.MODERATE:
|
||||
return 'Balanced security - Allows formatting, links, and tables (recommended)'
|
||||
case SecurityLevel.RELAXED:
|
||||
return 'Minimal filtering - Allows most content including images'
|
||||
default:
|
||||
return ''
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user