123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829 |
- ;(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.7.4';
- 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') {
- var template = document.createElement('template');
- if (template.content && template.content.ownerDocument) {
- document = template.content.ownerDocument;
- }
- }
- var implementation = document.implementation;
- 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 implementation.createHTMLDocument !== 'undefined' &&
- document.documentMode !== 9;
- /* Add properties to a lookup table */
- var _addToSet = function (set, array) {
- var l = array.length;
- while (l--) {
- if (typeof array[l] === 'string') {
- array[l] = array[l].toLowerCase();
- }
- 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', 'filter', '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',
- // SVG Filters
- 'feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite',
- 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap',
- 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur',
- 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset',
- 'feSpecularLighting', 'feTile', 'feTurbulence',
- //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', 'attributename', 'attributetype', 'azimuth', 'basefrequency',
- 'baseline-shift', 'begin', 'bias', 'by', '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',
- 'image-rendering', 'in', 'in2', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints',
- 'keysplines', 'keytimes', 'lengthadjust', 'letter-spacing', 'kernelmatrix',
- 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid',
- 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits',
- 'maskunits', 'max', 'mask', 'mode', 'min', 'numoctaves', 'offset', 'operator',
- 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order',
- 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits',
- 'points', 'preservealpha', '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',
- 'surfacescale', 'targetx', 'targety', 'transform', 'text-anchor', 'text-decoration',
- 'text-rendering', 'textlength', 'u1', 'u2', 'unicode', 'values', 'viewbox',
- 'visibility', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'word-spacing',
- 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2',
- 'y', 'y1', 'y2', 'z', 'zoomandpan',
- // 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', 'xmlns:xlink'
- ]);
- /* 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;
- /* Decide if unknown protocols are okay */
- var ALLOW_UNKNOWN_PROTOCOLS = false;
- /* Output should be safe for jQuery's $() factory? */
- var SAFE_FOR_JQUERY = false;
- /* Output should be safe for common template engines.
- * This means, DOMPurify removes data attributes, mustaches and ERB
- */
- var SAFE_FOR_TEMPLATES = false;
- /* Specify template detection regex for SAFE_FOR_TEMPLATES mode */
- var MUSTACHE_EXPR = /\{\{[\s\S]*|[\s\S]*\}\}/gm;
- var ERB_EXPR = /<%[\s\S]*|[\s\S]*%>/gm;
- /* 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'
- ]);
- /* Tags that are safe for data: URIs */
- var DATA_URI_TAGS = _addToSet({}, [
- 'audio', 'video', 'img', 'source'
- ]);
- /* Attributes safe for values like "javascript:" */
- var URI_SAFE_ATTRIBUTES = _addToSet({}, [
- 'alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder',
- 'summary', 'title', 'value', 'style', 'xmlns'
- ]);
- /* 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
- ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
- SAFE_FOR_JQUERY = cfg.SAFE_FOR_JQUERY || false; // Default false
- SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || 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 (SAFE_FOR_TEMPLATES) {
- ALLOW_DATA_ATTR = false;
- }
- 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, body;
- try {
- doc = new DOMParser().parseFromString(dirty, 'text/html');
- } catch (e) {
- }
- /* Some browsers throw, some browsers return null for the code above
- DOMParser with text/html support is only in very recent browsers. */
- if (!doc) {
- doc = implementation.createHTMLDocument('');
- body = doc.body;
- body.parentNode.removeChild(body.parentNode.firstElementChild);
- body.outerHTML = dirty;
- }
- /* Work on whole document or just its body */
- if (typeof doc.getElementsByTagName === 'function') {
- return doc.getElementsByTagName(
- WHOLE_DOCUMENT ? 'html' : 'body')[0];
- }
- 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) {
- var tagName, content;
- /* 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 */
- 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;
- }
- /* Convert markup to cover jQuery behavior */
- if (SAFE_FOR_JQUERY && !currentNode.firstElementChild &&
- (!currentNode.content || !currentNode.content.firstElementChild)) {
- currentNode.innerHTML = currentNode.textContent.replace(/</g, '<');
- }
- /* Sanitize element content to be template-safe */
- if (SAFE_FOR_TEMPLATES && currentNode.nodeType === 3) {
- /* Get the element's text content */
- content = currentNode.textContent;
- content = content.replace(MUSTACHE_EXPR, ' ');
- content = content.replace(ERB_EXPR, ' ');
- currentNode.textContent = content;
- }
- /* Execute a hook if present */
- _executeHook('afterSanitizeElements', currentNode, null);
- return false;
- };
- var DATA_ATTR = /^data-[\w.\u00B7-\uFFFF-]/;
- var IS_ALLOWED_URI = /^(?:(?:(?:f|ht)tps?|mailto|tel):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i;
- 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;
- /**
- * _sanitizeAttributes
- *
- * @protect attributes
- * @protect nodeName
- * @protect removeAttribute
- * @protect setAttribute
- *
- * @param node to sanitize
- * @return void
- */
- var _sanitizeAttributes = function (currentNode) {
- var attr, name, value, lcName, idAttr, attributes, hookEvent, l;
- /* Execute a hook if present */
- _executeHook('beforeSanitizeAttributes', currentNode, null);
- attributes = currentNode.attributes;
- /* Check if we have attributes; if not we might have a text node */
- if (!attributes) {
- return;
- }
- hookEvent = {
- attrName: '',
- attrValue: '',
- keepAttr: true
- };
- l = attributes.length;
- /* 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 {
- // This avoids a crash in Safari v9.0 with double-ids.
- // The trick is to first set the id to be empty and then to
- // remove the attriubute
- if (name === 'id') {
- currentNode.setAttribute(name, '');
- }
- 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;
- }
- /* Sanitize attribute content to be template-safe */
- if (SAFE_FOR_TEMPLATES) {
- value = value.replace(MUSTACHE_EXPR, ' ');
- value = value.replace(ERB_EXPR, ' ');
- }
- if (
- /* Check the name is permitted */
- (ALLOWED_ATTR[lcName] && !FORBID_ATTR[lcName] && (
- /* Check no script, data or unknown possibly unsafe URI
- unless we know URI values are safe for that attribute */
- URI_SAFE_ATTRIBUTES[lcName] ||
- IS_ALLOWED_URI.test(value.replace(ATTR_WHITESPACE, '')) ||
- /* Keep image data URIs alive if src is allowed */
- (lcName === 'src' && value.indexOf('data:') === 0 &&
- DATA_URI_TAGS[currentNode.nodeName.toLowerCase()])
- )) ||
- /* 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)
- * We don't need to check the value; it's always URI safe.
- */
- (ALLOW_DATA_ATTR && DATA_ATTR.test(lcName)) ||
- /* Allow unknown protocols:
- * This provides support for links that are handled by protocol handlers which may be unknown
- * ahead of time, e.g. fb:, spotify:
- */
- (ALLOW_UNKNOWN_PROTOCOLS && !IS_SCRIPT_OR_DATA.test(value.replace(ATTR_WHITESPACE, '')))
- ) {
- /* 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);
- };
- /**
- * _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) {
- var body, currentNode, oldNode, nodeIterator, returnNode;
- /* Make sure we have a string to sanitize.
- DO NOT return early, as this will return the wrong type if
- the user has requested a DOM object rather than a string */
- if (!dirty) {
- dirty = '';
- }
- /* Stringify, in case dirty is an object */
- if (typeof dirty !== 'string') {
- if (typeof dirty.toString !== 'function') {
- throw new TypeError('toString is not a function');
- } else {
- dirty = dirty.toString();
- }
- }
- /* Check we can run. Otherwise fall back or ignore */
- if (!DOMPurify.isSupported) {
- if (typeof window.toStaticHTML === 'object'
- || typeof window.toStaticHTML === 'function') {
- 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 */
- body = _initDocument(dirty);
- /* Check we have a DOM node from the data */
- if (!body) {
- return RETURN_DOM ? null : '';
- }
- /* Get node iterator */
- 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 */
- 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;
- }));
|