Files
js-xss/lib/xss.js

212 lines
5.4 KiB
JavaScript
Raw Normal View History

2014-02-13 14:58:36 +08:00
/**
2017-12-21 14:19:10 +08:00
* filter xss
2014-02-13 14:58:36 +08:00
*
2017-12-21 14:19:10 +08:00
* @author Zongmin Lei<leizongmin@gmail.com>
2014-02-13 14:58:36 +08:00
*/
2017-12-21 14:22:34 +08:00
var FilterCSS = require("cssfilter").FilterCSS;
var DEFAULT = require("./default");
var parser = require("./parser");
2014-02-13 14:58:36 +08:00
var parseTag = parser.parseTag;
var parseAttr = parser.parseAttr;
2017-12-21 14:22:34 +08:00
var _ = require("./util");
2014-02-13 14:58:36 +08:00
/**
2017-12-21 14:19:10 +08:00
* returns `true` if the input value is `undefined` or `null`
2014-02-13 14:58:36 +08:00
*
* @param {Object} obj
* @return {Boolean}
*/
2017-12-21 14:22:34 +08:00
function isNull(obj) {
return obj === undefined || obj === null;
2014-02-13 14:58:36 +08:00
}
/**
2017-12-21 14:19:10 +08:00
* get attributes for a tag
2014-02-13 14:58:36 +08:00
*
* @param {String} html
* @return {Object}
* - {String} html
* - {Boolean} closing
*/
2017-12-21 14:22:34 +08:00
function getAttrs(html) {
var i = _.spaceIndex(html);
2014-02-13 14:58:36 +08:00
if (i === -1) {
return {
2017-12-21 14:22:34 +08:00
html: "",
closing: html[html.length - 2] === "/",
2014-02-13 14:58:36 +08:00
};
}
2015-03-27 16:09:45 +11:00
html = _.trim(html.slice(i + 1, -1));
2017-12-21 14:22:34 +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 {
2017-12-21 14:22:34 +08:00
html: html,
closing: isClosing,
2014-02-13 14:58:36 +08:00
};
}
2016-12-20 09:13:35 +08:00
/**
2017-12-21 14:19:10 +08:00
* shallow copy
2016-12-20 09:13:35 +08:00
*
* @param {Object} obj
* @return {Object}
*/
2017-12-21 14:22:34 +08:00
function shallowCopyObject(obj) {
2016-12-20 09:13:35 +08:00
var ret = {};
for (var i in obj) {
ret[i] = obj[i];
}
return ret;
}
2014-02-13 14:58:36 +08:00
/**
2017-12-21 14:19:10 +08:00
* FilterXSS class
2014-02-13 14:58:36 +08:00
*
* @param {Object} options
* whiteList (or allowList), onTag, onTagAttr, onIgnoreTag,
* onIgnoreTagAttr, safeAttrValue, escapeHtml
* stripIgnoreTagBody, allowCommentTag, stripBlankChar
2017-12-21 14:19:10 +08:00
* css{whiteList, onAttr, onIgnoreAttr} `css=false` means don't use `cssfilter`
2014-02-13 14:58:36 +08:00
*/
2017-12-21 14:22:34 +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) {
2017-12-21 14:22:34 +08:00
console.error(
'Notes: cannot use these two options "stripIgnoreTag" and "onIgnoreTag" at the same time'
);
2014-02-13 16:27:49 +08:00
}
options.onIgnoreTag = DEFAULT.onIgnoreTagStripAll;
}
options.whiteList = options.whiteList || options.allowList || DEFAULT.whiteList;
2014-02-13 14:58:36 +08:00
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;
2022-05-27 22:57:50 +08:00
options.escapeHtml = options.escapeHtml || (options.allowCommentTag ? DEFAULT.escapeHtmlNotComment : DEFAULT.escapeHtml);
2014-02-13 14:58:36 +08:00
this.options = options;
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
}
/**
2017-12-21 14:19:10 +08:00
* start process and returns result
2014-02-13 14:58:36 +08:00
*
* @param {String} html
* @return {String}
*/
FilterXSS.prototype.process = function (html) {
2017-12-21 14:19:10 +08:00
// compatible with the input
2017-12-21 14:22:34 +08:00
html = html || "";
2015-01-12 14:04:29 +08:00
html = html.toString();
2017-12-21 14:22:34 +08:00
if (!html) return "";
2015-01-12 14:04:29 +08:00
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;
var escapeHtml = options.escapeHtml;
var cssFilter = me.cssFilter;
2014-02-13 14:58:36 +08:00
2017-12-21 14:19:10 +08:00
// remove invisible characters
if (options.stripBlankChar) {
html = DEFAULT.stripBlankChar(html);
}
2017-12-21 14:19:10 +08:00
// remove html comments
if (!options.allowCommentTag) {
html = DEFAULT.stripCommentTag(html);
}
2017-12-21 14:19:10 +08:00
// if enable stripIgnoreTagBody
2015-12-23 12:22:39 +08:00
var stripIgnoreTagBody = false;
2014-02-13 18:18:43 +08:00
if (options.stripIgnoreTagBody) {
2022-03-09 19:39:57 +08:00
stripIgnoreTagBody = DEFAULT.StripTagBody(
2017-12-21 14:22:34 +08:00
options.stripIgnoreTagBody,
onIgnoreTag
);
2014-02-13 18:18:43 +08:00
onIgnoreTag = stripIgnoreTagBody.onIgnoreTag;
}
2017-12-21 14:22:34 +08:00
var retHtml = parseTag(
html,
function (sourcePosition, position, tag, html, isClosing) {
2017-12-21 14:22:34 +08:00
var info = {
sourcePosition: sourcePosition,
position: position,
isClosing: isClosing,
2022-03-09 19:39:57 +08:00
isWhite: Object.prototype.hasOwnProperty.call(whiteList, tag),
2017-12-21 14:22:34 +08:00
};
// call `onTag()`
var ret = onTag(tag, html, info);
if (!isNull(ret)) return ret;
2014-02-13 14:58:36 +08:00
2017-12-21 14:22:34 +08:00
if (info.isWhite) {
if (info.isClosing) {
return "</" + tag + ">";
}
2014-02-13 14:58:36 +08:00
2017-12-21 14:22:34 +08:00
var attrs = getAttrs(html);
var whiteAttrList = whiteList[tag];
var attrsHtml = parseAttr(attrs.html, function (name, value) {
2017-12-21 14:22:34 +08:00
// call `onTagAttr()`
var isWhiteAttr = _.indexOf(whiteAttrList, name) !== -1;
var ret = onTagAttr(tag, name, value, isWhiteAttr);
if (!isNull(ret)) return ret;
2014-02-13 14:58:36 +08:00
2017-12-21 14:22:34 +08:00
if (isWhiteAttr) {
// call `safeAttrValue()`
value = safeAttrValue(tag, name, value, cssFilter);
if (value) {
return name + '="' + value + '"';
} else {
return name;
}
2014-02-13 14:58:36 +08:00
} else {
2017-12-21 14:22:34 +08:00
// call `onIgnoreTagAttr()`
2022-03-09 19:39:57 +08:00
ret = onIgnoreTagAttr(tag, name, value, isWhiteAttr);
2017-12-21 14:22:34 +08:00
if (!isNull(ret)) return ret;
return;
2014-02-13 14:58:36 +08:00
}
2017-12-21 14:22:34 +08:00
});
// build new tag html
2022-03-09 19:39:57 +08:00
html = "<" + tag;
2017-12-21 14:22:34 +08:00
if (attrsHtml) html += " " + attrsHtml;
if (attrs.closing) html += " /";
html += ">";
return html;
} else {
// call `onIgnoreTag()`
2022-03-09 19:39:57 +08:00
ret = onIgnoreTag(tag, html, info);
2017-12-21 14:22:34 +08:00
if (!isNull(ret)) return ret;
return escapeHtml(html);
}
},
escapeHtml
);
2014-02-13 18:18:43 +08:00
2017-12-21 14:19:10 +08:00
// if enable stripIgnoreTagBody
2014-02-13 18:18:43 +08:00
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;