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