diff options
Diffstat (limited to 'comm/mail/extensions/openpgp/content/ui/enigmailKeyManager.js')
-rw-r--r-- | comm/mail/extensions/openpgp/content/ui/enigmailKeyManager.js | 1442 |
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, +}; |