UNPKG

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