Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/base/test/browser/domSanitize.test.ts
5272 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import * as assert from 'assert';
7
import { sanitizeHtml } from '../../browser/domSanitize.js';
8
import { Schemas } from '../../common/network.js';
9
import { ensureNoDisposablesAreLeakedInTestSuite } from '../common/utils.js';
10
11
suite('DomSanitize', () => {
12
13
ensureNoDisposablesAreLeakedInTestSuite();
14
15
test('removes unsupported tags by default', () => {
16
const html = '<div>safe<script>alert(1)</script>content</div>';
17
const result = sanitizeHtml(html);
18
const str = result.toString();
19
20
assert.ok(str.includes('<div>'));
21
assert.ok(str.includes('safe'));
22
assert.ok(str.includes('content'));
23
assert.ok(!str.includes('<script>'));
24
assert.ok(!str.includes('alert(1)'));
25
});
26
27
test('removes unsupported attributes by default', () => {
28
const html = '<div onclick="alert(1)" title="safe">content</div>';
29
const result = sanitizeHtml(html);
30
const str = result.toString();
31
32
assert.ok(str.includes('<div title="safe">'));
33
assert.ok(!str.includes('onclick'));
34
assert.ok(!str.includes('alert(1)'));
35
});
36
37
test('allows custom tags via config', () => {
38
{
39
const html = '<div>removed</div><custom-tag>hello</custom-tag>';
40
const result = sanitizeHtml(html, {
41
allowedTags: { override: ['custom-tag'] }
42
});
43
assert.strictEqual(result.toString(), 'removed<custom-tag>hello</custom-tag>');
44
}
45
{
46
const html = '<div>kept</div><augmented-tag>world</augmented-tag>';
47
const result = sanitizeHtml(html, {
48
allowedTags: { augment: ['augmented-tag'] }
49
});
50
assert.strictEqual(result.toString(), '<div>kept</div><augmented-tag>world</augmented-tag>');
51
}
52
});
53
54
test('allows custom attributes via config', () => {
55
const html = '<div custom-attr="value">content</div>';
56
const result = sanitizeHtml(html, {
57
allowedAttributes: { override: ['custom-attr'] }
58
});
59
const str = result.toString();
60
61
assert.ok(str.includes('custom-attr="value"'));
62
});
63
64
test('Attributes in config should be case insensitive', () => {
65
const html = '<div Custom-Attr="value">content</div>';
66
67
{
68
const result = sanitizeHtml(html, {
69
allowedAttributes: { override: ['custom-attr'] }
70
});
71
assert.ok(result.toString().includes('custom-attr="value"'));
72
}
73
{
74
const result = sanitizeHtml(html, {
75
allowedAttributes: { override: ['CUSTOM-ATTR'] }
76
});
77
assert.ok(result.toString().includes('custom-attr="value"'));
78
}
79
});
80
81
test('removes unsupported protocols for href by default', () => {
82
const html = '<a href="javascript:alert(1)">bad link</a>';
83
const result = sanitizeHtml(html);
84
const str = result.toString();
85
86
assert.ok(str.includes('<a>bad link</a>'));
87
assert.ok(!str.includes('javascript:'));
88
});
89
90
test('removes unsupported protocols for src by default', () => {
91
const html = '<img alt="text" src="javascript:alert(1)">';
92
const result = sanitizeHtml(html);
93
const str = result.toString();
94
95
assert.ok(str.includes('<img alt="text">'));
96
assert.ok(!str.includes('javascript:'));
97
});
98
99
test('allows safe protocols for href', () => {
100
const html = '<a href="https://example.com">safe link</a>';
101
const result = sanitizeHtml(html);
102
103
assert.ok(result.toString().includes('href="https://example.com"'));
104
});
105
106
test('allows fragment links', () => {
107
const html = '<a href="#section">fragment link</a>';
108
const result = sanitizeHtml(html);
109
const str = result.toString();
110
111
assert.ok(str.includes('href="#section"'));
112
});
113
114
test('removes data images by default', () => {
115
const html = '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==">';
116
const result = sanitizeHtml(html);
117
const str = result.toString();
118
119
assert.ok(str.includes('<img>'));
120
assert.ok(!str.includes('src="data:'));
121
});
122
123
test('allows data images when enabled', () => {
124
const html = '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==">';
125
const result = sanitizeHtml(html, {
126
allowedMediaProtocols: { override: [Schemas.data] }
127
});
128
129
assert.ok(result.toString().includes('src="data:image/png;base64,'));
130
});
131
132
test('Removes relative paths for img src by default', () => {
133
const html = '<img src="path/img.png">';
134
const result = sanitizeHtml(html);
135
assert.strictEqual(result.toString(), '<img>');
136
});
137
138
test('Can allow relative paths for image', () => {
139
const html = '<img src="path/img.png">';
140
const result = sanitizeHtml(html, {
141
allowRelativeMediaPaths: true,
142
});
143
assert.strictEqual(result.toString(), '<img src="path/img.png">');
144
});
145
146
test('Supports dynamic attribute sanitization', () => {
147
const html = '<div title="a" other="1">text1</div><div title="b" other="2">text2</div>';
148
const result = sanitizeHtml(html, {
149
allowedAttributes: {
150
override: [
151
{
152
attributeName: 'title',
153
shouldKeep: (_el, data) => {
154
return data.attrValue.includes('b');
155
}
156
}
157
]
158
}
159
});
160
assert.strictEqual(result.toString(), '<div>text1</div><div title="b">text2</div>');
161
});
162
163
test('Supports changing attributes in dynamic sanitization', () => {
164
const html = '<div title="abc" other="1">text1</div><div title="xyz" other="2">text2</div>';
165
const result = sanitizeHtml(html, {
166
allowedAttributes: {
167
override: [
168
{
169
attributeName: 'title',
170
shouldKeep: (_el, data) => {
171
if (data.attrValue === 'abc') {
172
return false;
173
}
174
return data.attrValue + data.attrValue;
175
}
176
}
177
]
178
}
179
});
180
// xyz title should be preserved and doubled
181
assert.strictEqual(result.toString(), '<div>text1</div><div title="xyzxyz">text2</div>');
182
});
183
184
test('Attr name should clear previously set dynamic sanitizer', () => {
185
const html = '<div title="abc" other="1">text1</div><div title="xyz" other="2">text2</div>';
186
const result = sanitizeHtml(html, {
187
allowedAttributes: {
188
override: [
189
{
190
attributeName: 'title',
191
shouldKeep: () => false
192
},
193
'title' // Should allow everything since it comes after custom rule
194
]
195
}
196
});
197
assert.strictEqual(result.toString(), '<div title="abc">text1</div><div title="xyz">text2</div>');
198
});
199
200
suite('replaceWithPlaintext', () => {
201
202
test('replaces unsupported tags with plaintext representation', () => {
203
const html = '<div>safe<script>alert(1)</script>content</div>';
204
const result = sanitizeHtml(html, {
205
replaceWithPlaintext: true
206
});
207
const str = result.toString();
208
assert.strictEqual(str, `<div>safe&lt;script&gt;alert(1)&lt;/script&gt;content</div>`);
209
});
210
211
test('handles self-closing tags correctly', () => {
212
const html = '<div><input type="text"><custom-input /></div>';
213
const result = sanitizeHtml(html, {
214
replaceWithPlaintext: true
215
});
216
assert.strictEqual(result.toString(), '<div>&lt;input type="text"&gt;&lt;custom-input&gt;&lt;/custom-input&gt;</div>');
217
});
218
219
test('handles tags with attributes', () => {
220
const html = '<div><unknown-tag class="test" id="myid">content</unknown-tag></div>';
221
const result = sanitizeHtml(html, {
222
replaceWithPlaintext: true
223
});
224
assert.strictEqual(result.toString(), '<div>&lt;unknown-tag class="test" id="myid"&gt;content&lt;/unknown-tag&gt;</div>');
225
});
226
227
test('handles nested unsupported tags', () => {
228
const html = '<div><outer><inner>nested</inner></outer></div>';
229
const result = sanitizeHtml(html, {
230
replaceWithPlaintext: true
231
});
232
assert.strictEqual(result.toString(), '<div>&lt;outer&gt;&lt;inner&gt;nested&lt;/inner&gt;&lt;/outer&gt;</div>');
233
});
234
235
test('handles comments correctly', () => {
236
const html = '<div><!-- this is a comment -->content</div>';
237
const result = sanitizeHtml(html, {
238
replaceWithPlaintext: true
239
});
240
assert.strictEqual(result.toString(), '<div>&lt;!-- this is a comment --&gt;content</div>');
241
});
242
243
test('handles empty tags', () => {
244
const html = '<div><empty></empty></div>';
245
const result = sanitizeHtml(html, {
246
replaceWithPlaintext: true
247
});
248
assert.strictEqual(result.toString(), '<div>&lt;empty&gt;&lt;/empty&gt;</div>');
249
});
250
251
test('works with custom allowed tags configuration', () => {
252
const html = '<div><custom>allowed</custom><forbidden>not allowed</forbidden></div>';
253
const result = sanitizeHtml(html, {
254
replaceWithPlaintext: true,
255
allowedTags: { augment: ['custom'] }
256
});
257
assert.strictEqual(result.toString(), '<div><custom>allowed</custom>&lt;forbidden&gt;not allowed&lt;/forbidden&gt;</div>');
258
});
259
});
260
});
261
262