From e317f88ca843a56e3f5ad3db1eaafa79ba3fa4c8 Mon Sep 17 00:00:00 2001 From: Olivier Paroz Date: Thu, 24 Sep 2015 17:34:15 +0200 Subject: Update JS libraries --- js/vendor/dompurify/src/purify.js | 698 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 698 insertions(+) create mode 100644 js/vendor/dompurify/src/purify.js (limited to 'js/vendor/dompurify/src/purify.js') 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 ... 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(/ 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; +})); -- cgit v1.2.3