mirror of
https://github.com/Foundry376/Mailspring.git
synced 2024-12-25 09:32:33 +08:00
Switch to DOMPurify for HTML email sanitization
This commit is contained in:
parent
6ab1b642b7
commit
8b4f59ba49
5 changed files with 257 additions and 529 deletions
11
app/package-lock.json
generated
11
app/package-lock.json
generated
|
@ -21,6 +21,7 @@
|
|||
"collapse-whitespace": "^1.1.6",
|
||||
"debug": "github:emorikawa/debug#nylas",
|
||||
"deep-extend": "0.6.0",
|
||||
"dompurify": "^3.0.8",
|
||||
"emoji-data": "^0.2.0",
|
||||
"enzyme": "^3.8.0",
|
||||
"enzyme-adapter-react-16": "^1.9.0",
|
||||
|
@ -1154,6 +1155,11 @@
|
|||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dompurify": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.8.tgz",
|
||||
"integrity": "sha512-b7uwreMYL2eZhrSCRC4ahLTeZcPZxSmYfmcQGXGkXiZSNW1X85v+SDM5KsWcpivIiUBH47Ji7NtyUdpLeF5JZQ=="
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
|
||||
|
@ -5937,6 +5943,11 @@
|
|||
"domelementtype": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"dompurify": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.0.8.tgz",
|
||||
"integrity": "sha512-b7uwreMYL2eZhrSCRC4ahLTeZcPZxSmYfmcQGXGkXiZSNW1X85v+SDM5KsWcpivIiUBH47Ji7NtyUdpLeF5JZQ=="
|
||||
},
|
||||
"domutils": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz",
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
"license": "GPL-3.0",
|
||||
"main": "./src/browser/main.js",
|
||||
"dependencies": {
|
||||
"app-module-path": "^2.2.0",
|
||||
"@bengotow/slate-edit-list": "github:bengotow/slate-edit-list#b868e108",
|
||||
"@electron/remote": "^2.0.9",
|
||||
"app-module-path": "^2.2.0",
|
||||
"better-sqlite3": "^8.0.1",
|
||||
"cheerio": "^1.0.0-rc.6",
|
||||
"chromium-net-errors": "1.0.3",
|
||||
|
@ -23,6 +23,7 @@
|
|||
"collapse-whitespace": "^1.1.6",
|
||||
"debug": "github:emorikawa/debug#nylas",
|
||||
"deep-extend": "0.6.0",
|
||||
"dompurify": "^3.0.8",
|
||||
"emoji-data": "^0.2.0",
|
||||
"enzyme": "^3.8.0",
|
||||
"enzyme-adapter-react-16": "^1.9.0",
|
||||
|
|
|
@ -50,7 +50,7 @@ class DraftFactory {
|
|||
// Be sure to match over multiple lines with [\s\S]*
|
||||
// Regex explanation here: https://regex101.com/r/vO6eN2/1
|
||||
let transformed = (content || '').replace(cidRegexp, '');
|
||||
transformed = await SanitizeTransformer.run(transformed, SanitizeTransformer.Preset.UnsafeOnly);
|
||||
transformed = await SanitizeTransformer.run(transformed);
|
||||
transformed = await InlineStyleTransformer.run(transformed);
|
||||
return transformed;
|
||||
}
|
||||
|
@ -259,8 +259,8 @@ class DraftFactory {
|
|||
</div>
|
||||
`
|
||||
: `\n\n---------- ${localized('Forwarded Message')} ---------\n\n${fields.join(
|
||||
'\n'
|
||||
)}\n\n${body}`,
|
||||
'\n'
|
||||
)}\n\n${body}`,
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -168,7 +168,7 @@ class MessageBodyProcessor {
|
|||
// Sanitizing <script> tags, etc. isn't necessary because we use CORS rules
|
||||
// to prevent their execution and sandbox content in the iFrame, but we still
|
||||
// want to remove contenteditable attributes and other strange things.
|
||||
body = await SanitizeTransformer.run(body, SanitizeTransformer.Preset.UnsafeOnly);
|
||||
body = await SanitizeTransformer.run(body);
|
||||
|
||||
for (const extension of MessageStore.extensions()) {
|
||||
if (!extension.formatMessageBody) {
|
||||
|
|
|
@ -1,534 +1,250 @@
|
|||
/* eslint-disable no-prototype-builtins */
|
||||
/*
|
||||
The sanitizer implementation here is loosely based on
|
||||
https://www.quaxio.com/html_white_listed_sanitizer/
|
||||
*/
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
const asMap = arr => {
|
||||
const obj = {};
|
||||
arr.forEach(k => (obj[k] = true));
|
||||
return obj;
|
||||
};
|
||||
|
||||
const sanitizeURL = function(val, rules) {
|
||||
if (!val) {
|
||||
return null;
|
||||
}
|
||||
if (rules.except && rules.except.find(scheme => val.startsWith(scheme))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return val;
|
||||
};
|
||||
|
||||
// https://stackoverflow.com/questions/2725156/complete-list-of-html-tag-attributes-which-have-a-url-value
|
||||
const AttributesContainingLinks = [
|
||||
'src',
|
||||
'codebase',
|
||||
const AllowedTags = [
|
||||
'#text',
|
||||
'a',
|
||||
'abbr',
|
||||
'address',
|
||||
'area',
|
||||
'article',
|
||||
'aside',
|
||||
'b',
|
||||
'bdi',
|
||||
'bdo',
|
||||
'big',
|
||||
'blockquote',
|
||||
'body',
|
||||
'br',
|
||||
'button',
|
||||
'canvas',
|
||||
'caption',
|
||||
'cite',
|
||||
'background',
|
||||
'longdesc',
|
||||
'profile',
|
||||
'usemap',
|
||||
'center',
|
||||
'code',
|
||||
'col',
|
||||
'colgroup',
|
||||
'data',
|
||||
'href',
|
||||
'action',
|
||||
'formaction',
|
||||
'icon',
|
||||
'manifest',
|
||||
'poster',
|
||||
'srcdoc',
|
||||
'srcset',
|
||||
'archive',
|
||||
'classid',
|
||||
'datalist',
|
||||
'dd',
|
||||
'del',
|
||||
'details',
|
||||
'dfn',
|
||||
'dialog',
|
||||
'div',
|
||||
'dl',
|
||||
'dt',
|
||||
'em',
|
||||
'fieldset',
|
||||
'figcaption',
|
||||
'figure',
|
||||
'footer',
|
||||
'font',
|
||||
'form',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'header',
|
||||
'hr',
|
||||
'i',
|
||||
'img',
|
||||
'input',
|
||||
'ins',
|
||||
'kbd',
|
||||
'keygen',
|
||||
'label',
|
||||
'legend',
|
||||
'li',
|
||||
'main',
|
||||
'map',
|
||||
'mark',
|
||||
'menu',
|
||||
'menuitem',
|
||||
'meta',
|
||||
'meter',
|
||||
'nav',
|
||||
'ol',
|
||||
'optgroup',
|
||||
'option',
|
||||
'output',
|
||||
'p',
|
||||
'param',
|
||||
'picture',
|
||||
'pre',
|
||||
'progress',
|
||||
'q',
|
||||
'rp',
|
||||
'rt',
|
||||
'ruby',
|
||||
's',
|
||||
'samp',
|
||||
'section',
|
||||
'select',
|
||||
'small',
|
||||
'source',
|
||||
'span',
|
||||
'strong',
|
||||
'style',
|
||||
'strike',
|
||||
'sub',
|
||||
'summary',
|
||||
'sup',
|
||||
'table',
|
||||
'tbody',
|
||||
'td',
|
||||
'textarea',
|
||||
'tfoot',
|
||||
'th',
|
||||
'thead',
|
||||
'time',
|
||||
'title',
|
||||
'tr',
|
||||
'track',
|
||||
'u',
|
||||
'ul',
|
||||
'var',
|
||||
'wbr',
|
||||
'html',
|
||||
];
|
||||
|
||||
const NodesWithNonTextContent = asMap(['script', 'style', 'iframe', 'object', 'meta']);
|
||||
|
||||
const Preset = {
|
||||
PasteFragment: {
|
||||
fragment: true,
|
||||
allowedTags: asMap([
|
||||
'p',
|
||||
'b',
|
||||
'i',
|
||||
'em',
|
||||
'u',
|
||||
's',
|
||||
'strong',
|
||||
'center',
|
||||
'a',
|
||||
'br',
|
||||
'img',
|
||||
'ul',
|
||||
'ol',
|
||||
'li',
|
||||
'strike',
|
||||
'table',
|
||||
'tr',
|
||||
'td',
|
||||
'th',
|
||||
'col',
|
||||
'colgroup',
|
||||
'div',
|
||||
'html',
|
||||
'font',
|
||||
]),
|
||||
allowedSchemes: {
|
||||
default: { except: ['file:'] },
|
||||
},
|
||||
allowedAttributes: {
|
||||
default: asMap([
|
||||
'abbr',
|
||||
'accept',
|
||||
'acceptcharset',
|
||||
'accesskey',
|
||||
'action',
|
||||
'align',
|
||||
'alt',
|
||||
'async',
|
||||
'autocomplete',
|
||||
'axis',
|
||||
'border',
|
||||
'background',
|
||||
'bgcolor',
|
||||
'cellpadding',
|
||||
'cellspacing',
|
||||
'char',
|
||||
'charoff',
|
||||
'charset',
|
||||
'checked',
|
||||
'class',
|
||||
'classid',
|
||||
'classname',
|
||||
'color',
|
||||
'colspan',
|
||||
'cols',
|
||||
'content',
|
||||
'contextmenu',
|
||||
'controls',
|
||||
'coords',
|
||||
'data-overlay-id',
|
||||
'data-component-props',
|
||||
'data-component-key',
|
||||
'data',
|
||||
'datetime',
|
||||
'defer',
|
||||
'dir',
|
||||
'disabled',
|
||||
'download',
|
||||
'draggable',
|
||||
'enctype',
|
||||
'face',
|
||||
'form',
|
||||
'formaction',
|
||||
'formenctype',
|
||||
'formmethod',
|
||||
'formnovalidate',
|
||||
'formtarget',
|
||||
'frame',
|
||||
'frameborder',
|
||||
'headers',
|
||||
'height',
|
||||
'hidden',
|
||||
'high',
|
||||
'href',
|
||||
'hreflang',
|
||||
'htmlfor',
|
||||
'httpequiv',
|
||||
'icon',
|
||||
'id',
|
||||
'label',
|
||||
'lang',
|
||||
'list',
|
||||
'loop',
|
||||
'low',
|
||||
'manifest',
|
||||
'marginheight',
|
||||
'marginwidth',
|
||||
'max',
|
||||
'maxlength',
|
||||
'media',
|
||||
'mediagroup',
|
||||
'method',
|
||||
'min',
|
||||
'multiple',
|
||||
'muted',
|
||||
'name',
|
||||
'novalidate',
|
||||
'nowrap',
|
||||
'open',
|
||||
'optimum',
|
||||
'pattern',
|
||||
'placeholder',
|
||||
'poster',
|
||||
'preload',
|
||||
'radiogroup',
|
||||
'readonly',
|
||||
'rel',
|
||||
'required',
|
||||
'role',
|
||||
'rowspan',
|
||||
'rows',
|
||||
'rules',
|
||||
'sandbox',
|
||||
'scope',
|
||||
'scoped',
|
||||
'scrolling',
|
||||
'seamless',
|
||||
'selected',
|
||||
'shape',
|
||||
'size',
|
||||
'sizes',
|
||||
'sortable',
|
||||
'sorted',
|
||||
'span',
|
||||
'spellcheck',
|
||||
'src',
|
||||
'srcdoc',
|
||||
'srcset',
|
||||
'start',
|
||||
'step',
|
||||
'style',
|
||||
'summary',
|
||||
'tabindex',
|
||||
'target',
|
||||
'title',
|
||||
'translate',
|
||||
'type',
|
||||
'usemap',
|
||||
'valign',
|
||||
'value',
|
||||
'width',
|
||||
'wmode',
|
||||
]),
|
||||
},
|
||||
},
|
||||
|
||||
UnsafeOnly: {
|
||||
allowedTags: asMap([
|
||||
'a',
|
||||
'abbr',
|
||||
'address',
|
||||
'area',
|
||||
'article',
|
||||
'aside',
|
||||
'b',
|
||||
'bdi',
|
||||
'bdo',
|
||||
'big',
|
||||
'blockquote',
|
||||
'body',
|
||||
'br',
|
||||
'button',
|
||||
'canvas',
|
||||
'caption',
|
||||
'cite',
|
||||
'center',
|
||||
'code',
|
||||
'col',
|
||||
'colgroup',
|
||||
'data',
|
||||
'datalist',
|
||||
'dd',
|
||||
'del',
|
||||
'details',
|
||||
'dfn',
|
||||
'dialog',
|
||||
'div',
|
||||
'dl',
|
||||
'dt',
|
||||
'em',
|
||||
'fieldset',
|
||||
'figcaption',
|
||||
'figure',
|
||||
'footer',
|
||||
'font',
|
||||
'form',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'header',
|
||||
'hr',
|
||||
'i',
|
||||
'img',
|
||||
'input',
|
||||
'ins',
|
||||
'kbd',
|
||||
'keygen',
|
||||
'label',
|
||||
'legend',
|
||||
'li',
|
||||
'main',
|
||||
'map',
|
||||
'mark',
|
||||
'menu',
|
||||
'menuitem',
|
||||
'meta',
|
||||
'meter',
|
||||
'nav',
|
||||
'ol',
|
||||
'optgroup',
|
||||
'option',
|
||||
'output',
|
||||
'p',
|
||||
'param',
|
||||
'picture',
|
||||
'pre',
|
||||
'progress',
|
||||
'q',
|
||||
'rp',
|
||||
'rt',
|
||||
'ruby',
|
||||
's',
|
||||
'samp',
|
||||
'section',
|
||||
'select',
|
||||
'small',
|
||||
'source',
|
||||
'span',
|
||||
'strong',
|
||||
'style',
|
||||
'strike',
|
||||
'sub',
|
||||
'summary',
|
||||
'sup',
|
||||
'table',
|
||||
'tbody',
|
||||
'td',
|
||||
'textarea',
|
||||
'tfoot',
|
||||
'th',
|
||||
'thead',
|
||||
'time',
|
||||
'title',
|
||||
'tr',
|
||||
'track',
|
||||
'u',
|
||||
'ul',
|
||||
'var',
|
||||
'wbr',
|
||||
'html',
|
||||
]),
|
||||
allowedAttributes: {
|
||||
default: asMap([
|
||||
'abbr',
|
||||
'accept',
|
||||
'acceptcharset',
|
||||
'accesskey',
|
||||
'action',
|
||||
'align',
|
||||
'alt',
|
||||
'async',
|
||||
'autocomplete',
|
||||
'axis',
|
||||
'border',
|
||||
'background',
|
||||
'bgcolor',
|
||||
'cellpadding',
|
||||
'cellspacing',
|
||||
'char',
|
||||
'charoff',
|
||||
'charset',
|
||||
'checked',
|
||||
'classid',
|
||||
'class',
|
||||
'classname',
|
||||
'clear',
|
||||
'colspan',
|
||||
'cols',
|
||||
'color',
|
||||
'content',
|
||||
'contextmenu',
|
||||
'controls',
|
||||
'compact',
|
||||
'coords',
|
||||
'data',
|
||||
'datetime',
|
||||
'defer',
|
||||
'dir',
|
||||
'disabled',
|
||||
'download',
|
||||
'draggable',
|
||||
'enctype',
|
||||
'face',
|
||||
'form',
|
||||
'formaction',
|
||||
'formenctype',
|
||||
'formmethod',
|
||||
'formnovalidate',
|
||||
'formtarget',
|
||||
'frame',
|
||||
'frameborder',
|
||||
'headers',
|
||||
'height',
|
||||
'hidden',
|
||||
'high',
|
||||
'href',
|
||||
'hreflang',
|
||||
'htmlfor',
|
||||
'httpequiv',
|
||||
'hspace',
|
||||
'icon',
|
||||
'id',
|
||||
'label',
|
||||
'lang',
|
||||
'list',
|
||||
'loop',
|
||||
'low',
|
||||
'manifest',
|
||||
'marginheight',
|
||||
'marginwidth',
|
||||
'max',
|
||||
'maxlength',
|
||||
'media',
|
||||
'mediagroup',
|
||||
'method',
|
||||
'min',
|
||||
'multiple',
|
||||
'muted',
|
||||
'name',
|
||||
'novalidate',
|
||||
'nowrap',
|
||||
'noshade',
|
||||
'open',
|
||||
'optimum',
|
||||
'pattern',
|
||||
'placeholder',
|
||||
'poster',
|
||||
'preload',
|
||||
'radiogroup',
|
||||
'readonly',
|
||||
'rel',
|
||||
'required',
|
||||
'role',
|
||||
'rowspan',
|
||||
'rows',
|
||||
'rules',
|
||||
'sandbox',
|
||||
'scope',
|
||||
'scoped',
|
||||
'scrolling',
|
||||
'seamless',
|
||||
'selected',
|
||||
'shape',
|
||||
'size',
|
||||
'sizes',
|
||||
'start',
|
||||
'sortable',
|
||||
'sorted',
|
||||
'span',
|
||||
'spellcheck',
|
||||
'src',
|
||||
'srcdoc',
|
||||
'srcset',
|
||||
'start',
|
||||
'step',
|
||||
'style',
|
||||
'summary',
|
||||
'tabindex',
|
||||
'target',
|
||||
'title',
|
||||
'translate',
|
||||
'type',
|
||||
'usemap',
|
||||
'valign',
|
||||
'value',
|
||||
'vspace',
|
||||
'width',
|
||||
'wmode',
|
||||
]),
|
||||
},
|
||||
allowedSchemes: {
|
||||
default: { except: ['file:'] },
|
||||
},
|
||||
},
|
||||
};
|
||||
const AllowedAttributes = [
|
||||
'abbr',
|
||||
'accept',
|
||||
'acceptcharset',
|
||||
'accesskey',
|
||||
'action',
|
||||
'align',
|
||||
'alt',
|
||||
'async',
|
||||
'autocomplete',
|
||||
'axis',
|
||||
'border',
|
||||
'background',
|
||||
'bgcolor',
|
||||
'cellpadding',
|
||||
'cellspacing',
|
||||
'char',
|
||||
'charoff',
|
||||
'charset',
|
||||
'checked',
|
||||
'classid',
|
||||
'class',
|
||||
'classname',
|
||||
'clear',
|
||||
'colspan',
|
||||
'cols',
|
||||
'color',
|
||||
'content',
|
||||
'contextmenu',
|
||||
'controls',
|
||||
'compact',
|
||||
'coords',
|
||||
'data',
|
||||
'datetime',
|
||||
'defer',
|
||||
'dir',
|
||||
'disabled',
|
||||
'download',
|
||||
'draggable',
|
||||
'enctype',
|
||||
'face',
|
||||
'form',
|
||||
'formaction',
|
||||
'formenctype',
|
||||
'formmethod',
|
||||
'formnovalidate',
|
||||
'formtarget',
|
||||
'frame',
|
||||
'frameborder',
|
||||
'headers',
|
||||
'height',
|
||||
'hidden',
|
||||
'high',
|
||||
'href',
|
||||
'hreflang',
|
||||
'htmlfor',
|
||||
'httpequiv',
|
||||
'hspace',
|
||||
'icon',
|
||||
'id',
|
||||
'label',
|
||||
'lang',
|
||||
'list',
|
||||
'loop',
|
||||
'low',
|
||||
'manifest',
|
||||
'marginheight',
|
||||
'marginwidth',
|
||||
'max',
|
||||
'maxlength',
|
||||
'media',
|
||||
'mediagroup',
|
||||
'method',
|
||||
'min',
|
||||
'multiple',
|
||||
'muted',
|
||||
'name',
|
||||
'novalidate',
|
||||
'nowrap',
|
||||
'noshade',
|
||||
'open',
|
||||
'optimum',
|
||||
'pattern',
|
||||
'placeholder',
|
||||
'poster',
|
||||
'preload',
|
||||
'radiogroup',
|
||||
'readonly',
|
||||
'rel',
|
||||
'required',
|
||||
'role',
|
||||
'rowspan',
|
||||
'rows',
|
||||
'rules',
|
||||
'sandbox',
|
||||
'scope',
|
||||
'scoped',
|
||||
'scrolling',
|
||||
'seamless',
|
||||
'selected',
|
||||
'shape',
|
||||
'size',
|
||||
'sizes',
|
||||
'start',
|
||||
'sortable',
|
||||
'sorted',
|
||||
'span',
|
||||
'spellcheck',
|
||||
'src',
|
||||
'srcdoc',
|
||||
'srcset',
|
||||
'start',
|
||||
'step',
|
||||
'style',
|
||||
'summary',
|
||||
'tabindex',
|
||||
'target',
|
||||
'title',
|
||||
'translate',
|
||||
'type',
|
||||
'usemap',
|
||||
'valign',
|
||||
'value',
|
||||
'vspace',
|
||||
'width',
|
||||
'wmode',
|
||||
];
|
||||
|
||||
class SanitizeTransformer {
|
||||
Preset = Preset;
|
||||
|
||||
sanitizeNode(node: Element, settings) {
|
||||
const nodeName = node.nodeName.toLowerCase();
|
||||
if (nodeName === '#text') {
|
||||
return true; // text nodes are always safe, don't need to clone them
|
||||
}
|
||||
|
||||
if (nodeName === '#comment') {
|
||||
return false; // always strip comments
|
||||
}
|
||||
|
||||
if (!settings.allowedTags.hasOwnProperty(nodeName)) {
|
||||
// this node isn't allowed - what should we do with it?
|
||||
|
||||
// Nodes with non-text contents: completely remove them
|
||||
if (NodesWithNonTextContent.hasOwnProperty(nodeName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Nodes with text contents / no contents: replace with a `span` with the same children.
|
||||
// This allows us to ignore things like tables / table cells and still get their contents.
|
||||
const replacementNode = document.createElement('span');
|
||||
for (const child of Array.from(node.childNodes)) {
|
||||
replacementNode.appendChild(child);
|
||||
}
|
||||
node.parentNode.replaceChild(replacementNode, node);
|
||||
node = replacementNode;
|
||||
}
|
||||
|
||||
// identify the allowed attributes based on our settings
|
||||
let allowedForNodeName = settings.allowedAttributes.default;
|
||||
if (settings.allowedAttributes.hasOwnProperty(nodeName)) {
|
||||
allowedForNodeName = settings.allowedAttributes[nodeName];
|
||||
}
|
||||
|
||||
// remove and sanitize attributes using the whitelist / URL scheme rules
|
||||
for (let i = node.attributes.length - 1; i >= 0; i--) {
|
||||
const attr = node.attributes.item(i).name;
|
||||
if (!allowedForNodeName.hasOwnProperty(attr)) {
|
||||
node.removeAttribute(attr);
|
||||
continue;
|
||||
}
|
||||
if (AttributesContainingLinks.includes(attr)) {
|
||||
let rules = settings.allowedSchemes.default;
|
||||
if (settings.allowedSchemes.hasOwnProperty(nodeName)) {
|
||||
rules = settings.allowedSchemes[nodeName];
|
||||
}
|
||||
const sanitizedValue = sanitizeURL(node.getAttribute(attr), rules);
|
||||
if (sanitizedValue !== null) {
|
||||
node.setAttribute(attr, sanitizedValue);
|
||||
} else {
|
||||
node.removeAttribute(attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// recursively sanitize child nodes
|
||||
for (let i = node.childNodes.length - 1; i >= 0; i--) {
|
||||
if (!this.sanitizeNode(node.childNodes[i] as Element, settings)) {
|
||||
node.childNodes[i].remove();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async run(body, settings) {
|
||||
if (settings.fragment) {
|
||||
const doc = document.implementation.createHTMLDocument();
|
||||
const div = doc.createElement('div');
|
||||
div.innerHTML = body;
|
||||
this.sanitizeNode(div, settings);
|
||||
return div.innerHTML;
|
||||
} else {
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(body, 'text/html');
|
||||
this.sanitizeNode(doc.documentElement, settings);
|
||||
return doc.documentElement.innerHTML;
|
||||
}
|
||||
async run(bodyHTML: string) {
|
||||
return DOMPurify.sanitize(bodyHTML, {
|
||||
ALLOWED_TAGS: AllowedTags,
|
||||
ALLOWED_ATTR: AllowedAttributes,
|
||||
ALLOWED_URI_REGEXP: /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp|xxx):|[^a-z]|[a-z+.-]+(?:[^a-z+.\-:]|$))/i,
|
||||
KEEP_CONTENT: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue