2014-02-13 14:58:36 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 过滤XSS
|
|
|
|
|
|
*
|
|
|
|
|
|
* @author 老雷<leizongmin@gmail.com>
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2015-05-05 22:50:56 +08:00
|
|
|
|
var FilterCSS = require('cssfilter').FilterCSS;
|
2014-02-13 14:58:36 +08:00
|
|
|
|
var DEFAULT = require('./default');
|
|
|
|
|
|
var parser = require('./parser');
|
|
|
|
|
|
var parseTag = parser.parseTag;
|
|
|
|
|
|
var parseAttr = parser.parseAttr;
|
2015-03-27 16:09:45 +11:00
|
|
|
|
var _ = require('./util');
|
2014-02-13 14:58:36 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 返回值是否为空
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {Object} obj
|
|
|
|
|
|
* @return {Boolean}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function isNull (obj) {
|
|
|
|
|
|
return (obj === undefined || obj === null);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 取标签内的属性列表字符串
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {String} html
|
|
|
|
|
|
* @return {Object}
|
|
|
|
|
|
* - {String} html
|
|
|
|
|
|
* - {Boolean} closing
|
|
|
|
|
|
*/
|
|
|
|
|
|
function getAttrs (html) {
|
|
|
|
|
|
var i = html.indexOf(' ');
|
|
|
|
|
|
if (i === -1) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
html: '',
|
|
|
|
|
|
closing: (html[html.length - 2] === '/')
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
2015-03-27 16:09:45 +11:00
|
|
|
|
html = _.trim(html.slice(i + 1, -1));
|
2014-02-13 14:58:36 +08:00
|
|
|
|
var isClosing = (html[html.length - 1] === '/');
|
2015-03-27 16:09:45 +11:00
|
|
|
|
if (isClosing) html = _.trim(html.slice(0, -1));
|
2014-02-13 14:58:36 +08:00
|
|
|
|
return {
|
|
|
|
|
|
html: html,
|
|
|
|
|
|
closing: isClosing
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-12-20 09:13:35 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* 浅拷贝对象
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {Object} obj
|
|
|
|
|
|
* @return {Object}
|
|
|
|
|
|
*/
|
|
|
|
|
|
function shallowCopyObject (obj) {
|
|
|
|
|
|
var ret = {};
|
|
|
|
|
|
for (var i in obj) {
|
|
|
|
|
|
ret[i] = obj[i];
|
|
|
|
|
|
}
|
|
|
|
|
|
return ret;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-02-13 14:58:36 +08:00
|
|
|
|
/**
|
|
|
|
|
|
* XSS过滤对象
|
|
|
|
|
|
*
|
2015-05-05 22:50:56 +08:00
|
|
|
|
* @param {Object} options
|
|
|
|
|
|
* 选项:whiteList, onTag, onTagAttr, onIgnoreTag,
|
|
|
|
|
|
* onIgnoreTagAttr, safeAttrValue, escapeHtml
|
|
|
|
|
|
* stripIgnoreTagBody, allowCommentTag, stripBlankChar
|
2016-11-06 11:06:02 +08:00
|
|
|
|
* css{whiteList, onAttr, onIgnoreAttr} css=false表示禁用cssfilter
|
2014-02-13 14:58:36 +08:00
|
|
|
|
*/
|
|
|
|
|
|
function FilterXSS (options) {
|
2016-12-20 09:13:35 +08:00
|
|
|
|
options = shallowCopyObject(options || {});
|
2014-02-13 16:27:49 +08:00
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-02-13 14:58:36 +08:00
|
|
|
|
options.whiteList = 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;
|
2015-05-05 22:50:56 +08:00
|
|
|
|
|
2016-11-06 11:06:02 +08:00
|
|
|
|
if (options.css === false) {
|
|
|
|
|
|
this.cssFilter = false;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
options.css = options.css || {};
|
|
|
|
|
|
this.cssFilter = new FilterCSS(options.css);
|
|
|
|
|
|
}
|
2014-02-13 14:58:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 开始处理
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param {String} html
|
|
|
|
|
|
* @return {String}
|
|
|
|
|
|
*/
|
|
|
|
|
|
FilterXSS.prototype.process = function (html) {
|
2015-01-12 14:04:29 +08:00
|
|
|
|
// 兼容各种奇葩输入
|
|
|
|
|
|
html = html || '';
|
|
|
|
|
|
html = html.toString();
|
|
|
|
|
|
if (!html) return '';
|
|
|
|
|
|
|
2014-02-13 14:58:36 +08:00
|
|
|
|
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;
|
2015-05-05 22:50:56 +08:00
|
|
|
|
var escapeHtml = options.escapeHtml;
|
|
|
|
|
|
var cssFilter = me.cssFilter;
|
2014-02-13 14:58:36 +08:00
|
|
|
|
|
2015-01-22 14:20:55 +08:00
|
|
|
|
// 是否清除不可见字符
|
|
|
|
|
|
if (options.stripBlankChar) {
|
|
|
|
|
|
html = DEFAULT.stripBlankChar(html);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-04-03 11:47:21 +08:00
|
|
|
|
// 是否禁止备注标签
|
|
|
|
|
|
if (!options.allowCommentTag) {
|
|
|
|
|
|
html = DEFAULT.stripCommentTag(html);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-02-13 18:18:43 +08:00
|
|
|
|
// 如果开启了stripIgnoreTagBody
|
2015-12-23 12:22:39 +08:00
|
|
|
|
var stripIgnoreTagBody = false;
|
2014-02-13 18:18:43 +08:00
|
|
|
|
if (options.stripIgnoreTagBody) {
|
|
|
|
|
|
var stripIgnoreTagBody = DEFAULT.StripTagBody(options.stripIgnoreTagBody, onIgnoreTag);
|
|
|
|
|
|
onIgnoreTag = stripIgnoreTagBody.onIgnoreTag;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var retHtml = parseTag(html, function (sourcePosition, position, tag, html, isClosing) {
|
2014-02-13 14:58:36 +08:00
|
|
|
|
var info = {
|
2014-02-13 16:33:35 +08:00
|
|
|
|
sourcePosition: sourcePosition,
|
2014-02-13 14:58:36 +08:00
|
|
|
|
position: position,
|
|
|
|
|
|
isClosing: isClosing,
|
|
|
|
|
|
isWhite: (tag in whiteList)
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 调用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) {
|
|
|
|
|
|
|
|
|
|
|
|
// 调用onTagAttr处理
|
2015-03-27 16:09:45 +11:00
|
|
|
|
var isWhiteAttr = (_.indexOf(whiteAttrList, name) !== -1);
|
2014-02-13 15:55:36 +08:00
|
|
|
|
var ret = onTagAttr(tag, name, value, isWhiteAttr);
|
2014-02-13 14:58:36 +08:00
|
|
|
|
if (!isNull(ret)) return ret;
|
|
|
|
|
|
|
|
|
|
|
|
// 默认的属性处理方法
|
2014-02-13 15:55:36 +08:00
|
|
|
|
if (isWhiteAttr) {
|
2014-02-14 10:06:09 +08:00
|
|
|
|
// 白名单属性,调用safeAttrValue过滤属性值
|
2015-05-05 22:50:56 +08:00
|
|
|
|
value = safeAttrValue(tag, name, value, cssFilter);
|
2014-02-13 14:58:36 +08:00
|
|
|
|
if (value) {
|
|
|
|
|
|
return name + '="' + value + '"';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return name;
|
|
|
|
|
|
}
|
2014-02-13 15:55:36 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 非白名单属性,调用onIgnoreTagAttr处理
|
|
|
|
|
|
var ret = onIgnoreTagAttr(tag, name, value, isWhiteAttr);
|
|
|
|
|
|
if (!isNull(ret)) return ret;
|
|
|
|
|
|
return;
|
2014-02-13 14:58:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 构造新的标签代码
|
|
|
|
|
|
var html = '<' + tag;
|
|
|
|
|
|
if (attrsHtml) html += ' ' + attrsHtml;
|
|
|
|
|
|
if (attrs.closing) html += ' /';
|
|
|
|
|
|
html += '>';
|
|
|
|
|
|
return html;
|
|
|
|
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 非白名单标签,调用onIgnoreTag处理
|
|
|
|
|
|
var ret = onIgnoreTag(tag, html, info);
|
|
|
|
|
|
if (!isNull(ret)) return ret;
|
|
|
|
|
|
return escapeHtml(html);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}, escapeHtml);
|
2014-02-13 18:18:43 +08:00
|
|
|
|
|
|
|
|
|
|
// 如果开启了stripIgnoreTagBody,需要对结果再进行处理
|
|
|
|
|
|
if (stripIgnoreTagBody) {
|
|
|
|
|
|
retHtml = stripIgnoreTagBody.remove(retHtml);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return retHtml;
|
2014-02-13 14:58:36 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
2015-03-27 16:09:45 +11:00
|
|
|
|
module.exports = FilterXSS;
|