| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 | /** * filter xss * * @author Zongmin Lei<leizongmin@gmail.com> */var FilterCSS = require("cssfilter").FilterCSS;var DEFAULT = require("./default");var parser = require("./parser");var parseTag = parser.parseTag;var parseAttr = parser.parseAttr;var _ = require("./util");/** * returns `true` if the input value is `undefined` or `null` * * @param {Object} obj * @return {Boolean} */function isNull(obj) {  return obj === undefined || obj === null;}/** * get attributes for a tag * * @param {String} html * @return {Object} *   - {String} html *   - {Boolean} closing */function getAttrs(html) {  var i = _.spaceIndex(html);  if (i === -1) {    return {      html: "",      closing: html[html.length - 2] === "/",    };  }  html = _.trim(html.slice(i + 1, -1));  var isClosing = html[html.length - 1] === "/";  if (isClosing) html = _.trim(html.slice(0, -1));  return {    html: html,    closing: isClosing,  };}/** * shallow copy * * @param {Object} obj * @return {Object} */function shallowCopyObject(obj) {  var ret = {};  for (var i in obj) {    ret[i] = obj[i];  }  return ret;}function keysToLowerCase(obj) {  var ret = {};  for (var i in obj) {    if (Array.isArray(obj[i])) {      ret[i.toLowerCase()] = obj[i].map(function (item) {        return item.toLowerCase();      });    } else {      ret[i.toLowerCase()] = obj[i];    }  }  return ret;}/** * FilterXSS class * * @param {Object} options *        whiteList (or allowList), onTag, onTagAttr, onIgnoreTag, *        onIgnoreTagAttr, safeAttrValue, escapeHtml *        stripIgnoreTagBody, allowCommentTag, stripBlankChar *        css{whiteList, onAttr, onIgnoreAttr} `css=false` means don't use `cssfilter` */function FilterXSS(options) {  options = shallowCopyObject(options || {});  if (options.stripIgnoreTag) {    if (options.onIgnoreTag) {      console.error(        'Notes: cannot use these two options "stripIgnoreTag" and "onIgnoreTag" at the same time'      );    }    options.onIgnoreTag = DEFAULT.onIgnoreTagStripAll;  }  if (options.whiteList || options.allowList) {    options.whiteList = keysToLowerCase(options.whiteList || options.allowList);  } else {    options.whiteList = DEFAULT.whiteList;  }  options.onTag = options.onTag || DEFAULT.onTag;  options.onTagAttr = options.onTagAttr || DEFAULT.onTagAttr;  options.onIgnoreTag = options.onIgnoreTag || DEFAULT.onIgnoreTag;  options.onIgnoreTagAttr = options.onIgnoreTagAttr || DEFAULT.onIgnoreTagAttr;  options.safeAttrValue = options.safeAttrValue || DEFAULT.safeAttrValue;  options.escapeHtml = options.escapeHtml || DEFAULT.escapeHtml;  this.options = options;  if (options.css === false) {    this.cssFilter = false;  } else {    options.css = options.css || {};    this.cssFilter = new FilterCSS(options.css);  }}/** * start process and returns result * * @param {String} html * @return {String} */FilterXSS.prototype.process = function (html) {  // compatible with the input  html = html || "";  html = html.toString();  if (!html) return "";  var me = this;  var options = me.options;  var whiteList = options.whiteList;  var onTag = options.onTag;  var onIgnoreTag = options.onIgnoreTag;  var onTagAttr = options.onTagAttr;  var onIgnoreTagAttr = options.onIgnoreTagAttr;  var safeAttrValue = options.safeAttrValue;  var escapeHtml = options.escapeHtml;  var cssFilter = me.cssFilter;  // remove invisible characters  if (options.stripBlankChar) {    html = DEFAULT.stripBlankChar(html);  }  // remove html comments  if (!options.allowCommentTag) {    html = DEFAULT.stripCommentTag(html);  }  // if enable stripIgnoreTagBody  var stripIgnoreTagBody = false;  if (options.stripIgnoreTagBody) {    stripIgnoreTagBody = DEFAULT.StripTagBody(      options.stripIgnoreTagBody,      onIgnoreTag    );    onIgnoreTag = stripIgnoreTagBody.onIgnoreTag;  }  var retHtml = parseTag(    html,    function (sourcePosition, position, tag, html, isClosing) {      var info = {        sourcePosition: sourcePosition,        position: position,        isClosing: isClosing,        isWhite: Object.prototype.hasOwnProperty.call(whiteList, tag),      };      // call `onTag()`      var ret = onTag(tag, html, info);      if (!isNull(ret)) return ret;      if (info.isWhite) {        if (info.isClosing) {          return "</" + tag + ">";        }        var attrs = getAttrs(html);        var whiteAttrList = whiteList[tag];        var attrsHtml = parseAttr(attrs.html, function (name, value) {          // call `onTagAttr()`          var isWhiteAttr = _.indexOf(whiteAttrList, name) !== -1;          var ret = onTagAttr(tag, name, value, isWhiteAttr);          if (!isNull(ret)) return ret;          if (isWhiteAttr) {            // call `safeAttrValue()`            value = safeAttrValue(tag, name, value, cssFilter);            if (value) {              return name + '="' + value + '"';            } else {              return name;            }          } else {            // call `onIgnoreTagAttr()`            ret = onIgnoreTagAttr(tag, name, value, isWhiteAttr);            if (!isNull(ret)) return ret;            return;          }        });        // build new tag html        html = "<" + tag;        if (attrsHtml) html += " " + attrsHtml;        if (attrs.closing) html += " /";        html += ">";        return html;      } else {        // call `onIgnoreTag()`        ret = onIgnoreTag(tag, html, info);        if (!isNull(ret)) return ret;        return escapeHtml(html);      }    },    escapeHtml  );  // if enable stripIgnoreTagBody  if (stripIgnoreTagBody) {    retHtml = stripIgnoreTagBody.remove(retHtml);  }  return retHtml;};module.exports = FilterXSS;
 |