Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/nextcloud/gallery.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOlivier Paroz <github@oparoz.com>2015-09-24 18:34:15 +0300
committerOlivier Paroz <github@oparoz.com>2015-09-24 18:34:15 +0300
commite317f88ca843a56e3f5ad3db1eaafa79ba3fa4c8 (patch)
tree7e25bd9594b54d1be2f8af111b647600821d60c7 /js/vendor/dompurify/src/purify.js
parentb1c88d331d444efb540d862cb1c19df2de76f724 (diff)
Update JS libraries
Diffstat (limited to 'js/vendor/dompurify/src/purify.js')
-rw-r--r--js/vendor/dompurify/src/purify.js698
1 files changed, 698 insertions, 0 deletions
diff --git a/js/vendor/dompurify/src/purify.js b/js/vendor/dompurify/src/purify.js
new file mode 100644
index 00000000..79f2df9f
--- /dev/null
+++ b/js/vendor/dompurify/src/purify.js
@@ -0,0 +1,698 @@
+;(function(factory) {
+ 'use strict';
+ /* global window: false, define: false, module: false */
+ var root = typeof window === 'undefined' ? null : window;
+
+ if (typeof define === 'function' && define.amd) {
+ define(function(){ return factory(root); });
+ } else if (typeof module !== 'undefined') {
+ module.exports = factory(root);
+ } else {
+ root.DOMPurify = factory(root);
+ }
+}(function factory(window) {
+ 'use strict';
+
+ var DOMPurify = function(window) {
+ return factory(window);
+ };
+
+ /**
+ * Version label, exposed for easier checks
+ * if DOMPurify is up to date or not
+ */
+ DOMPurify.version = '0.6.7';
+
+ if (!window || !window.document || window.document.nodeType !== 9) {
+ // not running in a browser, provide a factory function
+ // so that you can pass your own Window
+ DOMPurify.isSupported = false;
+ return DOMPurify;
+ }
+
+ var document = window.document;
+ var originalDocument = document;
+ var DocumentFragment = window.DocumentFragment;
+ var HTMLTemplateElement = window.HTMLTemplateElement;
+ var NodeFilter = window.NodeFilter;
+ var NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap;
+ var Text = window.Text;
+ var Comment = window.Comment;
+ var DOMParser = window.DOMParser;
+
+ // As per issue #47, the web-components registry is inherited by a
+ // new document created via createHTMLDocument. As per the spec
+ // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
+ // a new empty registry is used when creating a template contents owner
+ // document, so we use that as our parent document to ensure nothing
+ // is inherited.
+ if (typeof HTMLTemplateElement === 'function') {
+ document = document.createElement('template').content.ownerDocument;
+ }
+ var createNodeIterator = document.createNodeIterator;
+ var getElementsByTagName = document.getElementsByTagName;
+ var createDocumentFragment = document.createDocumentFragment;
+ var importNode = originalDocument.importNode;
+
+ var hooks = {};
+
+ /**
+ * Expose whether this browser supports running the full DOMPurify.
+ */
+ DOMPurify.isSupported =
+ typeof DOMParser !== 'undefined' && document.documentMode !== 9;
+
+ /* Add properties to a lookup table */
+ var _addToSet = function(set, array) {
+ var l = array.length;
+ while (l--) {
+ set[array[l]] = true;
+ }
+ return set;
+ };
+
+ /* Shallow clone an object */
+ var _cloneObj = function(object) {
+ var newObject = {};
+ var property;
+ for (property in object) {
+ if (object.hasOwnProperty(property)) {
+ newObject[property] = object[property];
+ }
+ }
+ return newObject;
+ };
+
+ /**
+ * We consider the elements and attributes below to be safe. Ideally
+ * don't add any new ones but feel free to remove unwanted ones.
+ */
+
+ /* allowed element names */
+ var ALLOWED_TAGS = null;
+ var DEFAULT_ALLOWED_TAGS = _addToSet({}, [
+
+ // 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',
+
+ // SVG
+ 'svg','altglyph','altglyphdef','altglyphitem','animatecolor',
+ 'animatemotion','animatetransform','circle','clippath','defs','desc',
+ 'ellipse','font','g','glyph','glyphref','hkern','image','line',
+ 'lineargradient','marker','mask','metadata','mpath','path','pattern',
+ 'polygon','polyline','radialgradient','rect','stop','switch','symbol',
+ 'text','textpath','title','tref','tspan','view','vkern',
+
+ //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',
+
+ //Text
+ '#text'
+ ]);
+
+ /* Allowed attribute names */
+ var ALLOWED_ATTR = null;
+ var DEFAULT_ALLOWED_ATTR = _addToSet({}, [
+
+ // HTML
+ 'accept','action','align','alt','autocomplete','background','bgcolor',
+ 'border','cellpadding','cellspacing','checked','cite','class','clear','color',
+ 'cols','colspan','coords','datetime','default','dir','disabled',
+ 'download','enctype','face','for','headers','height','hidden','high','href',
+ 'hreflang','id','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','rows',
+ 'rowspan','spellcheck','scope','selected','shape','size','span',
+ 'srclang','start','src','step','style','summary','tabindex','title',
+ 'type','usemap','valign','value','width','xmlns',
+
+ // SVG
+ 'accent-height','accumulate','additivive','alignment-baseline',
+ 'ascent','azimuth','baseline-shift','bias','clip','clip-path',
+ 'clip-rule','color','color-interpolation','color-interpolation-filters',
+ 'color-profile','color-rendering','cx','cy','d','dy','dy','direction',
+ 'display','divisor','dur','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','image-rendering','in','in2','k1','k2','k3','k4','kerning',
+ 'letter-spacing','lighting-color','local','marker-end','marker-mid',
+ 'marker-start','max','mask','mode','min','offset','operator','opacity',
+ 'order','overflow','paint-order','path','points','r','rx','ry','radius',
+ 'restart','scale','seed','shape-rendering','stop-color','stop-opacity',
+ 'stroke-dasharray','stroke-dashoffset','stroke-linecap','stroke-linejoin',
+ 'stroke-miterlimit','stroke-opacity','stroke','stroke-width','transform',
+ 'text-anchor','text-decoration','text-rendering','u1','u2','viewbox',
+ 'visibility','word-spacing','wrap','writing-mode','x','x1','x2','y',
+ 'y1','y2','z',
+
+ // MathML
+ 'accent','accentunder','bevelled','close','columnsalign','columnlines',
+ 'columnspan','denomalign','depth','display','displaystyle','fence',
+ 'frame','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',
+
+ // XML
+ 'xlink:href','xml:id','xlink:title','xml:space'
+ ]);
+
+ /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
+ var FORBID_TAGS = null;
+
+ /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
+ var FORBID_ATTR = null;
+
+ /* Decide if custom data attributes are okay */
+ var ALLOW_DATA_ATTR = true;
+
+ /* Output should be safe for jQuery's $() factory? */
+ var SAFE_FOR_JQUERY = false;
+
+ /* Decide if document with <html>... should be returned */
+ var WHOLE_DOCUMENT = false;
+
+ /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html string.
+ * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
+ */
+ var RETURN_DOM = false;
+
+ /* Decide if a DOM `DocumentFragment` should be returned, instead of a html string */
+ var RETURN_DOM_FRAGMENT = false;
+
+ /* If `RETURN_DOM` or `RETURN_DOM_FRAGMENT` is enabled, decide if the returned DOM
+ * `Node` is imported into the current `Document`. If this flag is not enabled the
+ * `Node` will belong (its ownerDocument) to a fresh `HTMLDocument`, created by
+ * DOMPurify. */
+ var RETURN_DOM_IMPORT = false;
+
+ /* Output should be free from DOM clobbering attacks? */
+ var SANITIZE_DOM = true;
+
+ /* Keep element content when removing element? */
+ var KEEP_CONTENT = true;
+
+ /* Tags to ignore content of when KEEP_CONTENT is true */
+ var FORBID_CONTENTS = _addToSet({}, [
+ 'audio', 'head', 'math', 'script', 'style', 'svg', 'video'
+ ]);
+
+ /* Keep a reference to config to pass to hooks */
+ var CONFIG = null;
+
+ /* Ideally, do not touch anything below this line */
+ /* ______________________________________________ */
+
+ var formElement = document.createElement('form');
+
+ /**
+ * _parseConfig
+ *
+ * @param optional config literal
+ */
+ var _parseConfig = function(cfg) {
+ /* Shield configuration object from tampering */
+ if (typeof cfg !== 'object') {
+ cfg = {};
+ }
+
+ /* Set configuration parameters */
+ ALLOWED_TAGS = 'ALLOWED_TAGS' in cfg ?
+ _addToSet({}, cfg.ALLOWED_TAGS) : DEFAULT_ALLOWED_TAGS;
+ ALLOWED_ATTR = 'ALLOWED_ATTR' in cfg ?
+ _addToSet({}, cfg.ALLOWED_ATTR) : DEFAULT_ALLOWED_ATTR;
+ FORBID_TAGS = 'FORBID_TAGS' in cfg ?
+ _addToSet({}, cfg.FORBID_TAGS) : {};
+ FORBID_ATTR = 'FORBID_ATTR' in cfg ?
+ _addToSet({}, cfg.FORBID_ATTR) : {};
+ ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
+ SAFE_FOR_JQUERY = cfg.SAFE_FOR_JQUERY || false; // Default false
+ WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
+ RETURN_DOM = cfg.RETURN_DOM || false; // Default false
+ RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
+ RETURN_DOM_IMPORT = cfg.RETURN_DOM_IMPORT || false; // Default false
+ SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
+ KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
+
+ if (RETURN_DOM_FRAGMENT) {
+ RETURN_DOM = true;
+ }
+
+ /* Merge configuration parameters */
+ if (cfg.ADD_TAGS) {
+ if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
+ ALLOWED_TAGS = _cloneObj(ALLOWED_TAGS);
+ }
+ _addToSet(ALLOWED_TAGS, cfg.ADD_TAGS);
+ }
+ if (cfg.ADD_ATTR) {
+ if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
+ ALLOWED_ATTR = _cloneObj(ALLOWED_ATTR);
+ }
+ _addToSet(ALLOWED_ATTR, cfg.ADD_ATTR);
+ }
+
+ /* Add #text in case KEEP_CONTENT is set to true */
+ if (KEEP_CONTENT) { ALLOWED_TAGS['#text'] = true; }
+
+ // Prevent further manipulation of configuration.
+ // Not available in IE8, Safari 5, etc.
+ if (Object && 'freeze' in Object) { Object.freeze(cfg); }
+
+ CONFIG = cfg;
+ };
+
+ /**
+ * _forceRemove
+ *
+ * @param a DOM node
+ */
+ var _forceRemove = function(node) {
+ try {
+ node.parentNode.removeChild(node);
+ } catch (e) {
+ node.outerHTML = '';
+ }
+ };
+
+ /**
+ * _initDocument
+ *
+ * @param a string of dirty markup
+ * @return a DOM, filled with the dirty markup
+ */
+ var _initDocument = function(dirty) {
+
+ /* Create a HTML document using DOMParser */
+ var doc = new DOMParser().parseFromString(dirty, "text/html");
+
+ /* Work on whole document or just its body */
+ return getElementsByTagName.call(doc,
+ WHOLE_DOCUMENT ? 'html' : 'body')[0];
+ };
+
+ /**
+ * _createIterator
+ *
+ * @param document/fragment to create iterator for
+ * @return iterator instance
+ */
+ var _createIterator = function(root) {
+ return createNodeIterator.call(root.ownerDocument || root,
+ root,
+ NodeFilter.SHOW_ELEMENT
+ | NodeFilter.SHOW_COMMENT
+ | NodeFilter.SHOW_TEXT,
+ function() { return NodeFilter.FILTER_ACCEPT; },
+ false
+ );
+ };
+
+ /**
+ * _isClobbered
+ *
+ * @param element to check for clobbering attacks
+ * @return true if clobbered, false if safe
+ */
+ var _isClobbered = function(elm) {
+ if (elm instanceof Text || elm instanceof Comment) {
+ return false;
+ }
+ 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'
+ ) {
+ return true;
+ }
+ return false;
+ };
+
+ /**
+ * _sanitizeElements
+ *
+ * @protect nodeName
+ * @protect textContent
+ * @protect removeChild
+ *
+ * @param node to check for permission to exist
+ * @return true if node was killed, false if left alive
+ */
+ var _sanitizeElements = function(currentNode) {
+ /* Execute a hook if present */
+ _executeHook('beforeSanitizeElements', currentNode, null);
+
+ /* Check if element is clobbered or can clobber */
+ if (_isClobbered(currentNode)) {
+ _forceRemove(currentNode);
+ return true;
+ }
+
+ /* Now let's check the element's type and name */
+ var tagName = currentNode.nodeName.toLowerCase();
+
+ /* Execute a hook if present */
+ _executeHook('uponSanitizeElement', currentNode, {
+ tagName: tagName
+ });
+
+ /* Remove element if anything forbids its presence */
+ if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
+ /* Keep content except for black-listed elements */
+ if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]
+ && typeof currentNode.insertAdjacentHTML === 'function') {
+ try {
+ currentNode.insertAdjacentHTML('AfterEnd', currentNode.innerHTML);
+ } catch (e) {}
+ }
+ _forceRemove(currentNode);
+ return true;
+ }
+
+ /* Finally, convert markup to cover jQuery behavior */
+ if (SAFE_FOR_JQUERY && !currentNode.firstElementChild) {
+ currentNode.innerHTML = currentNode.textContent.replace(/</g, '&lt;');
+ }
+
+ /* Execute a hook if present */
+ _executeHook('afterSanitizeElements', currentNode, null);
+
+ return false;
+ };
+
+ /**
+ * _sanitizeAttributes
+ *
+ * @protect attributes
+ * @protect nodeName
+ * @protect removeAttribute
+ * @protect setAttribute
+ *
+ * @param node to sanitize
+ * @return void
+ */
+ var _sanitizeAttributes = function(currentNode) {
+ /* Execute a hook if present */
+ _executeHook('beforeSanitizeAttributes', currentNode, null);
+
+ var attributes = currentNode.attributes;
+
+ /* Check if we have attributes; if not we might have a text node */
+ if (!attributes) { return; }
+
+ var hookEvent = {
+ attrName: '',
+ attrValue: '',
+ keepAttr: true
+ },
+ l = attributes.length,
+ attr, name, value, lcName, idAttr;
+
+ /* Go backwards over all attributes; safely remove bad ones */
+ while (l--) {
+ attr = attributes[l];
+ name = attr.name;
+ value = attr.value;
+ lcName = name.toLowerCase();
+
+ /* Execute a hook if present */
+ hookEvent.attrName = lcName;
+ hookEvent.attrValue = value;
+ hookEvent.keepAttr = true;
+ _executeHook('uponSanitizeAttribute', currentNode, hookEvent );
+ value = hookEvent.attrValue;
+
+ /* Remove attribute */
+ // Safari (iOS + Mac), last tested v8.0.5, crashes if you try to
+ // remove a "name" attribute from an <img> tag that has an "id"
+ // attribute at the time.
+ if (lcName === 'name' &&
+ currentNode.nodeName === 'IMG' && attributes.id) {
+ idAttr = attributes.id;
+ attributes = Array.prototype.slice.apply(attributes);
+ currentNode.removeAttribute('id');
+ currentNode.removeAttribute(name);
+ if (attributes.indexOf(idAttr) > l) {
+ currentNode.setAttribute('id', idAttr.value);
+ }
+ } else {
+ currentNode.removeAttribute(name);
+ }
+
+ /* Did the hooks approve of the attribute? */
+ if (!hookEvent.keepAttr) {
+ continue;
+ }
+
+ /* Make sure attribute cannot clobber */
+ if (SANITIZE_DOM &&
+ (lcName === 'id' || lcName === 'name') &&
+ (value in window || value in document || value in formElement)) {
+ continue;
+ }
+
+ if (
+ /* Check the name is permitted */
+ (
+ (ALLOWED_ATTR[lcName] && !FORBID_ATTR[lcName]) ||
+ /* Allow potentially valid data-* attributes
+ * At least one character after "-" (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
+ * XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804) */
+ (ALLOW_DATA_ATTR && DATA_ATTR.test(lcName))
+ ) &&
+ /* Get rid of script and data URIs */
+ (
+ !IS_SCRIPT_OR_DATA.test(value.replace(ATTR_WHITESPACE,'')) ||
+ /* Keep image data URIs alive if src is allowed */
+ (lcName === 'src' && value.indexOf('data:') === 0 &&
+ currentNode.nodeName === 'IMG')
+ )
+ ) {
+ /* Handle invalid data-* attribute set by try-catching it */
+ try {
+ currentNode.setAttribute(name, value);
+ } catch (e) {}
+ }
+ }
+
+ /* Execute a hook if present */
+ _executeHook('afterSanitizeAttributes', currentNode, null);
+ };
+ var DATA_ATTR = /^data-[\w.\u00B7-\uFFFF-]/;
+ var IS_SCRIPT_OR_DATA = /^(?:\w+script|data):/i;
+ /* This needs to be extensive thanks to Webkit/Blink's behavior */
+ var ATTR_WHITESPACE = /[\x00-\x20\xA0\u1680\u180E\u2000-\u2029\u205f\u3000]/g;
+
+ /**
+ * _sanitizeShadowDOM
+ *
+ * @param fragment to iterate over recursively
+ * @return void
+ */
+ var _sanitizeShadowDOM = function(fragment) {
+ var shadowNode;
+ var shadowIterator = _createIterator(fragment);
+
+ /* Execute a hook if present */
+ _executeHook('beforeSanitizeShadowDOM', fragment, null);
+
+ while ( (shadowNode = shadowIterator.nextNode()) ) {
+ /* Execute a hook if present */
+ _executeHook('uponSanitizeShadowNode', shadowNode, null);
+
+ /* Sanitize tags and elements */
+ if (_sanitizeElements(shadowNode)) {
+ continue;
+ }
+
+ /* Deep shadow DOM detected */
+ if (shadowNode.content instanceof DocumentFragment) {
+ _sanitizeShadowDOM(shadowNode.content);
+ }
+
+ /* Check attributes, sanitize if necessary */
+ _sanitizeAttributes(shadowNode);
+ }
+
+ /* Execute a hook if present */
+ _executeHook('afterSanitizeShadowDOM', fragment, null);
+ };
+
+ /**
+ * _executeHook
+ * Execute user configurable hooks
+ *
+ * @param {String} entryPoint Name of the hook's entry point
+ * @param {Node} currentNode
+ */
+ var _executeHook = function(entryPoint, currentNode, data) {
+ if (!hooks[entryPoint]) { return; }
+
+ hooks[entryPoint].forEach(function(hook) {
+ hook.call(DOMPurify, currentNode, data, CONFIG);
+ });
+ };
+
+ /**
+ * sanitize
+ * Public method providing core sanitation functionality
+ *
+ * @param {String} dirty string
+ * @param {Object} configuration object
+ */
+ DOMPurify.sanitize = function(dirty, cfg) {
+ /* Check we can run. Otherwise fall back or ignore */
+ if (!DOMPurify.isSupported) {
+ if (typeof window.toStaticHTML === 'function' && typeof dirty === 'string') {
+ return window.toStaticHTML(dirty);
+ }
+ return dirty;
+ }
+
+ /* Assign config vars */
+ _parseConfig(cfg);
+
+ /* Exit directly if we have nothing to do */
+ if (!RETURN_DOM && !WHOLE_DOCUMENT && dirty.indexOf('<') === -1) {
+ return dirty;
+ }
+
+ /* Initialize the document to work on */
+ var body = _initDocument(dirty);
+
+ /* Check we have a DOM node from the data */
+ if (!body) {
+ return RETURN_DOM ? null : '';
+ }
+
+ /* Get node iterator */
+ var currentNode;
+ var oldNode;
+ var nodeIterator = _createIterator(body);
+
+ /* Now start iterating over the created document */
+ while ( (currentNode = nodeIterator.nextNode()) ) {
+
+ /* Fix IE's strange behavior with manipulated textNodes #89 */
+ if (currentNode.nodeType === 3 && currentNode === oldNode) {
+ continue;
+ }
+
+ /* Sanitize tags and elements */
+ if (_sanitizeElements(currentNode)) {
+ continue;
+ }
+
+ /* Shadow DOM detected, sanitize it */
+ if (currentNode.content instanceof DocumentFragment) {
+ _sanitizeShadowDOM(currentNode.content);
+ }
+
+ /* Check attributes, sanitize if necessary */
+ _sanitizeAttributes(currentNode);
+
+ oldNode = currentNode;
+ }
+
+ /* Return sanitized string or DOM */
+ var returnNode;
+ if (RETURN_DOM) {
+
+ if (RETURN_DOM_FRAGMENT) {
+ returnNode = createDocumentFragment.call(body.ownerDocument);
+
+ while (body.firstChild) {
+ returnNode.appendChild(body.firstChild);
+ }
+ } else {
+ returnNode = body;
+ }
+
+ if (RETURN_DOM_IMPORT) {
+ /* adoptNode() is not used because internal state is not reset
+ (e.g. the past names map of a HTMLFormElement), this is safe
+ in theory but we would rather not risk another attack vector.
+ The state that is cloned by importNode() is explicitly defined
+ by the specs. */
+ returnNode = importNode.call(originalDocument, returnNode, true);
+ }
+
+ return returnNode;
+ }
+
+ return WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;
+ };
+
+ /**
+ * addHook
+ * Public method to add DOMPurify hooks
+ *
+ * @param {String} entryPoint
+ * @param {Function} hookFunction
+ */
+ DOMPurify.addHook = function(entryPoint, hookFunction) {
+ if (typeof hookFunction !== 'function') { return; }
+ hooks[entryPoint] = hooks[entryPoint] || [];
+ hooks[entryPoint].push(hookFunction);
+ };
+
+ /**
+ * removeHook
+ * Public method to remove a DOMPurify hook at a given entryPoint
+ * (pops it from the stack of hooks if more are present)
+ *
+ * @param {String} entryPoint
+ * @return void
+ */
+ DOMPurify.removeHook = function(entryPoint) {
+ if (hooks[entryPoint]) {
+ hooks[entryPoint].pop();
+ }
+ };
+
+ /**
+ * removeHooks
+ * Public method to remove all DOMPurify hooks at a given entryPoint
+ *
+ * @param {String} entryPoint
+ * @return void
+ */
+ DOMPurify.removeHooks = function(entryPoint) {
+ if (hooks[entryPoint]) {
+ hooks[entryPoint] = [];
+ }
+ };
+
+ /**
+ * removeAllHooks
+ * Public method to remove all DOMPurify hooks
+ *
+ * @return void
+ */
+ DOMPurify.removeAllHooks = function() {
+ hooks = [];
+ };
+
+ return DOMPurify;
+}));