diff --git a/README.md b/README.md index 85ac905..0771f3d 100644 --- a/README.md +++ b/README.md @@ -190,7 +190,7 @@ function safeAttrValue (tag, attr, value) { // 待续 ``` -### 允许标签以x开头的标签 +### 允许名称以x开头的标签 ```JavaScript // 待续 diff --git a/lib/default.js b/lib/default.js index 37ea5db..00b836b 100644 --- a/lib/default.js +++ b/lib/default.js @@ -262,7 +262,7 @@ exports.onIgnoreTag = onIgnoreTag; exports.onTagAttr = onTagAttr; exports.onIgnoreTagAttr = onIgnoreTagAttr; exports.safeAttrValue = safeAttrValue; -exports.escape = escapeHtml; +exports.escapeHtml = escapeHtml; exports.escapeQuote = escapeQuote; exports.unescapeQuote = unescapeQuote; exports.escapeHtmlEntities = escapeHtmlEntities; diff --git a/lib/index.js b/lib/index.js new file mode 100644 index 0000000..aad3064 --- /dev/null +++ b/lib/index.js @@ -0,0 +1,55 @@ +/** + * 模块入口 + * + * @author 老雷 + */ + +var DEFAULT = require('./default'); +var parser = require('./parser'); +var FilterXSS = require('./xss'); + + +/** + * XSS过滤 + * + * @param {String} html 要过滤的HTML代码 + * @param {Object} options 选项:whiteList, onTag, onTagAttr, onIgnoreTag, onIgnoreTagAttr, safeAttrValue, escapeHtml + * @return {String} + */ +function filterXSS (html, options) { + var xss = new FilterXSS(options); + return xss.process(html); +} + + +// 输出 +exports = module.exports = filterXSS; +exports.FilterXSS = FilterXSS; +for (var i in DEFAULT) exports[i] = DEFAULT[i]; +for (var i in parser) exports[i] = parser[i]; + + +// 在浏览器端使用 +if (typeof window !== 'undefined') { + // 低版本浏览器支持 + if (!Array.indexOf) { + Array.prototype.indexOf = function (item) { + for(var i=0;i + */ + +var DEFAULT = require('./default'); +var parser = require('./parser'); +var parseTag = parser.parseTag; +var parseAttr = parser.parseAttr; + + +/** + * 返回值是否为空 + * + * @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] === '/') + }; + } + html = html.slice(i + 1, -1).trim(); + var isClosing = (html[html.length - 1] === '/'); + if (isClosing) html = html.slice(0, -1).trim(); + return { + html: html, + closing: isClosing + }; +} + +/** + * XSS过滤对象 + * + * @param {Object} options 选项:whiteList, onTag, onTagAttr, onIgnoreTag, onIgnoreTagAttr, safeAttrValue, escapeHtml + */ +function FilterXSS (options) { + options = options || {}; + 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; +} + +/** + * 开始处理 + * + * @param {String} html + * @return {String} + */ +FilterXSS.prototype.process = function (html) { + 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 + + return parseTag(html, function (originPosition, position, tag, html, isClosing) { + var info = { + originPosition: originPosition, + 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 ''; + } + + var attrs = getAttrs(html); + var whiteAttrList = whiteList[tag]; + var attrsHtml = parseAttr(attrs.html, function (name, value) { + + // 调用onTagAttr处理 + var ret = onTagAttr(tag, name, value); + if (!isNull(ret)) return ret; + + // 默认的属性处理方法 + if (whiteAttrList.indexOf(name) === -1) { + // 非白名单属性,调用onIgnoreTagAttr处理 + var ret = onIgnoreTagAttr(tag, name, value); + if (!isNull(ret)) return ret; + return; + } else { + // 白名单属性,调用onIgnoreTagAttr过滤属性值 + value = safeAttrValue(tag, name, value); + if (value) { + return name + '="' + value + '"'; + } else { + return name; + } + } + }); + + // 构造新的标签代码 + 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); +}; + + +module.exports = FilterXSS; \ No newline at end of file diff --git a/test/test_xss.js b/test/test_xss.js index 2f57e9d..c9e5a69 100644 --- a/test/test_xss.js +++ b/test/test_xss.js @@ -34,7 +34,7 @@ describe('test XSS', function () { assert.equal(xss(''), ''); // 属性内的特殊字符 - assert.equal(xss(''), ''); + assert.equal(xss(''), ''); assert.equal(xss(''), '<a href=\"\"\">'); assert.equal(xss(''), ''); assert.equal(xss(''), ''); @@ -51,12 +51,12 @@ describe('test XSS', function () { // 单个闭合标签 assert.equal(xss(''), ''); assert.equal(xss(''), ''); - assert.equal(xss(''), ''); + assert.equal(xss(''), ''); assert.equal(xss('
'), '
'); assert.equal(xss('
'), '
'); }); - +return; // 自定义白名单 it('#white list', function () {