UNPKG

53.2 kBJavaScriptView Raw
1/*! @license DOMPurify 2.3.2 | (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.2/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.2';
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 = null;
413 var DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
414
415 /* Tags that are safe for data: URIs */
416 var DATA_URI_TAGS = null;
417 var DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
418
419 /* Attributes safe for values like "javascript:" */
420 var URI_SAFE_ATTRIBUTES = null;
421 var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
422
423 var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
424 var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
425 var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
426 /* Document namespace */
427 var NAMESPACE = HTML_NAMESPACE;
428 var IS_EMPTY_INPUT = false;
429
430 /* Parsing of strict XHTML documents */
431 var PARSER_MEDIA_TYPE = void 0;
432 var SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
433 var DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
434 var transformCaseFunc = void 0;
435
436 /* Keep a reference to config to pass to hooks */
437 var CONFIG = null;
438
439 /* Ideally, do not touch anything below this line */
440 /* ______________________________________________ */
441
442 var formElement = document.createElement('form');
443
444 /**
445 * _parseConfig
446 *
447 * @param {Object} cfg optional config literal
448 */
449 // eslint-disable-next-line complexity
450 var _parseConfig = function _parseConfig(cfg) {
451 if (CONFIG && CONFIG === cfg) {
452 return;
453 }
454
455 /* Shield configuration object from tampering */
456 if (!cfg || (typeof cfg === 'undefined' ? 'undefined' : _typeof(cfg)) !== 'object') {
457 cfg = {};
458 }
459
460 /* Shield configuration object from prototype pollution */
461 cfg = clone(cfg);
462
463 /* Set configuration parameters */
464 ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS;
465 ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR;
466 URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR) : DEFAULT_URI_SAFE_ATTRIBUTES;
467 DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS) : DEFAULT_DATA_URI_TAGS;
468 FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg ? addToSet({}, cfg.FORBID_CONTENTS) : DEFAULT_FORBID_CONTENTS;
469 FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {};
470 FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {};
471 USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
472 ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
473 ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
474 ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
475 SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
476 WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
477 RETURN_DOM = cfg.RETURN_DOM || false; // Default false
478 RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
479 RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT !== false; // Default true
480 RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
481 FORCE_BODY = cfg.FORCE_BODY || false; // Default false
482 SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
483 KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
484 IN_PLACE = cfg.IN_PLACE || false; // Default false
485 IS_ALLOWED_URI$$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1;
486 NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
487 PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE in SUPPORTED_PARSER_MEDIA_TYPES ? cfg.PARSER_MEDIA_TYPE : DEFAULT_PARSER_MEDIA_TYPE;
488 // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
489 transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? function (x) {
490 return x;
491 } : stringToLowerCase;
492
493 if (SAFE_FOR_TEMPLATES) {
494 ALLOW_DATA_ATTR = false;
495 }
496
497 if (RETURN_DOM_FRAGMENT) {
498 RETURN_DOM = true;
499 }
500
501 /* Parse profile info */
502 if (USE_PROFILES) {
503 ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray$1(text)));
504 ALLOWED_ATTR = [];
505 if (USE_PROFILES.html === true) {
506 addToSet(ALLOWED_TAGS, html);
507 addToSet(ALLOWED_ATTR, html$1);
508 }
509
510 if (USE_PROFILES.svg === true) {
511 addToSet(ALLOWED_TAGS, svg);
512 addToSet(ALLOWED_ATTR, svg$1);
513 addToSet(ALLOWED_ATTR, xml);
514 }
515
516 if (USE_PROFILES.svgFilters === true) {
517 addToSet(ALLOWED_TAGS, svgFilters);
518 addToSet(ALLOWED_ATTR, svg$1);
519 addToSet(ALLOWED_ATTR, xml);
520 }
521
522 if (USE_PROFILES.mathMl === true) {
523 addToSet(ALLOWED_TAGS, mathMl);
524 addToSet(ALLOWED_ATTR, mathMl$1);
525 addToSet(ALLOWED_ATTR, xml);
526 }
527 }
528
529 /* Merge configuration parameters */
530 if (cfg.ADD_TAGS) {
531 if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
532 ALLOWED_TAGS = clone(ALLOWED_TAGS);
533 }
534
535 addToSet(ALLOWED_TAGS, cfg.ADD_TAGS);
536 }
537
538 if (cfg.ADD_ATTR) {
539 if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
540 ALLOWED_ATTR = clone(ALLOWED_ATTR);
541 }
542
543 addToSet(ALLOWED_ATTR, cfg.ADD_ATTR);
544 }
545
546 if (cfg.ADD_URI_SAFE_ATTR) {
547 addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR);
548 }
549
550 if (cfg.FORBID_CONTENTS) {
551 if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
552 FORBID_CONTENTS = clone(FORBID_CONTENTS);
553 }
554
555 addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS);
556 }
557
558 /* Add #text in case KEEP_CONTENT is set to true */
559 if (KEEP_CONTENT) {
560 ALLOWED_TAGS['#text'] = true;
561 }
562
563 /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
564 if (WHOLE_DOCUMENT) {
565 addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
566 }
567
568 /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
569 if (ALLOWED_TAGS.table) {
570 addToSet(ALLOWED_TAGS, ['tbody']);
571 delete FORBID_TAGS.tbody;
572 }
573
574 // Prevent further manipulation of configuration.
575 // Not available in IE8, Safari 5, etc.
576 if (freeze) {
577 freeze(cfg);
578 }
579
580 CONFIG = cfg;
581 };
582
583 var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
584
585 var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']);
586
587 /* Keep track of all possible SVG and MathML tags
588 * so that we can perform the namespace checks
589 * correctly. */
590 var ALL_SVG_TAGS = addToSet({}, svg);
591 addToSet(ALL_SVG_TAGS, svgFilters);
592 addToSet(ALL_SVG_TAGS, svgDisallowed);
593
594 var ALL_MATHML_TAGS = addToSet({}, mathMl);
595 addToSet(ALL_MATHML_TAGS, mathMlDisallowed);
596
597 /**
598 *
599 *
600 * @param {Element} element a DOM element whose namespace is being checked
601 * @returns {boolean} Return false if the element has a
602 * namespace that a spec-compliant parser would never
603 * return. Return true otherwise.
604 */
605 var _checkValidNamespace = function _checkValidNamespace(element) {
606 var parent = getParentNode(element);
607
608 // In JSDOM, if we're inside shadow DOM, then parentNode
609 // can be null. We just simulate parent in this case.
610 if (!parent || !parent.tagName) {
611 parent = {
612 namespaceURI: HTML_NAMESPACE,
613 tagName: 'template'
614 };
615 }
616
617 var tagName = stringToLowerCase(element.tagName);
618 var parentTagName = stringToLowerCase(parent.tagName);
619
620 if (element.namespaceURI === SVG_NAMESPACE) {
621 // The only way to switch from HTML namespace to SVG
622 // is via <svg>. If it happens via any other tag, then
623 // it should be killed.
624 if (parent.namespaceURI === HTML_NAMESPACE) {
625 return tagName === 'svg';
626 }
627
628 // The only way to switch from MathML to SVG is via
629 // svg if parent is either <annotation-xml> or MathML
630 // text integration points.
631 if (parent.namespaceURI === MATHML_NAMESPACE) {
632 return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
633 }
634
635 // We only allow elements that are defined in SVG
636 // spec. All others are disallowed in SVG namespace.
637 return Boolean(ALL_SVG_TAGS[tagName]);
638 }
639
640 if (element.namespaceURI === MATHML_NAMESPACE) {
641 // The only way to switch from HTML namespace to MathML
642 // is via <math>. If it happens via any other tag, then
643 // it should be killed.
644 if (parent.namespaceURI === HTML_NAMESPACE) {
645 return tagName === 'math';
646 }
647
648 // The only way to switch from SVG to MathML is via
649 // <math> and HTML integration points
650 if (parent.namespaceURI === SVG_NAMESPACE) {
651 return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
652 }
653
654 // We only allow elements that are defined in MathML
655 // spec. All others are disallowed in MathML namespace.
656 return Boolean(ALL_MATHML_TAGS[tagName]);
657 }
658
659 if (element.namespaceURI === HTML_NAMESPACE) {
660 // The only way to switch from SVG to HTML is via
661 // HTML integration points, and from MathML to HTML
662 // is via MathML text integration points
663 if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
664 return false;
665 }
666
667 if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
668 return false;
669 }
670
671 // Certain elements are allowed in both SVG and HTML
672 // namespace. We need to specify them explicitly
673 // so that they don't get erronously deleted from
674 // HTML namespace.
675 var commonSvgAndHTMLElements = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
676
677 // We disallow tags that are specific for MathML
678 // or SVG and should never appear in HTML namespace
679 return !ALL_MATHML_TAGS[tagName] && (commonSvgAndHTMLElements[tagName] || !ALL_SVG_TAGS[tagName]);
680 }
681
682 // The code should never reach this place (this means
683 // that the element somehow got namespace that is not
684 // HTML, SVG or MathML). Return false just in case.
685 return false;
686 };
687
688 /**
689 * _forceRemove
690 *
691 * @param {Node} node a DOM node
692 */
693 var _forceRemove = function _forceRemove(node) {
694 arrayPush(DOMPurify.removed, { element: node });
695 try {
696 // eslint-disable-next-line unicorn/prefer-dom-node-remove
697 node.parentNode.removeChild(node);
698 } catch (_) {
699 try {
700 node.outerHTML = emptyHTML;
701 } catch (_) {
702 node.remove();
703 }
704 }
705 };
706
707 /**
708 * _removeAttribute
709 *
710 * @param {String} name an Attribute name
711 * @param {Node} node a DOM node
712 */
713 var _removeAttribute = function _removeAttribute(name, node) {
714 try {
715 arrayPush(DOMPurify.removed, {
716 attribute: node.getAttributeNode(name),
717 from: node
718 });
719 } catch (_) {
720 arrayPush(DOMPurify.removed, {
721 attribute: null,
722 from: node
723 });
724 }
725
726 node.removeAttribute(name);
727
728 // We void attribute values for unremovable "is"" attributes
729 if (name === 'is' && !ALLOWED_ATTR[name]) {
730 if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
731 try {
732 _forceRemove(node);
733 } catch (_) {}
734 } else {
735 try {
736 node.setAttribute(name, '');
737 } catch (_) {}
738 }
739 }
740 };
741
742 /**
743 * _initDocument
744 *
745 * @param {String} dirty a string of dirty markup
746 * @return {Document} a DOM, filled with the dirty markup
747 */
748 var _initDocument = function _initDocument(dirty) {
749 /* Create a HTML document */
750 var doc = void 0;
751 var leadingWhitespace = void 0;
752
753 if (FORCE_BODY) {
754 dirty = '<remove></remove>' + dirty;
755 } else {
756 /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
757 var matches = stringMatch(dirty, /^[\r\n\t ]+/);
758 leadingWhitespace = matches && matches[0];
759 }
760
761 if (PARSER_MEDIA_TYPE === 'application/xhtml+xml') {
762 // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
763 dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
764 }
765
766 var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
767 /*
768 * Use the DOMParser API by default, fallback later if needs be
769 * DOMParser not work for svg when has multiple root element.
770 */
771 if (NAMESPACE === HTML_NAMESPACE) {
772 try {
773 doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
774 } catch (_) {}
775 }
776
777 /* Use createHTMLDocument in case DOMParser is not available */
778 if (!doc || !doc.documentElement) {
779 doc = implementation.createDocument(NAMESPACE, 'template', null);
780 try {
781 doc.documentElement.innerHTML = IS_EMPTY_INPUT ? '' : dirtyPayload;
782 } catch (_) {
783 // Syntax error if dirtyPayload is invalid xml
784 }
785 }
786
787 var body = doc.body || doc.documentElement;
788
789 if (dirty && leadingWhitespace) {
790 body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
791 }
792
793 /* Work on whole document or just its body */
794 if (NAMESPACE === HTML_NAMESPACE) {
795 return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
796 }
797
798 return WHOLE_DOCUMENT ? doc.documentElement : body;
799 };
800
801 /**
802 * _createIterator
803 *
804 * @param {Document} root document/fragment to create iterator for
805 * @return {Iterator} iterator instance
806 */
807 var _createIterator = function _createIterator(root) {
808 return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null, false);
809 };
810
811 /**
812 * _isClobbered
813 *
814 * @param {Node} elm element to check for clobbering attacks
815 * @return {Boolean} true if clobbered, false if safe
816 */
817 var _isClobbered = function _isClobbered(elm) {
818 if (elm instanceof Text || elm instanceof Comment) {
819 return false;
820 }
821
822 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') {
823 return true;
824 }
825
826 return false;
827 };
828
829 /**
830 * _isNode
831 *
832 * @param {Node} obj object to check whether it's a DOM node
833 * @return {Boolean} true is object is a DOM node
834 */
835 var _isNode = function _isNode(object) {
836 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';
837 };
838
839 /**
840 * _executeHook
841 * Execute user configurable hooks
842 *
843 * @param {String} entryPoint Name of the hook's entry point
844 * @param {Node} currentNode node to work on with the hook
845 * @param {Object} data additional hook parameters
846 */
847 var _executeHook = function _executeHook(entryPoint, currentNode, data) {
848 if (!hooks[entryPoint]) {
849 return;
850 }
851
852 arrayForEach(hooks[entryPoint], function (hook) {
853 hook.call(DOMPurify, currentNode, data, CONFIG);
854 });
855 };
856
857 /**
858 * _sanitizeElements
859 *
860 * @protect nodeName
861 * @protect textContent
862 * @protect removeChild
863 *
864 * @param {Node} currentNode to check for permission to exist
865 * @return {Boolean} true if node was killed, false if left alive
866 */
867 var _sanitizeElements = function _sanitizeElements(currentNode) {
868 var content = void 0;
869
870 /* Execute a hook if present */
871 _executeHook('beforeSanitizeElements', currentNode, null);
872
873 /* Check if element is clobbered or can clobber */
874 if (_isClobbered(currentNode)) {
875 _forceRemove(currentNode);
876 return true;
877 }
878
879 /* Check if tagname contains Unicode */
880 if (stringMatch(currentNode.nodeName, /[\u0080-\uFFFF]/)) {
881 _forceRemove(currentNode);
882 return true;
883 }
884
885 /* Now let's check the element's type and name */
886 var tagName = transformCaseFunc(currentNode.nodeName);
887
888 /* Execute a hook if present */
889 _executeHook('uponSanitizeElement', currentNode, {
890 tagName: tagName,
891 allowedTags: ALLOWED_TAGS
892 });
893
894 /* Detect mXSS attempts abusing namespace confusion */
895 if (!_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
896 _forceRemove(currentNode);
897 return true;
898 }
899
900 /* Mitigate a problem with templates inside select */
901 if (tagName === 'select' && regExpTest(/<template/i, currentNode.innerHTML)) {
902 _forceRemove(currentNode);
903 return true;
904 }
905
906 /* Remove element if anything forbids its presence */
907 if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
908 /* Keep content except for bad-listed elements */
909 if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
910 var parentNode = getParentNode(currentNode) || currentNode.parentNode;
911 var childNodes = getChildNodes(currentNode) || currentNode.childNodes;
912
913 if (childNodes && parentNode) {
914 var childCount = childNodes.length;
915
916 for (var i = childCount - 1; i >= 0; --i) {
917 parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
918 }
919 }
920 }
921
922 _forceRemove(currentNode);
923 return true;
924 }
925
926 /* Check whether element has a valid namespace */
927 if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
928 _forceRemove(currentNode);
929 return true;
930 }
931
932 if ((tagName === 'noscript' || tagName === 'noembed') && regExpTest(/<\/no(script|embed)/i, currentNode.innerHTML)) {
933 _forceRemove(currentNode);
934 return true;
935 }
936
937 /* Sanitize element content to be template-safe */
938 if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
939 /* Get the element's text content */
940 content = currentNode.textContent;
941 content = stringReplace(content, MUSTACHE_EXPR$$1, ' ');
942 content = stringReplace(content, ERB_EXPR$$1, ' ');
943 if (currentNode.textContent !== content) {
944 arrayPush(DOMPurify.removed, { element: currentNode.cloneNode() });
945 currentNode.textContent = content;
946 }
947 }
948
949 /* Execute a hook if present */
950 _executeHook('afterSanitizeElements', currentNode, null);
951
952 return false;
953 };
954
955 /**
956 * _isValidAttribute
957 *
958 * @param {string} lcTag Lowercase tag name of containing element.
959 * @param {string} lcName Lowercase attribute name.
960 * @param {string} value Attribute value.
961 * @return {Boolean} Returns true if `value` is valid, otherwise false.
962 */
963 // eslint-disable-next-line complexity
964 var _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
965 /* Make sure attribute cannot clobber */
966 if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
967 return false;
968 }
969
970 /* Allow valid data-* attributes: At least one character after "-"
971 (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
972 XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
973 We don't need to check the value; it's always URI safe. */
974 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]) {
975 return false;
976
977 /* Check value is safe. First, is attr inert? If so, is safe */
978 } 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 {
979 return false;
980 }
981
982 return true;
983 };
984
985 /**
986 * _sanitizeAttributes
987 *
988 * @protect attributes
989 * @protect nodeName
990 * @protect removeAttribute
991 * @protect setAttribute
992 *
993 * @param {Node} currentNode to sanitize
994 */
995 var _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
996 var attr = void 0;
997 var value = void 0;
998 var lcName = void 0;
999 var l = void 0;
1000 /* Execute a hook if present */
1001 _executeHook('beforeSanitizeAttributes', currentNode, null);
1002
1003 var attributes = currentNode.attributes;
1004
1005 /* Check if we have attributes; if not we might have a text node */
1006
1007 if (!attributes) {
1008 return;
1009 }
1010
1011 var hookEvent = {
1012 attrName: '',
1013 attrValue: '',
1014 keepAttr: true,
1015 allowedAttributes: ALLOWED_ATTR
1016 };
1017 l = attributes.length;
1018
1019 /* Go backwards over all attributes; safely remove bad ones */
1020 while (l--) {
1021 attr = attributes[l];
1022 var _attr = attr,
1023 name = _attr.name,
1024 namespaceURI = _attr.namespaceURI;
1025
1026 value = stringTrim(attr.value);
1027 lcName = transformCaseFunc(name);
1028
1029 /* Execute a hook if present */
1030 hookEvent.attrName = lcName;
1031 hookEvent.attrValue = value;
1032 hookEvent.keepAttr = true;
1033 hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
1034 _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
1035 value = hookEvent.attrValue;
1036 /* Did the hooks approve of the attribute? */
1037 if (hookEvent.forceKeepAttr) {
1038 continue;
1039 }
1040
1041 /* Remove attribute */
1042 _removeAttribute(name, currentNode);
1043
1044 /* Did the hooks approve of the attribute? */
1045 if (!hookEvent.keepAttr) {
1046 continue;
1047 }
1048
1049 /* Work around a security issue in jQuery 3.0 */
1050 if (regExpTest(/\/>/i, value)) {
1051 _removeAttribute(name, currentNode);
1052 continue;
1053 }
1054
1055 /* Sanitize attribute content to be template-safe */
1056 if (SAFE_FOR_TEMPLATES) {
1057 value = stringReplace(value, MUSTACHE_EXPR$$1, ' ');
1058 value = stringReplace(value, ERB_EXPR$$1, ' ');
1059 }
1060
1061 /* Is `value` valid for this attribute? */
1062 var lcTag = transformCaseFunc(currentNode.nodeName);
1063 if (!_isValidAttribute(lcTag, lcName, value)) {
1064 continue;
1065 }
1066
1067 /* Handle invalid data-* attribute set by try-catching it */
1068 try {
1069 if (namespaceURI) {
1070 currentNode.setAttributeNS(namespaceURI, name, value);
1071 } else {
1072 /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
1073 currentNode.setAttribute(name, value);
1074 }
1075
1076 arrayPop(DOMPurify.removed);
1077 } catch (_) {}
1078 }
1079
1080 /* Execute a hook if present */
1081 _executeHook('afterSanitizeAttributes', currentNode, null);
1082 };
1083
1084 /**
1085 * _sanitizeShadowDOM
1086 *
1087 * @param {DocumentFragment} fragment to iterate over recursively
1088 */
1089 var _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
1090 var shadowNode = void 0;
1091 var shadowIterator = _createIterator(fragment);
1092
1093 /* Execute a hook if present */
1094 _executeHook('beforeSanitizeShadowDOM', fragment, null);
1095
1096 while (shadowNode = shadowIterator.nextNode()) {
1097 /* Execute a hook if present */
1098 _executeHook('uponSanitizeShadowNode', shadowNode, null);
1099
1100 /* Sanitize tags and elements */
1101 if (_sanitizeElements(shadowNode)) {
1102 continue;
1103 }
1104
1105 /* Deep shadow DOM detected */
1106 if (shadowNode.content instanceof DocumentFragment) {
1107 _sanitizeShadowDOM(shadowNode.content);
1108 }
1109
1110 /* Check attributes, sanitize if necessary */
1111 _sanitizeAttributes(shadowNode);
1112 }
1113
1114 /* Execute a hook if present */
1115 _executeHook('afterSanitizeShadowDOM', fragment, null);
1116 };
1117
1118 /**
1119 * Sanitize
1120 * Public method providing core sanitation functionality
1121 *
1122 * @param {String|Node} dirty string or DOM node
1123 * @param {Object} configuration object
1124 */
1125 // eslint-disable-next-line complexity
1126 DOMPurify.sanitize = function (dirty, cfg) {
1127 var body = void 0;
1128 var importedNode = void 0;
1129 var currentNode = void 0;
1130 var oldNode = void 0;
1131 var returnNode = void 0;
1132 /* Make sure we have a string to sanitize.
1133 DO NOT return early, as this will return the wrong type if
1134 the user has requested a DOM object rather than a string */
1135 IS_EMPTY_INPUT = !dirty;
1136 if (IS_EMPTY_INPUT) {
1137 dirty = '<!-->';
1138 }
1139
1140 /* Stringify, in case dirty is an object */
1141 if (typeof dirty !== 'string' && !_isNode(dirty)) {
1142 // eslint-disable-next-line no-negated-condition
1143 if (typeof dirty.toString !== 'function') {
1144 throw typeErrorCreate('toString is not a function');
1145 } else {
1146 dirty = dirty.toString();
1147 if (typeof dirty !== 'string') {
1148 throw typeErrorCreate('dirty is not a string, aborting');
1149 }
1150 }
1151 }
1152
1153 /* Check we can run. Otherwise fall back or ignore */
1154 if (!DOMPurify.isSupported) {
1155 if (_typeof(window.toStaticHTML) === 'object' || typeof window.toStaticHTML === 'function') {
1156 if (typeof dirty === 'string') {
1157 return window.toStaticHTML(dirty);
1158 }
1159
1160 if (_isNode(dirty)) {
1161 return window.toStaticHTML(dirty.outerHTML);
1162 }
1163 }
1164
1165 return dirty;
1166 }
1167
1168 /* Assign config vars */
1169 if (!SET_CONFIG) {
1170 _parseConfig(cfg);
1171 }
1172
1173 /* Clean up removed elements */
1174 DOMPurify.removed = [];
1175
1176 /* Check if dirty is correctly typed for IN_PLACE */
1177 if (typeof dirty === 'string') {
1178 IN_PLACE = false;
1179 }
1180
1181 if (IN_PLACE) ; else if (dirty instanceof Node) {
1182 /* If dirty is a DOM element, append to an empty document to avoid
1183 elements being stripped by the parser */
1184 body = _initDocument('<!---->');
1185 importedNode = body.ownerDocument.importNode(dirty, true);
1186 if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
1187 /* Node is already a body, use as is */
1188 body = importedNode;
1189 } else if (importedNode.nodeName === 'HTML') {
1190 body = importedNode;
1191 } else {
1192 // eslint-disable-next-line unicorn/prefer-dom-node-append
1193 body.appendChild(importedNode);
1194 }
1195 } else {
1196 /* Exit directly if we have nothing to do */
1197 if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
1198 // eslint-disable-next-line unicorn/prefer-includes
1199 dirty.indexOf('<') === -1) {
1200 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
1201 }
1202
1203 /* Initialize the document to work on */
1204 body = _initDocument(dirty);
1205
1206 /* Check we have a DOM node from the data */
1207 if (!body) {
1208 return RETURN_DOM ? null : emptyHTML;
1209 }
1210 }
1211
1212 /* Remove first element node (ours) if FORCE_BODY is set */
1213 if (body && FORCE_BODY) {
1214 _forceRemove(body.firstChild);
1215 }
1216
1217 /* Get node iterator */
1218 var nodeIterator = _createIterator(IN_PLACE ? dirty : body);
1219
1220 /* Now start iterating over the created document */
1221 while (currentNode = nodeIterator.nextNode()) {
1222 /* Fix IE's strange behavior with manipulated textNodes #89 */
1223 if (currentNode.nodeType === 3 && currentNode === oldNode) {
1224 continue;
1225 }
1226
1227 /* Sanitize tags and elements */
1228 if (_sanitizeElements(currentNode)) {
1229 continue;
1230 }
1231
1232 /* Shadow DOM detected, sanitize it */
1233 if (currentNode.content instanceof DocumentFragment) {
1234 _sanitizeShadowDOM(currentNode.content);
1235 }
1236
1237 /* Check attributes, sanitize if necessary */
1238 _sanitizeAttributes(currentNode);
1239
1240 oldNode = currentNode;
1241 }
1242
1243 oldNode = null;
1244
1245 /* If we sanitized `dirty` in-place, return it. */
1246 if (IN_PLACE) {
1247 return dirty;
1248 }
1249
1250 /* Return sanitized string or DOM */
1251 if (RETURN_DOM) {
1252 if (RETURN_DOM_FRAGMENT) {
1253 returnNode = createDocumentFragment.call(body.ownerDocument);
1254
1255 while (body.firstChild) {
1256 // eslint-disable-next-line unicorn/prefer-dom-node-append
1257 returnNode.appendChild(body.firstChild);
1258 }
1259 } else {
1260 returnNode = body;
1261 }
1262
1263 if (RETURN_DOM_IMPORT) {
1264 /*
1265 AdoptNode() is not used because internal state is not reset
1266 (e.g. the past names map of a HTMLFormElement), this is safe
1267 in theory but we would rather not risk another attack vector.
1268 The state that is cloned by importNode() is explicitly defined
1269 by the specs.
1270 */
1271 returnNode = importNode.call(originalDocument, returnNode, true);
1272 }
1273
1274 return returnNode;
1275 }
1276
1277 var serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
1278
1279 /* Sanitize final string template-safe */
1280 if (SAFE_FOR_TEMPLATES) {
1281 serializedHTML = stringReplace(serializedHTML, MUSTACHE_EXPR$$1, ' ');
1282 serializedHTML = stringReplace(serializedHTML, ERB_EXPR$$1, ' ');
1283 }
1284
1285 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
1286 };
1287
1288 /**
1289 * Public method to set the configuration once
1290 * setConfig
1291 *
1292 * @param {Object} cfg configuration object
1293 */
1294 DOMPurify.setConfig = function (cfg) {
1295 _parseConfig(cfg);
1296 SET_CONFIG = true;
1297 };
1298
1299 /**
1300 * Public method to remove the configuration
1301 * clearConfig
1302 *
1303 */
1304 DOMPurify.clearConfig = function () {
1305 CONFIG = null;
1306 SET_CONFIG = false;
1307 };
1308
1309 /**
1310 * Public method to check if an attribute value is valid.
1311 * Uses last set config, if any. Otherwise, uses config defaults.
1312 * isValidAttribute
1313 *
1314 * @param {string} tag Tag name of containing element.
1315 * @param {string} attr Attribute name.
1316 * @param {string} value Attribute value.
1317 * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
1318 */
1319 DOMPurify.isValidAttribute = function (tag, attr, value) {
1320 /* Initialize shared config vars if necessary. */
1321 if (!CONFIG) {
1322 _parseConfig({});
1323 }
1324
1325 var lcTag = transformCaseFunc(tag);
1326 var lcName = transformCaseFunc(attr);
1327 return _isValidAttribute(lcTag, lcName, value);
1328 };
1329
1330 /**
1331 * AddHook
1332 * Public method to add DOMPurify hooks
1333 *
1334 * @param {String} entryPoint entry point for the hook to add
1335 * @param {Function} hookFunction function to execute
1336 */
1337 DOMPurify.addHook = function (entryPoint, hookFunction) {
1338 if (typeof hookFunction !== 'function') {
1339 return;
1340 }
1341
1342 hooks[entryPoint] = hooks[entryPoint] || [];
1343 arrayPush(hooks[entryPoint], hookFunction);
1344 };
1345
1346 /**
1347 * RemoveHook
1348 * Public method to remove a DOMPurify hook at a given entryPoint
1349 * (pops it from the stack of hooks if more are present)
1350 *
1351 * @param {String} entryPoint entry point for the hook to remove
1352 */
1353 DOMPurify.removeHook = function (entryPoint) {
1354 if (hooks[entryPoint]) {
1355 arrayPop(hooks[entryPoint]);
1356 }
1357 };
1358
1359 /**
1360 * RemoveHooks
1361 * Public method to remove all DOMPurify hooks at a given entryPoint
1362 *
1363 * @param {String} entryPoint entry point for the hooks to remove
1364 */
1365 DOMPurify.removeHooks = function (entryPoint) {
1366 if (hooks[entryPoint]) {
1367 hooks[entryPoint] = [];
1368 }
1369 };
1370
1371 /**
1372 * RemoveAllHooks
1373 * Public method to remove all DOMPurify hooks
1374 *
1375 */
1376 DOMPurify.removeAllHooks = function () {
1377 hooks = {};
1378 };
1379
1380 return DOMPurify;
1381}
1382
1383var purify = createDOMPurify();
1384
1385export default purify;
1386//# sourceMappingURL=purify.es.js.map