diff --git a/dist/purify.cjs.js b/dist/purify.cjs.js index 2ea51c14..94d1f75c 100644 --- a/dist/purify.cjs.js +++ b/dist/purify.cjs.js @@ -11,21 +11,17 @@ function _typeof(obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } - function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; - return _setPrototypeOf(o, p); } - function _isNativeReflectConstruct() { if (typeof Reflect === "undefined" || !Reflect.construct) return false; if (Reflect.construct.sham) return false; if (typeof Proxy === "function") return true; - try { Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); return true; @@ -33,7 +29,6 @@ function _isNativeReflectConstruct() { return false; } } - function _construct(Parent, args, Class) { if (_isNativeReflectConstruct()) { _construct = Reflect.construct; @@ -47,22 +42,17 @@ function _construct(Parent, args, Class) { return instance; }; } - return _construct.apply(null, arguments); } - function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } - function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } - function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } - function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); @@ -71,56 +61,46 @@ function _unsupportedIterableToArray(o, minLen) { if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } - function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; - for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; - return arr2; } - function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var hasOwnProperty = Object.hasOwnProperty, - setPrototypeOf = Object.setPrototypeOf, - isFrozen = Object.isFrozen, - getPrototypeOf = Object.getPrototypeOf, - getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; + setPrototypeOf = Object.setPrototypeOf, + isFrozen = Object.isFrozen, + getPrototypeOf = Object.getPrototypeOf, + getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; var freeze = Object.freeze, - seal = Object.seal, - create = Object.create; // eslint-disable-line import/no-mutable-exports - + seal = Object.seal, + create = Object.create; // eslint-disable-line import/no-mutable-exports var _ref = typeof Reflect !== 'undefined' && Reflect, - apply = _ref.apply, - construct = _ref.construct; - + apply = _ref.apply, + construct = _ref.construct; if (!apply) { apply = function apply(fun, thisValue, args) { return fun.apply(thisValue, args); }; } - if (!freeze) { freeze = function freeze(x) { return x; }; } - if (!seal) { seal = function seal(x) { return x; }; } - if (!construct) { construct = function construct(Func, args) { return _construct(Func, _toConsumableArray(args)); }; } - var arrayForEach = unapply(Array.prototype.forEach); var arrayPop = unapply(Array.prototype.pop); var arrayPush = unapply(Array.prototype.push); @@ -137,7 +117,6 @@ function unapply(func) { for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { args[_key - 1] = arguments[_key]; } - return apply(func, thisArg, args); }; } @@ -146,103 +125,89 @@ function unconstruct(func) { for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { args[_key2] = arguments[_key2]; } - return construct(func, args); }; } -/* Add properties to a lookup table */ +/* Add properties to a lookup table */ function addToSet(set, array, transformCaseFunc) { var _transformCaseFunc; - transformCaseFunc = (_transformCaseFunc = transformCaseFunc) !== null && _transformCaseFunc !== void 0 ? _transformCaseFunc : stringToLowerCase; - if (setPrototypeOf) { // Make 'in' and truthy checks like Boolean(set.constructor) // independent of any properties defined on Object.prototype. // Prevent prototype setters from intercepting set as a this value. setPrototypeOf(set, null); } - var l = array.length; - while (l--) { var element = array[l]; - if (typeof element === 'string') { var lcElement = transformCaseFunc(element); - if (lcElement !== element) { // Config presets (e.g. tags.js, attrs.js) are immutable. if (!isFrozen(array)) { array[l] = lcElement; } - element = lcElement; } } - set[element] = true; } - return set; } -/* Shallow clone an object */ +/* Shallow clone an object */ function clone(object) { var newObject = create(null); var property; - for (property in object) { if (apply(hasOwnProperty, object, [property]) === true) { newObject[property] = object[property]; } } - return newObject; } + /* IE10 doesn't support __lookupGetter__ so lets' * simulate it. It also automatically checks * if the prop is function or getter and behaves * accordingly. */ - function lookupGetter(object, prop) { while (object !== null) { var desc = getOwnPropertyDescriptor(object, prop); - if (desc) { if (desc.get) { return unapply(desc.get); } - if (typeof desc.value === 'function') { return unapply(desc.value); } } - object = getPrototypeOf(object); } - function fallbackValue(element) { console.warn('fallback value for', element); return null; } - return fallbackValue; } -var html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']); // SVG +var html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']); +// SVG var svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']); -var svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']); // List of SVG elements that are disallowed by default. +var svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']); + +// List of SVG elements that are disallowed by default. // We still need to know them so that we can do namespace // checks properly in case one wants to add them to // allow-list. - var svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'fedropshadow', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']); -var mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover']); // Similarly to SVG, we want to know all MathML elements, -// even those that we disallow by default. +var mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover']); +// Similarly to SVG, we want to know all MathML elements, +// even those that we disallow by default. var mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']); var text = freeze(['#text']); @@ -251,24 +216,26 @@ var svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline var mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']); var xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']); +// eslint-disable-next-line unicorn/better-regex var MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode - var ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm); var TMPLIT_EXPR = seal(/\${[\w\W]*}/gm); var DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape - var ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape - var IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape ); + var IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i); var ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex ); + var DOCTYPE_NAME = seal(/^html$/i); +var CUSTOM_ELEMENT = seal(/^[a-z][a-z\d]*(-[a-z\d]+)+$/i); var getGlobal = function getGlobal() { return typeof window === 'undefined' ? null : window; }; + /** * Creates a no-op policy for internal use only. * Don't export this function outside this module! @@ -277,25 +244,20 @@ var getGlobal = function getGlobal() { * @return {?TrustedTypePolicy} The policy created (or null, if Trusted Types * are not supported). */ - - var _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, document) { if (_typeof(trustedTypes) !== 'object' || typeof trustedTypes.createPolicy !== 'function') { return null; - } // Allow the callers to control the unique policy name + } + + // Allow the callers to control the unique policy name // by adding a data-tt-policy-suffix to the script element with the DOMPurify. // Policy creation with duplicate names throws in Trusted Types. - - var suffix = null; var ATTR_NAME = 'data-tt-policy-suffix'; - if (document.currentScript && document.currentScript.hasAttribute(ATTR_NAME)) { suffix = document.currentScript.getAttribute(ATTR_NAME); } - var policyName = 'dompurify' + (suffix ? '#' + suffix : ''); - try { return trustedTypes.createPolicy(policyName, { createHTML: function createHTML(html) { @@ -313,114 +275,106 @@ var _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, return null; } }; - function createDOMPurify() { var window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal(); - var DOMPurify = function DOMPurify(root) { return createDOMPurify(root); }; + /** * Version label, exposed for easier checks * if DOMPurify is up to date or not */ - - DOMPurify.version = '2.4.7'; + /** * Array of elements that DOMPurify removed during sanitation. * Empty if nothing was removed. */ - DOMPurify.removed = []; - if (!window || !window.document || window.document.nodeType !== 9) { // Not running in a browser, provide a factory function // so that you can pass your own Window DOMPurify.isSupported = false; return DOMPurify; } - var originalDocument = window.document; var document = window.document; var DocumentFragment = window.DocumentFragment, - HTMLTemplateElement = window.HTMLTemplateElement, - Node = window.Node, - Element = window.Element, - NodeFilter = window.NodeFilter, - _window$NamedNodeMap = window.NamedNodeMap, - NamedNodeMap = _window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap, - HTMLFormElement = window.HTMLFormElement, - DOMParser = window.DOMParser, - trustedTypes = window.trustedTypes; + HTMLTemplateElement = window.HTMLTemplateElement, + Node = window.Node, + Element = window.Element, + NodeFilter = window.NodeFilter, + _window$NamedNodeMap = window.NamedNodeMap, + NamedNodeMap = _window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap, + HTMLFormElement = window.HTMLFormElement, + DOMParser = window.DOMParser, + trustedTypes = window.trustedTypes; var ElementPrototype = Element.prototype; var cloneNode = lookupGetter(ElementPrototype, 'cloneNode'); var getNextSibling = lookupGetter(ElementPrototype, 'nextSibling'); var getChildNodes = lookupGetter(ElementPrototype, 'childNodes'); - var getParentNode = lookupGetter(ElementPrototype, 'parentNode'); // As per issue #47, the web-components registry is inherited by a + var getParentNode = lookupGetter(ElementPrototype, 'parentNode'); + + // As per issue #47, the web-components registry is inherited by a // new document created via createHTMLDocument. As per the spec // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries) // a new empty registry is used when creating a template contents owner // document, so we use that as our parent document to ensure nothing // is inherited. - if (typeof HTMLTemplateElement === 'function') { var template = document.createElement('template'); - if (template.content && template.content.ownerDocument) { document = template.content.ownerDocument; } } - var trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, originalDocument); - var emptyHTML = trustedTypesPolicy ? trustedTypesPolicy.createHTML('') : ''; var _document = document, - implementation = _document.implementation, - createNodeIterator = _document.createNodeIterator, - createDocumentFragment = _document.createDocumentFragment, - getElementsByTagName = _document.getElementsByTagName; + implementation = _document.implementation, + createNodeIterator = _document.createNodeIterator, + createDocumentFragment = _document.createDocumentFragment, + getElementsByTagName = _document.getElementsByTagName; var importNode = originalDocument.importNode; var documentMode = {}; - try { documentMode = clone(document).documentMode ? document.documentMode : {}; } catch (_) {} - var hooks = {}; + /** * Expose whether this browser supports running the full DOMPurify. */ - DOMPurify.isSupported = typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined && documentMode !== 9; var MUSTACHE_EXPR$1 = MUSTACHE_EXPR, - ERB_EXPR$1 = ERB_EXPR, - TMPLIT_EXPR$1 = TMPLIT_EXPR, - DATA_ATTR$1 = DATA_ATTR, - ARIA_ATTR$1 = ARIA_ATTR, - IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA, - ATTR_WHITESPACE$1 = ATTR_WHITESPACE; + ERB_EXPR$1 = ERB_EXPR, + TMPLIT_EXPR$1 = TMPLIT_EXPR, + DATA_ATTR$1 = DATA_ATTR, + ARIA_ATTR$1 = ARIA_ATTR, + IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA, + ATTR_WHITESPACE$1 = ATTR_WHITESPACE, + CUSTOM_ELEMENT$1 = CUSTOM_ELEMENT; var IS_ALLOWED_URI$1 = IS_ALLOWED_URI; + /** * We consider the elements and attributes below to be safe. Ideally * don't add any new ones but feel free to remove unwanted ones. */ /* allowed element names */ - var ALLOWED_TAGS = null; var DEFAULT_ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray(html$1), _toConsumableArray(svg$1), _toConsumableArray(svgFilters), _toConsumableArray(mathMl$1), _toConsumableArray(text))); - /* Allowed attribute names */ + /* Allowed attribute names */ var ALLOWED_ATTR = null; var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray(html), _toConsumableArray(svg), _toConsumableArray(mathMl), _toConsumableArray(xml))); + /* * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements. * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements) * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list) * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`. */ - var CUSTOM_ELEMENT_HANDLING = Object.seal(Object.create(null, { tagNameCheck: { writable: true, @@ -441,59 +395,60 @@ function createDOMPurify() { value: false } })); - /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */ + /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */ var FORBID_TAGS = null; - /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */ + /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */ var FORBID_ATTR = null; - /* Decide if ARIA attributes are okay */ + /* Decide if ARIA attributes are okay */ var ALLOW_ARIA_ATTR = true; - /* Decide if custom data attributes are okay */ + /* Decide if custom data attributes are okay */ var ALLOW_DATA_ATTR = true; - /* Decide if unknown protocols are okay */ + /* Decide if unknown protocols are okay */ var ALLOW_UNKNOWN_PROTOCOLS = false; + /* Decide if self-closing tags in attributes are allowed. * Usually removed due to a mXSS issue in jQuery 3.0 */ - var ALLOW_SELF_CLOSE_IN_ATTR = true; + /* Output should be safe for common template engines. * This means, DOMPurify removes data attributes, mustaches and ERB */ - var SAFE_FOR_TEMPLATES = false; - /* Decide if document with ... should be returned */ + /* Decide if document with ... should be returned */ var WHOLE_DOCUMENT = false; - /* Track whether config is already set on this instance of DOMPurify. */ + /* Track whether config is already set on this instance of DOMPurify. */ var SET_CONFIG = false; + /* Decide if all elements (e.g. style, script) must be children of * document.body. By default, browsers might move them to document.head */ - var FORCE_BODY = false; + /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html * string (or a TrustedHTML object if Trusted Types are supported). * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead */ - var RETURN_DOM = false; + /* Decide if a DOM `DocumentFragment` should be returned, instead of a html * string (or a TrustedHTML object if Trusted Types are supported) */ - var RETURN_DOM_FRAGMENT = false; + /* Try to return a Trusted Type object instead of a string, return a string in * case Trusted Types are not supported */ - var RETURN_TRUSTED_TYPE = false; + /* Output should be free from DOM clobbering attacks? * This sanitizes markups named with colliding, clobberable built-in DOM APIs. */ - var SANITIZE_DOM = true; + /* Achieve full DOM Clobbering protection by isolating the namespace of named * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules. * @@ -507,98 +462,98 @@ function createDOMPurify() { * Namespace isolation is implemented by prefixing `id` and `name` attributes * with a constant string, i.e., `user-content-` */ - var SANITIZE_NAMED_PROPS = false; var SANITIZE_NAMED_PROPS_PREFIX = 'user-content-'; - /* Keep element content when removing element? */ + /* Keep element content when removing element? */ var KEEP_CONTENT = true; + /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead * of importing it into a new Document and returning a sanitized copy */ - var IN_PLACE = false; - /* Allow usage of profiles like html, svg and mathMl */ + /* Allow usage of profiles like html, svg and mathMl */ var USE_PROFILES = {}; - /* Tags to ignore content of when KEEP_CONTENT is true */ + /* Tags to ignore content of when KEEP_CONTENT is true */ var FORBID_CONTENTS = null; var DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']); - /* Tags that are safe for data: URIs */ + /* Tags that are safe for data: URIs */ var DATA_URI_TAGS = null; var DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']); - /* Attributes safe for values like "javascript:" */ + /* Attributes safe for values like "javascript:" */ var URI_SAFE_ATTRIBUTES = null; var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']); var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML'; var SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; /* Document namespace */ - var NAMESPACE = HTML_NAMESPACE; var IS_EMPTY_INPUT = false; - /* Allowed XHTML+XML namespaces */ + /* Allowed XHTML+XML namespaces */ var ALLOWED_NAMESPACES = null; var DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString); - /* Parsing of strict XHTML documents */ + /* Parsing of strict XHTML documents */ var PARSER_MEDIA_TYPE; var SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html']; var DEFAULT_PARSER_MEDIA_TYPE = 'text/html'; var transformCaseFunc; - /* Keep a reference to config to pass to hooks */ + /* Keep a reference to config to pass to hooks */ var CONFIG = null; - /* Ideally, do not touch anything below this line */ + /* Ideally, do not touch anything below this line */ /* ______________________________________________ */ var formElement = document.createElement('form'); - var isRegexOrFunction = function isRegexOrFunction(testValue) { return testValue instanceof RegExp || testValue instanceof Function; }; + /** * _parseConfig * * @param {Object} cfg optional config literal */ // eslint-disable-next-line complexity - - var _parseConfig = function _parseConfig(cfg) { if (CONFIG && CONFIG === cfg) { return; } - /* Shield configuration object from tampering */ - + /* Shield configuration object from tampering */ if (!cfg || _typeof(cfg) !== 'object') { cfg = {}; } - /* Shield configuration object from prototype pollution */ - + /* Shield configuration object from prototype pollution */ cfg = clone(cfg); - PARSER_MEDIA_TYPE = // eslint-disable-next-line unicorn/prefer-includes - SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE : PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE; // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is. + PARSER_MEDIA_TYPE = + // eslint-disable-next-line unicorn/prefer-includes + SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE : PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE; + // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is. transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase; - /* Set configuration parameters */ + /* Set configuration parameters */ ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS; ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR; ALLOWED_NAMESPACES = 'ALLOWED_NAMESPACES' in cfg ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES; - URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), // eslint-disable-line indent - cfg.ADD_URI_SAFE_ATTR, // eslint-disable-line indent + URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), + // eslint-disable-line indent + cfg.ADD_URI_SAFE_ATTR, + // eslint-disable-line indent transformCaseFunc // eslint-disable-line indent ) // eslint-disable-line indent : DEFAULT_URI_SAFE_ATTRIBUTES; - DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), // eslint-disable-line indent - cfg.ADD_DATA_URI_TAGS, // eslint-disable-line indent + DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), + // eslint-disable-line indent + cfg.ADD_DATA_URI_TAGS, + // eslint-disable-line indent transformCaseFunc // eslint-disable-line indent ) // eslint-disable-line indent : DEFAULT_DATA_URI_TAGS; @@ -607,161 +562,127 @@ function createDOMPurify() { FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {}; USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false; ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true - ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true - ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false - ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true - SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false - WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false - RETURN_DOM = cfg.RETURN_DOM || false; // Default false - RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false - RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false - FORCE_BODY = cfg.FORCE_BODY || false; // Default false - SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true - SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false - KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true - IN_PLACE = cfg.IN_PLACE || false; // Default false - IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$1; NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE; CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {}; - if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) { CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck; } - if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) { CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck; } - if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') { CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements; } - if (SAFE_FOR_TEMPLATES) { ALLOW_DATA_ATTR = false; } - if (RETURN_DOM_FRAGMENT) { RETURN_DOM = true; } - /* Parse profile info */ - + /* Parse profile info */ if (USE_PROFILES) { ALLOWED_TAGS = addToSet({}, _toConsumableArray(text)); ALLOWED_ATTR = []; - if (USE_PROFILES.html === true) { addToSet(ALLOWED_TAGS, html$1); addToSet(ALLOWED_ATTR, html); } - if (USE_PROFILES.svg === true) { addToSet(ALLOWED_TAGS, svg$1); addToSet(ALLOWED_ATTR, svg); addToSet(ALLOWED_ATTR, xml); } - if (USE_PROFILES.svgFilters === true) { addToSet(ALLOWED_TAGS, svgFilters); addToSet(ALLOWED_ATTR, svg); addToSet(ALLOWED_ATTR, xml); } - if (USE_PROFILES.mathMl === true) { addToSet(ALLOWED_TAGS, mathMl$1); addToSet(ALLOWED_ATTR, mathMl); addToSet(ALLOWED_ATTR, xml); } } - /* Merge configuration parameters */ - + /* Merge configuration parameters */ if (cfg.ADD_TAGS) { if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) { ALLOWED_TAGS = clone(ALLOWED_TAGS); } - addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc); } - if (cfg.ADD_ATTR) { if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) { ALLOWED_ATTR = clone(ALLOWED_ATTR); } - addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc); } - if (cfg.ADD_URI_SAFE_ATTR) { addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc); } - if (cfg.FORBID_CONTENTS) { if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) { FORBID_CONTENTS = clone(FORBID_CONTENTS); } - addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc); } - /* Add #text in case KEEP_CONTENT is set to true */ - + /* Add #text in case KEEP_CONTENT is set to true */ if (KEEP_CONTENT) { ALLOWED_TAGS['#text'] = true; } - /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */ - + /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */ if (WHOLE_DOCUMENT) { addToSet(ALLOWED_TAGS, ['html', 'head', 'body']); } - /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */ - + /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */ if (ALLOWED_TAGS.table) { addToSet(ALLOWED_TAGS, ['tbody']); delete FORBID_TAGS.tbody; - } // Prevent further manipulation of configuration. - // Not available in IE8, Safari 5, etc. - + } + // Prevent further manipulation of configuration. + // Not available in IE8, Safari 5, etc. if (freeze) { freeze(cfg); } - CONFIG = cfg; }; - var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']); - var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']); // Certain elements are allowed in both SVG and HTML + var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']); + + // Certain elements are allowed in both SVG and HTML // namespace. We need to specify them explicitly // so that they don't get erroneously deleted from // HTML namespace. - var COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']); + /* Keep track of all possible SVG and MathML tags * so that we can perform the namespace checks * correctly. */ - var ALL_SVG_TAGS = addToSet({}, svg$1); addToSet(ALL_SVG_TAGS, svgFilters); addToSet(ALL_SVG_TAGS, svgDisallowed); var ALL_MATHML_TAGS = addToSet({}, mathMl$1); addToSet(ALL_MATHML_TAGS, mathMlDisallowed); + /** * * @@ -770,64 +691,59 @@ function createDOMPurify() { * namespace that a spec-compliant parser would never * return. Return true otherwise. */ - var _checkValidNamespace = function _checkValidNamespace(element) { - var parent = getParentNode(element); // In JSDOM, if we're inside shadow DOM, then parentNode - // can be null. We just simulate parent in this case. + var parent = getParentNode(element); + // In JSDOM, if we're inside shadow DOM, then parentNode + // can be null. We just simulate parent in this case. if (!parent || !parent.tagName) { parent = { namespaceURI: NAMESPACE, tagName: 'template' }; } - var tagName = stringToLowerCase(element.tagName); var parentTagName = stringToLowerCase(parent.tagName); - if (!ALLOWED_NAMESPACES[element.namespaceURI]) { return false; } - if (element.namespaceURI === SVG_NAMESPACE) { // The only way to switch from HTML namespace to SVG // is via . If it happens via any other tag, then // it should be killed. if (parent.namespaceURI === HTML_NAMESPACE) { return tagName === 'svg'; - } // The only way to switch from MathML to SVG is via` + } + + // The only way to switch from MathML to SVG is via` // svg if parent is either or MathML // text integration points. - - if (parent.namespaceURI === MATHML_NAMESPACE) { return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]); - } // We only allow elements that are defined in SVG - // spec. All others are disallowed in SVG namespace. - + } + // We only allow elements that are defined in SVG + // spec. All others are disallowed in SVG namespace. return Boolean(ALL_SVG_TAGS[tagName]); } - if (element.namespaceURI === MATHML_NAMESPACE) { // The only way to switch from HTML namespace to MathML // is via . If it happens via any other tag, then // it should be killed. if (parent.namespaceURI === HTML_NAMESPACE) { return tagName === 'math'; - } // The only way to switch from SVG to MathML is via - // and HTML integration points - + } + // The only way to switch from SVG to MathML is via + // and HTML integration points if (parent.namespaceURI === SVG_NAMESPACE) { return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName]; - } // We only allow elements that are defined in MathML - // spec. All others are disallowed in MathML namespace. - + } + // We only allow elements that are defined in MathML + // spec. All others are disallowed in MathML namespace. return Boolean(ALL_MATHML_TAGS[tagName]); } - if (element.namespaceURI === HTML_NAMESPACE) { // The only way to switch from SVG to HTML is via // HTML integration points, and from MathML to HTML @@ -835,39 +751,36 @@ function createDOMPurify() { if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) { return false; } - if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) { return false; - } // We disallow tags that are specific for MathML - // or SVG and should never appear in HTML namespace - + } + // We disallow tags that are specific for MathML + // or SVG and should never appear in HTML namespace return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]); - } // For XHTML and XML documents that support custom namespaces - + } + // For XHTML and XML documents that support custom namespaces if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) { return true; - } // The code should never reach this place (this means + } + + // The code should never reach this place (this means // that the element somehow got namespace that is not // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES). // Return false just in case. - - return false; }; + /** * _forceRemove * * @param {Node} node a DOM node */ - - var _forceRemove = function _forceRemove(node) { arrayPush(DOMPurify.removed, { element: node }); - try { // eslint-disable-next-line unicorn/prefer-dom-node-remove node.parentNode.removeChild(node); @@ -879,14 +792,13 @@ function createDOMPurify() { } } }; + /** * _removeAttribute * * @param {String} name an Attribute name * @param {Node} node a DOM node */ - - var _removeAttribute = function _removeAttribute(name, node) { try { arrayPush(DOMPurify.removed, { @@ -899,9 +811,9 @@ function createDOMPurify() { from: node }); } + node.removeAttribute(name); - node.removeAttribute(name); // We void attribute values for unremovable "is"" attributes - + // We void attribute values for unremovable "is"" attributes if (name === 'is' && !ALLOWED_ATTR[name]) { if (RETURN_DOM || RETURN_DOM_FRAGMENT) { try { @@ -914,19 +826,17 @@ function createDOMPurify() { } } }; + /** * _initDocument * * @param {String} dirty a string of dirty markup * @return {Document} a DOM, filled with the dirty markup */ - - var _initDocument = function _initDocument(dirty) { /* Create a HTML document */ var doc; var leadingWhitespace; - if (FORCE_BODY) { dirty = '' + dirty; } else { @@ -934,83 +844,74 @@ function createDOMPurify() { var matches = stringMatch(dirty, /^[\r\n\t ]+/); leadingWhitespace = matches && matches[0]; } - if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) { // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict) dirty = '' + dirty + ''; } - var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty; /* * Use the DOMParser API by default, fallback later if needs be * DOMParser not work for svg when has multiple root element. */ - if (NAMESPACE === HTML_NAMESPACE) { try { doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE); } catch (_) {} } - /* Use createHTMLDocument in case DOMParser is not available */ - + /* Use createHTMLDocument in case DOMParser is not available */ if (!doc || !doc.documentElement) { doc = implementation.createDocument(NAMESPACE, 'template', null); - try { doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload; - } catch (_) {// Syntax error if dirtyPayload is invalid xml + } catch (_) { + // Syntax error if dirtyPayload is invalid xml } } - var body = doc.body || doc.documentElement; - if (dirty && leadingWhitespace) { body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null); } - /* Work on whole document or just its body */ - + /* Work on whole document or just its body */ if (NAMESPACE === HTML_NAMESPACE) { return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0]; } - return WHOLE_DOCUMENT ? doc.documentElement : body; }; + /** * _createIterator * * @param {Document} root document/fragment to create iterator for * @return {Iterator} iterator instance */ - - var _createIterator = function _createIterator(root) { - return createNodeIterator.call(root.ownerDocument || root, root, // eslint-disable-next-line no-bitwise - NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null, false); + return createNodeIterator.call(root.ownerDocument || root, root, + // eslint-disable-next-line no-bitwise + NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION, null, false); }; + /** * _isClobbered * * @param {Node} elm element to check for clobbering attacks * @return {Boolean} true if clobbered, false if safe */ - - var _isClobbered = function _isClobbered(elm) { return elm instanceof HTMLFormElement && (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function' || typeof elm.hasChildNodes !== 'function'); }; + /** * _isNode * * @param {Node} obj object to check whether it's a DOM node * @return {Boolean} true is object is a DOM node */ - - var _isNode = function _isNode(object) { return _typeof(Node) === 'object' ? object instanceof Node : object && _typeof(object) === 'object' && typeof object.nodeType === 'number' && typeof object.nodeName === 'string'; }; + /** * _executeHook * Execute user configurable hooks @@ -1019,17 +920,15 @@ function createDOMPurify() { * @param {Node} currentNode node to work on with the hook * @param {Object} data additional hook parameters */ - - var _executeHook = function _executeHook(entryPoint, currentNode, data) { if (!hooks[entryPoint]) { return; } - arrayForEach(hooks[entryPoint], function (hook) { hook.call(DOMPurify, currentNode, data, CONFIG); }); }; + /** * _sanitizeElements * @@ -1040,110 +939,87 @@ function createDOMPurify() { * @param {Node} currentNode to check for permission to exist * @return {Boolean} true if node was killed, false if left alive */ - - var _sanitizeElements = function _sanitizeElements(currentNode) { var content; - /* Execute a hook if present */ + /* Execute a hook if present */ _executeHook('beforeSanitizeElements', currentNode, null); - /* Check if element is clobbered or can clobber */ - + /* Check if element is clobbered or can clobber */ if (_isClobbered(currentNode)) { _forceRemove(currentNode); - return true; } - /* Check if tagname contains Unicode */ - + /* Check if tagname contains Unicode */ if (regExpTest(/[\u0080-\uFFFF]/, currentNode.nodeName)) { _forceRemove(currentNode); - return true; } - /* Now let's check the element's type and name */ - + /* Now let's check the element's type and name */ var tagName = transformCaseFunc(currentNode.nodeName); - /* Execute a hook if present */ + /* Execute a hook if present */ _executeHook('uponSanitizeElement', currentNode, { tagName: tagName, allowedTags: ALLOWED_TAGS }); - /* Detect mXSS attempts abusing namespace confusion */ - + /* Detect mXSS attempts abusing namespace confusion */ if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) { _forceRemove(currentNode); - return true; } - /* Mitigate a problem with templates inside select */ - + /* Mitigate a problem with templates inside select */ if (tagName === 'select' && regExpTest(/