| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import {parseHtmlSubset, sanitizeInnerHtml} from 'chrome://resources/js/parse_html_subset.js'; |
| import {assertEquals, assertFalse, assertThrows} from 'chrome://webui-test/chai_assert.js'; |
| |
| declare global { |
| interface Window { |
| called: boolean; |
| } |
| } |
| |
| suite('ParseHtmlSubsetTest', function() { |
| function parseAndAssertThrows( |
| s: string, extraTags?: string[], extraAttrs?: string[]) { |
| assertThrows(function() { |
| parseHtmlSubset(s, extraTags, extraAttrs); |
| }); |
| } |
| |
| test('text', function() { |
| parseHtmlSubset(''); |
| parseHtmlSubset('abc'); |
| parseHtmlSubset(' '); |
| }); |
| |
| test('supported tags', function() { |
| parseHtmlSubset('<b>bold</b>'); |
| parseHtmlSubset('Some <b>bold</b> text'); |
| parseHtmlSubset('Some <strong>strong</strong> text'); |
| parseHtmlSubset('<B>bold</B>'); |
| parseHtmlSubset('Some <B>bold</B> text'); |
| parseHtmlSubset('Some <STRONG>strong</STRONG> text'); |
| parseHtmlSubset('<PRE>pre</PRE><BR>'); |
| parseHtmlSubset('Some <PRE>pre</PRE><BR> text', ['BR']); |
| }); |
| |
| test('invalid tags', function() { |
| parseAndAssertThrows('<unknown_tag>x</unknown_tag>'); |
| parseAndAssertThrows('<style>*{color:red;}</style>'); |
| parseAndAssertThrows( |
| '<script>alert(1)<' + |
| '/script>'); |
| }); |
| |
| test('invalid attributes', function() { |
| parseAndAssertThrows('<b onclick="alert(1)">x</b>'); |
| parseAndAssertThrows('<b style="color:red">x</b>'); |
| parseAndAssertThrows('<b foo>x</b>'); |
| parseAndAssertThrows('<b foo=bar></b>'); |
| }); |
| |
| test('valid anchors', function() { |
| parseHtmlSubset('<a href="https://google.com">Google</a>'); |
| parseHtmlSubset('<a href="chrome://settings">Google</a>'); |
| }); |
| |
| test('invalid anchor hrefs', function() { |
| parseAndAssertThrows('<a href="http://google.com">Google</a>'); |
| parseAndAssertThrows('<a href="ftp://google.com">Google</a>'); |
| parseAndAssertThrows('<a href="http/google.com">Google</a>'); |
| parseAndAssertThrows('<a href="javascript:alert(1)">Google</a>'); |
| parseAndAssertThrows( |
| '<a href="chrome-extension://whurblegarble">Google</a>'); |
| }); |
| |
| test('invalid anchor attributes', function() { |
| parseAndAssertThrows('<a name=foo>Google</a>'); |
| parseAndAssertThrows( |
| '<a onclick="alert(1)" href="https://google.com">Google</a>'); |
| parseAndAssertThrows( |
| '<a foo="bar(1)" href="https://google.com">Google</a>'); |
| }); |
| |
| test('anchor target', function() { |
| const df = parseHtmlSubset( |
| '<a href="https://google.com" target="_blank">Google</a>'); |
| assertEquals('_blank', (df.firstChild as HTMLAnchorElement).target); |
| }); |
| |
| test('invalid target', function() { |
| parseAndAssertThrows( |
| '<a href="https://google.com" target="foo">Google</a>'); |
| }); |
| |
| test('supported optional tags', function() { |
| parseHtmlSubset('<img>Some <b>bold</b> text', ['img']); |
| parseHtmlSubset('A list:<ul><li>An item</li></ul>', ['li', 'ul']); |
| }); |
| |
| test('supported optional tags without the argument', function() { |
| parseAndAssertThrows('<img>'); |
| }); |
| |
| test('invalid optional tags', function() { |
| parseAndAssertThrows('a pirate\'s<script>alert();</script>', ['script']); |
| }); |
| |
| test('supported optional attributes', function() { |
| let result = |
| parseHtmlSubset('<a role="link">link</a>', undefined, ['role']); |
| assertEquals( |
| 'link', (result.firstChild as HTMLAnchorElement).getAttribute('role')); |
| result = |
| parseHtmlSubset('<img src="chrome://favicon2/">', ['img'], ['src']); |
| assertEquals( |
| 'chrome://favicon2/', |
| (result.firstChild as HTMLAnchorElement).getAttribute('src')); |
| }); |
| |
| test('supported optional attributes without the argument', function() { |
| parseAndAssertThrows('<img src="chrome://favicon2/">', ['img']); |
| parseAndAssertThrows('<a id="test">link</a>'); |
| }); |
| |
| test('invalid optional attributes', function() { |
| parseAndAssertThrows( |
| '<a test="fancy">I\'m fancy!</a>', undefined, ['test']); |
| parseAndAssertThrows('<a name="fancy">I\'m fancy!</a>'); |
| }); |
| |
| test('invalid optional attribute\'s value', function() { |
| parseAndAssertThrows('<a is="xss-link">link</a>', undefined, ['is']); |
| }); |
| |
| test('sanitizeInnerHtml', function() { |
| assertEquals( |
| '<a href="chrome://foo"></a>', |
| sanitizeInnerHtml('<a href="chrome://foo"></a>').toString()); |
| assertThrows(() => { |
| sanitizeInnerHtml('<iframe></iframe>'); |
| }, 'IFRAME is not supported'); |
| assertEquals('<div></div>', sanitizeInnerHtml('<div></div>').toString()); |
| }); |
| |
| test('on error async', function(done) { |
| window.called = false; |
| |
| parseAndAssertThrows('<img onerror="window.called = true" src="_.png">'); |
| parseAndAssertThrows('<img src="_.png" onerror="window.called = true">'); |
| |
| window.setTimeout(function() { |
| assertFalse(window.called); |
| done(); |
| }); |
| }); |
| }); |