Mercurial Hosting > sceditor
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(); |