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