comparison src/sceditor.js @ 22:499f38b5eeff

remove DOMPurify
author Franklin Schmidt <fschmidt@gmail.com>
date Mon, 08 Aug 2022 23:41:05 -0600
parents 2edd68951454
children c2a85b2ec677
comparison
equal deleted inserted replaced
21:2edd68951454 22:499f38b5eeff
3135 * Range helper 3135 * Range helper
3136 * 3136 *
3137 * @class RangeHelper 3137 * @class RangeHelper
3138 * @name RangeHelper 3138 * @name RangeHelper
3139 */ 3139 */
3140 function RangeHelper(win, d, sanitize) { 3140 function RangeHelper(win, d) {
3141 var _createMarker, _prepareInput, 3141 var _createMarker, _prepareInput,
3142 doc = d || win.contentDocument || win.document, 3142 doc = d || win.contentDocument || win.document,
3143 startMarker = 'sceditor-start-marker', 3143 startMarker = 'sceditor-start-marker',
3144 endMarker = 'sceditor-end-marker', 3144 endMarker = 'sceditor-end-marker',
3145 base = this; 3145 base = this;
3171 html += base.selectedHtml() + endHTML; 3171 html += base.selectedHtml() + endHTML;
3172 } 3172 }
3173 3173
3174 div = createElement('p', {}, doc); 3174 div = createElement('p', {}, doc);
3175 node = doc.createDocumentFragment(); 3175 node = doc.createDocumentFragment();
3176 div.innerHTML = sanitize(html); 3176 div.innerHTML = html;
3177 3177
3178 while (div.firstChild) { 3178 while (div.firstChild) {
3179 appendChild(node, div.firstChild); 3179 appendChild(node, div.firstChild);
3180 } 3180 }
3181 3181
4095 node = node.nextSibling; 4095 node = node.nextSibling;
4096 } 4096 }
4097 }(root)); 4097 }(root));
4098 } 4098 }
4099 4099
4100 /*! @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.2.2/LICENSE */
4101
4102 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); } }
4103
4104 var hasOwnProperty = Object.hasOwnProperty,
4105 setPrototypeOf = Object.setPrototypeOf,
4106 isFrozen = Object.isFrozen,
4107 getPrototypeOf = Object.getPrototypeOf,
4108 getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
4109 var freeze = Object.freeze,
4110 seal = Object.seal,
4111 create = Object.create; // eslint-disable-line import/no-mutable-exports
4112
4113 var _ref = typeof Reflect !== 'undefined' && Reflect,
4114 apply = _ref.apply,
4115 construct = _ref.construct;
4116
4117 if (!apply) {
4118 apply = function apply(fun, thisValue, args) {
4119 return fun.apply(thisValue, args);
4120 };
4121 }
4122
4123 if (!freeze) {
4124 freeze = function freeze(x) {
4125 return x;
4126 };
4127 }
4128
4129 if (!seal) {
4130 seal = function seal(x) {
4131 return x;
4132 };
4133 }
4134
4135 if (!construct) {
4136 construct = function construct(Func, args) {
4137 return new (Function.prototype.bind.apply(Func, [null].concat(_toConsumableArray(args))))();
4138 };
4139 }
4140
4141 var typeErrorCreate = unconstruct(TypeError);
4142
4143 function unapply(func) {
4144 return function (thisArg) {
4145 for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
4146 args[_key - 1] = arguments[_key];
4147 }
4148
4149 return apply(func, thisArg, args);
4150 };
4151 }
4152
4153 function unconstruct(func) {
4154 return function () {
4155 for (var _len2 = arguments.length, args = Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
4156 args[_key2] = arguments[_key2];
4157 }
4158
4159 return construct(func, args);
4160 };
4161 }
4162
4163 /* Add properties to a lookup table */
4164 function addToSet(set, array) {
4165 if (setPrototypeOf) {
4166 // Make 'in' and truthy checks like Boolean(set.constructor)
4167 // independent of any properties defined on Object.prototype.
4168 // Prevent prototype setters from intercepting set as a this value.
4169 setPrototypeOf(set, null);
4170 }
4171
4172 var l = array.length;
4173 while (l--) {
4174 var element = array[l];
4175 if (typeof element === 'string') {
4176 var lcElement = element.toLowerCase();
4177 if (lcElement !== element) {
4178 // Config presets (e.g. tags.js, attrs.js) are immutable.
4179 if (!isFrozen(array)) {
4180 array[l] = lcElement;
4181 }
4182
4183 element = lcElement;
4184 }
4185 }
4186
4187 set[element] = true;
4188 }
4189
4190 return set;
4191 }
4192
4193 /* Shallow clone an object */
4194 function clone(object) {
4195 var newObject = create(null);
4196
4197 var property = void 0;
4198 for (property in object) {
4199 if (apply(hasOwnProperty, object, [property])) {
4200 newObject[property] = object[property];
4201 }
4202 }
4203
4204 return newObject;
4205 }
4206
4207 /* IE10 doesn't support __lookupGetter__ so lets'
4208 * simulate it. It also automatically checks
4209 * if the prop is function or getter and behaves
4210 * accordingly. */
4211 function lookupGetter(object, prop) {
4212 while (object !== null) {
4213 var desc = getOwnPropertyDescriptor(object, prop);
4214 if (desc) {
4215 if (desc.get) {
4216 return unapply(desc.get);
4217 }
4218
4219 if (typeof desc.value === 'function') {
4220 return unapply(desc.value);
4221 }
4222 }
4223
4224 object = getPrototypeOf(object);
4225 }
4226
4227 return null;
4228 }
4229
4230 var html = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);
4231
4232 // SVG
4233 var svg = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
4234
4235 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']);
4236
4237 // List of SVG elements that are disallowed by default.
4238 // We still need to know them so that we can do namespace
4239 // checks properly in case one wants to add them to
4240 // allow-list.
4241 var svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'fedropshadow', 'feimage', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);
4242
4243 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']);
4244
4245 // Similarly to SVG, we want to know all MathML elements,
4246 // even those that we disallow by default.
4247 var mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
4248
4249 var text = freeze(['#text']);
4250
4251 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']);
4252
4253 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']);
4254
4255 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']);
4256
4257 var xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);
4258
4259 // eslint-disable-next-line unicorn/better-regex
4260 var MUSTACHE_EXPR = seal(/\{\{[\s\S]*|[\s\S]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
4261 var ERB_EXPR = seal(/<%[\s\S]*|[\s\S]*%>/gm);
4262 var DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape
4263 var ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
4264 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
4265 );
4266 var IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
4267 var ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
4268 );
4269
4270 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; };
4271
4272 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); } }
4273
4274 var getGlobal = function getGlobal() {
4275 return typeof window === 'undefined' ? null : window;
4276 };
4277
4278 /**
4279 * Creates a no-op policy for internal use only.
4280 * Don't export this function outside this module!
4281 * @param {?TrustedTypePolicyFactory} trustedTypes The policy factory.
4282 * @param {Document} document The document object (to determine policy name suffix)
4283 * @return {?TrustedTypePolicy} The policy created (or null, if Trusted Types
4284 * are not supported).
4285 */
4286 var _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, document) {
4287 if ((typeof trustedTypes === 'undefined' ? 'undefined' : _typeof(trustedTypes)) !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
4288 return null;
4289 }
4290
4291 // Allow the callers to control the unique policy name
4292 // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
4293 // Policy creation with duplicate names throws in Trusted Types.
4294 var suffix = null;
4295 var ATTR_NAME = 'data-tt-policy-suffix';
4296 if (document.currentScript && document.currentScript.hasAttribute(ATTR_NAME)) {
4297 suffix = document.currentScript.getAttribute(ATTR_NAME);
4298 }
4299
4300 var policyName = 'dompurify' + (suffix ? '#' + suffix : '');
4301
4302 try {
4303 return trustedTypes.createPolicy(policyName, {
4304 createHTML: function createHTML(html$$1) {
4305 return html$$1;
4306 }
4307 });
4308 } catch (_) {
4309 // Policy creation failed (most likely another DOMPurify script has
4310 // already run). Skip creating the policy, as this will only cause errors
4311 // if TT are enforced.
4312 console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
4313 return null;
4314 }
4315 };
4316
4317 function createDOMPurify() {
4318 var window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
4319
4320 var DOMPurify = function DOMPurify(root) {
4321 return createDOMPurify(root);
4322 };
4323
4324 /**
4325 * Version label, exposed for easier checks
4326 * if DOMPurify is up to date or not
4327 */
4328 DOMPurify.version = '2.2.6';
4329
4330 /**
4331 * Array of elements that DOMPurify removed during sanitation.
4332 * Empty if nothing was removed.
4333 */
4334 DOMPurify.removed = [];
4335
4336 if (!window || !window.document || window.document.nodeType !== 9) {
4337 // Not running in a browser, provide a factory function
4338 // so that you can pass your own Window
4339 DOMPurify.isSupported = false;
4340
4341 return DOMPurify;
4342 }
4343
4344 var originalDocument = window.document;
4345
4346 var document = window.document;
4347 var DocumentFragment = window.DocumentFragment,
4348 HTMLTemplateElement = window.HTMLTemplateElement,
4349 Node = window.Node,
4350 Element = window.Element,
4351 NodeFilter = window.NodeFilter,
4352 _window$NamedNodeMap = window.NamedNodeMap,
4353 NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
4354 Text = window.Text,
4355 Comment = window.Comment,
4356 DOMParser = window.DOMParser,
4357 trustedTypes = window.trustedTypes;
4358
4359
4360 var ElementPrototype = Element.prototype;
4361
4362 var cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
4363 var getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
4364 var getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
4365 var getParentNode = lookupGetter(ElementPrototype, 'parentNode');
4366
4367 // As per issue #47, the web-components registry is inherited by a
4368 // new document created via createHTMLDocument. As per the spec
4369 // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
4370 // a new empty registry is used when creating a template contents owner
4371 // document, so we use that as our parent document to ensure nothing
4372 // is inherited.
4373 if (typeof HTMLTemplateElement === 'function') {
4374 var template = document.createElement('template');
4375 if (template.content && template.content.ownerDocument) {
4376 document = template.content.ownerDocument;
4377 }
4378 }
4379
4380 var trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, originalDocument);
4381 var emptyHTML = trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML('') : '';
4382
4383 var implementation = document.implementation;
4384 //var importNode = originalDocument.importNode;
4385
4386
4387 var documentMode = {};
4388 try {
4389 documentMode = clone(document).documentMode ? document.documentMode : {};
4390 } catch (_) {}
4391
4392 var hooks = {};
4393
4394 /**
4395 * Expose whether this browser supports running the full DOMPurify.
4396 */
4397 DOMPurify.isSupported = implementation && typeof implementation.createHTMLDocument !== 'undefined' && documentMode !== 9;
4398
4399 var MUSTACHE_EXPR$$1 = MUSTACHE_EXPR,
4400 ERB_EXPR$$1 = ERB_EXPR,
4401 DATA_ATTR$$1 = DATA_ATTR,
4402 ARIA_ATTR$$1 = ARIA_ATTR,
4403 IS_SCRIPT_OR_DATA$$1 = IS_SCRIPT_OR_DATA,
4404 ATTR_WHITESPACE$$1 = ATTR_WHITESPACE;
4405 var IS_ALLOWED_URI$$1 = IS_ALLOWED_URI;
4406
4407 /**
4408 * We consider the elements and attributes below to be safe. Ideally
4409 * don't add any new ones but feel free to remove unwanted ones.
4410 */
4411
4412 /* allowed element names */
4413
4414 var ALLOWED_TAGS = null;
4415 var DEFAULT_ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray$1(html), _toConsumableArray$1(svg), _toConsumableArray$1(svgFilters), _toConsumableArray$1(mathMl), _toConsumableArray$1(text)));
4416
4417 /* Allowed attribute names */
4418 var ALLOWED_ATTR = null;
4419 var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray$1(html$1), _toConsumableArray$1(svg$1), _toConsumableArray$1(mathMl$1), _toConsumableArray$1(xml)));
4420
4421 /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
4422 var FORBID_TAGS = null;
4423
4424 /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
4425 var FORBID_ATTR = null;
4426
4427 /* Decide if ARIA attributes are okay */
4428 var ALLOW_ARIA_ATTR = true;
4429
4430 /* Decide if custom data attributes are okay */
4431 var ALLOW_DATA_ATTR = true;
4432
4433 /* Decide if unknown protocols are okay */
4434 var ALLOW_UNKNOWN_PROTOCOLS = false;
4435
4436 /* Output should be safe for common template engines.
4437 * This means, DOMPurify removes data attributes, mustaches and ERB
4438 */
4439 var SAFE_FOR_TEMPLATES = false;
4440
4441 /* Decide if document with <html>... should be returned */
4442 var WHOLE_DOCUMENT = false;
4443
4444 /* Track whether config is already set on this instance of DOMPurify. */
4445 var SET_CONFIG = false;
4446
4447 /* Decide if all elements (e.g. style, script) must be children of
4448 * document.body. By default, browsers might move them to document.head */
4449 var FORCE_BODY = false;
4450
4451 /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
4452 * string (or a TrustedHTML object if Trusted Types are supported).
4453 * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
4454 */
4455 var RETURN_DOM = false;
4456
4457 /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
4458 * string (or a TrustedHTML object if Trusted Types are supported) */
4459 var RETURN_DOM_FRAGMENT = false;
4460
4461 /* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM
4462 * `Node` is imported into the current `Document`. If this flag is not enabled the
4463 * `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by
4464 * DOMPurify.
4465 *
4466 * This defaults to `true` starting DOMPurify 2.2.0. Note that setting it to `false`
4467 * might cause XSS from attacks hidden in closed shadowroots in case the browser
4468 * supports Declarative Shadow: DOM https://web.dev/declarative-shadow-dom/
4469 */
4470 var RETURN_DOM_IMPORT = true;
4471
4472 /* Try to return a Trusted Type object instead of a string, return a string in
4473 * case Trusted Types are not supported */
4474 var RETURN_TRUSTED_TYPE = false;
4475
4476 /* Output should be free from DOM clobbering attacks? */
4477 var SANITIZE_DOM = true;
4478
4479 /* Keep element content when removing element? */
4480 var KEEP_CONTENT = true;
4481
4482 /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
4483 * of importing it into a new Document and returning a sanitized copy */
4484 var IN_PLACE = false;
4485
4486 /* Allow usage of profiles like html, svg and mathMl */
4487 var USE_PROFILES = {};
4488
4489 /* Tags to ignore content of when KEEP_CONTENT is true */
4490 var FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);
4491
4492 /* Tags that are safe for data: URIs */
4493 var DATA_URI_TAGS = null;
4494 var DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);
4495
4496 /* Attributes safe for values like "javascript:" */
4497 var URI_SAFE_ATTRIBUTES = null;
4498 var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'summary', 'title', 'value', 'style', 'xmlns']);
4499
4500 /* Keep a reference to config to pass to hooks */
4501 var CONFIG = null;
4502
4503 /* Ideally, do not touch anything below this line */
4504 /* ______________________________________________ */
4505
4506 var formElement = document.createElement('form');
4507
4508 /**
4509 * _parseConfig
4510 *
4511 * @param {Object} cfg optional config literal
4512 */
4513 // eslint-disable-next-line complexity
4514 var _parseConfig = function _parseConfig(cfg) {
4515 if (CONFIG && CONFIG === cfg) {
4516 return;
4517 }
4518
4519 /* Shield configuration object from tampering */
4520 if (!cfg || (typeof cfg === 'undefined' ? 'undefined' : _typeof(cfg)) !== 'object') {
4521 cfg = {};
4522 }
4523
4524 /* Shield configuration object from prototype pollution */
4525 cfg = clone(cfg);
4526
4527 /* Set configuration parameters */
4528 ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS;
4529 ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR;
4530 URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR) : DEFAULT_URI_SAFE_ATTRIBUTES;
4531 DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS) : DEFAULT_DATA_URI_TAGS;
4532 FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {};
4533 FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {};
4534 USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
4535 ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
4536 ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
4537 ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
4538 SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
4539 WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
4540 RETURN_DOM = cfg.RETURN_DOM || false; // Default false
4541 RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
4542 RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT !== false; // Default true
4543 RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
4544 FORCE_BODY = cfg.FORCE_BODY || false; // Default false
4545 SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
4546 KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
4547 IN_PLACE = cfg.IN_PLACE || false; // Default false
4548 IS_ALLOWED_URI$$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1;
4549 if (SAFE_FOR_TEMPLATES) {
4550 ALLOW_DATA_ATTR = false;
4551 }
4552
4553 if (RETURN_DOM_FRAGMENT) {
4554 RETURN_DOM = true;
4555 }
4556
4557 /* Parse profile info */
4558 if (USE_PROFILES) {
4559 ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray$1(text)));
4560 ALLOWED_ATTR = [];
4561 if (USE_PROFILES.html === true) {
4562 addToSet(ALLOWED_TAGS, html);
4563 addToSet(ALLOWED_ATTR, html$1);
4564 }
4565
4566 if (USE_PROFILES.svg === true) {
4567 addToSet(ALLOWED_TAGS, svg);
4568 addToSet(ALLOWED_ATTR, svg$1);
4569 addToSet(ALLOWED_ATTR, xml);
4570 }
4571
4572 if (USE_PROFILES.svgFilters === true) {
4573 addToSet(ALLOWED_TAGS, svgFilters);
4574 addToSet(ALLOWED_ATTR, svg$1);
4575 addToSet(ALLOWED_ATTR, xml);
4576 }
4577
4578 if (USE_PROFILES.mathMl === true) {
4579 addToSet(ALLOWED_TAGS, mathMl);
4580 addToSet(ALLOWED_ATTR, mathMl$1);
4581 addToSet(ALLOWED_ATTR, xml);
4582 }
4583 }
4584
4585 /* Merge configuration parameters */
4586 if (cfg.ADD_TAGS) {
4587 if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
4588 ALLOWED_TAGS = clone(ALLOWED_TAGS);
4589 }
4590
4591 addToSet(ALLOWED_TAGS, cfg.ADD_TAGS);
4592 }
4593
4594 if (cfg.ADD_ATTR) {
4595 if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
4596 ALLOWED_ATTR = clone(ALLOWED_ATTR);
4597 }
4598
4599 addToSet(ALLOWED_ATTR, cfg.ADD_ATTR);
4600 }
4601
4602 if (cfg.ADD_URI_SAFE_ATTR) {
4603 addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR);
4604 }
4605
4606 /* Add #text in case KEEP_CONTENT is set to true */
4607 if (KEEP_CONTENT) {
4608 ALLOWED_TAGS['#text'] = true;
4609 }
4610
4611 /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
4612 if (WHOLE_DOCUMENT) {
4613 addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
4614 }
4615
4616 /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
4617 if (ALLOWED_TAGS.table) {
4618 addToSet(ALLOWED_TAGS, ['tbody']);
4619 delete FORBID_TAGS.tbody;
4620 }
4621
4622 // Prevent further manipulation of configuration.
4623 // Not available in IE8, Safari 5, etc.
4624 if (freeze) {
4625 freeze(cfg);
4626 }
4627
4628 CONFIG = cfg;
4629 };
4630
4631 var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
4632
4633 var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']);
4634
4635 /* Keep track of all possible SVG and MathML tags
4636 * so that we can perform the namespace checks
4637 * correctly. */
4638 var ALL_SVG_TAGS = addToSet({}, svg);
4639 addToSet(ALL_SVG_TAGS, svgFilters);
4640 addToSet(ALL_SVG_TAGS, svgDisallowed);
4641
4642 var ALL_MATHML_TAGS = addToSet({}, mathMl);
4643 addToSet(ALL_MATHML_TAGS, mathMlDisallowed);
4644
4645 var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
4646 var SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
4647 var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
4648
4649 /**
4650 *
4651 *
4652 * @param {Element} element a DOM element whose namespace is being checked
4653 * @returns {boolean} Return false if the element has a
4654 * namespace that a spec-compliant parser would never
4655 * return. Return true otherwise.
4656 */
4657 var _checkValidNamespace = function _checkValidNamespace(element) {
4658 var parent = getParentNode(element);
4659
4660 // In JSDOM, if we're inside shadow DOM, then parentNode
4661 // can be null. We just simulate parent in this case.
4662 if (!parent || !parent.tagName) {
4663 parent = {
4664 namespaceURI: HTML_NAMESPACE,
4665 tagName: 'template'
4666 };
4667 }
4668
4669 var tagName = element.tagName.toLowerCase();
4670 var parentTagName = parent.tagName.toLowerCase();
4671
4672 if (element.namespaceURI === SVG_NAMESPACE) {
4673 // The only way to switch from HTML namespace to SVG
4674 // is via <svg>. If it happens via any other tag, then
4675 // it should be killed.
4676 if (parent.namespaceURI === HTML_NAMESPACE) {
4677 return tagName === 'svg';
4678 }
4679
4680 // The only way to switch from MathML to SVG is via
4681 // svg if parent is either <annotation-xml> or MathML
4682 // text integration points.
4683 if (parent.namespaceURI === MATHML_NAMESPACE) {
4684 return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
4685 }
4686
4687 // We only allow elements that are defined in SVG
4688 // spec. All others are disallowed in SVG namespace.
4689 return Boolean(ALL_SVG_TAGS[tagName]);
4690 }
4691
4692 if (element.namespaceURI === MATHML_NAMESPACE) {
4693 // The only way to switch from HTML namespace to MathML
4694 // is via <math>. If it happens via any other tag, then
4695 // it should be killed.
4696 if (parent.namespaceURI === HTML_NAMESPACE) {
4697 return tagName === 'math';
4698 }
4699
4700 // The only way to switch from SVG to MathML is via
4701 // <math> and HTML integration points
4702 if (parent.namespaceURI === SVG_NAMESPACE) {
4703 return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
4704 }
4705
4706 // We only allow elements that are defined in MathML
4707 // spec. All others are disallowed in MathML namespace.
4708 return Boolean(ALL_MATHML_TAGS[tagName]);
4709 }
4710
4711 if (element.namespaceURI === HTML_NAMESPACE) {
4712 // The only way to switch from SVG to HTML is via
4713 // HTML integration points, and from MathML to HTML
4714 // is via MathML text integration points
4715 if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
4716 return false;
4717 }
4718
4719 if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
4720 return false;
4721 }
4722
4723 // Certain elements are allowed in both SVG and HTML
4724 // namespace. We need to specify them explicitly
4725 // so that they don't get erronously deleted from
4726 // HTML namespace.
4727 var commonSvgAndHTMLElements = addToSet({}, ['title', 'style', 'font', 'a', 'script']);
4728
4729 // We disallow tags that are specific for MathML
4730 // or SVG and should never appear in HTML namespace
4731 return !ALL_MATHML_TAGS[tagName] && (commonSvgAndHTMLElements[tagName] || !ALL_SVG_TAGS[tagName]);
4732 }
4733
4734 // The code should never reach this place (this means
4735 // that the element somehow got namespace that is not
4736 // HTML, SVG or MathML). Return false just in case.
4737 return false;
4738 };
4739
4740 /**
4741 * _forceRemove
4742 *
4743 * @param {Node} node a DOM node
4744 */
4745 var _forceRemove = function _forceRemove(node) {
4746 DOMPurify.removed.push({ element: node });
4747 try {
4748 node.parentNode.removeChild(node);
4749 } catch (_) {
4750 try {
4751 node.outerHTML = emptyHTML;
4752 } catch (_) {
4753 node.remove();
4754 }
4755 }
4756 };
4757
4758 /**
4759 * _removeAttribute
4760 *
4761 * @param {String} name an Attribute name
4762 * @param {Node} node a DOM node
4763 */
4764 var _removeAttribute = function _removeAttribute(name, node) {
4765 try {
4766 DOMPurify.removed.push({
4767 attribute: node.getAttributeNode(name),
4768 from: node
4769 });
4770 } catch (_) {
4771 DOMPurify.removed.push({
4772 attribute: null,
4773 from: node
4774 });
4775 }
4776
4777 node.removeAttribute(name);
4778 };
4779
4780 /**
4781 * _initDocument
4782 *
4783 * @param {String} dirty a string of dirty markup
4784 * @return {Document} a DOM, filled with the dirty markup
4785 */
4786 var _initDocument = function _initDocument(dirty) {
4787 /* Create a HTML document */
4788 var doc = void 0;
4789 var leadingWhitespace = void 0;
4790
4791 if (FORCE_BODY) {
4792 dirty = '<remove></remove>' + dirty;
4793 } else {
4794 /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
4795 var matches = dirty.match(/^[\r\n\t ]+/);
4796 leadingWhitespace = matches && matches[0];
4797 }
4798
4799 var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
4800 /* Use the DOMParser API by default, fallback later if needs be */
4801 try {
4802 doc = new DOMParser().parseFromString(dirtyPayload, 'text/html');
4803 } catch (_) {}
4804
4805 /* Use createHTMLDocument in case DOMParser is not available */
4806 if (!doc || !doc.documentElement) {
4807 doc = implementation.createHTMLDocument('');
4808 var _doc = doc,
4809 body = _doc.body;
4810
4811 body.parentNode.removeChild(body.parentNode.firstElementChild);
4812 body.outerHTML = dirtyPayload;
4813 }
4814
4815 if (dirty && leadingWhitespace) {
4816 doc.body.insertBefore(document.createTextNode(leadingWhitespace), doc.body.childNodes[0] || null);
4817 }
4818
4819 /* Work on whole document or just its body */
4820 return doc.getElementsByTagName(WHOLE_DOCUMENT ? 'html' : 'body')[0];
4821 };
4822
4823 /**
4824 * _createIterator
4825 *
4826 * @param {Document} root document/fragment to create iterator for
4827 * @return {Iterator} iterator instance
4828 */
4829 var _createIterator = function _createIterator(root) {
4830 let whatToShow = NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT;
4831 let filter = function () {
4832 return NodeFilter.FILTER_ACCEPT;
4833 };
4834 if(root.ownerDocument) {
4835 return root.ownerDocument.createNodeIterator(root,whatToShow,filter,false);
4836 } else {
4837 return root.createNodeIterator(root,whatToShow,filter,false);
4838 }
4839 };
4840
4841 /**
4842 * _isClobbered
4843 *
4844 * @param {Node} elm element to check for clobbering attacks
4845 * @return {Boolean} true if clobbered, false if safe
4846 */
4847 var _isClobbered = function _isClobbered(elm) {
4848 if (elm instanceof Text || elm instanceof Comment) {
4849 return false;
4850 }
4851
4852 if (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function') {
4853 return true;
4854 }
4855
4856 return false;
4857 };
4858
4859 /**
4860 * _isNode
4861 *
4862 * @param {Node} obj object to check whether it's a DOM node
4863 * @return {Boolean} true is object is a DOM node
4864 */
4865 var _isNode = function _isNode(object) {
4866 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';
4867 };
4868
4869 /**
4870 * _executeHook
4871 * Execute user configurable hooks
4872 *
4873 * @param {String} entryPoint Name of the hook's entry point
4874 * @param {Node} currentNode node to work on with the hook
4875 * @param {Object} data additional hook parameters
4876 */
4877 var _executeHook = function _executeHook(entryPoint, currentNode, data) {
4878 if (!hooks[entryPoint]) {
4879 return;
4880 }
4881
4882 hooks[entryPoint].forEach(function (hook) {
4883 hook.call(DOMPurify, currentNode, data, CONFIG);
4884 });
4885 };
4886
4887 /**
4888 * _sanitizeElements
4889 *
4890 * @protect nodeName
4891 * @protect textContent
4892 * @protect removeChild
4893 *
4894 * @param {Node} currentNode to check for permission to exist
4895 * @return {Boolean} true if node was killed, false if left alive
4896 */
4897 var _sanitizeElements = function _sanitizeElements(currentNode) {
4898 var content = void 0;
4899
4900 /* Execute a hook if present */
4901 _executeHook('beforeSanitizeElements', currentNode, null);
4902
4903 /* Check if element is clobbered or can clobber */
4904 if (_isClobbered(currentNode)) {
4905 _forceRemove(currentNode);
4906 return true;
4907 }
4908
4909 /* Check if tagname contains Unicode */
4910 if (currentNode.nodeName.match(/[\u0080-\uFFFF]/)) {
4911 _forceRemove(currentNode);
4912 return true;
4913 }
4914
4915 /* Now let's check the element's type and name */
4916 var tagName = currentNode.nodeName.toLowerCase();
4917
4918 /* Execute a hook if present */
4919 _executeHook('uponSanitizeElement', currentNode, {
4920 tagName: tagName,
4921 allowedTags: ALLOWED_TAGS
4922 });
4923
4924 /* Detect mXSS attempts abusing namespace confusion */
4925 if (!_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && /<[/\w]/g.test(currentNode.innerHTML) && /<[/\w]/g.text(currentNode.textContent)) {
4926 _forceRemove(currentNode);
4927 return true;
4928 }
4929
4930 /* Remove element if anything forbids its presence */
4931 if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
4932 /* Keep content except for bad-listed elements */
4933 if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
4934 var parentNode = getParentNode(currentNode);
4935 var childNodes = getChildNodes(currentNode);
4936 var childCount = childNodes.length;
4937 for (var i = childCount - 1; i >= 0; --i) {
4938 parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode));
4939 }
4940 }
4941
4942 _forceRemove(currentNode);
4943 return true;
4944 }
4945
4946 /* Check whether element has a valid namespace */
4947 if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
4948 _forceRemove(currentNode);
4949 return true;
4950 }
4951
4952 if ((tagName === 'noscript' || tagName === 'noembed') && /<\/no(script|embed)/i.test(currentNode.innerHTML)) {
4953 _forceRemove(currentNode);
4954 return true;
4955 }
4956
4957 /* Sanitize element content to be template-safe */
4958 if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
4959 /* Get the element's text content */
4960 content = currentNode.textContent;
4961 content = content.replace(MUSTACHE_EXPR$$1, ' ');
4962 content = content.replace(ERB_EXPR$$1, ' ');
4963 if (currentNode.textContent !== content) {
4964 DOMPurify.removed.push({ element: currentNode.cloneNode() });
4965 currentNode.textContent = content;
4966 }
4967 }
4968
4969 /* Execute a hook if present */
4970 _executeHook('afterSanitizeElements', currentNode, null);
4971
4972 return false;
4973 };
4974
4975 /**
4976 * _isValidAttribute
4977 *
4978 * @param {string} lcTag Lowercase tag name of containing element.
4979 * @param {string} lcName Lowercase attribute name.
4980 * @param {string} value Attribute value.
4981 * @return {Boolean} Returns true if `value` is valid, otherwise false.
4982 */
4983 // eslint-disable-next-line complexity
4984 var _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
4985 /* Make sure attribute cannot clobber */
4986 if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
4987 return false;
4988 }
4989
4990 /* Allow valid data-* attributes: At least one character after "-"
4991 (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
4992 XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
4993 We don't need to check the value; it's always URI safe. */
4994 if (ALLOW_DATA_ATTR && DATA_ATTR$$1.test(lcName)) ; else if (ALLOW_ARIA_ATTR && ARIA_ATTR$$1.test(lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
4995 return false;
4996
4997 /* Check value is safe. First, is attr inert? If so, is safe */
4998 } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (IS_ALLOWED_URI$$1.test(value.replace(ATTR_WHITESPACE$$1, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && value.indexOf('data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !IS_SCRIPT_OR_DATA$$1.test(value.replace(ATTR_WHITESPACE$$1, ''))) ; else if (!value) ; else {
4999 return false;
5000 }
5001
5002 return true;
5003 };
5004
5005 /**
5006 * _sanitizeAttributes
5007 *
5008 * @protect attributes
5009 * @protect nodeName
5010 * @protect removeAttribute
5011 * @protect setAttribute
5012 *
5013 * @param {Node} currentNode to sanitize
5014 */
5015 var _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
5016 var attr = void 0;
5017 var value = void 0;
5018 var lcName = void 0;
5019 var l = void 0;
5020 /* Execute a hook if present */
5021 _executeHook('beforeSanitizeAttributes', currentNode, null);
5022
5023 var attributes = currentNode.attributes;
5024
5025 /* Check if we have attributes; if not we might have a text node */
5026
5027 if (!attributes) {
5028 return;
5029 }
5030
5031 var hookEvent = {
5032 attrName: '',
5033 attrValue: '',
5034 keepAttr: true,
5035 allowedAttributes: ALLOWED_ATTR
5036 };
5037 l = attributes.length;
5038
5039 /* Go backwards over all attributes; safely remove bad ones */
5040 while (l--) {
5041 attr = attributes[l];
5042 var _attr = attr,
5043 name = _attr.name,
5044 namespaceURI = _attr.namespaceURI;
5045
5046 value = attr.value.trim();
5047 lcName = name.toLowerCase(name);
5048
5049 /* Execute a hook if present */
5050 hookEvent.attrName = lcName;
5051 hookEvent.attrValue = value;
5052 hookEvent.keepAttr = true;
5053 hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
5054 _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
5055 value = hookEvent.attrValue;
5056 /* Did the hooks approve of the attribute? */
5057 if (hookEvent.forceKeepAttr) {
5058 continue;
5059 }
5060
5061 /* Remove attribute */
5062 _removeAttribute(name, currentNode);
5063
5064 /* Did the hooks approve of the attribute? */
5065 if (!hookEvent.keepAttr) {
5066 continue;
5067 }
5068
5069 /* Work around a security issue in jQuery 3.0 */
5070 if (/\/>/i.test(value)) {
5071 _removeAttribute(name, currentNode);
5072 continue;
5073 }
5074
5075 /* Sanitize attribute content to be template-safe */
5076 if (SAFE_FOR_TEMPLATES) {
5077 value = value.replace(MUSTACHE_EXPR$$1, ' ');
5078 value = value.replace(ERB_EXPR$$1, ' ');
5079 }
5080
5081 /* Is `value` valid for this attribute? */
5082 var lcTag = currentNode.nodeName.toLowerCase();
5083 if (!_isValidAttribute(lcTag, lcName, value)) {
5084 continue;
5085 }
5086
5087 /* Handle invalid data-* attribute set by try-catching it */
5088 try {
5089 if (namespaceURI) {
5090 currentNode.setAttributeNS(namespaceURI, name, value);
5091 } else {
5092 /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
5093 currentNode.setAttribute(name, value);
5094 }
5095
5096 DOMPurify.removed.pop();
5097 } catch (_) {}
5098 }
5099
5100 /* Execute a hook if present */
5101 _executeHook('afterSanitizeAttributes', currentNode, null);
5102 };
5103
5104 /**
5105 * _sanitizeShadowDOM
5106 *
5107 * @param {DocumentFragment} fragment to iterate over recursively
5108 */
5109 var _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
5110 var shadowNode = void 0;
5111 var shadowIterator = _createIterator(fragment);
5112
5113 /* Execute a hook if present */
5114 _executeHook('beforeSanitizeShadowDOM', fragment, null);
5115
5116 while (shadowNode = shadowIterator.nextNode()) {
5117 /* Execute a hook if present */
5118 _executeHook('uponSanitizeShadowNode', shadowNode, null);
5119
5120 /* Sanitize tags and elements */
5121 if (_sanitizeElements(shadowNode)) {
5122 continue;
5123 }
5124
5125 /* Deep shadow DOM detected */
5126 if (shadowNode.content instanceof DocumentFragment) {
5127 _sanitizeShadowDOM(shadowNode.content);
5128 }
5129
5130 /* Check attributes, sanitize if necessary */
5131 _sanitizeAttributes(shadowNode);
5132 }
5133
5134 /* Execute a hook if present */
5135 _executeHook('afterSanitizeShadowDOM', fragment, null);
5136 };
5137
5138 /**
5139 * Sanitize
5140 * Public method providing core sanitation functionality
5141 *
5142 * @param {String|Node} dirty string or DOM node
5143 * @param {Object} configuration object
5144 */
5145 // eslint-disable-next-line complexity
5146 DOMPurify.sanitize = function (dirty, cfg) {
5147 var body = void 0;
5148 var importedNode = void 0;
5149 var currentNode = void 0;
5150 var oldNode = void 0;
5151 var returnNode = void 0;
5152 /* Make sure we have a string to sanitize.
5153 DO NOT return early, as this will return the wrong type if
5154 the user has requested a DOM object rather than a string */
5155 if (!dirty) {
5156 dirty = '<!-->';
5157 }
5158
5159 /* Stringify, in case dirty is an object */
5160 if (typeof dirty !== 'string' && !_isNode(dirty)) {
5161 // eslint-disable-next-line no-negated-condition
5162 if (typeof dirty.toString !== 'function') {
5163 throw typeErrorCreate('toString is not a function');
5164 } else {
5165 dirty = dirty.toString();
5166 if (typeof dirty !== 'string') {
5167 throw typeErrorCreate('dirty is not a string, aborting');
5168 }
5169 }
5170 }
5171
5172 /* Check we can run. Otherwise fall back or ignore */
5173 if (!DOMPurify.isSupported) {
5174 if (_typeof(window.toStaticHTML) === 'object' || typeof window.toStaticHTML === 'function') {
5175 if (typeof dirty === 'string') {
5176 return window.toStaticHTML(dirty);
5177 }
5178
5179 if (_isNode(dirty)) {
5180 return window.toStaticHTML(dirty.outerHTML);
5181 }
5182 }
5183
5184 return dirty;
5185 }
5186
5187 /* Assign config vars */
5188 if (!SET_CONFIG) {
5189 _parseConfig(cfg);
5190 }
5191
5192 /* Clean up removed elements */
5193 DOMPurify.removed = [];
5194
5195 /* Check if dirty is correctly typed for IN_PLACE */
5196 if (typeof dirty === 'string') {
5197 IN_PLACE = false;
5198 }
5199
5200 if (IN_PLACE) ; else if (dirty instanceof Node) {
5201 /* If dirty is a DOM element, append to an empty document to avoid
5202 elements being stripped by the parser */
5203 body = _initDocument('<!---->');
5204 importedNode = body.ownerDocument.importNode(dirty, true);
5205 if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
5206 /* Node is already a body, use as is */
5207 body = importedNode;
5208 } else if (importedNode.nodeName === 'HTML') {
5209 body = importedNode;
5210 } else {
5211 // eslint-disable-next-line unicorn/prefer-node-append
5212 body.appendChild(importedNode);
5213 }
5214 } else {
5215 /* Exit directly if we have nothing to do */
5216 if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
5217 // eslint-disable-next-line unicorn/prefer-includes
5218 dirty.indexOf('<') === -1) {
5219 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
5220 }
5221
5222 /* Initialize the document to work on */
5223 body = _initDocument(dirty);
5224
5225 /* Check we have a DOM node from the data */
5226 if (!body) {
5227 return RETURN_DOM ? null : emptyHTML;
5228 }
5229 }
5230
5231 /* Remove first element node (ours) if FORCE_BODY is set */
5232 if (body && FORCE_BODY) {
5233 _forceRemove(body.firstChild);
5234 }
5235
5236 /* Get node iterator */
5237 var nodeIterator = _createIterator(IN_PLACE ? dirty : body);
5238
5239 /* Now start iterating over the created document */
5240 while (currentNode = nodeIterator.nextNode()) {
5241 /* Fix IE's strange behavior with manipulated textNodes #89 */
5242 if (currentNode.nodeType === 3 && currentNode === oldNode) {
5243 continue;
5244 }
5245
5246 /* Sanitize tags and elements */
5247 if (_sanitizeElements(currentNode)) {
5248 continue;
5249 }
5250
5251 /* Shadow DOM detected, sanitize it */
5252 if (currentNode.content instanceof DocumentFragment) {
5253 _sanitizeShadowDOM(currentNode.content);
5254 }
5255
5256 /* Check attributes, sanitize if necessary */
5257 _sanitizeAttributes(currentNode);
5258
5259 oldNode = currentNode;
5260 }
5261
5262 oldNode = null;
5263
5264 /* If we sanitized `dirty` in-place, return it. */
5265 if (IN_PLACE) {
5266 return dirty;
5267 }
5268
5269 /* Return sanitized string or DOM */
5270 if (RETURN_DOM) {
5271 if (RETURN_DOM_FRAGMENT) {
5272 //returnNode = createDocumentFragment.call(body.ownerDocument);
5273 // untested
5274 returnNode = body.ownerDocument.createDocumentFragment();
5275
5276 while (body.firstChild) {
5277 // eslint-disable-next-line unicorn/prefer-node-append
5278 returnNode.appendChild(body.firstChild);
5279 }
5280 } else {
5281 returnNode = body;
5282 }
5283
5284 if (RETURN_DOM_IMPORT) {
5285 /*
5286 AdoptNode() is not used because internal state is not reset
5287 (e.g. the past names map of a HTMLFormElement), this is safe
5288 in theory but we would rather not risk another attack vector.
5289 The state that is cloned by importNode() is explicitly defined
5290 by the specs.
5291 */
5292 //returnNode = importNode.call(originalDocument, returnNode, true);
5293 // untested
5294 returnNode = originalDocument.importNode(returnNode, true);
5295 }
5296
5297 return returnNode;
5298 }
5299
5300 var serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
5301
5302 /* Sanitize final string template-safe */
5303 if (SAFE_FOR_TEMPLATES) {
5304 serializedHTML = serializedHTML.replace(MUSTACHE_EXPR$$1, ' ');
5305 serializedHTML = serializedHTML.replace(ERB_EXPR$$1, ' ');
5306 }
5307
5308 return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
5309 };
5310
5311 /**
5312 * Public method to set the configuration once
5313 * setConfig
5314 *
5315 * @param {Object} cfg configuration object
5316 */
5317 DOMPurify.setConfig = function (cfg) {
5318 _parseConfig(cfg);
5319 SET_CONFIG = true;
5320 };
5321
5322 /**
5323 * Public method to remove the configuration
5324 * clearConfig
5325 *
5326 */
5327 DOMPurify.clearConfig = function () {
5328 CONFIG = null;
5329 SET_CONFIG = false;
5330 };
5331
5332 /**
5333 * Public method to check if an attribute value is valid.
5334 * Uses last set config, if any. Otherwise, uses config defaults.
5335 * isValidAttribute
5336 *
5337 * @param {string} tag Tag name of containing element.
5338 * @param {string} attr Attribute name.
5339 * @param {string} value Attribute value.
5340 * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
5341 */
5342 DOMPurify.isValidAttribute = function (tag, attr, value) {
5343 /* Initialize shared config vars if necessary. */
5344 if (!CONFIG) {
5345 _parseConfig({});
5346 }
5347
5348 var lcTag = tag.toLowerCase();
5349 var lcName = attr.toLowerCase();
5350 return _isValidAttribute(lcTag, lcName, value);
5351 };
5352
5353 /**
5354 * AddHook
5355 * Public method to add DOMPurify hooks
5356 *
5357 * @param {String} entryPoint entry point for the hook to add
5358 * @param {Function} hookFunction function to execute
5359 */
5360 DOMPurify.addHook = function (entryPoint, hookFunction) {
5361 if (typeof hookFunction !== 'function') {
5362 return;
5363 }
5364
5365 hooks[entryPoint] = hooks[entryPoint] || [];
5366 hooks[entryPoint].push(hookFunction);
5367 };
5368
5369 /**
5370 * RemoveHook
5371 * Public method to remove a DOMPurify hook at a given entryPoint
5372 * (pops it from the stack of hooks if more are present)
5373 *
5374 * @param {String} entryPoint entry point for the hook to remove
5375 */
5376 DOMPurify.removeHook = function (entryPoint) {
5377 if (hooks[entryPoint]) {
5378 hooks[entryPoint].pop();
5379 }
5380 };
5381
5382 /**
5383 * RemoveHooks
5384 * Public method to remove all DOMPurify hooks at a given entryPoint
5385 *
5386 * @param {String} entryPoint entry point for the hooks to remove
5387 */
5388 DOMPurify.removeHooks = function (entryPoint) {
5389 if (hooks[entryPoint]) {
5390 hooks[entryPoint] = [];
5391 }
5392 };
5393
5394 /**
5395 * RemoveAllHooks
5396 * Public method to remove all DOMPurify hooks
5397 *
5398 */
5399 DOMPurify.removeAllHooks = function () {
5400 hooks = {};
5401 };
5402
5403 return DOMPurify;
5404 }
5405
5406 var purify = createDOMPurify();
5407 4100
5408 var globalWin = window; 4101 var globalWin = window;
5409 var globalDoc = document; 4102 var globalDoc = document;
5410 4103
5411 var IMAGE_MIME_REGEX = /^image\/(p?jpe?g|gif|png|bmp)$/i; 4104 var IMAGE_MIME_REGEX = /^image\/(p?jpe?g|gif|png|bmp)$/i;
5772 if (!Array.isArray(options.allowedIframeUrls)) { 4465 if (!Array.isArray(options.allowedIframeUrls)) {
5773 options.allowedIframeUrls = []; 4466 options.allowedIframeUrls = [];
5774 } 4467 }
5775 options.allowedIframeUrls.push('https://www.youtube-nocookie.com/embed/'); 4468 options.allowedIframeUrls.push('https://www.youtube-nocookie.com/embed/');
5776 4469
5777 // Create new instance of DOMPurify for each editor instance so can
5778 // have different allowed iframe URLs
5779 // eslint-disable-next-line new-cap
5780 var domPurify = purify();
5781
5782 // Allow iframes for things like YouTube, see:
5783 // https://github.com/cure53/DOMPurify/issues/340#issuecomment-670758980
5784 domPurify.addHook('uponSanitizeElement', function (node, data) {
5785 var allowedUrls = options.allowedIframeUrls;
5786
5787 if (data.tagName === 'iframe') {
5788 var src = attr(node, 'src') || '';
5789
5790 for (var i = 0; i < allowedUrls.length; i++) {
5791 var url = allowedUrls[i];
5792
5793 if (isString(url) && src.substr(0, url.length) === url) {
5794 return;
5795 }
5796
5797 // Handle regex
5798 if (url.test && url.test(src)) {
5799 return;
5800 }
5801 }
5802
5803 // No match so remove
5804 remove(node);
5805 }
5806 });
5807
5808 // Convert target attribute into data-sce-target attributes so XHTML format
5809 // can allow them
5810 domPurify.addHook('afterSanitizeAttributes', function (node) {
5811 if ('target' in node) {
5812 attr(node, 'data-sce-target', attr(node, 'target'));
5813 }
5814
5815 removeAttr(node, 'target');
5816 });
5817
5818 /**
5819 * Sanitize HTML to avoid XSS
5820 *
5821 * @param {string} html
5822 * @return {string} html
5823 * @private
5824 */
5825 function sanitize(html) {
5826 return domPurify.sanitize(html, {
5827 ADD_TAGS: ['iframe'],
5828 ADD_ATTR: ['allowfullscreen', 'frameborder', 'target']
5829 });
5830 }
5831 /** 4470 /**
5832 * Creates the editor iframe and textarea 4471 * Creates the editor iframe and textarea
5833 * @private 4472 * @private
5834 */ 4473 */
5835 init = function () { 4474 init = function () {
5990 4629
5991 var tabIndex = attr(original, 'tabindex'); 4630 var tabIndex = attr(original, 'tabindex');
5992 attr(sourceEditor, 'tabindex', tabIndex); 4631 attr(sourceEditor, 'tabindex', tabIndex);
5993 attr(wysiwygEditor, 'tabindex', tabIndex); 4632 attr(wysiwygEditor, 'tabindex', tabIndex);
5994 4633
5995 rangeHelper = new RangeHelper(wysiwygWindow, null, sanitize); 4634 rangeHelper = new RangeHelper(wysiwygWindow, null);
5996 4635
5997 // load any textarea value into the editor 4636 // load any textarea value into the editor
5998 hide(original); 4637 hide(original);
5999 base.val(original.value); 4638 base.val(original.value);
6000 4639
6978 5617
6979 data[types[i]] = clipboard.getData(types[i]); 5618 data[types[i]] = clipboard.getData(types[i]);
6980 } 5619 }
6981 // Call plugins here with file? 5620 // Call plugins here with file?
6982 data.text = data['text/plain']; 5621 data.text = data['text/plain'];
6983 data.html = sanitize(data['text/html']); 5622 data.html = data['text/html'];
6984 5623
6985 handlePasteData(data); 5624 handlePasteData(data);
6986 // If contentsFragment exists then we are already waiting for a 5625 // If contentsFragment exists then we are already waiting for a
6987 // previous paste so let the handler for that handle this one too 5626 // previous paste so let the handler for that handle this one too
6988 } else if (!pasteContentFragment) { 5627 } else if (!pasteContentFragment) {
7005 editable.scrollTop = scrollTop; 5644 editable.scrollTop = scrollTop;
7006 pasteContentFragment = false; 5645 pasteContentFragment = false;
7007 5646
7008 rangeHelper.restoreRange(); 5647 rangeHelper.restoreRange();
7009 5648
7010 handlePasteData({ html: sanitize(html) }); 5649 handlePasteData({ html: html });
7011 }, 0); 5650 }, 0);
7012 } 5651 }
7013 }; 5652 };
7014 5653
7015 /** 5654 /**
7023 pluginManager.call('pasteRaw', data); 5662 pluginManager.call('pasteRaw', data);
7024 trigger(editorContainer, 'pasteraw', data); 5663 trigger(editorContainer, 'pasteraw', data);
7025 5664
7026 if (data.html) { 5665 if (data.html) {
7027 // Sanitize again in case plugins modified the HTML 5666 // Sanitize again in case plugins modified the HTML
7028 pasteArea.innerHTML = sanitize(data.html); 5667 pasteArea.innerHTML = data.html;
7029 5668
7030 // fix any invalid nesting 5669 // fix any invalid nesting
7031 fixNesting(pasteArea); 5670 fixNesting(pasteArea);
7032 } else { 5671 } else {
7033 pasteArea.innerHTML = entities(data.text || ''); 5672 pasteArea.innerHTML = entities(data.text || '');
7507 base.setWysiwygEditorValue = function (value) { 6146 base.setWysiwygEditorValue = function (value) {
7508 if (!value) { 6147 if (!value) {
7509 value = '<p><br /></p>'; 6148 value = '<p><br /></p>';
7510 } 6149 }
7511 6150
7512 wysiwygBody.innerHTML = sanitize(value); 6151 wysiwygBody.innerHTML = value;
7513 replaceEmoticons(); 6152 replaceEmoticons();
7514 6153
7515 appendNewLine(); 6154 appendNewLine();
7516 triggerValueChanged(); 6155 triggerValueChanged();
7517 autoExpand(); 6156 autoExpand();