summaryrefslogtreecommitdiffstats
path: root/comm/mail/extensions/openpgp/content/modules/mimeDecrypt.jsm
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/extensions/openpgp/content/modules/mimeDecrypt.jsm')
-rw-r--r--comm/mail/extensions/openpgp/content/modules/mimeDecrypt.jsm933
1 files changed, 933 insertions, 0 deletions
diff --git a/comm/mail/extensions/openpgp/content/modules/mimeDecrypt.jsm b/comm/mail/extensions/openpgp/content/modules/mimeDecrypt.jsm
new file mode 100644
index 0000000000..c927df5fba
--- /dev/null
+++ b/comm/mail/extensions/openpgp/content/modules/mimeDecrypt.jsm
@@ -0,0 +1,933 @@
+/* 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";
+
+const EXPORTED_SYMBOLS = ["EnigmailMimeDecrypt"];
+
+/**
+ * Module for handling PGP/MIME encrypted messages
+ * implemented as an XPCOM object
+ */
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+const { EnigmailSingletons } = ChromeUtils.import(
+ "chrome://openpgp/content/modules/singletons.jsm"
+);
+const { MimeParser } = ChromeUtils.import("resource:///modules/mimeParser.jsm");
+
+const lazy = {};
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ EnigmailConstants: "chrome://openpgp/content/modules/constants.jsm",
+ EnigmailCore: "chrome://openpgp/content/modules/core.jsm",
+ EnigmailCryptoAPI: "chrome://openpgp/content/modules/cryptoAPI.jsm",
+ EnigmailData: "chrome://openpgp/content/modules/data.jsm",
+ EnigmailDecryption: "chrome://openpgp/content/modules/decryption.jsm",
+ EnigmailFuncs: "chrome://openpgp/content/modules/funcs.jsm",
+ EnigmailLog: "chrome://openpgp/content/modules/log.jsm",
+ EnigmailMime: "chrome://openpgp/content/modules/mime.jsm",
+ EnigmailURIs: "chrome://openpgp/content/modules/uris.jsm",
+ EnigmailVerify: "chrome://openpgp/content/modules/mimeVerify.jsm",
+});
+
+XPCOMUtils.defineLazyGetter(lazy, "l10n", () => {
+ return new Localization(["messenger/openpgp/openpgp.ftl"], true);
+});
+
+const ENCODING_DEFAULT = 0;
+const ENCODING_BASE64 = 1;
+const ENCODING_QP = 2;
+
+const LAST_MSG = EnigmailSingletons.lastDecryptedMessage;
+
+var gDebugLogLevel = 3;
+
+var gNumProc = 0;
+
+var EnigmailMimeDecrypt = {
+ /**
+ * create a new instance of a PGP/MIME decryption handler
+ */
+ newPgpMimeHandler() {
+ return new MimeDecryptHandler();
+ },
+
+ /**
+ * Wrap the decrypted output into a message/rfc822 attachment
+ *
+ * @param {string} decryptingMimePartNum: requested MIME part number
+ * @param {object} uri: nsIURI object of the decrypted message
+ *
+ * @returns {string}: prefix for message data
+ */
+ pretendAttachment(decryptingMimePartNum, uri) {
+ if (decryptingMimePartNum === "1" || !uri) {
+ return "";
+ }
+
+ let msg = "";
+ let mimePartNumber = lazy.EnigmailMime.getMimePartNumber(uri.spec);
+
+ if (mimePartNumber === decryptingMimePartNum + ".1") {
+ msg =
+ 'Content-Type: message/rfc822; name="attachment.eml"\r\n' +
+ "Content-Transfer-Encoding: 7bit\r\n" +
+ 'Content-Disposition: attachment; filename="attachment.eml"\r\n\r\n';
+
+ try {
+ let dbHdr = uri.QueryInterface(Ci.nsIMsgMessageUrl).messageHeader;
+ if (dbHdr.subject) {
+ msg += `Subject: ${dbHdr.subject}\r\n`;
+ }
+ if (dbHdr.author) {
+ msg += `From: ${dbHdr.author}\r\n`;
+ }
+ if (dbHdr.recipients) {
+ msg += `To: ${dbHdr.recipients}\r\n`;
+ }
+ if (dbHdr.ccList) {
+ msg += `Cc: ${dbHdr.ccList}\r\n`;
+ }
+ } catch (x) {
+ console.debug(x);
+ }
+ }
+
+ return msg;
+ },
+};
+
+////////////////////////////////////////////////////////////////////
+// handler for PGP/MIME encrypted messages
+// data is processed from libmime -> nsPgpMimeProxy
+
+function MimeDecryptHandler() {
+ lazy.EnigmailLog.DEBUG("mimeDecrypt.jsm: MimeDecryptHandler()\n"); // always log this one
+ this.mimeSvc = null;
+ this.initOk = false;
+ this.boundary = "";
+ this.pipe = null;
+ this.closePipe = false;
+ this.statusStr = "";
+ this.outQueue = "";
+ this.dataLength = 0;
+ this.bytesWritten = 0;
+ this.mimePartCount = 0;
+ this.headerMode = 0;
+ this.xferEncoding = ENCODING_DEFAULT;
+ this.matchedPgpDelimiter = 0;
+ this.exitCode = null;
+ this.msgWindow = null;
+ this.msgUriSpec = null;
+ this.returnStatus = null;
+ this.proc = null;
+ this.statusDisplayed = false;
+ this.uri = null;
+ this.backgroundJob = false;
+ this.decryptedHeaders = {};
+ this.mimePartNumber = "";
+ this.allowNestedDecrypt = false;
+ this.dataIsBase64 = null;
+ this.base64Cache = "";
+}
+
+MimeDecryptHandler.prototype = {
+ inStream: Cc["@mozilla.org/scriptableinputstream;1"].createInstance(
+ Ci.nsIScriptableInputStream
+ ),
+
+ onStartRequest(request, uri) {
+ if (!lazy.EnigmailCore.getService()) {
+ // Ensure Enigmail is initialized
+ return;
+ }
+ lazy.EnigmailLog.DEBUG("mimeDecrypt.jsm: onStartRequest\n"); // always log this one
+
+ ++gNumProc;
+ if (gNumProc > Services.prefs.getIntPref("temp.openpgp.maxNumProcesses")) {
+ lazy.EnigmailLog.DEBUG(
+ "mimeDecrypt.jsm: number of parallel requests above threshold - ignoring request\n"
+ );
+ return;
+ }
+
+ this.initOk = true;
+ this.mimeSvc = request.QueryInterface(Ci.nsIPgpMimeProxy);
+ if ("mimePart" in this.mimeSvc) {
+ this.mimePartNumber = this.mimeSvc.mimePart;
+ } else {
+ this.mimePartNumber = "";
+ }
+
+ if ("allowNestedDecrypt" in this.mimeSvc) {
+ this.allowNestedDecrypt = this.mimeSvc.allowNestedDecrypt;
+ }
+
+ if (this.allowNestedDecrypt) {
+ // We want to ignore signature status of the top level part "1".
+ // Unfortunately, because of our streaming approach to process
+ // MIME content, the parent MIME part was already processed,
+ // and it could have already called into the header sink to set
+ // the signature status. Or, an async job could be currently
+ // running, and the call into the header sink could happen in
+ // the near future.
+ // That means, we must inform the header sink to forget status
+ // information it might have already received for MIME part "1",
+ // an in addition, remember that future information for "1" should
+ // be ignored.
+
+ EnigmailSingletons.messageReader.ignoreStatusFrom("1");
+ }
+
+ if ("messageURI" in this.mimeSvc) {
+ this.uri = this.mimeSvc.messageURI;
+ if (this.uri) {
+ lazy.EnigmailLog.DEBUG(
+ "mimeDecrypt.jsm: onStartRequest: uri='" + this.uri.spec + "'\n"
+ );
+ } else {
+ lazy.EnigmailLog.DEBUG("mimeDecrypt.jsm: onStartRequest: uri=null\n");
+ }
+ } else if (uri) {
+ this.uri = uri.QueryInterface(Ci.nsIURI);
+ lazy.EnigmailLog.DEBUG(
+ "mimeDecrypt.jsm: onStartRequest: uri='" + this.uri.spec + "'\n"
+ );
+ }
+ this.pipe = null;
+ this.closePipe = false;
+ this.exitCode = null;
+ this.msgWindow = lazy.EnigmailVerify.lastWindow;
+ this.msgUriSpec = lazy.EnigmailVerify.lastMsgUri;
+
+ this.statusDisplayed = false;
+ this.returnStatus = null;
+ this.dataLength = 0;
+ this.decryptedData = "";
+ this.mimePartCount = 0;
+ this.bytesWritten = 0;
+ this.matchedPgpDelimiter = 0;
+ this.dataIsBase64 = null;
+ this.base64Cache = "";
+ this.outQueue = "";
+ this.statusStr = "";
+ this.headerMode = 0;
+ this.decryptedHeaders = {};
+ this.xferEncoding = ENCODING_DEFAULT;
+ this.boundary = lazy.EnigmailMime.getBoundary(this.mimeSvc.contentType);
+
+ let now = Date.now();
+ let timeoutReached =
+ EnigmailSingletons.lastMessageDecryptTime &&
+ now - EnigmailSingletons.lastMessageDecryptTime > 10000;
+ if (timeoutReached || !this.isReloadingLastMessage()) {
+ EnigmailSingletons.clearLastDecryptedMessage();
+ EnigmailSingletons.lastMessageDecryptTime = now;
+ }
+ },
+
+ processData(data) {
+ // detect MIME part boundary
+ if (data.includes(this.boundary)) {
+ LOCAL_DEBUG("mimeDecrypt.jsm: processData: found boundary\n");
+ ++this.mimePartCount;
+ this.headerMode = 1;
+ return;
+ }
+
+ // found PGP/MIME "body"
+ if (this.mimePartCount == 2) {
+ if (this.headerMode == 1) {
+ // we are in PGP/MIME main part headers
+ if (data.search(/\r|\n/) === 0) {
+ // end of Mime-part headers reached
+ this.headerMode = 2;
+ } else if (data.search(/^content-transfer-encoding:\s*/i) >= 0) {
+ // extract content-transfer-encoding
+ data = data.replace(/^content-transfer-encoding:\s*/i, "");
+ data = data.replace(/;.*/, "").toLowerCase().trim();
+ if (data.search(/base64/i) >= 0) {
+ this.xferEncoding = ENCODING_BASE64;
+ } else if (data.search(/quoted-printable/i) >= 0) {
+ this.xferEncoding = ENCODING_QP;
+ }
+ }
+ // else: PGP/MIME main part body
+ } else if (this.xferEncoding == ENCODING_QP) {
+ this.cacheData(lazy.EnigmailData.decodeQuotedPrintable(data));
+ } else {
+ this.cacheData(data);
+ }
+ }
+ },
+
+ onDataAvailable(req, stream, offset, count) {
+ // get data from libmime
+ if (!this.initOk) {
+ return;
+ }
+ this.inStream.init(stream);
+
+ if (count > 0) {
+ var data = this.inStream.read(count);
+
+ if (this.mimePartCount == 0 && this.dataIsBase64 === null) {
+ // try to determine if this could be a base64 encoded message part
+ this.dataIsBase64 = this.isBase64Encoding(data);
+ }
+
+ if (!this.dataIsBase64) {
+ if (data.search(/[\r\n][^\r\n]+[\r\n]/) >= 0) {
+ // process multi-line data line by line
+ let lines = data.replace(/\r\n/g, "\n").split(/\n/);
+
+ for (let i = 0; i < lines.length; i++) {
+ this.processData(lines[i] + "\r\n");
+ }
+ } else {
+ this.processData(data);
+ }
+ } else {
+ this.base64Cache += data;
+ }
+ }
+ },
+
+ /**
+ * Try to determine if data is base64 endoded
+ */
+ isBase64Encoding(str) {
+ let ret = false;
+
+ str = str.replace(/[\r\n]/, "");
+ if (str.search(/^[A-Za-z0-9+/=]+$/) === 0) {
+ let excess = str.length % 4;
+ str = str.substring(0, str.length - excess);
+
+ try {
+ atob(str);
+ // if the conversion succeeds, we have a base64 encoded message
+ ret = true;
+ } catch (ex) {
+ // not a base64 encoded
+ console.debug(ex);
+ }
+ }
+
+ return ret;
+ },
+
+ // cache encrypted data
+ cacheData(str) {
+ if (gDebugLogLevel > 4) {
+ LOCAL_DEBUG("mimeDecrypt.jsm: cacheData: " + str.length + "\n");
+ }
+
+ this.outQueue += str;
+ },
+
+ processBase64Message() {
+ LOCAL_DEBUG("mimeDecrypt.jsm: processBase64Message\n");
+
+ try {
+ this.base64Cache = lazy.EnigmailData.decodeBase64(this.base64Cache);
+ } catch (ex) {
+ // if decoding failed, try non-encoded version
+ console.debug(ex);
+ }
+
+ let lines = this.base64Cache.replace(/\r\n/g, "\n").split(/\n/);
+
+ for (let i = 0; i < lines.length; i++) {
+ this.processData(lines[i] + "\r\n");
+ }
+ },
+
+ /**
+ * Determine if we are reloading the same message as the previous one
+ *
+ * @returns Boolean
+ */
+ isReloadingLastMessage() {
+ if (!this.uri) {
+ return false;
+ }
+ if (!LAST_MSG.lastMessageURI) {
+ return false;
+ }
+ if ("lastMessageData" in LAST_MSG && LAST_MSG.lastMessageData === "") {
+ return false;
+ }
+
+ let currMsg = lazy.EnigmailURIs.msgIdentificationFromUrl(this.uri);
+
+ if (
+ LAST_MSG.lastMessageURI.folder === currMsg.folder &&
+ LAST_MSG.lastMessageURI.msgNum === currMsg.msgNum
+ ) {
+ return true;
+ }
+
+ return false;
+ },
+
+ onStopRequest(request, status, dummy) {
+ LOCAL_DEBUG("mimeDecrypt.jsm: onStopRequest\n");
+ --gNumProc;
+ if (!this.initOk) {
+ return;
+ }
+
+ if (this.dataIsBase64) {
+ this.processBase64Message();
+ }
+
+ this.msgWindow = lazy.EnigmailVerify.lastWindow;
+ this.msgUriSpec = lazy.EnigmailVerify.lastMsgUri;
+
+ let href = Services.wm.getMostRecentWindow(null)?.document?.location.href;
+
+ if (
+ href == "about:blank" ||
+ href == "chrome://messenger/content/viewSource.xhtml"
+ ) {
+ return;
+ }
+
+ let url = {};
+ let currMsg = lazy.EnigmailURIs.msgIdentificationFromUrl(this.uri);
+
+ this.backgroundJob = false;
+
+ if (this.uri) {
+ // return if not decrypting currently displayed message (except if
+ // printing, replying, etc)
+
+ this.backgroundJob =
+ this.uri.spec.search(/[&?]header=(print|quotebody)/) >= 0;
+
+ try {
+ if (!Services.prefs.getBoolPref("temp.openpgp.autoDecrypt")) {
+ // "decrypt manually" mode
+ let manUrl = {};
+
+ if (lazy.EnigmailVerify.getManualUri()) {
+ manUrl.value = lazy.EnigmailFuncs.getUrlFromUriSpec(
+ lazy.EnigmailVerify.getManualUri()
+ );
+ }
+
+ // print a message if not message explicitly decrypted
+ let currUrlSpec = this.uri.spec.replace(
+ /(\?.*)(number=[0-9]*)(&.*)?$/,
+ "?$2"
+ );
+ let manUrlSpec = manUrl.value.spec.replace(
+ /(\?.*)(number=[0-9]*)(&.*)?$/,
+ "?$2"
+ );
+
+ if (!this.backgroundJob && currUrlSpec.indexOf(manUrlSpec) !== 0) {
+ this.handleManualDecrypt();
+ return;
+ }
+ }
+
+ if (this.msgUriSpec) {
+ url.value = lazy.EnigmailFuncs.getUrlFromUriSpec(this.msgUriSpec);
+ }
+
+ if (
+ this.uri.spec.search(/[&?]header=[^&]+/) > 0 &&
+ this.uri.spec.search(/[&?]examineEncryptedParts=true/) < 0
+ ) {
+ if (
+ this.uri.spec.search(/[&?]header=(filter|enigmailFilter)(&.*)?$/) >
+ 0
+ ) {
+ lazy.EnigmailLog.DEBUG(
+ "mimeDecrypt.jsm: onStopRequest: detected incoming message processing\n"
+ );
+ return;
+ }
+ }
+
+ if (
+ this.uri.spec.search(/[&?]header=[^&]+/) < 0 &&
+ this.uri.spec.search(/[&?]part=[.0-9]+/) < 0 &&
+ this.uri.spec.search(/[&?]examineEncryptedParts=true/) < 0
+ ) {
+ if (this.uri && url && url.value) {
+ let fixedQueryRef = this.uri.pathQueryRef.replace(/&number=0$/, "");
+ if (
+ url.value.host !== this.uri.host ||
+ url.value.pathQueryRef !== fixedQueryRef
+ ) {
+ return;
+ }
+ }
+ }
+ } catch (ex) {
+ console.debug(ex);
+ lazy.EnigmailLog.writeException("mimeDecrypt.js", ex);
+ lazy.EnigmailLog.DEBUG(
+ "mimeDecrypt.jsm: error while processing " + this.msgUriSpec + "\n"
+ );
+ }
+ }
+
+ let spec = this.uri ? this.uri.spec : null;
+ lazy.EnigmailLog.DEBUG(
+ `mimeDecrypt.jsm: checking MIME structure for ${this.mimePartNumber} / ${spec}\n`
+ );
+
+ if (
+ !this.allowNestedDecrypt &&
+ !lazy.EnigmailMime.isRegularMimeStructure(
+ this.mimePartNumber,
+ spec,
+ false
+ )
+ ) {
+ EnigmailSingletons.addUriWithNestedEncryptedPart(this.msgUriSpec);
+ // ignore, do not display
+ return;
+ }
+
+ if (!this.isReloadingLastMessage()) {
+ if (this.xferEncoding == ENCODING_BASE64) {
+ this.outQueue = lazy.EnigmailData.decodeBase64(this.outQueue) + "\n";
+ }
+
+ let win = this.msgWindow;
+
+ if (!lazy.EnigmailDecryption.isReady(win)) {
+ return;
+ }
+
+ // limit output to 100 times message size to avoid DoS attack
+ let maxOutput = this.outQueue.length * 100;
+
+ lazy.EnigmailLog.DEBUG("mimeDecryp.jsm: starting decryption\n");
+ //EnigmailLog.DEBUG(this.outQueue + "\n");
+
+ let options = {
+ fromAddr: lazy.EnigmailDecryption.getFromAddr(win),
+ maxOutputLength: maxOutput,
+ };
+
+ if (!options.fromAddr) {
+ var win2 = Services.wm.getMostRecentWindow(null);
+ options.fromAddr = lazy.EnigmailDecryption.getFromAddr(win2);
+ }
+
+ const cApi = lazy.EnigmailCryptoAPI();
+ lazy.EnigmailLog.DEBUG(
+ "mimeDecrypt.jsm: got API: " + cApi.api_name + "\n"
+ );
+
+ // The processing of a contained signed message must be able to
+ // check that this parent object is encrypted. We set the msg ID
+ // early, despite the full results not yet being available.
+ LAST_MSG.lastMessageURI = currMsg;
+ LAST_MSG.mimePartNumber = this.mimePartNumber;
+
+ this.returnStatus = cApi.sync(cApi.decryptMime(this.outQueue, options));
+
+ if (!this.returnStatus) {
+ this.returnStatus = {
+ decryptedData: "",
+ exitCode: -1,
+ statusFlags: lazy.EnigmailConstants.DECRYPTION_FAILED,
+ };
+ }
+
+ if (
+ this.returnStatus.statusFlags & lazy.EnigmailConstants.DECRYPTION_OKAY
+ ) {
+ this.returnStatus.statusFlags |=
+ lazy.EnigmailConstants.PGP_MIME_ENCRYPTED;
+ }
+
+ if (this.returnStatus.exitCode) {
+ // Failure
+ if (this.returnStatus.decryptedData.length) {
+ // However, we got decrypted data.
+ // Did we get any verification failure flags?
+ // If yes, then conclude only verification failed.
+ if (
+ this.returnStatus.statusFlags &
+ (lazy.EnigmailConstants.BAD_SIGNATURE |
+ lazy.EnigmailConstants.UNCERTAIN_SIGNATURE |
+ lazy.EnigmailConstants.EXPIRED_SIGNATURE |
+ lazy.EnigmailConstants.EXPIRED_KEY_SIGNATURE)
+ ) {
+ this.returnStatus.statusFlags |=
+ lazy.EnigmailConstants.DECRYPTION_OKAY;
+ } else {
+ this.returnStatus.statusFlags |=
+ lazy.EnigmailConstants.DECRYPTION_FAILED;
+ }
+ } else {
+ // no data
+ this.returnStatus.statusFlags |=
+ lazy.EnigmailConstants.DECRYPTION_FAILED;
+ }
+ }
+
+ this.decryptedData = this.returnStatus.decryptedData;
+ this.handleResult(this.returnStatus.exitCode);
+
+ let decError =
+ this.returnStatus.statusFlags &
+ lazy.EnigmailConstants.DECRYPTION_FAILED;
+
+ // don't return decrypted data if decryption failed (because it's likely an MDC error),
+ // unless we are called for permanent decryption
+ if (decError) {
+ this.decryptedData = "";
+ }
+
+ this.displayStatus();
+
+ // HACK: remove filename from 1st HTML and plaintext parts to make TB display message without attachment
+ this.decryptedData = this.decryptedData.replace(
+ /^Content-Disposition: inline; filename="msg.txt"/m,
+ "Content-Disposition: inline"
+ );
+ this.decryptedData = this.decryptedData.replace(
+ /^Content-Disposition: inline; filename="msg.html"/m,
+ "Content-Disposition: inline"
+ );
+
+ let prefix = EnigmailMimeDecrypt.pretendAttachment(
+ this.mimePartNumber,
+ this.uri
+ );
+ this.returnData(prefix + this.decryptedData);
+
+ // don't remember the last message if it contains an embedded PGP/MIME message
+ // to avoid ending up in a loop
+ if (
+ this.mimePartNumber === "1" &&
+ this.decryptedData.search(
+ /^Content-Type:[\t ]+multipart\/encrypted/im
+ ) < 0 &&
+ !decError
+ ) {
+ LAST_MSG.lastMessageData = this.decryptedData;
+ LAST_MSG.lastStatus = this.returnStatus;
+ LAST_MSG.lastStatus.decryptedHeaders = this.decryptedHeaders;
+ } else {
+ LAST_MSG.lastMessageURI = null;
+ LAST_MSG.lastMessageData = "";
+ }
+
+ this.decryptedData = "";
+ lazy.EnigmailLog.DEBUG(
+ "mimeDecrypt.jsm: onStopRequest: process terminated\n"
+ ); // always log this one
+ this.proc = null;
+ } else {
+ this.returnStatus = LAST_MSG.lastStatus;
+ this.decryptedHeaders = LAST_MSG.lastStatus.decryptedHeaders;
+ this.mimePartNumber = LAST_MSG.mimePartNumber;
+ this.exitCode = 0;
+ this.displayStatus();
+ this.returnData(LAST_MSG.lastMessageData);
+ }
+ },
+
+ displayStatus() {
+ lazy.EnigmailLog.DEBUG("mimeDecrypt.jsm: displayStatus()\n");
+
+ if (
+ this.exitCode === null ||
+ this.msgWindow === null ||
+ this.statusDisplayed
+ ) {
+ lazy.EnigmailLog.DEBUG(
+ "mimeDecrypt.jsm: displayStatus: nothing to display\n"
+ );
+ return;
+ }
+
+ let uriSpec = this.uri ? this.uri.spec : null;
+
+ try {
+ lazy.EnigmailLog.DEBUG(
+ "mimeDecrypt.jsm: displayStatus for uri " + uriSpec + "\n"
+ );
+ let headerSink = EnigmailSingletons.messageReader;
+
+ if (headerSink && this.uri && !this.backgroundJob) {
+ headerSink.processDecryptionResult(
+ this.uri,
+ "modifyMessageHeaders",
+ JSON.stringify(this.decryptedHeaders),
+ this.mimePartNumber
+ );
+
+ headerSink.updateSecurityStatus(
+ this.msgUriSpec,
+ this.exitCode,
+ this.returnStatus.statusFlags,
+ this.returnStatus.extStatusFlags,
+ this.returnStatus.keyId,
+ this.returnStatus.userId,
+ this.returnStatus.sigDetails,
+ this.returnStatus.errorMsg,
+ this.returnStatus.blockSeparation,
+ this.uri,
+ JSON.stringify({
+ encryptedTo: this.returnStatus.encToDetails,
+ }),
+ this.mimePartNumber
+ );
+ } else {
+ this.updateHeadersInMsgDb();
+ }
+ this.statusDisplayed = true;
+ } catch (ex) {
+ console.debug(ex);
+ lazy.EnigmailLog.writeException("mimeDecrypt.jsm", ex);
+ }
+ LOCAL_DEBUG("mimeDecrypt.jsm: displayStatus done\n");
+ },
+
+ handleResult(exitCode) {
+ LOCAL_DEBUG("mimeDecrypt.jsm: done: " + exitCode + "\n");
+
+ if (gDebugLogLevel > 4) {
+ LOCAL_DEBUG(
+ "mimeDecrypt.jsm: done: decrypted data='" + this.decryptedData + "'\n"
+ );
+ }
+
+ // ensure newline at the end of the stream
+ if (!this.decryptedData.endsWith("\n")) {
+ this.decryptedData += "\r\n";
+ }
+
+ try {
+ this.extractEncryptedHeaders();
+ this.extractAutocryptGossip();
+ } catch (ex) {
+ console.debug(ex);
+ }
+
+ let mightNeedWrapper = true;
+
+ // It's unclear which scenario this check is supposed to fix.
+ // Based on a comment in mimeVerify (which seems code seems to be
+ // derived from), it might be intended to fix forwarding of empty
+ // messages. Not sure this is still necessary. We should check if
+ // this code can be removed.
+ let i = this.decryptedData.search(/\n\r?\n/);
+ // It's unknown why this test checks for > instead of >=
+ if (i > 0) {
+ var hdr = this.decryptedData.substr(0, i).split(/\r?\n/);
+ for (let j = 0; j < hdr.length; j++) {
+ if (hdr[j].search(/^\s*content-type:\s+text\/(plain|html)/i) >= 0) {
+ LOCAL_DEBUG(
+ "mimeDecrypt.jsm: done: adding multipart/mixed around " +
+ hdr[j] +
+ "\n"
+ );
+
+ this.addWrapperToDecryptedResult();
+ mightNeedWrapper = false;
+ break;
+ }
+ }
+ }
+
+ if (mightNeedWrapper) {
+ let headerBoundaryPosition = this.decryptedData.search(/\n\r?\n/);
+ if (
+ headerBoundaryPosition >= 0 &&
+ !/^Content-Type:/im.test(
+ this.decryptedData.substr(0, headerBoundaryPosition)
+ )
+ ) {
+ this.decryptedData =
+ "Content-Type: text/plain; charset=utf-8\r\n\r\n" +
+ this.decryptedData;
+ }
+ }
+
+ this.exitCode = exitCode;
+ },
+
+ addWrapperToDecryptedResult() {
+ let wrapper = lazy.EnigmailMime.createBoundary();
+
+ this.decryptedData =
+ 'Content-Type: multipart/mixed; boundary="' +
+ wrapper +
+ '"\r\n' +
+ "Content-Disposition: inline\r\n\r\n" +
+ "--" +
+ wrapper +
+ "\r\n" +
+ this.decryptedData +
+ "\r\n" +
+ "--" +
+ wrapper +
+ "--\r\n";
+ },
+
+ extractContentType(data) {
+ let i = data.search(/\n\r?\n/);
+ if (i <= 0) {
+ return null;
+ }
+
+ let headers = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(
+ Ci.nsIMimeHeaders
+ );
+ headers.initialize(data.substr(0, i));
+ return headers.extractHeader("content-type", false);
+ },
+
+ // return data to libMime
+ returnData(data) {
+ lazy.EnigmailLog.DEBUG(
+ "mimeDecrypt.jsm: returnData: " + data.length + " bytes\n"
+ );
+
+ let proto = null;
+ let ct = this.extractContentType(data);
+ if (ct && ct.search(/multipart\/signed/i) >= 0) {
+ proto = lazy.EnigmailMime.getProtocol(ct);
+ }
+
+ if (
+ proto &&
+ proto.search(/application\/(pgp|pkcs7|x-pkcs7)-signature/i) >= 0
+ ) {
+ try {
+ lazy.EnigmailLog.DEBUG(
+ "mimeDecrypt.jsm: returnData: using direct verification\n"
+ );
+ this.mimeSvc.contentType = ct;
+ if ("mimePart" in this.mimeSvc) {
+ this.mimeSvc.mimePart = this.mimeSvc.mimePart + ".1";
+ }
+ let veri = lazy.EnigmailVerify.newVerifier(proto);
+ veri.onStartRequest(this.mimeSvc, this.uri);
+ veri.onTextData(data);
+ veri.onStopRequest(null, 0);
+ } catch (ex) {
+ console.debug(ex);
+ lazy.EnigmailLog.ERROR(
+ "mimeDecrypt.jsm: returnData(): mimeSvc.onDataAvailable failed:\n" +
+ ex.toString()
+ );
+ }
+ } else {
+ try {
+ this.mimeSvc.outputDecryptedData(data, data.length);
+ } catch (ex) {
+ console.debug(ex);
+ lazy.EnigmailLog.ERROR(
+ "mimeDecrypt.jsm: returnData(): cannot send decrypted data to MIME processing:\n" +
+ ex.toString()
+ );
+ }
+ }
+ },
+
+ handleManualDecrypt() {
+ try {
+ let headerSink = EnigmailSingletons.messageReader;
+
+ if (headerSink && this.uri && !this.backgroundJob) {
+ headerSink.updateSecurityStatus(
+ this.msgUriSpec,
+ lazy.EnigmailConstants.POSSIBLE_PGPMIME,
+ 0,
+ 0,
+ "",
+ "",
+ "",
+ lazy.l10n.formatValueSync("possibly-pgp-mime"),
+ "",
+ this.uri,
+ null,
+ ""
+ );
+ }
+ } catch (ex) {
+ console.debug(ex);
+ }
+
+ return 0;
+ },
+
+ updateHeadersInMsgDb() {
+ if (this.mimePartNumber !== "1") {
+ return;
+ }
+ if (!this.uri) {
+ return;
+ }
+
+ if (this.decryptedHeaders && "subject" in this.decryptedHeaders) {
+ try {
+ let msgDbHdr = this.uri.QueryInterface(
+ Ci.nsIMsgMessageUrl
+ ).messageHeader;
+ msgDbHdr.subject = this.decryptedHeaders.subject;
+ } catch (x) {
+ console.debug(x);
+ }
+ }
+ },
+
+ extractEncryptedHeaders() {
+ let r = lazy.EnigmailMime.extractProtectedHeaders(this.decryptedData);
+ if (!r) {
+ return;
+ }
+
+ this.decryptedHeaders = r.newHeaders;
+ if (r.startPos >= 0 && r.endPos > r.startPos) {
+ this.decryptedData =
+ this.decryptedData.substr(0, r.startPos) +
+ this.decryptedData.substr(r.endPos);
+ }
+ },
+
+ /**
+ * Process the Autocrypt-Gossip header lines.
+ */
+ async extractAutocryptGossip() {
+ let gossipHeaders =
+ MimeParser.extractHeaders(this.decryptedData).get("autocrypt-gossip") ||
+ [];
+ for (let h of gossipHeaders) {
+ try {
+ let keyData = atob(
+ MimeParser.getParameter(h.replace(/ /g, ""), "keydata")
+ );
+ if (keyData) {
+ LAST_MSG.gossip.push(keyData);
+ }
+ } catch {}
+ }
+ },
+};
+
+////////////////////////////////////////////////////////////////////
+// General-purpose functions, not exported
+
+function LOCAL_DEBUG(str) {
+ if (gDebugLogLevel) {
+ lazy.EnigmailLog.DEBUG(str);
+ }
+}