/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/**
 * Tests for the collecting keys from messages.
 */

"use strict";

const { get_about_message, open_message_from_file } =
  ChromeUtils.importESModule(
    "resource://testing-common/mail/FolderDisplayHelpers.sys.mjs"
  );
const { OpenPGPTestUtils } = ChromeUtils.importESModule(
  "resource://testing-common/mail/OpenPGPTestUtils.sys.mjs"
);

const { MailServices } = ChromeUtils.importESModule(
  "resource:///modules/MailServices.sys.mjs"
);
const { CollectedKeysDB } = ChromeUtils.importESModule(
  "chrome://openpgp/content/modules/CollectedKeysDB.sys.mjs"
);

var aliceAcct;

/**
 * When testing a scenario that should automatically process the OpenPGP
 * contents (it's not suppressed e.g. because of a partial content),
 * then we need to wait for the automatic processing to complete.
 */
async function openpgpProcessed() {
  const [subject] = await TestUtils.topicObserved(
    "document-element-inserted",
    document => {
      return document.ownerGlobal?.location == "about:message";
    }
  );

  return BrowserTestUtils.waitForEvent(subject, "openpgpprocessed");
}

/**
 * Set up the base account, identity and keys needed for the tests.
 */
add_setup(async function () {
  aliceAcct = MailServices.accounts.createAccount();
  aliceAcct.incomingServer = MailServices.accounts.createIncomingServer(
    "alice",
    "openpgp.example",
    "pop3"
  );
  const aliceIdentity = MailServices.accounts.createIdentity();
  aliceIdentity.email = "alice@openpgp.example";
  aliceAcct.addIdentity(aliceIdentity);

  // Set up the alice's private key.
  // We need one key set up for use. Otherwise we do not process OpenPGP data.
  const [id] = await OpenPGPTestUtils.importPrivateKey(
    window,
    new FileUtils.File(
      getTestFilePath(
        "data/keys/alice@openpgp.example-0xf231550c4f47e38e-secret.asc"
      )
    )
  );
  aliceIdentity.setUnicharAttribute("openpgp_key_id", id);
});

/**
 * Test that an attached key is collected.
 */
add_task(async function testCollectKeyAttachment() {
  const keycollected = BrowserTestUtils.waitForEvent(window, "keycollected");
  const opengpgprocessed = openpgpProcessed();
  const msgc = await open_message_from_file(
    new FileUtils.File(
      getTestFilePath(
        "data/eml/unsigned-unencrypted-key-0x1f10171bfb881b1c-attached.eml"
      )
    )
  );
  await opengpgprocessed;
  const aboutMessage = get_about_message(msgc);

  Assert.ok(
    OpenPGPTestUtils.hasNoSignedIconState(aboutMessage.document),
    "signed icon is not displayed"
  );
  Assert.ok(
    !OpenPGPTestUtils.hasEncryptedIconState(aboutMessage.document, "ok"),
    "encrypted icon is not displayed"
  );
  await keycollected;

  const db = await CollectedKeysDB.getInstance();
  const keys = await db.findKeysForEmail("jdoe@invalid");
  Assert.equal(keys.length, 1, "should find one key");

  const sources = keys[0].sources;
  Assert.equal(sources.length, 1, "should have one source");
  const source = sources[0];

  Assert.equal(source.type, "attachment");
  Assert.equal(source.uri, "mid:4a735c72-dc19-48ff-4fa5-2c1f65513b27@invalid");
  Assert.equal(source.description, "OpenPGP_0x1F10171BFB881B1C.asc");

  await BrowserTestUtils.closeWindow(msgc);
});

/**
 * Test that an Autocrypt header key is collected.
 */
add_task(async function testCollectAutocrypt() {
  const keycollected = BrowserTestUtils.waitForEvent(window, "keycollected");
  const opengpgprocessed = openpgpProcessed();
  const msgc = await open_message_from_file(
    new FileUtils.File(
      getTestFilePath(
        "data/eml/unsigned-unencrypted-0x3099ff1238852b9f-autocrypt.eml"
      )
    )
  );
  await opengpgprocessed;
  const aboutMessage = get_about_message(msgc);

  Assert.ok(
    OpenPGPTestUtils.hasNoSignedIconState(aboutMessage.document),
    "signed icon is not displayed"
  );
  Assert.ok(
    !OpenPGPTestUtils.hasEncryptedIconState(aboutMessage.document, "ok"),
    "encrypted icon is not displayed"
  );
  await keycollected;

  const carolEmail = "carol@example.com";

  const db = await CollectedKeysDB.getInstance();
  let keys = await db.findKeysForEmail(carolEmail);
  Assert.equal(keys.length, 1, "should find one key");

  const sources = keys[0].sources;
  Assert.equal(sources.length, 1, "should have one source");
  const source = sources[0];

  Assert.equal(source.type, "autocrypt");
  Assert.equal(
    source.uri,
    "mid:b3609461-36e8-0371-1b9d-7ce6864ec66d@example.com"
  );
  Assert.equal(source.description, undefined);

  // Clean up to ensure other tests will not find this key
  db.deleteKeysForEmail(carolEmail);
  keys = await db.findKeysForEmail(carolEmail);
  Assert.equal(keys.length, 0, "should find zero keys after cleanup");

  await BrowserTestUtils.closeWindow(msgc);
});

