UNPKG

50.6 kBJavaScriptView Raw
1/*! @license DOMPurify | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.2.2/LICENSE */
2
3'use strict';
4
5function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
6
7var hasOwnProperty = Object.hasOwnProperty,
8 setPrototypeOf = Object.setPrototypeOf,
9 isFrozen = Object.isFrozen,
10 getPrototypeOf = Object.getPrototypeOf,
11 getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
12var freeze = Object.freeze,
13 seal = Object.seal,
14 create = Object.create; // eslint-disable-line import/no-mutable-exports
15
16var _ref = typeof Reflect !== 'undefined' && Reflect,
17 apply = _ref.apply,
18 construct = _ref.construct;
19
20if (!apply) {
21 apply = function apply(fun, thisValue, args) {
22 return fun.apply(thisValue, args);
23 };
24}
25
26if (!freeze) {
27 freeze = function freeze(x) {
28 return x;
29 };
30}
31
32if (!seal) {
33 seal = function seal(x) {
34 return x;
35 };
36}
37
38if (!construct) {
39 construct = function construct(Func, args) {
40 return new (Function.prototype.bind.apply(Func, [null].concat(_toConsumableArray(args))))();
41 };
42}
43
44var arrayForEach = unapply(Array.prototype.forEach);
45var arrayPop = unapply(Array.prototype.pop);
46var arrayPush = unapply(Array.prototype.push);
47
48var stringToLowerCase = unapply(String.prototype.toLowerCase);
49var stringMatch = unapply(String.prototype.match);
50var stringReplace = unapply(String.prototype.replace);
51var stringIndexOf = unapply(String.prototype.indexOf);
52var stringTrim = unapply(String.prototype.trim);
53
54var regExpTest = unapply(RegExp.prototype.test);
55
56var typeErrorCreate = unconstruct(TypeError);
57
58function unapply(func) {
59 return function (thisArg) {
60 for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
61 args[_key - 1] = arguments[_key];
62 }
63
64 return apply(func, thisArg, args);
65 };
66}
67
68function unconstruct(func) {
69 return function () {
70 for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
71 args[_key2] = arguments[_key2];
72 }
73
74 return construct(func, args);
75 };
76}
77
78/* Add properties to a lookup table */
79function addToSet(set, array) {
80 if (setPrototypeOf) {
81 // Make 'in' and truthy checks like Boolean(set.constructor)
82 // independent of any properties defined on Object.prototype.
83 // Prevent prototype setters from intercepting set as a this value.
84 setPrototypeOf(set, null);
85 }
86
87 var l = array.length;
88 while (l--) {
89 var element = array[l];
90 if (typeof element === 'string') {
91 var lcElement = stringToLowerCase(element);
92 if (lcElement !== element) {
93 // Config presets (e.g. tags.js, attrs.js) are immutable.
94 if (!isFrozen(array)) {
95 array[l] = lcElement;
96 }
97
98 element = lcElement;
99 }
100 }
101
102 set[element] = true;
103 }
104
105 return set;
106}
107
108/* Shallow clone an object */
109function clone(object) {
110 var newObject = create(null);
111
112 var property = void 0;
113 for (property in object) {
114 if (apply(hasOwnProperty, object, [property])) {
115 newObject[property] = object[property];
116 }
117 }
118
119 return newObject;
120}
121
122/* IE10 doesn't support __lookupGetter__ so lets'
123 * simulate it. It also automatically checks
124 * if the prop is function or getter and behaves
125 * accordingly. */
126function lookupGetter(object, prop) {
127 while (object !== null) {
128 var desc = getOwnPropertyDescriptor(object, prop);
129 if (desc) {
130 if (desc.get) {
131 return unapply(desc.get);
132 }
133
134 if (typeof desc.value === 'function') {
135 return unapply(desc.value);
136 }
137 }
138
139 object = getPrototypeOf(object);
140 }
141
142 return null;
143}
144
145var html = 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']);
146
147// SVG
148var svg = 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']);
149
150var svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);
151
152// List of SVG elements that are disallowed by default.
153// We still need to know them so that we can do namespace
154// checks properly in case one wants to add them to
155// allow-list.
156var svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'fedropshadow', 'feimage', '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']);
157
158var mathMl = 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']);
159
160// Similarly to SVG, we want to know all MathML elements,
161// even those that we disallow by default.
162var mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
163
164var text = freeze(['#text']);
165
166var html$1 = 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', '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']);
167
168var svg$1 = 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', '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']);
169
170var mathMl$1 = 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']);
171
172var xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
173
174// eslint-disable-next-line unicorn/better-regex
175var MUSTACHE_EXPR = seal(/\{\{[\s\S]*|[\s\S]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
176var ERB_EXPR = seal(/<%[\s\S]*|[\s\S]*%>/gm);
177var DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape
178var ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
179var 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
180);
181var IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
182var ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
183);
184
185var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
186
187function _toConsumableArray$1(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
188
189var getGlobal = function getGlobal() {
190 return typeof window === 'undefined' ? null : window;
191};
192
193/**
194 * Creates a no-op policy for internal use only.
195 * Don't export this function outside this module!
196 * @param {?TrustedTypePolicyFactory} trustedTypes The policy factory.
197 * @param {Document} document The document object (to determine policy name suffix)
198 * @return {?TrustedTypePolicy} The policy created (or null, if Trusted Types
199 * are not supported).
200 */
201var _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, document) {
202 if ((typeof trustedTypes === 'undefined' ? 'undefined' : _typeof(trustedTypes)) !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
203 return null;
204 }
205
206 // Allow the callers to control the unique policy name
207 // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
208 // Policy creation with duplicate names throws in Trusted Types.
209 var suffix = null;
210 var ATTR_NAME = 'data-tt-policy-suffix';
211 if (document.currentScript && document.currentScript.hasAttribute(ATTR_NAME)) {
212 suffix = document.currentScript.getAttribute(ATTR_NAME);
213 }
214
215 var policyName = 'dompurify' + (suffix ? '#' + suffix : '');
216
217 try {
218 return trustedTypes.createPolicy(policyName, {
219 createHTML: function createHTML(html$$1) {
220 return html$$1;
221 }
222 });
223 } catch (_) {
224 // Policy creation failed (most likely another DOMPurify script has
225 // already run). Skip creating the policy, as this will only cause errors
226 // if TT are enforced.
227 console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
228 return null;
229 }
230};
231
232function createDOMPurify() {
233 var window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
234
235 var DOMPurify = function DOMPurify(root) {
236 return createDOMPurify(root);
237 };
238
239 /**
240 * Version label, exposed for easier checks
241 * if DOMPurify is up to date or not
242 */
243 DOMPurify.version = '2.2.6';
244
245 /**
246 * Array of elements that DOMPurify removed during sanitation.
247 * Empty if nothing was removed.
248 */
249 DOMPurify.removed = [];
250
251 if (!window || !window.document || window.document.nodeType !== 9) {
252 // Not running in a browser, provide a factory function
253 // so that you can pass your own Window
254 DOMPurify.isSupported = false;
255
256 return DOMPurify;
257 }
258
259 var originalDocument = window.document;
260
261 var document = window.document;
262 var DocumentFragment = window.DocumentFragment,
263 HTMLTemplateElement = window.HTMLTemplateElement,
264 Node = window.Node,
265 Element = window.Element,
266 NodeFilter = window.NodeFilter,
267 _window$NamedNodeMap = window.NamedNodeMap,
268 NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
269 Text = window.Text,
270 Comment = window.Comment,
271 DOMParser = window.DOMParser,
272 trustedTypes = window.trustedTypes;
273
274
275 var ElementPrototype = Element.prototype;
276
277 var cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
278 var getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
279 var getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
280 var getParentNode = lookupGetter(ElementPrototype, 'parentNode');
281
282 // As per issue #47, the web-components registry is inherited by a
283 // new document created via createHTMLDocument. As per the spec
284 // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
285 // a new empty registry is used when creating a template contents owner
286 // document, so we use that as our parent document to ensure nothing
287 // is inherited.
288 if (typeof HTMLTemplateElement === 'function') {
289 var template = document.createElement('template');
290 if (template.content && template.content.ownerDocument) {
291 document = template.content.ownerDocument;
292 }
293 }
294
295 var trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, originalDocument);
296 var emptyHTML = trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML('') : '';
297
298 var _document = document,
299 implementation = _document.implementation,
300 createNodeIterator = _document.createNodeIterator,
301 getElementsByTagName = _document.getElementsByTagName,
302 createDocumentFragment = _document.createDocumentFragment;
303 var importNode = originalDocument.importNode;
304
305
306 var documentMode = {};
307 try {
308 documentMode = clone(document).documentMode ? document.documentMode : {};
309 } catch (_) {}
310
311 var hooks = {};
312
313 /**
314 * Expose whether this browser supports running the full DOMPurify.
315 */
316 DOMPurify.isSupported = implementation && typeof implementation.createHTMLDocument !== 'undefined' && documentMode !== 9;
317
318 var MUSTACHE_EXPR$$1 = MUSTACHE_EXPR,
319 ERB_EXPR$$1 = ERB_EXPR,
320 DATA_ATTR$$1 = DATA_ATTR,
321 ARIA_ATTR$$1 = ARIA_ATTR,
322 IS_SCRIPT_OR_DATA$$1 = IS_SCRIPT_OR_DATA,
323 ATTR_WHITESPACE$$1 = ATTR_WHITESPACE;
324 var IS_ALLOWED_URI$$1 = IS_ALLOWED_URI;
325
326 /**
327 * We consider the elements and attributes below to be safe. Ideally
328 * don't add any new ones but feel free to remove unwanted ones.
329 */
330
331 /* allowed element names */
332
333 var ALLOWED_TAGS = null;
334 var DEFAULT_ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray$1(html), _toConsumableArray$1(svg), _toConsumableArray$1(svgFilters), _toConsumableArray$1(mathMl), _toConsumableArray$1(text)));
335
336 /* Allowed attribute names */
337 var ALLOWED_ATTR = null;
338 var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray$1(html$1), _toConsumableArray$1(svg$1), _toConsumableArray$1(mathMl$1), _toConsumableArray$1(xml)));
339
340 /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
341 var FORBID_TAGS = null;
342
343 /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
344 var FORBID_ATTR = null;
345
346 /* Decide if ARIA attributes are okay */
347 var ALLOW_ARIA_ATTR = true;
348
349 /* Decide if custom data attributes are okay */
350 var ALLOW_DATA_ATTR = true;
351
352 /* Decide if unknown protocols are okay */
353 var ALLOW_UNKNOWN_PROTOCOLS = false;
354
355 /* Output should be safe for common template engines.
356 * This means, DOMPurify removes data attributes, mustaches and ERB
357 */
358 var SAFE_FOR_TEMPLATES = false;
359
360 /* Decide if document with <html>... should be returned */
361 var WHOLE_DOCUMENT = false;
362
363 /* Track whether config is already set on this instance of DOMPurify. */
364 var SET_CONFIG = false;
365
366 /* Decide if all elements (e.g. style, script) must be children of
367 * document.body. By default, browsers might move them to document.head */
368 var FORCE_BODY = false;
369
370 /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
371 * string (or a TrustedHTML object if Trusted Types are supported).
372 * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
373 */
374 var RETURN_DOM = false;
375
376 /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
377 * string (or a TrustedHTML object if Trusted Types are supported) */
378 var RETURN_DOM_FRAGMENT = false;
379
380 /* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM
381 * `Node` is imported into the current `Document`. If this flag is not enabled the
382 * `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by
383 * DOMPurify.
384 *
385 * This defaults to `true` starting DOMPurify 2.2.0. Note that setting it to `false`
386 * might cause XSS from attacks hidden in closed shadowroots in case the browser
387 * supports Declarative Shadow: DOM https://web.dev/declarative-shadow-dom/
388 */
389 var RETURN_DOM_IMPORT = true;
390
391 /* Try to return a Trusted Type object instead of a string, return a string in
392 * case Trusted Types are not supported */
393 var RETURN_TRUSTED_TYPE = false;
394
395 /* Output should be free from DOM clobbering attacks? */
396 var SANITIZE_DOM = true;
397
398 /* Keep element content when removing element? */
399 var KEEP_CONTENT = true;
400
401 /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
402 * of importing it into a new Document and returning a sanitized copy */
403 var IN_PLACE = false;
404
405 /* Allow usage of profiles like html, svg and mathMl */
406 var USE_PROFILES = {};
407
408 /* Tags to ignore content of when KEEP_CONTENT is true */
409 var 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']);
410
411 /* Tags that are safe for data: URIs */
412 var DATA_URI_TAGS = null;
413 var DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
414
415 /* Attributes safe for values like "javascript:" */
416 var URI_SAFE_ATTRIBUTES = null;
417 var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'summary', 'title', 'value', 'style', 'xmlns']);
418
419 /* Keep a reference to config to pass to hooks */
420 var CONFIG = null;
421
422 /* Ideally, do not touch anything below this line */
423 /* ______________________________________________ */
424
425 var formElement = document.createElement('form');
426
427 /**
428 * _parseConfig
429 *
430 * @param {Object} cfg optional config literal
431 */
432 // eslint-disable-next-line complexity
433 var _parseConfig = function _parseConfig(cfg) {
434 if (CONFIG && CONFIG === cfg) {
435 return;
436 }
437
438 /* Shield configuration object from tampering */
439 if (!cfg || (typeof cfg === 'undefined' ? 'undefined' : _typeof(cfg)) !== 'object') {
440 cfg = {};
441 }
442
443 /* Shield configuration object from prototype pollution */
444 cfg = clone(cfg);
445
446 /* Set configuration parameters */
447 ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS;
448 ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR;
449 URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR) : DEFAULT_URI_SAFE_ATTRIBUTES;
450 DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS) : DEFAULT_DATA_URI_TAGS;
451 FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {};
452 FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {};
453 USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
454 ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
455 ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
456 ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
457 SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
458 WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
459 RETURN_DOM = cfg.RETURN_DOM || false; // Default false
460 RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
461 RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT !== false; // Default true
462 RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
463 FORCE_BODY = cfg.FORCE_BODY || false; // Default false
464 SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
465 KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
466 IN_PLACE = cfg.IN_PLACE || false; // Default false
467 IS_ALLOWED_URI$$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1;
468 if (SAFE_FOR_TEMPLATES) {
469 ALLOW_DATA_ATTR = false;
470 }
471
472 if (RETURN_DOM_FRAGMENT) {
473 RETURN_DOM = true;
474 }
475
476 /* Parse profile info */
477 if (USE_PROFILES) {
478 ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray$1(text)));
479 ALLOWED_ATTR = [];
480 if (USE_PROFILES.html === true) {
481 addToSet(ALLOWED_TAGS, html);
482 addToSet(ALLOWED_ATTR, html$1);
483 }
484
485 if (USE_PROFILES.svg === true) {
486 addToSet(ALLOWED_TAGS, svg);
487 addToSet(ALLOWED_ATTR, svg$1);
488 addToSet(ALLOWED_ATTR, xml);
489 }
490
491 if (USE_PROFILES.svgFilters === true) {
492 addToSet(ALLOWED_TAGS, svgFilters);
493 addToSet(ALLOWED_ATTR, svg$1);
494 addToSet(ALLOWED_ATTR, xml);
495 }
496
497 if (USE_PROFILES.mathMl === true) {
498 addToSet(ALLOWED_TAGS, mathMl);
499 addToSet(ALLOWED_ATTR, mathMl$1);
500 addToSet(ALLOWED_ATTR, xml);
501 }
502 }
503
504 /* Merge configuration parameters */
505 if (cfg.ADD_TAGS) {
506 if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
507 ALLOWED_TAGS = clone(ALLOWED_TAGS);
508 }
509
510 addToSet(ALLOWED_TAGS, cfg.ADD_TAGS);
511 }
512
513 if (cfg.ADD_ATTR) {
514 if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
515 ALLOWED_ATTR = clone(ALLOWED_ATTR);
516 }
517
518 addToSet(ALLOWED_ATTR, cfg.ADD_ATTR);
519 }
520
521 if (cfg.ADD_URI_SAFE_ATTR) {
522 addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR);
523 }
524
525 /* Add #text in case KEEP_CONTENT is set to true */
526 if (KEEP_CONTENT) {
527 ALLOWED_TAGS['#text'] = true;
528 }
529
530 /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
531 if (WHOLE_DOCUMENT) {
532 addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
533 }
534
535 /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
536 if (ALLOWED_TAGS.table) {
537 addToSet(ALLOWED_TAGS, ['tbody']);
538 delete FORBID_TAGS.tbody;
539 }
540
541 // Prevent further manipulation of configuration.
542 // Not available in IE8, Safari 5, etc.
543 if (freeze) {
544 freeze(cfg);
545 }
546
547 CONFIG = cfg;
548 };
549
550 var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
551
552 var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']);
553
554 /* Keep track of all possible SVG and MathML tags
555 * so that we can perform the namespace checks
556 * correctly. */
557 var ALL_SVG_TAGS = addToSet({}, svg);
558 addToSet(ALL_SVG_TAGS, svgFilters);
559 addToSet(ALL_SVG_TAGS, svgDisallowed);
560
561 var ALL_MATHML_TAGS = addToSet({}, mathMl);
562 addToSet(ALL_MATHML_TAGS, mathMlDisallowed);
563
564 var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
565 var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
566 var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
567
568 /**
569 *
570 *
571 * @param {Element} element a DOM element whose namespace is being checked
572 * @returns {boolean} Return false if the element has a
573 * namespace that a spec-compliant parser would never
574 * return. Return true otherwise.
575 */
576 var _checkValidNamespace = function _checkValidNamespace(element) {
577 var parent = getParentNode(element);
578
579 // In JSDOM, if we're inside shadow DOM, then parentNode
580 // can be null. We just simulate parent in this case.
581 if (!parent || !parent.tagName) {
582 parent = {
583 namespaceURI: HTML_NAMESPACE,
584 tagName: 'template'
585 };
586 }
587
588 var tagName = stringToLowerCase(element.tagName);
589 var parentTagName = stringToLowerCase(parent.tagName);
590
591 if (element.namespaceURI === SVG_NAMESPACE) {
592 // The only way to switch from HTML namespace to SVG
593 // is via <svg>. If it happens via any other tag, then
594 // it should be killed.
595 if (parent.namespaceURI === HTML_NAMESPACE) {
596 return tagName === 'svg';
597 }
598
599 // The only way to switch from MathML to SVG is via
600 // svg if parent is either <annotation-xml> or MathML
601 // text integration points.
602 if (parent.namespaceURI === MATHML_NAMESPACE) {
603 return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
604 }
605
606 // We only allow elements that are defined in SVG
607 // spec. All others are disallowed in SVG namespace.
608 return Boolean(ALL_SVG_TAGS[tagName]);
609 }
610
611 if (element.namespaceURI === MATHML_NAMESPACE) {
612 // The only way to switch from HTML namespace to MathML
613 // is via <math>. If it happens via any other tag, then
614 // it should be killed.
615 if (parent.namespaceURI === HTML_NAMESPACE) {
616 return tagName === 'math';
617 }
618
619 // The only way to switch from SVG to MathML is via
620 // <math> and HTML integration points
621 if (parent.namespaceURI === SVG_NAMESPACE) {
622 return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
623 }
624
625 // We only allow elements that are defined in MathML
626 // spec. All others are disallowed in MathML namespace.
627 return Boolean(ALL_MATHML_TAGS[tagName]);
628 }
629
630 if (element.namespaceURI === HTML_NAMESPACE) {
631 // The only way to switch from SVG to HTML is via
632 // HTML integration points, and from MathML to HTML
633 // is via MathML text integration points
634 if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
635 return false;
636 }
637
638 if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
639 return false;
640 }
641
642 // Certain elements are allowed in both SVG and HTML
643 // namespace. We need to specify them explicitly
644 // so that they don't get erronously deleted from
645 // HTML namespace.
646 var commonSvgAndHTMLElements = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
647
648 // We disallow tags that are specific for MathML
649 // or SVG and should never appear in HTML namespace
650 return !ALL_MATHML_TAGS[tagName] && (commonSvgAndHTMLElements[tagName] || !ALL_SVG_TAGS[tagName]);
651 }
652
653 // The code should never reach this place (this means
654 // that the element somehow got namespace that is not
655 // HTML, SVG or MathML). Return false just in case.
656 return false;
657 };
658
659 /**
660 * _forceRemove
661 *
662 * @param {Node} node a DOM node
663 */
664 var _forceRemove = function _forceRemove(node) {
665 arrayPush(DOMPurify.removed, { element: node });
666 try {
667 node.parentNode.removeChild(node);
668 } catch (_) {
669 try {
670 node.outerHTML = emptyHTML;
671 } catch (_) {
672 node.remove();
673 }
674 }
675 };
676
677 /**
678 * _removeAttribute
679 *
680 * @param {String} name an Attribute name
681 * @param {Node} node a DOM node
682 */
683 var _removeAttribute = function _removeAttribute(name, node) {
684 try {
685 arrayPush(DOMPurify.removed, {
686 attribute: node.getAttributeNode(name),
687 from: node
688 });
689 } catch (_) {
690 arrayPush(DOMPurify.removed, {
691 attribute: null,
692 from: node
693 });
694 }
695
696 node.removeAttribute(name);
697 };
698
699 /**
700 * _initDocument
701 *
702 * @param {String} dirty a string of dirty markup
703 * @return {Document} a DOM, filled with the dirty markup
704 */
705 var _initDocument = function _initDocument(dirty) {
706 /* Create a HTML document */
707 var doc = void 0;
708 var leadingWhitespace = void 0;
709
710 if (FORCE_BODY) {
711 dirty = '<remove></remove>' + dirty;
712 } else {
713 /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
714 var matches = stringMatch(dirty, /^[\r\n\t ]+/);
715 leadingWhitespace = matches && matches[0];
716 }
717
718 var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
719 /* Use the DOMParser API by default, fallback later if needs be */
720 try {
721 doc = new DOMParser().parseFromString(dirtyPayload, 'text/html');
722 } catch (_) {}
723
724 /* Use createHTMLDocument in case DOMParser is not available */
725 if (!doc || !doc.documentElement) {
726 doc = implementation.createHTMLDocument('');
727 var _doc = doc,
728 body = _doc.body;
729
730 body.parentNode.removeChild(body.parentNode.firstElementChild);
731 body.outerHTML = dirtyPayload;
732 }
733
734 if (dirty && leadingWhitespace) {
735 doc.body.insertBefore(document.createTextNode(leadingWhitespace), doc.body.childNodes[0] || null);
736 }
737
738 /* Work on whole document or just its body */
739 return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
740 };
741
742 /**
743 * _createIterator
744 *
745 * @param {Document} root document/fragment to create iterator for
746 * @return {Iterator} iterator instance
747 */
748 var _createIterator = function _createIterator(root) {
749 return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, function () {
750 return NodeFilter.FILTER_ACCEPT;
751 }, false);
752 };
753
754 /**
755 * _isClobbered
756 *
757 * @param {Node} elm element to check for clobbering attacks
758 * @return {Boolean} true if clobbered, false if safe
759 */
760 var _isClobbered = function _isClobbered(elm) {
761 if (elm instanceof Text || elm instanceof Comment) {
762 return false;
763 }
764
765 if (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') {
766 return true;
767 }
768
769 return false;
770 };
771
772 /**
773 * _isNode
774 *
775 * @param {Node} obj object to check whether it's a DOM node
776 * @return {Boolean} true is object is a DOM node
777 */
778 var _isNode = function _isNode(object) {
779 return (typeof Node === 'undefined' ? 'undefined' : _typeof(Node)) === 'object' ? object instanceof Node : object && (typeof object === 'undefined' ? 'undefined' : _typeof(object)) === 'object' && typeof object.nodeType === 'number' && typeof object.nodeName === 'string';
780 };
781
782 /**
783 * _executeHook
784 * Execute user configurable hooks
785 *
786 * @param {String} entryPoint Name of the hook's entry point
787 * @param {Node} currentNode node to work on with the hook
788 * @param {Object} data additional hook parameters
789 */
790 var _executeHook = function _executeHook(entryPoint, currentNode, data) {
791 if (!hooks[entryPoint]) {
792 return;
793 }
794
795 arrayForEach(hooks[entryPoint], function (hook) {
796 hook.call(DOMPurify, currentNode, data, CONFIG);
797 });
798 };
799
800 /**
801 * _sanitizeElements
802 *
803 * @protect nodeName
804 * @protect textContent
805 * @protect removeChild
806 *
807 * @param {Node} currentNode to check for permission to exist
808 * @return {Boolean} true if node was killed, false if left alive
809 */
810 var _sanitizeElements = function _sanitizeElements(currentNode) {
811 var content = void 0;
812
813 /* Execute a hook if present */
814 _executeHook('beforeSanitizeElements', currentNode, null);
815
816 /* Check if element is clobbered or can clobber */
817 if (_isClobbered(currentNode)) {
818 _forceRemove(currentNode);
819 return true;
820 }
821
822 /* Check if tagname contains Unicode */
823 if (stringMatch(currentNode.nodeName, /[\u0080-\uFFFF]/)) {
824 _forceRemove(currentNode);
825 return true;
826 }
827
828 /* Now let's check the element's type and name */
829 var tagName = stringToLowerCase(currentNode.nodeName);
830
831 /* Execute a hook if present */
832 _executeHook('uponSanitizeElement', currentNode, {
833 tagName: tagName,
834 allowedTags: ALLOWED_TAGS
835 });
836
837 /* Detect mXSS attempts abusing namespace confusion */
838 if (!_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
839 _forceRemove(currentNode);
840 return true;
841 }
842
843 /* Remove element if anything forbids its presence */
844 if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
845 /* Keep content except for bad-listed elements */
846 if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
847 var parentNode = getParentNode(currentNode);
848 var childNodes = getChildNodes(currentNode);
849 var childCount = childNodes.length;
850 for (var i = childCount - 1; i >= 0; --i) {
851 parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
852 }
853 }
854
855 _forceRemove(currentNode);
856 return true;
857 }
858
859 /* Check whether element has a valid namespace */
860 if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
861 _forceRemove(currentNode);
862 return true;
863 }
864
865 if ((tagName === 'noscript' || tagName === 'noembed') && regExpTest(/<\/no(script|embed)/i, currentNode.innerHTML)) {
866 _forceRemove(currentNode);
867 return true;
868 }
869
870 /* Sanitize element content to be template-safe */
871 if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
872 /* Get the element's text content */
873 content = currentNode.textContent;
874 content = stringReplace(content, MUSTACHE_EXPR$$1, ' ');
875 content = stringReplace(content, ERB_EXPR$$1, ' ');
876 if (currentNode.textContent !== content) {
877 arrayPush(DOMPurify.removed, { element: currentNode.cloneNode() });
878 currentNode.textContent = content;
879 }
880 }
881
882 /* Execute a hook if present */
883 _executeHook('afterSanitizeElements', currentNode, null);
884
885 return false;
886 };
887
888 /**
889 * _isValidAttribute
890 *
891 * @param {string} lcTag Lowercase tag name of containing element.
892 * @param {string} lcName Lowercase attribute name.
893 * @param {string} value Attribute value.
894 * @return {Boolean} Returns true if `value` is valid, otherwise false.
895 */
896 // eslint-disable-next-line complexity
897 var _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
898 /* Make sure attribute cannot clobber */
899 if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
900 return false;
901 }
902
903 /* Allow valid data-* attributes: At least one character after "-"
904 (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
905 XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
906 We don't need to check the value; it's always URI safe. */
907 if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$$1, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
908 return false;
909
910 /* Check value is safe. First, is attr inert? If so, is safe */
911 } 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 {
912 return false;
913 }
914
915 return true;
916 };
917
918 /**
919 * _sanitizeAttributes
920 *
921 * @protect attributes
922 * @protect nodeName
923 * @protect removeAttribute
924 * @protect setAttribute
925 *
926 * @param {Node} currentNode to sanitize
927 */
928 var _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
929 var attr = void 0;
930 var value = void 0;
931 var lcName = void 0;
932 var l = void 0;
933 /* Execute a hook if present */
934 _executeHook('beforeSanitizeAttributes', currentNode, null);
935
936 var attributes = currentNode.attributes;
937
938 /* Check if we have attributes; if not we might have a text node */
939
940 if (!attributes) {
941 return;
942 }
943
944 var hookEvent = {
945 attrName: '',
946 attrValue: '',
947 keepAttr: true,
948 allowedAttributes: ALLOWED_ATTR
949 };
950 l = attributes.length;
951
952 /* Go backwards over all attributes; safely remove bad ones */
953 while (l--) {
954 attr = attributes[l];
955 var _attr = attr,
956 name = _attr.name,
957 namespaceURI = _attr.namespaceURI;
958
959 value = stringTrim(attr.value);
960 lcName = stringToLowerCase(name);
961
962 /* Execute a hook if present */
963 hookEvent.attrName = lcName;
964 hookEvent.attrValue = value;
965 hookEvent.keepAttr = true;
966 hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
967 _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
968 value = hookEvent.attrValue;
969 /* Did the hooks approve of the attribute? */
970 if (hookEvent.forceKeepAttr) {
971 continue;
972 }
973
974 /* Remove attribute */
975 _removeAttribute(name, currentNode);
976
977 /* Did the hooks approve of the attribute? */
978 if (!hookEvent.keepAttr) {
979 continue;
980 }
981
982 /* Work around a security issue in jQuery 3.0 */
983 if (regExpTest(/\/>/i, value)) {
984 _removeAttribute(name, currentNode);
985 continue;
986 }
987
988 /* Sanitize attribute content to be template-safe */
989 if (SAFE_FOR_TEMPLATES) {
990 value = stringReplace(value, MUSTACHE_EXPR$$1, ' ');
991 value = stringReplace(value, ERB_EXPR$$1, ' ');
992 }
993
994 /* Is `value` valid for this attribute? */
995 var lcTag = currentNode.nodeName.toLowerCase();
996 if (!_isValidAttribute(lcTag, lcName, value)) {
997 continue;
998 }
999
1000 /* Handle invalid data-* attribute set by try-catching it */
1001 try {
1002 if (namespaceURI) {
1003 currentNode.setAttributeNS(namespaceURI, name, value);
1004 } else {
1005 /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
1006 currentNode.setAttribute(name, value);
1007 }
1008
1009 arrayPop(DOMPurify.removed);
1010 } catch (_) {}
1011 }
1012
1013 /* Execute a hook if present */
1014 _executeHook('afterSanitizeAttributes', currentNode, null);
1015 };
1016
1017 /**
1018 * _sanitizeShadowDOM
1019 *
1020 * @param {DocumentFragment} fragment to iterate over recursively
1021 */
1022 var _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
1023 var shadowNode = void 0;
1024 var shadowIterator = _createIterator(fragment);
1025
1026 /* Execute a hook if present */
1027 _executeHook('beforeSanitizeShadowDOM', fragment, null);
1028
1029 while (shadowNode = shadowIterator.nextNode()) {
1030 /* Execute a hook if present */
1031 _executeHook('uponSanitizeShadowNode', shadowNode, null);
1032
1033 /* Sanitize tags and elements */
1034 if (_sanitizeElements(shadowNode)) {
1035 continue;
1036 }
1037
1038 /* Deep shadow DOM detected */
1039 if (shadowNode.content instanceof DocumentFragment) {
1040 _sanitizeShadowDOM(shadowNode.content);
1041 }
1042
1043 /* Check attributes, sanitize if necessary */
1044 _sanitizeAttributes(shadowNode);
1045 }
1046
1047 /* Execute a hook if present */
1048 _executeHook('afterSanitizeShadowDOM', fragment, null);
1049 };
1050
1051 /**
1052 * Sanitize
1053 * Public method providing core sanitation functionality
1054 *
1055 * @param {String|Node} dirty string or DOM node
1056 * @param {Object} configuration object
1057 */
1058 // eslint-disable-next-line complexity
1059 DOMPurify.sanitize = function (dirty, cfg) {
1060 var body = void 0;
1061 var importedNode = void 0;
1062 var currentNode = void 0;
1063 var oldNode = void 0;
1064 var returnNode = void 0;
1065 /* Make sure we have a string to sanitize.
1066 DO NOT return early, as this will return the wrong type if
1067 the user has requested a DOM object rather than a string */
1068 if (!dirty) {
1069 dirty = '<!-->';
1070 }
1071
1072 /* Stringify, in case dirty is an object */
1073 if (typeof dirty !== 'string' && !_isNode(dirty)) {
1074 // eslint-disable-next-line no-negated-condition
1075 if (typeof dirty.toString !== 'function') {
1076 throw typeErrorCreate('toString is not a function');
1077 } else {
1078 dirty = dirty.toString();
1079 if (typeof dirty !== 'string') {
1080 throw typeErrorCreate('dirty is not a string, aborting');
1081 }
1082 }
1083 }
1084
1085 /* Check we can run. Otherwise fall back or ignore */
1086 if (!DOMPurify.isSupported) {
1087 if (_typeof(window.toStaticHTML) === 'object' || typeof window.toStaticHTML === 'function') {
1088 if (typeof dirty === 'string') {
1089 return window.toStaticHTML(dirty);
1090 }
1091
1092 if (_isNode(dirty)) {
1093 return window.toStaticHTML(dirty.outerHTML);
1094 }
1095 }
1096
1097 return dirty;
1098 }
1099
1100 /* Assign config vars */
1101 if (!SET_CONFIG) {
1102 _parseConfig(cfg);
1103 }
1104
1105 /* Clean up removed elements */
1106 DOMPurify.removed = [];
1107
1108 /* Check if dirty is correctly typed for IN_PLACE */
1109 if (typeof dirty === 'string') {
1110 IN_PLACE = false;
1111 }
1112
1113 if (IN_PLACE) ; else if (dirty instanceof Node) {
1114 /* If dirty is a DOM element, append to an empty document to avoid
1115 elements being stripped by the parser */
1116 body = _initDocument('<!---->');
1117 importedNode = body.ownerDocument.importNode(dirty, true);
1118 if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
1119 /* Node is already a body, use as is */
1120 body = importedNode;
1121 } else if (importedNode.nodeName === 'HTML') {
1122 body = importedNode;
1123 } else {
1124 // eslint-disable-next-line unicorn/prefer-node-append
1125 body.appendChild(importedNode);
1126 }
1127 } else {
1128 /* Exit directly if we have nothing to do */
1129 if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
1130 // eslint-disable-next-line unicorn/prefer-includes
1131 dirty.indexOf('<') === -1) {
1132 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
1133 }
1134
1135 /* Initialize the document to work on */
1136 body = _initDocument(dirty);
1137
1138 /* Check we have a DOM node from the data */
1139 if (!body) {
1140 return RETURN_DOM ? null : emptyHTML;
1141 }
1142 }
1143
1144 /* Remove first element node (ours) if FORCE_BODY is set */
1145 if (body && FORCE_BODY) {
1146 _forceRemove(body.firstChild);
1147 }
1148
1149 /* Get node iterator */
1150 var nodeIterator = _createIterator(IN_PLACE ? dirty : body);
1151
1152 /* Now start iterating over the created document */
1153 while (currentNode = nodeIterator.nextNode()) {
1154 /* Fix IE's strange behavior with manipulated textNodes #89 */
1155 if (currentNode.nodeType === 3 && currentNode === oldNode) {
1156 continue;
1157 }
1158
1159 /* Sanitize tags and elements */
1160 if (_sanitizeElements(currentNode)) {
1161 continue;
1162 }
1163
1164 /* Shadow DOM detected, sanitize it */
1165 if (currentNode.content instanceof DocumentFragment) {
1166 _sanitizeShadowDOM(currentNode.content);
1167 }
1168
1169 /* Check attributes, sanitize if necessary */
1170 _sanitizeAttributes(currentNode);
1171
1172 oldNode = currentNode;
1173 }
1174
1175 oldNode = null;
1176
1177 /* If we sanitized `dirty` in-place, return it. */
1178 if (IN_PLACE) {
1179 return dirty;
1180 }
1181
1182 /* Return sanitized string or DOM */
1183 if (RETURN_DOM) {
1184 if (RETURN_DOM_FRAGMENT) {
1185 returnNode = createDocumentFragment.call(body.ownerDocument);
1186
1187 while (body.firstChild) {
1188 // eslint-disable-next-line unicorn/prefer-node-append
1189 returnNode.appendChild(body.firstChild);
1190 }
1191 } else {
1192 returnNode = body;
1193 }
1194
1195 if (RETURN_DOM_IMPORT) {
1196 /*
1197 AdoptNode() is not used because internal state is not reset
1198 (e.g. the past names map of a HTMLFormElement), this is safe
1199 in theory but we would rather not risk another attack vector.
1200 The state that is cloned by importNode() is explicitly defined
1201 by the specs.
1202 */
1203 returnNode = importNode.call(originalDocument, returnNode, true);
1204 }
1205
1206 return returnNode;
1207 }
1208
1209 var serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
1210
1211 /* Sanitize final string template-safe */
1212 if (SAFE_FOR_TEMPLATES) {
1213 serializedHTML = stringReplace(serializedHTML, MUSTACHE_EXPR$$1, ' ');
1214 serializedHTML = stringReplace(serializedHTML, ERB_EXPR$$1, ' ');
1215 }
1216
1217 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
1218 };
1219
1220 /**
1221 * Public method to set the configuration once
1222 * setConfig
1223 *
1224 * @param {Object} cfg configuration object
1225 */
1226 DOMPurify.setConfig = function (cfg) {
1227 _parseConfig(cfg);
1228 SET_CONFIG = true;
1229 };
1230
1231 /**
1232 * Public method to remove the configuration
1233 * clearConfig
1234 *
1235 */
1236 DOMPurify.clearConfig = function () {
1237 CONFIG = null;
1238 SET_CONFIG = false;
1239 };
1240
1241 /**
1242 * Public method to check if an attribute value is valid.
1243 * Uses last set config, if any. Otherwise, uses config defaults.
1244 * isValidAttribute
1245 *
1246 * @param {string} tag Tag name of containing element.
1247 * @param {string} attr Attribute name.
1248 * @param {string} value Attribute value.
1249 * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
1250 */
1251 DOMPurify.isValidAttribute = function (tag, attr, value) {
1252 /* Initialize shared config vars if necessary. */
1253 if (!CONFIG) {
1254 _parseConfig({});
1255 }
1256
1257 var lcTag = stringToLowerCase(tag);
1258 var lcName = stringToLowerCase(attr);
1259 return _isValidAttribute(lcTag, lcName, value);
1260 };
1261
1262 /**
1263 * AddHook
1264 * Public method to add DOMPurify hooks
1265 *
1266 * @param {String} entryPoint entry point for the hook to add
1267 * @param {Function} hookFunction function to execute
1268 */
1269 DOMPurify.addHook = function (entryPoint, hookFunction) {
1270 if (typeof hookFunction !== 'function') {
1271 return;
1272 }
1273
1274 hooks[entryPoint] = hooks[entryPoint] || [];
1275 arrayPush(hooks[entryPoint], hookFunction);
1276 };
1277
1278 /**
1279 * RemoveHook
1280 * Public method to remove a DOMPurify hook at a given entryPoint
1281 * (pops it from the stack of hooks if more are present)
1282 *
1283 * @param {String} entryPoint entry point for the hook to remove
1284 */
1285 DOMPurify.removeHook = function (entryPoint) {
1286 if (hooks[entryPoint]) {
1287 arrayPop(hooks[entryPoint]);
1288 }
1289 };
1290
1291 /**
1292 * RemoveHooks
1293 * Public method to remove all DOMPurify hooks at a given entryPoint
1294 *
1295 * @param {String} entryPoint entry point for the hooks to remove
1296 */
1297 DOMPurify.removeHooks = function (entryPoint) {
1298 if (hooks[entryPoint]) {
1299 hooks[entryPoint] = [];
1300 }
1301 };
1302
1303 /**
1304 * RemoveAllHooks
1305 * Public method to remove all DOMPurify hooks
1306 *
1307 */
1308 DOMPurify.removeAllHooks = function () {
1309 hooks = {};
1310 };
1311
1312 return DOMPurify;
1313}
1314
1315var purify = createDOMPurify();
1316
1317module.exports = purify;
1318//# sourceMappingURL=purify.cjs.js.map