Path: blob/main/src/vs/base/test/browser/domSanitize.test.ts
5272 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as assert from 'assert';6import { sanitizeHtml } from '../../browser/domSanitize.js';7import { Schemas } from '../../common/network.js';8import { ensureNoDisposablesAreLeakedInTestSuite } from '../common/utils.js';910suite('DomSanitize', () => {1112ensureNoDisposablesAreLeakedInTestSuite();1314test('removes unsupported tags by default', () => {15const html = '<div>safe<script>alert(1)</script>content</div>';16const result = sanitizeHtml(html);17const str = result.toString();1819assert.ok(str.includes('<div>'));20assert.ok(str.includes('safe'));21assert.ok(str.includes('content'));22assert.ok(!str.includes('<script>'));23assert.ok(!str.includes('alert(1)'));24});2526test('removes unsupported attributes by default', () => {27const html = '<div onclick="alert(1)" title="safe">content</div>';28const result = sanitizeHtml(html);29const str = result.toString();3031assert.ok(str.includes('<div title="safe">'));32assert.ok(!str.includes('onclick'));33assert.ok(!str.includes('alert(1)'));34});3536test('allows custom tags via config', () => {37{38const html = '<div>removed</div><custom-tag>hello</custom-tag>';39const result = sanitizeHtml(html, {40allowedTags: { override: ['custom-tag'] }41});42assert.strictEqual(result.toString(), 'removed<custom-tag>hello</custom-tag>');43}44{45const html = '<div>kept</div><augmented-tag>world</augmented-tag>';46const result = sanitizeHtml(html, {47allowedTags: { augment: ['augmented-tag'] }48});49assert.strictEqual(result.toString(), '<div>kept</div><augmented-tag>world</augmented-tag>');50}51});5253test('allows custom attributes via config', () => {54const html = '<div custom-attr="value">content</div>';55const result = sanitizeHtml(html, {56allowedAttributes: { override: ['custom-attr'] }57});58const str = result.toString();5960assert.ok(str.includes('custom-attr="value"'));61});6263test('Attributes in config should be case insensitive', () => {64const html = '<div Custom-Attr="value">content</div>';6566{67const result = sanitizeHtml(html, {68allowedAttributes: { override: ['custom-attr'] }69});70assert.ok(result.toString().includes('custom-attr="value"'));71}72{73const result = sanitizeHtml(html, {74allowedAttributes: { override: ['CUSTOM-ATTR'] }75});76assert.ok(result.toString().includes('custom-attr="value"'));77}78});7980test('removes unsupported protocols for href by default', () => {81const html = '<a href="javascript:alert(1)">bad link</a>';82const result = sanitizeHtml(html);83const str = result.toString();8485assert.ok(str.includes('<a>bad link</a>'));86assert.ok(!str.includes('javascript:'));87});8889test('removes unsupported protocols for src by default', () => {90const html = '<img alt="text" src="javascript:alert(1)">';91const result = sanitizeHtml(html);92const str = result.toString();9394assert.ok(str.includes('<img alt="text">'));95assert.ok(!str.includes('javascript:'));96});9798test('allows safe protocols for href', () => {99const html = '<a href="https://example.com">safe link</a>';100const result = sanitizeHtml(html);101102assert.ok(result.toString().includes('href="https://example.com"'));103});104105test('allows fragment links', () => {106const html = '<a href="#section">fragment link</a>';107const result = sanitizeHtml(html);108const str = result.toString();109110assert.ok(str.includes('href="#section"'));111});112113test('removes data images by default', () => {114const html = '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==">';115const result = sanitizeHtml(html);116const str = result.toString();117118assert.ok(str.includes('<img>'));119assert.ok(!str.includes('src="data:'));120});121122test('allows data images when enabled', () => {123const html = '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==">';124const result = sanitizeHtml(html, {125allowedMediaProtocols: { override: [Schemas.data] }126});127128assert.ok(result.toString().includes('src="data:image/png;base64,'));129});130131test('Removes relative paths for img src by default', () => {132const html = '<img src="path/img.png">';133const result = sanitizeHtml(html);134assert.strictEqual(result.toString(), '<img>');135});136137test('Can allow relative paths for image', () => {138const html = '<img src="path/img.png">';139const result = sanitizeHtml(html, {140allowRelativeMediaPaths: true,141});142assert.strictEqual(result.toString(), '<img src="path/img.png">');143});144145test('Supports dynamic attribute sanitization', () => {146const html = '<div title="a" other="1">text1</div><div title="b" other="2">text2</div>';147const result = sanitizeHtml(html, {148allowedAttributes: {149override: [150{151attributeName: 'title',152shouldKeep: (_el, data) => {153return data.attrValue.includes('b');154}155}156]157}158});159assert.strictEqual(result.toString(), '<div>text1</div><div title="b">text2</div>');160});161162test('Supports changing attributes in dynamic sanitization', () => {163const html = '<div title="abc" other="1">text1</div><div title="xyz" other="2">text2</div>';164const result = sanitizeHtml(html, {165allowedAttributes: {166override: [167{168attributeName: 'title',169shouldKeep: (_el, data) => {170if (data.attrValue === 'abc') {171return false;172}173return data.attrValue + data.attrValue;174}175}176]177}178});179// xyz title should be preserved and doubled180assert.strictEqual(result.toString(), '<div>text1</div><div title="xyzxyz">text2</div>');181});182183test('Attr name should clear previously set dynamic sanitizer', () => {184const html = '<div title="abc" other="1">text1</div><div title="xyz" other="2">text2</div>';185const result = sanitizeHtml(html, {186allowedAttributes: {187override: [188{189attributeName: 'title',190shouldKeep: () => false191},192'title' // Should allow everything since it comes after custom rule193]194}195});196assert.strictEqual(result.toString(), '<div title="abc">text1</div><div title="xyz">text2</div>');197});198199suite('replaceWithPlaintext', () => {200201test('replaces unsupported tags with plaintext representation', () => {202const html = '<div>safe<script>alert(1)</script>content</div>';203const result = sanitizeHtml(html, {204replaceWithPlaintext: true205});206const str = result.toString();207assert.strictEqual(str, `<div>safe<script>alert(1)</script>content</div>`);208});209210test('handles self-closing tags correctly', () => {211const html = '<div><input type="text"><custom-input /></div>';212const result = sanitizeHtml(html, {213replaceWithPlaintext: true214});215assert.strictEqual(result.toString(), '<div><input type="text"><custom-input></custom-input></div>');216});217218test('handles tags with attributes', () => {219const html = '<div><unknown-tag class="test" id="myid">content</unknown-tag></div>';220const result = sanitizeHtml(html, {221replaceWithPlaintext: true222});223assert.strictEqual(result.toString(), '<div><unknown-tag class="test" id="myid">content</unknown-tag></div>');224});225226test('handles nested unsupported tags', () => {227const html = '<div><outer><inner>nested</inner></outer></div>';228const result = sanitizeHtml(html, {229replaceWithPlaintext: true230});231assert.strictEqual(result.toString(), '<div><outer><inner>nested</inner></outer></div>');232});233234test('handles comments correctly', () => {235const html = '<div><!-- this is a comment -->content</div>';236const result = sanitizeHtml(html, {237replaceWithPlaintext: true238});239assert.strictEqual(result.toString(), '<div><!-- this is a comment -->content</div>');240});241242test('handles empty tags', () => {243const html = '<div><empty></empty></div>';244const result = sanitizeHtml(html, {245replaceWithPlaintext: true246});247assert.strictEqual(result.toString(), '<div><empty></empty></div>');248});249250test('works with custom allowed tags configuration', () => {251const html = '<div><custom>allowed</custom><forbidden>not allowed</forbidden></div>';252const result = sanitizeHtml(html, {253replaceWithPlaintext: true,254allowedTags: { augment: ['custom'] }255});256assert.strictEqual(result.toString(), '<div><custom>allowed</custom><forbidden>not allowed</forbidden></div>');257});258});259});260261262