reformat by prettier

This commit is contained in:
Zongmin Lei
2017-12-21 14:22:34 +08:00
parent 32a4bece31
commit 9b85b8f2d6
9 changed files with 850 additions and 642 deletions

View File

@@ -4,42 +4,39 @@
* @author Zongmin Lei<leizongmin@gmail.com>
*/
var xss = require('./');
var readline = require('readline');
var xss = require("./");
var readline = require("readline");
var rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
console.log('Enter a blank line to do xss(), enter "@quit" to exit.\n');
function take (c, n) {
var ret = '';
function take(c, n) {
var ret = "";
for (var i = 0; i < n; i++) {
ret += c;
}
return ret;
}
function setPrompt (line) {
function setPrompt(line) {
line = line.toString();
rl.setPrompt('[' + line + ']' + take(' ', 5 - line.length));
rl.setPrompt("[" + line + "]" + take(" ", 5 - line.length));
rl.prompt();
}
setPrompt(1);
var html = [];
rl.on('line', function (line) {
if (line === '@quit') return process.exit();
if (line === '') {
console.log('');
console.log(xss(html.join('\r\n')));
console.log('');
rl.on("line", function(line) {
if (line === "@quit") return process.exit();
if (line === "") {
console.log("");
console.log(xss(html.join("\r\n")));
console.log("");
html = [];
} else {
html.push(line);

View File

@@ -4,75 +4,75 @@
* @author Zongmin Lei<leizongmin@gmail.com>
*/
var FilterCSS = require('cssfilter').FilterCSS;
var getDefaultCSSWhiteList = require('cssfilter').getDefaultWhiteList;
var _ = require('./util');
var FilterCSS = require("cssfilter").FilterCSS;
var getDefaultCSSWhiteList = require("cssfilter").getDefaultWhiteList;
var _ = require("./util");
function getDefaultWhiteList () {
function getDefaultWhiteList() {
return {
a: ['target', 'href', 'title'],
abbr: ['title'],
a: ["target", "href", "title"],
abbr: ["title"],
address: [],
area: ['shape', 'coords', 'href', 'alt'],
area: ["shape", "coords", "href", "alt"],
article: [],
aside: [],
audio: ['autoplay', 'controls', 'loop', 'preload', 'src'],
b: [],
bdi: ['dir'],
bdo: ['dir'],
big: [],
blockquote: ['cite'],
br: [],
aside: [],
audio: ["autoplay", "controls", "loop", "preload", "src"],
b: [],
bdi: ["dir"],
bdo: ["dir"],
big: [],
blockquote: ["cite"],
br: [],
caption: [],
center: [],
cite: [],
code: [],
col: ['align', 'valign', 'span', 'width'],
colgroup: ['align', 'valign', 'span', 'width'],
dd: [],
del: ['datetime'],
details: ['open'],
div: [],
dl: [],
dt: [],
em: [],
font: ['color', 'size', 'face'],
cite: [],
code: [],
col: ["align", "valign", "span", "width"],
colgroup: ["align", "valign", "span", "width"],
dd: [],
del: ["datetime"],
details: ["open"],
div: [],
dl: [],
dt: [],
em: [],
font: ["color", "size", "face"],
footer: [],
h1: [],
h2: [],
h3: [],
h4: [],
h5: [],
h6: [],
h1: [],
h2: [],
h3: [],
h4: [],
h5: [],
h6: [],
header: [],
hr: [],
i: [],
img: ['src', 'alt', 'title', 'width', 'height'],
ins: ['datetime'],
li: [],
mark: [],
nav: [],
ol: [],
p: [],
pre: [],
s: [],
section:[],
small: [],
span: [],
sub: [],
sup: [],
hr: [],
i: [],
img: ["src", "alt", "title", "width", "height"],
ins: ["datetime"],
li: [],
mark: [],
nav: [],
ol: [],
p: [],
pre: [],
s: [],
section: [],
small: [],
span: [],
sub: [],
sup: [],
strong: [],
table: ['width', 'border', 'align', 'valign'],
tbody: ['align', 'valign'],
td: ['width', 'rowspan', 'colspan', 'align', 'valign'],
tfoot: ['align', 'valign'],
th: ['width', 'rowspan', 'colspan', 'align', 'valign'],
thead: ['align', 'valign'],
tr: ['rowspan', 'align', 'valign'],
tt: [],
u: [],
ul: [],
video: ['autoplay', 'controls', 'loop', 'preload', 'src', 'height', 'width']
table: ["width", "border", "align", "valign"],
tbody: ["align", "valign"],
td: ["width", "rowspan", "colspan", "align", "valign"],
tfoot: ["align", "valign"],
th: ["width", "rowspan", "colspan", "align", "valign"],
thead: ["align", "valign"],
tr: ["rowspan", "align", "valign"],
tt: [],
u: [],
ul: [],
video: ["autoplay", "controls", "loop", "preload", "src", "height", "width"]
};
}
@@ -86,7 +86,7 @@ var defaultCSSFilter = new FilterCSS();
* @param {Object} options
* @return {String}
*/
function onTag (tag, html, options) {
function onTag(tag, html, options) {
// do nothing
}
@@ -98,7 +98,7 @@ function onTag (tag, html, options) {
* @param {Object} options
* @return {String}
*/
function onIgnoreTag (tag, html, options) {
function onIgnoreTag(tag, html, options) {
// do nothing
}
@@ -110,7 +110,7 @@ function onIgnoreTag (tag, html, options) {
* @param {String} value
* @return {String}
*/
function onTagAttr (tag, name, value) {
function onTagAttr(tag, name, value) {
// do nothing
}
@@ -122,7 +122,7 @@ function onTagAttr (tag, name, value) {
* @param {String} value
* @return {String}
*/
function onIgnoreTagAttr (tag, name, value) {
function onIgnoreTagAttr(tag, name, value) {
// do nothing
}
@@ -131,8 +131,8 @@ function onIgnoreTagAttr (tag, name, value) {
*
* @param {String} html
*/
function escapeHtml (html) {
return html.replace(REGEXP_LT, '&lt;').replace(REGEXP_GT, '&gt;');
function escapeHtml(html) {
return html.replace(REGEXP_LT, "&lt;").replace(REGEXP_GT, "&gt;");
}
/**
@@ -144,42 +144,46 @@ function escapeHtml (html) {
* @param {Object} cssFilter
* @return {String}
*/
function safeAttrValue (tag, name, value, cssFilter) {
function safeAttrValue(tag, name, value, cssFilter) {
// unescape attribute value firstly
value = friendlyAttrValue(value);
if (name === 'href' || name === 'src') {
if (name === "href" || name === "src") {
// filter `href` and `src` attribute
// only allow the value that starts with `http://` | `https://` | `mailto:` | `/` | `#`
value = _.trim(value);
if (value === '#') return '#';
if (!(value.substr(0, 7) === 'http://' ||
value.substr(0, 8) === 'https://' ||
value.substr(0, 7) === 'mailto:' ||
value.substr(0, 4) === 'tel:' ||
value[0] === '#' ||
value[0] === '/')) {
return '';
if (value === "#") return "#";
if (
!(
value.substr(0, 7) === "http://" ||
value.substr(0, 8) === "https://" ||
value.substr(0, 7) === "mailto:" ||
value.substr(0, 4) === "tel:" ||
value[0] === "#" ||
value[0] === "/"
)
) {
return "";
}
} else if (name === 'background') {
} else if (name === "background") {
// filter `background` attribute (maybe no use)
// `javascript:`
REGEXP_DEFAULT_ON_TAG_ATTR_4.lastIndex = 0;
if (REGEXP_DEFAULT_ON_TAG_ATTR_4.test(value)) {
return '';
return "";
}
} else if (name === 'style') {
} else if (name === "style") {
// `expression()`
REGEXP_DEFAULT_ON_TAG_ATTR_7.lastIndex = 0;
if (REGEXP_DEFAULT_ON_TAG_ATTR_7.test(value)) {
return '';
return "";
}
// `url()`
REGEXP_DEFAULT_ON_TAG_ATTR_8.lastIndex = 0;
if (REGEXP_DEFAULT_ON_TAG_ATTR_8.test(value)) {
REGEXP_DEFAULT_ON_TAG_ATTR_4.lastIndex = 0;
if (REGEXP_DEFAULT_ON_TAG_ATTR_4.test(value)) {
return '';
return "";
}
}
if (cssFilter !== false) {
@@ -198,15 +202,15 @@ var REGEXP_LT = /</g;
var REGEXP_GT = />/g;
var REGEXP_QUOTE = /"/g;
var REGEXP_QUOTE_2 = /&quot;/g;
var REGEXP_ATTR_VALUE_1 = /&#([a-zA-Z0-9]*);?/img;
var REGEXP_ATTR_VALUE_COLON = /&colon;?/img;
var REGEXP_ATTR_VALUE_NEWLINE = /&newline;?/img;
var REGEXP_DEFAULT_ON_TAG_ATTR_3 = /\/\*|\*\//mg;
var REGEXP_DEFAULT_ON_TAG_ATTR_4 = /((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a)\:/ig;
var REGEXP_DEFAULT_ON_TAG_ATTR_5 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:/ig;
var REGEXP_DEFAULT_ON_TAG_ATTR_6 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:\s*image\//ig;
var REGEXP_DEFAULT_ON_TAG_ATTR_7 = /e\s*x\s*p\s*r\s*e\s*s\s*s\s*i\s*o\s*n\s*\(.*/ig;
var REGEXP_DEFAULT_ON_TAG_ATTR_8 = /u\s*r\s*l\s*\(.*/ig;
var REGEXP_ATTR_VALUE_1 = /&#([a-zA-Z0-9]*);?/gim;
var REGEXP_ATTR_VALUE_COLON = /&colon;?/gim;
var REGEXP_ATTR_VALUE_NEWLINE = /&newline;?/gim;
var REGEXP_DEFAULT_ON_TAG_ATTR_3 = /\/\*|\*\//gm;
var REGEXP_DEFAULT_ON_TAG_ATTR_4 = /((j\s*a\s*v\s*a|v\s*b|l\s*i\s*v\s*e)\s*s\s*c\s*r\s*i\s*p\s*t\s*|m\s*o\s*c\s*h\s*a)\:/gi;
var REGEXP_DEFAULT_ON_TAG_ATTR_5 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:/gi;
var REGEXP_DEFAULT_ON_TAG_ATTR_6 = /^[\s"'`]*(d\s*a\s*t\s*a\s*)\:\s*image\//gi;
var REGEXP_DEFAULT_ON_TAG_ATTR_7 = /e\s*x\s*p\s*r\s*e\s*s\s*s\s*i\s*o\s*n\s*\(.*/gi;
var REGEXP_DEFAULT_ON_TAG_ATTR_8 = /u\s*r\s*l\s*\(.*/gi;
/**
* escape doube quote
@@ -214,8 +218,8 @@ var REGEXP_DEFAULT_ON_TAG_ATTR_8 = /u\s*r\s*l\s*\(.*/ig;
* @param {String} str
* @return {String} str
*/
function escapeQuote (str) {
return str.replace(REGEXP_QUOTE, '&quot;');
function escapeQuote(str) {
return str.replace(REGEXP_QUOTE, "&quot;");
}
/**
@@ -224,7 +228,7 @@ function escapeQuote (str) {
* @param {String} str
* @return {String} str
*/
function unescapeQuote (str) {
function unescapeQuote(str) {
return str.replace(REGEXP_QUOTE_2, '"');
}
@@ -234,11 +238,11 @@ function unescapeQuote (str) {
* @param {String} str
* @return {String}
*/
function escapeHtmlEntities (str) {
return str.replace(REGEXP_ATTR_VALUE_1, function replaceUnicode (str, code) {
return (code[0] === 'x' || code[0] === 'X')
? String.fromCharCode(parseInt(code.substr(1), 16))
: String.fromCharCode(parseInt(code, 10));
function escapeHtmlEntities(str) {
return str.replace(REGEXP_ATTR_VALUE_1, function replaceUnicode(str, code) {
return code[0] === "x" || code[0] === "X"
? String.fromCharCode(parseInt(code.substr(1), 16))
: String.fromCharCode(parseInt(code, 10));
});
}
@@ -248,9 +252,10 @@ function escapeHtmlEntities (str) {
* @param {String} str
* @return {String}
*/
function escapeDangerHtml5Entities (str) {
return str.replace(REGEXP_ATTR_VALUE_COLON, ':')
.replace(REGEXP_ATTR_VALUE_NEWLINE, ' ');
function escapeDangerHtml5Entities(str) {
return str
.replace(REGEXP_ATTR_VALUE_COLON, ":")
.replace(REGEXP_ATTR_VALUE_NEWLINE, " ");
}
/**
@@ -259,10 +264,10 @@ function escapeDangerHtml5Entities (str) {
* @param {String} str
* @return {String}
*/
function clearNonPrintableCharacter (str) {
var str2 = '';
function clearNonPrintableCharacter(str) {
var str2 = "";
for (var i = 0, len = str.length; i < len; i++) {
str2 += str.charCodeAt(i) < 32 ? ' ' : str.charAt(i);
str2 += str.charCodeAt(i) < 32 ? " " : str.charAt(i);
}
return _.trim(str2);
}
@@ -273,7 +278,7 @@ function clearNonPrintableCharacter (str) {
* @param {String} str
* @return {String}
*/
function friendlyAttrValue (str) {
function friendlyAttrValue(str) {
str = unescapeQuote(str);
str = escapeHtmlEntities(str);
str = escapeDangerHtml5Entities(str);
@@ -287,7 +292,7 @@ function friendlyAttrValue (str) {
* @param {String} str
* @return {String}
*/
function escapeAttrValue (str) {
function escapeAttrValue(str) {
str = escapeQuote(str);
str = escapeHtml(str);
return str;
@@ -296,8 +301,8 @@ function escapeAttrValue (str) {
/**
* `onIgnoreTag` function for removing all the tags that are not in whitelist
*/
function onIgnoreTagStripAll () {
return '';
function onIgnoreTagStripAll() {
return "";
}
/**
@@ -307,43 +312,46 @@ function onIgnoreTagStripAll () {
* @param {array} tags
* @param {function} next
*/
function StripTagBody (tags, next) {
if (typeof(next) !== 'function') {
next = function () {};
function StripTagBody(tags, next) {
if (typeof next !== "function") {
next = function() {};
}
var isRemoveAllTag = !Array.isArray(tags);
function isRemoveTag (tag) {
function isRemoveTag(tag) {
if (isRemoveAllTag) return true;
return (_.indexOf(tags, tag) !== -1);
return _.indexOf(tags, tag) !== -1;
}
var removeList = [];
var posStart = false;
return {
onIgnoreTag: function (tag, html, options) {
onIgnoreTag: function(tag, html, options) {
if (isRemoveTag(tag)) {
if (options.isClosing) {
var ret = '[/removed]';
var ret = "[/removed]";
var end = options.position + ret.length;
removeList.push([posStart !== false ? posStart : options.position, end]);
removeList.push([
posStart !== false ? posStart : options.position,
end
]);
posStart = false;
return ret;
} else {
if (!posStart) {
posStart = options.position;
}
return '[removed]';
return "[removed]";
}
} else {
return next(tag, html, options);
}
},
remove: function (html) {
var rethtml = '';
remove: function(html) {
var rethtml = "";
var lastPos = 0;
_.forEach(removeList, function (pos) {
_.forEach(removeList, function(pos) {
rethtml += html.slice(lastPos, pos[0]);
lastPos = pos[1];
});
@@ -359,8 +367,8 @@ function StripTagBody (tags, next) {
* @param {String} html
* @return {String}
*/
function stripCommentTag (html) {
return html.replace(STRIP_COMMENT_TAG_REGEXP, '');
function stripCommentTag(html) {
return html.replace(STRIP_COMMENT_TAG_REGEXP, "");
}
var STRIP_COMMENT_TAG_REGEXP = /<!--[\s\S]*?-->/g;
@@ -370,9 +378,9 @@ var STRIP_COMMENT_TAG_REGEXP = /<!--[\s\S]*?-->/g;
* @param {String} html
* @return {String}
*/
function stripBlankChar (html) {
var chars = html.split('');
chars = chars.filter(function (char) {
function stripBlankChar(html) {
var chars = html.split("");
chars = chars.filter(function(char) {
var c = char.charCodeAt(0);
if (c === 127) return false;
if (c <= 31) {
@@ -381,10 +389,9 @@ function stripBlankChar (html) {
}
return true;
});
return chars.join('');
return chars.join("");
}
exports.whiteList = getDefaultWhiteList();
exports.getDefaultWhiteList = getDefaultWhiteList;
exports.onTag = onTag;

View File

@@ -4,10 +4,9 @@
* @author Zongmin Lei<leizongmin@gmail.com>
*/
var DEFAULT = require('./default');
var parser = require('./parser');
var FilterXSS = require('./xss');
var DEFAULT = require("./default");
var parser = require("./parser");
var FilterXSS = require("./xss");
/**
* filter xss function
@@ -16,19 +15,17 @@ var FilterXSS = require('./xss');
* @param {Object} options { whiteList, onTag, onTagAttr, onIgnoreTag, onIgnoreTagAttr, safeAttrValue, escapeHtml }
* @return {String}
*/
function filterXSS (html, options) {
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];
// using `xss` on the browser, output `filterXSS` to the globals
if (typeof window !== 'undefined') {
if (typeof window !== "undefined") {
window.filterXSS = module.exports;
}

View File

@@ -4,7 +4,7 @@
* @author Zongmin Lei<leizongmin@gmail.com>
*/
var _ = require('./util');
var _ = require("./util");
/**
* get tag name
@@ -12,7 +12,7 @@ var _ = require('./util');
* @param {String} html e.g. '<a hef="#">'
* @return {String}
*/
function getTagName (html) {
function getTagName(html) {
var i = _.spaceIndex(html);
if (i === -1) {
var tagName = html.slice(1, -1);
@@ -20,8 +20,8 @@ function getTagName (html) {
var tagName = html.slice(1, i + 1);
}
tagName = _.trim(tagName).toLowerCase();
if (tagName.slice(0, 1) === '/') tagName = tagName.slice(1);
if (tagName.slice(-1) === '/') tagName = tagName.slice(0, -1);
if (tagName.slice(0, 1) === "/") tagName = tagName.slice(1);
if (tagName.slice(-1) === "/") tagName = tagName.slice(0, -1);
return tagName;
}
@@ -31,8 +31,8 @@ function getTagName (html) {
* @param {String} html 如:'<a hef="#">'
* @return {Boolean}
*/
function isClosing (html) {
return (html.slice(0, 2) === '</');
function isClosing(html) {
return html.slice(0, 2) === "</";
}
/**
@@ -43,46 +43,48 @@ function isClosing (html) {
* @param {Function} escapeHtml
* @return {String}
*/
function parseTag (html, onTag, escapeHtml) {
'user strict';
function parseTag(html, onTag, escapeHtml) {
"user strict";
var rethtml = '';
var rethtml = "";
var lastPos = 0;
var tagStart = false;
var quoteStart = false;
var currentPos = 0;
var len = html.length;
var currentTagName = '';
var currentTagName = "";
for (currentPos = 0; currentPos < len; currentPos++) {
var c = html.charAt(currentPos);
if (tagStart === false) {
if (c === '<') {
if (c === "<") {
tagStart = currentPos;
continue;
}
} else {
if (quoteStart === false) {
if (c === '<') {
if (c === "<") {
rethtml += escapeHtml(html.slice(lastPos, currentPos));
tagStart = currentPos;
lastPos = currentPos;
continue;
}
if (c === '>') {
if (c === ">") {
rethtml += escapeHtml(html.slice(lastPos, tagStart));
currentHtml = html.slice(tagStart, currentPos + 1);
currentTagName = getTagName(currentHtml);
rethtml += onTag(tagStart,
rethtml.length,
currentTagName,
currentHtml,
isClosing(currentHtml));
rethtml += onTag(
tagStart,
rethtml.length,
currentTagName,
currentHtml,
isClosing(currentHtml)
);
lastPos = currentPos + 1;
tagStart = false;
continue;
}
if ((c === '"' || c === "'") && html.charAt(currentPos - 1) === '=') {
if ((c === '"' || c === "'") && html.charAt(currentPos - 1) === "=") {
quoteStart = c;
continue;
}
@@ -101,7 +103,7 @@ function parseTag (html, onTag, escapeHtml) {
return rethtml;
}
var REGEXP_ILLEGAL_ATTR_NAME = /[^a-zA-Z0-9_:\.\-]/img;
var REGEXP_ILLEGAL_ATTR_NAME = /[^a-zA-Z0-9_:\.\-]/gim;
/**
* parse input attributes and returns processed attributes
@@ -110,33 +112,37 @@ var REGEXP_ILLEGAL_ATTR_NAME = /[^a-zA-Z0-9_:\.\-]/img;
* @param {Function} onAttr e.g. `function (name, value)`
* @return {String}
*/
function parseAttr (html, onAttr) {
'user strict';
function parseAttr(html, onAttr) {
"user strict";
var lastPos = 0;
var retAttrs = [];
var tmpName = false;
var len = html.length;
function addAttr (name, value) {
function addAttr(name, value) {
name = _.trim(name);
name = name.replace(REGEXP_ILLEGAL_ATTR_NAME, '').toLowerCase();
name = name.replace(REGEXP_ILLEGAL_ATTR_NAME, "").toLowerCase();
if (name.length < 1) return;
var ret = onAttr(name, value || '');
var ret = onAttr(name, value || "");
if (ret) retAttrs.push(ret);
};
}
// 逐个分析字符
for (var i = 0; i < len; i++) {
var c = html.charAt(i);
var v, j;
if (tmpName === false && c === '=') {
if (tmpName === false && c === "=") {
tmpName = html.slice(lastPos, i);
lastPos = i + 1;
continue;
}
if (tmpName !== false) {
if (i === lastPos && (c === '"' || c === "'") && html.charAt(i - 1) === '=') {
if (
i === lastPos &&
(c === '"' || c === "'") &&
html.charAt(i - 1) === "="
) {
j = html.indexOf(c, i + 1);
if (j === -1) {
break;
@@ -151,7 +157,7 @@ function parseAttr (html, onAttr) {
}
}
if (/\s|\n|\t/.test(c)) {
html = html.replace(/\s|\n|\t/g, ' ');
html = html.replace(/\s|\n|\t/g, " ");
if (tmpName === false) {
j = findNextEqual(html, i);
if (j === -1) {
@@ -188,44 +194,45 @@ function parseAttr (html, onAttr) {
}
}
return _.trim(retAttrs.join(' '));
return _.trim(retAttrs.join(" "));
}
function findNextEqual (str, i) {
function findNextEqual(str, i) {
for (; i < str.length; i++) {
var c = str[i];
if (c === ' ') continue;
if (c === '=') return i;
if (c === " ") continue;
if (c === "=") return i;
return -1;
}
}
function findBeforeEqual (str, i) {
function findBeforeEqual(str, i) {
for (; i > 0; i--) {
var c = str[i];
if (c === ' ') continue;
if (c === '=') return i;
if (c === " ") continue;
if (c === "=") return i;
return -1;
}
}
function isQuoteWrapString (text) {
if ((text[0] === '"' && text[text.length - 1] === '"') ||
(text[0] === '\'' && text[text.length - 1] === '\'')) {
function isQuoteWrapString(text) {
if (
(text[0] === '"' && text[text.length - 1] === '"') ||
(text[0] === "'" && text[text.length - 1] === "'")
) {
return true;
} else {
return false;
}
};
}
function stripQuoteWrap (text) {
function stripQuoteWrap(text) {
if (isQuoteWrapString(text)) {
return text.substr(1, text.length - 2);
} else {
return text;
}
};
}
exports.parseTag = parseTag;
exports.parseAttr = parseAttr;

View File

@@ -1,5 +1,5 @@
module.exports = {
indexOf: function (arr, item) {
indexOf: function(arr, item) {
var i, j;
if (Array.prototype.indexOf) {
return arr.indexOf(item);
@@ -11,7 +11,7 @@ module.exports = {
}
return -1;
},
forEach: function (arr, fn, scope) {
forEach: function(arr, fn, scope) {
var i, j;
if (Array.prototype.forEach) {
return arr.forEach(fn, scope);
@@ -20,15 +20,15 @@ module.exports = {
fn.call(scope, arr[i], i, arr);
}
},
trim: function (str) {
trim: function(str) {
if (String.prototype.trim) {
return str.trim();
}
return str.replace(/(^\s*)|(\s*$)/g, '');
return str.replace(/(^\s*)|(\s*$)/g, "");
},
spaceIndex: function (str) {
var reg = /\s|\n|\t/;
var match = reg.exec(str);
return match ? match.index : -1;
spaceIndex: function(str) {
var reg = /\s|\n|\t/;
var match = reg.exec(str);
return match ? match.index : -1;
}
};

View File

@@ -4,13 +4,12 @@
* @author Zongmin Lei<leizongmin@gmail.com>
*/
var FilterCSS = require('cssfilter').FilterCSS;
var DEFAULT = require('./default');
var parser = require('./parser');
var FilterCSS = require("cssfilter").FilterCSS;
var DEFAULT = require("./default");
var parser = require("./parser");
var parseTag = parser.parseTag;
var parseAttr = parser.parseAttr;
var _ = require('./util');
var _ = require("./util");
/**
* returns `true` if the input value is `undefined` or `null`
@@ -18,8 +17,8 @@ var _ = require('./util');
* @param {Object} obj
* @return {Boolean}
*/
function isNull (obj) {
return (obj === undefined || obj === null);
function isNull(obj) {
return obj === undefined || obj === null;
}
/**
@@ -30,19 +29,19 @@ function isNull (obj) {
* - {String} html
* - {Boolean} closing
*/
function getAttrs (html) {
function getAttrs(html) {
var i = _.spaceIndex(html);
if (i === -1) {
return {
html: '',
closing: (html[html.length - 2] === '/')
html: "",
closing: html[html.length - 2] === "/"
};
}
html = _.trim(html.slice(i + 1, -1));
var isClosing = (html[html.length - 1] === '/');
var isClosing = html[html.length - 1] === "/";
if (isClosing) html = _.trim(html.slice(0, -1));
return {
html: html,
html: html,
closing: isClosing
};
}
@@ -53,7 +52,7 @@ function getAttrs (html) {
* @param {Object} obj
* @return {Object}
*/
function shallowCopyObject (obj) {
function shallowCopyObject(obj) {
var ret = {};
for (var i in obj) {
ret[i] = obj[i];
@@ -70,12 +69,14 @@ function shallowCopyObject (obj) {
* stripIgnoreTagBody, allowCommentTag, stripBlankChar
* css{whiteList, onAttr, onIgnoreAttr} `css=false` means don't use `cssfilter`
*/
function FilterXSS (options) {
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');
console.error(
'Notes: cannot use these two options "stripIgnoreTag" and "onIgnoreTag" at the same time'
);
}
options.onIgnoreTag = DEFAULT.onIgnoreTagStripAll;
}
@@ -103,11 +104,11 @@ function FilterXSS (options) {
* @param {String} html
* @return {String}
*/
FilterXSS.prototype.process = function (html) {
FilterXSS.prototype.process = function(html) {
// compatible with the input
html = html || '';
html = html || "";
html = html.toString();
if (!html) return '';
if (!html) return "";
var me = this;
var options = me.options;
@@ -133,67 +134,71 @@ FilterXSS.prototype.process = function (html) {
// if enable stripIgnoreTagBody
var stripIgnoreTagBody = false;
if (options.stripIgnoreTagBody) {
var stripIgnoreTagBody = DEFAULT.StripTagBody(options.stripIgnoreTagBody, onIgnoreTag);
var 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: (tag in whiteList)
};
var retHtml = parseTag(
html,
function(sourcePosition, position, tag, html, isClosing) {
var info = {
sourcePosition: sourcePosition,
position: position,
isClosing: isClosing,
isWhite: tag in whiteList
};
// 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()`
var ret = onIgnoreTagAttr(tag, name, value, isWhiteAttr);
if (!isNull(ret)) return ret;
return;
}
});
// build new tag html
var html = '<' + tag;
if (attrsHtml) html += ' ' + attrsHtml;
if (attrs.closing) html += ' /';
html += '>';
return html;
} else {
// call `onIgnoreTag()`
var ret = onIgnoreTag(tag, html, info);
// call `onTag()`
var ret = onTag(tag, html, info);
if (!isNull(ret)) return ret;
return escapeHtml(html);
}
}, escapeHtml);
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()`
var ret = onIgnoreTagAttr(tag, name, value, isWhiteAttr);
if (!isNull(ret)) return ret;
return;
}
});
// build new tag html
var html = "<" + tag;
if (attrsHtml) html += " " + attrsHtml;
if (attrs.closing) html += " /";
html += ">";
return html;
} else {
// call `onIgnoreTag()`
var ret = onIgnoreTag(tag, html, info);
if (!isNull(ret)) return ret;
return escapeHtml(html);
}
},
escapeHtml
);
// if enable stripIgnoreTagBody
if (stripIgnoreTagBody) {
@@ -203,5 +208,4 @@ FilterXSS.prototype.process = function (html) {
return retHtml;
};
module.exports = FilterXSS;

View File

@@ -4,65 +4,63 @@
* @author Zongmin Lei<leizongmin@gmail.com>
*/
var assert = require('assert');
var xss = require('../');
var debug = require('debug')('xss:test');
var assert = require("assert");
var xss = require("../");
var debug = require("debug")("xss:test");
describe('test custom XSS method', function () {
it('#onTag - match tag', function () {
describe("test custom XSS method", function() {
it("#onTag - match tag", function() {
var source = 'dd<a href="#"><b><c>haha</c></b></a><br>ff';
var i = 0;
var html = xss(source, {
onTag: function (tag, html, options) {
onTag: function(tag, html, options) {
debug(arguments);
i++;
if (i === 1) {
assert.equal(tag, 'a');
assert.equal(tag, "a");
assert.equal(html, '<a href="#">');
assert.equal(options.isClosing, false);
assert.equal(options.position, 2);
assert.equal(options.sourcePosition, 2);
assert.equal(options.isWhite, true);
} else if (i === 2) {
assert.equal(tag, 'b');
assert.equal(html, '<b>');
assert.equal(tag, "b");
assert.equal(html, "<b>");
assert.equal(options.isClosing, false);
assert.equal(options.position, 14);
assert.equal(options.sourcePosition, 14);
assert.equal(options.isWhite, true);
} else if (i === 3) {
assert.equal(tag, 'c');
assert.equal(html, '<c>');
assert.equal(tag, "c");
assert.equal(html, "<c>");
assert.equal(options.isClosing, false);
assert.equal(options.position, 17);
assert.equal(options.sourcePosition, 17);
assert.equal(options.isWhite, false);
} else if (i === 4) {
assert.equal(tag, 'c');
assert.equal(html, '</c>');
assert.equal(tag, "c");
assert.equal(html, "</c>");
assert.equal(options.isClosing, true);
assert.equal(options.position, 30);
assert.equal(options.sourcePosition, 24);
assert.equal(options.isWhite, false);
} else if (i === 5) {
assert.equal(tag, 'b');
assert.equal(html, '</b>');
assert.equal(tag, "b");
assert.equal(html, "</b>");
assert.equal(options.isClosing, true);
assert.equal(options.position, 40);
assert.equal(options.sourcePosition, 28);
assert.equal(options.isWhite, true);
} else if (i === 6) {
assert.equal(tag, 'a');
assert.equal(html, '</a>');
assert.equal(tag, "a");
assert.equal(html, "</a>");
assert.equal(options.isClosing, true);
assert.equal(options.position, 44);
assert.equal(options.sourcePosition, 32);
assert.equal(options.isWhite, true);
} else if (i === 7) {
assert.equal(tag, 'br');
assert.equal(html, '<br>');
assert.equal(tag, "br");
assert.equal(html, "<br>");
assert.equal(options.isClosing, false);
assert.equal(options.position, 48);
assert.equal(options.sourcePosition, 36);
@@ -73,15 +71,17 @@ describe('test custom XSS method', function () {
}
});
debug(html);
assert.equal(html, 'dd<a href="#"><b>&lt;c&gt;haha&lt;/c&gt;</b></a><br>ff');
assert.equal(
html,
'dd<a href="#"><b>&lt;c&gt;haha&lt;/c&gt;</b></a><br>ff'
);
});
it('#onTag - return new html', function () {
it("#onTag - return new html", function() {
var source = 'dd<a href="#"><b><c>haha</c></b></a><br>ff';
var i = 0;
var html = xss(source, {
onTag: function (tag, html, options) {
onTag: function(tag, html, options) {
debug(html);
return html;
}
@@ -90,23 +90,23 @@ describe('test custom XSS method', function () {
assert.equal(html, source);
});
it('#onIgnoreTag - match tag', function () {
it("#onIgnoreTag - match tag", function() {
var source = 'dd<a href="#"><b><c>haha</c></b></a><br>ff';
var i = 0;
var html = xss(source, {
onIgnoreTag: function (tag, html, options) {
onIgnoreTag: function(tag, html, options) {
debug(arguments);
i++;
if (i === 1) {
assert.equal(tag, 'c');
assert.equal(html, '<c>');
assert.equal(tag, "c");
assert.equal(html, "<c>");
assert.equal(options.isClosing, false);
assert.equal(options.position, 17);
assert.equal(options.sourcePosition, 17);
assert.equal(options.isWhite, false);
} else if (i === 2) {
assert.equal(tag, 'c');
assert.equal(html, '</c>');
assert.equal(tag, "c");
assert.equal(html, "</c>");
assert.equal(options.isClosing, true);
assert.equal(options.position, 30);
assert.equal(options.sourcePosition, 24);
@@ -117,45 +117,52 @@ describe('test custom XSS method', function () {
}
});
debug(html);
assert.equal(html, 'dd<a href="#"><b>&lt;c&gt;haha&lt;/c&gt;</b></a><br>ff');
assert.equal(
html,
'dd<a href="#"><b>&lt;c&gt;haha&lt;/c&gt;</b></a><br>ff'
);
});
it('#onIgnoreTag - return new html', function () {
it("#onIgnoreTag - return new html", function() {
var source = 'dd<a href="#"><b><c>haha</c></b></a><br>ff';
var i = 0;
var html = xss(source, {
onIgnoreTag: function (tag, html, options) {
onIgnoreTag: function(tag, html, options) {
debug(html);
return '[' + (options.isClosing ? '/' : '') + 'removed]';
return "[" + (options.isClosing ? "/" : "") + "removed]";
}
});
debug(html);
assert.equal(html, 'dd<a href="#"><b>[removed]haha[/removed]</b></a><br>ff');
assert.equal(
html,
'dd<a href="#"><b>[removed]haha[/removed]</b></a><br>ff'
);
});
it('#onTagAttr - match attr', function () {
var source = '<a href="#" target="_blank" checked data-a="b">hi</a href="d">';
it("#onTagAttr - match attr", function() {
var source =
'<a href="#" target="_blank" checked data-a="b">hi</a href="d">';
var i = 0;
var html = xss(source, {
onTagAttr: function (tag, name, value, isWhiteAttr) {
onTagAttr: function(tag, name, value, isWhiteAttr) {
debug(arguments);
assert.equal(tag, 'a');
assert.equal(tag, "a");
i++;
if (i === 1) {
assert.equal(name, 'href');
assert.equal(value, '#');
assert.equal(name, "href");
assert.equal(value, "#");
assert.equal(isWhiteAttr, true);
} else if (i === 2) {
assert.equal(name, 'target');
assert.equal(value, '_blank');
assert.equal(name, "target");
assert.equal(value, "_blank");
assert.equal(isWhiteAttr, true);
} else if (i === 3) {
assert.equal(name, 'checked');
assert.equal(value, '');
assert.equal(name, "checked");
assert.equal(value, "");
assert.equal(isWhiteAttr, false);
} else if (i === 4) {
assert.equal(name, 'data-a');
assert.equal(value, 'b');
assert.equal(name, "data-a");
assert.equal(value, "b");
assert.equal(isWhiteAttr, false);
} else {
throw new Error();
@@ -166,34 +173,36 @@ describe('test custom XSS method', function () {
assert.equal(html, '<a href="#" target="_blank">hi</a>');
});
it('#onTagAttr - match attr', function () {
var source = '<a href="#" target="_blank" checked data-a="b">hi</a href="d">';
it("#onTagAttr - match attr", function() {
var source =
'<a href="#" target="_blank" checked data-a="b">hi</a href="d">';
var i = 0;
var html = xss(source, {
onTagAttr: function (tag, name, value, isWhiteAttr) {
onTagAttr: function(tag, name, value, isWhiteAttr) {
debug(arguments);
return '$' + name + '$';
return "$" + name + "$";
}
});
debug(html);
assert.equal(html, '<a $href$ $target$ $checked$ $data-a$>hi</a>');
assert.equal(html, "<a $href$ $target$ $checked$ $data-a$>hi</a>");
});
it('#onIgnoreTagAttr - match attr', function () {
var source = '<a href="#" target="_blank" checked data-a="b">hi</a href="d">';
it("#onIgnoreTagAttr - match attr", function() {
var source =
'<a href="#" target="_blank" checked data-a="b">hi</a href="d">';
var i = 0;
var html = xss(source, {
onIgnoreTagAttr: function (tag, name, value, isWhiteAttr) {
onIgnoreTagAttr: function(tag, name, value, isWhiteAttr) {
debug(arguments);
assert.equal(tag, 'a');
assert.equal(tag, "a");
i++;
if (i === 1) {
assert.equal(name, 'checked');
assert.equal(value, '');
assert.equal(name, "checked");
assert.equal(value, "");
assert.equal(isWhiteAttr, false);
} else if (i === 2) {
assert.equal(name, 'data-a');
assert.equal(value, 'b');
assert.equal(name, "data-a");
assert.equal(value, "b");
assert.equal(isWhiteAttr, false);
} else {
throw new Error();
@@ -204,136 +213,150 @@ describe('test custom XSS method', function () {
assert.equal(html, '<a href="#" target="_blank">hi</a>');
});
it('#onIgnoreTagAttr - match attr', function () {
var source = '<a href="#" target="_blank" checked data-a="b">hi</a href="d">';
it("#onIgnoreTagAttr - match attr", function() {
var source =
'<a href="#" target="_blank" checked data-a="b">hi</a href="d">';
var i = 0;
var html = xss(source, {
onIgnoreTagAttr: function (tag, name, value, isWhiteAttr) {
onIgnoreTagAttr: function(tag, name, value, isWhiteAttr) {
debug(arguments);
return '$' + name + '$';
return "$" + name + "$";
}
});
debug(html);
assert.equal(html, '<a href="#" target="_blank" $checked$ $data-a$>hi</a>');
});
it('#escapeHtml - default', function () {
var source = '<x>yy</x><a>bb</a>';
it("#escapeHtml - default", function() {
var source = "<x>yy</x><a>bb</a>";
var html = xss(source);
debug(html);
assert.equal(html, '&lt;x&gt;yy&lt;/x&gt;<a>bb</a>');
assert.equal(html, "&lt;x&gt;yy&lt;/x&gt;<a>bb</a>");
});
it('#escapeHtml - return new value', function () {
var source = '<x>yy</x><a>bb</a>';
it("#escapeHtml - return new value", function() {
var source = "<x>yy</x><a>bb</a>";
var html = xss(source, {
escapeHtml: function (str) {
return (str ? '[' + str + ']' : str);
escapeHtml: function(str) {
return str ? "[" + str + "]" : str;
}
});
debug(html);
assert.equal(html, '[<x>][yy][</x>]<a>[bb]</a>');
assert.equal(html, "[<x>][yy][</x>]<a>[bb]</a>");
});
it('#safeAttrValue - default', function () {
it("#safeAttrValue - default", function() {
var source = '<a href="javascript:alert(/xss/)" title="hi">link</a>';
var html = xss(source);
debug(html);
assert.equal(html, '<a href title="hi">link</a>');
});
it('#safeAttrValue - return new value', function () {
it("#safeAttrValue - return new value", function() {
var source = '<a href="javascript:alert(/xss/)" title="hi">link</a>';
var html = xss(source, {
safeAttrValue: function (tag, name, value) {
safeAttrValue: function(tag, name, value) {
debug(arguments);
assert.equal(tag, 'a');
return '$' + name + '$';
assert.equal(tag, "a");
return "$" + name + "$";
}
});
debug(html);
assert.equal(html, '<a href="$href$" title="$title$">link</a>');
});
it('#stripIgnoreTag', function () {
var source = '<x>yy</x><a>bb</a>';
it("#stripIgnoreTag", function() {
var source = "<x>yy</x><a>bb</a>";
var html = xss(source, {
stripIgnoreTag: true
});
debug(html);
assert.equal(html, 'yy<a>bb</a>');
assert.equal(html, "yy<a>bb</a>");
});
it('#stripTagBody - true', function () {
var source = '<a>link</a><x>haha</x><y>a<y></y>b</y>k';
it("#stripTagBody - true", function() {
var source = "<a>link</a><x>haha</x><y>a<y></y>b</y>k";
var html = xss(source, {
stripIgnoreTagBody: true
});
debug(html);
assert.equal(html, '<a>link</a>bk');
assert.equal(html, "<a>link</a>bk");
});
it('#stripIgnoreTagBody - *', function () {
var source = '<a>link</a><x>haha</x><y>a<y></y>b</y>k';
it("#stripIgnoreTagBody - *", function() {
var source = "<a>link</a><x>haha</x><y>a<y></y>b</y>k";
var html = xss(source, {
stripIgnoreTagBody: '*'
stripIgnoreTagBody: "*"
});
debug(html);
assert.equal(html, '<a>link</a>bk');
assert.equal(html, "<a>link</a>bk");
});
it('#stripIgnoreTagBody - [\'x\']', function () {
var source = '<a>link</a><x>haha</x><y>a<y></y>b</y>k';
it("#stripIgnoreTagBody - ['x']", function() {
var source = "<a>link</a><x>haha</x><y>a<y></y>b</y>k";
var html = xss(source, {
stripIgnoreTagBody: ['x']
stripIgnoreTagBody: ["x"]
});
debug(html);
assert.equal(html, '<a>link</a>&lt;y&gt;a&lt;y&gt;&lt;/y&gt;b&lt;/y&gt;k');
assert.equal(html, "<a>link</a>&lt;y&gt;a&lt;y&gt;&lt;/y&gt;b&lt;/y&gt;k");
});
it('#stripIgnoreTagBody - [\'x\'] & onIgnoreTag', function () {
var source = '<a>link</a><x>haha</x><y>a<y></y>b</y>k';
it("#stripIgnoreTagBody - ['x'] & onIgnoreTag", function() {
var source = "<a>link</a><x>haha</x><y>a<y></y>b</y>k";
var html = xss(source, {
stripIgnoreTagBody: ['x'],
onIgnoreTag: function (tag, html, options) {
return '$' + tag + '$';
stripIgnoreTagBody: ["x"],
onIgnoreTag: function(tag, html, options) {
return "$" + tag + "$";
}
});
debug(html);
assert.equal(html, '<a>link</a>$y$a$y$$y$b$y$k');
assert.equal(html, "<a>link</a>$y$a$y$$y$b$y$k");
});
it('#stripIgnoreTag & stripIgnoreTagBody', function () {
var source = '<scri' + 'pt>alert(/xss/);</scri' + 'pt>';
it("#stripIgnoreTag & stripIgnoreTagBody", function() {
var source = "<scri" + "pt>alert(/xss/);</scri" + "pt>";
var html = xss(source, {
stripIgnoreTag: true,
stripIgnoreTagBody: ['script']
stripIgnoreTag: true,
stripIgnoreTagBody: ["script"]
});
debug(html);
assert.equal(html, '');
assert.equal(html, "");
});
it('#stripIgnoreTag & stripIgnoreTagBody - 2', function () {
var source = 'ooxx<scri' + 'pt>alert(/xss/);</scri' + 'pt>';
it("#stripIgnoreTag & stripIgnoreTagBody - 2", function() {
var source = "ooxx<scri" + "pt>alert(/xss/);</scri" + "pt>";
var html = xss(source, {
stripIgnoreTag: true,
stripIgnoreTagBody: ['script']
stripIgnoreTag: true,
stripIgnoreTagBody: ["script"]
});
debug(html);
assert.equal(html, 'ooxx');
assert.equal(html, "ooxx");
});
it('cssFilter', function () {
it("cssFilter", function() {
var whiteList = xss.getDefaultWhiteList();
whiteList.div.push('style');
assert.equal(xss('<div style="width: 50%; vertical-align: top;">hello</div>', { whiteList: whiteList }),
'<div style="width:50%;">hello</div>');
assert.equal(xss('<div style="width: 50%; vertical-align: top;">hello</div>', { whiteList: whiteList, css: false }),
'<div style="width: 50%; vertical-align: top;">hello</div>');
whiteList.div.push("style");
assert.equal(
xss('<div style="width: 50%; vertical-align: top;">hello</div>', {
whiteList: whiteList
}),
'<div style="width:50%;">hello</div>'
);
assert.equal(
xss('<div style="width: 50%; vertical-align: top;">hello</div>', {
whiteList: whiteList,
css: false
}),
'<div style="width: 50%; vertical-align: top;">hello</div>'
);
var css = { whiteList: xss.getDefaultCSSWhiteList() };
css.whiteList['vertical-align'] = true;
assert.equal(xss('<div style="width: 50%; vertical-align: top;">hello</div>', { whiteList: whiteList, css: css }),
'<div style="width:50%; vertical-align:top;">hello</div>');
css.whiteList["vertical-align"] = true;
assert.equal(
xss('<div style="width: 50%; vertical-align: top;">hello</div>', {
whiteList: whiteList,
css: css
}),
'<div style="width:50%; vertical-align:top;">hello</div>'
);
});
});
});

View File

@@ -4,124 +4,136 @@
* @author Zongmin Lei<leizongmin@gmail.com>
*/
var assert = require('assert');
var parser = require('../lib/parser');
var assert = require("assert");
var parser = require("../lib/parser");
var parseTag = parser.parseTag;
var parseAttr = parser.parseAttr;
var debug = require('debug')('xss:test');
var debug = require("debug")("xss:test");
describe('test HTML parser', function () {
function escapeHtml (html) {
return html.replace(/</g, '&lt;').replace(/>/g, '&gt;');
describe("test HTML parser", function() {
function escapeHtml(html) {
return html.replace(/</g, "&lt;").replace(/>/g, "&gt;");
}
function attr (n, v) {
function attr(n, v) {
if (v) {
return n + '="' + v.replace(/"/g, '&quote;') + '"';
return n + '="' + v.replace(/"/g, "&quote;") + '"';
} else {
return n;
}
}
it('#parseTag', function () {
it("#parseTag", function() {
var i = 0;
var html = parseTag('hello<A href="#">www</A>ccc<b><br/>', function (sourcePosition, position, tag, html, isClosing) {
i++;
debug(arguments);
if (i === 1) {
// 第1个标签
assert.equal(sourcePosition, 5);
assert.equal(position, 5);
assert.equal(tag, 'a');
assert.equal(html, '<A href="#">');
assert.equal(isClosing, false);
return '[link]';
} else if (i === 2) {
// 第2个标签
assert.equal(sourcePosition, 20);
assert.equal(position, 14);
assert.equal(tag, 'a');
assert.equal(html, '</A>');
assert.equal(isClosing, true);
return '[/link]';
} else if (i === 3) {
// 第3个标签
assert.equal(sourcePosition, 27);
assert.equal(position, 24);
assert.equal(tag, 'b');
assert.equal(html, '<b>');
assert.equal(isClosing, false);
return '[B]';
} else if (i === 4) {
// 第4个标签
assert.equal(sourcePosition, 30);
assert.equal(position, 27);
assert.equal(tag, 'br');
assert.equal(html, '<br/>');
assert.equal(isClosing, false);
return '[BR]';
} else {
throw new Error();
}
}, escapeHtml);
var html = parseTag(
'hello<A href="#">www</A>ccc<b><br/>',
function(sourcePosition, position, tag, html, isClosing) {
i++;
debug(arguments);
if (i === 1) {
// 第1个标签
assert.equal(sourcePosition, 5);
assert.equal(position, 5);
assert.equal(tag, "a");
assert.equal(html, '<A href="#">');
assert.equal(isClosing, false);
return "[link]";
} else if (i === 2) {
// 第2个标签
assert.equal(sourcePosition, 20);
assert.equal(position, 14);
assert.equal(tag, "a");
assert.equal(html, "</A>");
assert.equal(isClosing, true);
return "[/link]";
} else if (i === 3) {
// 第3个标签
assert.equal(sourcePosition, 27);
assert.equal(position, 24);
assert.equal(tag, "b");
assert.equal(html, "<b>");
assert.equal(isClosing, false);
return "[B]";
} else if (i === 4) {
// 第4个标签
assert.equal(sourcePosition, 30);
assert.equal(position, 27);
assert.equal(tag, "br");
assert.equal(html, "<br/>");
assert.equal(isClosing, false);
return "[BR]";
} else {
throw new Error();
}
},
escapeHtml
);
debug(html);
assert.equal(html, 'hello[link]www[/link]ccc[B][BR]');
assert.equal(html, "hello[link]www[/link]ccc[B][BR]");
});
it('#parseAttr', function () {
it("#parseAttr", function() {
var i = 0;
var html = parseAttr('href="#"attr1=b attr2=c attr3 attr4=\'value4"\'attr5/', function (name, value) {
i++;
debug(arguments);
if (i === 1) {
assert.equal(name, 'href');
assert.equal(value, '#');
return attr(name, value);
} else if (i === 2) {
assert.equal(name, 'attr1');
assert.equal(value, 'b');
return attr(name, value);
} else if (i === 3) {
assert.equal(name, 'attr2');
assert.equal(value, 'c');
return attr(name, value);
} else if (i === 4) {
assert.equal(name, 'attr3');
assert.equal(value, '');
return attr(name, value);
} else if (i === 5) {
assert.equal(name, 'attr4');
assert.equal(value, 'value4"');
return attr(name, value);
} else if (i === 6) {
assert.equal(name, 'attr5');
assert.equal(value, '');
return attr(name, value);
} else {
throw new Error();
var html = parseAttr(
'href="#"attr1=b attr2=c attr3 attr4=\'value4"\'attr5/',
function(name, value) {
i++;
debug(arguments);
if (i === 1) {
assert.equal(name, "href");
assert.equal(value, "#");
return attr(name, value);
} else if (i === 2) {
assert.equal(name, "attr1");
assert.equal(value, "b");
return attr(name, value);
} else if (i === 3) {
assert.equal(name, "attr2");
assert.equal(value, "c");
return attr(name, value);
} else if (i === 4) {
assert.equal(name, "attr3");
assert.equal(value, "");
return attr(name, value);
} else if (i === 5) {
assert.equal(name, "attr4");
assert.equal(value, 'value4"');
return attr(name, value);
} else if (i === 6) {
assert.equal(name, "attr5");
assert.equal(value, "");
return attr(name, value);
} else {
throw new Error();
}
}
});
);
debug(html);
assert.equal(html, 'href="#" attr1="b" attr2="c" attr3 attr4="value4&quote;" attr5');
assert.equal(
html,
'href="#" attr1="b" attr2="c" attr3 attr4="value4&quote;" attr5'
);
});
it('#parseTag & #parseAttr', function () {
var html = parseTag('hi:<a href="#"target=_blank title="this is a link">link</a>', function (sourcePosition, position, tag, html, isClosing) {
if (tag === 'a') {
if (isClosing) return '</a>';
var attrhtml = parseAttr(html.slice(2, -1), function (name, value) {
if (name === 'href' || name === 'target') {
return attr(name, value);
}
});
return '<a ' + attrhtml + '>';
} else {
return escapeHtml(html);
}
}, escapeHtml);
it("#parseTag & #parseAttr", function() {
var html = parseTag(
'hi:<a href="#"target=_blank title="this is a link">link</a>',
function(sourcePosition, position, tag, html, isClosing) {
if (tag === "a") {
if (isClosing) return "</a>";
var attrhtml = parseAttr(html.slice(2, -1), function(name, value) {
if (name === "href" || name === "target") {
return attr(name, value);
}
});
return "<a " + attrhtml + ">";
} else {
return escapeHtml(html);
}
},
escapeHtml
);
debug(html);
assert.equal(html, 'hi:<a href="#" target="_blank">link</a>');
});
});

View File

@@ -4,251 +4,413 @@
* @author Zongmin Lei<leizongmin@gmail.com>
*/
var assert = require('assert');
var _xss = require('../');
var debug = require('debug')('xss:test');
var assert = require("assert");
var _xss = require("../");
var debug = require("debug")("xss:test");
function xss(html, options) {
debug(JSON.stringify(html));
var ret = _xss(html, options);
debug('\t' + JSON.stringify(ret));
debug("\t" + JSON.stringify(ret));
return ret;
}
describe('test XSS', function () {
it('#normal', function () {
describe("test XSS", function() {
it("#normal", function() {
// 兼容各种奇葩输入
assert.equal(xss(), '');
assert.equal(xss(null), '');
assert.equal(xss(123), '123');
assert.equal(xss({ a: 1111 }), '[object Object]');
assert.equal(xss(), "");
assert.equal(xss(null), "");
assert.equal(xss(123), "123");
assert.equal(xss({ a: 1111 }), "[object Object]");
// 清除不可见字符
assert.equal(xss('a\u0000\u0001\u0002\u0003\r\n b'), 'a\u0000\u0001\u0002\u0003\r\n b');
assert.equal(xss('a\u0000\u0001\u0002\u0003\r\n b', { stripBlankChar: true }), 'a\r\n b');
assert.equal(
xss("a\u0000\u0001\u0002\u0003\r\n b"),
"a\u0000\u0001\u0002\u0003\r\n b"
);
assert.equal(
xss("a\u0000\u0001\u0002\u0003\r\n b", { stripBlankChar: true }),
"a\r\n b"
);
// 过滤不在白名单的标签
assert.equal(xss('<b>abcd</b>'), '<b>abcd</b>');
assert.equal(xss('<o>abcd</o>'), '&lt;o&gt;abcd&lt;/o&gt;');
assert.equal(xss('<b>abcd</o>'), '<b>abcd&lt;/o&gt;');
assert.equal(xss('<b><o>abcd</b></o>'), '<b>&lt;o&gt;abcd</b>&lt;/o&gt;');
assert.equal(xss('<hr>'), '<hr>');
assert.equal(xss('<xss>'), '&lt;xss&gt;');
assert.equal(xss("<b>abcd</b>"), "<b>abcd</b>");
assert.equal(xss("<o>abcd</o>"), "&lt;o&gt;abcd&lt;/o&gt;");
assert.equal(xss("<b>abcd</o>"), "<b>abcd&lt;/o&gt;");
assert.equal(xss("<b><o>abcd</b></o>"), "<b>&lt;o&gt;abcd</b>&lt;/o&gt;");
assert.equal(xss("<hr>"), "<hr>");
assert.equal(xss("<xss>"), "&lt;xss&gt;");
assert.equal(xss('<xss o="x">'), '&lt;xss o="x"&gt;');
assert.equal(xss('<a><b>c</b></a>'), '<a><b>c</b></a>');
assert.equal(xss('<a><c>b</c></a>'), '<a>&lt;c&gt;b&lt;/c&gt;</a>');
assert.equal(xss("<a><b>c</b></a>"), "<a><b>c</b></a>");
assert.equal(xss("<a><c>b</c></a>"), "<a>&lt;c&gt;b&lt;/c&gt;</a>");
// 过滤不是标签的<>
assert.equal(xss('<>>'), '&lt;&gt;&gt;');
assert.equal(xss('<scri' + 'pt>'), '&lt;script&gt;');
assert.equal(xss('<<a>b>'), '&lt;<a>b&gt;');
assert.equal(xss('<<<a>>b</a><x>'), '&lt;&lt;<a>&gt;b</a>&lt;x&gt;');
assert.equal(xss("<>>"), "&lt;&gt;&gt;");
assert.equal(xss("<scri" + "pt>"), "&lt;script&gt;");
assert.equal(xss("<<a>b>"), "&lt;<a>b&gt;");
assert.equal(xss("<<<a>>b</a><x>"), "&lt;&lt;<a>&gt;b</a>&lt;x&gt;");
// 过滤不在白名单中的属性
assert.equal(xss('<a oo="1" xx="2" title="3">yy</a>'), '<a title="3">yy</a>');
assert.equal(xss('<a title xx oo>pp</a>'), '<a title>pp</a>');
assert.equal(xss('<a title "">pp</a>'), '<a title>pp</a>');
assert.equal(xss('<a t="">'), '<a>');
assert.equal(
xss('<a oo="1" xx="2" title="3">yy</a>'),
'<a title="3">yy</a>'
);
assert.equal(xss("<a title xx oo>pp</a>"), "<a title>pp</a>");
assert.equal(xss('<a title "">pp</a>'), "<a title>pp</a>");
assert.equal(xss('<a t="">'), "<a>");
// 属性内的特殊字符
assert.equal(xss('<a title="\'<<>>">'), '<a title="\'&lt;&lt;&gt;&gt;">');
assert.equal(xss('<a title=""">'), '<a title>');
assert.equal(xss('<a h=title="oo">'), '<a>');
assert.equal(xss('<a h= title="oo">'), '<a>');
assert.equal(xss('<a title="javascript&colonalert(/xss/)">'), '<a title="javascript:alert(/xss/)">');
assert.equal(xss('<a title"hell aa="fdfd title="ok">hello</a>'), '<a>hello</a>');
assert.equal(xss('<a title=""">'), "<a title>");
assert.equal(xss('<a h=title="oo">'), "<a>");
assert.equal(xss('<a h= title="oo">'), "<a>");
assert.equal(
xss('<a title="javascript&colonalert(/xss/)">'),
'<a title="javascript:alert(/xss/)">'
);
assert.equal(
xss('<a title"hell aa="fdfd title="ok">hello</a>'),
"<a>hello</a>"
);
// 自动将属性值的单引号转为双引号
assert.equal(xss('<a title=\'abcd\'>'), '<a title="abcd">');
assert.equal(xss('<a title=\'"\'>'), '<a title="&quot;">');
assert.equal(xss("<a title='abcd'>"), '<a title="abcd">');
assert.equal(xss("<a title='\"'>"), '<a title="&quot;">');
// 没有双引号括起来的属性值
assert.equal(xss('<a title=home>'), '<a title="home">');
assert.equal(xss("<a title=home>"), '<a title="home">');
assert.equal(xss('<a title=abc("d")>'), '<a title="abc(&quot;d&quot;)">');
assert.equal(xss('<a title=abc(\'d\')>'), '<a title="abc(\'d\')">');
assert.equal(xss("<a title=abc('d')>"), "<a title=\"abc('d')\">");
// 单个闭合标签
assert.equal(xss('<img src/>'), '<img src />');
assert.equal(xss('<img src />'), '<img src />');
assert.equal(xss('<img src//>'), '<img src />');
assert.equal(xss('<br/>'), '<br />');
assert.equal(xss('<br />'), '<br />');
assert.equal(xss("<img src/>"), "<img src />");
assert.equal(xss("<img src />"), "<img src />");
assert.equal(xss("<img src//>"), "<img src />");
assert.equal(xss("<br/>"), "<br />");
assert.equal(xss("<br />"), "<br />");
// 畸形属性格式
assert.equal(xss('<a target = "_blank" title ="bbb">'), '<a target="_blank" title="bbb">');
assert.equal(xss('<a target = "_blank" title = title = "bbb">'), '<a target="_blank" title="title">');
assert.equal(xss('<img width = 100 height =200 title="xxx">'),
'<img width="100" height="200" title="xxx">');
assert.equal(xss('<img width = 100 height =200 title=xxx>'),
'<img width="100" height="200" title="xxx">');
assert.equal(xss('<img width = 100 height =200 title= xxx>'),
'<img width="100" height="200" title="xxx">');
assert.equal(xss('<img width = 100 height =200 title= "xxx">'),
'<img width="100" height="200" title="xxx">');
assert.equal(xss('<img width = 100 height =200 title= \'xxx\'>'),
'<img width="100" height="200" title="xxx">');
assert.equal(xss('<img width = 100 height =200 title = \'xxx\'>'),
'<img width="100" height="200" title="xxx">');
assert.equal(xss('<img width = 100 height =200 title= "xxx" no=yes alt="yyy">'),
'<img width="100" height="200" title="xxx" alt="yyy">');
assert.equal(xss('<img width = 100 height =200 title= "xxx" no=yes alt="\'yyy\'">'),
'<img width="100" height="200" title="xxx" alt="\'yyy\'">');
assert.equal(
xss('<a target = "_blank" title ="bbb">'),
'<a target="_blank" title="bbb">'
);
assert.equal(
xss('<a target = "_blank" title = title = "bbb">'),
'<a target="_blank" title="title">'
);
assert.equal(
xss('<img width = 100 height =200 title="xxx">'),
'<img width="100" height="200" title="xxx">'
);
assert.equal(
xss("<img width = 100 height =200 title=xxx>"),
'<img width="100" height="200" title="xxx">'
);
assert.equal(
xss("<img width = 100 height =200 title= xxx>"),
'<img width="100" height="200" title="xxx">'
);
assert.equal(
xss('<img width = 100 height =200 title= "xxx">'),
'<img width="100" height="200" title="xxx">'
);
assert.equal(
xss("<img width = 100 height =200 title= 'xxx'>"),
'<img width="100" height="200" title="xxx">'
);
assert.equal(
xss("<img width = 100 height =200 title = 'xxx'>"),
'<img width="100" height="200" title="xxx">'
);
assert.equal(
xss('<img width = 100 height =200 title= "xxx" no=yes alt="yyy">'),
'<img width="100" height="200" title="xxx" alt="yyy">'
);
assert.equal(
xss(
'<img width = 100 height =200 title= "xxx" no=yes alt="\'yyy\'">'
),
'<img width="100" height="200" title="xxx" alt="\'yyy\'">'
);
// 使用Tab或换行符分隔的属性
assert.equal(xss('<img width=100 height=200\nsrc="#"/>'), '<img width="100" height="200" src="#" />');
assert.equal(xss('<a\ttarget="_blank"\ntitle="bbb">'), '<a target="_blank" title="bbb">');
assert.equal(xss('<a\ntarget="_blank"\ttitle="bbb">'), '<a target="_blank" title="bbb">');
assert.equal(xss('<a\n\n\n\ttarget="_blank"\t\t\t\ntitle="bbb">'), '<a target="_blank" title="bbb">');
assert.equal(
xss('<img width=100 height=200\nsrc="#"/>'),
'<img width="100" height="200" src="#" />'
);
assert.equal(
xss('<a\ttarget="_blank"\ntitle="bbb">'),
'<a target="_blank" title="bbb">'
);
assert.equal(
xss('<a\ntarget="_blank"\ttitle="bbb">'),
'<a target="_blank" title="bbb">'
);
assert.equal(
xss('<a\n\n\n\ttarget="_blank"\t\t\t\ntitle="bbb">'),
'<a target="_blank" title="bbb">'
);
});
// 自定义白名单
it('#white list', function () {
it("#white list", function() {
// 过滤所有标签
assert.equal(xss('<a title="xx">bb</a>', { whiteList: {} }), '&lt;a title="xx"&gt;bb&lt;/a&gt;');
assert.equal(xss('<hr>', { whiteList: {} }), '&lt;hr&gt;');
assert.equal(
xss('<a title="xx">bb</a>', { whiteList: {} }),
'&lt;a title="xx"&gt;bb&lt;/a&gt;'
);
assert.equal(xss("<hr>", { whiteList: {} }), "&lt;hr&gt;");
// 增加白名单标签及属性
assert.equal(xss('<ooxx yy="ok" cc="no">uu</ooxx>', { whiteList: { ooxx: ['yy'] } }), '<ooxx yy="ok">uu</ooxx>');
assert.equal(
xss('<ooxx yy="ok" cc="no">uu</ooxx>', { whiteList: { ooxx: ["yy"] } }),
'<ooxx yy="ok">uu</ooxx>'
);
});
// XSS攻击测试https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
it('#XSS_Filter_Evasion_Cheat_Sheet', function () {
assert.equal(xss('></SCRI' + 'PT>">\'><SCRI' + 'PT>alert(String.fromCharCode(88,83,83))</SCRI' + 'PT>'),
'&gt;&lt;/SCRIPT&gt;"&gt;\'&gt;&lt;SCRIPT&gt;alert(String.fromCharCode(88,83,83))&lt;/SCRIPT&gt;');
it("#XSS_Filter_Evasion_Cheat_Sheet", function() {
assert.equal(
xss(
"></SCRI" +
"PT>\">'><SCRI" +
"PT>alert(String.fromCharCode(88,83,83))</SCRI" +
"PT>"
),
"&gt;&lt;/SCRIPT&gt;\"&gt;'&gt;&lt;SCRIPT&gt;alert(String.fromCharCode(88,83,83))&lt;/SCRIPT&gt;"
);
assert.equal(xss(';!--"<XSS>=&{()}'), ';!--"&lt;XSS&gt;=&{()}');
assert.equal(xss('<SCRIPT SRC=http://ha.ckers.org/xss.js></SCRI' + 'PT>'),
'&lt;SCRIPT SRC=http://ha.ckers.org/xss.js&gt;&lt;/SCRIPT&gt;');
assert.equal(
xss("<SCRIPT SRC=http://ha.ckers.org/xss.js></SCRI" + "PT>"),
"&lt;SCRIPT SRC=http://ha.ckers.org/xss.js&gt;&lt;/SCRIPT&gt;"
);
assert.equal(xss('<IMG SRC="javascript:alert(\'XSS\');">'), '<img src>');
assert.equal(xss("<IMG SRC=\"javascript:alert('XSS');\">"), "<img src>");
assert.equal(xss('<IMG SRC=javascript:alert(\'XSS\')>'), '<img src>');
assert.equal(xss("<IMG SRC=javascript:alert('XSS')>"), "<img src>");
assert.equal(xss('<IMG SRC=JaVaScRiPt:alert(\'XSS\')>'), '<img src>');
assert.equal(xss("<IMG SRC=JaVaScRiPt:alert('XSS')>"), "<img src>");
assert.equal(xss('<IMG SRC=`javascript:alert("RSnake says, \'XSS\'")`>'), '<img src>');
assert.equal(
xss("<IMG SRC=`javascript:alert(\"RSnake says, 'XSS'\")`>"),
"<img src>"
);
assert.equal(xss('<IMG """><SCRI' + 'PT>alert("XSS")</SCRI' + 'PT>">'), '<img>&lt;SCRIPT&gt;alert("XSS")&lt;/SCRIPT&gt;"&gt;');
assert.equal(
xss('<IMG """><SCRI' + 'PT>alert("XSS")</SCRI' + 'PT>">'),
'<img>&lt;SCRIPT&gt;alert("XSS")&lt;/SCRIPT&gt;"&gt;'
);
assert.equal(xss('<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>'), '<img src>');
assert.equal(
xss("<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))>"),
"<img src>"
);
assert.equal(xss('<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>'),
'<img src>');
assert.equal(
xss(
"<IMG SRC=&#106;&#97;&#118;&#97;&#115;&#99;&#114;&#105;&#112;&#116;&#58;&#97;&#108;&#101;&#114;&#116;&#40;&#39;&#88;&#83;&#83;&#39;&#41;>"
),
"<img src>"
);
assert.equal(xss('<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>'),
'<img src>');
assert.equal(
xss(
"<IMG SRC=&#0000106&#0000097&#0000118&#0000097&#0000115&#0000099&#0000114&#0000105&#0000112&#0000116&#0000058&#0000097&#0000108&#0000101&#0000114&#0000116&#0000040&#0000039&#0000088&#0000083&#0000083&#0000039&#0000041>"
),
"<img src>"
);
assert.equal(xss('<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>'),
'<img src>');
assert.equal(
xss(
"<IMG SRC=&#x6A&#x61&#x76&#x61&#x73&#x63&#x72&#x69&#x70&#x74&#x3A&#x61&#x6C&#x65&#x72&#x74&#x28&#x27&#x58&#x53&#x53&#x27&#x29>"
),
"<img src>"
);
assert.equal(xss('<IMG SRC="jav ascript:alert(\'XSS\');">'), '<img src>');
assert.equal(xss("<IMG SRC=\"jav ascript:alert('XSS');\">"), "<img src>");
assert.equal(xss('<IMG SRC="jav&#x09;ascript:alert(\'XSS\');">'), '<img src>');
assert.equal(
xss("<IMG SRC=\"jav&#x09;ascript:alert('XSS');\">"),
"<img src>"
);
assert.equal(xss('<IMG SRC="jav\nascript:alert(\'XSS\');">'), '<img src>');
assert.equal(xss("<IMG SRC=\"jav\nascript:alert('XSS');\">"), "<img src>");
assert.equal(xss('<IMG SRC=java\0script:alert(\"XSS\")>'), '<img src>');
assert.equal(xss('<IMG SRC=java\0script:alert("XSS")>'), "<img src>");
assert.equal(xss('<IMG SRC=" &#14; javascript:alert(\'XSS\');">'), '<img src>');
assert.equal(
xss("<IMG SRC=\" &#14; javascript:alert('XSS');\">"),
"<img src>"
);
assert.equal(xss('<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRI' + 'PT>'),
'&lt;SCRIPT/XSS SRC=\"http://ha.ckers.org/xss.js\"&gt;&lt;/SCRIPT&gt;');
assert.equal(
xss('<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRI' + "PT>"),
'&lt;SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"&gt;&lt;/SCRIPT&gt;'
);
assert.equal(xss('<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")>'),
'&lt;BODY onload!#$%&()*~+-_.,:;?@[/|]^`=alert(\"XSS\")&gt;');
assert.equal(
xss('<BODY onload!#$%&()*~+-_.,:;?@[/|]^`=alert("XSS")>'),
'&lt;BODY onload!#$%&()*~+-_.,:;?@[/|]^`=alert("XSS")&gt;'
);
assert.equal(xss('<<SCRI' + 'PT>alert("XSS");//<</SCRI' + 'PT>'),
'&lt;&lt;SCRIPT&gt;alert(\"XSS\");//&lt;&lt;/SCRIPT&gt;');
assert.equal(
xss("<<SCRI" + 'PT>alert("XSS");//<</SCRI' + "PT>"),
'&lt;&lt;SCRIPT&gt;alert("XSS");//&lt;&lt;/SCRIPT&gt;'
);
assert.equal(xss('<SCRIPT SRC=http://ha.ckers.org/xss.js?< B >'),
'&lt;SCRIPT SRC=http://ha.ckers.org/xss.js?&lt; B &gt;');
assert.equal(
xss("<SCRIPT SRC=http://ha.ckers.org/xss.js?< B >"),
"&lt;SCRIPT SRC=http://ha.ckers.org/xss.js?&lt; B &gt;"
);
assert.equal(xss('<SCRIPT SRC=//ha.ckers.org/.j'),
'&lt;SCRIPT SRC=//ha.ckers.org/.j');
assert.equal(
xss("<SCRIPT SRC=//ha.ckers.org/.j"),
"&lt;SCRIPT SRC=//ha.ckers.org/.j"
);
assert.equal(xss('<ſcript src="https://xss.haozi.me/j.js"></ſcript>'),
'&lt;ſcript src="https://xss.haozi.me/j.js"&gt;&lt;/ſcript&gt;');
assert.equal(
xss('<ſcript src="https://xss.haozi.me/j.js"></ſcript>'),
'&lt;ſcript src="https://xss.haozi.me/j.js"&gt;&lt;/ſcript&gt;'
);
assert.equal(xss('<IMG SRC="javascript:alert(\'XSS\')"'),
'&lt;IMG SRC=\"javascript:alert(\'XSS\')"');
assert.equal(
xss("<IMG SRC=\"javascript:alert('XSS')\""),
"&lt;IMG SRC=\"javascript:alert('XSS')\""
);
assert.equal(xss('<iframe src=http://ha.ckers.org/scriptlet.html <'),
'&lt;iframe src=http://ha.ckers.org/scriptlet.html &lt;');
assert.equal(
xss("<iframe src=http://ha.ckers.org/scriptlet.html <"),
"&lt;iframe src=http://ha.ckers.org/scriptlet.html &lt;"
);
// 过滤 javascript:
assert.equal(xss('<a style="url(\'javascript:alert(1)\')">', { whiteList: { a: ['style'] } }), '<a style>');
assert.equal(xss('<td background="url(\'javascript:alert(1)\')">', { whiteList: { td: ['background'] } }), '<td background>');
assert.equal(
xss("<a style=\"url('javascript:alert(1)')\">", {
whiteList: { a: ["style"] }
}),
"<a style>"
);
assert.equal(
xss("<td background=\"url('javascript:alert(1)')\">", {
whiteList: { td: ["background"] }
}),
"<td background>"
);
// 过滤 style
assert.equal(xss('<DIV STYLE="width: \nexpression(alert(1));">', { whiteList: { div: ['style'] } }), '<div style>');
assert.equal(
xss('<DIV STYLE="width: \nexpression(alert(1));">', {
whiteList: { div: ["style"] }
}),
"<div style>"
);
// 不正常的url
assert.equal(xss('<DIV STYLE="background:\n url (javascript:ooxx);">', { whiteList: { div: ['style'] } }), '<div style>');
assert.equal(xss('<DIV STYLE="background:url (javascript:ooxx);">', { whiteList: { div: ['style'] } }), '<div style>');
assert.equal(
xss('<DIV STYLE="background:\n url (javascript:ooxx);">', {
whiteList: { div: ["style"] }
}),
"<div style>"
);
assert.equal(
xss('<DIV STYLE="background:url (javascript:ooxx);">', {
whiteList: { div: ["style"] }
}),
"<div style>"
);
// 正常的url
assert.equal(xss('<DIV STYLE="background: url (ooxx);">', { whiteList: { div: ['style'] } }), '<div style="background:url (ooxx);">');
assert.equal(
xss('<DIV STYLE="background: url (ooxx);">', {
whiteList: { div: ["style"] }
}),
'<div style="background:url (ooxx);">'
);
assert.equal(xss('<IMG SRC=\'vbscript:msgbox("XSS")\'>'), '<img src>');
assert.equal(xss("<IMG SRC='vbscript:msgbox(\"XSS\")'>"), "<img src>");
assert.equal(xss('<IMG SRC="livescript:[code]">'), '<img src>');
assert.equal(xss('<IMG SRC="livescript:[code]">'), "<img src>");
assert.equal(xss('<IMG SRC="mocha:[code]">'), '<img src>');
assert.equal(xss('<IMG SRC="mocha:[code]">'), "<img src>");
assert.equal(xss('<a href="javas/**/cript:alert(\'XSS\');">'), '<a href>');
assert.equal(xss("<a href=\"javas/**/cript:alert('XSS');\">"), "<a href>");
assert.equal(xss('<a href="javascript">'), '<a href>');
assert.equal(xss('<a href="javascript">'), "<a href>");
assert.equal(xss('<a href="/javascript/a">'), '<a href="/javascript/a">');
assert.equal(xss('<a href="/javascript/a">'), '<a href="/javascript/a">');
assert.equal(xss('<a href="http://aa.com">'), '<a href="http://aa.com">');
assert.equal(xss('<a href="https://aa.com">'), '<a href="https://aa.com">');
assert.equal(xss('<a href="mailto:me@ucdok.com">'), '<a href="mailto:me@ucdok.com">');
assert.equal(
xss('<a href="mailto:me@ucdok.com">'),
'<a href="mailto:me@ucdok.com">'
);
assert.equal(xss('<a href="tel:0123456789">'), '<a href="tel:0123456789">');
assert.equal(xss('<a href="#hello">'), '<a href="#hello">');
assert.equal(xss('<a href="other">'), '<a href>');
assert.equal(xss('<a href="other">'), "<a href>");
// 这个暂时不知道怎么处理
//assert.equal(xss('¼script¾alert(¢XSS¢)¼/script¾'), '');
assert.equal(xss('<!--[if gte IE 4]><SCRI' + 'PT>alert(\'XSS\');</SCRI' + 'PT><![endif]--> END', { allowCommentTag: true }),
'&lt;!--[if gte IE 4]&gt;&lt;SCRIPT&gt;alert(\'XSS\');&lt;/SCRIPT&gt;&lt;![endif]--&gt; END');
assert.equal(xss('<!--[if gte IE 4]><SCRI' + 'PT>alert(\'XSS\');</SCRI' + 'PT><![endif]--> END'), ' END');
assert.equal(
xss(
"<!--[if gte IE 4]><SCRI" +
"PT>alert('XSS');</SCRI" +
"PT><![endif]--> END",
{ allowCommentTag: true }
),
"&lt;!--[if gte IE 4]&gt;&lt;SCRIPT&gt;alert('XSS');&lt;/SCRIPT&gt;&lt;![endif]--&gt; END"
);
assert.equal(
xss(
"<!--[if gte IE 4]><SCRI" +
"PT>alert('XSS');</SCRI" +
"PT><![endif]--> END"
),
" END"
);
// HTML5新增实体编码 冒号&colon; 换行&NewLine;
assert.equal(xss('<a href="javascript&colon;alert(/xss/)">'), '<a href>');
assert.equal(xss('<a href="javascript&colonalert(/xss/)">'), '<a href>');
assert.equal(xss('<a href="a&NewLine;b">'), '<a href>');
assert.equal(xss('<a href="a&NewLineb">'), '<a href>');
assert.equal(xss('<a href="javasc&NewLine;ript&colon;alert(1)">'), '<a href>');
assert.equal(xss('<a href="javascript&colon;alert(/xss/)">'), "<a href>");
assert.equal(xss('<a href="javascript&colonalert(/xss/)">'), "<a href>");
assert.equal(xss('<a href="a&NewLine;b">'), "<a href>");
assert.equal(xss('<a href="a&NewLineb">'), "<a href>");
assert.equal(
xss('<a href="javasc&NewLine;ript&colon;alert(1)">'),
"<a href>"
);
// data URI 协议过滤
assert.equal(xss('<a href="data:">'), '<a href>');
assert.equal(xss('<a href="d a t a : ">'), '<a href>');
assert.equal(xss('<a href="data: html/text;">'), '<a href>');
assert.equal(xss('<a href="data:html/text;">'), '<a href>');
assert.equal(xss('<a href="data:html /text;">'), '<a href>');
assert.equal(xss('<a href="data: image/text;">'), '<a href>');
assert.equal(xss('<img src="data: aaa/text;">'), '<img src>');
assert.equal(xss('<img src="data:image/png; base64; ofdkofiodiofl">'), '<img src>');
assert.equal(xss('<a href="data:">'), "<a href>");
assert.equal(xss('<a href="d a t a : ">'), "<a href>");
assert.equal(xss('<a href="data: html/text;">'), "<a href>");
assert.equal(xss('<a href="data:html/text;">'), "<a href>");
assert.equal(xss('<a href="data:html /text;">'), "<a href>");
assert.equal(xss('<a href="data: image/text;">'), "<a href>");
assert.equal(xss('<img src="data: aaa/text;">'), "<img src>");
assert.equal(
xss('<img src="data:image/png; base64; ofdkofiodiofl">'),
"<img src>"
);
// HTML备注处理
assert.equal(xss('<!-- -->', { allowCommentTag: false }), '');
assert.equal(xss('<!-- a -->', { allowCommentTag: false }), '');
assert.equal(xss('<!--sa -->ss', { allowCommentTag: false }), 'ss');
assert.equal(xss('<!-- ', { allowCommentTag: false }), '&lt;!-- ');
assert.equal(
xss("<!-- -->", { allowCommentTag: false }),
""
);
assert.equal(
xss("<!-- a -->", { allowCommentTag: false }),
""
);
assert.equal(xss("<!--sa -->ss", { allowCommentTag: false }), "ss");
assert.equal(
xss("<!-- ", { allowCommentTag: false }),
"&lt;!-- "
);
});
it('no options mutated', function () {
it("no options mutated", function() {
var options = {};
var ret = xss('test', options);
var ret = xss("test", options);
// console.log(options);
assert.deepEqual(options, {});
@@ -256,5 +418,4 @@ describe('test XSS', function () {
// console.log(options);
assert.deepEqual(options, {});
});
});