UNPKG

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