purify.js 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009
  1. (function (global, factory) {
  2. typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  3. typeof define === 'function' && define.amd ? define(factory) :
  4. (global.DOMPurify = factory());
  5. }(this, (function () { 'use strict';
  6. var html = ['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr'];
  7. // SVG
  8. var svg = ['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'audio', 'canvas', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'video', 'view', 'vkern'];
  9. var svgFilters = ['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence'];
  10. var mathMl = ['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmuliscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mpspace', 'msqrt', 'mystyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover'];
  11. var text = ['#text'];
  12. var html$1 = ['accept', 'action', 'align', 'alt', 'autocomplete', 'background', 'bgcolor', 'border', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'coords', 'crossorigin', 'datetime', 'default', 'dir', 'disabled', 'download', 'enctype', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'integrity', 'ismap', 'label', 'lang', 'list', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'multiple', 'name', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'type', 'usemap', 'valign', 'value', 'width', 'xmlns'];
  13. var svg$1 = ['accent-height', 'accumulate', 'additivive', 'alignment-baseline', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'specularconstant', 'specularexponent', 'spreadmethod', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'tabindex', 'targetx', 'targety', 'transform', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan'];
  14. var mathMl$1 = ['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns'];
  15. var xml = ['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink'];
  16. /* Add properties to a lookup table */
  17. function addToSet(set, array) {
  18. var l = array.length;
  19. while (l--) {
  20. if (typeof array[l] === 'string') {
  21. array[l] = array[l].toLowerCase();
  22. }
  23. set[array[l]] = true;
  24. }
  25. return set;
  26. }
  27. /* Shallow clone an object */
  28. function clone(object) {
  29. var newObject = {};
  30. var property = void 0;
  31. for (property in object) {
  32. if (Object.prototype.hasOwnProperty.call(object, property)) {
  33. newObject[property] = object[property];
  34. }
  35. }
  36. return newObject;
  37. }
  38. var MUSTACHE_EXPR = /\{\{[\s\S]*|[\s\S]*\}\}/gm; // Specify template detection regex for SAFE_FOR_TEMPLATES mode
  39. var ERB_EXPR = /<%[\s\S]*|[\s\S]*%>/gm;
  40. var DATA_ATTR = /^data-[\-\w.\u00B7-\uFFFF]/; // eslint-disable-line no-useless-escape
  41. var ARIA_ATTR = /^aria-[\-\w]+$/; // eslint-disable-line no-useless-escape
  42. var IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i; // eslint-disable-line no-useless-escape
  43. var IS_SCRIPT_OR_DATA = /^(?:\w+script|data):/i;
  44. var ATTR_WHITESPACE = /[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205f\u3000]/g; // eslint-disable-line no-control-regex
  45. 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; };
  46. 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); } }
  47. var getGlobal = function getGlobal() {
  48. return typeof window === 'undefined' ? null : window;
  49. };
  50. function createDOMPurify() {
  51. var window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
  52. var DOMPurify = function DOMPurify(root) {
  53. return createDOMPurify(root);
  54. };
  55. /**
  56. * Version label, exposed for easier checks
  57. * if DOMPurify is up to date or not
  58. */
  59. DOMPurify.version = '1.0.7';
  60. /**
  61. * Array of elements that DOMPurify removed during sanitation.
  62. * Empty if nothing was removed.
  63. */
  64. DOMPurify.removed = [];
  65. if (!window || !window.document || window.document.nodeType !== 9) {
  66. // Not running in a browser, provide a factory function
  67. // so that you can pass your own Window
  68. DOMPurify.isSupported = false;
  69. return DOMPurify;
  70. }
  71. var originalDocument = window.document;
  72. var useDOMParser = false; // See comment below
  73. var removeTitle = false; // See comment below
  74. var document = window.document;
  75. var DocumentFragment = window.DocumentFragment,
  76. HTMLTemplateElement = window.HTMLTemplateElement,
  77. Node = window.Node,
  78. NodeFilter = window.NodeFilter,
  79. _window$NamedNodeMap = window.NamedNodeMap,
  80. NamedNodeMap = _window$NamedNodeMap === undefined ? window.NamedNodeMap || window.MozNamedAttrMap : _window$NamedNodeMap,
  81. Text = window.Text,
  82. Comment = window.Comment,
  83. DOMParser = window.DOMParser;
  84. // As per issue #47, the web-components registry is inherited by a
  85. // new document created via createHTMLDocument. As per the spec
  86. // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
  87. // a new empty registry is used when creating a template contents owner
  88. // document, so we use that as our parent document to ensure nothing
  89. // is inherited.
  90. if (typeof HTMLTemplateElement === 'function') {
  91. var template = document.createElement('template');
  92. if (template.content && template.content.ownerDocument) {
  93. document = template.content.ownerDocument;
  94. }
  95. }
  96. var _document = document,
  97. implementation = _document.implementation,
  98. createNodeIterator = _document.createNodeIterator,
  99. getElementsByTagName = _document.getElementsByTagName,
  100. createDocumentFragment = _document.createDocumentFragment;
  101. var importNode = originalDocument.importNode;
  102. var hooks = {};
  103. /**
  104. * Expose whether this browser supports running the full DOMPurify.
  105. */
  106. DOMPurify.isSupported = implementation && typeof implementation.createHTMLDocument !== 'undefined' && document.documentMode !== 9;
  107. var MUSTACHE_EXPR$$1 = MUSTACHE_EXPR,
  108. ERB_EXPR$$1 = ERB_EXPR,
  109. DATA_ATTR$$1 = DATA_ATTR,
  110. ARIA_ATTR$$1 = ARIA_ATTR,
  111. IS_SCRIPT_OR_DATA$$1 = IS_SCRIPT_OR_DATA,
  112. ATTR_WHITESPACE$$1 = ATTR_WHITESPACE;
  113. var IS_ALLOWED_URI$$1 = IS_ALLOWED_URI;
  114. /**
  115. * We consider the elements and attributes below to be safe. Ideally
  116. * don't add any new ones but feel free to remove unwanted ones.
  117. */
  118. /* allowed element names */
  119. var ALLOWED_TAGS = null;
  120. var DEFAULT_ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray(html), _toConsumableArray(svg), _toConsumableArray(svgFilters), _toConsumableArray(mathMl), _toConsumableArray(text)));
  121. /* Allowed attribute names */
  122. var ALLOWED_ATTR = null;
  123. var DEFAULT_ALLOWED_ATTR = addToSet({}, [].concat(_toConsumableArray(html$1), _toConsumableArray(svg$1), _toConsumableArray(mathMl$1), _toConsumableArray(xml)));
  124. /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
  125. var FORBID_TAGS = null;
  126. /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
  127. var FORBID_ATTR = null;
  128. /* Decide if ARIA attributes are okay */
  129. var ALLOW_ARIA_ATTR = true;
  130. /* Decide if custom data attributes are okay */
  131. var ALLOW_DATA_ATTR = true;
  132. /* Decide if unknown protocols are okay */
  133. var ALLOW_UNKNOWN_PROTOCOLS = false;
  134. /* Output should be safe for jQuery's $() factory? */
  135. var SAFE_FOR_JQUERY = false;
  136. /* Output should be safe for common template engines.
  137. * This means, DOMPurify removes data attributes, mustaches and ERB
  138. */
  139. var SAFE_FOR_TEMPLATES = false;
  140. /* Decide if document with <html>... should be returned */
  141. var WHOLE_DOCUMENT = false;
  142. /* Track whether config is already set on this instance of DOMPurify. */
  143. var SET_CONFIG = false;
  144. /* Decide if all elements (e.g. style, script) must be children of
  145. * document.body. By default, browsers might move them to document.head */
  146. var FORCE_BODY = false;
  147. /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html string.
  148. * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
  149. */
  150. var RETURN_DOM = false;
  151. /* Decide if a DOM `DocumentFragment` should be returned, instead of a html string */
  152. var RETURN_DOM_FRAGMENT = false;
  153. /* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM
  154. * `Node` is imported into the current `Document`. If this flag is not enabled the
  155. * `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by
  156. * DOMPurify. */
  157. var RETURN_DOM_IMPORT = false;
  158. /* Output should be free from DOM clobbering attacks? */
  159. var SANITIZE_DOM = true;
  160. /* Keep element content when removing element? */
  161. var KEEP_CONTENT = true;
  162. /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
  163. * of importing it into a new Document and returning a sanitized copy */
  164. var IN_PLACE = false;
  165. /* Allow usage of profiles like html, svg and mathMl */
  166. var USE_PROFILES = {};
  167. /* Tags to ignore content of when KEEP_CONTENT is true */
  168. var FORBID_CONTENTS = addToSet({}, ['audio', 'head', 'math', 'script', 'style', 'template', 'svg', 'video']);
  169. /* Tags that are safe for data: URIs */
  170. var DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image']);
  171. /* Attributes safe for values like "javascript:" */
  172. var URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'summary', 'title', 'value', 'style', 'xmlns']);
  173. /* Keep a reference to config to pass to hooks */
  174. var CONFIG = null;
  175. /* Ideally, do not touch anything below this line */
  176. /* ______________________________________________ */
  177. var formElement = document.createElement('form');
  178. /**
  179. * _parseConfig
  180. *
  181. * @param {Object} cfg optional config literal
  182. */
  183. // eslint-disable-next-line complexity
  184. var _parseConfig = function _parseConfig(cfg) {
  185. /* Shield configuration object from tampering */
  186. if ((typeof cfg === 'undefined' ? 'undefined' : _typeof(cfg)) !== 'object') {
  187. cfg = {};
  188. }
  189. /* Set configuration parameters */
  190. ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ? addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS;
  191. ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ? addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR;
  192. FORBID_TAGS = 'FORBID_TAGS' in cfg ? addToSet({}, cfg.FORBID_TAGS) : {};
  193. FORBID_ATTR = 'FORBID_ATTR' in cfg ? addToSet({}, cfg.FORBID_ATTR) : {};
  194. USE_PROFILES = 'USE_PROFILES' in cfg ? cfg.USE_PROFILES : false;
  195. ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
  196. ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
  197. ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
  198. SAFE_FOR_JQUERY = cfg.SAFE_FOR_JQUERY || false; // Default false
  199. SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
  200. WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
  201. RETURN_DOM = cfg.RETURN_DOM || false; // Default false
  202. RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
  203. RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT || false; // Default false
  204. FORCE_BODY = cfg.FORCE_BODY || false; // Default false
  205. SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
  206. KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
  207. IN_PLACE = cfg.IN_PLACE || false; // Default false
  208. IS_ALLOWED_URI$$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI$$1;
  209. if (SAFE_FOR_TEMPLATES) {
  210. ALLOW_DATA_ATTR = false;
  211. }
  212. if (RETURN_DOM_FRAGMENT) {
  213. RETURN_DOM = true;
  214. }
  215. /* Parse profile info */
  216. if (USE_PROFILES) {
  217. ALLOWED_TAGS = addToSet({}, [].concat(_toConsumableArray(text)));
  218. ALLOWED_ATTR = [];
  219. if (USE_PROFILES.html === true) {
  220. addToSet(ALLOWED_TAGS, html);
  221. addToSet(ALLOWED_ATTR, html$1);
  222. }
  223. if (USE_PROFILES.svg === true) {
  224. addToSet(ALLOWED_TAGS, svg);
  225. addToSet(ALLOWED_ATTR, svg$1);
  226. addToSet(ALLOWED_ATTR, xml);
  227. }
  228. if (USE_PROFILES.svgFilters === true) {
  229. addToSet(ALLOWED_TAGS, svgFilters);
  230. addToSet(ALLOWED_ATTR, svg$1);
  231. addToSet(ALLOWED_ATTR, xml);
  232. }
  233. if (USE_PROFILES.mathMl === true) {
  234. addToSet(ALLOWED_TAGS, mathMl);
  235. addToSet(ALLOWED_ATTR, mathMl$1);
  236. addToSet(ALLOWED_ATTR, xml);
  237. }
  238. }
  239. /* Merge configuration parameters */
  240. if (cfg.ADD_TAGS) {
  241. if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
  242. ALLOWED_TAGS = clone(ALLOWED_TAGS);
  243. }
  244. addToSet(ALLOWED_TAGS, cfg.ADD_TAGS);
  245. }
  246. if (cfg.ADD_ATTR) {
  247. if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
  248. ALLOWED_ATTR = clone(ALLOWED_ATTR);
  249. }
  250. addToSet(ALLOWED_ATTR, cfg.ADD_ATTR);
  251. }
  252. if (cfg.ADD_URI_SAFE_ATTR) {
  253. addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR);
  254. }
  255. /* Add #text in case KEEP_CONTENT is set to true */
  256. if (KEEP_CONTENT) {
  257. ALLOWED_TAGS['#text'] = true;
  258. }
  259. /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
  260. if (WHOLE_DOCUMENT) {
  261. addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
  262. }
  263. /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286 */
  264. if (ALLOWED_TAGS.table) {
  265. addToSet(ALLOWED_TAGS, ['tbody']);
  266. }
  267. // Prevent further manipulation of configuration.
  268. // Not available in IE8, Safari 5, etc.
  269. if (Object && 'freeze' in Object) {
  270. Object.freeze(cfg);
  271. }
  272. CONFIG = cfg;
  273. };
  274. /**
  275. * _forceRemove
  276. *
  277. * @param {Node} node a DOM node
  278. */
  279. var _forceRemove = function _forceRemove(node) {
  280. DOMPurify.removed.push({ element: node });
  281. try {
  282. node.parentNode.removeChild(node);
  283. } catch (err) {
  284. node.outerHTML = '';
  285. }
  286. };
  287. /**
  288. * _removeAttribute
  289. *
  290. * @param {String} name an Attribute name
  291. * @param {Node} node a DOM node
  292. */
  293. var _removeAttribute = function _removeAttribute(name, node) {
  294. try {
  295. DOMPurify.removed.push({
  296. attribute: node.getAttributeNode(name),
  297. from: node
  298. });
  299. } catch (err) {
  300. DOMPurify.removed.push({
  301. attribute: null,
  302. from: node
  303. });
  304. }
  305. node.removeAttribute(name);
  306. };
  307. /**
  308. * _initDocument
  309. *
  310. * @param {String} dirty a string of dirty markup
  311. * @return {Document} a DOM, filled with the dirty markup
  312. */
  313. var _initDocument = function _initDocument(dirty) {
  314. /* Create a HTML document */
  315. var doc = void 0;
  316. if (FORCE_BODY) {
  317. dirty = '<remove></remove>' + dirty;
  318. }
  319. /* Use DOMParser to workaround Firefox bug (see comment below) */
  320. if (useDOMParser) {
  321. try {
  322. doc = new DOMParser().parseFromString(dirty, 'text/html');
  323. } catch (err) {}
  324. }
  325. /* Remove title to fix an mXSS bug in older MS Edge */
  326. if (removeTitle) {
  327. addToSet(FORBID_TAGS, ['title']);
  328. }
  329. /* Otherwise use createHTMLDocument, because DOMParser is unsafe in
  330. Safari (see comment below) */
  331. if (!doc || !doc.documentElement) {
  332. doc = implementation.createHTMLDocument('');
  333. var _doc = doc,
  334. body = _doc.body;
  335. body.parentNode.removeChild(body.parentNode.firstElementChild);
  336. body.outerHTML = dirty;
  337. }
  338. /* Work on whole document or just its body */
  339. return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
  340. };
  341. // Firefox uses a different parser for innerHTML rather than
  342. // DOMParser (see https://bugzilla.mozilla.org/show_bug.cgi?id=1205631)
  343. // which means that you *must* use DOMParser, otherwise the output may
  344. // not be safe if used in a document.write context later.
  345. //
  346. // So we feature detect the Firefox bug and use the DOMParser if necessary.
  347. //
  348. // MS Edge, in older versions, is affected by an mXSS behavior. The second
  349. // check tests for the behavior and fixes it if necessary.
  350. if (DOMPurify.isSupported) {
  351. (function () {
  352. try {
  353. var doc = _initDocument('<svg><p><style><img src="</style><img src=x onerror=alert(1)//">');
  354. if (doc.querySelector('svg img')) {
  355. useDOMParser = true;
  356. }
  357. } catch (err) {}
  358. })();
  359. (function () {
  360. try {
  361. var doc = _initDocument('<x/><title>&lt;/title&gt;&lt;img&gt;');
  362. if (doc.querySelector('title').textContent.match(/<\/title/)) {
  363. removeTitle = true;
  364. }
  365. } catch (err) {}
  366. })();
  367. }
  368. /**
  369. * _createIterator
  370. *
  371. * @param {Document} root document/fragment to create iterator for
  372. * @return {Iterator} iterator instance
  373. */
  374. var _createIterator = function _createIterator(root) {
  375. return createNodeIterator.call(root.ownerDocument || root, root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT, function () {
  376. return NodeFilter.FILTER_ACCEPT;
  377. }, false);
  378. };
  379. /**
  380. * _isClobbered
  381. *
  382. * @param {Node} elm element to check for clobbering attacks
  383. * @return {Boolean} true if clobbered, false if safe
  384. */
  385. var _isClobbered = function _isClobbered(elm) {
  386. if (elm instanceof Text || elm instanceof Comment) {
  387. return false;
  388. }
  389. 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') {
  390. return true;
  391. }
  392. return false;
  393. };
  394. /**
  395. * _isNode
  396. *
  397. * @param {Node} obj object to check whether it's a DOM node
  398. * @return {Boolean} true is object is a DOM node
  399. */
  400. var _isNode = function _isNode(obj) {
  401. return (typeof Node === 'undefined' ? 'undefined' : _typeof(Node)) === 'object' ? obj instanceof Node : obj && (typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object' && typeof obj.nodeType === 'number' && typeof obj.nodeName === 'string';
  402. };
  403. /**
  404. * _executeHook
  405. * Execute user configurable hooks
  406. *
  407. * @param {String} entryPoint Name of the hook's entry point
  408. * @param {Node} currentNode node to work on with the hook
  409. * @param {Object} data additional hook parameters
  410. */
  411. var _executeHook = function _executeHook(entryPoint, currentNode, data) {
  412. if (!hooks[entryPoint]) {
  413. return;
  414. }
  415. hooks[entryPoint].forEach(function (hook) {
  416. hook.call(DOMPurify, currentNode, data, CONFIG);
  417. });
  418. };
  419. /**
  420. * _sanitizeElements
  421. *
  422. * @protect nodeName
  423. * @protect textContent
  424. * @protect removeChild
  425. *
  426. * @param {Node} currentNode to check for permission to exist
  427. * @return {Boolean} true if node was killed, false if left alive
  428. */
  429. var _sanitizeElements = function _sanitizeElements(currentNode) {
  430. var content = void 0;
  431. /* Execute a hook if present */
  432. _executeHook('beforeSanitizeElements', currentNode, null);
  433. /* Check if element is clobbered or can clobber */
  434. if (_isClobbered(currentNode)) {
  435. _forceRemove(currentNode);
  436. return true;
  437. }
  438. /* Now let's check the element's type and name */
  439. var tagName = currentNode.nodeName.toLowerCase();
  440. /* Execute a hook if present */
  441. _executeHook('uponSanitizeElement', currentNode, {
  442. tagName: tagName,
  443. allowedTags: ALLOWED_TAGS
  444. });
  445. /* Remove element if anything forbids its presence */
  446. if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
  447. /* Keep content except for black-listed elements */
  448. if (KEEP_CONTENT && !FORBID_CONTENTS[tagName] && typeof currentNode.insertAdjacentHTML === 'function') {
  449. try {
  450. currentNode.insertAdjacentHTML('AfterEnd', currentNode.innerHTML);
  451. } catch (err) {}
  452. }
  453. _forceRemove(currentNode);
  454. return true;
  455. }
  456. /* Convert markup to cover jQuery behavior */
  457. if (SAFE_FOR_JQUERY && !currentNode.firstElementChild && (!currentNode.content || !currentNode.content.firstElementChild) && /</g.test(currentNode.textContent)) {
  458. DOMPurify.removed.push({ element: currentNode.cloneNode() });
  459. if (currentNode.innerHTML) {
  460. currentNode.innerHTML = currentNode.innerHTML.replace(/</g, '&lt;');
  461. } else {
  462. currentNode.innerHTML = currentNode.textContent.replace(/</g, '&lt;');
  463. }
  464. }
  465. /* Sanitize element content to be template-safe */
  466. if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
  467. /* Get the element's text content */
  468. content = currentNode.textContent;
  469. content = content.replace(MUSTACHE_EXPR$$1, ' ');
  470. content = content.replace(ERB_EXPR$$1, ' ');
  471. if (currentNode.textContent !== content) {
  472. DOMPurify.removed.push({ element: currentNode.cloneNode() });
  473. currentNode.textContent = content;
  474. }
  475. }
  476. /* Execute a hook if present */
  477. _executeHook('afterSanitizeElements', currentNode, null);
  478. return false;
  479. };
  480. /**
  481. * _isValidAttribute
  482. *
  483. * @param {string} lcTag Lowercase tag name of containing element.
  484. * @param {string} lcName Lowercase attribute name.
  485. * @param {string} value Attribute value.
  486. * @return {Boolean} Returns true if `value` is valid, otherwise false.
  487. */
  488. var _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
  489. /* Make sure attribute cannot clobber */
  490. if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
  491. return false;
  492. }
  493. /* Sanitize attribute content to be template-safe */
  494. if (SAFE_FOR_TEMPLATES) {
  495. value = value.replace(MUSTACHE_EXPR$$1, ' ');
  496. value = value.replace(ERB_EXPR$$1, ' ');
  497. }
  498. /* Allow valid data-* attributes: At least one character after "-"
  499. (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
  500. XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
  501. We don't need to check the value; it's always URI safe. */
  502. if (ALLOW_DATA_ATTR && DATA_ATTR$$1.test(lcName)) {
  503. // This attribute is safe
  504. } else if (ALLOW_ARIA_ATTR && ARIA_ATTR$$1.test(lcName)) {
  505. // This attribute is safe
  506. /* Otherwise, check the name is permitted */
  507. } else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
  508. return false;
  509. /* Check value is safe. First, is attr inert? If so, is safe */
  510. } else if (URI_SAFE_ATTRIBUTES[lcName]) {
  511. // This attribute is safe
  512. /* Check no script, data or unknown possibly unsafe URI
  513. unless we know URI values are safe for that attribute */
  514. } else if (IS_ALLOWED_URI$$1.test(value.replace(ATTR_WHITESPACE$$1, ''))) {
  515. // This attribute is safe
  516. /* Keep image data URIs alive if src/xlink:href is allowed */
  517. } else if ((lcName === 'src' || lcName === 'xlink:href') && value.indexOf('data:') === 0 && DATA_URI_TAGS[lcTag]) {
  518. // This attribute is safe
  519. /* Allow unknown protocols: This provides support for links that
  520. are handled by protocol handlers which may be unknown ahead of
  521. time, e.g. fb:, spotify: */
  522. } else if (ALLOW_UNKNOWN_PROTOCOLS && !IS_SCRIPT_OR_DATA$$1.test(value.replace(ATTR_WHITESPACE$$1, ''))) {
  523. // This attribute is safe
  524. /* Check for binary attributes */
  525. // eslint-disable-next-line no-negated-condition
  526. } else if (!value) {
  527. // Binary attributes are safe at this point
  528. /* Anything else, presume unsafe, do not add it back */
  529. } else {
  530. return false;
  531. }
  532. return true;
  533. };
  534. /**
  535. * _sanitizeAttributes
  536. *
  537. * @protect attributes
  538. * @protect nodeName
  539. * @protect removeAttribute
  540. * @protect setAttribute
  541. *
  542. * @param {Node} node to sanitize
  543. */
  544. // eslint-disable-next-line complexity
  545. var _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
  546. var attr = void 0;
  547. var value = void 0;
  548. var lcName = void 0;
  549. var idAttr = void 0;
  550. var l = void 0;
  551. /* Execute a hook if present */
  552. _executeHook('beforeSanitizeAttributes', currentNode, null);
  553. var attributes = currentNode.attributes;
  554. /* Check if we have attributes; if not we might have a text node */
  555. if (!attributes) {
  556. return;
  557. }
  558. var hookEvent = {
  559. attrName: '',
  560. attrValue: '',
  561. keepAttr: true,
  562. allowedAttributes: ALLOWED_ATTR
  563. };
  564. l = attributes.length;
  565. /* Go backwards over all attributes; safely remove bad ones */
  566. while (l--) {
  567. attr = attributes[l];
  568. var _attr = attr,
  569. name = _attr.name;
  570. value = attr.value.trim();
  571. lcName = name.toLowerCase();
  572. /* Execute a hook if present */
  573. hookEvent.attrName = lcName;
  574. hookEvent.attrValue = value;
  575. hookEvent.keepAttr = true;
  576. _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
  577. value = hookEvent.attrValue;
  578. /* Remove attribute */
  579. // Safari (iOS + Mac), last tested v8.0.5, crashes if you try to
  580. // remove a "name" attribute from an <img> tag that has an "id"
  581. // attribute at the time.
  582. if (lcName === 'name' && currentNode.nodeName === 'IMG' && attributes.id) {
  583. idAttr = attributes.id;
  584. attributes = Array.prototype.slice.apply(attributes);
  585. _removeAttribute('id', currentNode);
  586. _removeAttribute(name, currentNode);
  587. if (attributes.indexOf(idAttr) > l) {
  588. currentNode.setAttribute('id', idAttr.value);
  589. }
  590. } else if (
  591. // This works around a bug in Safari, where input[type=file]
  592. // cannot be dynamically set after type has been removed
  593. currentNode.nodeName === 'INPUT' && lcName === 'type' && value === 'file' && (ALLOWED_ATTR[lcName] || !FORBID_ATTR[lcName])) {
  594. continue;
  595. } else {
  596. // This avoids a crash in Safari v9.0 with double-ids.
  597. // The trick is to first set the id to be empty and then to
  598. // remove the attribute
  599. if (name === 'id') {
  600. currentNode.setAttribute(name, '');
  601. }
  602. _removeAttribute(name, currentNode);
  603. }
  604. /* Did the hooks approve of the attribute? */
  605. if (!hookEvent.keepAttr) {
  606. continue;
  607. }
  608. /* Is `value` valid for this attribute? */
  609. var lcTag = currentNode.nodeName.toLowerCase();
  610. if (!_isValidAttribute(lcTag, lcName, value)) {
  611. continue;
  612. }
  613. /* Handle invalid data-* attribute set by try-catching it */
  614. try {
  615. currentNode.setAttribute(name, value);
  616. DOMPurify.removed.pop();
  617. } catch (err) {}
  618. }
  619. /* Execute a hook if present */
  620. _executeHook('afterSanitizeAttributes', currentNode, null);
  621. };
  622. /**
  623. * _sanitizeShadowDOM
  624. *
  625. * @param {DocumentFragment} fragment to iterate over recursively
  626. */
  627. var _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
  628. var shadowNode = void 0;
  629. var shadowIterator = _createIterator(fragment);
  630. /* Execute a hook if present */
  631. _executeHook('beforeSanitizeShadowDOM', fragment, null);
  632. while (shadowNode = shadowIterator.nextNode()) {
  633. /* Execute a hook if present */
  634. _executeHook('uponSanitizeShadowNode', shadowNode, null);
  635. /* Sanitize tags and elements */
  636. if (_sanitizeElements(shadowNode)) {
  637. continue;
  638. }
  639. /* Deep shadow DOM detected */
  640. if (shadowNode.content instanceof DocumentFragment) {
  641. _sanitizeShadowDOM(shadowNode.content);
  642. }
  643. /* Check attributes, sanitize if necessary */
  644. _sanitizeAttributes(shadowNode);
  645. }
  646. /* Execute a hook if present */
  647. _executeHook('afterSanitizeShadowDOM', fragment, null);
  648. };
  649. /**
  650. * Sanitize
  651. * Public method providing core sanitation functionality
  652. *
  653. * @param {String|Node} dirty string or DOM node
  654. * @param {Object} configuration object
  655. */
  656. // eslint-disable-next-line complexity
  657. DOMPurify.sanitize = function (dirty, cfg) {
  658. var body = void 0;
  659. var importedNode = void 0;
  660. var currentNode = void 0;
  661. var oldNode = void 0;
  662. var returnNode = void 0;
  663. /* Make sure we have a string to sanitize.
  664. DO NOT return early, as this will return the wrong type if
  665. the user has requested a DOM object rather than a string */
  666. if (!dirty) {
  667. dirty = '<!-->';
  668. }
  669. /* Stringify, in case dirty is an object */
  670. if (typeof dirty !== 'string' && !_isNode(dirty)) {
  671. // eslint-disable-next-line no-negated-condition
  672. if (typeof dirty.toString !== 'function') {
  673. throw new TypeError('toString is not a function');
  674. } else {
  675. dirty = dirty.toString();
  676. if (typeof dirty !== 'string') {
  677. throw new TypeError('dirty is not a string, aborting');
  678. }
  679. }
  680. }
  681. /* Check we can run. Otherwise fall back or ignore */
  682. if (!DOMPurify.isSupported) {
  683. if (_typeof(window.toStaticHTML) === 'object' || typeof window.toStaticHTML === 'function') {
  684. if (typeof dirty === 'string') {
  685. return window.toStaticHTML(dirty);
  686. }
  687. if (_isNode(dirty)) {
  688. return window.toStaticHTML(dirty.outerHTML);
  689. }
  690. }
  691. return dirty;
  692. }
  693. /* Assign config vars */
  694. if (!SET_CONFIG) {
  695. _parseConfig(cfg);
  696. }
  697. /* Clean up removed elements */
  698. DOMPurify.removed = [];
  699. if (IN_PLACE) {
  700. /* No special handling necessary for in-place sanitization */
  701. } else if (dirty instanceof Node) {
  702. /* If dirty is a DOM element, append to an empty document to avoid
  703. elements being stripped by the parser */
  704. body = _initDocument('<!-->');
  705. importedNode = body.ownerDocument.importNode(dirty, true);
  706. if (importedNode.nodeType === 1 && importedNode.nodeName === 'BODY') {
  707. /* Node is already a body, use as is */
  708. body = importedNode;
  709. } else {
  710. body.appendChild(importedNode);
  711. }
  712. } else {
  713. /* Exit directly if we have nothing to do */
  714. if (!RETURN_DOM && !WHOLE_DOCUMENT && dirty.indexOf('<') === -1) {
  715. return dirty;
  716. }
  717. /* Initialize the document to work on */
  718. body = _initDocument(dirty);
  719. /* Check we have a DOM node from the data */
  720. if (!body) {
  721. return RETURN_DOM ? null : '';
  722. }
  723. }
  724. /* Remove first element node (ours) if FORCE_BODY is set */
  725. if (body && FORCE_BODY) {
  726. _forceRemove(body.firstChild);
  727. }
  728. /* Get node iterator */
  729. var nodeIterator = _createIterator(IN_PLACE ? dirty : body);
  730. /* Now start iterating over the created document */
  731. while (currentNode = nodeIterator.nextNode()) {
  732. /* Fix IE's strange behavior with manipulated textNodes #89 */
  733. if (currentNode.nodeType === 3 && currentNode === oldNode) {
  734. continue;
  735. }
  736. /* Sanitize tags and elements */
  737. if (_sanitizeElements(currentNode)) {
  738. continue;
  739. }
  740. /* Shadow DOM detected, sanitize it */
  741. if (currentNode.content instanceof DocumentFragment) {
  742. _sanitizeShadowDOM(currentNode.content);
  743. }
  744. /* Check attributes, sanitize if necessary */
  745. _sanitizeAttributes(currentNode);
  746. oldNode = currentNode;
  747. }
  748. /* If we sanitized `dirty` in-place, return it. */
  749. if (IN_PLACE) {
  750. return dirty;
  751. }
  752. /* Return sanitized string or DOM */
  753. if (RETURN_DOM) {
  754. if (RETURN_DOM_FRAGMENT) {
  755. returnNode = createDocumentFragment.call(body.ownerDocument);
  756. while (body.firstChild) {
  757. returnNode.appendChild(body.firstChild);
  758. }
  759. } else {
  760. returnNode = body;
  761. }
  762. if (RETURN_DOM_IMPORT) {
  763. /* AdoptNode() is not used because internal state is not reset
  764. (e.g. the past names map of a HTMLFormElement), this is safe
  765. in theory but we would rather not risk another attack vector.
  766. The state that is cloned by importNode() is explicitly defined
  767. by the specs. */
  768. returnNode = importNode.call(originalDocument, returnNode, true);
  769. }
  770. return returnNode;
  771. }
  772. return WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
  773. };
  774. /**
  775. * Public method to set the configuration once
  776. * setConfig
  777. *
  778. * @param {Object} cfg configuration object
  779. */
  780. DOMPurify.setConfig = function (cfg) {
  781. _parseConfig(cfg);
  782. SET_CONFIG = true;
  783. };
  784. /**
  785. * Public method to remove the configuration
  786. * clearConfig
  787. *
  788. */
  789. DOMPurify.clearConfig = function () {
  790. CONFIG = null;
  791. SET_CONFIG = false;
  792. };
  793. /**
  794. * Public method to check if an attribute value is valid.
  795. * Uses last set config, if any. Otherwise, uses config defaults.
  796. * isValidAttribute
  797. *
  798. * @param {string} tag Tag name of containing element.
  799. * @param {string} attr Attribute name.
  800. * @param {string} value Attribute value.
  801. * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
  802. */
  803. DOMPurify.isValidAttribute = function (tag, attr, value) {
  804. /* Initialize shared config vars if necessary. */
  805. if (!CONFIG) {
  806. _parseConfig({});
  807. }
  808. var lcTag = tag.toLowerCase();
  809. var lcName = attr.toLowerCase();
  810. return _isValidAttribute(lcTag, lcName, value);
  811. };
  812. /**
  813. * AddHook
  814. * Public method to add DOMPurify hooks
  815. *
  816. * @param {String} entryPoint entry point for the hook to add
  817. * @param {Function} hookFunction function to execute
  818. */
  819. DOMPurify.addHook = function (entryPoint, hookFunction) {
  820. if (typeof hookFunction !== 'function') {
  821. return;
  822. }
  823. hooks[entryPoint] = hooks[entryPoint] || [];
  824. hooks[entryPoint].push(hookFunction);
  825. };
  826. /**
  827. * RemoveHook
  828. * Public method to remove a DOMPurify hook at a given entryPoint
  829. * (pops it from the stack of hooks if more are present)
  830. *
  831. * @param {String} entryPoint entry point for the hook to remove
  832. */
  833. DOMPurify.removeHook = function (entryPoint) {
  834. if (hooks[entryPoint]) {
  835. hooks[entryPoint].pop();
  836. }
  837. };
  838. /**
  839. * RemoveHooks
  840. * Public method to remove all DOMPurify hooks at a given entryPoint
  841. *
  842. * @param {String} entryPoint entry point for the hooks to remove
  843. */
  844. DOMPurify.removeHooks = function (entryPoint) {
  845. if (hooks[entryPoint]) {
  846. hooks[entryPoint] = [];
  847. }
  848. };
  849. /**
  850. * RemoveAllHooks
  851. * Public method to remove all DOMPurify hooks
  852. *
  853. */
  854. DOMPurify.removeAllHooks = function () {
  855. hooks = {};
  856. };
  857. return DOMPurify;
  858. }
  859. var purify = createDOMPurify();
  860. return purify;
  861. })));