UNPKG

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