UNPKG

46.7 kBJavaScriptView Raw
1/*! @license DOMPurify | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.0.8/LICENSE */
2
3'use strict';
4
5function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
6
7var hasOwnProperty = Object.hasOwnProperty,
8 setPrototypeOf = Object.setPrototypeOf,
9 isFrozen = Object.isFrozen,
10 objectKeys = Object.keys;
11var freeze = Object.freeze,
12 seal = Object.seal; // eslint-disable-line import/no-mutable-exports
13
14var _ref = typeof Reflect !== 'undefined' && Reflect,
15 apply = _ref.apply,
16 construct = _ref.construct;
17
18if (!apply) {
19 apply = function apply(fun, thisValue, args) {
20 return fun.apply(thisValue, args);
21 };
22}
23
24if (!freeze) {
25 freeze = function freeze(x) {
26 return x;
27 };
28}
29
30if (!seal) {
31 seal = function seal(x) {
32 return x;
33 };
34}
35
36if (!construct) {
37 construct = function construct(Func, args) {
38 return new (Function.prototype.bind.apply(Func, [null].concat(_toConsumableArray(args))))();
39 };
40}
41
42var arrayForEach = unapply(Array.prototype.forEach);
43var arrayIndexOf = unapply(Array.prototype.indexOf);
44var arrayJoin = unapply(Array.prototype.join);
45var arrayPop = unapply(Array.prototype.pop);
46var arrayPush = unapply(Array.prototype.push);
47var arraySlice = unapply(Array.prototype.slice);
48
49var stringToLowerCase = unapply(String.prototype.toLowerCase);
50var stringMatch = unapply(String.prototype.match);
51var stringReplace = unapply(String.prototype.replace);
52var stringIndexOf = unapply(String.prototype.indexOf);
53var stringTrim = unapply(String.prototype.trim);
54
55var regExpTest = unapply(RegExp.prototype.test);
56var regExpCreate = unconstruct(RegExp);
57
58var typeErrorCreate = unconstruct(TypeError);
59
60function unapply(func) {
61 return function (thisArg) {
62 for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
63 args[_key - 1] = arguments[_key];
64 }
65
66 return apply(func, thisArg, args);
67 };
68}
69
70function unconstruct(func) {
71 return function () {
72 for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
73 args[_key2] = arguments[_key2];
74 }
75
76 return construct(func, args);
77 };
78}
79
80/* Add properties to a lookup table */
81function addToSet(set, array) {
82 if (setPrototypeOf) {
83 // Make 'in' and truthy checks like Boolean(set.constructor)
84 // independent of any properties defined on Object.prototype.
85 // Prevent prototype setters from intercepting set as a this value.
86 setPrototypeOf(set, null);
87 }
88
89 var l = array.length;
90 while (l--) {
91 var element = array[l];
92 if (typeof element === 'string') {
93 var lcElement = stringToLowerCase(element);
94 if (lcElement !== element) {
95 // Config presets (e.g. tags.js, attrs.js) are immutable.
96 if (!isFrozen(array)) {
97 array[l] = lcElement;
98 }
99
100 element = lcElement;
101 }
102 }
103
104 set[element] = true;
105 }
106
107 return set;
108}
109
110/* Shallow clone an object */
111function clone(object) {
112 var newObject = {};
113
114 var property = void 0;
115 for (property in object) {
116 if (apply(hasOwnProperty, object, [property])) {
117 newObject[property] = object[property];
118 }
119 }
120
121 return newObject;
122}
123
124var 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', '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']);
125
126// SVG
127var svg = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'audio', 'canvas', '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', 'video', 'view', 'vkern']);
128
129var 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']);
130
131var 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']);
132
133var text = freeze(['#text']);
134
135var 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']);
136
137var svg$1 = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', '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', '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']);
138
139var 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']);
140
141var xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
142
143// eslint-disable-next-line unicorn/better-regex
144var MUSTACHE_EXPR = seal(/\{\{[\s\S]*|[\s\S]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
145var ERB_EXPR = seal(/<%[\s\S]*|[\s\S]*%>/gm);
146var DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape
147var ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
148var 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
149);
150var IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
151var ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g // eslint-disable-line no-control-regex
152);
153
154var _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; };
155
156function _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); } }
157
158var getGlobal = function getGlobal() {
159 return typeof window === 'undefined' ? null : window;
160};
161
162/**
163 * Creates a no-op policy for internal use only.
164 * Don't export this function outside this module!
165 * @param {?TrustedTypePolicyFactory} trustedTypes The policy factory.
166 * @param {Document} document The document object (to determine policy name suffix)
167 * @return {?TrustedTypePolicy} The policy created (or null, if Trusted Types
168 * are not supported).
169 */
170var _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, document) {
171 if ((typeof trustedTypes === 'undefined' ? 'undefined' : _typeof(trustedTypes)) !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
172 return null;
173 }
174
175 // Allow the callers to control the unique policy name
176 // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
177 // Policy creation with duplicate names throws in Trusted Types.
178 var suffix = null;
179 var ATTR_NAME = 'data-tt-policy-suffix';
180 if (document.currentScript && document.currentScript.hasAttribute(ATTR_NAME)) {
181 suffix = document.currentScript.getAttribute(ATTR_NAME);
182 }
183
184 var policyName = 'dompurify' + (suffix ? '#' + suffix : '');
185
186 try {
187 return trustedTypes.createPolicy(policyName, {
188 createHTML: function createHTML(html$$1) {
189 return html$$1;
190 }
191 });
192 } catch (_) {
193 // Policy creation failed (most likely another DOMPurify script has
194 // already run). Skip creating the policy, as this will only cause errors
195 // if TT are enforced.
196 console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
197 return null;
198 }
199};
200
201function createDOMPurify() {
202 var window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
203
204 var DOMPurify = function DOMPurify(root) {
205 return createDOMPurify(root);
206 };
207
208 /**
209 * Version label, exposed for easier checks
210 * if DOMPurify is up to date or not
211 */
212 DOMPurify.version = '2.0.12';
213
214 /**
215 * Array of elements that DOMPurify removed during sanitation.
216 * Empty if nothing was removed.
217 */
218 DOMPurify.removed = [];
219
220 if (!window || !window.document || window.document.nodeType !== 9) {
221 // Not running in a browser, provide a factory function
222 // so that you can pass your own Window
223 DOMPurify.isSupported = false;
224
225 return DOMPurify;
226 }
227
228 var originalDocument = window.document;
229 var removeTitle = false;
230
231 var document = window.document;
232 var DocumentFragment = window.DocumentFragment,
233 HTMLTemplateElement = window.HTMLTemplateElement,
234 Node = window.Node,
235 NodeFilter = window.NodeFilter,
236 _window$NamedNodeMap = window.NamedNodeMap,
237 NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
238 Text = window.Text,
239 Comment = window.Comment,
240 DOMParser = window.DOMParser,
241 trustedTypes = window.trustedTypes;
242
243 // As per issue #47, the web-components registry is inherited by a
244 // new document created via createHTMLDocument. As per the spec
245 // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
246 // a new empty registry is used when creating a template contents owner
247 // document, so we use that as our parent document to ensure nothing
248 // is inherited.
249
250 if (typeof HTMLTemplateElement === 'function') {
251 var template = document.createElement('template');
252 if (template.content && template.content.ownerDocument) {
253 document = template.content.ownerDocument;
254 }
255 }
256
257 var trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, originalDocument);
258 var emptyHTML = trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML('') : '';
259
260 var _document = document,
261 implementation = _document.implementation,
262 createNodeIterator = _document.createNodeIterator,
263 getElementsByTagName = _document.getElementsByTagName,
264 createDocumentFragment = _document.createDocumentFragment;
265 var importNode = originalDocument.importNode;
266
267
268 var hooks = {};
269
270 /**
271 * Expose whether this browser supports running the full DOMPurify.
272 */
273 DOMPurify.isSupported = implementation && typeof implementation.createHTMLDocument !== 'undefined' && document.documentMode !== 9;
274
275 var MUSTACHE_EXPR$$1 = MUSTACHE_EXPR,
276 ERB_EXPR$$1 = ERB_EXPR,
277 DATA_ATTR$$1 = DATA_ATTR,
278 ARIA_ATTR$$1 = ARIA_ATTR,
279 IS_SCRIPT_OR_DATA$$1 = IS_SCRIPT_OR_DATA,
280 ATTR_WHITESPACE$$1 = ATTR_WHITESPACE;
281 var IS_ALLOWED_URI$$1 = IS_ALLOWED_URI;
282
283 /**
284 * We consider the elements and attributes below to be safe. Ideally
285 * don't add any new ones but feel free to remove unwanted ones.
286 */
287
288 /* allowed element names */
289
290 var ALLOWED_TAGS = null;
291 var DEFAULT_ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray$1(html), _toConsumableArray$1(svg), _toConsumableArray$1(svgFilters), _toConsumableArray$1(mathMl), _toConsumableArray$1(text)));
292
293 /* Allowed attribute names */
294 var ALLOWED_ATTR = null;
295 var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray$1(html$1), _toConsumableArray$1(svg$1), _toConsumableArray$1(mathMl$1), _toConsumableArray$1(xml)));
296
297 /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
298 var FORBID_TAGS = null;
299
300 /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
301 var FORBID_ATTR = null;
302
303 /* Decide if ARIA attributes are okay */
304 var ALLOW_ARIA_ATTR = true;
305
306 /* Decide if custom data attributes are okay */
307 var ALLOW_DATA_ATTR = true;
308
309 /* Decide if unknown protocols are okay */
310 var ALLOW_UNKNOWN_PROTOCOLS = false;
311
312 /* Output should be safe for jQuery's $() factory? */
313 var SAFE_FOR_JQUERY = false;
314
315 /* Output should be safe for common template engines.
316 * This means, DOMPurify removes data attributes, mustaches and ERB
317 */
318 var SAFE_FOR_TEMPLATES = false;
319
320 /* Decide if document with <html>... should be returned */
321 var WHOLE_DOCUMENT = false;
322
323 /* Track whether config is already set on this instance of DOMPurify. */
324 var SET_CONFIG = false;
325
326 /* Decide if all elements (e.g. style, script) must be children of
327 * document.body. By default, browsers might move them to document.head */
328 var FORCE_BODY = false;
329
330 /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
331 * string (or a TrustedHTML object if Trusted Types are supported).
332 * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
333 */
334 var RETURN_DOM = false;
335
336 /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
337 * string (or a TrustedHTML object if Trusted Types are supported) */
338 var RETURN_DOM_FRAGMENT = false;
339
340 /* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM
341 * `Node` is imported into the current `Document`. If this flag is not enabled the
342 * `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by
343 * DOMPurify. */
344 var RETURN_DOM_IMPORT = false;
345
346 /* Try to return a Trusted Type object instead of a string, retrun a string in
347 * case Trusted Types are not supported */
348 var RETURN_TRUSTED_TYPE = false;
349
350 /* Output should be free from DOM clobbering attacks? */
351 var SANITIZE_DOM = true;
352
353 /* Keep element content when removing element? */
354 var KEEP_CONTENT = true;
355
356 /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
357 * of importing it into a new Document and returning a sanitized copy */
358 var IN_PLACE = false;
359
360 /* Allow usage of profiles like html, svg and mathMl */
361 var USE_PROFILES = {};
362
363 /* Tags to ignore content of when KEEP_CONTENT is true */
364 var FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
365
366 /* Tags that are safe for data: URIs */
367 var DATA_URI_TAGS = null;
368 var DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
369
370 /* Attributes safe for values like "javascript:" */
371 var URI_SAFE_ATTRIBUTES = null;
372 var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'summary', 'title', 'value', 'style', 'xmlns']);
373
374 /* Keep a reference to config to pass to hooks */
375 var CONFIG = null;
376
377 /* Ideally, do not touch anything below this line */
378 /* ______________________________________________ */
379
380 var formElement = document.createElement('form');
381
382 /**
383 * _parseConfig
384 *
385 * @param {Object} cfg optional config literal
386 */
387 // eslint-disable-next-line complexity
388 var _parseConfig = function _parseConfig(cfg) {
389 if (CONFIG && CONFIG === cfg) {
390 return;
391 }
392
393 /* Shield configuration object from tampering */
394 if (!cfg || (typeof cfg === 'undefined' ? 'undefined' : _typeof(cfg)) !== 'object') {
395 cfg = {};
396 }
397
398 /* Set configuration parameters */
399 ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS;
400 ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR;
401 URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR) : DEFAULT_URI_SAFE_ATTRIBUTES;
402 DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS) : DEFAULT_DATA_URI_TAGS;
403 FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {};
404 FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {};
405 USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
406 ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
407 ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
408 ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
409 SAFE_FOR_JQUERY = cfg.SAFE_FOR_JQUERY || false; // Default false
410 SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
411 WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
412 RETURN_DOM = cfg.RETURN_DOM || false; // Default false
413 RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
414 RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT || false; // Default false
415 RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
416 FORCE_BODY = cfg.FORCE_BODY || false; // Default false
417 SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
418 KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
419 IN_PLACE = cfg.IN_PLACE || false; // Default false
420 IS_ALLOWED_URI$$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1;
421 if (SAFE_FOR_TEMPLATES) {
422 ALLOW_DATA_ATTR = false;
423 }
424
425 if (RETURN_DOM_FRAGMENT) {
426 RETURN_DOM = true;
427 }
428
429 /* Parse profile info */
430 if (USE_PROFILES) {
431 ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray$1(text)));
432 ALLOWED_ATTR = [];
433 if (USE_PROFILES.html === true) {
434 addToSet(ALLOWED_TAGS, html);
435 addToSet(ALLOWED_ATTR, html$1);
436 }
437
438 if (USE_PROFILES.svg === true) {
439 addToSet(ALLOWED_TAGS, svg);
440 addToSet(ALLOWED_ATTR, svg$1);
441 addToSet(ALLOWED_ATTR, xml);
442 }
443
444 if (USE_PROFILES.svgFilters === true) {
445 addToSet(ALLOWED_TAGS, svgFilters);
446 addToSet(ALLOWED_ATTR, svg$1);
447 addToSet(ALLOWED_ATTR, xml);
448 }
449
450 if (USE_PROFILES.mathMl === true) {
451 addToSet(ALLOWED_TAGS, mathMl);
452 addToSet(ALLOWED_ATTR, mathMl$1);
453 addToSet(ALLOWED_ATTR, xml);
454 }
455 }
456
457 /* Merge configuration parameters */
458 if (cfg.ADD_TAGS) {
459 if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
460 ALLOWED_TAGS = clone(ALLOWED_TAGS);
461 }
462
463 addToSet(ALLOWED_TAGS, cfg.ADD_TAGS);
464 }
465
466 if (cfg.ADD_ATTR) {
467 if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
468 ALLOWED_ATTR = clone(ALLOWED_ATTR);
469 }
470
471 addToSet(ALLOWED_ATTR, cfg.ADD_ATTR);
472 }
473
474 if (cfg.ADD_URI_SAFE_ATTR) {
475 addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR);
476 }
477
478 /* Add #text in case KEEP_CONTENT is set to true */
479 if (KEEP_CONTENT) {
480 ALLOWED_TAGS['#text'] = true;
481 }
482
483 /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
484 if (WHOLE_DOCUMENT) {
485 addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
486 }
487
488 /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
489 if (ALLOWED_TAGS.table) {
490 addToSet(ALLOWED_TAGS, ['tbody']);
491 delete FORBID_TAGS.tbody;
492 }
493
494 // Prevent further manipulation of configuration.
495 // Not available in IE8, Safari 5, etc.
496 if (freeze) {
497 freeze(cfg);
498 }
499
500 CONFIG = cfg;
501 };
502
503 /**
504 * _forceRemove
505 *
506 * @param {Node} node a DOM node
507 */
508 var _forceRemove = function _forceRemove(node) {
509 arrayPush(DOMPurify.removed, { element: node });
510 try {
511 // eslint-disable-next-line unicorn/prefer-node-remove
512 node.parentNode.removeChild(node);
513 } catch (_) {
514 node.outerHTML = emptyHTML;
515 }
516 };
517
518 /**
519 * _removeAttribute
520 *
521 * @param {String} name an Attribute name
522 * @param {Node} node a DOM node
523 */
524 var _removeAttribute = function _removeAttribute(name, node) {
525 try {
526 arrayPush(DOMPurify.removed, {
527 attribute: node.getAttributeNode(name),
528 from: node
529 });
530 } catch (_) {
531 arrayPush(DOMPurify.removed, {
532 attribute: null,
533 from: node
534 });
535 }
536
537 node.removeAttribute(name);
538 };
539
540 /**
541 * _initDocument
542 *
543 * @param {String} dirty a string of dirty markup
544 * @return {Document} a DOM, filled with the dirty markup
545 */
546 var _initDocument = function _initDocument(dirty) {
547 /* Create a HTML document */
548 var doc = void 0;
549 var leadingWhitespace = void 0;
550
551 if (FORCE_BODY) {
552 dirty = '<remove></remove>' + dirty;
553 } else {
554 /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
555 var matches = stringMatch(dirty, /^[\r\n\t ]+/);
556 leadingWhitespace = matches && matches[0];
557 }
558
559 var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
560 /* Use the DOMParser API by default, fallback later if needs be */
561 try {
562 doc = new DOMParser().parseFromString(dirtyPayload, 'text/html');
563 } catch (_) {}
564
565 /* Remove title to fix a mXSS bug in older MS Edge */
566 if (removeTitle) {
567 addToSet(FORBID_TAGS, ['title']);
568 }
569
570 /* Use createHTMLDocument in case DOMParser is not available */
571 if (!doc || !doc.documentElement) {
572 doc = implementation.createHTMLDocument('');
573 var _doc = doc,
574 body = _doc.body;
575
576 body.parentNode.removeChild(body.parentNode.firstElementChild);
577 body.outerHTML = dirtyPayload;
578 }
579
580 if (dirty && leadingWhitespace) {
581 doc.body.insertBefore(document.createTextNode(leadingWhitespace), doc.body.childNodes[0] || null);
582 }
583
584 /* Work on whole document or just its body */
585 return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
586 };
587
588 /* Here we test for a broken feature in Edge that might cause mXSS */
589 if (DOMPurify.isSupported) {
590 (function () {
591 try {
592 var doc = _initDocument('<x/><title>&lt;/title&gt;&lt;img&gt;');
593 if (regExpTest(/<\/title/, doc.querySelector('title').innerHTML)) {
594 removeTitle = true;
595 }
596 } catch (_) {}
597 })();
598 }
599
600 /**
601 * _createIterator
602 *
603 * @param {Document} root document/fragment to create iterator for
604 * @return {Iterator} iterator instance
605 */
606 var _createIterator = function _createIterator(root) {
607 return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, function () {
608 return NodeFilter.FILTER_ACCEPT;
609 }, false);
610 };
611
612 /**
613 * _isClobbered
614 *
615 * @param {Node} elm element to check for clobbering attacks
616 * @return {Boolean} true if clobbered, false if safe
617 */
618 var _isClobbered = function _isClobbered(elm) {
619 if (elm instanceof Text || elm instanceof Comment) {
620 return false;
621 }
622
623 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') {
624 return true;
625 }
626
627 return false;
628 };
629
630 /**
631 * _isNode
632 *
633 * @param {Node} obj object to check whether it's a DOM node
634 * @return {Boolean} true is object is a DOM node
635 */
636 var _isNode = function _isNode(object) {
637 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';
638 };
639
640 /**
641 * _executeHook
642 * Execute user configurable hooks
643 *
644 * @param {String} entryPoint Name of the hook's entry point
645 * @param {Node} currentNode node to work on with the hook
646 * @param {Object} data additional hook parameters
647 */
648 var _executeHook = function _executeHook(entryPoint, currentNode, data) {
649 if (!hooks[entryPoint]) {
650 return;
651 }
652
653 arrayForEach(hooks[entryPoint], function (hook) {
654 hook.call(DOMPurify, currentNode, data, CONFIG);
655 });
656 };
657
658 /**
659 * _sanitizeElements
660 *
661 * @protect nodeName
662 * @protect textContent
663 * @protect removeChild
664 *
665 * @param {Node} currentNode to check for permission to exist
666 * @return {Boolean} true if node was killed, false if left alive
667 */
668 // eslint-disable-next-line complexity
669 var _sanitizeElements = function _sanitizeElements(currentNode) {
670 var content = void 0;
671
672 /* Execute a hook if present */
673 _executeHook('beforeSanitizeElements', currentNode, null);
674
675 /* Check if element is clobbered or can clobber */
676 if (_isClobbered(currentNode)) {
677 _forceRemove(currentNode);
678 return true;
679 }
680
681 /* Now let's check the element's type and name */
682 var tagName = stringToLowerCase(currentNode.nodeName);
683
684 /* Execute a hook if present */
685 _executeHook('uponSanitizeElement', currentNode, {
686 tagName: tagName,
687 allowedTags: ALLOWED_TAGS
688 });
689
690 /* Take care of an mXSS pattern using p, br inside svg, math */
691 if ((tagName === 'svg' || tagName === 'math') && currentNode.querySelectorAll('p, br').length !== 0) {
692 _forceRemove(currentNode);
693 return true;
694 }
695
696 /* Remove element if anything forbids its presence */
697 if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
698 /* Keep content except for bad-listed elements */
699 if (KEEP_CONTENT && !FORBID_CONTENTS[tagName] && typeof currentNode.insertAdjacentHTML === 'function') {
700 try {
701 var htmlToInsert = currentNode.innerHTML;
702 currentNode.insertAdjacentHTML('AfterEnd', trustedTypesPolicy ? trustedTypesPolicy.createHTML(htmlToInsert) : htmlToInsert);
703 } catch (_) {}
704 }
705
706 _forceRemove(currentNode);
707 return true;
708 }
709
710 /* Remove in case a noscript/noembed XSS is suspected */
711 if (tagName === 'noscript' && regExpTest(/<\/noscript/i, currentNode.innerHTML)) {
712 _forceRemove(currentNode);
713 return true;
714 }
715
716 if (tagName === 'noembed' && regExpTest(/<\/noembed/i, currentNode.innerHTML)) {
717 _forceRemove(currentNode);
718 return true;
719 }
720
721 /* Convert markup to cover jQuery behavior */
722 if (SAFE_FOR_JQUERY && !currentNode.firstElementChild && (!currentNode.content || !currentNode.content.firstElementChild) && regExpTest(/</g, currentNode.textContent)) {
723 arrayPush(DOMPurify.removed, { element: currentNode.cloneNode() });
724 if (currentNode.innerHTML) {
725 currentNode.innerHTML = stringReplace(currentNode.innerHTML, /</g, '&lt;');
726 } else {
727 currentNode.innerHTML = stringReplace(currentNode.textContent, /</g, '&lt;');
728 }
729 }
730
731 /* Sanitize element content to be template-safe */
732 if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
733 /* Get the element's text content */
734 content = currentNode.textContent;
735 content = stringReplace(content, MUSTACHE_EXPR$$1, ' ');
736 content = stringReplace(content, ERB_EXPR$$1, ' ');
737 if (currentNode.textContent !== content) {
738 arrayPush(DOMPurify.removed, { element: currentNode.cloneNode() });
739 currentNode.textContent = content;
740 }
741 }
742
743 /* Execute a hook if present */
744 _executeHook('afterSanitizeElements', currentNode, null);
745
746 return false;
747 };
748
749 /**
750 * _isValidAttribute
751 *
752 * @param {string} lcTag Lowercase tag name of containing element.
753 * @param {string} lcName Lowercase attribute name.
754 * @param {string} value Attribute value.
755 * @return {Boolean} Returns true if `value` is valid, otherwise false.
756 */
757 // eslint-disable-next-line complexity
758 var _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
759 /* Make sure attribute cannot clobber */
760 if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
761 return false;
762 }
763
764 /* Allow valid data-* attributes: At least one character after "-"
765 (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
766 XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
767 We don't need to check the value; it's always URI safe. */
768 if (ALLOW_DATA_ATTR && regExpTest(DATA_ATTR$$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$$1, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
769 return false;
770
771 /* Check value is safe. First, is attr inert? If so, is safe */
772 } 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 {
773 return false;
774 }
775
776 return true;
777 };
778
779 /**
780 * _sanitizeAttributes
781 *
782 * @protect attributes
783 * @protect nodeName
784 * @protect removeAttribute
785 * @protect setAttribute
786 *
787 * @param {Node} currentNode to sanitize
788 */
789 // eslint-disable-next-line complexity
790 var _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
791 var attr = void 0;
792 var value = void 0;
793 var lcName = void 0;
794 var idAttr = void 0;
795 var l = void 0;
796 /* Execute a hook if present */
797 _executeHook('beforeSanitizeAttributes', currentNode, null);
798
799 var attributes = currentNode.attributes;
800
801 /* Check if we have attributes; if not we might have a text node */
802
803 if (!attributes) {
804 return;
805 }
806
807 var hookEvent = {
808 attrName: '',
809 attrValue: '',
810 keepAttr: true,
811 allowedAttributes: ALLOWED_ATTR
812 };
813 l = attributes.length;
814
815 /* Go backwards over all attributes; safely remove bad ones */
816 while (l--) {
817 attr = attributes[l];
818 var _attr = attr,
819 name = _attr.name,
820 namespaceURI = _attr.namespaceURI;
821
822 value = stringTrim(attr.value);
823 lcName = stringToLowerCase(name);
824
825 /* Execute a hook if present */
826 hookEvent.attrName = lcName;
827 hookEvent.attrValue = value;
828 hookEvent.keepAttr = true;
829 hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
830 _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
831 value = hookEvent.attrValue;
832 /* Did the hooks approve of the attribute? */
833 if (hookEvent.forceKeepAttr) {
834 continue;
835 }
836
837 /* Remove attribute */
838 // Safari (iOS + Mac), last tested v8.0.5, crashes if you try to
839 // remove a "name" attribute from an <img> tag that has an "id"
840 // attribute at the time.
841 if (lcName === 'name' && currentNode.nodeName === 'IMG' && attributes.id) {
842 idAttr = attributes.id;
843 attributes = arraySlice(attributes, []);
844 _removeAttribute('id', currentNode);
845 _removeAttribute(name, currentNode);
846 if (arrayIndexOf(attributes, idAttr) > l) {
847 currentNode.setAttribute('id', idAttr.value);
848 }
849 } else if (
850 // This works around a bug in Safari, where input[type=file]
851 // cannot be dynamically set after type has been removed
852 currentNode.nodeName === 'INPUT' && lcName === 'type' && value === 'file' && hookEvent.keepAttr && (ALLOWED_ATTR[lcName] || !FORBID_ATTR[lcName])) {
853 continue;
854 } else {
855 // This avoids a crash in Safari v9.0 with double-ids.
856 // The trick is to first set the id to be empty and then to
857 // remove the attribute
858 if (name === 'id') {
859 currentNode.setAttribute(name, '');
860 }
861
862 _removeAttribute(name, currentNode);
863 }
864
865 /* Did the hooks approve of the attribute? */
866 if (!hookEvent.keepAttr) {
867 continue;
868 }
869
870 /* Work around a security issue in jQuery 3.0 */
871 if (SAFE_FOR_JQUERY && regExpTest(/\/>/i, value)) {
872 _removeAttribute(name, currentNode);
873 continue;
874 }
875
876 /* Take care of an mXSS pattern using namespace switches */
877 if (regExpTest(/svg|math/i, currentNode.namespaceURI) && regExpTest(regExpCreate('</(' + arrayJoin(objectKeys(FORBID_CONTENTS), '|') + ')', 'i'), value)) {
878 _removeAttribute(name, currentNode);
879 continue;
880 }
881
882 /* Sanitize attribute content to be template-safe */
883 if (SAFE_FOR_TEMPLATES) {
884 value = stringReplace(value, MUSTACHE_EXPR$$1, ' ');
885 value = stringReplace(value, ERB_EXPR$$1, ' ');
886 }
887
888 /* Is `value` valid for this attribute? */
889 var lcTag = currentNode.nodeName.toLowerCase();
890 if (!_isValidAttribute(lcTag, lcName, value)) {
891 continue;
892 }
893
894 /* Handle invalid data-* attribute set by try-catching it */
895 try {
896 if (namespaceURI) {
897 currentNode.setAttributeNS(namespaceURI, name, value);
898 } else {
899 /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
900 currentNode.setAttribute(name, value);
901 }
902
903 arrayPop(DOMPurify.removed);
904 } catch (_) {}
905 }
906
907 /* Execute a hook if present */
908 _executeHook('afterSanitizeAttributes', currentNode, null);
909 };
910
911 /**
912 * _sanitizeShadowDOM
913 *
914 * @param {DocumentFragment} fragment to iterate over recursively
915 */
916 var _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
917 var shadowNode = void 0;
918 var shadowIterator = _createIterator(fragment);
919
920 /* Execute a hook if present */
921 _executeHook('beforeSanitizeShadowDOM', fragment, null);
922
923 while (shadowNode = shadowIterator.nextNode()) {
924 /* Execute a hook if present */
925 _executeHook('uponSanitizeShadowNode', shadowNode, null);
926
927 /* Sanitize tags and elements */
928 if (_sanitizeElements(shadowNode)) {
929 continue;
930 }
931
932 /* Deep shadow DOM detected */
933 if (shadowNode.content instanceof DocumentFragment) {
934 _sanitizeShadowDOM(shadowNode.content);
935 }
936
937 /* Check attributes, sanitize if necessary */
938 _sanitizeAttributes(shadowNode);
939 }
940
941 /* Execute a hook if present */
942 _executeHook('afterSanitizeShadowDOM', fragment, null);
943 };
944
945 /**
946 * Sanitize
947 * Public method providing core sanitation functionality
948 *
949 * @param {String|Node} dirty string or DOM node
950 * @param {Object} configuration object
951 */
952 // eslint-disable-next-line complexity
953 DOMPurify.sanitize = function (dirty, cfg) {
954 var body = void 0;
955 var importedNode = void 0;
956 var currentNode = void 0;
957 var oldNode = void 0;
958 var returnNode = void 0;
959 /* Make sure we have a string to sanitize.
960 DO NOT return early, as this will return the wrong type if
961 the user has requested a DOM object rather than a string */
962 if (!dirty) {
963 dirty = '<!-->';
964 }
965
966 /* Stringify, in case dirty is an object */
967 if (typeof dirty !== 'string' && !_isNode(dirty)) {
968 // eslint-disable-next-line no-negated-condition
969 if (typeof dirty.toString !== 'function') {
970 throw typeErrorCreate('toString is not a function');
971 } else {
972 dirty = dirty.toString();
973 if (typeof dirty !== 'string') {
974 throw typeErrorCreate('dirty is not a string, aborting');
975 }
976 }
977 }
978
979 /* Check we can run. Otherwise fall back or ignore */
980 if (!DOMPurify.isSupported) {
981 if (_typeof(window.toStaticHTML) === 'object' || typeof window.toStaticHTML === 'function') {
982 if (typeof dirty === 'string') {
983 return window.toStaticHTML(dirty);
984 }
985
986 if (_isNode(dirty)) {
987 return window.toStaticHTML(dirty.outerHTML);
988 }
989 }
990
991 return dirty;
992 }
993
994 /* Assign config vars */
995 if (!SET_CONFIG) {
996 _parseConfig(cfg);
997 }
998
999 /* Clean up removed elements */
1000 DOMPurify.removed = [];
1001
1002 /* Check if dirty is correctly typed for IN_PLACE */
1003 if (typeof dirty === 'string') {
1004 IN_PLACE = false;
1005 }
1006
1007 if (IN_PLACE) ; else if (dirty instanceof Node) {
1008 /* If dirty is a DOM element, append to an empty document to avoid
1009 elements being stripped by the parser */
1010 body = _initDocument('<!-->');
1011 importedNode = body.ownerDocument.importNode(dirty, true);
1012 if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
1013 /* Node is already a body, use as is */
1014 body = importedNode;
1015 } else if (importedNode.nodeName === 'HTML') {
1016 body = importedNode;
1017 } else {
1018 // eslint-disable-next-line unicorn/prefer-node-append
1019 body.appendChild(importedNode);
1020 }
1021 } else {
1022 /* Exit directly if we have nothing to do */
1023 if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
1024 // eslint-disable-next-line unicorn/prefer-includes
1025 dirty.indexOf('<') === -1) {
1026 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
1027 }
1028
1029 /* Initialize the document to work on */
1030 body = _initDocument(dirty);
1031
1032 /* Check we have a DOM node from the data */
1033 if (!body) {
1034 return RETURN_DOM ? null : emptyHTML;
1035 }
1036 }
1037
1038 /* Remove first element node (ours) if FORCE_BODY is set */
1039 if (body && FORCE_BODY) {
1040 _forceRemove(body.firstChild);
1041 }
1042
1043 /* Get node iterator */
1044 var nodeIterator = _createIterator(IN_PLACE ? dirty : body);
1045
1046 /* Now start iterating over the created document */
1047 while (currentNode = nodeIterator.nextNode()) {
1048 /* Fix IE's strange behavior with manipulated textNodes #89 */
1049 if (currentNode.nodeType === 3 && currentNode === oldNode) {
1050 continue;
1051 }
1052
1053 /* Sanitize tags and elements */
1054 if (_sanitizeElements(currentNode)) {
1055 continue;
1056 }
1057
1058 /* Shadow DOM detected, sanitize it */
1059 if (currentNode.content instanceof DocumentFragment) {
1060 _sanitizeShadowDOM(currentNode.content);
1061 }
1062
1063 /* Check attributes, sanitize if necessary */
1064 _sanitizeAttributes(currentNode);
1065
1066 oldNode = currentNode;
1067 }
1068
1069 oldNode = null;
1070
1071 /* If we sanitized `dirty` in-place, return it. */
1072 if (IN_PLACE) {
1073 return dirty;
1074 }
1075
1076 /* Return sanitized string or DOM */
1077 if (RETURN_DOM) {
1078 if (RETURN_DOM_FRAGMENT) {
1079 returnNode = createDocumentFragment.call(body.ownerDocument);
1080
1081 while (body.firstChild) {
1082 // eslint-disable-next-line unicorn/prefer-node-append
1083 returnNode.appendChild(body.firstChild);
1084 }
1085 } else {
1086 returnNode = body;
1087 }
1088
1089 if (RETURN_DOM_IMPORT) {
1090 /*
1091 AdoptNode() is not used because internal state is not reset
1092 (e.g. the past names map of a HTMLFormElement), this is safe
1093 in theory but we would rather not risk another attack vector.
1094 The state that is cloned by importNode() is explicitly defined
1095 by the specs.
1096 */
1097 returnNode = importNode.call(originalDocument, returnNode, true);
1098 }
1099
1100 return returnNode;
1101 }
1102
1103 var serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
1104
1105 /* Sanitize final string template-safe */
1106 if (SAFE_FOR_TEMPLATES) {
1107 serializedHTML = stringReplace(serializedHTML, MUSTACHE_EXPR$$1, ' ');
1108 serializedHTML = stringReplace(serializedHTML, ERB_EXPR$$1, ' ');
1109 }
1110
1111 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
1112 };
1113
1114 /**
1115 * Public method to set the configuration once
1116 * setConfig
1117 *
1118 * @param {Object} cfg configuration object
1119 */
1120 DOMPurify.setConfig = function (cfg) {
1121 _parseConfig(cfg);
1122 SET_CONFIG = true;
1123 };
1124
1125 /**
1126 * Public method to remove the configuration
1127 * clearConfig
1128 *
1129 */
1130 DOMPurify.clearConfig = function () {
1131 CONFIG = null;
1132 SET_CONFIG = false;
1133 };
1134
1135 /**
1136 * Public method to check if an attribute value is valid.
1137 * Uses last set config, if any. Otherwise, uses config defaults.
1138 * isValidAttribute
1139 *
1140 * @param {string} tag Tag name of containing element.
1141 * @param {string} attr Attribute name.
1142 * @param {string} value Attribute value.
1143 * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
1144 */
1145 DOMPurify.isValidAttribute = function (tag, attr, value) {
1146 /* Initialize shared config vars if necessary. */
1147 if (!CONFIG) {
1148 _parseConfig({});
1149 }
1150
1151 var lcTag = stringToLowerCase(tag);
1152 var lcName = stringToLowerCase(attr);
1153 return _isValidAttribute(lcTag, lcName, value);
1154 };
1155
1156 /**
1157 * AddHook
1158 * Public method to add DOMPurify hooks
1159 *
1160 * @param {String} entryPoint entry point for the hook to add
1161 * @param {Function} hookFunction function to execute
1162 */
1163 DOMPurify.addHook = function (entryPoint, hookFunction) {
1164 if (typeof hookFunction !== 'function') {
1165 return;
1166 }
1167
1168 hooks[entryPoint] = hooks[entryPoint] || [];
1169 arrayPush(hooks[entryPoint], hookFunction);
1170 };
1171
1172 /**
1173 * RemoveHook
1174 * Public method to remove a DOMPurify hook at a given entryPoint
1175 * (pops it from the stack of hooks if more are present)
1176 *
1177 * @param {String} entryPoint entry point for the hook to remove
1178 */
1179 DOMPurify.removeHook = function (entryPoint) {
1180 if (hooks[entryPoint]) {
1181 arrayPop(hooks[entryPoint]);
1182 }
1183 };
1184
1185 /**
1186 * RemoveHooks
1187 * Public method to remove all DOMPurify hooks at a given entryPoint
1188 *
1189 * @param {String} entryPoint entry point for the hooks to remove
1190 */
1191 DOMPurify.removeHooks = function (entryPoint) {
1192 if (hooks[entryPoint]) {
1193 hooks[entryPoint] = [];
1194 }
1195 };
1196
1197 /**
1198 * RemoveAllHooks
1199 * Public method to remove all DOMPurify hooks
1200 *
1201 */
1202 DOMPurify.removeAllHooks = function () {
1203 hooks = {};
1204 };
1205
1206 return DOMPurify;
1207}
1208
1209var purify = createDOMPurify();
1210
1211module.exports = purify;
1212//# sourceMappingURL=purify.cjs.js.map
1213
\No newline at end of file