|
1
|
|
|
/*! @license DOMPurify 2.3.8 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.8/LICENSE */ |
|
2
|
|
|
|
|
3
|
|
|
(function (global, factory) { |
|
4
|
|
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : |
|
5
|
|
|
typeof define === 'function' && define.amd ? define(factory) : |
|
6
|
|
|
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.DOMPurify = factory()); |
|
|
|
|
|
|
7
|
|
|
})(this, (function () { 'use strict'; |
|
8
|
|
|
|
|
9
|
|
View Code Duplication |
function _typeof(obj) { |
|
|
|
|
|
|
10
|
|
|
"@babel/helpers - typeof"; |
|
|
|
|
|
|
11
|
|
|
|
|
12
|
|
|
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { |
|
|
|
|
|
|
13
|
|
|
return typeof obj; |
|
14
|
|
|
} : function (obj) { |
|
15
|
|
|
return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; |
|
16
|
|
|
}, _typeof(obj); |
|
17
|
|
|
} |
|
18
|
|
|
|
|
19
|
|
|
function _setPrototypeOf(o, p) { |
|
20
|
|
|
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { |
|
21
|
|
|
o.__proto__ = p; |
|
22
|
|
|
return o; |
|
23
|
|
|
}; |
|
24
|
|
|
|
|
25
|
|
|
return _setPrototypeOf(o, p); |
|
26
|
|
|
} |
|
27
|
|
|
|
|
28
|
|
View Code Duplication |
function _isNativeReflectConstruct() { |
|
|
|
|
|
|
29
|
|
|
if (typeof Reflect === "undefined" || !Reflect.construct) return false; |
|
30
|
|
|
if (Reflect.construct.sham) return false; |
|
31
|
|
|
if (typeof Proxy === "function") return true; |
|
32
|
|
|
|
|
33
|
|
|
try { |
|
34
|
|
|
Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); |
|
35
|
|
|
return true; |
|
36
|
|
|
} catch (e) { |
|
37
|
|
|
return false; |
|
38
|
|
|
} |
|
39
|
|
|
} |
|
40
|
|
|
|
|
41
|
|
|
function _construct(Parent, args, Class) { |
|
42
|
|
|
if (_isNativeReflectConstruct()) { |
|
43
|
|
|
_construct = Reflect.construct; |
|
44
|
|
|
} else { |
|
45
|
|
|
_construct = function _construct(Parent, args, Class) { |
|
46
|
|
|
var a = [null]; |
|
47
|
|
|
a.push.apply(a, args); |
|
48
|
|
|
var Constructor = Function.bind.apply(Parent, a); |
|
49
|
|
|
var instance = new Constructor(); |
|
50
|
|
|
if (Class) _setPrototypeOf(instance, Class.prototype); |
|
51
|
|
|
return instance; |
|
52
|
|
|
}; |
|
53
|
|
|
} |
|
54
|
|
|
|
|
55
|
|
|
return _construct.apply(null, arguments); |
|
56
|
|
|
} |
|
57
|
|
|
|
|
58
|
|
|
function _toConsumableArray(arr) { |
|
59
|
|
|
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); |
|
60
|
|
|
} |
|
61
|
|
|
|
|
62
|
|
|
function _arrayWithoutHoles(arr) { |
|
63
|
|
|
if (Array.isArray(arr)) return _arrayLikeToArray(arr); |
|
|
|
|
|
|
64
|
|
|
} |
|
65
|
|
|
|
|
66
|
|
|
function _iterableToArray(iter) { |
|
67
|
|
|
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); |
|
|
|
|
|
|
68
|
|
|
} |
|
69
|
|
|
|
|
70
|
|
|
function _unsupportedIterableToArray(o, minLen) { |
|
71
|
|
|
if (!o) return; |
|
|
|
|
|
|
72
|
|
|
if (typeof o === "string") return _arrayLikeToArray(o, minLen); |
|
73
|
|
|
var n = Object.prototype.toString.call(o).slice(8, -1); |
|
74
|
|
|
if (n === "Object" && o.constructor) n = o.constructor.name; |
|
75
|
|
|
if (n === "Map" || n === "Set") return Array.from(o); |
|
76
|
|
|
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); |
|
|
|
|
|
|
77
|
|
|
} |
|
78
|
|
|
|
|
79
|
|
|
function _arrayLikeToArray(arr, len) { |
|
80
|
|
|
if (len == null || len > arr.length) len = arr.length; |
|
|
|
|
|
|
81
|
|
|
|
|
82
|
|
|
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; |
|
|
|
|
|
|
83
|
|
|
|
|
84
|
|
|
return arr2; |
|
85
|
|
|
} |
|
86
|
|
|
|
|
87
|
|
|
function _nonIterableSpread() { |
|
88
|
|
|
throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); |
|
89
|
|
|
} |
|
90
|
|
|
|
|
91
|
|
|
var hasOwnProperty = Object.hasOwnProperty, |
|
92
|
|
|
setPrototypeOf = Object.setPrototypeOf, |
|
93
|
|
|
isFrozen = Object.isFrozen, |
|
94
|
|
|
getPrototypeOf = Object.getPrototypeOf, |
|
95
|
|
|
getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor; |
|
96
|
|
|
var freeze = Object.freeze, |
|
97
|
|
|
seal = Object.seal, |
|
98
|
|
|
create = Object.create; // eslint-disable-line import/no-mutable-exports |
|
99
|
|
|
|
|
100
|
|
|
var _ref = typeof Reflect !== 'undefined' && Reflect, |
|
101
|
|
|
apply = _ref.apply, |
|
102
|
|
|
construct = _ref.construct; |
|
103
|
|
|
|
|
104
|
|
|
if (!apply) { |
|
105
|
|
|
apply = function apply(fun, thisValue, args) { |
|
106
|
|
|
return fun.apply(thisValue, args); |
|
107
|
|
|
}; |
|
108
|
|
|
} |
|
109
|
|
|
|
|
110
|
|
|
if (!freeze) { |
|
111
|
|
|
freeze = function freeze(x) { |
|
112
|
|
|
return x; |
|
113
|
|
|
}; |
|
114
|
|
|
} |
|
115
|
|
|
|
|
116
|
|
|
if (!seal) { |
|
117
|
|
|
seal = function seal(x) { |
|
118
|
|
|
return x; |
|
119
|
|
|
}; |
|
120
|
|
|
} |
|
121
|
|
|
|
|
122
|
|
|
if (!construct) { |
|
123
|
|
|
construct = function construct(Func, args) { |
|
124
|
|
|
return _construct(Func, _toConsumableArray(args)); |
|
125
|
|
|
}; |
|
126
|
|
|
} |
|
127
|
|
|
|
|
128
|
|
|
var arrayForEach = unapply(Array.prototype.forEach); |
|
129
|
|
|
var arrayPop = unapply(Array.prototype.pop); |
|
130
|
|
|
var arrayPush = unapply(Array.prototype.push); |
|
131
|
|
|
var stringToLowerCase = unapply(String.prototype.toLowerCase); |
|
132
|
|
|
var stringMatch = unapply(String.prototype.match); |
|
133
|
|
|
var stringReplace = unapply(String.prototype.replace); |
|
134
|
|
|
var stringIndexOf = unapply(String.prototype.indexOf); |
|
135
|
|
|
var stringTrim = unapply(String.prototype.trim); |
|
136
|
|
|
var regExpTest = unapply(RegExp.prototype.test); |
|
137
|
|
|
var typeErrorCreate = unconstruct(TypeError); |
|
138
|
|
|
function unapply(func) { |
|
139
|
|
|
return function (thisArg) { |
|
140
|
|
|
for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { |
|
|
|
|
|
|
141
|
|
|
args[_key - 1] = arguments[_key]; |
|
142
|
|
|
} |
|
143
|
|
|
|
|
144
|
|
|
return apply(func, thisArg, args); |
|
145
|
|
|
}; |
|
146
|
|
|
} |
|
147
|
|
|
function unconstruct(func) { |
|
148
|
|
|
return function () { |
|
149
|
|
|
for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { |
|
|
|
|
|
|
150
|
|
|
args[_key2] = arguments[_key2]; |
|
151
|
|
|
} |
|
152
|
|
|
|
|
153
|
|
|
return construct(func, args); |
|
154
|
|
|
}; |
|
155
|
|
|
} |
|
156
|
|
|
/* Add properties to a lookup table */ |
|
157
|
|
|
|
|
158
|
|
|
function addToSet(set, array) { |
|
159
|
|
|
if (setPrototypeOf) { |
|
160
|
|
|
// Make 'in' and truthy checks like Boolean(set.constructor) |
|
161
|
|
|
// independent of any properties defined on Object.prototype. |
|
162
|
|
|
// Prevent prototype setters from intercepting set as a this value. |
|
163
|
|
|
setPrototypeOf(set, null); |
|
164
|
|
|
} |
|
165
|
|
|
|
|
166
|
|
|
var l = array.length; |
|
167
|
|
|
|
|
168
|
|
|
while (l--) { |
|
169
|
|
|
var element = array[l]; |
|
170
|
|
|
|
|
171
|
|
|
if (typeof element === 'string') { |
|
172
|
|
|
var lcElement = stringToLowerCase(element); |
|
173
|
|
|
|
|
174
|
|
|
if (lcElement !== element) { |
|
175
|
|
|
// Config presets (e.g. tags.js, attrs.js) are immutable. |
|
176
|
|
|
if (!isFrozen(array)) { |
|
177
|
|
|
array[l] = lcElement; |
|
178
|
|
|
} |
|
179
|
|
|
|
|
180
|
|
|
element = lcElement; |
|
181
|
|
|
} |
|
182
|
|
|
} |
|
183
|
|
|
|
|
184
|
|
|
set[element] = true; |
|
185
|
|
|
} |
|
186
|
|
|
|
|
187
|
|
|
return set; |
|
188
|
|
|
} |
|
189
|
|
|
/* Shallow clone an object */ |
|
190
|
|
|
|
|
191
|
|
|
function clone(object) { |
|
192
|
|
|
var newObject = create(null); |
|
193
|
|
|
var property; |
|
194
|
|
|
|
|
195
|
|
|
for (property in object) { |
|
196
|
|
|
if (apply(hasOwnProperty, object, [property])) { |
|
197
|
|
|
newObject[property] = object[property]; |
|
198
|
|
|
} |
|
199
|
|
|
} |
|
200
|
|
|
|
|
201
|
|
|
return newObject; |
|
202
|
|
|
} |
|
203
|
|
|
/* IE10 doesn't support __lookupGetter__ so lets' |
|
204
|
|
|
* simulate it. It also automatically checks |
|
205
|
|
|
* if the prop is function or getter and behaves |
|
206
|
|
|
* accordingly. */ |
|
207
|
|
|
|
|
208
|
|
|
function lookupGetter(object, prop) { |
|
209
|
|
|
while (object !== null) { |
|
210
|
|
|
var desc = getOwnPropertyDescriptor(object, prop); |
|
211
|
|
|
|
|
212
|
|
|
if (desc) { |
|
213
|
|
|
if (desc.get) { |
|
214
|
|
|
return unapply(desc.get); |
|
215
|
|
|
} |
|
216
|
|
|
|
|
217
|
|
|
if (typeof desc.value === 'function') { |
|
218
|
|
|
return unapply(desc.value); |
|
219
|
|
|
} |
|
220
|
|
|
} |
|
221
|
|
|
|
|
222
|
|
|
object = getPrototypeOf(object); |
|
223
|
|
|
} |
|
224
|
|
|
|
|
225
|
|
|
function fallbackValue(element) { |
|
226
|
|
|
console.warn('fallback value for', element); |
|
227
|
|
|
return null; |
|
228
|
|
|
} |
|
229
|
|
|
|
|
230
|
|
|
return fallbackValue; |
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
|
|
var html$1 = 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']); // SVG |
|
234
|
|
|
|
|
235
|
|
|
var svg$1 = 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']); |
|
236
|
|
|
var svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']); // List of SVG elements that are disallowed by default. |
|
237
|
|
|
// We still need to know them so that we can do namespace |
|
238
|
|
|
// checks properly in case one wants to add them to |
|
239
|
|
|
// allow-list. |
|
240
|
|
|
|
|
241
|
|
|
var svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'fedropshadow', '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']); |
|
242
|
|
|
var mathMl$1 = 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']); // Similarly to SVG, we want to know all MathML elements, |
|
243
|
|
|
// even those that we disallow by default. |
|
244
|
|
|
|
|
245
|
|
|
var mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']); |
|
246
|
|
|
var text = freeze(['#text']); |
|
247
|
|
|
|
|
248
|
|
|
var html = 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', 'nonce', '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', 'slot']); |
|
249
|
|
|
var svg = 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', 'transform-origin', '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']); |
|
250
|
|
|
var mathMl = 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']); |
|
251
|
|
|
var xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']); |
|
252
|
|
|
|
|
253
|
|
|
var MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode |
|
254
|
|
|
|
|
255
|
|
|
var ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm); |
|
256
|
|
|
var DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape |
|
257
|
|
|
|
|
258
|
|
|
var ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape |
|
259
|
|
|
|
|
260
|
|
|
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 |
|
261
|
|
|
); |
|
262
|
|
|
var IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i); |
|
263
|
|
|
var ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex |
|
264
|
|
|
); |
|
265
|
|
|
var DOCTYPE_NAME = seal(/^html$/i); |
|
266
|
|
|
|
|
267
|
|
|
var getGlobal = function getGlobal() { |
|
268
|
|
|
return typeof window === 'undefined' ? null : window; |
|
269
|
|
|
}; |
|
270
|
|
|
/** |
|
271
|
|
|
* Creates a no-op policy for internal use only. |
|
272
|
|
|
* Don't export this function outside this module! |
|
273
|
|
|
* @param {?TrustedTypePolicyFactory} trustedTypes The policy factory. |
|
274
|
|
|
* @param {Document} document The document object (to determine policy name suffix) |
|
275
|
|
|
* @return {?TrustedTypePolicy} The policy created (or null, if Trusted Types |
|
276
|
|
|
* are not supported). |
|
277
|
|
|
*/ |
|
278
|
|
|
|
|
279
|
|
|
|
|
280
|
|
|
var _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, document) { |
|
281
|
|
|
if (_typeof(trustedTypes) !== 'object' || typeof trustedTypes.createPolicy !== 'function') { |
|
282
|
|
|
return null; |
|
283
|
|
|
} // Allow the callers to control the unique policy name |
|
284
|
|
|
// by adding a data-tt-policy-suffix to the script element with the DOMPurify. |
|
285
|
|
|
// Policy creation with duplicate names throws in Trusted Types. |
|
286
|
|
|
|
|
287
|
|
|
|
|
288
|
|
|
var suffix = null; |
|
289
|
|
|
var ATTR_NAME = 'data-tt-policy-suffix'; |
|
290
|
|
|
|
|
291
|
|
|
if (document.currentScript && document.currentScript.hasAttribute(ATTR_NAME)) { |
|
292
|
|
|
suffix = document.currentScript.getAttribute(ATTR_NAME); |
|
293
|
|
|
} |
|
294
|
|
|
|
|
295
|
|
|
var policyName = 'dompurify' + (suffix ? '#' + suffix : ''); |
|
296
|
|
|
|
|
297
|
|
|
try { |
|
298
|
|
|
return trustedTypes.createPolicy(policyName, { |
|
299
|
|
|
createHTML: function createHTML(html) { |
|
300
|
|
|
return html; |
|
301
|
|
|
} |
|
302
|
|
|
}); |
|
303
|
|
|
} catch (_) { |
|
304
|
|
|
// Policy creation failed (most likely another DOMPurify script has |
|
305
|
|
|
// already run). Skip creating the policy, as this will only cause errors |
|
306
|
|
|
// if TT are enforced. |
|
307
|
|
|
console.warn('TrustedTypes policy ' + policyName + ' could not be created.'); |
|
308
|
|
|
return null; |
|
309
|
|
|
} |
|
310
|
|
|
}; |
|
311
|
|
|
|
|
312
|
|
|
function createDOMPurify() { |
|
313
|
|
|
var window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal(); |
|
314
|
|
|
|
|
315
|
|
|
var DOMPurify = function DOMPurify(root) { |
|
316
|
|
|
return createDOMPurify(root); |
|
317
|
|
|
}; |
|
318
|
|
|
/** |
|
319
|
|
|
* Version label, exposed for easier checks |
|
320
|
|
|
* if DOMPurify is up to date or not |
|
321
|
|
|
*/ |
|
322
|
|
|
|
|
323
|
|
|
|
|
324
|
|
|
DOMPurify.version = '2.3.8'; |
|
325
|
|
|
/** |
|
326
|
|
|
* Array of elements that DOMPurify removed during sanitation. |
|
327
|
|
|
* Empty if nothing was removed. |
|
328
|
|
|
*/ |
|
329
|
|
|
|
|
330
|
|
|
DOMPurify.removed = []; |
|
331
|
|
|
|
|
332
|
|
|
if (!window || !window.document || window.document.nodeType !== 9) { |
|
333
|
|
|
// Not running in a browser, provide a factory function |
|
334
|
|
|
// so that you can pass your own Window |
|
335
|
|
|
DOMPurify.isSupported = false; |
|
336
|
|
|
return DOMPurify; |
|
337
|
|
|
} |
|
338
|
|
|
|
|
339
|
|
|
var originalDocument = window.document; |
|
340
|
|
|
var document = window.document; |
|
341
|
|
|
var DocumentFragment = window.DocumentFragment, |
|
342
|
|
|
HTMLTemplateElement = window.HTMLTemplateElement, |
|
343
|
|
|
Node = window.Node, |
|
344
|
|
|
Element = window.Element, |
|
345
|
|
|
NodeFilter = window.NodeFilter, |
|
346
|
|
|
_window$NamedNodeMap = window.NamedNodeMap, |
|
347
|
|
|
NamedNodeMap = _window$NamedNodeMap === void 0 ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap, |
|
|
|
|
|
|
348
|
|
|
HTMLFormElement = window.HTMLFormElement, |
|
349
|
|
|
DOMParser = window.DOMParser, |
|
350
|
|
|
trustedTypes = window.trustedTypes; |
|
351
|
|
|
var ElementPrototype = Element.prototype; |
|
352
|
|
|
var cloneNode = lookupGetter(ElementPrototype, 'cloneNode'); |
|
353
|
|
|
var getNextSibling = lookupGetter(ElementPrototype, 'nextSibling'); |
|
354
|
|
|
var getChildNodes = lookupGetter(ElementPrototype, 'childNodes'); |
|
355
|
|
|
var getParentNode = lookupGetter(ElementPrototype, 'parentNode'); // As per issue #47, the web-components registry is inherited by a |
|
356
|
|
|
// new document created via createHTMLDocument. As per the spec |
|
357
|
|
|
// (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries) |
|
358
|
|
|
// a new empty registry is used when creating a template contents owner |
|
359
|
|
|
// document, so we use that as our parent document to ensure nothing |
|
360
|
|
|
// is inherited. |
|
361
|
|
|
|
|
362
|
|
|
if (typeof HTMLTemplateElement === 'function') { |
|
363
|
|
|
var template = document.createElement('template'); |
|
364
|
|
|
|
|
365
|
|
|
if (template.content && template.content.ownerDocument) { |
|
366
|
|
|
document = template.content.ownerDocument; |
|
367
|
|
|
} |
|
368
|
|
|
} |
|
369
|
|
|
|
|
370
|
|
|
var trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, originalDocument); |
|
371
|
|
|
|
|
372
|
|
|
var emptyHTML = trustedTypesPolicy ? trustedTypesPolicy.createHTML('') : ''; |
|
373
|
|
|
var _document = document, |
|
374
|
|
|
implementation = _document.implementation, |
|
375
|
|
|
createNodeIterator = _document.createNodeIterator, |
|
376
|
|
|
createDocumentFragment = _document.createDocumentFragment, |
|
377
|
|
|
getElementsByTagName = _document.getElementsByTagName; |
|
378
|
|
|
var importNode = originalDocument.importNode; |
|
379
|
|
|
var documentMode = {}; |
|
380
|
|
|
|
|
381
|
|
|
try { |
|
382
|
|
|
documentMode = clone(document).documentMode ? document.documentMode : {}; |
|
383
|
|
|
} catch (_) {} |
|
|
|
|
|
|
384
|
|
|
|
|
385
|
|
|
var hooks = {}; |
|
386
|
|
|
/** |
|
387
|
|
|
* Expose whether this browser supports running the full DOMPurify. |
|
388
|
|
|
*/ |
|
389
|
|
|
|
|
390
|
|
|
DOMPurify.isSupported = typeof getParentNode === 'function' && implementation && typeof implementation.createHTMLDocument !== 'undefined' && documentMode !== 9; |
|
391
|
|
|
var MUSTACHE_EXPR$1 = MUSTACHE_EXPR, |
|
392
|
|
|
ERB_EXPR$1 = ERB_EXPR, |
|
393
|
|
|
DATA_ATTR$1 = DATA_ATTR, |
|
394
|
|
|
ARIA_ATTR$1 = ARIA_ATTR, |
|
395
|
|
|
IS_SCRIPT_OR_DATA$1 = IS_SCRIPT_OR_DATA, |
|
396
|
|
|
ATTR_WHITESPACE$1 = ATTR_WHITESPACE; |
|
397
|
|
|
var IS_ALLOWED_URI$1 = IS_ALLOWED_URI; |
|
398
|
|
|
/** |
|
399
|
|
|
* We consider the elements and attributes below to be safe. Ideally |
|
400
|
|
|
* don't add any new ones but feel free to remove unwanted ones. |
|
401
|
|
|
*/ |
|
402
|
|
|
|
|
403
|
|
|
/* allowed element names */ |
|
404
|
|
|
|
|
405
|
|
|
var ALLOWED_TAGS = null; |
|
406
|
|
|
var DEFAULT_ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray(html$1), _toConsumableArray(svg$1), _toConsumableArray(svgFilters), _toConsumableArray(mathMl$1), _toConsumableArray(text))); |
|
407
|
|
|
/* Allowed attribute names */ |
|
408
|
|
|
|
|
409
|
|
|
var ALLOWED_ATTR = null; |
|
410
|
|
|
var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray(html), _toConsumableArray(svg), _toConsumableArray(mathMl), _toConsumableArray(xml))); |
|
411
|
|
|
/* |
|
412
|
|
|
* Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements. |
|
413
|
|
|
* @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements) |
|
414
|
|
|
* @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list) |
|
415
|
|
|
* @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`. |
|
416
|
|
|
*/ |
|
417
|
|
|
|
|
418
|
|
|
var CUSTOM_ELEMENT_HANDLING = Object.seal(Object.create(null, { |
|
419
|
|
|
tagNameCheck: { |
|
420
|
|
|
writable: true, |
|
421
|
|
|
configurable: false, |
|
422
|
|
|
enumerable: true, |
|
423
|
|
|
value: null |
|
424
|
|
|
}, |
|
425
|
|
|
attributeNameCheck: { |
|
426
|
|
|
writable: true, |
|
427
|
|
|
configurable: false, |
|
428
|
|
|
enumerable: true, |
|
429
|
|
|
value: null |
|
430
|
|
|
}, |
|
431
|
|
|
allowCustomizedBuiltInElements: { |
|
432
|
|
|
writable: true, |
|
433
|
|
|
configurable: false, |
|
434
|
|
|
enumerable: true, |
|
435
|
|
|
value: false |
|
436
|
|
|
} |
|
437
|
|
|
})); |
|
438
|
|
|
/* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */ |
|
439
|
|
|
|
|
440
|
|
|
var FORBID_TAGS = null; |
|
441
|
|
|
/* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */ |
|
442
|
|
|
|
|
443
|
|
|
var FORBID_ATTR = null; |
|
444
|
|
|
/* Decide if ARIA attributes are okay */ |
|
445
|
|
|
|
|
446
|
|
|
var ALLOW_ARIA_ATTR = true; |
|
447
|
|
|
/* Decide if custom data attributes are okay */ |
|
448
|
|
|
|
|
449
|
|
|
var ALLOW_DATA_ATTR = true; |
|
450
|
|
|
/* Decide if unknown protocols are okay */ |
|
451
|
|
|
|
|
452
|
|
|
var ALLOW_UNKNOWN_PROTOCOLS = false; |
|
453
|
|
|
/* Output should be safe for common template engines. |
|
454
|
|
|
* This means, DOMPurify removes data attributes, mustaches and ERB |
|
455
|
|
|
*/ |
|
456
|
|
|
|
|
457
|
|
|
var SAFE_FOR_TEMPLATES = false; |
|
458
|
|
|
/* Decide if document with <html>... should be returned */ |
|
459
|
|
|
|
|
460
|
|
|
var WHOLE_DOCUMENT = false; |
|
461
|
|
|
/* Track whether config is already set on this instance of DOMPurify. */ |
|
462
|
|
|
|
|
463
|
|
|
var SET_CONFIG = false; |
|
464
|
|
|
/* Decide if all elements (e.g. style, script) must be children of |
|
465
|
|
|
* document.body. By default, browsers might move them to document.head */ |
|
466
|
|
|
|
|
467
|
|
|
var FORCE_BODY = false; |
|
468
|
|
|
/* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html |
|
469
|
|
|
* string (or a TrustedHTML object if Trusted Types are supported). |
|
470
|
|
|
* If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead |
|
471
|
|
|
*/ |
|
472
|
|
|
|
|
473
|
|
|
var RETURN_DOM = false; |
|
474
|
|
|
/* Decide if a DOM `DocumentFragment` should be returned, instead of a html |
|
475
|
|
|
* string (or a TrustedHTML object if Trusted Types are supported) */ |
|
476
|
|
|
|
|
477
|
|
|
var RETURN_DOM_FRAGMENT = false; |
|
478
|
|
|
/* Try to return a Trusted Type object instead of a string, return a string in |
|
479
|
|
|
* case Trusted Types are not supported */ |
|
480
|
|
|
|
|
481
|
|
|
var RETURN_TRUSTED_TYPE = false; |
|
482
|
|
|
/* Output should be free from DOM clobbering attacks? */ |
|
483
|
|
|
|
|
484
|
|
|
var SANITIZE_DOM = true; |
|
485
|
|
|
/* Keep element content when removing element? */ |
|
486
|
|
|
|
|
487
|
|
|
var KEEP_CONTENT = true; |
|
488
|
|
|
/* If a `Node` is passed to sanitize(), then performs sanitization in-place instead |
|
489
|
|
|
* of importing it into a new Document and returning a sanitized copy */ |
|
490
|
|
|
|
|
491
|
|
|
var IN_PLACE = false; |
|
492
|
|
|
/* Allow usage of profiles like html, svg and mathMl */ |
|
493
|
|
|
|
|
494
|
|
|
var USE_PROFILES = {}; |
|
495
|
|
|
/* Tags to ignore content of when KEEP_CONTENT is true */ |
|
496
|
|
|
|
|
497
|
|
|
var FORBID_CONTENTS = null; |
|
498
|
|
|
var DEFAULT_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']); |
|
499
|
|
|
/* Tags that are safe for data: URIs */ |
|
500
|
|
|
|
|
501
|
|
|
var DATA_URI_TAGS = null; |
|
502
|
|
|
var DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']); |
|
503
|
|
|
/* Attributes safe for values like "javascript:" */ |
|
504
|
|
|
|
|
505
|
|
|
var URI_SAFE_ATTRIBUTES = null; |
|
506
|
|
|
var DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']); |
|
507
|
|
|
var MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML'; |
|
508
|
|
|
var SVG_NAMESPACE = 'http://www.w3.org/2000/svg'; |
|
509
|
|
|
var HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml'; |
|
510
|
|
|
/* Document namespace */ |
|
511
|
|
|
|
|
512
|
|
|
var NAMESPACE = HTML_NAMESPACE; |
|
513
|
|
|
var IS_EMPTY_INPUT = false; |
|
514
|
|
|
/* Parsing of strict XHTML documents */ |
|
515
|
|
|
|
|
516
|
|
|
var PARSER_MEDIA_TYPE; |
|
517
|
|
|
var SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html']; |
|
518
|
|
|
var DEFAULT_PARSER_MEDIA_TYPE = 'text/html'; |
|
519
|
|
|
var transformCaseFunc; |
|
520
|
|
|
/* Keep a reference to config to pass to hooks */ |
|
521
|
|
|
|
|
522
|
|
|
var CONFIG = null; |
|
523
|
|
|
/* Ideally, do not touch anything below this line */ |
|
524
|
|
|
|
|
525
|
|
|
/* ______________________________________________ */ |
|
526
|
|
|
|
|
527
|
|
|
var formElement = document.createElement('form'); |
|
528
|
|
|
|
|
529
|
|
|
var isRegexOrFunction = function isRegexOrFunction(testValue) { |
|
530
|
|
|
return testValue instanceof RegExp || testValue instanceof Function; |
|
531
|
|
|
}; |
|
532
|
|
|
/** |
|
533
|
|
|
* _parseConfig |
|
534
|
|
|
* |
|
535
|
|
|
* @param {Object} cfg optional config literal |
|
536
|
|
|
*/ |
|
537
|
|
|
// eslint-disable-next-line complexity |
|
538
|
|
|
|
|
539
|
|
|
|
|
540
|
|
|
var _parseConfig = function _parseConfig(cfg) { |
|
541
|
|
|
if (CONFIG && CONFIG === cfg) { |
|
542
|
|
|
return; |
|
543
|
|
|
} |
|
544
|
|
|
/* Shield configuration object from tampering */ |
|
545
|
|
|
|
|
546
|
|
|
|
|
547
|
|
|
if (!cfg || _typeof(cfg) !== 'object') { |
|
548
|
|
|
cfg = {}; |
|
549
|
|
|
} |
|
550
|
|
|
/* Shield configuration object from prototype pollution */ |
|
551
|
|
|
|
|
552
|
|
|
|
|
553
|
|
|
cfg = clone(cfg); |
|
554
|
|
|
/* Set configuration parameters */ |
|
555
|
|
|
|
|
556
|
|
|
ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS; |
|
557
|
|
|
ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR; |
|
558
|
|
|
URI_SAFE_ATTRIBUTES = 'ADD_URI_SAFE_ATTR' in cfg ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES), cfg.ADD_URI_SAFE_ATTR) : DEFAULT_URI_SAFE_ATTRIBUTES; |
|
559
|
|
|
DATA_URI_TAGS = 'ADD_DATA_URI_TAGS' in cfg ? addToSet(clone(DEFAULT_DATA_URI_TAGS), cfg.ADD_DATA_URI_TAGS) : DEFAULT_DATA_URI_TAGS; |
|
560
|
|
|
FORBID_CONTENTS = 'FORBID_CONTENTS' in cfg ? addToSet({}, cfg.FORBID_CONTENTS) : DEFAULT_FORBID_CONTENTS; |
|
561
|
|
|
FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {}; |
|
562
|
|
|
FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {}; |
|
563
|
|
|
USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false; |
|
564
|
|
|
ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true |
|
565
|
|
|
|
|
566
|
|
|
ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true |
|
567
|
|
|
|
|
568
|
|
|
ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false |
|
569
|
|
|
|
|
570
|
|
|
SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false |
|
571
|
|
|
|
|
572
|
|
|
WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false |
|
573
|
|
|
|
|
574
|
|
|
RETURN_DOM = cfg.RETURN_DOM || false; // Default false |
|
575
|
|
|
|
|
576
|
|
|
RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false |
|
577
|
|
|
|
|
578
|
|
|
RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false |
|
579
|
|
|
|
|
580
|
|
|
FORCE_BODY = cfg.FORCE_BODY || false; // Default false |
|
581
|
|
|
|
|
582
|
|
|
SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true |
|
583
|
|
|
|
|
584
|
|
|
KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true |
|
585
|
|
|
|
|
586
|
|
|
IN_PLACE = cfg.IN_PLACE || false; // Default false |
|
587
|
|
|
|
|
588
|
|
|
IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$1; |
|
589
|
|
|
NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE; |
|
590
|
|
|
|
|
591
|
|
|
if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) { |
|
592
|
|
|
CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck; |
|
593
|
|
|
} |
|
594
|
|
|
|
|
595
|
|
|
if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) { |
|
596
|
|
|
CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck; |
|
597
|
|
|
} |
|
598
|
|
|
|
|
599
|
|
|
if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') { |
|
600
|
|
|
CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements; |
|
601
|
|
|
} |
|
602
|
|
|
|
|
603
|
|
|
PARSER_MEDIA_TYPE = // eslint-disable-next-line unicorn/prefer-includes |
|
604
|
|
|
SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? PARSER_MEDIA_TYPE = DEFAULT_PARSER_MEDIA_TYPE : PARSER_MEDIA_TYPE = cfg.PARSER_MEDIA_TYPE; // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is. |
|
605
|
|
|
|
|
606
|
|
|
transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? function (x) { |
|
607
|
|
|
return x; |
|
608
|
|
|
} : stringToLowerCase; |
|
609
|
|
|
|
|
610
|
|
|
if (SAFE_FOR_TEMPLATES) { |
|
611
|
|
|
ALLOW_DATA_ATTR = false; |
|
612
|
|
|
} |
|
613
|
|
|
|
|
614
|
|
|
if (RETURN_DOM_FRAGMENT) { |
|
615
|
|
|
RETURN_DOM = true; |
|
616
|
|
|
} |
|
617
|
|
|
/* Parse profile info */ |
|
618
|
|
|
|
|
619
|
|
|
|
|
620
|
|
|
if (USE_PROFILES) { |
|
621
|
|
|
ALLOWED_TAGS = addToSet({}, _toConsumableArray(text)); |
|
622
|
|
|
ALLOWED_ATTR = []; |
|
623
|
|
|
|
|
624
|
|
|
if (USE_PROFILES.html === true) { |
|
625
|
|
|
addToSet(ALLOWED_TAGS, html$1); |
|
626
|
|
|
addToSet(ALLOWED_ATTR, html); |
|
627
|
|
|
} |
|
628
|
|
|
|
|
629
|
|
|
if (USE_PROFILES.svg === true) { |
|
630
|
|
|
addToSet(ALLOWED_TAGS, svg$1); |
|
631
|
|
|
addToSet(ALLOWED_ATTR, svg); |
|
632
|
|
|
addToSet(ALLOWED_ATTR, xml); |
|
633
|
|
|
} |
|
634
|
|
|
|
|
635
|
|
|
if (USE_PROFILES.svgFilters === true) { |
|
636
|
|
|
addToSet(ALLOWED_TAGS, svgFilters); |
|
637
|
|
|
addToSet(ALLOWED_ATTR, svg); |
|
638
|
|
|
addToSet(ALLOWED_ATTR, xml); |
|
639
|
|
|
} |
|
640
|
|
|
|
|
641
|
|
|
if (USE_PROFILES.mathMl === true) { |
|
642
|
|
|
addToSet(ALLOWED_TAGS, mathMl$1); |
|
643
|
|
|
addToSet(ALLOWED_ATTR, mathMl); |
|
644
|
|
|
addToSet(ALLOWED_ATTR, xml); |
|
645
|
|
|
} |
|
646
|
|
|
} |
|
647
|
|
|
/* Merge configuration parameters */ |
|
648
|
|
|
|
|
649
|
|
|
|
|
650
|
|
|
if (cfg.ADD_TAGS) { |
|
651
|
|
|
if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) { |
|
652
|
|
|
ALLOWED_TAGS = clone(ALLOWED_TAGS); |
|
653
|
|
|
} |
|
654
|
|
|
|
|
655
|
|
|
addToSet(ALLOWED_TAGS, cfg.ADD_TAGS); |
|
656
|
|
|
} |
|
657
|
|
|
|
|
658
|
|
|
if (cfg.ADD_ATTR) { |
|
659
|
|
|
if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) { |
|
660
|
|
|
ALLOWED_ATTR = clone(ALLOWED_ATTR); |
|
661
|
|
|
} |
|
662
|
|
|
|
|
663
|
|
|
addToSet(ALLOWED_ATTR, cfg.ADD_ATTR); |
|
664
|
|
|
} |
|
665
|
|
|
|
|
666
|
|
|
if (cfg.ADD_URI_SAFE_ATTR) { |
|
667
|
|
|
addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR); |
|
668
|
|
|
} |
|
669
|
|
|
|
|
670
|
|
|
if (cfg.FORBID_CONTENTS) { |
|
671
|
|
|
if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) { |
|
672
|
|
|
FORBID_CONTENTS = clone(FORBID_CONTENTS); |
|
673
|
|
|
} |
|
674
|
|
|
|
|
675
|
|
|
addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS); |
|
676
|
|
|
} |
|
677
|
|
|
/* Add #text in case KEEP_CONTENT is set to true */ |
|
678
|
|
|
|
|
679
|
|
|
|
|
680
|
|
|
if (KEEP_CONTENT) { |
|
681
|
|
|
ALLOWED_TAGS['#text'] = true; |
|
682
|
|
|
} |
|
683
|
|
|
/* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */ |
|
684
|
|
|
|
|
685
|
|
|
|
|
686
|
|
|
if (WHOLE_DOCUMENT) { |
|
687
|
|
|
addToSet(ALLOWED_TAGS, ['html', 'head', 'body']); |
|
688
|
|
|
} |
|
689
|
|
|
/* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */ |
|
690
|
|
|
|
|
691
|
|
|
|
|
692
|
|
|
if (ALLOWED_TAGS.table) { |
|
693
|
|
|
addToSet(ALLOWED_TAGS, ['tbody']); |
|
694
|
|
|
delete FORBID_TAGS.tbody; |
|
695
|
|
|
} // Prevent further manipulation of configuration. |
|
696
|
|
|
// Not available in IE8, Safari 5, etc. |
|
697
|
|
|
|
|
698
|
|
|
|
|
699
|
|
|
if (freeze) { |
|
700
|
|
|
freeze(cfg); |
|
701
|
|
|
} |
|
702
|
|
|
|
|
703
|
|
|
CONFIG = cfg; |
|
704
|
|
|
}; |
|
705
|
|
|
|
|
706
|
|
|
var MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']); |
|
707
|
|
|
var HTML_INTEGRATION_POINTS = addToSet({}, ['foreignobject', 'desc', 'title', 'annotation-xml']); // Certain elements are allowed in both SVG and HTML |
|
708
|
|
|
// namespace. We need to specify them explicitly |
|
709
|
|
|
// so that they don't get erroneously deleted from |
|
710
|
|
|
// HTML namespace. |
|
711
|
|
|
|
|
712
|
|
|
var COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']); |
|
713
|
|
|
/* Keep track of all possible SVG and MathML tags |
|
714
|
|
|
* so that we can perform the namespace checks |
|
715
|
|
|
* correctly. */ |
|
716
|
|
|
|
|
717
|
|
|
var ALL_SVG_TAGS = addToSet({}, svg$1); |
|
718
|
|
|
addToSet(ALL_SVG_TAGS, svgFilters); |
|
719
|
|
|
addToSet(ALL_SVG_TAGS, svgDisallowed); |
|
720
|
|
|
var ALL_MATHML_TAGS = addToSet({}, mathMl$1); |
|
721
|
|
|
addToSet(ALL_MATHML_TAGS, mathMlDisallowed); |
|
722
|
|
|
/** |
|
723
|
|
|
* |
|
724
|
|
|
* |
|
725
|
|
|
* @param {Element} element a DOM element whose namespace is being checked |
|
726
|
|
|
* @returns {boolean} Return false if the element has a |
|
727
|
|
|
* namespace that a spec-compliant parser would never |
|
728
|
|
|
* return. Return true otherwise. |
|
729
|
|
|
*/ |
|
730
|
|
|
|
|
731
|
|
|
var _checkValidNamespace = function _checkValidNamespace(element) { |
|
732
|
|
|
var parent = getParentNode(element); // In JSDOM, if we're inside shadow DOM, then parentNode |
|
733
|
|
|
// can be null. We just simulate parent in this case. |
|
734
|
|
|
|
|
735
|
|
|
if (!parent || !parent.tagName) { |
|
736
|
|
|
parent = { |
|
737
|
|
|
namespaceURI: HTML_NAMESPACE, |
|
738
|
|
|
tagName: 'template' |
|
739
|
|
|
}; |
|
740
|
|
|
} |
|
741
|
|
|
|
|
742
|
|
|
var tagName = stringToLowerCase(element.tagName); |
|
743
|
|
|
var parentTagName = stringToLowerCase(parent.tagName); |
|
744
|
|
|
|
|
745
|
|
|
if (element.namespaceURI === SVG_NAMESPACE) { |
|
746
|
|
|
// The only way to switch from HTML namespace to SVG |
|
747
|
|
|
// is via <svg>. If it happens via any other tag, then |
|
748
|
|
|
// it should be killed. |
|
749
|
|
|
if (parent.namespaceURI === HTML_NAMESPACE) { |
|
750
|
|
|
return tagName === 'svg'; |
|
751
|
|
|
} // The only way to switch from MathML to SVG is via |
|
752
|
|
|
// svg if parent is either <annotation-xml> or MathML |
|
753
|
|
|
// text integration points. |
|
754
|
|
|
|
|
755
|
|
|
|
|
756
|
|
|
if (parent.namespaceURI === MATHML_NAMESPACE) { |
|
757
|
|
|
return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]); |
|
758
|
|
|
} // We only allow elements that are defined in SVG |
|
759
|
|
|
// spec. All others are disallowed in SVG namespace. |
|
760
|
|
|
|
|
761
|
|
|
|
|
762
|
|
|
return Boolean(ALL_SVG_TAGS[tagName]); |
|
763
|
|
|
} |
|
764
|
|
|
|
|
765
|
|
|
if (element.namespaceURI === MATHML_NAMESPACE) { |
|
766
|
|
|
// The only way to switch from HTML namespace to MathML |
|
767
|
|
|
// is via <math>. If it happens via any other tag, then |
|
768
|
|
|
// it should be killed. |
|
769
|
|
|
if (parent.namespaceURI === HTML_NAMESPACE) { |
|
770
|
|
|
return tagName === 'math'; |
|
771
|
|
|
} // The only way to switch from SVG to MathML is via |
|
772
|
|
|
// <math> and HTML integration points |
|
773
|
|
|
|
|
774
|
|
|
|
|
775
|
|
|
if (parent.namespaceURI === SVG_NAMESPACE) { |
|
776
|
|
|
return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName]; |
|
777
|
|
|
} // We only allow elements that are defined in MathML |
|
778
|
|
|
// spec. All others are disallowed in MathML namespace. |
|
779
|
|
|
|
|
780
|
|
|
|
|
781
|
|
|
return Boolean(ALL_MATHML_TAGS[tagName]); |
|
782
|
|
|
} |
|
783
|
|
|
|
|
784
|
|
|
if (element.namespaceURI === HTML_NAMESPACE) { |
|
785
|
|
|
// The only way to switch from SVG to HTML is via |
|
786
|
|
|
// HTML integration points, and from MathML to HTML |
|
787
|
|
|
// is via MathML text integration points |
|
788
|
|
|
if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) { |
|
789
|
|
|
return false; |
|
790
|
|
|
} |
|
791
|
|
|
|
|
792
|
|
|
if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) { |
|
793
|
|
|
return false; |
|
794
|
|
|
} // We disallow tags that are specific for MathML |
|
795
|
|
|
// or SVG and should never appear in HTML namespace |
|
796
|
|
|
|
|
797
|
|
|
|
|
798
|
|
|
return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]); |
|
799
|
|
|
} // The code should never reach this place (this means |
|
800
|
|
|
// that the element somehow got namespace that is not |
|
801
|
|
|
// HTML, SVG or MathML). Return false just in case. |
|
802
|
|
|
|
|
803
|
|
|
|
|
804
|
|
|
return false; |
|
805
|
|
|
}; |
|
806
|
|
|
/** |
|
807
|
|
|
* _forceRemove |
|
808
|
|
|
* |
|
809
|
|
|
* @param {Node} node a DOM node |
|
810
|
|
|
*/ |
|
811
|
|
|
|
|
812
|
|
|
|
|
813
|
|
|
var _forceRemove = function _forceRemove(node) { |
|
814
|
|
|
arrayPush(DOMPurify.removed, { |
|
815
|
|
|
element: node |
|
816
|
|
|
}); |
|
817
|
|
|
|
|
818
|
|
|
try { |
|
819
|
|
|
// eslint-disable-next-line unicorn/prefer-dom-node-remove |
|
820
|
|
|
node.parentNode.removeChild(node); |
|
821
|
|
|
} catch (_) { |
|
822
|
|
|
try { |
|
823
|
|
|
node.outerHTML = emptyHTML; |
|
824
|
|
|
} catch (_) { |
|
825
|
|
|
node.remove(); |
|
826
|
|
|
} |
|
827
|
|
|
} |
|
828
|
|
|
}; |
|
829
|
|
|
/** |
|
830
|
|
|
* _removeAttribute |
|
831
|
|
|
* |
|
832
|
|
|
* @param {String} name an Attribute name |
|
833
|
|
|
* @param {Node} node a DOM node |
|
834
|
|
|
*/ |
|
835
|
|
|
|
|
836
|
|
|
|
|
837
|
|
|
var _removeAttribute = function _removeAttribute(name, node) { |
|
838
|
|
|
try { |
|
839
|
|
|
arrayPush(DOMPurify.removed, { |
|
840
|
|
|
attribute: node.getAttributeNode(name), |
|
841
|
|
|
from: node |
|
842
|
|
|
}); |
|
843
|
|
|
} catch (_) { |
|
844
|
|
|
arrayPush(DOMPurify.removed, { |
|
845
|
|
|
attribute: null, |
|
846
|
|
|
from: node |
|
847
|
|
|
}); |
|
848
|
|
|
} |
|
849
|
|
|
|
|
850
|
|
|
node.removeAttribute(name); // We void attribute values for unremovable "is"" attributes |
|
851
|
|
|
|
|
852
|
|
|
if (name === 'is' && !ALLOWED_ATTR[name]) { |
|
853
|
|
|
if (RETURN_DOM || RETURN_DOM_FRAGMENT) { |
|
854
|
|
|
try { |
|
855
|
|
|
_forceRemove(node); |
|
856
|
|
|
} catch (_) {} |
|
|
|
|
|
|
857
|
|
|
} else { |
|
858
|
|
|
try { |
|
859
|
|
|
node.setAttribute(name, ''); |
|
860
|
|
|
} catch (_) {} |
|
|
|
|
|
|
861
|
|
|
} |
|
862
|
|
|
} |
|
863
|
|
|
}; |
|
864
|
|
|
/** |
|
865
|
|
|
* _initDocument |
|
866
|
|
|
* |
|
867
|
|
|
* @param {String} dirty a string of dirty markup |
|
868
|
|
|
* @return {Document} a DOM, filled with the dirty markup |
|
869
|
|
|
*/ |
|
870
|
|
|
|
|
871
|
|
|
|
|
872
|
|
|
var _initDocument = function _initDocument(dirty) { |
|
873
|
|
|
/* Create a HTML document */ |
|
874
|
|
|
var doc; |
|
875
|
|
|
var leadingWhitespace; |
|
876
|
|
|
|
|
877
|
|
|
if (FORCE_BODY) { |
|
878
|
|
|
dirty = '<remove></remove>' + dirty; |
|
879
|
|
|
} else { |
|
880
|
|
|
/* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */ |
|
881
|
|
|
var matches = stringMatch(dirty, /^[\r\n\t ]+/); |
|
882
|
|
|
leadingWhitespace = matches && matches[0]; |
|
883
|
|
|
} |
|
884
|
|
|
|
|
885
|
|
|
if (PARSER_MEDIA_TYPE === 'application/xhtml+xml') { |
|
886
|
|
|
// Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict) |
|
887
|
|
|
dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>'; |
|
888
|
|
|
} |
|
889
|
|
|
|
|
890
|
|
|
var dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty; |
|
891
|
|
|
/* |
|
892
|
|
|
* Use the DOMParser API by default, fallback later if needs be |
|
893
|
|
|
* DOMParser not work for svg when has multiple root element. |
|
894
|
|
|
*/ |
|
895
|
|
|
|
|
896
|
|
|
if (NAMESPACE === HTML_NAMESPACE) { |
|
897
|
|
|
try { |
|
898
|
|
|
doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE); |
|
899
|
|
|
} catch (_) {} |
|
|
|
|
|
|
900
|
|
|
} |
|
901
|
|
|
/* Use createHTMLDocument in case DOMParser is not available */ |
|
902
|
|
|
|
|
903
|
|
|
|
|
904
|
|
|
if (!doc || !doc.documentElement) { |
|
905
|
|
|
doc = implementation.createDocument(NAMESPACE, 'template', null); |
|
906
|
|
|
|
|
907
|
|
|
try { |
|
908
|
|
|
doc.documentElement.innerHTML = IS_EMPTY_INPUT ? '' : dirtyPayload; |
|
909
|
|
|
} catch (_) {// Syntax error if dirtyPayload is invalid xml |
|
|
|
|
|
|
910
|
|
|
} |
|
911
|
|
|
} |
|
912
|
|
|
|
|
913
|
|
|
var body = doc.body || doc.documentElement; |
|
914
|
|
|
|
|
915
|
|
|
if (dirty && leadingWhitespace) { |
|
916
|
|
|
body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null); |
|
917
|
|
|
} |
|
918
|
|
|
/* Work on whole document or just its body */ |
|
919
|
|
|
|
|
920
|
|
|
|
|
921
|
|
|
if (NAMESPACE === HTML_NAMESPACE) { |
|
922
|
|
|
return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0]; |
|
923
|
|
|
} |
|
924
|
|
|
|
|
925
|
|
|
return WHOLE_DOCUMENT ? doc.documentElement : body; |
|
926
|
|
|
}; |
|
927
|
|
|
/** |
|
928
|
|
|
* _createIterator |
|
929
|
|
|
* |
|
930
|
|
|
* @param {Document} root document/fragment to create iterator for |
|
931
|
|
|
* @return {Iterator} iterator instance |
|
932
|
|
|
*/ |
|
933
|
|
|
|
|
934
|
|
|
|
|
935
|
|
|
var _createIterator = function _createIterator(root) { |
|
936
|
|
|
return createNodeIterator.call(root.ownerDocument || root, root, // eslint-disable-next-line no-bitwise |
|
937
|
|
|
NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, null, false); |
|
938
|
|
|
}; |
|
939
|
|
|
/** |
|
940
|
|
|
* _isClobbered |
|
941
|
|
|
* |
|
942
|
|
|
* @param {Node} elm element to check for clobbering attacks |
|
943
|
|
|
* @return {Boolean} true if clobbered, false if safe |
|
944
|
|
|
*/ |
|
945
|
|
|
|
|
946
|
|
|
|
|
947
|
|
|
var _isClobbered = function _isClobbered(elm) { |
|
948
|
|
|
return elm instanceof HTMLFormElement && (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'); |
|
949
|
|
|
}; |
|
950
|
|
|
/** |
|
951
|
|
|
* _isNode |
|
952
|
|
|
* |
|
953
|
|
|
* @param {Node} obj object to check whether it's a DOM node |
|
954
|
|
|
* @return {Boolean} true is object is a DOM node |
|
955
|
|
|
*/ |
|
956
|
|
|
|
|
957
|
|
|
|
|
958
|
|
|
var _isNode = function _isNode(object) { |
|
959
|
|
|
return _typeof(Node) === 'object' ? object instanceof Node : object && _typeof(object) === 'object' && typeof object.nodeType === 'number' && typeof object.nodeName === 'string'; |
|
960
|
|
|
}; |
|
961
|
|
|
/** |
|
962
|
|
|
* _executeHook |
|
963
|
|
|
* Execute user configurable hooks |
|
964
|
|
|
* |
|
965
|
|
|
* @param {String} entryPoint Name of the hook's entry point |
|
966
|
|
|
* @param {Node} currentNode node to work on with the hook |
|
967
|
|
|
* @param {Object} data additional hook parameters |
|
968
|
|
|
*/ |
|
969
|
|
|
|
|
970
|
|
|
|
|
971
|
|
|
var _executeHook = function _executeHook(entryPoint, currentNode, data) { |
|
972
|
|
|
if (!hooks[entryPoint]) { |
|
973
|
|
|
return; |
|
974
|
|
|
} |
|
975
|
|
|
|
|
976
|
|
|
arrayForEach(hooks[entryPoint], function (hook) { |
|
977
|
|
|
hook.call(DOMPurify, currentNode, data, CONFIG); |
|
978
|
|
|
}); |
|
979
|
|
|
}; |
|
980
|
|
|
/** |
|
981
|
|
|
* _sanitizeElements |
|
982
|
|
|
* |
|
983
|
|
|
* @protect nodeName |
|
984
|
|
|
* @protect textContent |
|
985
|
|
|
* @protect removeChild |
|
986
|
|
|
* |
|
987
|
|
|
* @param {Node} currentNode to check for permission to exist |
|
988
|
|
|
* @return {Boolean} true if node was killed, false if left alive |
|
989
|
|
|
*/ |
|
990
|
|
|
|
|
991
|
|
|
|
|
992
|
|
|
var _sanitizeElements = function _sanitizeElements(currentNode) { |
|
993
|
|
|
var content; |
|
994
|
|
|
/* Execute a hook if present */ |
|
995
|
|
|
|
|
996
|
|
|
_executeHook('beforeSanitizeElements', currentNode, null); |
|
997
|
|
|
/* Check if element is clobbered or can clobber */ |
|
998
|
|
|
|
|
999
|
|
|
|
|
1000
|
|
|
if (_isClobbered(currentNode)) { |
|
1001
|
|
|
_forceRemove(currentNode); |
|
1002
|
|
|
|
|
1003
|
|
|
return true; |
|
1004
|
|
|
} |
|
1005
|
|
|
/* Check if tagname contains Unicode */ |
|
1006
|
|
|
|
|
1007
|
|
|
|
|
1008
|
|
|
if (regExpTest(/[\u0080-\uFFFF]/, currentNode.nodeName)) { |
|
1009
|
|
|
_forceRemove(currentNode); |
|
1010
|
|
|
|
|
1011
|
|
|
return true; |
|
1012
|
|
|
} |
|
1013
|
|
|
/* Now let's check the element's type and name */ |
|
1014
|
|
|
|
|
1015
|
|
|
|
|
1016
|
|
|
var tagName = transformCaseFunc(currentNode.nodeName); |
|
1017
|
|
|
/* Execute a hook if present */ |
|
1018
|
|
|
|
|
1019
|
|
|
_executeHook('uponSanitizeElement', currentNode, { |
|
1020
|
|
|
tagName: tagName, |
|
1021
|
|
|
allowedTags: ALLOWED_TAGS |
|
1022
|
|
|
}); |
|
1023
|
|
|
/* Detect mXSS attempts abusing namespace confusion */ |
|
1024
|
|
|
|
|
1025
|
|
|
|
|
1026
|
|
|
if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && (!_isNode(currentNode.content) || !_isNode(currentNode.content.firstElementChild)) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) { |
|
1027
|
|
|
_forceRemove(currentNode); |
|
1028
|
|
|
|
|
1029
|
|
|
return true; |
|
1030
|
|
|
} |
|
1031
|
|
|
/* Mitigate a problem with templates inside select */ |
|
1032
|
|
|
|
|
1033
|
|
|
|
|
1034
|
|
|
if (tagName === 'select' && regExpTest(/<template/i, currentNode.innerHTML)) { |
|
1035
|
|
|
_forceRemove(currentNode); |
|
1036
|
|
|
|
|
1037
|
|
|
return true; |
|
1038
|
|
|
} |
|
1039
|
|
|
/* Remove element if anything forbids its presence */ |
|
1040
|
|
|
|
|
1041
|
|
|
|
|
1042
|
|
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { |
|
1043
|
|
|
/* Check if we have a custom element to handle */ |
|
1044
|
|
|
if (!FORBID_TAGS[tagName] && _basicCustomElementTest(tagName)) { |
|
1045
|
|
|
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) return false; |
|
1046
|
|
|
if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) return false; |
|
1047
|
|
|
} |
|
1048
|
|
|
/* Keep content except for bad-listed elements */ |
|
1049
|
|
|
|
|
1050
|
|
|
|
|
1051
|
|
|
if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) { |
|
1052
|
|
|
var parentNode = getParentNode(currentNode) || currentNode.parentNode; |
|
1053
|
|
|
var childNodes = getChildNodes(currentNode) || currentNode.childNodes; |
|
1054
|
|
|
|
|
1055
|
|
|
if (childNodes && parentNode) { |
|
1056
|
|
|
var childCount = childNodes.length; |
|
1057
|
|
|
|
|
1058
|
|
|
for (var i = childCount - 1; i >= 0; --i) { |
|
1059
|
|
|
parentNode.insertBefore(cloneNode(childNodes[i], true), getNextSibling(currentNode)); |
|
1060
|
|
|
} |
|
1061
|
|
|
} |
|
1062
|
|
|
} |
|
1063
|
|
|
|
|
1064
|
|
|
_forceRemove(currentNode); |
|
1065
|
|
|
|
|
1066
|
|
|
return true; |
|
1067
|
|
|
} |
|
1068
|
|
|
/* Check whether element has a valid namespace */ |
|
1069
|
|
|
|
|
1070
|
|
|
|
|
1071
|
|
|
if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) { |
|
1072
|
|
|
_forceRemove(currentNode); |
|
1073
|
|
|
|
|
1074
|
|
|
return true; |
|
1075
|
|
|
} |
|
1076
|
|
|
|
|
1077
|
|
|
if ((tagName === 'noscript' || tagName === 'noembed') && regExpTest(/<\/no(script|embed)/i, currentNode.innerHTML)) { |
|
1078
|
|
|
_forceRemove(currentNode); |
|
1079
|
|
|
|
|
1080
|
|
|
return true; |
|
1081
|
|
|
} |
|
1082
|
|
|
/* Sanitize element content to be template-safe */ |
|
1083
|
|
|
|
|
1084
|
|
|
|
|
1085
|
|
|
if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) { |
|
1086
|
|
|
/* Get the element's text content */ |
|
1087
|
|
|
content = currentNode.textContent; |
|
1088
|
|
|
content = stringReplace(content, MUSTACHE_EXPR$1, ' '); |
|
1089
|
|
|
content = stringReplace(content, ERB_EXPR$1, ' '); |
|
1090
|
|
|
|
|
1091
|
|
|
if (currentNode.textContent !== content) { |
|
1092
|
|
|
arrayPush(DOMPurify.removed, { |
|
1093
|
|
|
element: currentNode.cloneNode() |
|
1094
|
|
|
}); |
|
1095
|
|
|
currentNode.textContent = content; |
|
1096
|
|
|
} |
|
1097
|
|
|
} |
|
1098
|
|
|
/* Execute a hook if present */ |
|
1099
|
|
|
|
|
1100
|
|
|
|
|
1101
|
|
|
_executeHook('afterSanitizeElements', currentNode, null); |
|
1102
|
|
|
|
|
1103
|
|
|
return false; |
|
1104
|
|
|
}; |
|
1105
|
|
|
/** |
|
1106
|
|
|
* _isValidAttribute |
|
1107
|
|
|
* |
|
1108
|
|
|
* @param {string} lcTag Lowercase tag name of containing element. |
|
1109
|
|
|
* @param {string} lcName Lowercase attribute name. |
|
1110
|
|
|
* @param {string} value Attribute value. |
|
1111
|
|
|
* @return {Boolean} Returns true if `value` is valid, otherwise false. |
|
1112
|
|
|
*/ |
|
1113
|
|
|
// eslint-disable-next-line complexity |
|
1114
|
|
|
|
|
1115
|
|
|
|
|
1116
|
|
|
var _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) { |
|
1117
|
|
|
/* Make sure attribute cannot clobber */ |
|
1118
|
|
|
if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) { |
|
1119
|
|
|
return false; |
|
1120
|
|
|
} |
|
1121
|
|
|
/* Allow valid data-* attributes: At least one character after "-" |
|
1122
|
|
|
(https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes) |
|
1123
|
|
|
XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804) |
|
1124
|
|
|
We don't need to check the value; it's always URI safe. */ |
|
1125
|
|
|
|
|
1126
|
|
|
|
|
1127
|
|
|
if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR$1, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR$1, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) { |
|
1128
|
|
|
if ( // First condition does a very basic check if a) it's basically a valid custom element tagname AND |
|
1129
|
|
|
// b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck |
|
1130
|
|
|
// and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck |
|
1131
|
|
|
_basicCustomElementTest(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) || // Alternative, second condition checks if it's an `is`-attribute, AND |
|
1132
|
|
|
// the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck |
|
1133
|
|
|
lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else { |
|
1134
|
|
|
return false; |
|
1135
|
|
|
} |
|
1136
|
|
|
/* Check value is safe. First, is attr inert? If so, is safe */ |
|
1137
|
|
|
|
|
1138
|
|
|
} else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA$1, stringReplace(value, ATTR_WHITESPACE$1, ''))) ; else if (!value) ; else { |
|
1139
|
|
|
return false; |
|
1140
|
|
|
} |
|
1141
|
|
|
|
|
1142
|
|
|
return true; |
|
1143
|
|
|
}; |
|
1144
|
|
|
/** |
|
1145
|
|
|
* _basicCustomElementCheck |
|
1146
|
|
|
* checks if at least one dash is included in tagName, and it's not the first char |
|
1147
|
|
|
* for more sophisticated checking see https://github.com/sindresorhus/validate-element-name |
|
1148
|
|
|
* @param {string} tagName name of the tag of the node to sanitize |
|
1149
|
|
|
*/ |
|
1150
|
|
|
|
|
1151
|
|
|
|
|
1152
|
|
|
var _basicCustomElementTest = function _basicCustomElementTest(tagName) { |
|
1153
|
|
|
return tagName.indexOf('-') > 0; |
|
1154
|
|
|
}; |
|
1155
|
|
|
/** |
|
1156
|
|
|
* _sanitizeAttributes |
|
1157
|
|
|
* |
|
1158
|
|
|
* @protect attributes |
|
1159
|
|
|
* @protect nodeName |
|
1160
|
|
|
* @protect removeAttribute |
|
1161
|
|
|
* @protect setAttribute |
|
1162
|
|
|
* |
|
1163
|
|
|
* @param {Node} currentNode to sanitize |
|
1164
|
|
|
*/ |
|
1165
|
|
|
|
|
1166
|
|
|
|
|
1167
|
|
|
var _sanitizeAttributes = function _sanitizeAttributes(currentNode) { |
|
1168
|
|
|
var attr; |
|
1169
|
|
|
var value; |
|
1170
|
|
|
var lcName; |
|
1171
|
|
|
var l; |
|
1172
|
|
|
/* Execute a hook if present */ |
|
1173
|
|
|
|
|
1174
|
|
|
_executeHook('beforeSanitizeAttributes', currentNode, null); |
|
1175
|
|
|
|
|
1176
|
|
|
var attributes = currentNode.attributes; |
|
1177
|
|
|
/* Check if we have attributes; if not we might have a text node */ |
|
1178
|
|
|
|
|
1179
|
|
|
if (!attributes) { |
|
1180
|
|
|
return; |
|
1181
|
|
|
} |
|
1182
|
|
|
|
|
1183
|
|
|
var hookEvent = { |
|
1184
|
|
|
attrName: '', |
|
1185
|
|
|
attrValue: '', |
|
1186
|
|
|
keepAttr: true, |
|
1187
|
|
|
allowedAttributes: ALLOWED_ATTR |
|
1188
|
|
|
}; |
|
1189
|
|
|
l = attributes.length; |
|
1190
|
|
|
/* Go backwards over all attributes; safely remove bad ones */ |
|
1191
|
|
|
|
|
1192
|
|
|
while (l--) { |
|
1193
|
|
|
attr = attributes[l]; |
|
1194
|
|
|
var _attr = attr, |
|
1195
|
|
|
name = _attr.name, |
|
1196
|
|
|
namespaceURI = _attr.namespaceURI; |
|
1197
|
|
|
value = name === 'value' ? attr.value : stringTrim(attr.value); |
|
1198
|
|
|
lcName = transformCaseFunc(name); |
|
1199
|
|
|
/* Execute a hook if present */ |
|
1200
|
|
|
|
|
1201
|
|
|
hookEvent.attrName = lcName; |
|
1202
|
|
|
hookEvent.attrValue = value; |
|
1203
|
|
|
hookEvent.keepAttr = true; |
|
1204
|
|
|
hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set |
|
1205
|
|
|
|
|
1206
|
|
|
_executeHook('uponSanitizeAttribute', currentNode, hookEvent); |
|
1207
|
|
|
|
|
1208
|
|
|
value = hookEvent.attrValue; |
|
1209
|
|
|
/* Did the hooks approve of the attribute? */ |
|
1210
|
|
|
|
|
1211
|
|
|
if (hookEvent.forceKeepAttr) { |
|
1212
|
|
|
continue; |
|
1213
|
|
|
} |
|
1214
|
|
|
/* Remove attribute */ |
|
1215
|
|
|
|
|
1216
|
|
|
|
|
1217
|
|
|
_removeAttribute(name, currentNode); |
|
1218
|
|
|
/* Did the hooks approve of the attribute? */ |
|
1219
|
|
|
|
|
1220
|
|
|
|
|
1221
|
|
|
if (!hookEvent.keepAttr) { |
|
1222
|
|
|
continue; |
|
1223
|
|
|
} |
|
1224
|
|
|
/* Work around a security issue in jQuery 3.0 */ |
|
1225
|
|
|
|
|
1226
|
|
|
|
|
1227
|
|
|
if (regExpTest(/\/>/i, value)) { |
|
1228
|
|
|
_removeAttribute(name, currentNode); |
|
1229
|
|
|
|
|
1230
|
|
|
continue; |
|
1231
|
|
|
} |
|
1232
|
|
|
/* Sanitize attribute content to be template-safe */ |
|
1233
|
|
|
|
|
1234
|
|
|
|
|
1235
|
|
|
if (SAFE_FOR_TEMPLATES) { |
|
1236
|
|
|
value = stringReplace(value, MUSTACHE_EXPR$1, ' '); |
|
1237
|
|
|
value = stringReplace(value, ERB_EXPR$1, ' '); |
|
1238
|
|
|
} |
|
1239
|
|
|
/* Is `value` valid for this attribute? */ |
|
1240
|
|
|
|
|
1241
|
|
|
|
|
1242
|
|
|
var lcTag = transformCaseFunc(currentNode.nodeName); |
|
1243
|
|
|
|
|
1244
|
|
|
if (!_isValidAttribute(lcTag, lcName, value)) { |
|
1245
|
|
|
continue; |
|
1246
|
|
|
} |
|
1247
|
|
|
/* Handle invalid data-* attribute set by try-catching it */ |
|
1248
|
|
|
|
|
1249
|
|
|
|
|
1250
|
|
|
try { |
|
1251
|
|
|
if (namespaceURI) { |
|
1252
|
|
|
currentNode.setAttributeNS(namespaceURI, name, value); |
|
1253
|
|
|
} else { |
|
1254
|
|
|
/* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */ |
|
1255
|
|
|
currentNode.setAttribute(name, value); |
|
1256
|
|
|
} |
|
1257
|
|
|
|
|
1258
|
|
|
arrayPop(DOMPurify.removed); |
|
1259
|
|
|
} catch (_) {} |
|
|
|
|
|
|
1260
|
|
|
} |
|
1261
|
|
|
/* Execute a hook if present */ |
|
1262
|
|
|
|
|
1263
|
|
|
|
|
1264
|
|
|
_executeHook('afterSanitizeAttributes', currentNode, null); |
|
1265
|
|
|
}; |
|
1266
|
|
|
/** |
|
1267
|
|
|
* _sanitizeShadowDOM |
|
1268
|
|
|
* |
|
1269
|
|
|
* @param {DocumentFragment} fragment to iterate over recursively |
|
1270
|
|
|
*/ |
|
1271
|
|
|
|
|
1272
|
|
|
|
|
1273
|
|
|
var _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) { |
|
1274
|
|
|
var shadowNode; |
|
1275
|
|
|
|
|
1276
|
|
|
var shadowIterator = _createIterator(fragment); |
|
1277
|
|
|
/* Execute a hook if present */ |
|
1278
|
|
|
|
|
1279
|
|
|
|
|
1280
|
|
|
_executeHook('beforeSanitizeShadowDOM', fragment, null); |
|
1281
|
|
|
|
|
1282
|
|
|
while (shadowNode = shadowIterator.nextNode()) { |
|
1283
|
|
|
/* Execute a hook if present */ |
|
1284
|
|
|
_executeHook('uponSanitizeShadowNode', shadowNode, null); |
|
1285
|
|
|
/* Sanitize tags and elements */ |
|
1286
|
|
|
|
|
1287
|
|
|
|
|
1288
|
|
|
if (_sanitizeElements(shadowNode)) { |
|
1289
|
|
|
continue; |
|
1290
|
|
|
} |
|
1291
|
|
|
/* Deep shadow DOM detected */ |
|
1292
|
|
|
|
|
1293
|
|
|
|
|
1294
|
|
|
if (shadowNode.content instanceof DocumentFragment) { |
|
1295
|
|
|
_sanitizeShadowDOM(shadowNode.content); |
|
1296
|
|
|
} |
|
1297
|
|
|
/* Check attributes, sanitize if necessary */ |
|
1298
|
|
|
|
|
1299
|
|
|
|
|
1300
|
|
|
_sanitizeAttributes(shadowNode); |
|
1301
|
|
|
} |
|
1302
|
|
|
/* Execute a hook if present */ |
|
1303
|
|
|
|
|
1304
|
|
|
|
|
1305
|
|
|
_executeHook('afterSanitizeShadowDOM', fragment, null); |
|
1306
|
|
|
}; |
|
1307
|
|
|
/** |
|
1308
|
|
|
* Sanitize |
|
1309
|
|
|
* Public method providing core sanitation functionality |
|
1310
|
|
|
* |
|
1311
|
|
|
* @param {String|Node} dirty string or DOM node |
|
1312
|
|
|
* @param {Object} configuration object |
|
1313
|
|
|
*/ |
|
1314
|
|
|
// eslint-disable-next-line complexity |
|
1315
|
|
|
|
|
1316
|
|
|
|
|
1317
|
|
|
DOMPurify.sanitize = function (dirty, cfg) { |
|
1318
|
|
|
var body; |
|
1319
|
|
|
var importedNode; |
|
1320
|
|
|
var currentNode; |
|
1321
|
|
|
var oldNode; |
|
1322
|
|
|
var returnNode; |
|
1323
|
|
|
/* Make sure we have a string to sanitize. |
|
1324
|
|
|
DO NOT return early, as this will return the wrong type if |
|
1325
|
|
|
the user has requested a DOM object rather than a string */ |
|
1326
|
|
|
|
|
1327
|
|
|
IS_EMPTY_INPUT = !dirty; |
|
1328
|
|
|
|
|
1329
|
|
|
if (IS_EMPTY_INPUT) { |
|
1330
|
|
|
dirty = '<!-->'; |
|
1331
|
|
|
} |
|
1332
|
|
|
/* Stringify, in case dirty is an object */ |
|
1333
|
|
|
|
|
1334
|
|
|
|
|
1335
|
|
|
if (typeof dirty !== 'string' && !_isNode(dirty)) { |
|
1336
|
|
|
// eslint-disable-next-line no-negated-condition |
|
1337
|
|
|
if (typeof dirty.toString !== 'function') { |
|
1338
|
|
|
throw typeErrorCreate('toString is not a function'); |
|
1339
|
|
|
} else { |
|
1340
|
|
|
dirty = dirty.toString(); |
|
1341
|
|
|
|
|
1342
|
|
|
if (typeof dirty !== 'string') { |
|
1343
|
|
|
throw typeErrorCreate('dirty is not a string, aborting'); |
|
1344
|
|
|
} |
|
1345
|
|
|
} |
|
1346
|
|
|
} |
|
1347
|
|
|
/* Check we can run. Otherwise fall back or ignore */ |
|
1348
|
|
|
|
|
1349
|
|
|
|
|
1350
|
|
|
if (!DOMPurify.isSupported) { |
|
1351
|
|
|
if (_typeof(window.toStaticHTML) === 'object' || typeof window.toStaticHTML === 'function') { |
|
1352
|
|
|
if (typeof dirty === 'string') { |
|
1353
|
|
|
return window.toStaticHTML(dirty); |
|
1354
|
|
|
} |
|
1355
|
|
|
|
|
1356
|
|
|
if (_isNode(dirty)) { |
|
1357
|
|
|
return window.toStaticHTML(dirty.outerHTML); |
|
1358
|
|
|
} |
|
1359
|
|
|
} |
|
1360
|
|
|
|
|
1361
|
|
|
return dirty; |
|
1362
|
|
|
} |
|
1363
|
|
|
/* Assign config vars */ |
|
1364
|
|
|
|
|
1365
|
|
|
|
|
1366
|
|
|
if (!SET_CONFIG) { |
|
1367
|
|
|
_parseConfig(cfg); |
|
1368
|
|
|
} |
|
1369
|
|
|
/* Clean up removed elements */ |
|
1370
|
|
|
|
|
1371
|
|
|
|
|
1372
|
|
|
DOMPurify.removed = []; |
|
1373
|
|
|
/* Check if dirty is correctly typed for IN_PLACE */ |
|
1374
|
|
|
|
|
1375
|
|
|
if (typeof dirty === 'string') { |
|
1376
|
|
|
IN_PLACE = false; |
|
1377
|
|
|
} |
|
1378
|
|
|
|
|
1379
|
|
|
if (IN_PLACE) { |
|
1380
|
|
|
/* Do some early pre-sanitization to avoid unsafe root nodes */ |
|
1381
|
|
|
if (dirty.nodeName) { |
|
1382
|
|
|
var tagName = transformCaseFunc(dirty.nodeName); |
|
1383
|
|
|
|
|
1384
|
|
|
if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) { |
|
1385
|
|
|
throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place'); |
|
1386
|
|
|
} |
|
1387
|
|
|
} |
|
1388
|
|
|
} else if (dirty instanceof Node) { |
|
1389
|
|
|
/* If dirty is a DOM element, append to an empty document to avoid |
|
1390
|
|
|
elements being stripped by the parser */ |
|
1391
|
|
|
body = _initDocument('<!---->'); |
|
1392
|
|
|
importedNode = body.ownerDocument.importNode(dirty, true); |
|
1393
|
|
|
|
|
1394
|
|
|
if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') { |
|
1395
|
|
|
/* Node is already a body, use as is */ |
|
1396
|
|
|
body = importedNode; |
|
1397
|
|
|
} else if (importedNode.nodeName === 'HTML') { |
|
1398
|
|
|
body = importedNode; |
|
1399
|
|
|
} else { |
|
1400
|
|
|
// eslint-disable-next-line unicorn/prefer-dom-node-append |
|
1401
|
|
|
body.appendChild(importedNode); |
|
1402
|
|
|
} |
|
1403
|
|
|
} else { |
|
1404
|
|
|
/* Exit directly if we have nothing to do */ |
|
1405
|
|
|
if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT && // eslint-disable-next-line unicorn/prefer-includes |
|
1406
|
|
|
dirty.indexOf('<') === -1) { |
|
1407
|
|
|
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty; |
|
1408
|
|
|
} |
|
1409
|
|
|
/* Initialize the document to work on */ |
|
1410
|
|
|
|
|
1411
|
|
|
|
|
1412
|
|
|
body = _initDocument(dirty); |
|
1413
|
|
|
/* Check we have a DOM node from the data */ |
|
1414
|
|
|
|
|
1415
|
|
|
if (!body) { |
|
1416
|
|
|
return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : ''; |
|
1417
|
|
|
} |
|
1418
|
|
|
} |
|
1419
|
|
|
/* Remove first element node (ours) if FORCE_BODY is set */ |
|
1420
|
|
|
|
|
1421
|
|
|
|
|
1422
|
|
|
if (body && FORCE_BODY) { |
|
1423
|
|
|
_forceRemove(body.firstChild); |
|
1424
|
|
|
} |
|
1425
|
|
|
/* Get node iterator */ |
|
1426
|
|
|
|
|
1427
|
|
|
|
|
1428
|
|
|
var nodeIterator = _createIterator(IN_PLACE ? dirty : body); |
|
|
|
|
|
|
1429
|
|
|
/* Now start iterating over the created document */ |
|
1430
|
|
|
|
|
1431
|
|
|
|
|
1432
|
|
|
while (currentNode = nodeIterator.nextNode()) { |
|
1433
|
|
|
/* Fix IE's strange behavior with manipulated textNodes #89 */ |
|
1434
|
|
|
if (currentNode.nodeType === 3 && currentNode === oldNode) { |
|
|
|
|
|
|
1435
|
|
|
continue; |
|
1436
|
|
|
} |
|
1437
|
|
|
/* Sanitize tags and elements */ |
|
1438
|
|
|
|
|
1439
|
|
|
|
|
1440
|
|
|
if (_sanitizeElements(currentNode)) { |
|
1441
|
|
|
continue; |
|
1442
|
|
|
} |
|
1443
|
|
|
/* Shadow DOM detected, sanitize it */ |
|
1444
|
|
|
|
|
1445
|
|
|
|
|
1446
|
|
|
if (currentNode.content instanceof DocumentFragment) { |
|
1447
|
|
|
_sanitizeShadowDOM(currentNode.content); |
|
1448
|
|
|
} |
|
1449
|
|
|
/* Check attributes, sanitize if necessary */ |
|
1450
|
|
|
|
|
1451
|
|
|
|
|
1452
|
|
|
_sanitizeAttributes(currentNode); |
|
1453
|
|
|
|
|
1454
|
|
|
oldNode = currentNode; |
|
1455
|
|
|
} |
|
1456
|
|
|
|
|
1457
|
|
|
oldNode = null; |
|
|
|
|
|
|
1458
|
|
|
/* If we sanitized `dirty` in-place, return it. */ |
|
1459
|
|
|
|
|
1460
|
|
|
if (IN_PLACE) { |
|
1461
|
|
|
return dirty; |
|
1462
|
|
|
} |
|
1463
|
|
|
/* Return sanitized string or DOM */ |
|
1464
|
|
|
|
|
1465
|
|
|
|
|
1466
|
|
|
if (RETURN_DOM) { |
|
1467
|
|
|
if (RETURN_DOM_FRAGMENT) { |
|
1468
|
|
|
returnNode = createDocumentFragment.call(body.ownerDocument); |
|
1469
|
|
|
|
|
1470
|
|
|
while (body.firstChild) { |
|
1471
|
|
|
// eslint-disable-next-line unicorn/prefer-dom-node-append |
|
1472
|
|
|
returnNode.appendChild(body.firstChild); |
|
1473
|
|
|
} |
|
1474
|
|
|
} else { |
|
1475
|
|
|
returnNode = body; |
|
1476
|
|
|
} |
|
1477
|
|
|
|
|
1478
|
|
|
if (ALLOWED_ATTR.shadowroot) { |
|
1479
|
|
|
/* |
|
1480
|
|
|
AdoptNode() is not used because internal state is not reset |
|
1481
|
|
|
(e.g. the past names map of a HTMLFormElement), this is safe |
|
1482
|
|
|
in theory but we would rather not risk another attack vector. |
|
1483
|
|
|
The state that is cloned by importNode() is explicitly defined |
|
1484
|
|
|
by the specs. |
|
1485
|
|
|
*/ |
|
1486
|
|
|
returnNode = importNode.call(originalDocument, returnNode, true); |
|
1487
|
|
|
} |
|
1488
|
|
|
|
|
1489
|
|
|
return returnNode; |
|
1490
|
|
|
} |
|
1491
|
|
|
|
|
1492
|
|
|
var serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML; |
|
1493
|
|
|
/* Serialize doctype if allowed */ |
|
1494
|
|
|
|
|
1495
|
|
|
if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) { |
|
1496
|
|
|
serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML; |
|
1497
|
|
|
} |
|
1498
|
|
|
/* Sanitize final string template-safe */ |
|
1499
|
|
|
|
|
1500
|
|
|
|
|
1501
|
|
|
if (SAFE_FOR_TEMPLATES) { |
|
1502
|
|
|
serializedHTML = stringReplace(serializedHTML, MUSTACHE_EXPR$1, ' '); |
|
1503
|
|
|
serializedHTML = stringReplace(serializedHTML, ERB_EXPR$1, ' '); |
|
1504
|
|
|
} |
|
1505
|
|
|
|
|
1506
|
|
|
return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML; |
|
1507
|
|
|
}; |
|
1508
|
|
|
/** |
|
1509
|
|
|
* Public method to set the configuration once |
|
1510
|
|
|
* setConfig |
|
1511
|
|
|
* |
|
1512
|
|
|
* @param {Object} cfg configuration object |
|
1513
|
|
|
*/ |
|
1514
|
|
|
|
|
1515
|
|
|
|
|
1516
|
|
|
DOMPurify.setConfig = function (cfg) { |
|
1517
|
|
|
_parseConfig(cfg); |
|
1518
|
|
|
|
|
1519
|
|
|
SET_CONFIG = true; |
|
1520
|
|
|
}; |
|
1521
|
|
|
/** |
|
1522
|
|
|
* Public method to remove the configuration |
|
1523
|
|
|
* clearConfig |
|
1524
|
|
|
* |
|
1525
|
|
|
*/ |
|
1526
|
|
|
|
|
1527
|
|
|
|
|
1528
|
|
|
DOMPurify.clearConfig = function () { |
|
1529
|
|
|
CONFIG = null; |
|
1530
|
|
|
SET_CONFIG = false; |
|
1531
|
|
|
}; |
|
1532
|
|
|
/** |
|
1533
|
|
|
* Public method to check if an attribute value is valid. |
|
1534
|
|
|
* Uses last set config, if any. Otherwise, uses config defaults. |
|
1535
|
|
|
* isValidAttribute |
|
1536
|
|
|
* |
|
1537
|
|
|
* @param {string} tag Tag name of containing element. |
|
1538
|
|
|
* @param {string} attr Attribute name. |
|
1539
|
|
|
* @param {string} value Attribute value. |
|
1540
|
|
|
* @return {Boolean} Returns true if `value` is valid. Otherwise, returns false. |
|
1541
|
|
|
*/ |
|
1542
|
|
|
|
|
1543
|
|
|
|
|
1544
|
|
|
DOMPurify.isValidAttribute = function (tag, attr, value) { |
|
1545
|
|
|
/* Initialize shared config vars if necessary. */ |
|
1546
|
|
|
if (!CONFIG) { |
|
1547
|
|
|
_parseConfig({}); |
|
1548
|
|
|
} |
|
1549
|
|
|
|
|
1550
|
|
|
var lcTag = transformCaseFunc(tag); |
|
1551
|
|
|
var lcName = transformCaseFunc(attr); |
|
1552
|
|
|
return _isValidAttribute(lcTag, lcName, value); |
|
1553
|
|
|
}; |
|
1554
|
|
|
/** |
|
1555
|
|
|
* AddHook |
|
1556
|
|
|
* Public method to add DOMPurify hooks |
|
1557
|
|
|
* |
|
1558
|
|
|
* @param {String} entryPoint entry point for the hook to add |
|
1559
|
|
|
* @param {Function} hookFunction function to execute |
|
1560
|
|
|
*/ |
|
1561
|
|
|
|
|
1562
|
|
|
|
|
1563
|
|
|
DOMPurify.addHook = function (entryPoint, hookFunction) { |
|
1564
|
|
|
if (typeof hookFunction !== 'function') { |
|
1565
|
|
|
return; |
|
1566
|
|
|
} |
|
1567
|
|
|
|
|
1568
|
|
|
hooks[entryPoint] = hooks[entryPoint] || []; |
|
1569
|
|
|
arrayPush(hooks[entryPoint], hookFunction); |
|
1570
|
|
|
}; |
|
1571
|
|
|
/** |
|
1572
|
|
|
* RemoveHook |
|
1573
|
|
|
* Public method to remove a DOMPurify hook at a given entryPoint |
|
1574
|
|
|
* (pops it from the stack of hooks if more are present) |
|
1575
|
|
|
* |
|
1576
|
|
|
* @param {String} entryPoint entry point for the hook to remove |
|
1577
|
|
|
* @return {Function} removed(popped) hook |
|
1578
|
|
|
*/ |
|
1579
|
|
|
|
|
1580
|
|
|
|
|
1581
|
|
|
DOMPurify.removeHook = function (entryPoint) { |
|
1582
|
|
|
if (hooks[entryPoint]) { |
|
|
|
|
|
|
1583
|
|
|
return arrayPop(hooks[entryPoint]); |
|
1584
|
|
|
} |
|
1585
|
|
|
}; |
|
1586
|
|
|
/** |
|
1587
|
|
|
* RemoveHooks |
|
1588
|
|
|
* Public method to remove all DOMPurify hooks at a given entryPoint |
|
1589
|
|
|
* |
|
1590
|
|
|
* @param {String} entryPoint entry point for the hooks to remove |
|
1591
|
|
|
*/ |
|
1592
|
|
|
|
|
1593
|
|
|
|
|
1594
|
|
|
DOMPurify.removeHooks = function (entryPoint) { |
|
1595
|
|
|
if (hooks[entryPoint]) { |
|
1596
|
|
|
hooks[entryPoint] = []; |
|
1597
|
|
|
} |
|
1598
|
|
|
}; |
|
1599
|
|
|
/** |
|
1600
|
|
|
* RemoveAllHooks |
|
1601
|
|
|
* Public method to remove all DOMPurify hooks |
|
1602
|
|
|
* |
|
1603
|
|
|
*/ |
|
1604
|
|
|
|
|
1605
|
|
|
|
|
1606
|
|
|
DOMPurify.removeAllHooks = function () { |
|
1607
|
|
|
hooks = {}; |
|
1608
|
|
|
}; |
|
1609
|
|
|
|
|
1610
|
|
|
return DOMPurify; |
|
1611
|
|
|
} |
|
1612
|
|
|
|
|
1613
|
|
|
var purify = createDOMPurify(); |
|
1614
|
|
|
|
|
1615
|
|
|
return purify; |
|
1616
|
|
|
|
|
1617
|
|
|
})); |
|
1618
|
|
|
//# sourceMappingURL=purify.js.map |
|
1619
|
|
|
|