UNPKG

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