-
Notifications
You must be signed in to change notification settings - Fork 0
/
index.js
116 lines (94 loc) · 2.98 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
const _assign = require('lodash.assign');
const _escape = require('lodash.escape');
const url = require('url');
const defaultOptions = {
allowedTags: [
'br', 'hr',
'div', 'p',
'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
'blockquote',
'pre', 'code',
'ul', 'ol', 'nl', 'li',
'table', 'thead', 'tbody', 'tr', 'th', 'td',
'b', 'strong', 'i', 'em', 'strike',
'a'
],
allowedAttrs: {
'a': ['href', 'target'],
},
allowedProtocols: {
'*': ['http', 'https', 'ftp', 'mailto']
},
allowNullProtocol: true
};
const htmlTagRegExp = /<\s*(\/?)\s*([a-z][a-z0-9]*)\b(.*)>/gi;
const htmlAttrsRegExp = /([a-z]+)(?:=(?:"(.+?)"|'(.+?)'))?\s*/gi;
function escapeHtml(text, options) {
options = _assign(defaultOptions, options);
return text.replace(htmlTagRegExp, function(match, isClosing, tagName, tagAttrs) {
if (options.allowedTags.indexOf(tagName) < 0) {
return _escape(match);
} else {
return escapeTag((isClosing === '/'), tagName, tagAttrs, options);
}
});
}
function escapeTag(isClosing, tagName, tagAttrs, options) {
let _tagAttrs = '';
if (!isClosing) {
let match = null;
while ((match = htmlAttrsRegExp.exec(tagAttrs)) !== null) {
const attrName = match[1];
let attrValue = (match[2] || match[3]);
if (!isAllowed(tagName, attrName, options.allowedAttrs)) {
continue;
}
if (attrValue) {
attrValue = trimQuotes(attrValue);
if (attrName === 'href') {
attrValue = escapeHref(tagName, attrValue, options);
} else {
attrValue = _escape(attrValue);
}
}
_tagAttrs += (' ' + attrName);
_tagAttrs += (attrValue ? ('="' + attrValue + '"') : '');
}
}
return '<' + (isClosing ? '/' : '') + tagName + _tagAttrs + '>';
}
function isAllowed(tagName, value, whitelist) {
if (whitelist[tagName] && whitelist[tagName].indexOf(value) >= 0) {
return true;
}
if (whitelist['*'] && whitelist['*'].indexOf(value) >= 0) {
return true;
}
return false;
}
function trimQuotes(text) {
if ((text.startsWith('"') && text.endsWith('"')) ||
(text.startsWith("'") && text.endsWith("'"))) {
return text.substring(1, text.length - 1);
} else {
return text;
}
}
function escapeHref(tagName, href, options) {
const parsed = url.parse(href);
let protocol = parsed.protocol;
href = parsed.href;
if (protocol === null) {
if (!options.allowNullProtocol) {
return '';
}
} else {
protocol = protocol.substr(0, protocol.length - 1);
if (!isAllowed(tagName, protocol, options.allowedProtocols)) {
return '';
}
}
return href;
}
module.exports = escapeHtml;
module.exports.defaultOptions = defaultOptions;