UNPKG

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