Path: blob/main/src/vs/base/test/browser/domSanitize.test.ts
3296 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);101const str = result.toString();102103assert.ok(str.includes('href="https://example.com"'));104});105106test('allows fragment links', () => {107const html = '<a href="#section">fragment link</a>';108const result = sanitizeHtml(html);109const str = result.toString();110111assert.ok(str.includes('href="#section"'));112});113114test('removes data images by default', () => {115const html = '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==">';116const result = sanitizeHtml(html);117const str = result.toString();118119assert.ok(str.includes('<img>'));120assert.ok(!str.includes('src="data:'));121});122123test('allows data images when enabled', () => {124const html = '<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg==">';125const result = sanitizeHtml(html, {126allowedMediaProtocols: { override: [Schemas.data] }127});128const str = result.toString();129130assert.ok(str.includes('src="data:image/png;base64,'));131});132133test('Supports dynamic attribute sanitization', () => {134const html = '<div title="a" other="1">text1</div><div title="b" other="2">text2</div>';135const result = sanitizeHtml(html, {136allowedAttributes: {137override: [138{139attributeName: 'title',140shouldKeep: (_el, data) => {141return data.attrValue.includes('b');142}143}144]145}146});147const str = result.toString();148assert.strictEqual(str, '<div>text1</div><div title="b">text2</div>');149});150151test('Supports changing attributes in dynamic sanitization', () => {152const html = '<div title="abc" other="1">text1</div><div title="xyz" other="2">text2</div>';153const result = sanitizeHtml(html, {154allowedAttributes: {155override: [156{157attributeName: 'title',158shouldKeep: (_el, data) => {159if (data.attrValue === 'abc') {160return false;161}162return data.attrValue + data.attrValue;163}164}165]166}167});168// xyz title should be preserved and doubled169assert.strictEqual(result.toString(), '<div>text1</div><div title="xyzxyz">text2</div>');170});171172test('Attr name should clear previously set dynamic sanitizer', () => {173const html = '<div title="abc" other="1">text1</div><div title="xyz" other="2">text2</div>';174const result = sanitizeHtml(html, {175allowedAttributes: {176override: [177{178attributeName: 'title',179shouldKeep: () => false180},181'title' // Should allow everything since it comes after custom rule182]183}184});185assert.strictEqual(result.toString(), '<div title="abc">text1</div><div title="xyz">text2</div>');186});187188suite('replaceWithPlaintext', () => {189190test('replaces unsupported tags with plaintext representation', () => {191const html = '<div>safe<script>alert(1)</script>content</div>';192const result = sanitizeHtml(html, {193replaceWithPlaintext: true194});195const str = result.toString();196assert.strictEqual(str, `<div>safe<script>alert(1)</script>content</div>`);197});198199test('handles self-closing tags correctly', () => {200const html = '<div><input type="text"><custom-input /></div>';201const result = sanitizeHtml(html, {202replaceWithPlaintext: true203});204assert.strictEqual(result.toString(), '<div><input type="text"><custom-input></custom-input></div>');205});206207test('handles tags with attributes', () => {208const html = '<div><unknown-tag class="test" id="myid">content</unknown-tag></div>';209const result = sanitizeHtml(html, {210replaceWithPlaintext: true211});212assert.strictEqual(result.toString(), '<div><unknown-tag class="test" id="myid">content</unknown-tag></div>');213});214215test('handles nested unsupported tags', () => {216const html = '<div><outer><inner>nested</inner></outer></div>';217const result = sanitizeHtml(html, {218replaceWithPlaintext: true219});220assert.strictEqual(result.toString(), '<div><outer><inner>nested</inner></outer></div>');221});222223test('handles comments correctly', () => {224const html = '<div><!-- this is a comment -->content</div>';225const result = sanitizeHtml(html, {226replaceWithPlaintext: true227});228assert.strictEqual(result.toString(), '<div><!-- this is a comment -->content</div>');229});230231test('handles empty tags', () => {232const html = '<div><empty></empty></div>';233const result = sanitizeHtml(html, {234replaceWithPlaintext: true235});236assert.strictEqual(result.toString(), '<div><empty></empty></div>');237});238239test('works with custom allowed tags configuration', () => {240const html = '<div><custom>allowed</custom><forbidden>not allowed</forbidden></div>';241const result = sanitizeHtml(html, {242replaceWithPlaintext: true,243allowedTags: { augment: ['custom'] }244});245assert.strictEqual(result.toString(), '<div><custom>allowed</custom><forbidden>not allowed</forbidden></div>');246});247});248});249250251