chromium/chrome/test/data/webui/js/parse_html_subset_test.ts

// 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();
    });
  });
});