summaryrefslogtreecommitdiffstats
path: root/node_modules/xss/lib
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/xss/lib')
-rw-r--r--node_modules/xss/lib/cli.js45
-rw-r--r--node_modules/xss/lib/default.js415
-rw-r--r--node_modules/xss/lib/index.js40
-rw-r--r--node_modules/xss/lib/parser.js239
-rw-r--r--node_modules/xss/lib/util.js34
-rw-r--r--node_modules/xss/lib/xss.js211
6 files changed, 984 insertions, 0 deletions
diff --git a/node_modules/xss/lib/cli.js b/node_modules/xss/lib/cli.js
new file mode 100644
index 0000000..4aa075e
--- /dev/null
+++ b/node_modules/xss/lib/cli.js
@@ -0,0 +1,45 @@
+/**
+ * command line tool
+ *
+ * @author Zongmin Lei<leizongmin@gmail.com>
+ */
+
+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 = "";
+ for (var i = 0; i < n; i++) {
+ ret += c;
+ }
+ return ret;
+}
+
+function setPrompt(line) {
+ line = line.toString();
+ 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("");
+ html = [];
+ } else {
+ html.push(line);
+ }
+ setPrompt(html.length + 1);
+});
diff --git a/node_modules/xss/lib/default.js b/node_modules/xss/lib/default.js
new file mode 100644
index 0000000..1452ed3
--- /dev/null
+++ b/node_modules/xss/lib/default.js
@@ -0,0 +1,415 @@
+/**
+ * default settings
+ *
+ * @author Zongmin Lei<leizongmin@gmail.com>
+ */
+
+var FilterCSS = require("cssfilter").FilterCSS;
+var getDefaultCSSWhiteList = require("cssfilter").getDefaultWhiteList;
+var _ = require("./util");
+
+function getDefaultWhiteList() {
+ return {
+ a: ["target", "href", "title"],
+ abbr: ["title"],
+ address: [],
+ area: ["shape", "coords", "href", "alt"],
+ article: [],
+ 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"],
+ footer: [],
+ 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: [],
+ 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"]
+ };
+}
+
+var defaultCSSFilter = new FilterCSS();
+
+/**
+ * default onTag function
+ *
+ * @param {String} tag
+ * @param {String} html
+ * @param {Object} options
+ * @return {String}
+ */
+function onTag(tag, html, options) {
+ // do nothing
+}
+
+/**
+ * default onIgnoreTag function
+ *
+ * @param {String} tag
+ * @param {String} html
+ * @param {Object} options
+ * @return {String}
+ */
+function onIgnoreTag(tag, html, options) {
+ // do nothing
+}
+
+/**
+ * default onTagAttr function
+ *
+ * @param {String} tag
+ * @param {String} name
+ * @param {String} value
+ * @return {String}
+ */
+function onTagAttr(tag, name, value) {
+ // do nothing
+}
+
+/**
+ * default onIgnoreTagAttr function
+ *
+ * @param {String} tag
+ * @param {String} name
+ * @param {String} value
+ * @return {String}
+ */
+function onIgnoreTagAttr(tag, name, value) {
+ // do nothing
+}
+
+/**
+ * default escapeHtml function
+ *
+ * @param {String} html
+ */
+function escapeHtml(html) {
+ return html.replace(REGEXP_LT, "&lt;").replace(REGEXP_GT, "&gt;");
+}
+
+/**
+ * default safeAttrValue function
+ *
+ * @param {String} tag
+ * @param {String} name
+ * @param {String} value
+ * @param {Object} cssFilter
+ * @return {String}
+ */
+function safeAttrValue(tag, name, value, cssFilter) {
+ // unescape attribute value firstly
+ value = friendlyAttrValue(value);
+
+ 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 "";
+ }
+ } 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 "";
+ }
+ } else if (name === "style") {
+ // `expression()`
+ REGEXP_DEFAULT_ON_TAG_ATTR_7.lastIndex = 0;
+ if (REGEXP_DEFAULT_ON_TAG_ATTR_7.test(value)) {
+ 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 "";
+ }
+ }
+ if (cssFilter !== false) {
+ cssFilter = cssFilter || defaultCSSFilter;
+ value = cssFilter.process(value);
+ }
+ }
+
+ // escape `<>"` before returns
+ value = escapeAttrValue(value);
+ return value;
+}
+
+// RegExp list
+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]*);?/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
+ *
+ * @param {String} str
+ * @return {String} str
+ */
+function escapeQuote(str) {
+ return str.replace(REGEXP_QUOTE, "&quot;");
+}
+
+/**
+ * unescape double quote
+ *
+ * @param {String} str
+ * @return {String} str
+ */
+function unescapeQuote(str) {
+ return str.replace(REGEXP_QUOTE_2, '"');
+}
+
+/**
+ * escape html entities
+ *
+ * @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));
+ });
+}
+
+/**
+ * escape html5 new danger entities
+ *
+ * @param {String} str
+ * @return {String}
+ */
+function escapeDangerHtml5Entities(str) {
+ return str
+ .replace(REGEXP_ATTR_VALUE_COLON, ":")
+ .replace(REGEXP_ATTR_VALUE_NEWLINE, " ");
+}
+
+/**
+ * clear nonprintable characters
+ *
+ * @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 _.trim(str2);
+}
+
+/**
+ * get friendly attribute value
+ *
+ * @param {String} str
+ * @return {String}
+ */
+function friendlyAttrValue(str) {
+ str = unescapeQuote(str);
+ str = escapeHtmlEntities(str);
+ str = escapeDangerHtml5Entities(str);
+ str = clearNonPrintableCharacter(str);
+ return str;
+}
+
+/**
+ * unescape attribute value
+ *
+ * @param {String} str
+ * @return {String}
+ */
+function escapeAttrValue(str) {
+ str = escapeQuote(str);
+ str = escapeHtml(str);
+ return str;
+}
+
+/**
+ * `onIgnoreTag` function for removing all the tags that are not in whitelist
+ */
+function onIgnoreTagStripAll() {
+ return "";
+}
+
+/**
+ * remove tag body
+ * specify a `tags` list, if the tag is not in the `tags` list then process by the specify function (optional)
+ *
+ * @param {array} tags
+ * @param {function} next
+ */
+function StripTagBody(tags, next) {
+ if (typeof next !== "function") {
+ next = function() {};
+ }
+
+ var isRemoveAllTag = !Array.isArray(tags);
+ function isRemoveTag(tag) {
+ if (isRemoveAllTag) return true;
+ return _.indexOf(tags, tag) !== -1;
+ }
+
+ var removeList = [];
+ var posStart = false;
+
+ return {
+ onIgnoreTag: function(tag, html, options) {
+ if (isRemoveTag(tag)) {
+ if (options.isClosing) {
+ var ret = "[/removed]";
+ var end = options.position + ret.length;
+ removeList.push([
+ posStart !== false ? posStart : options.position,
+ end
+ ]);
+ posStart = false;
+ return ret;
+ } else {
+ if (!posStart) {
+ posStart = options.position;
+ }
+ return "[removed]";
+ }
+ } else {
+ return next(tag, html, options);
+ }
+ },
+ remove: function(html) {
+ var rethtml = "";
+ var lastPos = 0;
+ _.forEach(removeList, function(pos) {
+ rethtml += html.slice(lastPos, pos[0]);
+ lastPos = pos[1];
+ });
+ rethtml += html.slice(lastPos);
+ return rethtml;
+ }
+ };
+}
+
+/**
+ * remove html comments
+ *
+ * @param {String} html
+ * @return {String}
+ */
+function stripCommentTag(html) {
+ return html.replace(STRIP_COMMENT_TAG_REGEXP, "");
+}
+var STRIP_COMMENT_TAG_REGEXP = /<!--[\s\S]*?-->/g;
+
+/**
+ * remove invisible characters
+ *
+ * @param {String} html
+ * @return {String}
+ */
+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) {
+ if (c === 10 || c === 13) return true;
+ return false;
+ }
+ return true;
+ });
+ return chars.join("");
+}
+
+exports.whiteList = getDefaultWhiteList();
+exports.getDefaultWhiteList = getDefaultWhiteList;
+exports.onTag = onTag;
+exports.onIgnoreTag = onIgnoreTag;
+exports.onTagAttr = onTagAttr;
+exports.onIgnoreTagAttr = onIgnoreTagAttr;
+exports.safeAttrValue = safeAttrValue;
+exports.escapeHtml = escapeHtml;
+exports.escapeQuote = escapeQuote;
+exports.unescapeQuote = unescapeQuote;
+exports.escapeHtmlEntities = escapeHtmlEntities;
+exports.escapeDangerHtml5Entities = escapeDangerHtml5Entities;
+exports.clearNonPrintableCharacter = clearNonPrintableCharacter;
+exports.friendlyAttrValue = friendlyAttrValue;
+exports.escapeAttrValue = escapeAttrValue;
+exports.onIgnoreTagStripAll = onIgnoreTagStripAll;
+exports.StripTagBody = StripTagBody;
+exports.stripCommentTag = stripCommentTag;
+exports.stripBlankChar = stripBlankChar;
+exports.cssFilter = defaultCSSFilter;
+exports.getDefaultCSSWhiteList = getDefaultCSSWhiteList;
diff --git a/node_modules/xss/lib/index.js b/node_modules/xss/lib/index.js
new file mode 100644
index 0000000..e0c1a06
--- /dev/null
+++ b/node_modules/xss/lib/index.js
@@ -0,0 +1,40 @@
+/**
+ * xss
+ *
+ * @author Zongmin Lei<leizongmin@gmail.com>
+ */
+
+var DEFAULT = require("./default");
+var parser = require("./parser");
+var FilterXSS = require("./xss");
+
+/**
+ * filter xss function
+ *
+ * @param {String} 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;
+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") {
+ window.filterXSS = module.exports;
+}
+
+// using `xss` on the WebWorker, output `filterXSS` to the globals
+function isWorkerEnv() {
+ return typeof self !== 'undefined' && typeof DedicatedWorkerGlobalScope !== 'undefined' && self instanceof DedicatedWorkerGlobalScope;
+}
+if (isWorkerEnv()) {
+ self.filterXSS = module.exports;
+}
diff --git a/node_modules/xss/lib/parser.js b/node_modules/xss/lib/parser.js
new file mode 100644
index 0000000..7c15def
--- /dev/null
+++ b/node_modules/xss/lib/parser.js
@@ -0,0 +1,239 @@
+/**
+ * Simple HTML Parser
+ *
+ * @author Zongmin Lei<leizongmin@gmail.com>
+ */
+
+var _ = require("./util");
+
+/**
+ * get tag name
+ *
+ * @param {String} html e.g. '<a hef="#">'
+ * @return {String}
+ */
+function getTagName(html) {
+ var i = _.spaceIndex(html);
+ if (i === -1) {
+ var tagName = html.slice(1, -1);
+ } else {
+ 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);
+ return tagName;
+}
+
+/**
+ * is close tag?
+ *
+ * @param {String} html 如:'<a hef="#">'
+ * @return {Boolean}
+ */
+function isClosing(html) {
+ return html.slice(0, 2) === "</";
+}
+
+/**
+ * parse input html and returns processed html
+ *
+ * @param {String} html
+ * @param {Function} onTag e.g. function (sourcePosition, position, tag, html, isClosing)
+ * @param {Function} escapeHtml
+ * @return {String}
+ */
+function parseTag(html, onTag, escapeHtml) {
+ "user strict";
+
+ var rethtml = "";
+ var lastPos = 0;
+ var tagStart = false;
+ var quoteStart = false;
+ var currentPos = 0;
+ var len = html.length;
+ var currentTagName = "";
+ var currentHtml = "";
+
+ for (currentPos = 0; currentPos < len; currentPos++) {
+ var c = html.charAt(currentPos);
+ if (tagStart === false) {
+ if (c === "<") {
+ tagStart = currentPos;
+ continue;
+ }
+ } else {
+ if (quoteStart === false) {
+ if (c === "<") {
+ rethtml += escapeHtml(html.slice(lastPos, currentPos));
+ tagStart = currentPos;
+ lastPos = currentPos;
+ continue;
+ }
+ 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)
+ );
+ lastPos = currentPos + 1;
+ tagStart = false;
+ continue;
+ }
+ if ((c === '"' || c === "'") && html.charAt(currentPos - 1) === "=") {
+ quoteStart = c;
+ continue;
+ }
+ } else {
+ if (c === quoteStart) {
+ quoteStart = false;
+ continue;
+ }
+ }
+ }
+ }
+ if (lastPos < html.length) {
+ rethtml += escapeHtml(html.substr(lastPos));
+ }
+
+ return rethtml;
+}
+
+var REGEXP_ILLEGAL_ATTR_NAME = /[^a-zA-Z0-9_:\.\-]/gim;
+
+/**
+ * parse input attributes and returns processed attributes
+ *
+ * @param {String} html e.g. `href="#" target="_blank"`
+ * @param {Function} onAttr e.g. `function (name, value)`
+ * @return {String}
+ */
+function parseAttr(html, onAttr) {
+ "user strict";
+
+ var lastPos = 0;
+ var retAttrs = [];
+ var tmpName = false;
+ var len = html.length;
+
+ function addAttr(name, value) {
+ name = _.trim(name);
+ name = name.replace(REGEXP_ILLEGAL_ATTR_NAME, "").toLowerCase();
+ if (name.length < 1) return;
+ 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 === "=") {
+ tmpName = html.slice(lastPos, i);
+ lastPos = i + 1;
+ continue;
+ }
+ if (tmpName !== false) {
+ if (
+ i === lastPos &&
+ (c === '"' || c === "'") &&
+ html.charAt(i - 1) === "="
+ ) {
+ j = html.indexOf(c, i + 1);
+ if (j === -1) {
+ break;
+ } else {
+ v = _.trim(html.slice(lastPos + 1, j));
+ addAttr(tmpName, v);
+ tmpName = false;
+ i = j;
+ lastPos = i + 1;
+ continue;
+ }
+ }
+ }
+ if (/\s|\n|\t/.test(c)) {
+ html = html.replace(/\s|\n|\t/g, " ");
+ if (tmpName === false) {
+ 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 {
+ 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;
+ }
+ }
+ }
+ }
+
+ if (lastPos < html.length) {
+ if (tmpName === false) {
+ addAttr(html.slice(lastPos));
+ } else {
+ 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;
diff --git a/node_modules/xss/lib/util.js b/node_modules/xss/lib/util.js
new file mode 100644
index 0000000..1dcd7fa
--- /dev/null
+++ b/node_modules/xss/lib/util.js
@@ -0,0 +1,34 @@
+module.exports = {
+ indexOf: function(arr, item) {
+ var i, j;
+ if (Array.prototype.indexOf) {
+ return arr.indexOf(item);
+ }
+ for (i = 0, j = arr.length; i < j; i++) {
+ if (arr[i] === item) {
+ return i;
+ }
+ }
+ return -1;
+ },
+ forEach: function(arr, fn, scope) {
+ var i, j;
+ if (Array.prototype.forEach) {
+ return arr.forEach(fn, scope);
+ }
+ for (i = 0, j = arr.length; i < j; i++) {
+ fn.call(scope, arr[i], i, arr);
+ }
+ },
+ trim: function(str) {
+ if (String.prototype.trim) {
+ return str.trim();
+ }
+ return str.replace(/(^\s*)|(\s*$)/g, "");
+ },
+ spaceIndex: function(str) {
+ var reg = /\s|\n|\t/;
+ var match = reg.exec(str);
+ return match ? match.index : -1;
+ }
+};
diff --git a/node_modules/xss/lib/xss.js b/node_modules/xss/lib/xss.js
new file mode 100644
index 0000000..74d2e42
--- /dev/null
+++ b/node_modules/xss/lib/xss.js
@@ -0,0 +1,211 @@
+/**
+ * filter xss
+ *
+ * @author Zongmin Lei<leizongmin@gmail.com>
+ */
+
+var FilterCSS = require("cssfilter").FilterCSS;
+var DEFAULT = require("./default");
+var parser = require("./parser");
+var parseTag = parser.parseTag;
+var parseAttr = parser.parseAttr;
+var _ = require("./util");
+
+/**
+ * returns `true` if the input value is `undefined` or `null`
+ *
+ * @param {Object} obj
+ * @return {Boolean}
+ */
+function isNull(obj) {
+ return obj === undefined || obj === null;
+}
+
+/**
+ * get attributes for a tag
+ *
+ * @param {String} html
+ * @return {Object}
+ * - {String} html
+ * - {Boolean} closing
+ */
+function getAttrs(html) {
+ var i = _.spaceIndex(html);
+ if (i === -1) {
+ return {
+ html: "",
+ closing: html[html.length - 2] === "/"
+ };
+ }
+ html = _.trim(html.slice(i + 1, -1));
+ var isClosing = html[html.length - 1] === "/";
+ if (isClosing) html = _.trim(html.slice(0, -1));
+ return {
+ html: html,
+ closing: isClosing
+ };
+}
+
+/**
+ * shallow copy
+ *
+ * @param {Object} obj
+ * @return {Object}
+ */
+function shallowCopyObject(obj) {
+ var ret = {};
+ for (var i in obj) {
+ ret[i] = obj[i];
+ }
+ return ret;
+}
+
+/**
+ * FilterXSS class
+ *
+ * @param {Object} options
+ * whiteList, onTag, onTagAttr, onIgnoreTag,
+ * onIgnoreTagAttr, safeAttrValue, escapeHtml
+ * stripIgnoreTagBody, allowCommentTag, stripBlankChar
+ * css{whiteList, onAttr, onIgnoreAttr} `css=false` means don't use `cssfilter`
+ */
+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'
+ );
+ }
+ options.onIgnoreTag = DEFAULT.onIgnoreTagStripAll;
+ }
+
+ 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;
+
+ if (options.css === false) {
+ this.cssFilter = false;
+ } else {
+ options.css = options.css || {};
+ this.cssFilter = new FilterCSS(options.css);
+ }
+}
+
+/**
+ * start process and returns result
+ *
+ * @param {String} html
+ * @return {String}
+ */
+FilterXSS.prototype.process = function(html) {
+ // compatible with the input
+ html = html || "";
+ html = html.toString();
+ if (!html) return "";
+
+ 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;
+
+ // remove invisible characters
+ if (options.stripBlankChar) {
+ html = DEFAULT.stripBlankChar(html);
+ }
+
+ // remove html comments
+ if (!options.allowCommentTag) {
+ html = DEFAULT.stripCommentTag(html);
+ }
+
+ // if enable stripIgnoreTagBody
+ var stripIgnoreTagBody = false;
+ if (options.stripIgnoreTagBody) {
+ 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: whiteList.hasOwnProperty(tag)
+ };
+
+ // 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);
+ if (!isNull(ret)) return ret;
+ return escapeHtml(html);
+ }
+ },
+ escapeHtml
+ );
+
+ // if enable stripIgnoreTagBody
+ if (stripIgnoreTagBody) {
+ retHtml = stripIgnoreTagBody.remove(retHtml);
+ }
+
+ return retHtml;
+};
+
+module.exports = FilterXSS;