UNPKG

60.3 kBJavaScriptView Raw
1/*! @license DOMPurify 2.3.10 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.10/LICENSE */
2
3'use strict';
4
5function _typeof(obj) {
6 "@babel/helpers - typeof";
7
8 return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) {
9 return typeof obj;
10 } : function (obj) {
11 return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
12 }, _typeof(obj);
13}
14
15function _setPrototypeOf(o, p) {
16 _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
17 o.__proto__ = p;
18 return o;
19 };
20
21 return _setPrototypeOf(o, p);
22}
23
24function _isNativeReflectConstruct() {
25 if (typeof Reflect === "undefined" || !Reflect.construct) return false;
26 if (Reflect.construct.sham) return false;
27 if (typeof Proxy === "function") return true;
28
29 try {
30 Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
31 return true;
32 } catch (e) {
33 return false;
34 }
35}
36
37function _construct(Parent, args, Class) {
38 if (_isNativeReflectConstruct()) {
39 _construct = Reflect.construct;
40 } else {
41 _construct = function _construct(Parent, args, Class) {
42 var a = [null];
43 a.push.apply(a, args);
44 var Constructor = Function.bind.apply(Parent, a);
45 var instance = new Constructor();
46 if (Class) _setPrototypeOf(instance, Class.prototype);
47 return instance;
48 };
49 }
50
51 return _construct.apply(null, arguments);
52}
53
54function _toConsumableArray(arr) {
55 return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread();
56}
57
58function _arrayWithoutHoles(arr) {
59 if (Array.isArray(arr)) return _arrayLikeToArray(arr);
60}
61
62function _iterableToArray(iter) {
63 if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
64}
65
66function _unsupportedIterableToArray(o, minLen) {
67 if (!o) return;
68 if (typeof o === "string") return _arrayLikeToArray(o, minLen);
69 var n = Object.prototype.toString.call(o).slice(8, -1);
70 if (n === "Object" && o.constructor) n = o.constructor.name;
71 if (n === "Map" || n === "Set") return Array.from(o);
72 if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
73}
74
75function _arrayLikeToArray(arr, len) {
76 if (len == null || len > arr.length) len = arr.length;
77
78 for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
79
80 return arr2;
81}
82
83function _nonIterableSpread() {
84 throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
85}
86
87var hasOwnProperty = Object.hasOwnProperty,
88 setPrototypeOf = Object.setPrototypeOf,
89 isFrozen = Object.isFrozen,
90 getPrototypeOf = Object.getPrototypeOf,
91 getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
92var freeze = Object.freeze,
93 seal = Object.seal,
94 create = Object.create; // eslint-disable-line import/no-mutable-exports
95
96var _ref = typeof Reflect !== 'undefined' && Reflect,
97 apply = _ref.apply,
98 construct = _ref.construct;
99
100if (!apply) {
101 apply = function apply(fun, thisValue, args) {
102 return fun.apply(thisValue, args);
103 };
104}
105
106if (!freeze) {
107 freeze = function freeze(x) {
108 return x;
109 };
110}
111
112if (!seal) {
113 seal = function seal(x) {
114 return x;
115 };
116}
117
118if (!construct) {
119 construct = function construct(Func, args) {
120 return _construct(Func, _toConsumableArray(args));
121 };
122}
123
124var arrayForEach = unapply(Array.prototype.forEach);
125var arrayPop = unapply(Array.prototype.pop);
126var arrayPush = unapply(Array.prototype.push);
127var stringToLowerCase = unapply(String.prototype.toLowerCase);
128var stringMatch = unapply(String.prototype.match);
129var stringReplace = unapply(String.prototype.replace);
130var stringIndexOf = unapply(String.prototype.indexOf);
131var stringTrim = unapply(String.prototype.trim);
132var regExpTest = unapply(RegExp.prototype.test);
133var typeErrorCreate = unconstruct(TypeError);
134function unapply(func) {
135 return function (thisArg) {
136 for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
137 args[_key - 1] = arguments[_key];
138 }
139
140 return apply(func, thisArg, args);
141 };
142}
143function unconstruct(func) {
144 return function () {
145 for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
146 args[_key2] = arguments[_key2];
147 }
148
149 return construct(func, args);
150 };
151}
152/* Add properties to a lookup table */
153
154function addToSet(set, array, transformCaseFunc) {
155 transformCaseFunc = transformCaseFunc ? transformCaseFunc : stringToLowerCase;
156
157 if (setPrototypeOf) {
158 // Make 'in' and truthy checks like Boolean(set.constructor)
159 // independent of any properties defined on Object.prototype.
160 // Prevent prototype setters from intercepting set as a this value.
161 setPrototypeOf(set, null);
162 }
163
164 var l = array.length;
165
166 while (l--) {
167 var element = array[l];
168
169 if (typeof element === 'string') {
170 var lcElement = transformCaseFunc(element);
171
172 if (lcElement !== element) {
173 // Config presets (e.g. tags.js, attrs.js) are immutable.
174 if (!isFrozen(array)) {
175 array[l] = lcElement;
176 }
177
178 element = lcElement;
179 }
180 }
181
182 set[element] = true;
183 }
184
185 return set;
186}
187/* Shallow clone an object */
188
189function clone(object) {
190 var newObject = create(null);
191 var property;
192
193 for (property in object) {
194 if (apply(hasOwnProperty, object, [property])) {
195 newObject[property] = object[property];
196 }
197 }
198
199 return newObject;
200}
201/* IE10 doesn't support __lookupGetter__ so lets'
202 * simulate it. It also automatically checks
203 * if the prop is function or getter and behaves
204 * accordingly. */
205
206function lookupGetter(object, prop) {
207 while (object !== null) {
208 var desc = getOwnPropertyDescriptor(object, prop);
209
210 if (desc) {
211 if (desc.get) {
212 return unapply(desc.get);
213 }
214
215 if (typeof desc.value === 'function') {
216 return unapply(desc.value);
217 }
218 }
219
220 object = getPrototypeOf(object);
221 }
222
223 function fallbackValue(element) {
224 console.warn('fallback value for', element);
225 return null;
226 }
227
228 return fallbackValue;
229}
230
231var 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
232
233var 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']);
234var 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.
235// We still need to know them so that we can do namespace
236// checks properly in case one wants to add them to
237// allow-list.
238
239var 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']);
240var 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,
241// even those that we disallow by default.
242
243var mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
244var text = freeze(['#text']);
245
246var html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'xmlns', 'slot']);
247var svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
248var 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']);
249var xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
250
251var MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
252
253var ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
254var DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape
255
256var ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
257
258var 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
259);
260var IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
261var ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
262);
263var DOCTYPE_NAME = seal(/^html$/i);
264
265var getGlobal = function getGlobal() {
266 return typeof window === 'undefined' ? null : window;
267};
268/**
269 * Creates a no-op policy for internal use only.
270 * Don't export this function outside this module!
271 * @param {?TrustedTypePolicyFactory} trustedTypes The policy factory.
272 * @param {Document} document The document object (to determine policy name suffix)
273 * @return {?TrustedTypePolicy} The policy created (or null, if Trusted Types
274 * are not supported).
275 */
276
277
278var _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, document) {
279 if (_typeof(trustedTypes) !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
280 return null;
281 } // Allow the callers to control the unique policy name
282 // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
283 // Policy creation with duplicate names throws in Trusted Types.
284
285
286 var suffix = null;
287 var ATTR_NAME = 'data-tt-policy-suffix';
288
289 if (document.currentScript && document.currentScript.hasAttribute(ATTR_NAME)) {
290 suffix = document.currentScript.getAttribute(ATTR_NAME);
291 }
292
293 var policyName = 'dompurify' + (suffix ? '#' + suffix : '');
294
295 try {
296 return trustedTypes.createPolicy(policyName, {
297 createHTML: function createHTML(html) {
298 return html;
299 },
300 createScriptURL: function createScriptURL(scriptUrl) {
301 return scriptUrl;
302 }
303 });
304 } catch (_) {
305 // Policy creation failed (most likely another DOMPurify script has
306 // already run). Skip creating the policy, as this will only cause errors
307 // if TT are enforced.
308 console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
309 return null;
310 }
311};
312
313function createDOMPurify() {
314 var window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
315
316 var DOMPurify = function DOMPurify(root) {
317 return createDOMPurify(root);
318 };
319 /**
320 * Version label, exposed for easier checks
321 * if DOMPurify is up to date or not
322 */
323
324
325 DOMPurify.version = '2.3.10';
326 /**
327 * Array of elements that DOMPurify removed during sanitation.
328 * Empty if nothing was removed.
329 */
330
331 DOMPurify.removed = [];
332
333 if (!window || !window.document || window.document.nodeType !== 9) {
334 // Not running in a browser, provide a factory function
335 // so that you can pass your own Window
336 DOMPurify.isSupported = false;
337 return DOMPurify;
338 }
339
340 var originalDocument = window.document;
341 var document = window.document;
342 var DocumentFragment = window.DocumentFragment,
343 HTMLTemplateElement = window.HTMLTemplateElement,
344 Node = window.Node,
345 Element = window.Element,
346 NodeFilter = window.NodeFilter,
347 _window$NamedNodeMap = window.NamedNodeMap,
348 NamedNodeMap = _window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
349 HTMLFormElement = window.HTMLFormElement,
350 DOMParser = window.DOMParser,
351 trustedTypes = window.trustedTypes;
352 var ElementPrototype = Element.prototype;
353 var cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
354 var getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
355 var getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
356 var getParentNode = lookupGetter(ElementPrototype, 'parentNode'); // As per issue #47, the web-components registry is inherited by a
357 // new document created via createHTMLDocument. As per the spec
358 // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
359 // a new empty registry is used when creating a template contents owner
360 // document, so we use that as our parent document to ensure nothing
361 // is inherited.
362
363 if (typeof HTMLTemplateElement === 'function') {
364 var template = document.createElement('template');
365
366 if (template.content && template.content.ownerDocument) {
367 document = template.content.ownerDocument;
368 }
369 }
370
371 var trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, originalDocument);
372
373 var emptyHTML = trustedTypesPolicy ? trustedTypesPolicy.createHTML('') : '';
374 var _document = document,
375 implementation = _document.implementation,
376 createNodeIterator = _document.createNodeIterator,
377 createDocumentFragment = _document.createDocumentFragment,
378 getElementsByTagName = _document.getElementsByTagName;
379 var importNode = originalDocument.importNode;
380 var documentMode = {};
381
382 try {
383 documentMode = clone(document).documentMode ? document.documentMode : {};
384 } catch (_) {}
385
386 var hooks = {};
387 /**
388 * Expose whether this browser supports running the full DOMPurify.
389 */
390
391 DOMPurify.isSupported = typeof getParentNode === 'function' && implementation && typeof implementation.createHTMLDocument !== 'undefined' && documentMode !== 9;
392 var MUSTACHE_EXPR$1 = MUSTACHE_EXPR,
393 ERB_EXPR$1 = ERB_EXPR,
394 DATA_ATTR$1 = DATA_ATTR,
395 ARIA_ATTR$1 = ARIA_ATTR,
396 IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA,
397 ATTR_WHITESPACE$1 = ATTR_WHITESPACE;
398 var IS_ALLOWED_URI$1 = IS_ALLOWED_URI;
399 /**
400 * We consider the elements and attributes below to be safe. Ideally
401 * don't add any new ones but feel free to remove unwanted ones.
402 */
403
404 /* allowed element names */
405
406 var ALLOWED_TAGS = null;
407 var DEFAULT_ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray(html$1), _toConsumableArray(svg$1), _toConsumableArray(svgFilters), _toConsumableArray(mathMl$1), _toConsumableArray(text)));
408 /* Allowed attribute names */
409
410 var ALLOWED_ATTR = null;
411 var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray(html), _toConsumableArray(svg), _toConsumableArray(mathMl), _toConsumableArray(xml)));
412 /*
413 * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.
414 * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
415 * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
416 * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
417 */
418
419 var CUSTOM_ELEMENT_HANDLING = Object.seal(Object.create(null, {
420 tagNameCheck: {
421 writable: true,
422 configurable: false,
423 enumerable: true,
424 value: null
425 },
426 attributeNameCheck: {
427 writable: true,
428 configurable: false,
429 enumerable: true,
430 value: null
431 },
432 allowCustomizedBuiltInElements: {
433 writable: true,
434 configurable: false,
435 enumerable: true,
436 value: false
437 }
438 }));
439 /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
440
441 var FORBID_TAGS = null;
442 /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
443
444 var FORBID_ATTR = null;
445 /* Decide if ARIA attributes are okay */
446
447 var ALLOW_ARIA_ATTR = true;
448 /* Decide if custom data attributes are okay */
449
450 var ALLOW_DATA_ATTR = true;
451 /* Decide if unknown protocols are okay */
452
453 var ALLOW_UNKNOWN_PROTOCOLS = false;
454 /* Output should be safe for common template engines.
455 * This means, DOMPurify removes data attributes, mustaches and ERB
456 */
457
458 var SAFE_FOR_TEMPLATES = false;
459 /* Decide if document with <html>... should be returned */
460
461 var WHOLE_DOCUMENT = false;
462 /* Track whether config is already set on this instance of DOMPurify. */
463
464 var SET_CONFIG = false;
465 /* Decide if all elements (e.g. style, script) must be children of
466 * document.body. By default, browsers might move them to document.head */
467
468 var FORCE_BODY = false;
469 /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
470 * string (or a TrustedHTML object if Trusted Types are supported).
471 * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
472 */
473
474 var RETURN_DOM = false;
475 /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
476 * string (or a TrustedHTML object if Trusted Types are supported) */
477
478 var RETURN_DOM_FRAGMENT = false;
479 /* Try to return a Trusted Type object instead of a string, return a string in
480 * case Trusted Types are not supported */
481
482 var RETURN_TRUSTED_TYPE = false;
483 /* Output should be free from DOM clobbering attacks? */
484
485 var SANITIZE_DOM = true;
486 /* Keep element content when removing element? */
487
488 var KEEP_CONTENT = true;
489 /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
490 * of importing it into a new Document and returning a sanitized copy */
491
492 var IN_PLACE = false;
493 /* Allow usage of profiles like html, svg and mathMl */
494
495 var USE_PROFILES = {};
496 /* Tags to ignore content of when KEEP_CONTENT is true */
497
498 var FORBID_CONTENTS = null;
499 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']);
500 /* Tags that are safe for data: URIs */
501
502 var DATA_URI_TAGS = null;
503 var DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
504 /* Attributes safe for values like "javascript:" */
505
506 var URI_SAFE_ATTRIBUTES = null;
507 var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
508 var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
509 var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
510 var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
511 /* Document namespace */
512
513 var NAMESPACE = HTML_NAMESPACE;
514 var IS_EMPTY_INPUT = false;
515 /* Parsing of strict XHTML documents */
516
517 var PARSER_MEDIA_TYPE;
518 var SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
519 var DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
520 var transformCaseFunc;
521 /* Keep a reference to config to pass to hooks */
522
523 var CONFIG = null;
524 /* Ideally, do not touch anything below this line */
525
526 /* ______________________________________________ */
527
528 var formElement = document.createElement('form');
529
530 var isRegexOrFunction = function isRegexOrFunction(testValue) {
531 return testValue instanceof RegExp || testValue instanceof Function;
532 };
533 /**
534 * _parseConfig
535 *
536 * @param {Object} cfg optional config literal
537 */
538 // eslint-disable-next-line complexity
539
540
541 var _parseConfig = function _parseConfig(cfg) {
542 if (CONFIG && CONFIG === cfg) {
543 return;
544 }
545 /* Shield configuration object from tampering */
546
547
548 if (!cfg || _typeof(cfg) !== 'object') {
549 cfg = {};
550 }
551 /* Shield configuration object from prototype pollution */
552
553
554 cfg = clone(cfg);
555 PARSER_MEDIA_TYPE = // eslint-disable-next-line unicorn/prefer-includes
556 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.
557
558 transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? function (x) {
559 return x;
560 } : stringToLowerCase;
561 /* Set configuration parameters */
562
563 ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
564 ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
565 URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), // eslint-disable-line indent
566 cfg.ADD_URI_SAFE_ATTR, // eslint-disable-line indent
567 transformCaseFunc // eslint-disable-line indent
568 ) // eslint-disable-line indent
569 : DEFAULT_URI_SAFE_ATTRIBUTES;
570 DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), // eslint-disable-line indent
571 cfg.ADD_DATA_URI_TAGS, // eslint-disable-line indent
572 transformCaseFunc // eslint-disable-line indent
573 ) // eslint-disable-line indent
574 : DEFAULT_DATA_URI_TAGS;
575 FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
576 FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {};
577 FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};
578 USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
579 ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
580
581 ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
582
583 ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
584
585 SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
586
587 WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
588
589 RETURN_DOM = cfg.RETURN_DOM || false; // Default false
590
591 RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
592
593 RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
594
595 FORCE_BODY = cfg.FORCE_BODY || false; // Default false
596
597 SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
598
599 KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
600
601 IN_PLACE = cfg.IN_PLACE || false; // Default false
602
603 IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$1;
604 NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
605
606 if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
607 CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
608 }
609
610 if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
611 CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
612 }
613
614 if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
615 CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
616 }
617
618 if (SAFE_FOR_TEMPLATES) {
619 ALLOW_DATA_ATTR = false;
620 }
621
622 if (RETURN_DOM_FRAGMENT) {
623 RETURN_DOM = true;
624 }
625 /* Parse profile info */
626
627
628 if (USE_PROFILES) {
629 ALLOWED_TAGS = addToSet({}, _toConsumableArray(text));
630 ALLOWED_ATTR = [];
631
632 if (USE_PROFILES.html === true) {
633 addToSet(ALLOWED_TAGS, html$1);
634 addToSet(ALLOWED_ATTR, html);
635 }
636
637 if (USE_PROFILES.svg === true) {
638 addToSet(ALLOWED_TAGS, svg$1);
639 addToSet(ALLOWED_ATTR, svg);
640 addToSet(ALLOWED_ATTR, xml);
641 }
642
643 if (USE_PROFILES.svgFilters === true) {
644 addToSet(ALLOWED_TAGS, svgFilters);
645 addToSet(ALLOWED_ATTR, svg);
646 addToSet(ALLOWED_ATTR, xml);
647 }
648
649 if (USE_PROFILES.mathMl === true) {
650 addToSet(ALLOWED_TAGS, mathMl$1);
651 addToSet(ALLOWED_ATTR, mathMl);
652 addToSet(ALLOWED_ATTR, xml);
653 }
654 }
655 /* Merge configuration parameters */
656
657
658 if (cfg.ADD_TAGS) {
659 if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
660 ALLOWED_TAGS = clone(ALLOWED_TAGS);
661 }
662
663 addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
664 }
665
666 if (cfg.ADD_ATTR) {
667 if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
668 ALLOWED_ATTR = clone(ALLOWED_ATTR);
669 }
670
671 addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
672 }
673
674 if (cfg.ADD_URI_SAFE_ATTR) {
675 addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
676 }
677
678 if (cfg.FORBID_CONTENTS) {
679 if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
680 FORBID_CONTENTS = clone(FORBID_CONTENTS);
681 }
682
683 addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
684 }
685 /* Add #text in case KEEP_CONTENT is set to true */
686
687
688 if (KEEP_CONTENT) {
689 ALLOWED_TAGS['#text'] = true;
690 }
691 /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
692
693
694 if (WHOLE_DOCUMENT) {
695 addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
696 }
697 /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
698
699
700 if (ALLOWED_TAGS.table) {
701 addToSet(ALLOWED_TAGS, ['tbody']);
702 delete FORBID_TAGS.tbody;
703 } // Prevent further manipulation of configuration.
704 // Not available in IE8, Safari 5, etc.
705
706
707 if (freeze) {
708 freeze(cfg);
709 }
710
711 CONFIG = cfg;
712 };
713
714 var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
715 var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']); // Certain elements are allowed in both SVG and HTML
716 // namespace. We need to specify them explicitly
717 // so that they don't get erroneously deleted from
718 // HTML namespace.
719
720 var COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
721 /* Keep track of all possible SVG and MathML tags
722 * so that we can perform the namespace checks
723 * correctly. */
724
725 var ALL_SVG_TAGS = addToSet({}, svg$1);
726 addToSet(ALL_SVG_TAGS, svgFilters);
727 addToSet(ALL_SVG_TAGS, svgDisallowed);
728 var ALL_MATHML_TAGS = addToSet({}, mathMl$1);
729 addToSet(ALL_MATHML_TAGS, mathMlDisallowed);
730 /**
731 *
732 *
733 * @param {Element} element a DOM element whose namespace is being checked
734 * @returns {boolean} Return false if the element has a
735 * namespace that a spec-compliant parser would never
736 * return. Return true otherwise.
737 */
738
739 var _checkValidNamespace = function _checkValidNamespace(element) {
740 var parent = getParentNode(element); // In JSDOM, if we're inside shadow DOM, then parentNode
741 // can be null. We just simulate parent in this case.
742
743 if (!parent || !parent.tagName) {
744 parent = {
745 namespaceURI: HTML_NAMESPACE,
746 tagName: 'template'
747 };
748 }
749
750 var tagName = stringToLowerCase(element.tagName);
751 var parentTagName = stringToLowerCase(parent.tagName);
752
753 if (element.namespaceURI === SVG_NAMESPACE) {
754 // The only way to switch from HTML namespace to SVG
755 // is via <svg>. If it happens via any other tag, then
756 // it should be killed.
757 if (parent.namespaceURI === HTML_NAMESPACE) {
758 return tagName === 'svg';
759 } // The only way to switch from MathML to SVG is via
760 // svg if parent is either <annotation-xml> or MathML
761 // text integration points.
762
763
764 if (parent.namespaceURI === MATHML_NAMESPACE) {
765 return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
766 } // We only allow elements that are defined in SVG
767 // spec. All others are disallowed in SVG namespace.
768
769
770 return Boolean(ALL_SVG_TAGS[tagName]);
771 }
772
773 if (element.namespaceURI === MATHML_NAMESPACE) {
774 // The only way to switch from HTML namespace to MathML
775 // is via <math>. If it happens via any other tag, then
776 // it should be killed.
777 if (parent.namespaceURI === HTML_NAMESPACE) {
778 return tagName === 'math';
779 } // The only way to switch from SVG to MathML is via
780 // <math> and HTML integration points
781
782
783 if (parent.namespaceURI === SVG_NAMESPACE) {
784 return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
785 } // We only allow elements that are defined in MathML
786 // spec. All others are disallowed in MathML namespace.
787
788
789 return Boolean(ALL_MATHML_TAGS[tagName]);
790 }
791
792 if (element.namespaceURI === HTML_NAMESPACE) {
793 // The only way to switch from SVG to HTML is via
794 // HTML integration points, and from MathML to HTML
795 // is via MathML text integration points
796 if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
797 return false;
798 }
799
800 if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
801 return false;
802 } // We disallow tags that are specific for MathML
803 // or SVG and should never appear in HTML namespace
804
805
806 return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
807 } // The code should never reach this place (this means
808 // that the element somehow got namespace that is not
809 // HTML, SVG or MathML). Return false just in case.
810
811
812 return false;
813 };
814 /**
815 * _forceRemove
816 *
817 * @param {Node} node a DOM node
818 */
819
820
821 var _forceRemove = function _forceRemove(node) {
822 arrayPush(DOMPurify.removed, {
823 element: node
824 });
825
826 try {
827 // eslint-disable-next-line unicorn/prefer-dom-node-remove
828 node.parentNode.removeChild(node);
829 } catch (_) {
830 try {
831 node.outerHTML = emptyHTML;
832 } catch (_) {
833 node.remove();
834 }
835 }
836 };
837 /**
838 * _removeAttribute
839 *
840 * @param {String} name an Attribute name
841 * @param {Node} node a DOM node
842 */
843
844
845 var _removeAttribute = function _removeAttribute(name, node) {
846 try {
847 arrayPush(DOMPurify.removed, {
848 attribute: node.getAttributeNode(name),
849 from: node
850 });
851 } catch (_) {
852 arrayPush(DOMPurify.removed, {
853 attribute: null,
854 from: node
855 });
856 }
857
858 node.removeAttribute(name); // We void attribute values for unremovable "is"" attributes
859
860 if (name === 'is' && !ALLOWED_ATTR[name]) {
861 if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
862 try {
863 _forceRemove(node);
864 } catch (_) {}
865 } else {
866 try {
867 node.setAttribute(name, '');
868 } catch (_) {}
869 }
870 }
871 };
872 /**
873 * _initDocument
874 *
875 * @param {String} dirty a string of dirty markup
876 * @return {Document} a DOM, filled with the dirty markup
877 */
878
879
880 var _initDocument = function _initDocument(dirty) {
881 /* Create a HTML document */
882 var doc;
883 var leadingWhitespace;
884
885 if (FORCE_BODY) {
886 dirty = '<remove></remove>' + dirty;
887 } else {
888 /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
889 var matches = stringMatch(dirty, /^[\r\n\t ]+/);
890 leadingWhitespace = matches && matches[0];
891 }
892
893 if (PARSER_MEDIA_TYPE === 'application/xhtml+xml') {
894 // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
895 dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
896 }
897
898 var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
899 /*
900 * Use the DOMParser API by default, fallback later if needs be
901 * DOMParser not work for svg when has multiple root element.
902 */
903
904 if (NAMESPACE === HTML_NAMESPACE) {
905 try {
906 doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
907 } catch (_) {}
908 }
909 /* Use createHTMLDocument in case DOMParser is not available */
910
911
912 if (!doc || !doc.documentElement) {
913 doc = implementation.createDocument(NAMESPACE, 'template', null);
914
915 try {
916 doc.documentElement.innerHTML = IS_EMPTY_INPUT ? '' : dirtyPayload;
917 } catch (_) {// Syntax error if dirtyPayload is invalid xml
918 }
919 }
920
921 var body = doc.body || doc.documentElement;
922
923 if (dirty && leadingWhitespace) {
924 body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
925 }
926 /* Work on whole document or just its body */
927
928
929 if (NAMESPACE === HTML_NAMESPACE) {
930 return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
931 }
932
933 return WHOLE_DOCUMENT ? doc.documentElement : body;
934 };
935 /**
936 * _createIterator
937 *
938 * @param {Document} root document/fragment to create iterator for
939 * @return {Iterator} iterator instance
940 */
941
942
943 var _createIterator = function _createIterator(root) {
944 return createNodeIterator.call(root.ownerDocument || root, root, // eslint-disable-next-line no-bitwise
945 NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null, false);
946 };
947 /**
948 * _isClobbered
949 *
950 * @param {Node} elm element to check for clobbering attacks
951 * @return {Boolean} true if clobbered, false if safe
952 */
953
954
955 var _isClobbered = function _isClobbered(elm) {
956 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');
957 };
958 /**
959 * _isNode
960 *
961 * @param {Node} obj object to check whether it's a DOM node
962 * @return {Boolean} true is object is a DOM node
963 */
964
965
966 var _isNode = function _isNode(object) {
967 return _typeof(Node) === 'object' ? object instanceof Node : object && _typeof(object) === 'object' && typeof object.nodeType === 'number' && typeof object.nodeName === 'string';
968 };
969 /**
970 * _executeHook
971 * Execute user configurable hooks
972 *
973 * @param {String} entryPoint Name of the hook's entry point
974 * @param {Node} currentNode node to work on with the hook
975 * @param {Object} data additional hook parameters
976 */
977
978
979 var _executeHook = function _executeHook(entryPoint, currentNode, data) {
980 if (!hooks[entryPoint]) {
981 return;
982 }
983
984 arrayForEach(hooks[entryPoint], function (hook) {
985 hook.call(DOMPurify, currentNode, data, CONFIG);
986 });
987 };
988 /**
989 * _sanitizeElements
990 *
991 * @protect nodeName
992 * @protect textContent
993 * @protect removeChild
994 *
995 * @param {Node} currentNode to check for permission to exist
996 * @return {Boolean} true if node was killed, false if left alive
997 */
998
999
1000 var _sanitizeElements = function _sanitizeElements(currentNode) {
1001 var content;
1002 /* Execute a hook if present */
1003
1004 _executeHook('beforeSanitizeElements', currentNode, null);
1005 /* Check if element is clobbered or can clobber */
1006
1007
1008 if (_isClobbered(currentNode)) {
1009 _forceRemove(currentNode);
1010
1011 return true;
1012 }
1013 /* Check if tagname contains Unicode */
1014
1015
1016 if (regExpTest(/[\u0080-\uFFFF]/, currentNode.nodeName)) {
1017 _forceRemove(currentNode);
1018
1019 return true;
1020 }
1021 /* Now let's check the element's type and name */
1022
1023
1024 var tagName = transformCaseFunc(currentNode.nodeName);
1025 /* Execute a hook if present */
1026
1027 _executeHook('uponSanitizeElement', currentNode, {
1028 tagName: tagName,
1029 allowedTags: ALLOWED_TAGS
1030 });
1031 /* Detect mXSS attempts abusing namespace confusion */
1032
1033
1034 if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
1035 _forceRemove(currentNode);
1036
1037 return true;
1038 }
1039 /* Mitigate a problem with templates inside select */
1040
1041
1042 if (tagName === 'select' && regExpTest(/<template/i, currentNode.innerHTML)) {
1043 _forceRemove(currentNode);
1044
1045 return true;
1046 }
1047 /* Remove element if anything forbids its presence */
1048
1049
1050 if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
1051 /* Check if we have a custom element to handle */
1052 if (!FORBID_TAGS[tagName] && _basicCustomElementTest(tagName)) {
1053 if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false;
1054 if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false;
1055 }
1056 /* Keep content except for bad-listed elements */
1057
1058
1059 if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
1060 var parentNode = getParentNode(currentNode) || currentNode.parentNode;
1061 var childNodes = getChildNodes(currentNode) || currentNode.childNodes;
1062
1063 if (childNodes && parentNode) {
1064 var childCount = childNodes.length;
1065
1066 for (var i = childCount - 1; i >= 0; --i) {
1067 parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
1068 }
1069 }
1070 }
1071
1072 _forceRemove(currentNode);
1073
1074 return true;
1075 }
1076 /* Check whether element has a valid namespace */
1077
1078
1079 if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
1080 _forceRemove(currentNode);
1081
1082 return true;
1083 }
1084
1085 if ((tagName === 'noscript' || tagName === 'noembed') && regExpTest(/<\/no(script|embed)/i, currentNode.innerHTML)) {
1086 _forceRemove(currentNode);
1087
1088 return true;
1089 }
1090 /* Sanitize element content to be template-safe */
1091
1092
1093 if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
1094 /* Get the element's text content */
1095 content = currentNode.textContent;
1096 content = stringReplace(content, MUSTACHE_EXPR$1, ' ');
1097 content = stringReplace(content, ERB_EXPR$1, ' ');
1098
1099 if (currentNode.textContent !== content) {
1100 arrayPush(DOMPurify.removed, {
1101 element: currentNode.cloneNode()
1102 });
1103 currentNode.textContent = content;
1104 }
1105 }
1106 /* Execute a hook if present */
1107
1108
1109 _executeHook('afterSanitizeElements', currentNode, null);
1110
1111 return false;
1112 };
1113 /**
1114 * _isValidAttribute
1115 *
1116 * @param {string} lcTag Lowercase tag name of containing element.
1117 * @param {string} lcName Lowercase attribute name.
1118 * @param {string} value Attribute value.
1119 * @return {Boolean} Returns true if `value` is valid, otherwise false.
1120 */
1121 // eslint-disable-next-line complexity
1122
1123
1124 var _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
1125 /* Make sure attribute cannot clobber */
1126 if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
1127 return false;
1128 }
1129 /* Allow valid data-* attributes: At least one character after "-"
1130 (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
1131 XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
1132 We don't need to check the value; it's always URI safe. */
1133
1134
1135 if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
1136 if ( // First condition does a very basic check if a) it's basically a valid custom element tagname AND
1137 // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1138 // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
1139 _basicCustomElementTest(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) || // Alternative, second condition checks if it's an `is`-attribute, AND
1140 // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
1141 lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else {
1142 return false;
1143 }
1144 /* Check value is safe. First, is attr inert? If so, is safe */
1145
1146 } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if (!value) ; else {
1147 return false;
1148 }
1149
1150 return true;
1151 };
1152 /**
1153 * _basicCustomElementCheck
1154 * checks if at least one dash is included in tagName, and it's not the first char
1155 * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
1156 * @param {string} tagName name of the tag of the node to sanitize
1157 */
1158
1159
1160 var _basicCustomElementTest = function _basicCustomElementTest(tagName) {
1161 return tagName.indexOf('-') > 0;
1162 };
1163 /**
1164 * _sanitizeAttributes
1165 *
1166 * @protect attributes
1167 * @protect nodeName
1168 * @protect removeAttribute
1169 * @protect setAttribute
1170 *
1171 * @param {Node} currentNode to sanitize
1172 */
1173
1174
1175 var _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
1176 var attr;
1177 var value;
1178 var lcName;
1179 var l;
1180 /* Execute a hook if present */
1181
1182 _executeHook('beforeSanitizeAttributes', currentNode, null);
1183
1184 var attributes = currentNode.attributes;
1185 /* Check if we have attributes; if not we might have a text node */
1186
1187 if (!attributes) {
1188 return;
1189 }
1190
1191 var hookEvent = {
1192 attrName: '',
1193 attrValue: '',
1194 keepAttr: true,
1195 allowedAttributes: ALLOWED_ATTR
1196 };
1197 l = attributes.length;
1198 /* Go backwards over all attributes; safely remove bad ones */
1199
1200 while (l--) {
1201 attr = attributes[l];
1202 var _attr = attr,
1203 name = _attr.name,
1204 namespaceURI = _attr.namespaceURI;
1205 value = name === 'value' ? attr.value : stringTrim(attr.value);
1206 lcName = transformCaseFunc(name);
1207 /* Execute a hook if present */
1208
1209 hookEvent.attrName = lcName;
1210 hookEvent.attrValue = value;
1211 hookEvent.keepAttr = true;
1212 hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
1213
1214 _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
1215
1216 value = hookEvent.attrValue;
1217 /* Did the hooks approve of the attribute? */
1218
1219 if (hookEvent.forceKeepAttr) {
1220 continue;
1221 }
1222 /* Remove attribute */
1223
1224
1225 _removeAttribute(name, currentNode);
1226 /* Did the hooks approve of the attribute? */
1227
1228
1229 if (!hookEvent.keepAttr) {
1230 continue;
1231 }
1232 /* Work around a security issue in jQuery 3.0 */
1233
1234
1235 if (regExpTest(/\/>/i, value)) {
1236 _removeAttribute(name, currentNode);
1237
1238 continue;
1239 }
1240 /* Sanitize attribute content to be template-safe */
1241
1242
1243 if (SAFE_FOR_TEMPLATES) {
1244 value = stringReplace(value, MUSTACHE_EXPR$1, ' ');
1245 value = stringReplace(value, ERB_EXPR$1, ' ');
1246 }
1247 /* Is `value` valid for this attribute? */
1248
1249
1250 var lcTag = transformCaseFunc(currentNode.nodeName);
1251
1252 if (!_isValidAttribute(lcTag, lcName, value)) {
1253 continue;
1254 }
1255 /* Handle attributes that require Trusted Types */
1256
1257
1258 if (trustedTypesPolicy && _typeof(trustedTypes) === 'object' && typeof trustedTypes.getAttributeType === 'function') {
1259 if (namespaceURI) ; else {
1260 switch (trustedTypes.getAttributeType(lcTag, lcName)) {
1261 case 'TrustedHTML':
1262 value = trustedTypesPolicy.createHTML(value);
1263 break;
1264
1265 case 'TrustedScriptURL':
1266 value = trustedTypesPolicy.createScriptURL(value);
1267 break;
1268 }
1269 }
1270 }
1271 /* Handle invalid data-* attribute set by try-catching it */
1272
1273
1274 try {
1275 if (namespaceURI) {
1276 currentNode.setAttributeNS(namespaceURI, name, value);
1277 } else {
1278 /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
1279 currentNode.setAttribute(name, value);
1280 }
1281
1282 arrayPop(DOMPurify.removed);
1283 } catch (_) {}
1284 }
1285 /* Execute a hook if present */
1286
1287
1288 _executeHook('afterSanitizeAttributes', currentNode, null);
1289 };
1290 /**
1291 * _sanitizeShadowDOM
1292 *
1293 * @param {DocumentFragment} fragment to iterate over recursively
1294 */
1295
1296
1297 var _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
1298 var shadowNode;
1299
1300 var shadowIterator = _createIterator(fragment);
1301 /* Execute a hook if present */
1302
1303
1304 _executeHook('beforeSanitizeShadowDOM', fragment, null);
1305
1306 while (shadowNode = shadowIterator.nextNode()) {
1307 /* Execute a hook if present */
1308 _executeHook('uponSanitizeShadowNode', shadowNode, null);
1309 /* Sanitize tags and elements */
1310
1311
1312 if (_sanitizeElements(shadowNode)) {
1313 continue;
1314 }
1315 /* Deep shadow DOM detected */
1316
1317
1318 if (shadowNode.content instanceof DocumentFragment) {
1319 _sanitizeShadowDOM(shadowNode.content);
1320 }
1321 /* Check attributes, sanitize if necessary */
1322
1323
1324 _sanitizeAttributes(shadowNode);
1325 }
1326 /* Execute a hook if present */
1327
1328
1329 _executeHook('afterSanitizeShadowDOM', fragment, null);
1330 };
1331 /**
1332 * Sanitize
1333 * Public method providing core sanitation functionality
1334 *
1335 * @param {String|Node} dirty string or DOM node
1336 * @param {Object} configuration object
1337 */
1338 // eslint-disable-next-line complexity
1339
1340
1341 DOMPurify.sanitize = function (dirty, cfg) {
1342 var body;
1343 var importedNode;
1344 var currentNode;
1345 var oldNode;
1346 var returnNode;
1347 /* Make sure we have a string to sanitize.
1348 DO NOT return early, as this will return the wrong type if
1349 the user has requested a DOM object rather than a string */
1350
1351 IS_EMPTY_INPUT = !dirty;
1352
1353 if (IS_EMPTY_INPUT) {
1354 dirty = '<!-->';
1355 }
1356 /* Stringify, in case dirty is an object */
1357
1358
1359 if (typeof dirty !== 'string' && !_isNode(dirty)) {
1360 // eslint-disable-next-line no-negated-condition
1361 if (typeof dirty.toString !== 'function') {
1362 throw typeErrorCreate('toString is not a function');
1363 } else {
1364 dirty = dirty.toString();
1365
1366 if (typeof dirty !== 'string') {
1367 throw typeErrorCreate('dirty is not a string, aborting');
1368 }
1369 }
1370 }
1371 /* Check we can run. Otherwise fall back or ignore */
1372
1373
1374 if (!DOMPurify.isSupported) {
1375 if (_typeof(window.toStaticHTML) === 'object' || typeof window.toStaticHTML === 'function') {
1376 if (typeof dirty === 'string') {
1377 return window.toStaticHTML(dirty);
1378 }
1379
1380 if (_isNode(dirty)) {
1381 return window.toStaticHTML(dirty.outerHTML);
1382 }
1383 }
1384
1385 return dirty;
1386 }
1387 /* Assign config vars */
1388
1389
1390 if (!SET_CONFIG) {
1391 _parseConfig(cfg);
1392 }
1393 /* Clean up removed elements */
1394
1395
1396 DOMPurify.removed = [];
1397 /* Check if dirty is correctly typed for IN_PLACE */
1398
1399 if (typeof dirty === 'string') {
1400 IN_PLACE = false;
1401 }
1402
1403 if (IN_PLACE) {
1404 /* Do some early pre-sanitization to avoid unsafe root nodes */
1405 if (dirty.nodeName) {
1406 var tagName = transformCaseFunc(dirty.nodeName);
1407
1408 if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
1409 throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
1410 }
1411 }
1412 } else if (dirty instanceof Node) {
1413 /* If dirty is a DOM element, append to an empty document to avoid
1414 elements being stripped by the parser */
1415 body = _initDocument('<!---->');
1416 importedNode = body.ownerDocument.importNode(dirty, true);
1417
1418 if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
1419 /* Node is already a body, use as is */
1420 body = importedNode;
1421 } else if (importedNode.nodeName === 'HTML') {
1422 body = importedNode;
1423 } else {
1424 // eslint-disable-next-line unicorn/prefer-dom-node-append
1425 body.appendChild(importedNode);
1426 }
1427 } else {
1428 /* Exit directly if we have nothing to do */
1429 if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && // eslint-disable-next-line unicorn/prefer-includes
1430 dirty.indexOf('<') === -1) {
1431 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
1432 }
1433 /* Initialize the document to work on */
1434
1435
1436 body = _initDocument(dirty);
1437 /* Check we have a DOM node from the data */
1438
1439 if (!body) {
1440 return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
1441 }
1442 }
1443 /* Remove first element node (ours) if FORCE_BODY is set */
1444
1445
1446 if (body && FORCE_BODY) {
1447 _forceRemove(body.firstChild);
1448 }
1449 /* Get node iterator */
1450
1451
1452 var nodeIterator = _createIterator(IN_PLACE ? dirty : body);
1453 /* Now start iterating over the created document */
1454
1455
1456 while (currentNode = nodeIterator.nextNode()) {
1457 /* Fix IE's strange behavior with manipulated textNodes #89 */
1458 if (currentNode.nodeType === 3 && currentNode === oldNode) {
1459 continue;
1460 }
1461 /* Sanitize tags and elements */
1462
1463
1464 if (_sanitizeElements(currentNode)) {
1465 continue;
1466 }
1467 /* Shadow DOM detected, sanitize it */
1468
1469
1470 if (currentNode.content instanceof DocumentFragment) {
1471 _sanitizeShadowDOM(currentNode.content);
1472 }
1473 /* Check attributes, sanitize if necessary */
1474
1475
1476 _sanitizeAttributes(currentNode);
1477
1478 oldNode = currentNode;
1479 }
1480
1481 oldNode = null;
1482 /* If we sanitized `dirty` in-place, return it. */
1483
1484 if (IN_PLACE) {
1485 return dirty;
1486 }
1487 /* Return sanitized string or DOM */
1488
1489
1490 if (RETURN_DOM) {
1491 if (RETURN_DOM_FRAGMENT) {
1492 returnNode = createDocumentFragment.call(body.ownerDocument);
1493
1494 while (body.firstChild) {
1495 // eslint-disable-next-line unicorn/prefer-dom-node-append
1496 returnNode.appendChild(body.firstChild);
1497 }
1498 } else {
1499 returnNode = body;
1500 }
1501
1502 if (ALLOWED_ATTR.shadowroot) {
1503 /*
1504 AdoptNode() is not used because internal state is not reset
1505 (e.g. the past names map of a HTMLFormElement), this is safe
1506 in theory but we would rather not risk another attack vector.
1507 The state that is cloned by importNode() is explicitly defined
1508 by the specs.
1509 */
1510 returnNode = importNode.call(originalDocument, returnNode, true);
1511 }
1512
1513 return returnNode;
1514 }
1515
1516 var serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
1517 /* Serialize doctype if allowed */
1518
1519 if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
1520 serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
1521 }
1522 /* Sanitize final string template-safe */
1523
1524
1525 if (SAFE_FOR_TEMPLATES) {
1526 serializedHTML = stringReplace(serializedHTML, MUSTACHE_EXPR$1, ' ');
1527 serializedHTML = stringReplace(serializedHTML, ERB_EXPR$1, ' ');
1528 }
1529
1530 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
1531 };
1532 /**
1533 * Public method to set the configuration once
1534 * setConfig
1535 *
1536 * @param {Object} cfg configuration object
1537 */
1538
1539
1540 DOMPurify.setConfig = function (cfg) {
1541 _parseConfig(cfg);
1542
1543 SET_CONFIG = true;
1544 };
1545 /**
1546 * Public method to remove the configuration
1547 * clearConfig
1548 *
1549 */
1550
1551
1552 DOMPurify.clearConfig = function () {
1553 CONFIG = null;
1554 SET_CONFIG = false;
1555 };
1556 /**
1557 * Public method to check if an attribute value is valid.
1558 * Uses last set config, if any. Otherwise, uses config defaults.
1559 * isValidAttribute
1560 *
1561 * @param {string} tag Tag name of containing element.
1562 * @param {string} attr Attribute name.
1563 * @param {string} value Attribute value.
1564 * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
1565 */
1566
1567
1568 DOMPurify.isValidAttribute = function (tag, attr, value) {
1569 /* Initialize shared config vars if necessary. */
1570 if (!CONFIG) {
1571 _parseConfig({});
1572 }
1573
1574 var lcTag = transformCaseFunc(tag);
1575 var lcName = transformCaseFunc(attr);
1576 return _isValidAttribute(lcTag, lcName, value);
1577 };
1578 /**
1579 * AddHook
1580 * Public method to add DOMPurify hooks
1581 *
1582 * @param {String} entryPoint entry point for the hook to add
1583 * @param {Function} hookFunction function to execute
1584 */
1585
1586
1587 DOMPurify.addHook = function (entryPoint, hookFunction) {
1588 if (typeof hookFunction !== 'function') {
1589 return;
1590 }
1591
1592 hooks[entryPoint] = hooks[entryPoint] || [];
1593 arrayPush(hooks[entryPoint], hookFunction);
1594 };
1595 /**
1596 * RemoveHook
1597 * Public method to remove a DOMPurify hook at a given entryPoint
1598 * (pops it from the stack of hooks if more are present)
1599 *
1600 * @param {String} entryPoint entry point for the hook to remove
1601 * @return {Function} removed(popped) hook
1602 */
1603
1604
1605 DOMPurify.removeHook = function (entryPoint) {
1606 if (hooks[entryPoint]) {
1607 return arrayPop(hooks[entryPoint]);
1608 }
1609 };
1610 /**
1611 * RemoveHooks
1612 * Public method to remove all DOMPurify hooks at a given entryPoint
1613 *
1614 * @param {String} entryPoint entry point for the hooks to remove
1615 */
1616
1617
1618 DOMPurify.removeHooks = function (entryPoint) {
1619 if (hooks[entryPoint]) {
1620 hooks[entryPoint] = [];
1621 }
1622 };
1623 /**
1624 * RemoveAllHooks
1625 * Public method to remove all DOMPurify hooks
1626 *
1627 */
1628
1629
1630 DOMPurify.removeAllHooks = function () {
1631 hooks = {};
1632 };
1633
1634 return DOMPurify;
1635}
1636
1637var purify = createDOMPurify();
1638
1639module.exports = purify;
1640//# sourceMappingURL=purify.cjs.js.map