Fixed issue #37 support unstrict HTML format: allow spaces between attribute name and attribute value
This commit is contained in:
@@ -41,7 +41,7 @@ function isClosing (html) {
|
||||
* @param {String} html
|
||||
* @param {Function} onTag 处理标签的函数
|
||||
* 参数格式: function (sourcePosition, position, tag, html, isClosing)
|
||||
* @param {Function} escapeHtml 对HTML进行转义的韩松
|
||||
* @param {Function} escapeHtml 对HTML进行转义的函数
|
||||
* @return {String}
|
||||
*/
|
||||
function parseTag (html, onTag, escapeHtml) {
|
||||
@@ -127,12 +127,14 @@ function parseAttr (html, onAttr) {
|
||||
name = _.trim(name);
|
||||
name = name.replace(REGEXP_ATTR_NAME, '').toLowerCase();
|
||||
if (name.length < 1) return;
|
||||
retAttrs.push(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),v;
|
||||
var c = html.charAt(i);
|
||||
var v, j;
|
||||
if (tmpName === false && c === '=') {
|
||||
tmpName = html.slice(lastPos, i);
|
||||
lastPos = i + 1;
|
||||
@@ -140,7 +142,7 @@ function parseAttr (html, onAttr) {
|
||||
}
|
||||
if (tmpName !== false) {
|
||||
if (i === lastPos && (c === '"' || c === "'")) {
|
||||
var j = html.indexOf(c, i + 1);
|
||||
j = html.indexOf(c, i + 1);
|
||||
if (j === -1) {
|
||||
break;
|
||||
} else {
|
||||
@@ -154,15 +156,31 @@ function parseAttr (html, onAttr) {
|
||||
}
|
||||
}
|
||||
if (c === ' ') {
|
||||
v = _.trim(html.slice(lastPos, i));
|
||||
if (tmpName === false) {
|
||||
addAttr(v);
|
||||
j = findNextEqual(html, i);
|
||||
if (j === -1) {
|
||||
v = _.trim(html.slice(lastPos, i));
|
||||
addAttr(v);
|
||||
tmpName = false;
|
||||
lastPos = i + 1;
|
||||
continue;
|
||||
} else {
|
||||
i = j - 1;
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
addAttr(tmpName, v);
|
||||
j = findBeforeEqual(html, i - 1);
|
||||
if (j === -1) {
|
||||
v = _.trim(html.slice(lastPos, i));
|
||||
v = stripQuoteWrap(v);
|
||||
addAttr(tmpName, v);
|
||||
tmpName = false;
|
||||
lastPos = i + 1;
|
||||
continue;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
tmpName = false;
|
||||
lastPos = i + 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,12 +188,54 @@ function parseAttr (html, onAttr) {
|
||||
if (tmpName === false) {
|
||||
addAttr(html.slice(lastPos));
|
||||
} else {
|
||||
addAttr(tmpName, html.slice(lastPos));
|
||||
addAttr(tmpName, stripQuoteWrap(_.trim(html.slice(lastPos))));
|
||||
}
|
||||
}
|
||||
|
||||
return _.trim(retAttrs.join(' '));
|
||||
}
|
||||
|
||||
function findNextEqual (str, i) {
|
||||
for (; i < str.length; i++) {
|
||||
var c = str[i];
|
||||
if (c === ' ') continue;
|
||||
if (c === '=') return i;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
function findBeforeEqual (str, i) {
|
||||
for (; i > 0; i--) {
|
||||
var c = str[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] === '\'')) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
function stripQuoteWrap (text) {
|
||||
if (isQuoteWrapString(text)) {
|
||||
return text.substr(1, text.length - 2);
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
exports.parseTag = parseTag;
|
||||
exports.parseAttr = parseAttr;
|
||||
|
||||
|
||||
console.log(parseAttr(' src = "#" alt ="bbb"', function (n, v) {
|
||||
console.log('%s=%s', n, v);
|
||||
return n + '=' + v;
|
||||
}));
|
||||
@@ -57,7 +57,7 @@ describe('test XSS', function () {
|
||||
assert.equal(xss('<a title="\'<<>>">'), '<a title="\'<<>>">');
|
||||
assert.equal(xss('<a title=""">'), '<a title=\"\"\">');
|
||||
assert.equal(xss('<a h=title="oo">'), '<a>');
|
||||
assert.equal(xss('<a h= title="oo">'), '<a title="oo">');
|
||||
assert.equal(xss('<a h= title="oo">'), '<a>');
|
||||
assert.equal(xss('<a title="javascript&colonalert(/xss/)">'), '<a title="javascript:alert(/xss/)">');
|
||||
|
||||
// 自动将属性值的单引号转为双引号
|
||||
@@ -76,6 +76,26 @@ describe('test XSS', function () {
|
||||
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\'">');
|
||||
|
||||
});
|
||||
|
||||
// 自定义白名单
|
||||
|
||||
Reference in New Issue
Block a user