summaryrefslogtreecommitdiffstats
path: root/comm/mail/extensions/openpgp/content/ui/enigmailKeyManager.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/extensions/openpgp/content/ui/enigmailKeyManager.js')
-rw-r--r--comm/mail/extensions/openpgp/content/ui/enigmailKeyManager.js1442
1 files changed, 1442 insertions, 0 deletions
diff --git a/comm/mail/extensions/openpgp/content/ui/enigmailKeyManager.js b/comm/mail/extensions/openpgp/content/ui/enigmailKeyManager.js
new file mode 100644
index 0000000000..e72cf8e6bc
--- /dev/null
+++ b/comm/mail/extensions/openpgp/content/ui/enigmailKeyManager.js
@@ -0,0 +1,1442 @@
+/*
+ * 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 https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+/* global GetEnigmailSvc, EnigRevokeKey */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+var { EnigmailCore } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/core.jsm"
+);
+var { EnigmailStreams } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/streams.jsm"
+);
+var { EnigmailFuncs } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/funcs.jsm"
+);
+var { EnigmailWindows } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/windows.jsm"
+);
+var { EnigmailKeyServer } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/keyserver.jsm"
+);
+var { EnigmailWks } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/webKey.jsm"
+);
+var { EnigmailCryptoAPI } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/cryptoAPI.jsm"
+);
+var { KeyLookupHelper } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/keyLookupHelper.jsm"
+);
+var { EnigmailTrust } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/trust.jsm"
+);
+var { PgpSqliteDb2 } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/sqliteDb.jsm"
+);
+var { EnigmailLog } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/log.jsm"
+);
+var { EnigmailKeyRing } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/keyRing.jsm"
+);
+var { EnigmailKey } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/key.jsm"
+);
+var { EnigmailConstants } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/constants.jsm"
+);
+var { EnigmailDialog } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/dialog.jsm"
+);
+var { EnigmailKeyserverURIs } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/keyserverUris.jsm"
+);
+
+const ENIG_KEY_EXPIRED = "e";
+const ENIG_KEY_REVOKED = "r";
+const ENIG_KEY_INVALID = "i";
+const ENIG_KEY_DISABLED = "d";
+const ENIG_KEY_NOT_VALID =
+ ENIG_KEY_EXPIRED + ENIG_KEY_REVOKED + ENIG_KEY_INVALID + ENIG_KEY_DISABLED;
+
+var l10n = new Localization(["messenger/openpgp/openpgp.ftl"], true);
+
+const INPUT = 0;
+const RESULT = 1;
+
+var gUserList;
+var gKeyList;
+var gEnigLastSelectedKeys = null;
+var gKeySortList = null;
+var gSearchInput = null;
+var gTreeChildren = null;
+var gShowInvalidKeys = null;
+var gShowOthersKeys = null;
+var gTimeoutId = null;
+
+function enigmailKeyManagerLoad() {
+ EnigmailLog.DEBUG("enigmailKeyManager.js: enigmailKeyManagerLoad\n");
+
+ // Close the key manager if GnuPG is not available
+ if (!EnigmailCore.getService(window)) {
+ window.close();
+ return;
+ }
+
+ gUserList = document.getElementById("pgpKeyList");
+ gSearchInput = document.getElementById("filterKey");
+ gShowInvalidKeys = document.getElementById("showInvalidKeys");
+ gShowOthersKeys = document.getElementById("showOthersKeys");
+
+ window.addEventListener("reload-keycache", reloadKeys);
+ gSearchInput.addEventListener("keydown", event => {
+ switch (event.key) {
+ case "Escape":
+ event.target.value = "";
+ // fall through
+ case "Enter":
+ if (gTimeoutId) {
+ clearTimeout(gTimeoutId);
+ gTimeoutId = null;
+ }
+ gKeyListView.applyFilter(0);
+ event.preventDefault();
+ break;
+ default:
+ gTimeoutId = setTimeout(() => {
+ gKeyListView.applyFilter(0);
+ }, 200);
+ break;
+ }
+ });
+
+ gUserList.addEventListener("click", onListClick, true);
+ document.getElementById("statusText").value = l10n.formatValueSync(
+ "key-man-loading-keys"
+ );
+ document.getElementById("progressBar").style.visibility = "visible";
+ setTimeout(loadkeyList, 100);
+
+ gUserList.view = gKeyListView;
+ gSearchInput.focus();
+
+ // Dialog event listeners.
+ document.addEventListener("dialogaccept", onDialogAccept);
+ document.addEventListener("dialogcancel", onDialogClose);
+}
+
+function onDialogAccept() {
+ if (window.arguments[0].okCallback) {
+ window.arguments[0].okCallback();
+ }
+ window.close();
+}
+
+function onDialogClose() {
+ if (window.arguments[0].cancelCallback) {
+ window.arguments[0].cancelCallback();
+ }
+ window.close();
+}
+
+function loadkeyList() {
+ EnigmailLog.DEBUG("enigmailKeyManager.js: loadkeyList\n");
+
+ sortTree();
+ gKeyListView.applyFilter(0);
+ document.getElementById("pleaseWait").hidePopup();
+ document.getElementById("statusText").value = "";
+ document.getElementById("progressBar").style.visibility = "collapse";
+}
+
+function clearKeyCache() {
+ EnigmailKeyRing.clearCache();
+ refreshKeys();
+}
+
+function refreshKeys() {
+ EnigmailLog.DEBUG("enigmailKeyManager.js: refreshKeys\n");
+ var keyList = getSelectedKeys();
+ gEnigLastSelectedKeys = [];
+ for (var i = 0; i < keyList.length; i++) {
+ gEnigLastSelectedKeys[keyList[i]] = 1;
+ }
+
+ buildKeyList(true);
+}
+
+function reloadKeys() {
+ let i = 0;
+ let c = Components.stack;
+
+ while (c) {
+ if (c.name == "reloadKeys") {
+ i++;
+ }
+ c = c.caller;
+ }
+
+ // detect recursion and don't continue if too much recursion
+ // this can happen if the key list is empty
+ if (i < 4) {
+ buildKeyList(true);
+ }
+}
+
+function buildKeyList(refresh) {
+ EnigmailLog.DEBUG("enigmailKeyManager.js: buildKeyList\n");
+
+ var keyListObj = {};
+
+ if (refresh) {
+ EnigmailKeyRing.clearCache();
+ }
+
+ keyListObj = EnigmailKeyRing.getAllKeys(
+ window,
+ getSortColumn(),
+ getSortDirection()
+ );
+
+ if (!keyListObj.keySortList) {
+ return;
+ }
+
+ gKeyList = keyListObj.keyList;
+ gKeySortList = keyListObj.keySortList;
+
+ gKeyListView.keysRefreshed();
+}
+
+function getSelectedKeys() {
+ let selList = [];
+ let rangeCount = gUserList.view.selection.getRangeCount();
+ for (let i = 0; i < rangeCount; i++) {
+ let start = {};
+ let end = {};
+ gUserList.view.selection.getRangeAt(i, start, end);
+ for (let c = start.value; c <= end.value; c++) {
+ try {
+ //selList.push(gUserList.view.getItemAtIndex(c).getAttribute("keyNum"));
+ selList.push(gKeyListView.getFilteredRow(c).keyNum);
+ } catch (ex) {
+ return [];
+ }
+ }
+ }
+ return selList;
+}
+
+function getSelectedKeyIds() {
+ let keyList = getSelectedKeys();
+
+ let a = [];
+ for (let i in keyList) {
+ a.push(gKeyList[keyList[i]].keyId);
+ }
+
+ return a;
+}
+
+function enigmailKeyMenu() {
+ var keyList = getSelectedKeys();
+
+ let haveSecretForAll;
+ if (keyList.length == 0) {
+ haveSecretForAll = false;
+ } else {
+ haveSecretForAll = true;
+ for (let key of keyList) {
+ if (!gKeyList[key].secretAvailable) {
+ haveSecretForAll = false;
+ break;
+ }
+ }
+ }
+
+ let singleSecretSelected = keyList.length == 1 && haveSecretForAll;
+
+ // Make the selected key count available to translations.
+ for (let el of document.querySelectorAll(".enigmail-bulk-key-operation")) {
+ el.setAttribute(
+ "data-l10n-args",
+ JSON.stringify({ count: keyList.length })
+ );
+ }
+
+ document.getElementById("backupSecretKey").disabled = !haveSecretForAll;
+ document.getElementById("uploadToServer").disabled = !singleSecretSelected;
+
+ document.getElementById("revokeKey").disabled =
+ keyList.length != 1 || !gKeyList[keyList[0]].secretAvailable;
+ document.getElementById("ctxRevokeKey").hidden =
+ keyList.length != 1 || !gKeyList[keyList[0]].secretAvailable;
+
+ document.getElementById("importFromClipbrd").disabled =
+ !Services.clipboard.hasDataMatchingFlavors(
+ ["text/plain"],
+ Ci.nsIClipboard.kGlobalClipboard
+ );
+
+ for (let item of document.querySelectorAll(
+ ".requires-single-key-selection"
+ )) {
+ item.disabled = keyList.length != 1;
+ }
+
+ for (let item of document.querySelectorAll(".requires-key-selection")) {
+ item.disabled = keyList.length == 0;
+ }
+
+ // Disable the "Generate key" menu item if no mail account is available.
+ document
+ .getElementById("genKey")
+ .setAttribute("disabled", MailServices.accounts.defaultAccount == null);
+
+ // Disable the context menu if no keys are selected.
+ return keyList.length > 0;
+}
+
+function onListClick(event) {
+ if (event.detail > 2) {
+ return;
+ }
+
+ if (event.type === "click") {
+ // Mouse event
+ let { col } = gUserList.getCellAt(event.clientX, event.clientY);
+
+ if (!col) {
+ // not clicked on a valid column (e.g. scrollbar)
+ return;
+ }
+ }
+
+ if (event.detail != 2) {
+ return;
+ }
+
+ // do not propagate double clicks
+ event.stopPropagation();
+ enigmailKeyDetails();
+}
+
+function enigmailSelectAllKeys() {
+ gUserList.view.selection.selectAll();
+}
+
+/**
+ * Open the Key Properties subdialog.
+ *
+ * @param {string|null} keyId - Optional ID of the selected OpenPGP Key.
+ */
+function enigmailKeyDetails(keyId = null) {
+ if (!keyId) {
+ let keyList = getSelectedKeys();
+ // Interrupt if we don't have a single selected key nor a key was passed.
+ if (keyList.length != 1) {
+ return;
+ }
+ keyId = gKeyList[keyList[0]].keyId;
+ }
+
+ if (EnigmailWindows.openKeyDetails(window, keyId, false)) {
+ refreshKeys();
+ }
+}
+
+async function enigmailDeleteKey() {
+ var keyList = getSelectedKeys();
+ var deleteSecret = false;
+
+ if (keyList.length == 1) {
+ // one key selected
+ var userId = gKeyList[keyList[0]].userId;
+ if (gKeyList[keyList[0]].secretAvailable) {
+ if (
+ !EnigmailDialog.confirmDlg(
+ window,
+ l10n.formatValueSync("delete-secret-key", {
+ userId,
+ }),
+ l10n.formatValueSync("dlg-button-delete")
+ )
+ ) {
+ return;
+ }
+ deleteSecret = true;
+ } else if (
+ !EnigmailDialog.confirmDlg(
+ window,
+ l10n.formatValueSync("delete-pub-key", {
+ userId,
+ }),
+ l10n.formatValueSync("dlg-button-delete")
+ )
+ ) {
+ return;
+ }
+ } else {
+ // several keys selected
+ for (var i = 0; i < keyList.length; i++) {
+ if (gKeyList[keyList[i]].secretAvailable) {
+ deleteSecret = true;
+ }
+ }
+
+ if (deleteSecret) {
+ if (
+ !EnigmailDialog.confirmDlg(
+ window,
+ l10n.formatValueSync("delete-mix"),
+ l10n.formatValueSync("dlg-button-delete")
+ )
+ ) {
+ return;
+ }
+ } else if (
+ !EnigmailDialog.confirmDlg(
+ window,
+ l10n.formatValueSync("delete-selected-pub-key"),
+ l10n.formatValueSync("dlg-button-delete")
+ )
+ ) {
+ return;
+ }
+ }
+
+ const cApi = EnigmailCryptoAPI();
+ for (let j in keyList) {
+ let fpr = gKeyList[keyList[j]].fpr;
+ await cApi.deleteKey(fpr, deleteSecret);
+ await PgpSqliteDb2.deleteAcceptance(fpr);
+ }
+ clearKeyCache();
+ gUserList.view.selection.clearSelection();
+}
+
+async function enigCreateKeyMsg() {
+ var keyList = getSelectedKeyIds();
+ var tmpFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ tmpFile.append("key.asc");
+ tmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
+
+ // save file
+ var exitCodeObj = {};
+ var errorMsgObj = {};
+
+ var keyIdArray = [];
+ for (let id of keyList) {
+ keyIdArray.push("0x" + id);
+ }
+
+ await EnigmailKeyRing.extractPublicKeys(
+ keyIdArray, // full
+ null,
+ null,
+ tmpFile,
+ exitCodeObj,
+ errorMsgObj
+ );
+ if (exitCodeObj.value !== 0) {
+ EnigmailDialog.alert(window, errorMsgObj.value);
+ return;
+ }
+
+ // create attachment
+ var ioServ = Services.io;
+ var tmpFileURI = ioServ.newFileURI(tmpFile);
+ var keyAttachment = Cc[
+ "@mozilla.org/messengercompose/attachment;1"
+ ].createInstance(Ci.nsIMsgAttachment);
+ keyAttachment.url = tmpFileURI.spec;
+ if (keyList.length == 1) {
+ keyAttachment.name = "0x" + keyList[0] + ".asc";
+ } else {
+ keyAttachment.name = "pgpkeys.asc";
+ }
+ keyAttachment.temporary = true;
+ keyAttachment.contentType = "application/pgp-keys";
+
+ // create Msg
+ var msgCompFields = Cc[
+ "@mozilla.org/messengercompose/composefields;1"
+ ].createInstance(Ci.nsIMsgCompFields);
+ msgCompFields.addAttachment(keyAttachment);
+
+ var msgCompSvc = Cc["@mozilla.org/messengercompose;1"].getService(
+ Ci.nsIMsgComposeService
+ );
+
+ var msgCompParam = Cc[
+ "@mozilla.org/messengercompose/composeparams;1"
+ ].createInstance(Ci.nsIMsgComposeParams);
+ msgCompParam.composeFields = msgCompFields;
+ msgCompParam.identity = EnigmailFuncs.getDefaultIdentity();
+ msgCompParam.type = Ci.nsIMsgCompType.New;
+ msgCompParam.format = Ci.nsIMsgCompFormat.Default;
+ msgCompParam.originalMsgURI = "";
+ msgCompSvc.OpenComposeWindowWithParams("", msgCompParam);
+}
+
+async function enigmailRevokeKey() {
+ var keyList = getSelectedKeys();
+ let keyInfo = gKeyList[keyList[0]];
+ EnigRevokeKey(keyInfo, function (success) {
+ if (success) {
+ refreshKeys();
+ }
+ });
+}
+
+async function enigmailExportKeys(which) {
+ let exportSecretKey = which == "secret";
+ var keyList = getSelectedKeys();
+ var defaultFileName;
+
+ if (keyList.length == 1) {
+ let extension = exportSecretKey ? "secret.asc" : "public.asc";
+ defaultFileName = gKeyList[keyList[0]].userId.replace(/[<>]/g, "");
+ defaultFileName =
+ defaultFileName +
+ "-" +
+ `(0x${gKeyList[keyList[0]].keyId})` +
+ "-" +
+ extension;
+ } else {
+ let id = exportSecretKey
+ ? "default-pub-sec-key-filename"
+ : "default-pub-key-filename";
+ defaultFileName = l10n.formatValueSync(id) + ".asc";
+ }
+
+ if (exportSecretKey) {
+ var fprArray = [];
+ for (let id of keyList) {
+ fprArray.push(gKeyList[id].fpr);
+ }
+ EnigmailKeyRing.backupSecretKeysInteractive(
+ window,
+ defaultFileName,
+ fprArray
+ );
+ } else {
+ let keyList2 = getSelectedKeyIds();
+ var keyIdArray = [];
+ for (let id of keyList2) {
+ keyIdArray.push("0x" + id);
+ }
+ await EnigmailKeyRing.exportPublicKeysInteractive(
+ window,
+ defaultFileName,
+ keyIdArray
+ );
+ }
+}
+
+async function enigmailImportFromClipbrd() {
+ if (
+ !EnigmailDialog.confirmDlg(
+ window,
+ l10n.formatValueSync("import-from-clip"),
+ l10n.formatValueSync("key-man-button-import")
+ )
+ ) {
+ return;
+ }
+
+ let cBoardContent = await navigator.clipboard.readText();
+ var errorMsgObj = {};
+ var preview = await EnigmailKey.getKeyListFromKeyBlock(
+ cBoardContent,
+ errorMsgObj,
+ true,
+ true,
+ false
+ );
+ // should we allow importing secret keys?
+ if (preview && preview.length > 0) {
+ let confirmImport = false;
+ let outParam = {};
+ confirmImport = EnigmailDialog.confirmPubkeyImport(
+ window,
+ preview,
+ outParam
+ );
+ if (confirmImport) {
+ // import
+ EnigmailKeyRing.importKey(
+ window,
+ false,
+ cBoardContent,
+ false,
+ "",
+ errorMsgObj,
+ null,
+ false,
+ [],
+ true,
+ outParam.acceptance
+ );
+ var keyList = preview.map(function (a) {
+ return a.id;
+ });
+ EnigmailDialog.keyImportDlg(window, keyList);
+ refreshKeys();
+ }
+ } else {
+ document.l10n.formatValue("preview-failed").then(value => {
+ EnigmailDialog.alert(window, value);
+ });
+ }
+}
+
+/**
+ * Places the fingerprint of each selected key onto the keyboard.
+ */
+async function copyOpenPGPFingerPrints() {
+ let fprs = getSelectedKeys()
+ .map(idx => gKeyList[idx].fpr)
+ .join("\n");
+ return navigator.clipboard.writeText(fprs);
+}
+
+/**
+ * Places the key id of each key selected onto the clipboard.
+ */
+async function copyOpenPGPKeyIds() {
+ let ids = getSelectedKeyIds();
+ return navigator.clipboard.writeText(ids.map(id => `0x${id}`).join("\n"));
+}
+
+async function enigmailCopyToClipbrd() {
+ var keyList = getSelectedKeyIds();
+ if (keyList.length === 0) {
+ document.l10n.formatValue("no-key-selected").then(value => {
+ EnigmailDialog.info(window, value);
+ });
+ return;
+ }
+ var exitCodeObj = {};
+ var errorMsgObj = {};
+
+ var keyIdArray = [];
+ for (let id of keyList) {
+ keyIdArray.push("0x" + id);
+ }
+
+ let keyData = await EnigmailKeyRing.extractPublicKeys(
+ keyIdArray, // full
+ null,
+ null,
+ null,
+ exitCodeObj,
+ errorMsgObj
+ );
+ if (exitCodeObj.value !== 0) {
+ l10n.formatValue("copy-to-clipbrd-failed").then(value => {
+ EnigmailDialog.alert(window, value);
+ });
+ return;
+ }
+ navigator.clipboard
+ .writeText(keyData)
+ .then(() => {
+ l10n.formatValue("copy-to-clipbrd-ok").then(value => {
+ EnigmailDialog.info(window, value);
+ });
+ })
+ .catch(err => {
+ l10n.formatValue("copy-to-clipbrd-failed").then(value => {
+ EnigmailDialog.alert(window, value);
+ });
+ });
+}
+
+async function enigmailSearchKey() {
+ var result = {
+ value: "",
+ };
+ if (
+ !Services.prompt.prompt(
+ window,
+ l10n.formatValueSync("enig-prompt"),
+ l10n.formatValueSync("openpgp-key-man-discover-prompt"),
+ result,
+ "",
+ {}
+ )
+ ) {
+ return;
+ }
+
+ result.value = result.value.trim();
+
+ let imported = false;
+ if (EnigmailFuncs.stringLooksLikeEmailAddress(result.value)) {
+ imported = await KeyLookupHelper.lookupAndImportByEmail(
+ "interactive-import",
+ window,
+ result.value,
+ true
+ );
+ } else {
+ imported = await KeyLookupHelper.lookupAndImportByKeyID(
+ "interactive-import",
+ window,
+ result.value,
+ true
+ );
+ }
+
+ if (imported) {
+ refreshKeys();
+ }
+}
+
+async function enigmailUploadKey() {
+ // Always upload to the first configured keyserver with a supported protocol.
+ let selKeyList = getSelectedKeys();
+ if (selKeyList.length != 1) {
+ return;
+ }
+
+ let keyId = gKeyList[selKeyList[0]].keyId;
+ let ks = EnigmailKeyserverURIs.getUploadKeyServer();
+
+ let ok = await EnigmailKeyServer.upload(keyId, ks);
+ document.l10n
+ .formatValue(ok ? "openpgp-key-publish-ok" : "openpgp-key-publish-fail", {
+ keyserver: ks,
+ })
+ .then(value => {
+ EnigmailDialog.alert(window, value);
+ });
+}
+
+/*
+function enigmailUploadToWkd() {
+ let selKeyList = getSelectedKeys();
+ let keyList = [];
+ for (let i = 0; i < selKeyList.length; i++) {
+ keyList.push(gKeyList[selKeyList[i]]);
+ }
+
+ EnigmailWks.wksUpload(keyList, window)
+ .then(result => {
+ if (result.length > 0) {
+ EnigmailDialog.info(window, "Key(s) sent successfully");
+ } else if (keyList.length === 1) {
+ EnigmailDialog.alert(
+ window,
+ "Sending of keys failed" +
+ "\n\n" +
+ "The key %S does not have a WKS identity.".replace("%S", keyList[0].userId)
+ );
+ } else {
+ EnigmailDialog.alert(
+ window,
+ "The upload was not successful - your provider does not seem to support WKS."
+ );
+ }
+ })
+ .catch(error => {
+ EnigmailDialog.alert(
+ "Sending of keys failed" + "\n" + error
+ );
+ });
+}
+*/
+
+function enigmailImportKeysFromUrl() {
+ var result = {
+ value: "",
+ };
+ if (
+ !Services.prompt.prompt(
+ window,
+ l10n.formatValueSync("enig-prompt"),
+ l10n.formatValueSync("import-from-url"),
+ result,
+ "",
+ {}
+ )
+ ) {
+ return;
+ }
+ var p = new Promise(function (resolve, reject) {
+ var cbFunc = async function (data) {
+ EnigmailLog.DEBUG("enigmailImportKeysFromUrl: _cbFunc()\n");
+ var errorMsgObj = {};
+
+ var preview = await EnigmailKey.getKeyListFromKeyBlock(
+ data,
+ errorMsgObj,
+ true,
+ true,
+ false
+ );
+ // should we allow importing secret keys?
+ if (preview && preview.length > 0) {
+ let confirmImport = false;
+ let outParam = {};
+ confirmImport = EnigmailDialog.confirmPubkeyImport(
+ window,
+ preview,
+ outParam
+ );
+ if (confirmImport) {
+ EnigmailKeyRing.importKey(
+ window,
+ false,
+ data,
+ false,
+ "",
+ errorMsgObj,
+ null,
+ false,
+ [],
+ true,
+ outParam.acceptance
+ );
+ errorMsgObj.preview = preview;
+ resolve(errorMsgObj);
+ }
+ } else {
+ EnigmailDialog.alert(
+ window,
+ await document.l10n.formatValue("preview-failed")
+ );
+ }
+ };
+
+ try {
+ var bufferListener = EnigmailStreams.newStringStreamListener(cbFunc);
+ var msgUri = Services.io.newURI(result.value.trim());
+
+ var channel = EnigmailStreams.createChannel(msgUri);
+ channel.asyncOpen(bufferListener, msgUri);
+ } catch (ex) {
+ var err = {
+ value: ex,
+ };
+ reject(err);
+ }
+ });
+
+ p.then(function (errorMsgObj) {
+ var keyList = errorMsgObj.preview.map(function (a) {
+ return a.id;
+ });
+ EnigmailDialog.keyImportDlg(window, keyList);
+ refreshKeys();
+ }).catch(async function (reason) {
+ EnigmailDialog.alert(
+ window,
+ await document.l10n.formatValue("general-error", {
+ reason: reason.value,
+ })
+ );
+ });
+}
+
+function initiateAcKeyTransfer() {
+ EnigmailWindows.inititateAcSetupMessage();
+}
+
+//
+// ----- key filtering functionality -----
+//
+
+function determineHiddenKeys(keyObj, showInvalidKeys, showOthersKeys) {
+ var show = true;
+
+ const INVALID_KEYS = "ierdD";
+
+ if (
+ !showInvalidKeys &&
+ INVALID_KEYS.includes(EnigmailTrust.getTrustCode(keyObj))
+ ) {
+ show = false;
+ }
+
+ if (!showOthersKeys && !keyObj.secretAvailable) {
+ show = false;
+ }
+
+ return show;
+}
+
+function getSortDirection() {
+ return gUserList.getAttribute("sortDirection") == "ascending" ? 1 : -1;
+}
+
+function sortTree(column) {
+ var columnName;
+ var order = getSortDirection();
+
+ //if the column is passed and it's already sorted by that column, reverse sort
+ if (column) {
+ columnName = column.id;
+ if (gUserList.getAttribute("sortResource") == columnName) {
+ order *= -1;
+ } else {
+ document
+ .getElementById(gUserList.getAttribute("sortResource"))
+ .removeAttribute("sortDirection");
+ order = 1;
+ }
+ } else {
+ columnName = gUserList.getAttribute("sortResource");
+ }
+ gUserList.setAttribute(
+ "sortDirection",
+ order == 1 ? "ascending" : "descending"
+ );
+ let col = document.getElementById(columnName);
+ if (col) {
+ col.setAttribute("sortDirection", order == 1 ? "ascending" : "descending");
+ gUserList.setAttribute("sortResource", columnName);
+ } else {
+ gUserList.setAttribute("sortResource", "enigUserNameCol");
+ }
+ buildKeyList(false);
+}
+
+function getSortColumn() {
+ switch (gUserList.getAttribute("sortResource")) {
+ case "enigUserNameCol":
+ return "userid";
+ case "keyCol":
+ return "keyid";
+ case "createdCol":
+ return "created";
+ case "expCol":
+ return "expiry";
+ case "fprCol":
+ return "fpr";
+ default:
+ return "?";
+ }
+}
+
+/**
+ * Open the OpenPGP Key Wizard to generate a new key or import secret keys.
+ *
+ * @param {boolean} isImport - If the keyWizard should automatically switch to
+ * the import or create screen as requested by the user.
+ */
+function openKeyWizard(isImport = false) {
+ let args = {
+ gSubDialog: null,
+ cancelCallback: clearKeyCache,
+ okCallback: clearKeyCache,
+ okImportCallback: clearKeyCache,
+ okExternalCallback: clearKeyCache,
+ keyDetailsDialog: enigmailKeyDetails,
+ isCreate: !isImport,
+ isImport,
+ };
+
+ window.browsingContext.topChromeWindow.openDialog(
+ "chrome://openpgp/content/ui/keyWizard.xhtml",
+ "enigmail:KeyWizard",
+ "dialog,modal,centerscreen,resizable",
+ args
+ );
+}
+
+/***************************** TreeView for user list ***********************************/
+/**
+ * gKeyListView implements the nsITreeView interface for the displayed list.
+ *
+ * For speed reasons, we use two lists:
+ * - keyViewList: contains the full list of pointers to all keys and rows that are
+ * potentially displayed ordered according to the sort column
+ * - keyFilterList: contains the indexes to keyViewList of the keys that are displayed
+ * according to the current filter criteria.
+ */
+var gKeyListView = {
+ keyViewList: [],
+ keyFilterList: [],
+
+ //// nsITreeView implementation
+
+ rowCount: 0,
+ selection: null,
+
+ canDrop(index, orientation, dataTransfer) {
+ return false;
+ },
+
+ cycleCell(row, col) {},
+ cycleHeader(col) {},
+ drop(row, orientation, dataTransfer) {},
+
+ getCellProperties(row, col) {
+ let r = this.getFilteredRow(row);
+ if (!r) {
+ return "";
+ }
+
+ let keyObj = gKeyList[r.keyNum];
+ if (!keyObj) {
+ return "";
+ }
+
+ let keyTrustStyle = "";
+
+ switch (r.rowType) {
+ case "key":
+ case "uid":
+ switch (keyObj.keyTrust) {
+ case "q":
+ keyTrustStyle = "enigmail_keyValid_unknown";
+ break;
+ case "r":
+ keyTrustStyle = "enigmail_keyValid_revoked";
+ break;
+ case "e":
+ keyTrustStyle = "enigmail_keyValid_expired";
+ break;
+ case "n":
+ keyTrustStyle = "enigmail_keyTrust_untrusted";
+ break;
+ case "m":
+ keyTrustStyle = "enigmail_keyTrust_marginal";
+ break;
+ case "f":
+ keyTrustStyle = "enigmail_keyTrust_full";
+ break;
+ case "u":
+ keyTrustStyle = "enigmail_keyTrust_ultimate";
+ break;
+ case "-":
+ keyTrustStyle = "enigmail_keyTrust_unknown";
+ break;
+ default:
+ keyTrustStyle = "enigmail_keyTrust_unknown";
+ break;
+ }
+
+ if (
+ keyObj.keyTrust.length > 0 &&
+ ENIG_KEY_NOT_VALID.includes(keyObj.keyTrust.charAt(0))
+ ) {
+ keyTrustStyle += " enigKeyInactive";
+ }
+
+ if (r.rowType === "key" && keyObj.secretAvailable) {
+ keyTrustStyle += " enigmailOwnKey";
+ }
+ break;
+ }
+
+ return keyTrustStyle;
+ },
+
+ getCellText(row, col) {
+ let r = this.getFilteredRow(row);
+ if (!r) {
+ return "";
+ }
+ let keyObj = gKeyList[r.keyNum];
+ if (!keyObj) {
+ return "???";
+ }
+
+ switch (r.rowType) {
+ case "key":
+ switch (col.id) {
+ case "enigUserNameCol":
+ return keyObj.userId;
+ case "keyCol":
+ return `0x${keyObj.keyId}`;
+ case "createdCol":
+ return keyObj.created;
+ case "expCol":
+ return keyObj.effectiveExpiry;
+ case "fprCol":
+ return keyObj.fprFormatted;
+ }
+ break;
+ case "uid":
+ switch (col.id) {
+ case "enigUserNameCol":
+ return keyObj.userIds[r.uidNum].userId;
+ }
+ break;
+ }
+
+ return "";
+ },
+ getCellValue(row, col) {
+ return "";
+ },
+ getColumnProperties(col) {
+ return "";
+ },
+
+ getImageSrc(row, col) {
+ let r = this.getFilteredRow(row);
+ if (!r) {
+ return null;
+ }
+ //let keyObj = gKeyList[r.keyNum];
+
+ return null;
+ },
+
+ /**
+ * indentation level for rows
+ */
+ getLevel(row) {
+ let r = this.getFilteredRow(row);
+ if (!r) {
+ return 0;
+ }
+
+ switch (r.rowType) {
+ case "key":
+ return 0;
+ case "uid":
+ return 1;
+ }
+
+ return 0;
+ },
+
+ getParentIndex(idx) {
+ return -1;
+ },
+ getProgressMode(row, col) {},
+
+ getRowProperties(row) {
+ return "";
+ },
+ hasNextSibling(rowIndex, afterIndex) {
+ return false;
+ },
+ isContainer(row) {
+ let r = this.getFilteredRow(row);
+ if (!r) {
+ return false;
+ }
+ switch (r.rowType) {
+ case "key":
+ return true;
+ }
+
+ return false;
+ },
+ isContainerEmpty(row) {
+ let r = this.getFilteredRow(row);
+ if (!r) {
+ return true;
+ }
+ switch (r.rowType) {
+ case "key":
+ return !r.hasSubUID;
+ }
+ return true;
+ },
+ isContainerOpen(row) {
+ return this.getFilteredRow(row).isOpen;
+ },
+ isEditable(row, col) {
+ return false;
+ },
+ isSelectable(row, col) {
+ return true;
+ },
+ isSeparator(index) {
+ return false;
+ },
+ isSorted() {
+ return false;
+ },
+ performAction(action) {},
+ performActionOnCell(action, row, col) {},
+ performActionOnRow(action, row) {},
+ selectionChanged() {},
+ // void setCellText(in long row, in nsITreeColumn col, in AString value);
+ // void setCellValue(in long row, in nsITreeColumn col, in AString value);
+ setTree(treebox) {
+ this.treebox = treebox;
+ },
+
+ toggleOpenState(row) {
+ let r = this.getFilteredRow(row);
+ if (!r) {
+ return;
+ }
+ let realRow = this.keyFilterList[row];
+ switch (r.rowType) {
+ case "key":
+ if (r.isOpen) {
+ let i = 0;
+ while (
+ this.getFilteredRow(row + 1 + i) &&
+ this.getFilteredRow(row + 1 + i).keyNum === r.keyNum
+ ) {
+ ++i;
+ }
+
+ this.keyViewList.splice(realRow + 1, i);
+ r.isOpen = false;
+ this.applyFilter(row);
+ } else {
+ this.appendUids("uid", r.keyNum, realRow, this.keyViewList[row]);
+
+ r.isOpen = true;
+ this.applyFilter(row);
+ }
+ break;
+ }
+ },
+
+ /**
+ * add UIDs for a given key to key view
+ *
+ * @param uidType: String - one of uid (user ID), uat (photo)
+ * @param keyNum: Number - index of key in gKeyList
+ * @param realRow: Number - index of row in keyViewList (i.e. without filter)
+ *
+ * @returns Number: number of UIDs added
+ */
+ appendUids(uidType, keyNum, realRow, parentRow) {
+ let keyObj = gKeyList[keyNum];
+ let uidAdded = 0;
+
+ for (let i = 0; i < keyObj.userIds.length; i++) {
+ if (keyObj.userIds[i].type === uidType) {
+ if (keyObj.userIds[i].userId == keyObj.userId) {
+ continue;
+ }
+ ++uidAdded;
+ this.keyViewList.splice(realRow + uidAdded, 0, {
+ rowType: uidType,
+ keyNum,
+ parent: parentRow,
+ uidNum: i,
+ });
+ }
+ }
+
+ return uidAdded;
+ },
+
+ /**
+ * Reload key list entirely
+ */
+ keysRefreshed() {
+ this.keyViewList = [];
+ this.keyFilterList = [];
+ for (let i = 0; i < gKeySortList.length; i++) {
+ this.keyViewList.push({
+ row: i,
+ rowType: "key",
+ fpr: gKeySortList[i].fpr,
+ keyNum: gKeySortList[i].keyNum,
+ isOpen: false,
+ hasSubUID: gKeyList[gKeySortList[i].keyNum].userIds.length > 1,
+ });
+ }
+
+ this.applyFilter(0);
+ let oldRowCount = this.rowCount;
+ this.rowCount = this.keyViewList.length;
+ gUserList.rowCountChanged(0, this.rowCount - oldRowCount);
+ },
+
+ /**
+ * If no search term is entered, decide which keys to display
+ *
+ * @returns array of keyNums (= display some keys) or null (= display ALL keys)
+ */
+ showOrHideAllKeys() {
+ var showInvalidKeys = gShowInvalidKeys.getAttribute("checked") == "true";
+ var showOthersKeys = gShowOthersKeys.getAttribute("checked") == "true";
+
+ document.getElementById("nothingFound").hidePopup();
+
+ if (showInvalidKeys && showOthersKeys) {
+ return null;
+ }
+
+ let keyShowList = [];
+ for (let i = 0; i < gKeyList.length; i++) {
+ if (determineHiddenKeys(gKeyList[i], showInvalidKeys, showOthersKeys)) {
+ keyShowList.push(i);
+ }
+ }
+
+ return keyShowList;
+ },
+
+ /**
+ * Search for keys that match filter criteria
+ *
+ * @returns array of keyNums (= display some keys) or null (= display ALL keys)
+ */
+ getFilteredKeys() {
+ let searchTxt = gSearchInput.value;
+
+ if (!searchTxt || searchTxt.length === 0) {
+ return this.showOrHideAllKeys();
+ }
+
+ if (!gKeyList) {
+ return [];
+ }
+ let showInvalidKeys = gShowInvalidKeys.getAttribute("checked") == "true";
+ let showOthersKeys = gShowOthersKeys.getAttribute("checked") == "true";
+
+ // skip leading 0x in case we search for a key:
+ if (searchTxt.length > 2 && searchTxt.substr(0, 2).toLowerCase() == "0x") {
+ searchTxt = searchTxt.substr(2);
+ }
+
+ searchTxt = searchTxt.toLowerCase();
+ searchTxt = searchTxt.replace(/^(\s*)(.*)/, "$2").replace(/\s+$/, ""); // trim spaces
+
+ // check if we search for a full fingerprint (with optional spaces every 4 letters)
+ var fpr = null;
+ if (searchTxt.length == 49) {
+ // possible fingerprint with spaces?
+ if (
+ searchTxt.search(/^[0-9a-f ]*$/) >= 0 &&
+ searchTxt[4] == " " &&
+ searchTxt[9] == " " &&
+ searchTxt[14] == " " &&
+ searchTxt[19] == " " &&
+ searchTxt[24] == " " &&
+ searchTxt[29] == " " &&
+ searchTxt[34] == " " &&
+ searchTxt[39] == " " &&
+ searchTxt[44] == " "
+ ) {
+ fpr = searchTxt.replace(/ /g, "");
+ }
+ } else if (searchTxt.length == 40) {
+ // possible fingerprint without spaces
+ if (searchTxt.search(/^[0-9a-f ]*$/) >= 0) {
+ fpr = searchTxt;
+ }
+ }
+
+ let keyShowList = [];
+
+ for (let i = 0; i < gKeyList.length; i++) {
+ let keyObj = gKeyList[i];
+ let uid = keyObj.userId;
+ let showKey = false;
+
+ // does a user ID (partially) match?
+ for (let idx = 0; idx < keyObj.userIds.length; idx++) {
+ uid = keyObj.userIds[idx].userId;
+ if (uid.toLowerCase().includes(searchTxt)) {
+ showKey = true;
+ }
+ }
+
+ // does the full fingerprint (without spaces) match?
+ // - no partial match check because this is special for the collapsed spaces inside the fingerprint
+ if (showKey === false && fpr && keyObj.fpr.toLowerCase() == fpr) {
+ showKey = true;
+ }
+ // does the fingerprint (partially) match?
+ if (showKey === false && keyObj.fpr.toLowerCase().includes(searchTxt)) {
+ showKey = true;
+ }
+ // does a sub key of (partially) match?
+ if (showKey === false) {
+ for (
+ let subKeyIdx = 0;
+ subKeyIdx < keyObj.subKeys.length;
+ subKeyIdx++
+ ) {
+ let subkey = keyObj.subKeys[subKeyIdx].keyId;
+ if (subkey.toLowerCase().includes(searchTxt)) {
+ showKey = true;
+ }
+ }
+ }
+ // take option to show invalid/untrusted... keys into account
+ if (
+ showKey &&
+ determineHiddenKeys(keyObj, showInvalidKeys, showOthersKeys)
+ ) {
+ keyShowList.push(i);
+ }
+ }
+
+ return keyShowList;
+ },
+
+ /**
+ * Trigger re-displaying the list of keys and apply a filter
+ *
+ * @param selectedRow: Number - the row that is currently selected or
+ * clicked on
+ */
+ applyFilter(selectedRow) {
+ let keyDisplayList = this.getFilteredKeys();
+
+ this.keyFilterList = [];
+ if (keyDisplayList === null) {
+ for (let i = 0; i < this.keyViewList.length; i++) {
+ this.keyFilterList.push(i);
+ }
+
+ this.adjustRowCount(this.keyViewList.length, selectedRow);
+ } else {
+ for (let i = 0; i < this.keyViewList.length; i++) {
+ if (keyDisplayList.includes(this.keyViewList[i].keyNum)) {
+ this.keyFilterList.push(i);
+ }
+ }
+
+ this.adjustRowCount(this.keyFilterList.length, selectedRow);
+ }
+ },
+
+ /**
+ * Re-calculate the row count and instruct the view to update
+ */
+ adjustRowCount(newRowCount, selectedRow) {
+ if (this.rowCount === newRowCount) {
+ gUserList.invalidate();
+ return;
+ }
+
+ let delta = newRowCount - this.rowCount;
+ this.rowCount = newRowCount;
+ gUserList.rowCountChanged(selectedRow, delta);
+ },
+
+ /**
+ * Determine the row object from the a filtered row number
+ *
+ * @param row: Number - row number of displayed (=filtered) list
+ * @returns Object: keyViewList entry of corresponding row
+ */
+ getFilteredRow(row) {
+ let r = this.keyFilterList[row];
+ if (r !== undefined) {
+ return this.keyViewList[r];
+ }
+ return null;
+ },
+
+ treebox: null,
+};