/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/**
 * Tests sending messages to addresses with non-ASCII characters.
 */
/* import-globals-from ../../../test/resources/alertTestUtils.js */
load("../../../resources/alertTestUtils.js");

var test = null;
var server;
var finished = false;

var sentFolder;

var kSender = "from@foo.invalid";
var kToASCII = "to@foo.invalid";
var kToValid = "to@v\u00E4lid.foo.invalid";
var kToValidACE = "to@xn--vlid-loa.foo.invalid";
var kToInvalid = "b\u00F8rken.to@invalid.foo.invalid";
var kToInvalidWithoutDomain = "b\u00F8rken.to";
var NS_ERROR_ILLEGAL_LOCALPART = 0x80553139;
var NS_MSG_NO_RECIPIENTS = 0x805530df;

// for alertTestUtils.js
let resolveAlert;
function alertPS(parent, aDialogText, aText) {
  var composeProps = Services.strings.createBundle(
    "chrome://messenger/locale/messengercompose/composeMsgs.properties"
  );
  var expectedAlertMessage =
    composeProps.GetStringFromName("sendFailed") +
    "\n" +
    composeProps.GetStringFromName(
      test == kToInvalidWithoutDomain
        ? "noRecipients"
        : "errorIllegalLocalPart2"
    );
  if (test != kToInvalidWithoutDomain) {
    expectedAlertMessage = expectedAlertMessage.replace("%s", test);
  }

  // we should only get here for the kToInvalid test case
  Assert.equal(aText, expectedAlertMessage);
  resolveAlert();
}

// message listener implementations
function MsgSendListener(aRecipient, originalData) {
  this.rcpt = aRecipient;
  this.originalData = originalData;
}

/**
 * @implements {nsIMsgSendListener}
 * @implements {nsIMsgCopyServiceListener}
 */
MsgSendListener.prototype = {
  // nsIMsgSendListener
  onStartSending() {},
  onSendProgress() {},
  onStatus() {},
  onStopSending(aMsgID, aStatus) {
    try {
      if (test == kToValid || test == kToASCII) {
        Assert.equal(aStatus, 0);
        do_check_transaction(server.playTransaction(), [
          "EHLO test",
          "MAIL FROM:<" +
            kSender +
            "> BODY=8BITMIME SIZE=" +
            this.originalData.length,
          "RCPT TO:<" + this.rcpt + ">",
          "DATA",
        ]);
        // Compare data file to what the server received
        Assert.equal(this.originalData, server._daemon.post);
      } else {
        Assert.equal(
          aStatus,
          test == kToInvalidWithoutDomain
            ? NS_MSG_NO_RECIPIENTS
            : NS_ERROR_ILLEGAL_LOCALPART
        );
        do_check_transaction(server.playTransaction(), ["EHLO test"]);
        // Local address (before the @) has non-ascii char(s) or the @ is
        // missing from the address. An alert is triggered after the EHLO is
        // sent. Nothing else occurs so we "finish" the test to avoid
        // NS_ERROR_ABORT test failure due to timeout waiting for the send
        // (which doesn't occurs) to complete.
      }
    } catch (e) {
      do_throw(e);
    } finally {
      server.stop();
      var thread = Services.tm.currentThread;
      while (thread.hasPendingEvents()) {
        thread.processNextEvent(false);
      }
      do_test_finished();
    }
  },
  onGetDraftFolderURI() {},
  onSendNotPerformed() {},
  onTransportSecurityError() {},

  // nsIMsgCopyServiceListener
  onStartCopy() {},
  onProgress() {},
  setMessageKey() {},
  getMessageId() {
    return null;
  },
  onStopCopy(aStatus) {
    Assert.equal(aStatus, 0);
    try {
      // Now do a comparison of what is in the sent mail folder
      let msgData = mailTestUtils.loadMessageToString(
        sentFolder,
        mailTestUtils.firstMsgHdr(sentFolder)
      );
      // Skip the headers etc that mailnews adds
      var pos = msgData.indexOf("From:");
      Assert.notEqual(pos, -1);
      msgData = msgData.substr(pos);
      Assert.equal(this.originalData, msgData);
    } catch (e) {
      do_throw(e);
    } finally {
      finished = true;
    }
  },

  // QueryInterface
  QueryInterface: ChromeUtils.generateQI([
    "nsIMsgSendListener",
    "nsIMsgCopyServiceListener",
  ]),
};

