/**
 * @license
 * Copyright The Closure Library Authors.
 * SPDX-License-Identifier: Apache-2.0
 */

/** @fileoverview Unit Test for the unsafe API of the HTML Sanitizer. */

goog.module('goog.html.UnsafeTest');
goog.setTestOnly();

const Const = goog.require('goog.string.Const');
const HtmlSanitizer = goog.require('goog.html.sanitizer.HtmlSanitizer');
const HtmlSanitizerAttributePolicy = goog.requireType('goog.html.sanitizer.HtmlSanitizerAttributePolicy');
const SafeHtml = goog.require('goog.html.SafeHtml');
const TagWhitelist = goog.require('goog.html.sanitizer.TagWhitelist');
const dom = goog.require('goog.testing.dom');
const functions = goog.require('goog.functions');
const testSuite = goog.require('goog.testing.testSuite');
const unsafe = goog.require('goog.html.sanitizer.unsafe');
const {AllowedAttributes} = goog.require('goog.html.sanitizer.attributeallowlists');

const just = Const.from('test');

/**
 * Sanitizes the original HTML and asserts that it is the same as the expected
 * HTML. Supports adding tags and attributes through the unsafe API.
 * @param {string} originalHtml
 * @param {string} expectedHtml
 * @param {?Array<string>=} tags
 * @param {?Array<(string|!HtmlSanitizerAttributePolicy)>=}
 *     attrs
 * @param {?HtmlSanitizer.Builder=} opt_builder
 */
function assertSanitizedHtml(
    originalHtml, expectedHtml, tags = undefined, attrs = undefined,
    opt_builder) {
  let builder = opt_builder || new HtmlSanitizer.Builder();
  if (tags) builder = unsafe.alsoAllowTags(just, builder, tags);
  if (attrs) builder = unsafe.alsoAllowAttributes(just, builder, attrs);
  const sanitizer = builder.build();
  const sanitized = sanitizer.sanitize(originalHtml);
  dom.assertHtmlMatches(
      expectedHtml, SafeHtml.unwrap(sanitized),
      true /* opt_strictAttributes */);
}

testSuite({
  testAllowEmptyTagList() {
    const input = '<sdf><aaa></aaa></sdf><b></b>';
    const expected = '<span><span></span></span><b></b>';
    assertSanitizedHtml(input, expected, []);
  },

  testAllowBlacklistedTag() {
    const input = '<div><script>aaa</script></div>';
    assertSanitizedHtml(input, input, ['SCriPT']);
  },

  testAllowUnknownTags() {
    const input = '<hello><bye>aaa</bye></hello><zzz></zzz>';
    const expected = '<hello><span>aaa</span></hello><zzz></zzz>';
    assertSanitizedHtml(input, expected, ['HElLO', 'zZZ']);
  },

  testAllowAlreadyWhiteListedTag() {
    const input = '<hello><p><zzz></zzz></p></hello>';
    const expected = '<span><p><zzz></zzz></p></span>';
    assertSanitizedHtml(input, expected, ['p', 'ZZZ']);
  },

  testAllowEmptyAttrList() {
    const input = '<a href="#" qwe="nope">b</a>';
    const expected = '<a href="#">b</a>';
    assertSanitizedHtml(input, expected, null, []);
  },

  testAllowUnknownAttributeSimple() {
    const input = '<qqq zzz="3" nnn="no"></qqq>';
    const expected = '<span zzz="3"></span>';
    assertSanitizedHtml(input, expected, null, ['Zzz']);
  },

  testAllowUnknownAttributeWildCard() {
    const input = '<div ab="yes" bb="no"><img ab="yep" bb="no" /></div>';
    const expected = '<div ab="yes"><img ab="yep" /></div>';
    assertSanitizedHtml(
        input, expected, null, [{tagName: '*', attributeName: 'aB'}]);
  },

  testAllowUnknownAttributeOnSpecificTag() {
    const input = '<a www="3" zzz="4">fff</a><img www="3" />';
    const expected = '<a www="3">fff</a><img />';
    assertSanitizedHtml(
        input, expected, null, [{tagName: 'a', attributeName: 'WwW'}]);
  },

  testAllowUnknownAttributePolicy() {
    const input = '<img ab="yes" /><img ab="no" />';
    const expected = '<img ab="yes" /><img />';
    assertSanitizedHtml(input, expected, null, [{
                          tagName: '*',
                          attributeName: 'aB',
                          policy: function(value, hints) {
                            assertEquals(hints.attributeName, 'ab');
                            return value === 'yes' ? value : null;
                          },
                        }]);
  },

  testAllowOverwriteAttrPolicy() {
    const input = '<a href="yes"></a><a href="no"></a>';
    const expected = '<a href="yes"></a><a></a>';
    assertSanitizedHtml(input, expected, null, [{
                          tagName: 'a',
                          attributeName: 'href',
                          policy: function(value) {
                            return value === 'yes' ? value : null;
                          },
                        }]);
  },

  testAllowDAttribute() {
    const input = '<path d="1.5 1.5 1.5 14.5 14.5 14.5 14.5 1.5"/>';
    const expected = '<path d="1.5 1.5 1.5 14.5 14.5 14.5 14.5 1.5"/>';
    assertSanitizedHtml(
        input, expected, ['path'], [{tagName: 'path', attributeName: 'd'}]);
  },

  testWhitelistAliasing() {
    const builder = new HtmlSanitizer.Builder();
    unsafe.alsoAllowTags(just, builder, ['QqQ']);
    unsafe.alsoAllowAttributes(just, builder, ['QqQ']);
    builder.build();
    assertUndefined(TagWhitelist['QQQ']);
    assertUndefined(TagWhitelist['QqQ']);
    assertUndefined(TagWhitelist['qqq']);
    assertUndefined(AllowedAttributes['* QQQ']);
    assertUndefined(AllowedAttributes['* QqQ']);
    assertUndefined(AllowedAttributes['* qqq']);
  },

  testAllowRelaxExistingAttributePolicyWildcard() {
    const input = '<a href="javascript:alert(1)"></a>';
    // define a tag-specific one, takes precedence
    assertSanitizedHtml(
        input, input, null,
        [{tagName: 'a', attributeName: 'href', policy: functions.identity}]);
    // overwrite the global one
    assertSanitizedHtml(
        input, input, null,
        [{tagName: '*', attributeName: 'href', policy: functions.identity}]);
  },

  testAllowRelaxExistingAttributePolicySpecific() {
    const input = '<a target="foo"></a>';
    const expected = '<a></a>';
    // overwrite the global one, the specific one still has precedence
    assertSanitizedHtml(input, expected, null, [
      {tagName: '*', attributeName: 'target', policy: functions.identity},
    ]);
    // overwrite the tag-specific one, this one should take precedence
    assertSanitizedHtml(input, input, null, [
      {tagName: 'a', attributeName: 'target', policy: functions.identity},
    ]);
  },

  testAlsoAllowTagsInBlacklist() {
    const input = '<video controls><source src="video.mp4" type="video/mp4">';
    assertSanitizedHtml(input, input, ['video', 'source'], [
      {tagName: 'video', attributeName: 'controls', policy: functions.identity},
      {tagName: 'source', attributeName: 'src', policy: functions.identity},
      {tagName: 'source', attributeName: 'type', policy: functions.identity},
    ]);
  },
});
