UNPKG

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