async function doSendTest(aRecipient, aRecipientExpected, waitForPrompt) {
  info(`Testing send to ${aRecipient} will get sent to ${aRecipientExpected}`);
  const promiseAlertReceived = new Promise(resolve => {
    resolveAlert = resolve;
  });
  test = aRecipient;
  server = setupServerDaemon();
  server.start();
  var smtpServer = getBasicSmtpServer(server.port);
  var identity = getSmtpIdentity(kSender, smtpServer);
  Assert.equal(identity.doFcc, true);

  // Random test file with data we don't actually care about. ;-)
  var testFile = do_get_file("data/message1.eml");
  var originalData = await IOUtils.readUTF8(testFile.path);

  // Handle the server in a try/catch/finally loop so that we always will stop
  // the server if something fails.
  try {
    do_test_pending();
    var compFields = Cc[
      "@mozilla.org/messengercompose/composefields;1"
    ].createInstance(Ci.nsIMsgCompFields);
    compFields.from = identity.email;
    compFields.to = aRecipient;

    var msgSend = Cc["@mozilla.org/messengercompose/send;1"].createInstance(
      Ci.nsIMsgSend
    );
    msgSend.sendMessageFile(
      identity,
      "",
      compFields,
      testFile,
      false,
      false,
      Ci.nsIMsgSend.nsMsgDeliverNow,
      null,
      new MsgSendListener(aRecipientExpected, originalData),
      null,
      null
    );

    server.performTest();
    do_timeout(10000, function () {
      if (!finished) {
        do_throw("Notifications of message send/copy not received");
      }
    });
    if (waitForPrompt) {
      await promiseAlertReceived;
    }
  } catch (e) {
    Assert.ok(false, "Send fail: " + e);
  } finally {
    server.stop();
    var thread = Services.tm.currentThread;
    while (thread.hasPendingEvents()) {
      thread.processNextEvent(true);
    }
  }
}

add_setup(function () {
  registerAlertTestUtils();

  // Ensure we have at least one mail account
  localAccountUtils.loadLocalMailAccount();
  MailServices.accounts.setSpecialFolders();
  sentFolder = localAccountUtils.rootFolder.createLocalSubfolder("Sent");
});

add_task(async function plainASCIIRecipient() {
  // Test 1:
  // Plain ASCII recipient address.
  await doSendTest(kToASCII, kToASCII, false);
});

add_task(async function domainContainsNonAscii() {
  // Test 2:
  // The recipient's domain part contains a non-ASCII character, hence the
  // address needs to be converted to ACE before sending.
  // The old code would just strip the non-ASCII character and try to send
  // the message to the remaining - wrong! - address.
  // The new code will translate the domain part to ACE for the SMTP
  // transaction (only), i.e. the To: header will stay as stated by the sender.
  await doSendTest(kToValid, kToValidACE, false);
});

add_task(async function localContainsNonAscii() {
  // Test 3:
  // The recipient's local part contains a non-ASCII character, which is not
  // allowed with unextended SMTP.
  // The old code would just strip the invalid character and try to send the
  // message to the remaining - wrong! - address.
  // The new code will present an informational message box and deny sending.
  await doSendTest(kToInvalid, kToInvalid, true);
});

add_task(async function invalidCharNoAt() {
  // Test 4:
  // Bug 856506. invalid char without '@' causes crash.
  await doSendTest(kToInvalidWithoutDomain, kToInvalidWithoutDomain, true);
});
