"use strict";

const server = createHttpServer({ hosts: ["example.com"] });
server.registerDirectory("/data/", do_get_file("data"));

async function testPublicSuffix({ permissions, testBackground, testContent }) {
  async function runTest(testFn, completionMessage) {
    try {
      dump(`Running test before sending ${completionMessage}\n`);
      await testFn();
    } catch (e) {
      browser.test.fail(`Unexpected error: ${e}`);
    }
    browser.test.sendMessage(completionMessage);
  }
  const extension = ExtensionTestUtils.loadExtension({
    background: `(${runTest})(${testBackground}, "background_done");`,
    manifest: {
      content_scripts: [
        {
          run_at: "document_end",
          js: ["test.js"],
          matches: ["http://example.com/data/file_sample.html"],
        },
      ],
      permissions,
    },
    files: {
      "test.js": `(${runTest})(${testContent}, "content_done");`,
    },
  });

  // Run background script.
  await extension.startup();
  await extension.awaitMessage("background_done");

  // Run content script.
  const page = await ExtensionTestUtils.loadContentPage(
    "http://example.com/data/file_sample.html"
  );
  await extension.awaitMessage("content_done");
  await page.close();

  await extension.unload();
}

// Tests publicSuffix API methods: isKnownSuffix(), getKnownSuffix(), getDomain()
// using a range of input hostnames and options.
add_task(async function test_publicSuffix() {
  function testBackground() {
    function testMethod(method, args, description, expect) {
      if (!(expect && typeof expect === "object")) {
        expect = { returns: expect };
      }

      const func = () => browser.publicSuffix[method](...args);
      const argsDescription = args.map(arg => JSON.stringify(arg)).join(", ");
      const fullDescription = `${method}(${argsDescription}) - ${description}`;
      if (expect.throws) {
        browser.test.assertThrows(func, expect.throws, fullDescription);
      } else {
        try {
          browser.test.assertEq(expect.returns, func(), fullDescription);
        } catch (e) {
          browser.test.fail(`Unexpected error: ${e} - ${fullDescription}`);
        }
      }
    }

    function testApi(test) {
      const { hostname, getDomain_options, description, expect } = test;
      testMethod(
        "isKnownSuffix",
        [hostname],
        description,
        expect.isKnownSuffix
      );
      testMethod(
        "getKnownSuffix",
        [hostname],
        description,
        expect.getKnownSuffix
      );
      testMethod(
        "getDomain",
        getDomain_options ? [hostname, getDomain_options] : [hostname],
        description,
        expect.getDomain
      );
    }

    testApi({
      hostname: "example.net",
      description: "eTLD+1",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "net",
        getDomain: "example.net",
      },
    });

    testApi({
      hostname: "www.example.net",
      description: "eTLD+2",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "net",
        getDomain: "example.net",
      },
    });

    testApi({
      hostname: "net",
      description: "is an eTLD itself, single-label",
      expect: {
        isKnownSuffix: true,
        getKnownSuffix: "net",
        getDomain: null,
      },
    });

    testApi({
      hostname: "net",
      getDomain_options: { allowPlainSuffix: true },
      description: "is an eTLD itself, single-label, using options",
      expect: {
        isKnownSuffix: true,
        getKnownSuffix: "net",
        getDomain: "net",
      },
    });

    testApi({
      hostname: "co.uk",
      description: "is an eTLD itself, multi-label, ICANN section",
      expect: {
        isKnownSuffix: true,
        getKnownSuffix: "co.uk",
        getDomain: null,
      },
    });

    testApi({
      hostname: "co.uk",
      getDomain_options: { allowPlainSuffix: true },
      description:
        "is an eTLD itself, multi-label, ICANN section, using options",
      expect: {
        isKnownSuffix: true,
        getKnownSuffix: "co.uk",
        getDomain: "co.uk",
      },
    });

    testApi({
      hostname: "github.io",
      description: "is an eTLD itself, multi-label, private section",
      expect: {
        isKnownSuffix: true,
        getKnownSuffix: "github.io",
        getDomain: null,
      },
    });

    testApi({
      hostname: "github.io",
      getDomain_options: { allowPlainSuffix: true },
      description:
        "is an eTLD itself, multi-label, private section, using options",
      expect: {
        isKnownSuffix: true,
        getKnownSuffix: "github.io",
        getDomain: "github.io",
      },
    });

    testApi({
      hostname: "banana",
      description: "no matching eTLD in PSL, single-label",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: null,
        getDomain: null,
      },
    });

    testApi({
      hostname: "banana",
      getDomain_options: { allowUnknownSuffix: true },
      description: "no matching eTLD in PSL, single-label, using options",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: null,
        // allowPlainSuffix defaults to false, so getDomain returns null.
        getDomain: null,
      },
    });

    testApi({
      hostname: "banana",
      getDomain_options: { allowUnknownSuffix: true, allowPlainSuffix: true },
      description: "no matching eTLD in PSL, single-label, using more options",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: null,
        getDomain: "banana",
      },
    });

    testApi({
      hostname: "my.net.banana",
      description: "no matching eTLD in PSL, multi-label",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: null,
        getDomain: null,
      },
    });

    testApi({
      hostname: "my.net.banana",
      getDomain_options: { allowUnknownSuffix: true },
      description: "no matching eTLD in PSL, multi-label, using options",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: null,
        getDomain: "net.banana",
      },
    });

    testApi({
      hostname: "banana.net",
      description: "has an eTLD in the ICANN section",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "net",
        getDomain: "banana.net",
      },
    });

    testApi({
      hostname: "facebook.co.uk",
      description: "has an eTLD in the ICANN section, multi-label suffix",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "co.uk",
        getDomain: "facebook.co.uk",
      },
    });

    testApi({
      hostname: "banana.github.io",
      description: "has an eTLD in the Private section",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "github.io",
        getDomain: "banana.github.io",
      },
    });

    testApi({
      hostname: "127.0.0.1",
      description: "IP address, IPv4",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: null,
        getDomain: null,
      },
    });

    testApi({
      hostname: "127.0.0.1",
      getDomain_options: { allowIPAddress: true },
      description: "IP address, IPv4, using options",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: null,
        getDomain: "127.0.0.1",
      },
    });

    testApi({
      hostname: "[127.0.0.1]",
      getDomain_options: { allowIPAddress: true },
      description: "Invalid IP address, IPv4, using options",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: { throws: "Invalid hostname: [127.0.0.1]" },
        getDomain: { throws: "Invalid hostname: [127.0.0.1]" },
      },
    });

    testApi({
      hostname: "[::1]",
      description: "IP address, IPv6",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: null,
        getDomain: null,
      },
    });

    testApi({
      hostname: "[::1]",
      getDomain_options: { allowIPAddress: true },
      description: "IP address, IPv6, using options",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: null,
        getDomain: "[::1]",
      },
    });

    testApi({
      hostname: "::1",
      description: "IP address, IPv6, no brackets",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: { throws: "Invalid hostname: ::1" },
        getDomain: { throws: "Invalid hostname: ::1" },
      },
    });

    testApi({
      hostname: "::1",
      getDomain_options: { allowIPAddress: true },
      description: "IP address, IPv6, no brackets, using options",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: { throws: "Invalid hostname: ::1" },
        getDomain: { throws: "Invalid hostname: ::1" },
      },
    });

    testApi({
      hostname: "[example.com]",
      description: "domain name in brackets",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: { throws: "Invalid hostname: [example.com]" },
        getDomain: { throws: "Invalid hostname: [example.com]" },
      },
    });

    testApi({
      hostname: "[example.com:80]",
      description: "domain name in brackets, with port",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: { throws: "Invalid hostname: [example.com:80]" },
        getDomain: { throws: "Invalid hostname: [example.com:80]" },
      },
    });

    testApi({
      hostname: "EXAMPLE.NET",
      description: "uppercase",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "net",
        getDomain: "example.net",
      },
    });

    testApi({
      hostname: ".example.net",
      description: "dot in front",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: { throws: "Invalid hostname: .example.net" },
        getDomain: { throws: "Invalid hostname: .example.net" },
      },
    });

    testApi({
      hostname: "example.net.",
      description: "dot in the end, this is an FQDN",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "net.",
        getDomain: "example.net.",
      },
    });

    testApi({
      hostname: "مليسيا",
      description: "this is an IDN that is also an eTLD",
      expect: {
        isKnownSuffix: true,
        getKnownSuffix: "xn--mgbx4cd0ab",
        getDomain: null,
      },
    });

    testApi({
      hostname: "xn--mgbx4cd0ab",
      description: "this is an IDN that is also an eTLD, in punycode form",
      expect: {
        isKnownSuffix: true,
        getKnownSuffix: "xn--mgbx4cd0ab",
        getDomain: null,
      },
    });

    testApi({
      hostname: "foo.مليسيا",
      description: "this is an IDN",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "xn--mgbx4cd0ab",
        getDomain: "foo.xn--mgbx4cd0ab",
      },
    });

    testApi({
      hostname: "foo.مليسيا",
      getDomain_options: { encoding: "display" },
      description: "this is an IDN, using options",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "xn--mgbx4cd0ab",
        getDomain: "foo.مليسيا",
      },
    });

    testApi({
      hostname: "foo.xn--mgbx4cd0ab",
      description: "this is an IDN, but punycode",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "xn--mgbx4cd0ab",
        getDomain: "foo.xn--mgbx4cd0ab",
      },
    });

    testApi({
      hostname: "foo.xn--mgbx4cd0ab",
      getDomain_options: { encoding: "display" },
      description: "this is an IDN, but punycode, using options",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "xn--mgbx4cd0ab",
        getDomain: "foo.مليسيا",
      },
    });

    testApi({
      hostname: "xn--bs-red.com",
      getDomain_options: { encoding: "display" },
      description:
        "this is an IDN, but punycode, with a unicode confusable label",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "com",
        // not converted to unicode due to unicode confusable.
        getDomain: "xn--bs-red.com",
      },
    });

    testApi({
      hostname: "*.com",
      description: "contains invalid character '*', 1-label",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: { throws: "Invalid hostname: *.com" },
        getDomain: { throws: "Invalid hostname: *.com" },
      },
    });

    testApi({
      hostname: "^.com",
      description: "contains invalid character '^', 1-label",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: { throws: "Invalid hostname: ^.com" },
        getDomain: { throws: "Invalid hostname: ^.com" },
      },
    });

    testApi({
      hostname: "*.mydomain.com",
      description: "contains invalid character '*', 2-label",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: { throws: "Invalid hostname: *.mydomain.com" },
        getDomain: { throws: "Invalid hostname: *.mydomain.com" },
      },
    });

    testApi({
      hostname: "^.mydomain.com",
      description: "contains invalid character '^', 2-label",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: { throws: "Invalid hostname: ^.mydomain.com" },
        getDomain: { throws: "Invalid hostname: ^.mydomain.com" },
      },
    });

    testApi({
      hostname: "",
      description: "empty string",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: { throws: "Invalid hostname: " },
        getDomain: { throws: "Invalid hostname: " },
      },
    });

    testApi({
      hostname: ".",
      description: "no domain labels",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: { throws: "Invalid hostname: ." },
        getDomain: { throws: "Invalid hostname: ." },
      },
    });

    testApi({
      hostname: "example..com",
      description: "contains an empty domain label",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: { throws: "Invalid hostname: example..com" },
        getDomain: { throws: "Invalid hostname: example..com" },
      },
    });

    testApi({
      hostname: "ck",
      description: "wildcard rule *.ck, is incomplete eTLD",
      expect: {
        isKnownSuffix: true,
        getKnownSuffix: "ck",
        getDomain: null,
      },
    });

    testApi({
      hostname: "banana.ck",
      description: "wildcard rule *.ck, is eTLD",
      expect: {
        isKnownSuffix: true,
        getKnownSuffix: "banana.ck",
        getDomain: null,
      },
    });

    testApi({
      hostname: "mydomain.banana.ck",
      description: "wildcard rule *.ck, is eTLD+1",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "banana.ck",
        getDomain: "mydomain.banana.ck",
      },
    });

    testApi({
      hostname: "www.ck",
      description: "exception rule !www.ck",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "ck",
        getDomain: "www.ck",
      },
    });

    testApi({
      hostname: "mydomain.www.ck",
      description: "exception rule !www.ck, subdomain",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "ck",
        getDomain: "www.ck",
      },
    });

    testApi({
      hostname: "compute.amazonaws.com",
      description:
        "wildcard rule *.compute.amazonaws.com, private section, incomplete eTLD",
      expect: {
        isKnownSuffix: true,
        getKnownSuffix: "compute.amazonaws.com",
        getDomain: null,
      },
    });

    testApi({
      hostname: "banana.compute.amazonaws.com",
      description:
        "wildcard rule *.compute.amazonaws.com, private section, eTLD",
      expect: {
        isKnownSuffix: true,
        getKnownSuffix: "banana.compute.amazonaws.com",
        getDomain: null,
      },
    });

    testApi({
      hostname: "mydomain.banana.compute.amazonaws.com",
      description:
        "wildcard rule *.compute.amazonaws.com, private section, eTLD+1",
      expect: {
        isKnownSuffix: false,
        getKnownSuffix: "banana.compute.amazonaws.com",
        getDomain: "mydomain.banana.compute.amazonaws.com",
      },
    });
  }

  function testContent() {
    browser.test.assertEq(
      undefined,
      browser.publicSuffix,
      "publicSuffix should not be available in a content script"
    );
  }

  await testPublicSuffix({
    permissions: ["publicSuffix"],
    testBackground,
    testContent,
  });
});
