1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 | var FilterCSS = require("cssfilter").FilterCSS;
|
8 | var DEFAULT = require("./default");
|
9 | var parser = require("./parser");
|
10 | var parseTag = parser.parseTag;
|
11 | var parseAttr = parser.parseAttr;
|
12 | var _ = require("./util");
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 | function isNull(obj) {
|
21 | return obj === undefined || obj === null;
|
22 | }
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 | function getAttrs(html) {
|
33 | var i = _.spaceIndex(html);
|
34 | if (i === -1) {
|
35 | return {
|
36 | html: "",
|
37 | closing: html[html.length - 2] === "/",
|
38 | };
|
39 | }
|
40 | html = _.trim(html.slice(i + 1, -1));
|
41 | var isClosing = html[html.length - 1] === "/";
|
42 | if (isClosing) html = _.trim(html.slice(0, -1));
|
43 | return {
|
44 | html: html,
|
45 | closing: isClosing,
|
46 | };
|
47 | }
|
48 |
|
49 |
|
50 |
|
51 |
|
52 |
|
53 |
|
54 |
|
55 | function shallowCopyObject(obj) {
|
56 | var ret = {};
|
57 | for (var i in obj) {
|
58 | ret[i] = obj[i];
|
59 | }
|
60 | return ret;
|
61 | }
|
62 |
|
63 | function keysToLowerCase(obj) {
|
64 | var ret = {};
|
65 | for (var i in obj) {
|
66 | if (Array.isArray(obj[i])) {
|
67 | ret[i.toLowerCase()] = obj[i].map(function (item) {
|
68 | return item.toLowerCase();
|
69 | });
|
70 | } else {
|
71 | ret[i.toLowerCase()] = obj[i];
|
72 | }
|
73 | }
|
74 | return ret;
|
75 | }
|
76 |
|
77 |
|
78 |
|
79 |
|
80 |
|
81 |
|
82 |
|
83 |
|
84 |
|
85 |
|
86 | function FilterXSS(options) {
|
87 | options = shallowCopyObject(options || {});
|
88 |
|
89 | if (options.stripIgnoreTag) {
|
90 | if (options.onIgnoreTag) {
|
91 | console.error(
|
92 | 'Notes: cannot use these two options "stripIgnoreTag" and "onIgnoreTag" at the same time'
|
93 | );
|
94 | }
|
95 | options.onIgnoreTag = DEFAULT.onIgnoreTagStripAll;
|
96 | }
|
97 | if (options.whiteList || options.allowList) {
|
98 | options.whiteList = keysToLowerCase(options.whiteList || options.allowList);
|
99 | } else {
|
100 | options.whiteList = DEFAULT.whiteList;
|
101 | }
|
102 |
|
103 | this.attributeWrapSign = options.singleQuotedAttributeValue === true ? "'" : DEFAULT.attributeWrapSign;
|
104 |
|
105 | options.onTag = options.onTag || DEFAULT.onTag;
|
106 | options.onTagAttr = options.onTagAttr || DEFAULT.onTagAttr;
|
107 | options.onIgnoreTag = options.onIgnoreTag || DEFAULT.onIgnoreTag;
|
108 | options.onIgnoreTagAttr = options.onIgnoreTagAttr || DEFAULT.onIgnoreTagAttr;
|
109 | options.safeAttrValue = options.safeAttrValue || DEFAULT.safeAttrValue;
|
110 | options.escapeHtml = options.escapeHtml || DEFAULT.escapeHtml;
|
111 | this.options = options;
|
112 |
|
113 | if (options.css === false) {
|
114 | this.cssFilter = false;
|
115 | } else {
|
116 | options.css = options.css || {};
|
117 | this.cssFilter = new FilterCSS(options.css);
|
118 | }
|
119 | }
|
120 |
|
121 |
|
122 |
|
123 |
|
124 |
|
125 |
|
126 |
|
127 | FilterXSS.prototype.process = function (html) {
|
128 |
|
129 | html = html || "";
|
130 | html = html.toString();
|
131 | if (!html) return "";
|
132 |
|
133 | var me = this;
|
134 | var options = me.options;
|
135 | var whiteList = options.whiteList;
|
136 | var onTag = options.onTag;
|
137 | var onIgnoreTag = options.onIgnoreTag;
|
138 | var onTagAttr = options.onTagAttr;
|
139 | var onIgnoreTagAttr = options.onIgnoreTagAttr;
|
140 | var safeAttrValue = options.safeAttrValue;
|
141 | var escapeHtml = options.escapeHtml;
|
142 | var attributeWrapSign = me.attributeWrapSign;
|
143 | var cssFilter = me.cssFilter;
|
144 |
|
145 |
|
146 | if (options.stripBlankChar) {
|
147 | html = DEFAULT.stripBlankChar(html);
|
148 | }
|
149 |
|
150 |
|
151 | if (!options.allowCommentTag) {
|
152 | html = DEFAULT.stripCommentTag(html);
|
153 | }
|
154 |
|
155 |
|
156 | var stripIgnoreTagBody = false;
|
157 | if (options.stripIgnoreTagBody) {
|
158 | stripIgnoreTagBody = DEFAULT.StripTagBody(
|
159 | options.stripIgnoreTagBody,
|
160 | onIgnoreTag
|
161 | );
|
162 | onIgnoreTag = stripIgnoreTagBody.onIgnoreTag;
|
163 | }
|
164 |
|
165 | var retHtml = parseTag(
|
166 | html,
|
167 | function (sourcePosition, position, tag, html, isClosing) {
|
168 | var info = {
|
169 | sourcePosition: sourcePosition,
|
170 | position: position,
|
171 | isClosing: isClosing,
|
172 | isWhite: Object.prototype.hasOwnProperty.call(whiteList, tag),
|
173 | };
|
174 |
|
175 |
|
176 | var ret = onTag(tag, html, info);
|
177 | if (!isNull(ret)) return ret;
|
178 |
|
179 | if (info.isWhite) {
|
180 | if (info.isClosing) {
|
181 | return "</" + tag + ">";
|
182 | }
|
183 |
|
184 | var attrs = getAttrs(html);
|
185 | var whiteAttrList = whiteList[tag];
|
186 | var attrsHtml = parseAttr(attrs.html, function (name, value) {
|
187 |
|
188 | var isWhiteAttr = _.indexOf(whiteAttrList, name) !== -1;
|
189 | var ret = onTagAttr(tag, name, value, isWhiteAttr);
|
190 | if (!isNull(ret)) return ret;
|
191 |
|
192 | if (isWhiteAttr) {
|
193 |
|
194 | value = safeAttrValue(tag, name, value, cssFilter);
|
195 | if (value) {
|
196 | return name + '=' + attributeWrapSign + value + attributeWrapSign;
|
197 | } else {
|
198 | return name;
|
199 | }
|
200 | } else {
|
201 |
|
202 | ret = onIgnoreTagAttr(tag, name, value, isWhiteAttr);
|
203 | if (!isNull(ret)) return ret;
|
204 | return;
|
205 | }
|
206 | });
|
207 |
|
208 |
|
209 | html = "<" + tag;
|
210 | if (attrsHtml) html += " " + attrsHtml;
|
211 | if (attrs.closing) html += " /";
|
212 | html += ">";
|
213 | return html;
|
214 | } else {
|
215 |
|
216 | ret = onIgnoreTag(tag, html, info);
|
217 | if (!isNull(ret)) return ret;
|
218 | return escapeHtml(html);
|
219 | }
|
220 | },
|
221 | escapeHtml
|
222 | );
|
223 |
|
224 |
|
225 | if (stripIgnoreTagBody) {
|
226 | retHtml = stripIgnoreTagBody.remove(retHtml);
|
227 | }
|
228 |
|
229 | return retHtml;
|
230 | };
|
231 |
|
232 | module.exports = FilterXSS;
|