/**
 * Test that an Autocrypt-Gossip header key is collected.
 */
add_task(async function testCollectAutocryptGossip() {
  const keycollected = BrowserTestUtils.waitForEvent(window, "keycollected");
  const keycollected2 = BrowserTestUtils.waitForEvent(window, "keycollected");
  const keycollected3 = BrowserTestUtils.waitForEvent(window, "keycollected");
  const opengpgprocessed = openpgpProcessed();
  const msgc = await open_message_from_file(
    new FileUtils.File(
      getTestFilePath("data/eml/signed-encrypted-autocrypt-gossip.eml")
    )
  );
  await opengpgprocessed;
  const aboutMessage = get_about_message(msgc);

  Assert.ok(
    OpenPGPTestUtils.hasSignedIconState(aboutMessage.document, "unknown"),
    "signed icon is displayed"
  );
  Assert.ok(
    OpenPGPTestUtils.hasEncryptedIconState(aboutMessage.document, "ok"),
    "encrypted icon is displayed"
  );

  await keycollected;
  await keycollected2;
  await keycollected3;

  const carolEmail = "carol@example.com";

  const db = await CollectedKeysDB.getInstance();
  let keys = await db.findKeysForEmail(carolEmail);
  Assert.equal(keys.length, 1, "should find one key");

  const sources = keys[0].sources;
  Assert.equal(sources.length, 1, "should have one source");
  const source = sources[0];

  Assert.equal(source.type, "autocrypt");
  Assert.equal(
    source.uri,
    "mid:e8690528-d187-4d99-b505-9f3d6a2704ca@openpgp.example"
  );
  Assert.equal(source.description, undefined);

  // Clean up to ensure other tests will not find this key
  db.deleteKeysForEmail(carolEmail);
  keys = await db.findKeysForEmail(carolEmail);
  Assert.equal(keys.length, 0, "should find zero keys after cleanup");

  await BrowserTestUtils.closeWindow(msgc);
});

/**
 * Test that we don't collect keys that refer to an email address that
 * isn't one of the message participants, and that we don't collect keys
 * if we already have a personal key for an email address.
 */
add_task(async function testSkipFakeOrUnrelatedKeys() {
  const opengpgprocessed = openpgpProcessed();
  const msgc = await open_message_from_file(
    new FileUtils.File(
      getTestFilePath("data/eml/unrelated-and-fake-keys-attached.eml")
    )
  );
  await opengpgprocessed;
  const aboutMessage = get_about_message(msgc);

  Assert.ok(
    OpenPGPTestUtils.hasNoSignedIconState(aboutMessage.document),
    "signed icon is not displayed"
  );
  Assert.ok(
    !OpenPGPTestUtils.hasEncryptedIconState(aboutMessage.document, "ok"),
    "encrypted icon is not displayed"
  );

  const db = await CollectedKeysDB.getInstance();

  let keys = await db.findKeysForEmail("alice@openpgp.example");
  Assert.equal(
    keys.length,
    0,
    "the attached key for alice should have been ignored because we have a personal key for that address"
  );

  keys = await db.findKeysForEmail("stranger@example.com");
  Assert.equal(
    keys.length,
    0,
    "the attached key for stranger should have been ignored because stranger isn't a participant of this message"
  );

  const bobEmail = "bob@openpgp.example";

  keys = await db.findKeysForEmail(bobEmail);
  Assert.equal(keys.length, 1, "bob's key should have been collected");

  db.deleteKeysForEmail(bobEmail);
  keys = await db.findKeysForEmail(bobEmail);
  Assert.equal(keys.length, 0, "should find zero keys after cleanup");

  await BrowserTestUtils.closeWindow(msgc);
});

/**
 * If an email contains two different keys for the same email address,
 * don't import any keys for that email address.
 */
add_task(async function testSkipDuplicateKeys() {
  const opengpgprocessed = openpgpProcessed();
  const msgc = await open_message_from_file(
    new FileUtils.File(getTestFilePath("data/eml/eve-duplicate.eml"))
  );
  await opengpgprocessed;
  const aboutMessage = get_about_message(msgc);

  Assert.ok(
    OpenPGPTestUtils.hasNoSignedIconState(aboutMessage.document),
    "signed icon is not displayed"
  );
  Assert.ok(
    !OpenPGPTestUtils.hasEncryptedIconState(aboutMessage.document, "ok"),
    "encrypted icon is not displayed"
  );

  const db = await CollectedKeysDB.getInstance();

  const keys = await db.findKeysForEmail("eve@example.com");
  Assert.equal(
    keys.length,
    0,
    "the attached keys for eve should have been ignored"
  );

  await BrowserTestUtils.closeWindow(msgc);
});

registerCleanupFunction(async function tearDown() {
  MailServices.accounts.removeAccount(aliceAcct, true);
  await OpenPGPTestUtils.removeKeyById("0xf231550c4f47e38e", true);
  await CollectedKeysDB.deleteDb();
});
