@toast-ui/chart
Version:
TOAST UI Application: Chart
87 lines (86 loc) • 3.83 kB
JavaScript
import { findNodes, removeNode } from "./dom";
import { isString, toArray } from "./utils";
const HTML_ATTR_LIST_RX = new RegExp('^(abbr|align|alt|axis|bgcolor|border|cellpadding|cellspacing|class|clear|' +
'color|cols|compact|coords|dir|face|headers|height|hreflang|hspace|' +
'ismap|lang|language|nohref|nowrap|rel|rev|rows|rules|' +
'scope|scrolling|shape|size|span|start|summary|tabindex|target|title|type|' +
'valign|value|vspace|width|checked|mathvariant|encoding|id|name|' +
'background|cite|href|longdesc|src|usemap|xlink:href|data-+|checked|style)', 'g');
const SVG_ATTR_LIST_RX = new RegExp('^(accent-height|accumulate|additive|alphabetic|arabic-form|ascent|' +
'baseProfile|bbox|begin|by|calcMode|cap-height|class|color|color-rendering|content|' +
'cx|cy|d|dx|dy|descent|display|dur|end|fill|fill-rule|font-family|font-size|font-stretch|' +
'font-style|font-variant|font-weight|from|fx|fy|g1|g2|glyph-name|gradientUnits|hanging|' +
'height|horiz-adv-x|horiz-origin-x|ideographic|k|keyPoints|keySplines|keyTimes|lang|' +
'marker-end|marker-mid|marker-start|markerHeight|markerUnits|markerWidth|mathematical|' +
'max|min|offset|opacity|orient|origin|overline-position|overline-thickness|panose-1|' +
'path|pathLength|points|preserveAspectRatio|r|refX|refY|repeatCount|repeatDur|' +
'requiredExtensions|requiredFeatures|restart|rotate|rx|ry|slope|stemh|stemv|stop-color|' +
'stop-opacity|strikethrough-position|strikethrough-thickness|stroke|stroke-dasharray|' +
'stroke-dashoffset|stroke-linecap|stroke-linejoin|stroke-miterlimit|stroke-opacity|' +
'stroke-width|systemLanguage|target|text-anchor|to|transform|type|u1|u2|underline-position|' +
'underline-thickness|unicode|unicode-range|units-per-em|values|version|viewBox|visibility|' +
'width|widths|x|x-height|x1|x2|xlink:actuate|xlink:arcrole|xlink:role|xlink:show|xlink:title|' +
'xlink:type|xml:base|xml:lang|xml:space|xmlns|xmlns:xlink|y|y1|y2|zoomAndPan)', 'g');
const DEFAULT_TAG_DENY_LIST = [
'script',
'iframe',
'textarea',
'form',
'button',
'select',
'input',
'meta',
'style',
'link',
'title',
'embed',
'object',
];
const XSS_ATTR_RX = /href|src|background/gi;
const XSS_VALUE_RX = /((java|vb|live)script|x):/gi;
const ON_EVENT_RX = /^on\S+/;
export function sanitizeHTML(html) {
const root = document.createElement('div');
if (isString(html)) {
html = html.replace(/<!--[\s\S]*?-->/g, '');
root.innerHTML = html;
}
else {
root.appendChild(html);
}
removeUnnecessaryTags(root);
leaveOnlyWhitelistAttribute(root);
return root.innerHTML;
}
function removeUnnecessaryTags(html) {
const removedTags = findNodes(html, DEFAULT_TAG_DENY_LIST.join(','));
removedTags.forEach((node) => {
removeNode(node);
});
}
function isXSSAttribute(attrName, attrValue) {
return attrName.match(XSS_ATTR_RX) && attrValue.match(XSS_VALUE_RX);
}
function removeBlacklistAttributes(node, blacklistAttrs) {
blacklistAttrs.forEach(({ name }) => {
if (ON_EVENT_RX.test(name)) {
node[name] = null;
}
if (node.getAttribute(name)) {
node.removeAttribute(name);
}
});
}
function leaveOnlyWhitelistAttribute(html) {
findNodes(html, '*').forEach((node) => {
const { attributes } = node;
const blacklist = toArray(attributes).filter((attr) => {
const { name, value } = attr;
const htmlAttr = name.match(HTML_ATTR_LIST_RX);
const svgAttr = name.match(SVG_ATTR_LIST_RX);
const xssAttr = htmlAttr && isXSSAttribute(name, value);
return (!htmlAttr && !svgAttr) || xssAttr;
});
removeBlacklistAttributes(node, blacklist);
});
}