You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							230 lines
						
					
					
						
							5.7 KiB
						
					
					
				
			
		
		
	
	
							230 lines
						
					
					
						
							5.7 KiB
						
					
					
				/**
 | 
						|
 * 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;
 |