dompurify 0.9.0 → 1.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,954 @@
1
+ 'use strict';
2
+
3
+ var html = ['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', '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'];
4
+
5
+ // SVG
6
+ var svg = ['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'];
7
+
8
+ var svgFilters = ['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'feSpecularLighting', 'feTile', 'feTurbulence'];
9
+
10
+ var mathMl = ['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmuliscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mpspace', 'msqrt', 'mystyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover'];
11
+
12
+ var text = ['#text'];
13
+
14
+ var html$1 = ['accept', 'action', 'align', 'alt', 'autocomplete', 'background', 'bgcolor', 'border', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'coords', 'crossorigin', 'datetime', 'default', 'dir', 'disabled', 'download', 'enctype', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'integrity', 'ismap', 'label', 'lang', 'list', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'multiple', 'name', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', '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', 'type', 'usemap', 'valign', 'value', 'width', 'xmlns'];
15
+
16
+ var svg$1 = ['accent-height', 'accumulate', 'additivive', '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', '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', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'specularconstant', 'specularexponent', 'spreadmethod', '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', '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'];
17
+
18
+ var mathMl$1 = ['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', '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'];
19
+
20
+ var xml = ['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink'];
21
+
22
+ /* Add properties to a lookup table */
23
+ function addToSet(set, array) {
24
+ var l = array.length;
25
+ while (l--) {
26
+ if (typeof array[l] === 'string') {
27
+ array[l] = array[l].toLowerCase();
28
+ }
29
+ set[array[l]] = true;
30
+ }
31
+ return set;
32
+ }
33
+
34
+ /* Shallow clone an object */
35
+ function clone(object) {
36
+ var newObject = {};
37
+ var property = void 0;
38
+ for (property in object) {
39
+ if (Object.prototype.hasOwnProperty.call(object, property)) {
40
+ newObject[property] = object[property];
41
+ }
42
+ }
43
+ return newObject;
44
+ }
45
+
46
+ var MUSTACHE_EXPR = /\{\{[\s\S]*|[\s\S]*\}\}/gm; // Specify template detection regex for SAFE_FOR_TEMPLATES mode
47
+ var ERB_EXPR = /<%[\s\S]*|[\s\S]*%>/gm;
48
+ var DATA_ATTR = /^data-[\-\w.\u00B7-\uFFFF]/; // eslint-disable-line no-useless-escape
49
+ var ARIA_ATTR = /^aria-[\-\w]+$/; // eslint-disable-line no-useless-escape
50
+ var IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i; // eslint-disable-line no-useless-escape
51
+ var IS_SCRIPT_OR_DATA = /^(?:\w+script|data):/i;
52
+ var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g; // This needs to be extensive thanks to Webkit/Blink's behavior
53
+
54
+ 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; };
55
+
56
+ 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); } }
57
+
58
+ var getGlobal = function getGlobal() {
59
+ return typeof window === 'undefined' ? null : window;
60
+ };
61
+
62
+ function createDOMPurify() {
63
+ var window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
64
+
65
+ var DOMPurify = function DOMPurify(root) {
66
+ return createDOMPurify(root);
67
+ };
68
+
69
+ /**
70
+ * Version label, exposed for easier checks
71
+ * if DOMPurify is up to date or not
72
+ */
73
+ DOMPurify.version = '1.0.3';
74
+
75
+ /**
76
+ * Array of elements that DOMPurify removed during sanitation.
77
+ * Empty if nothing was removed.
78
+ */
79
+ DOMPurify.removed = [];
80
+
81
+ if (!window || !window.document || window.document.nodeType !== 9) {
82
+ // Not running in a browser, provide a factory function
83
+ // so that you can pass your own Window
84
+ DOMPurify.isSupported = false;
85
+
86
+ return DOMPurify;
87
+ }
88
+
89
+ var originalDocument = window.document;
90
+ var useDOMParser = false; // See comment below
91
+ var useXHR = false;
92
+
93
+ var document = window.document;
94
+ var DocumentFragment = window.DocumentFragment,
95
+ HTMLTemplateElement = window.HTMLTemplateElement,
96
+ Node = window.Node,
97
+ NodeFilter = window.NodeFilter,
98
+ _window$NamedNodeMap = window.NamedNodeMap,
99
+ NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
100
+ Text = window.Text,
101
+ Comment = window.Comment,
102
+ DOMParser = window.DOMParser,
103
+ _window$XMLHttpReques = window.XMLHttpRequest,
104
+ XMLHttpRequest = _window$XMLHttpReques === undefined ? window.XMLHttpRequest : _window$XMLHttpReques,
105
+ _window$encodeURI = window.encodeURI,
106
+ encodeURI = _window$encodeURI === undefined ? window.encodeURI : _window$encodeURI;
107
+
108
+ // As per issue #47, the web-components registry is inherited by a
109
+ // new document created via createHTMLDocument. As per the spec
110
+ // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
111
+ // a new empty registry is used when creating a template contents owner
112
+ // document, so we use that as our parent document to ensure nothing
113
+ // is inherited.
114
+
115
+ if (typeof HTMLTemplateElement === 'function') {
116
+ var template = document.createElement('template');
117
+ if (template.content && template.content.ownerDocument) {
118
+ document = template.content.ownerDocument;
119
+ }
120
+ }
121
+
122
+ var _document = document,
123
+ implementation = _document.implementation,
124
+ createNodeIterator = _document.createNodeIterator,
125
+ getElementsByTagName = _document.getElementsByTagName,
126
+ createDocumentFragment = _document.createDocumentFragment;
127
+
128
+ var importNode = originalDocument.importNode;
129
+
130
+ var hooks = {};
131
+
132
+ /**
133
+ * Expose whether this browser supports running the full DOMPurify.
134
+ */
135
+ DOMPurify.isSupported = implementation && typeof implementation.createHTMLDocument !== 'undefined' && document.documentMode !== 9;
136
+
137
+ var MUSTACHE_EXPR$$1 = MUSTACHE_EXPR,
138
+ ERB_EXPR$$1 = ERB_EXPR,
139
+ DATA_ATTR$$1 = DATA_ATTR,
140
+ ARIA_ATTR$$1 = ARIA_ATTR,
141
+ IS_SCRIPT_OR_DATA$$1 = IS_SCRIPT_OR_DATA,
142
+ ATTR_WHITESPACE$$1 = ATTR_WHITESPACE;
143
+
144
+
145
+ var IS_ALLOWED_URI$$1 = IS_ALLOWED_URI;
146
+ /**
147
+ * We consider the elements and attributes below to be safe. Ideally
148
+ * don't add any new ones but feel free to remove unwanted ones.
149
+ */
150
+
151
+ /* allowed element names */
152
+ var ALLOWED_TAGS = null;
153
+ var DEFAULT_ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray(html), _toConsumableArray(svg), _toConsumableArray(svgFilters), _toConsumableArray(mathMl), _toConsumableArray(text)));
154
+
155
+ /* Allowed attribute names */
156
+ var ALLOWED_ATTR = null;
157
+ var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray(html$1), _toConsumableArray(svg$1), _toConsumableArray(mathMl$1), _toConsumableArray(xml)));
158
+
159
+ /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
160
+ var FORBID_TAGS = null;
161
+
162
+ /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
163
+ var FORBID_ATTR = null;
164
+
165
+ /* Decide if ARIA attributes are okay */
166
+ var ALLOW_ARIA_ATTR = true;
167
+
168
+ /* Decide if custom data attributes are okay */
169
+ var ALLOW_DATA_ATTR = true;
170
+
171
+ /* Decide if unknown protocols are okay */
172
+ var ALLOW_UNKNOWN_PROTOCOLS = false;
173
+
174
+ /* Output should be safe for jQuery's $() factory? */
175
+ var SAFE_FOR_JQUERY = false;
176
+
177
+ /* Output should be safe for common template engines.
178
+ * This means, DOMPurify removes data attributes, mustaches and ERB
179
+ */
180
+ var SAFE_FOR_TEMPLATES = false;
181
+
182
+ /* Decide if document with <html>... should be returned */
183
+ var WHOLE_DOCUMENT = false;
184
+
185
+ /* Track whether config is already set on this instance of DOMPurify. */
186
+ var SET_CONFIG = false;
187
+
188
+ /* Decide if all elements (e.g. style, script) must be children of
189
+ * document.body. By default, browsers might move them to document.head */
190
+ var FORCE_BODY = false;
191
+
192
+ /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html string.
193
+ * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
194
+ */
195
+ var RETURN_DOM = false;
196
+
197
+ /* Decide if a DOM `DocumentFragment` should be returned, instead of a html string */
198
+ var RETURN_DOM_FRAGMENT = false;
199
+
200
+ /* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM
201
+ * `Node` is imported into the current `Document`. If this flag is not enabled the
202
+ * `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by
203
+ * DOMPurify. */
204
+ var RETURN_DOM_IMPORT = false;
205
+
206
+ /* Output should be free from DOM clobbering attacks? */
207
+ var SANITIZE_DOM = true;
208
+
209
+ /* Keep element content when removing element? */
210
+ var KEEP_CONTENT = true;
211
+
212
+ /* Allow usage of profiles like html, svg and mathMl */
213
+ var USE_PROFILES = {};
214
+
215
+ /* Tags to ignore content of when KEEP_CONTENT is true */
216
+ var FORBID_CONTENTS = addToSet({}, ['audio', 'head', 'math', 'script', 'style', 'template', 'svg', 'video']);
217
+
218
+ /* Tags that are safe for data: URIs */
219
+ var DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image']);
220
+
221
+ /* Attributes safe for values like "javascript:" */
222
+ var URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'summary', 'title', 'value', 'style', 'xmlns']);
223
+
224
+ /* Keep a reference to config to pass to hooks */
225
+ var CONFIG = null;
226
+
227
+ /* Ideally, do not touch anything below this line */
228
+ /* ______________________________________________ */
229
+
230
+ var formElement = document.createElement('form');
231
+
232
+ /**
233
+ * _parseConfig
234
+ *
235
+ * @param optional config literal
236
+ */
237
+ // eslint-disable-next-line complexity
238
+ var _parseConfig = function _parseConfig(cfg) {
239
+ /* Shield configuration object from tampering */
240
+ if ((typeof cfg === 'undefined' ? 'undefined' : _typeof(cfg)) !== 'object') {
241
+ cfg = {};
242
+ }
243
+ /* Set configuration parameters */
244
+ ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS;
245
+ ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR;
246
+ FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {};
247
+ FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {};
248
+ USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
249
+ ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
250
+ ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
251
+ ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
252
+ SAFE_FOR_JQUERY = cfg.SAFE_FOR_JQUERY || false; // Default false
253
+ SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
254
+ WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
255
+ RETURN_DOM = cfg.RETURN_DOM || false; // Default false
256
+ RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
257
+ RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT || false; // Default false
258
+ FORCE_BODY = cfg.FORCE_BODY || false; // Default false
259
+ SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
260
+ KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
261
+
262
+ IS_ALLOWED_URI$$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1;
263
+
264
+ if (SAFE_FOR_TEMPLATES) {
265
+ ALLOW_DATA_ATTR = false;
266
+ }
267
+
268
+ if (RETURN_DOM_FRAGMENT) {
269
+ RETURN_DOM = true;
270
+ }
271
+
272
+ /* Parse profile info */
273
+ if (USE_PROFILES) {
274
+ ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray(text)));
275
+ ALLOWED_ATTR = [];
276
+ if (USE_PROFILES.html === true) {
277
+ addToSet(ALLOWED_TAGS, html);
278
+ addToSet(ALLOWED_ATTR, html$1);
279
+ }
280
+ if (USE_PROFILES.svg === true) {
281
+ addToSet(ALLOWED_TAGS, svg);
282
+ addToSet(ALLOWED_ATTR, svg$1);
283
+ addToSet(ALLOWED_ATTR, xml);
284
+ }
285
+ if (USE_PROFILES.svgFilters === true) {
286
+ addToSet(ALLOWED_TAGS, svgFilters);
287
+ addToSet(ALLOWED_ATTR, svg$1);
288
+ addToSet(ALLOWED_ATTR, xml);
289
+ }
290
+ if (USE_PROFILES.mathMl === true) {
291
+ addToSet(ALLOWED_TAGS, mathMl);
292
+ addToSet(ALLOWED_ATTR, mathMl$1);
293
+ addToSet(ALLOWED_ATTR, xml);
294
+ }
295
+ }
296
+
297
+ /* Merge configuration parameters */
298
+ if (cfg.ADD_TAGS) {
299
+ if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
300
+ ALLOWED_TAGS = clone(ALLOWED_TAGS);
301
+ }
302
+ addToSet(ALLOWED_TAGS, cfg.ADD_TAGS);
303
+ }
304
+ if (cfg.ADD_ATTR) {
305
+ if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
306
+ ALLOWED_ATTR = clone(ALLOWED_ATTR);
307
+ }
308
+ addToSet(ALLOWED_ATTR, cfg.ADD_ATTR);
309
+ }
310
+ if (cfg.ADD_URI_SAFE_ATTR) {
311
+ addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR);
312
+ }
313
+
314
+ /* Add #text in case KEEP_CONTENT is set to true */
315
+ if (KEEP_CONTENT) {
316
+ ALLOWED_TAGS['#text'] = true;
317
+ }
318
+
319
+ // Prevent further manipulation of configuration.
320
+ // Not available in IE8, Safari 5, etc.
321
+ if (Object && 'freeze' in Object) {
322
+ Object.freeze(cfg);
323
+ }
324
+
325
+ CONFIG = cfg;
326
+ };
327
+
328
+ /**
329
+ * _forceRemove
330
+ *
331
+ * @param a DOM node
332
+ */
333
+ var _forceRemove = function _forceRemove(node) {
334
+ DOMPurify.removed.push({ element: node });
335
+ try {
336
+ node.parentNode.removeChild(node);
337
+ } catch (err) {
338
+ node.outerHTML = '';
339
+ }
340
+ };
341
+
342
+ /**
343
+ * _removeAttribute
344
+ *
345
+ * @param an Attribute name
346
+ * @param a DOM node
347
+ */
348
+ var _removeAttribute = function _removeAttribute(name, node) {
349
+ DOMPurify.removed.push({
350
+ attribute: node.getAttributeNode(name),
351
+ from: node
352
+ });
353
+ node.removeAttribute(name);
354
+ };
355
+
356
+ /**
357
+ * _initDocument
358
+ *
359
+ * @param a string of dirty markup
360
+ * @return a DOM, filled with the dirty markup
361
+ */
362
+ var _initDocument = function _initDocument(dirty) {
363
+ /* Create a HTML document */
364
+ var doc = void 0;
365
+ var body = void 0;
366
+
367
+ if (FORCE_BODY) {
368
+ dirty = '<remove></remove>' + dirty;
369
+ }
370
+
371
+ /* Use XHR if necessary because Safari 10.1 and newer are buggy */
372
+ if (useXHR) {
373
+ try {
374
+ dirty = encodeURI(dirty);
375
+ } catch (err) {}
376
+ var xhr = new XMLHttpRequest();
377
+ xhr.responseType = 'document';
378
+ xhr.open('GET', 'data:text/html;charset=utf-8,' + dirty, false);
379
+ xhr.send(null);
380
+ doc = xhr.response;
381
+ }
382
+
383
+ /* Use DOMParser to workaround Firefox bug (see comment below) */
384
+ if (useDOMParser) {
385
+ try {
386
+ doc = new DOMParser().parseFromString(dirty, 'text/html');
387
+ } catch (err) {}
388
+ }
389
+
390
+ /* Otherwise use createHTMLDocument, because DOMParser is unsafe in
391
+ Safari (see comment below) */
392
+ if (!doc || !doc.documentElement) {
393
+ doc = implementation.createHTMLDocument('');
394
+ body = doc.body;
395
+ body.parentNode.removeChild(body.parentNode.firstElementChild);
396
+ body.outerHTML = dirty;
397
+ }
398
+
399
+ /* Work on whole document or just its body */
400
+ return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
401
+ };
402
+
403
+ // Safari 10.1+ (unfixed as of time of writing) has a catastrophic bug in
404
+ // its implementation of DOMParser such that the following executes the
405
+ // JavaScript:
406
+ //
407
+ // new DOMParser()
408
+ // .parseFromString('<svg onload=alert(document.domain)>', 'text/html');
409
+ //
410
+ // Later, it was also noticed that even more assumed benign and inert ways
411
+ // of creating a document are now insecure thanks to Safari. So we work
412
+ // around that with a feature test and use XHR to create the document in
413
+ // case we really have to. That one seems safe for now.
414
+ //
415
+ // However, Firefox uses a different parser for innerHTML rather than
416
+ // DOMParser (see https://bugzilla.mozilla.org/show_bug.cgi?id=1205631)
417
+ // which means that you *must* use DOMParser, otherwise the output may
418
+ // not be safe if used in a document.write context later.
419
+ //
420
+ // So we feature detect the Firefox bug and use the DOMParser if necessary.
421
+ if (DOMPurify.isSupported) {
422
+ (function () {
423
+ var doc = _initDocument('<svg><g onload="this.parentNode.remove()"></g></svg>');
424
+ if (!doc.querySelector('svg')) {
425
+ useXHR = true;
426
+ }
427
+ try {
428
+ doc = _initDocument('<svg><p><style><img src="</style><img src=x onerror=alert(1)//">');
429
+ if (doc.querySelector('svg img')) {
430
+ useDOMParser = true;
431
+ }
432
+ } catch (err) {}
433
+ })();
434
+ }
435
+
436
+ /**
437
+ * _createIterator
438
+ *
439
+ * @param document/fragment to create iterator for
440
+ * @return iterator instance
441
+ */
442
+ var _createIterator = function _createIterator(root) {
443
+ return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, function () {
444
+ return NodeFilter.FILTER_ACCEPT;
445
+ }, false);
446
+ };
447
+
448
+ /**
449
+ * _isClobbered
450
+ *
451
+ * @param element to check for clobbering attacks
452
+ * @return true if clobbered, false if safe
453
+ */
454
+ var _isClobbered = function _isClobbered(elm) {
455
+ if (elm instanceof Text || elm instanceof Comment) {
456
+ return false;
457
+ }
458
+ 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') {
459
+ return true;
460
+ }
461
+ return false;
462
+ };
463
+
464
+ /**
465
+ * _isNode
466
+ *
467
+ * @param object to check whether it's a DOM node
468
+ * @return true is object is a DOM node
469
+ */
470
+ var _isNode = function _isNode(obj) {
471
+ return (typeof Node === 'undefined' ? 'undefined' : _typeof(Node)) === 'object' ? obj instanceof Node : obj && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object' && typeof obj.nodeType === 'number' && typeof obj.nodeName === 'string';
472
+ };
473
+
474
+ /**
475
+ * _executeHook
476
+ * Execute user configurable hooks
477
+ *
478
+ * @param {String} entryPoint Name of the hook's entry point
479
+ * @param {Node} currentNode
480
+ */
481
+ var _executeHook = function _executeHook(entryPoint, currentNode, data) {
482
+ if (!hooks[entryPoint]) {
483
+ return;
484
+ }
485
+
486
+ hooks[entryPoint].forEach(function (hook) {
487
+ hook.call(DOMPurify, currentNode, data, CONFIG);
488
+ });
489
+ };
490
+
491
+ /**
492
+ * _sanitizeElements
493
+ *
494
+ * @protect nodeName
495
+ * @protect textContent
496
+ * @protect removeChild
497
+ *
498
+ * @param node to check for permission to exist
499
+ * @return true if node was killed, false if left alive
500
+ */
501
+ var _sanitizeElements = function _sanitizeElements(currentNode) {
502
+ var content = void 0;
503
+
504
+ /* Execute a hook if present */
505
+ _executeHook('beforeSanitizeElements', currentNode, null);
506
+
507
+ /* Check if element is clobbered or can clobber */
508
+ if (_isClobbered(currentNode)) {
509
+ _forceRemove(currentNode);
510
+ return true;
511
+ }
512
+
513
+ /* Now let's check the element's type and name */
514
+ var tagName = currentNode.nodeName.toLowerCase();
515
+
516
+ /* Execute a hook if present */
517
+ _executeHook('uponSanitizeElement', currentNode, {
518
+ tagName: tagName,
519
+ allowedTags: ALLOWED_TAGS
520
+ });
521
+
522
+ /* Remove element if anything forbids its presence */
523
+ if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
524
+ /* Keep content except for black-listed elements */
525
+ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName] && typeof currentNode.insertAdjacentHTML === 'function') {
526
+ try {
527
+ currentNode.insertAdjacentHTML('AfterEnd', currentNode.innerHTML);
528
+ } catch (err) {}
529
+ }
530
+ _forceRemove(currentNode);
531
+ return true;
532
+ }
533
+
534
+ /* Convert markup to cover jQuery behavior */
535
+ if (SAFE_FOR_JQUERY && !currentNode.firstElementChild && (!currentNode.content || !currentNode.content.firstElementChild) && /</g.test(currentNode.textContent)) {
536
+ DOMPurify.removed.push({ element: currentNode.cloneNode() });
537
+ currentNode.innerHTML = currentNode.textContent.replace(/</g, '&lt;');
538
+ }
539
+
540
+ /* Sanitize element content to be template-safe */
541
+ if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
542
+ /* Get the element's text content */
543
+ content = currentNode.textContent;
544
+ content = content.replace(MUSTACHE_EXPR$$1, ' ');
545
+ content = content.replace(ERB_EXPR$$1, ' ');
546
+ if (currentNode.textContent !== content) {
547
+ DOMPurify.removed.push({ element: currentNode.cloneNode() });
548
+ currentNode.textContent = content;
549
+ }
550
+ }
551
+
552
+ /* Execute a hook if present */
553
+ _executeHook('afterSanitizeElements', currentNode, null);
554
+
555
+ return false;
556
+ };
557
+
558
+ /**
559
+ * _sanitizeAttributes
560
+ *
561
+ * @protect attributes
562
+ * @protect nodeName
563
+ * @protect removeAttribute
564
+ * @protect setAttribute
565
+ *
566
+ * @param node to sanitize
567
+ * @return void
568
+ */
569
+ // eslint-disable-next-line complexity
570
+ var _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
571
+ var attr = void 0;
572
+ var name = void 0;
573
+ var value = void 0;
574
+ var lcName = void 0;
575
+ var idAttr = void 0;
576
+ var attributes = void 0;
577
+ var l = void 0;
578
+ /* Execute a hook if present */
579
+ _executeHook('beforeSanitizeAttributes', currentNode, null);
580
+
581
+ attributes = currentNode.attributes;
582
+
583
+ /* Check if we have attributes; if not we might have a text node */
584
+ if (!attributes) {
585
+ return;
586
+ }
587
+
588
+ var hookEvent = {
589
+ attrName: '',
590
+ attrValue: '',
591
+ keepAttr: true,
592
+ allowedAttributes: ALLOWED_ATTR
593
+ };
594
+ l = attributes.length;
595
+
596
+ /* Go backwards over all attributes; safely remove bad ones */
597
+ while (l--) {
598
+ attr = attributes[l];
599
+ name = attr.name;
600
+ value = attr.value.trim();
601
+ lcName = name.toLowerCase();
602
+
603
+ /* Execute a hook if present */
604
+ hookEvent.attrName = lcName;
605
+ hookEvent.attrValue = value;
606
+ hookEvent.keepAttr = true;
607
+ _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
608
+ value = hookEvent.attrValue;
609
+
610
+ /* Remove attribute */
611
+ // Safari (iOS + Mac), last tested v8.0.5, crashes if you try to
612
+ // remove a "name" attribute from an <img> tag that has an "id"
613
+ // attribute at the time.
614
+ if (lcName === 'name' && currentNode.nodeName === 'IMG' && attributes.id) {
615
+ idAttr = attributes.id;
616
+ attributes = Array.prototype.slice.apply(attributes);
617
+ _removeAttribute('id', currentNode);
618
+ _removeAttribute(name, currentNode);
619
+ if (attributes.indexOf(idAttr) > l) {
620
+ currentNode.setAttribute('id', idAttr.value);
621
+ }
622
+ } else if (
623
+ // This works around a bug in Safari, where input[type=file]
624
+ // cannot be dynamically set after type has been removed
625
+ currentNode.nodeName === 'INPUT' && lcName === 'type' && value === 'file' && (ALLOWED_ATTR[lcName] || !FORBID_ATTR[lcName])) {
626
+ continue;
627
+ } else {
628
+ // This avoids a crash in Safari v9.0 with double-ids.
629
+ // The trick is to first set the id to be empty and then to
630
+ // remove the attribute
631
+ if (name === 'id') {
632
+ currentNode.setAttribute(name, '');
633
+ }
634
+ _removeAttribute(name, currentNode);
635
+ }
636
+
637
+ /* Did the hooks approve of the attribute? */
638
+ if (!hookEvent.keepAttr) {
639
+ continue;
640
+ }
641
+
642
+ /* Make sure attribute cannot clobber */
643
+ if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
644
+ continue;
645
+ }
646
+
647
+ /* Sanitize attribute content to be template-safe */
648
+ if (SAFE_FOR_TEMPLATES) {
649
+ value = value.replace(MUSTACHE_EXPR$$1, ' ');
650
+ value = value.replace(ERB_EXPR$$1, ' ');
651
+ }
652
+
653
+ /* Allow valid data-* attributes: At least one character after "-"
654
+ (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
655
+ XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
656
+ We don't need to check the value; it's always URI safe. */
657
+ if (ALLOW_DATA_ATTR && DATA_ATTR$$1.test(lcName)) {
658
+ // This attribute is safe
659
+ } else if (ALLOW_ARIA_ATTR && ARIA_ATTR$$1.test(lcName)) {
660
+ // This attribute is safe
661
+ /* Otherwise, check the name is permitted */
662
+ } else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
663
+ continue;
664
+
665
+ /* Check value is safe. First, is attr inert? If so, is safe */
666
+ } else if (URI_SAFE_ATTRIBUTES[lcName]) {
667
+ // This attribute is safe
668
+ /* Check no script, data or unknown possibly unsafe URI
669
+ unless we know URI values are safe for that attribute */
670
+ } else if (IS_ALLOWED_URI$$1.test(value.replace(ATTR_WHITESPACE$$1, ''))) {
671
+ // This attribute is safe
672
+ /* Keep image data URIs alive if src/xlink:href is allowed */
673
+ } else if ((lcName === 'src' || lcName === 'xlink:href') && value.indexOf('data:') === 0 && DATA_URI_TAGS[currentNode.nodeName.toLowerCase()]) {
674
+ // This attribute is safe
675
+ /* Allow unknown protocols: This provides support for links that
676
+ are handled by protocol handlers which may be unknown ahead of
677
+ time, e.g. fb:, spotify: */
678
+ } else if (ALLOW_UNKNOWN_PROTOCOLS && !IS_SCRIPT_OR_DATA$$1.test(value.replace(ATTR_WHITESPACE$$1, ''))) {
679
+ // This attribute is safe
680
+ /* Check for binary attributes */
681
+ // eslint-disable-next-line no-negated-condition
682
+ } else if (!value) {
683
+ // Binary attributes are safe at this point
684
+ /* Anything else, presume unsafe, do not add it back */
685
+ } else {
686
+ continue;
687
+ }
688
+
689
+ /* Handle invalid data-* attribute set by try-catching it */
690
+ try {
691
+ currentNode.setAttribute(name, value);
692
+ DOMPurify.removed.pop();
693
+ } catch (err) {}
694
+ }
695
+
696
+ /* Execute a hook if present */
697
+ _executeHook('afterSanitizeAttributes', currentNode, null);
698
+ };
699
+
700
+ /**
701
+ * _sanitizeShadowDOM
702
+ *
703
+ * @param fragment to iterate over recursively
704
+ * @return void
705
+ */
706
+ var _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
707
+ var shadowNode = void 0;
708
+ var shadowIterator = _createIterator(fragment);
709
+
710
+ /* Execute a hook if present */
711
+ _executeHook('beforeSanitizeShadowDOM', fragment, null);
712
+
713
+ while (shadowNode = shadowIterator.nextNode()) {
714
+ /* Execute a hook if present */
715
+ _executeHook('uponSanitizeShadowNode', shadowNode, null);
716
+
717
+ /* Sanitize tags and elements */
718
+ if (_sanitizeElements(shadowNode)) {
719
+ continue;
720
+ }
721
+
722
+ /* Deep shadow DOM detected */
723
+ if (shadowNode.content instanceof DocumentFragment) {
724
+ _sanitizeShadowDOM(shadowNode.content);
725
+ }
726
+
727
+ /* Check attributes, sanitize if necessary */
728
+ _sanitizeAttributes(shadowNode);
729
+ }
730
+
731
+ /* Execute a hook if present */
732
+ _executeHook('afterSanitizeShadowDOM', fragment, null);
733
+ };
734
+
735
+ /**
736
+ * Sanitize
737
+ * Public method providing core sanitation functionality
738
+ *
739
+ * @param {String|Node} dirty string or DOM node
740
+ * @param {Object} configuration object
741
+ */
742
+ // eslint-disable-next-line complexity
743
+ DOMPurify.sanitize = function (dirty, cfg) {
744
+ var body = void 0;
745
+ var importedNode = void 0;
746
+ var currentNode = void 0;
747
+ var oldNode = void 0;
748
+ var returnNode = void 0;
749
+ /* Make sure we have a string to sanitize.
750
+ DO NOT return early, as this will return the wrong type if
751
+ the user has requested a DOM object rather than a string */
752
+ if (!dirty) {
753
+ dirty = '<!-->';
754
+ }
755
+
756
+ /* Stringify, in case dirty is an object */
757
+ if (typeof dirty !== 'string' && !_isNode(dirty)) {
758
+ // eslint-disable-next-line no-negated-condition
759
+ if (typeof dirty.toString !== 'function') {
760
+ throw new TypeError('toString is not a function');
761
+ } else {
762
+ dirty = dirty.toString();
763
+ if (typeof dirty !== 'string') {
764
+ throw new TypeError('dirty is not a string, aborting');
765
+ }
766
+ }
767
+ }
768
+
769
+ /* Check we can run. Otherwise fall back or ignore */
770
+ if (!DOMPurify.isSupported) {
771
+ if (_typeof(window.toStaticHTML) === 'object' || typeof window.toStaticHTML === 'function') {
772
+ if (typeof dirty === 'string') {
773
+ return window.toStaticHTML(dirty);
774
+ } else if (_isNode(dirty)) {
775
+ return window.toStaticHTML(dirty.outerHTML);
776
+ }
777
+ }
778
+ return dirty;
779
+ }
780
+
781
+ /* Assign config vars */
782
+ if (!SET_CONFIG) {
783
+ _parseConfig(cfg);
784
+ }
785
+
786
+ /* Clean up removed elements */
787
+ DOMPurify.removed = [];
788
+
789
+ if (dirty instanceof Node) {
790
+ /* If dirty is a DOM element, append to an empty document to avoid
791
+ elements being stripped by the parser */
792
+ body = _initDocument('<!-->');
793
+ importedNode = body.ownerDocument.importNode(dirty, true);
794
+ if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
795
+ /* Node is already a body, use as is */
796
+ body = importedNode;
797
+ } else {
798
+ body.appendChild(importedNode);
799
+ }
800
+ } else {
801
+ /* Exit directly if we have nothing to do */
802
+ if (!RETURN_DOM && !WHOLE_DOCUMENT && dirty.indexOf('<') === -1) {
803
+ return dirty;
804
+ }
805
+
806
+ /* Initialize the document to work on */
807
+ body = _initDocument(dirty);
808
+
809
+ /* Check we have a DOM node from the data */
810
+ if (!body) {
811
+ return RETURN_DOM ? null : '';
812
+ }
813
+ }
814
+
815
+ /* Remove first element node (ours) if FORCE_BODY is set */
816
+ if (FORCE_BODY) {
817
+ _forceRemove(body.firstChild);
818
+ }
819
+
820
+ /* Get node iterator */
821
+ var nodeIterator = _createIterator(body);
822
+
823
+ /* Now start iterating over the created document */
824
+ while (currentNode = nodeIterator.nextNode()) {
825
+ /* Fix IE's strange behavior with manipulated textNodes #89 */
826
+ if (currentNode.nodeType === 3 && currentNode === oldNode) {
827
+ continue;
828
+ }
829
+
830
+ /* Sanitize tags and elements */
831
+ if (_sanitizeElements(currentNode)) {
832
+ continue;
833
+ }
834
+
835
+ /* Shadow DOM detected, sanitize it */
836
+ if (currentNode.content instanceof DocumentFragment) {
837
+ _sanitizeShadowDOM(currentNode.content);
838
+ }
839
+
840
+ /* Check attributes, sanitize if necessary */
841
+ _sanitizeAttributes(currentNode);
842
+
843
+ oldNode = currentNode;
844
+ }
845
+
846
+ /* Return sanitized string or DOM */
847
+ if (RETURN_DOM) {
848
+ if (RETURN_DOM_FRAGMENT) {
849
+ returnNode = createDocumentFragment.call(body.ownerDocument);
850
+
851
+ while (body.firstChild) {
852
+ returnNode.appendChild(body.firstChild);
853
+ }
854
+ } else {
855
+ returnNode = body;
856
+ }
857
+
858
+ if (RETURN_DOM_IMPORT) {
859
+ /* AdoptNode() is not used because internal state is not reset
860
+ (e.g. the past names map of a HTMLFormElement), this is safe
861
+ in theory but we would rather not risk another attack vector.
862
+ The state that is cloned by importNode() is explicitly defined
863
+ by the specs. */
864
+ returnNode = importNode.call(originalDocument, returnNode, true);
865
+ }
866
+
867
+ return returnNode;
868
+ }
869
+
870
+ return WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
871
+ };
872
+
873
+ /**
874
+ * Public method to set the configuration once
875
+ * setConfig
876
+ *
877
+ * @param {Object} configuration object
878
+ * @return void
879
+ */
880
+ DOMPurify.setConfig = function (cfg) {
881
+ _parseConfig(cfg);
882
+ SET_CONFIG = true;
883
+ };
884
+
885
+ /**
886
+ * Public method to remove the configuration
887
+ * clearConfig
888
+ *
889
+ * @return void
890
+ */
891
+ DOMPurify.clearConfig = function () {
892
+ CONFIG = null;
893
+ SET_CONFIG = false;
894
+ };
895
+
896
+ /**
897
+ * AddHook
898
+ * Public method to add DOMPurify hooks
899
+ *
900
+ * @param {String} entryPoint
901
+ * @param {Function} hookFunction
902
+ */
903
+ DOMPurify.addHook = function (entryPoint, hookFunction) {
904
+ if (typeof hookFunction !== 'function') {
905
+ return;
906
+ }
907
+ hooks[entryPoint] = hooks[entryPoint] || [];
908
+ hooks[entryPoint].push(hookFunction);
909
+ };
910
+
911
+ /**
912
+ * RemoveHook
913
+ * Public method to remove a DOMPurify hook at a given entryPoint
914
+ * (pops it from the stack of hooks if more are present)
915
+ *
916
+ * @param {String} entryPoint
917
+ * @return void
918
+ */
919
+ DOMPurify.removeHook = function (entryPoint) {
920
+ if (hooks[entryPoint]) {
921
+ hooks[entryPoint].pop();
922
+ }
923
+ };
924
+
925
+ /**
926
+ * RemoveHooks
927
+ * Public method to remove all DOMPurify hooks at a given entryPoint
928
+ *
929
+ * @param {String} entryPoint
930
+ * @return void
931
+ */
932
+ DOMPurify.removeHooks = function (entryPoint) {
933
+ if (hooks[entryPoint]) {
934
+ hooks[entryPoint] = [];
935
+ }
936
+ };
937
+
938
+ /**
939
+ * RemoveAllHooks
940
+ * Public method to remove all DOMPurify hooks
941
+ *
942
+ * @return void
943
+ */
944
+ DOMPurify.removeAllHooks = function () {
945
+ hooks = {};
946
+ };
947
+
948
+ return DOMPurify;
949
+ }
950
+
951
+ var purify = createDOMPurify();
952
+
953
+ module.exports = purify;
954
+ //# sourceMappingURL=purify.cjs.js.map