UNPKG

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