/** * 默认配置 * * @author 老雷 */ // 默认白名单 var whiteList = { h1: [], h2: [], h3: [], h4: [], h5: [], h6: [], hr: [], span: [], strong: [], b: [], i: [], br: [], p: [], pre: [], code: [], a: ['target', 'href', 'title'], img: ['src', 'alt', 'title', 'width', 'height'], div: [], table: ['width', 'border'], tr: ['rowspan'], td: ['width', 'colspan'], th: ['width', 'colspan'], tbody: [], thead: [], ul: [], li: [], ol: [], dl: [], dt: [], em: [], cite: [], section:[], header: [], footer: [], blockquote: [], audio: ['autoplay', 'controls', 'loop', 'preload', 'src'], video: ['autoplay', 'controls', 'loop', 'preload', 'src', 'height', 'width'] }; /** * 匹配到标签时的处理方法 * * @param {String} tag * @param {String} html * @param {Object} options * @return {String} */ function onTag (tag, html, options) { } /** * 匹配到不在白名单上的标签时的处理方法 * * @param {String} tag * @param {String} html * @param {Object} options * @return {String} */ function onIgnoreTag (tag, html, options) { } /** * 匹配到标签属性时的处理方法 * * @param {String} tag * @param {String} name * @param {String} value * @return {String} */ function onTagAttr (tag, name, value) { } /** * 匹配到不在白名单上的标签属性时的处理方法 * * @param {String} tag * @param {String} name * @param {String} value * @return {String} */ function onIgnoreTagAttr (tag, name, value) { } /** * HTML转义 * * @param {String} html */ function escapeHtml (html) { return html.replace(REGEXP_LT, '<').replace(REGEXP_GT, '>'); } /** * 安全的标签属性值 * * @param {String} tag * @param {String} name * @param {String} value * @return {String} */ function safeAttrValue (tag, name, value) { // 转换为友好的属性值,再做判断 value = friendlyAttrValue(value); if (name === 'href' || name === 'src') { // 过滤 href 和 src 属性 // javascript: REGEXP_DEFAULT_ON_TAG_ATTR_1.lastIndex = 0; if (REGEXP_DEFAULT_ON_TAG_ATTR_1.test(value)) { return '#'; } // /*注释*/ REGEXP_DEFAULT_ON_TAG_ATTR_2.lastIndex = 0; if (REGEXP_DEFAULT_ON_TAG_ATTR_2.test(value)) { return '#'; } // data: REGEXP_DEFAULT_ON_TAG_ATTR_5.lastIndex = 0; if (REGEXP_DEFAULT_ON_TAG_ATTR_5.test(value)) { // 允许 data: image/* 类型 REGEXP_DEFAULT_ON_TAG_ATTR_6.lastIndex = 0; if (!REGEXP_DEFAULT_ON_TAG_ATTR_6.test(value)) { return '#'; } } } else if (name === 'style') { // 过滤 style 属性 (这个xss漏洞较老了,可能已经不适用) // javascript: REGEXP_DEFAULT_ON_TAG_ATTR_3.lastIndex = 0; if (REGEXP_DEFAULT_ON_TAG_ATTR_3.test(value)) { return '#'; } // /*注释*/ REGEXP_DEFAULT_ON_TAG_ATTR_4.lastIndex = 0; if (REGEXP_DEFAULT_ON_TAG_ATTR_4.test(value)) { return ''; } } // 输出时需要转义<>" value = escapeAttrValue(value); return value; } // 正则表达式 var REGEXP_LT = //g; var REGEXP_QUOTE = /"/g; var REGEXP_QUOTE_2 = /"/g; var REGEXP_ATTR_VALUE_1 = /&#([a-zA-Z0-9]*);?/img; var REGEXP_ATTR_VALUE_COLON = /:?/img; var REGEXP_ATTR_VALUE_NEWLINE = /&newline;?/img; var REGEXP_DEFAULT_ON_TAG_ATTR_1 = /\/\*|\*\//mg; var REGEXP_DEFAULT_ON_TAG_ATTR_2 = /^[\s"'`]*((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_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; /** * 对双引号进行转义 * * @param {String} str * @return {String} str */ function escapeQuote (str) { return str.replace(REGEXP_QUOTE, '"e;'); } /** * 对双引号进行转义 * * @param {String} str * @return {String} str */ function unescapeQuote (str) { return str.replace(REGEXP_QUOTE_2, '"'); } /** * 对html实体编码进行转义 * * @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)); }); } /** * 对html5新增的危险实体编码进行转义 * * @param {String} str * @return {String} */ function escapeDangerHtml5Entities (str) { return str.replace(REGEXP_ATTR_VALUE_COLON, ':') .replace(REGEXP_ATTR_VALUE_NEWLINE, ' '); } /** * 清除不可见字符 * * @param {String} str * @return {String} */ function clearNonPrintableCharacter (str) { var str2 = ''; for (var i = 0, len = str.length; i < len; i++) { str2 += str.charCodeAt(i) < 32 ? ' ' : str.charAt(i); } return str2.trim(); } /** * 将标签的属性值转换成一般字符,便于分析 * * @param {String} str * @return {String} */ function friendlyAttrValue (str) { str = unescapeQuote(str); // 双引号 str = escapeHtmlEntities(str); // 转换HTML实体编码 str = escapeDangerHtml5Entities(str); // 转换危险的HTML5新增实体编码 str = clearNonPrintableCharacter(str); // 清除不可见字符 return str; } /** * 转义用于输出的标签属性值 * * @param {String} str * @return {String} */ function escapeAttrValue (str) { str = escapeQuote(str); str = escapeHtml(str); return str; } exports.whiteList = whiteList; exports.onTag = onTag; exports.onIgnoreTag = onIgnoreTag; exports.onTagAttr = onTagAttr; exports.onIgnoreTagAttr = onIgnoreTagAttr; exports.safeAttrValue = safeAttrValue; exports.escape = escapeHtml; exports.escapeQuote = escapeQuote; exports.unescapeQuote = unescapeQuote; exports.escapeHtmlEntities = escapeHtmlEntities; exports.escapeDangerHtml5Entities = escapeDangerHtml5Entities; exports.clearNonPrintableCharacter = clearNonPrintableCharacter; exports.friendlyAttrValue = friendlyAttrValue; exports.escapeAttrValue = escapeAttrValue;