Bug 1672851 - Fix missing openpgp encryption label for PGP inline encrypted messages. r=mkmelin a=wsmwk
authorAlessandro Castellani <alessandro@thunderbird.net>
Wed, 28 Oct 2020 21:13:46 -0700 (2020-10-29)
changeset 40032 b379b74ed3c17d8488a8573f933259b76f33dcc1
parent 40031 b69b92ac74a822707bc017a21905bf9a67f8a89c
child 40033 0760c5edac573b68e94030f2a8f80394c50e9f17
push id106
push userkaie@kuix.de
push dateFri, 13 Nov 2020 10:41:12 +0000 (2020-11-13)
treeherdercomm-esr78@eec8c8a0d2e3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmkmelin, wsmwk
bugs1672851
Bug 1672851 - Fix missing openpgp encryption label for PGP inline encrypted messages. r=mkmelin a=wsmwk
mail/base/content/mailWindowOverlay.js
mail/base/content/messageWindow.xhtml
mail/base/content/messenger.xhtml
mail/base/content/msgSecurityPane.inc.xhtml
mail/extensions/openpgp/content/ui/enigmailMessengerOverlay.js
mail/extensions/openpgp/content/ui/enigmailMsgHdrViewOverlay.js
mail/test/browser/openpgp/browser_viewMessage.js
mail/test/browser/openpgp/data/eml/alice-broken-exchange.eml
mail/test/browser/openpgp/data/eml/alice-partially-encrypted.eml
--- a/mail/base/content/mailWindowOverlay.js
+++ b/mail/base/content/mailWindowOverlay.js
@@ -3152,25 +3152,23 @@ var gMessageNotificationBar = {
   },
 
   get brandBundle() {
     delete this.brandBundle;
     return (this.brandBundle = document.getElementById("bundle_brand"));
   },
 
   get msgNotificationBar() {
-    delete this.msgNotificationBar;
-
-    let newNotificationBox = new MozElements.NotificationBox(element => {
-      element.setAttribute("flex", "1");
-      element.setAttribute("notificationside", "top");
-      document.getElementById("mail-notification-top").append(element);
-    });
-
-    return (this.msgNotificationBar = newNotificationBox);
+    if (!this._notificationBox) {
+      this._notificationBox = new MozElements.NotificationBox(element => {
+        element.setAttribute("notificationside", "top");
+        document.getElementById("mail-notification-top").append(element);
+      });
+    }
+    return this._notificationBox;
   },
 
   setJunkMsg(aMsgHdr) {
     goUpdateCommand("button_junk");
 
     let brandName = this.brandBundle.getString("brandShortName");
     let junkBarMsg = this.stringBundle.getFormattedString("junkBarMessage", [
       brandName,
--- a/mail/base/content/messageWindow.xhtml
+++ b/mail/base/content/messageWindow.xhtml
@@ -238,19 +238,19 @@
       <hbox id="msgHeaderView" collapsed="true" class="main-header-area">
 #include msgHdrView.inc.xhtml
       </hbox>
       <vbox id="singlemessage">
 #include ../../../calendar/lightning/content/imip-bar-overlay.inc.xhtml
       </vbox>
       <!-- The msgNotificationBar appears on top of the message and displays
            information like: junk, mdn, remote content and phishing warnings -->
-      <hbox id="mail-notification-top">
+      <vbox id="mail-notification-top">
         <!-- notificationbox will be added here lazily. -->
-      </hbox>
+      </vbox>
 
       <!-- message view -->
       <browser id="messagepane"
                context="mailContext"
                tooltip="aHTMLTooltip"
                style="height: 0px; min-height: 1px"
                flex="1"
                name="messagepane"
--- a/mail/base/content/messenger.xhtml
+++ b/mail/base/content/messenger.xhtml
@@ -742,19 +742,19 @@
                     <vbox id="singlemessage" flex="1">
                       <hbox id="msgHeaderView" collapsed="true" class="main-header-area">
 #include msgHdrView.inc.xhtml
                       </hbox>
 #include ../../../calendar/lightning/content/imip-bar-overlay.inc.xhtml
                       <!-- The msgNotificationBar appears on top of the message
                            and displays information like: junk, contains remote
                            images, or is a suspected phishing URL. -->
-                      <hbox id="mail-notification-top">
+                      <vbox id="mail-notification-top">
                         <!-- notificationbox will be added here lazily. -->
-                      </hbox>
+                      </vbox>
                       <vbox flex="1">
                         <!-- The messagepanewrapper hbox exists to allow
                              extensions to add sidebars to the message. -->
                         <hbox id="messagepanewrapper" flex="1">
                           <stack flex="1">
                             <browser id="messagepane"
                                      context="mailContext"
                                      datetimepicker="DateTimePickerPanel"
--- a/mail/base/content/msgSecurityPane.inc.xhtml
+++ b/mail/base/content/msgSecurityPane.inc.xhtml
@@ -32,49 +32,16 @@
               class="inline-notification-container info-container">
           <hbox class="inline-notification-wrapper align-center">
             <image class="notification-image notification-image-info"/>
             <description data-l10n-id="openpgp-missing-signature-key"/>
             <button data-l10n-id="openpgp-search-signature-key"
                     oncommand="Enigmail.msg.searchSignatureKey();"/>
           </hbox>
         </hbox>
-
-        <!-- Corrupted message by MS Exchange -->
-        <hbox id="brokenExchangeBox" hidden="true"
-              class="inline-notification-container info-container">
-          <hbox class="inline-notification-wrapper">
-            <image class="notification-image notification-image-info"/>
-            <description data-l10n-id="openpgp-broken-exchange-info"/>
-            <button id="brokenExchangeRepairButton"
-                    data-l10n-id="openpgp-broken-exchange-repair"
-                    oncommand="Enigmail.msg.fixBuggyExchangeMail();"/>
-            <label id="brokenExchangeWait" data-l10n-id="openpgp-broken-exchange-wait"/>
-          </hbox>
-        </hbox>
-
-        <!-- Unable to decrypt -->
-        <hbox id="cannotDecryptBox" hidden="true"
-              class="inline-notification-container error-container">
-          <hbox class="inline-notification-wrapper align-center">
-            <image class="notification-image notification-image-error"/>
-            <description id="cannot-decrypt-explanation"/>
-          </hbox>
-        </hbox>
-
-        <!-- Message partially encrypted -->
-        <hbox id="partialOpenPGPBox" hidden="true"
-              class="inline-notification-container info-container">
-          <hbox class="inline-notification-wrapper align-center">
-            <image class="notification-image notification-image-info"/>
-            <description id="preventedPartialExplanation"/>
-            <button id="openpgpProcessPartial"
-                    oncommand="Enigmail.msg.processOpenPGPSubset();"/>
-          </hbox>
-        </hbox>
       </vbox>
 
       <label id="signatureLabel" class="message-security-label"/>
       <label id="signatureHeader" collapsed="true" />
       <description id="signatureExplanation"/>
 
       <vbox id="signatureCert" class="message-security-container"
             collapsed="true">
--- a/mail/extensions/openpgp/content/ui/enigmailMessengerOverlay.js
+++ b/mail/extensions/openpgp/content/ui/enigmailMessengerOverlay.js
@@ -9,19 +9,24 @@
 // TODO: check if this is safe
 /* eslint-disable no-unsanitized/property */
 
 /* Globals from Thunderbird: */
 /* global ReloadMessage: false, gDBView: false, gSignatureStatus: false, gEncryptionStatus: false, showMessageReadSecurityInfo: false */
 /* global gFolderDisplay: false, messenger: false, currentAttachments: false, msgWindow: false, PanelUI: false */
 /* global currentHeaderData: false, gViewAllHeaders: false, gExpandedHeaderList: false, goDoCommand: false, HandleSelectedAttachments: false */
 /* global statusFeedback: false, displayAttachmentsForExpandedView: false, gMessageListeners: false, gExpandedHeaderView */
-/* global MailServices: false, gMessageDisplay: false */
-
-/* import-globals-from ../BondOpenPGP.jsm */
+/* globals gMessageNotificationBar, gMessageDisplay */
+
+var { MailServices } = ChromeUtils.import(
+  "resource:///modules/MailServices.jsm"
+);
+var { BondOpenPGP } = ChromeUtils.import(
+  "chrome://openpgp/content/BondOpenPGP.jsm"
+);
 
 var EnigmailCompat = ChromeUtils.import(
   "chrome://openpgp/content/modules/compat.jsm"
 ).EnigmailCompat;
 var EnigmailCore = ChromeUtils.import(
   "chrome://openpgp/content/modules/core.jsm"
 ).EnigmailCore;
 var EnigmailFuncs = ChromeUtils.import(
@@ -167,16 +172,29 @@ Enigmail.msg = {
     //"autocrypt-setup-message",
   ],
   buggyExchangeEmailContent: null, // for HACK for MS-EXCHANGE-Server Problem
   buggyMailType: null,
   changedAttributes: [],
   lastSMimeReloadURI: "",
   allAttachmentsDone: false,
   messageDecryptDone: false,
+  showPartialDecryptionReminder: false,
+
+  get notificationBox() {
+    return gMessageNotificationBar.msgNotificationBar;
+  },
+
+  removeNotification(value) {
+    let item = this.notificationBox.getNotificationWithValue(value);
+    // Remove the notification only if the user didn't previously close it.
+    if (item) {
+      this.notificationBox.removeNotification(item, true);
+    }
+  },
 
   messengerStartup() {
     if (!BondOpenPGP.isEnabled()) {
       return;
     }
 
     let self = this;
 
@@ -370,46 +388,37 @@ Enigmail.msg = {
     if (revealBox) {
       // there are situations when evealBox is not yet present
       revealBox.setAttribute("hidden", !attachmentList ? "true" : "false");
     }
   },
 
   messageCleanup() {
     EnigmailLog.DEBUG("enigmailMessengerOverlay.js: messageCleanup\n");
-
-    let element = document.getElementById("brokenExchangeBox");
-    if (element) {
-      element.hidden = true;
+    for (let value of [
+      "decryptInlinePGReminder",
+      "decryptInlinePG",
+      "brokenExchangeProgress",
+    ]) {
+      this.removeNotification(value);
     }
-    element = document.getElementById("openpgpKeyBox");
+
+    let element = document.getElementById("openpgpKeyBox");
     if (element) {
       element.hidden = true;
     }
     element = document.getElementById("hasConflictingKeyOpenPGP");
     if (element) {
       element.setAttribute("hidden", true);
     }
     element = document.getElementById("signatureKeyBox");
     if (element) {
       element.hidden = true;
       element.removeAttribute("keyid");
     }
-    element = document.getElementById("cannotDecryptBox");
-    if (element) {
-      element.hidden = true;
-    }
-    element = document.getElementById("partialOpenPGPBox");
-    if (element) {
-      element.hidden = true;
-    }
-    element = document.getElementById("openpgpProcessPartial");
-    if (element) {
-      element.removeAttribute("hidden");
-    }
 
     this.setAttachmentReveal(null);
 
     if (Enigmail.msg.createdURIs.length) {
       // Cleanup messages belonging to this window (just in case)
       var enigmailSvc = Enigmail.getEnigmailSvc();
       if (enigmailSvc) {
         EnigmailLog.DEBUG(
@@ -653,19 +662,41 @@ Enigmail.msg = {
   },
 
   // callback function for automatic decryption
   messageAutoDecrypt() {
     EnigmailLog.DEBUG("enigmailMessengerOverlay.js: messageAutoDecrypt:\n");
     Enigmail.msg.messageDecrypt(null, true);
   },
 
-  notifyMessageDecryptDone() {
+  async notifyMessageDecryptDone() {
     Enigmail.msg.messageDecryptDone = true;
     Enigmail.msg.processAfterAttachmentsAndDecrypt();
+
+    // Interrupt if the message wasn't properly decrypted.
+    if (
+      !Enigmail.msg.decryptedMessage ||
+      typeof Enigmail.msg.decryptedMessage == "undefined"
+    ) {
+      return;
+    }
+
+    // Show the partial inline encryption reminder only if the decryption action
+    // came from a partially inline encrypted message.
+    if (Enigmail.msg.showPartialDecryptionReminder) {
+      Enigmail.msg.showPartialDecryptionReminder = false;
+
+      this.notificationBox.appendNotification(
+        await document.l10n.formatValue("openpgp-reminder-partial-display"),
+        "decryptInlinePGReminder",
+        null,
+        this.notificationBox.PRIORITY_INFO_HIGH,
+        null
+      );
+    }
   },
 
   // analyse message header and decrypt/verify message
   messageDecrypt(event, isAuto) {
     EnigmailLog.DEBUG(
       "enigmailMessengerOverlay.js: messageDecrypt: " + event + "\n"
     );
 
@@ -1041,17 +1072,17 @@ Enigmail.msg = {
       "",
       "",
       uri,
       "",
       "1"
     );
   },
 
-  messageParse(
+  async messageParse(
     interactive,
     importOnly,
     contentEncoding,
     msgUriSpec,
     isAuto,
     pbMessageIndex = "0"
   ) {
     EnigmailLog.DEBUG(
@@ -1220,27 +1251,39 @@ Enigmail.msg = {
         if (ht & EnigmailConstants.UNCERTAIN_SIGNATURE) {
           infoId = "openpgp-partially-signed";
           buttonId = "openpgp-partial-verify-button";
         } else {
           infoId = "openpgp-partially-encrypted";
           buttonId = "openpgp-partial-decrypt-button";
         }
 
-        document.getElementById("partialOpenPGPBox").removeAttribute("hidden");
-        let descElement = document.getElementById(
-          "preventedPartialExplanation"
+        let [description, buttonLabel] = await document.l10n.formatValues([
+          { id: infoId },
+          { id: buttonId },
+        ]);
+
+        let buttons = [
+          {
+            label: buttonLabel,
+            popup: null,
+            callback(aNotification, aButton) {
+              Enigmail.msg.processOpenPGPSubset();
+              return false; // Close notification.
+            },
+          },
+        ];
+
+        this.notificationBox.appendNotification(
+          description,
+          "decryptInlinePG",
+          null,
+          this.notificationBox.PRIORITY_INFO_HIGH,
+          buttons
         );
-        if (descElement) {
-          document.l10n.setAttributes(descElement, infoId);
-        }
-        let buttonElement = document.getElementById("openpgpProcessPartial");
-        if (buttonElement) {
-          document.l10n.setAttributes(buttonElement, buttonId);
-        }
         return;
       }
     }
 
     var mozPlainText = bodyElement.innerHTML.search(/class="moz-text-plain"/);
 
     if (mozPlainText >= 0 && mozPlainText < 40) {
       // workaround for too much expanded emoticons in plaintext msg
@@ -1317,25 +1360,18 @@ Enigmail.msg = {
 
     if (hasHead || hasTail) {
       return EnigmailConstants.PARTIALLY_PGP | crypto;
     }
 
     return 0;
   },
 
-  processOpenPGPSubset() {
-    let descElement = document.getElementById("preventedPartialExplanation");
-    document.l10n.setAttributes(
-      descElement,
-      "openpgp-reminder-partial-display"
-    );
-    document
-      .getElementById("openpgpProcessPartial")
-      .setAttribute("hidden", true);
+  async processOpenPGPSubset() {
+    Enigmail.msg.showPartialDecryptionReminder = true;
     this.messageDecrypt(null, false);
   },
 
   getBodyElement(pbMessageIndex = "0") {
     let bodyElement = null;
 
     // Thunderbird
     let msgFrame = document.getElementById("messagepane");
@@ -1886,58 +1922,56 @@ Enigmail.msg = {
   },
 
   /**
    * Fix broken PGP/MIME messages from MS-Exchange by replacing the broken original
    * message with a fixed copy.
    *
    * no return
    */
-  fixBuggyExchangeMail() {
+  async fixBuggyExchangeMail() {
     EnigmailLog.DEBUG("enigmailMessengerOverlay.js: fixBuggyExchangeMail:\n");
 
-    document
-      .getElementById("brokenExchangeRepairButton")
-      .setAttribute("hidden", true);
-    document.getElementById("brokenExchangeWait").removeAttribute("hidden");
-
-    function hideBrokenExchangePane() {
-      document.getElementById("brokenExchangeBox").hidden = true;
-    }
+    this.notificationBox.appendNotification(
+      await document.l10n.formatValue("openpgp-broken-exchange-wait"),
+      "brokenExchangeProgress",
+      null,
+      this.notificationBox.PRIORITY_INFO_HIGH,
+      null
+    );
 
     let msg = gFolderDisplay.messageDisplay.displayedMessage;
-
     let p = EnigmailFixExchangeMsg.fixExchangeMessage(msg, this.buggyMailType);
+
     p.then(function(msgKey) {
-      // display message with given msgKey
-
+      // Display message with given msgKey.
       EnigmailLog.DEBUG(
         "enigmailMessengerOverlay.js: fixBuggyExchangeMail: _success: msgKey=" +
           msgKey +
           "\n"
       );
 
       if (msgKey) {
         let index = gFolderDisplay.view.dbView.findIndexFromKey(msgKey, true);
         EnigmailLog.DEBUG("  ** index = " + index + "\n");
 
         EnigmailTimer.setTimeout(function() {
           gFolderDisplay.view.dbView.selectMsgByKey(msgKey);
         }, 750);
       }
-
-      hideBrokenExchangePane();
     });
     p.catch(async function() {
       EnigmailDialog.alert(
         window,
         l10n.formatValueSync("fix-broken-exchange-msg-failed")
       );
-      hideBrokenExchangePane();
     });
+
+    // Remove the brokenExchangeProgress notification at the end fo the process.
+    this.removeNotification("brokenExchangeProgress");
   },
 
   /**
    * Hide attachments containing OpenPGP keys
    */
   hidePgpKeys() {
     let keys = [];
     for (let i = 0; i < currentAttachments.length; i++) {
--- a/mail/extensions/openpgp/content/ui/enigmailMsgHdrViewOverlay.js
+++ b/mail/extensions/openpgp/content/ui/enigmailMsgHdrViewOverlay.js
@@ -104,16 +104,19 @@ Enigmail.hdrView = {
   msgEncryptionAllKeyIds: null,
 
   reset() {
     this.msgSignatureState = EnigmailConstants.MSG_SIG_NONE;
     this.msgEncryptionState = EnigmailConstants.MSG_ENC_NONE;
     this.msgSignatureKeyId = "";
     this.msgEncryptionKeyId = null;
     this.msgEncryptionAllKeyIds = null;
+    for (let value of ["decryptionFailed", "brokenExchange"]) {
+      Enigmail.msg.removeNotification(value);
+    }
   },
 
   hdrViewLoad() {
     EnigmailLog.DEBUG("enigmailMsgHdrViewOverlay.js: this.hdrViewLoad\n");
 
     // THE FOLLOWING OVERRIDES CODE IN msgHdrViewOverlay.js
     // which wouldn't work otherwise
 
@@ -490,23 +493,27 @@ Enigmail.hdrView = {
         infoId = "openpgp-cannot-decrypt-because-missing-key";
       } else {
         this.msgEncryptionState = EnigmailConstants.MSG_ENC_FAILURE;
         if (statusFlags & EnigmailConstants.MISSING_MDC) {
           unhideBar = true;
           infoId = "openpgp-cannot-decrypt-because-mdc";
         }
       }
+
       if (unhideBar) {
-        document.getElementById("cannotDecryptBox").removeAttribute("hidden");
-        let descElement = document.getElementById("cannot-decrypt-explanation");
-        if (descElement) {
-          document.l10n.setAttributes(descElement, infoId);
-        }
+        Enigmail.msg.notificationBox.appendNotification(
+          await document.l10n.formatValue(infoId),
+          "decryptionFailed",
+          "chrome://global/skin/icons/warning.svg",
+          Enigmail.msg.notificationBox.PRIORITY_CRITICAL_MEDIUM,
+          null
+        );
       }
+
       this.msgSignatureState = EnigmailConstants.MSG_SIG_NONE;
       encryptedUINode.hidden = false;
     } else if (statusFlags & EnigmailConstants.DECRYPTION_OKAY) {
       EnigmailURIs.rememberEncryptedUri(this.lastEncryptedMsgKey);
       encryptedUINode.setAttribute("encrypted", "ok");
       this.msgEncryptionState = EnigmailConstants.MSG_ENC_OK;
       if (secInfo.xtraStatus && secInfo.xtraStatus == "buggyMailFormat") {
         console.log(
@@ -1092,17 +1099,17 @@ Enigmail.hdrView = {
         if (hasSiblings(Enigmail.msg.mimeParts, mimePartNumber, parentNum)) {
           return true;
         }
       }
 
       return false;
     },
 
-    updateSecurityStatus(
+    async updateSecurityStatus(
       unusedUriSpec,
       exitCode,
       statusFlags,
       extStatusFlags,
       keyId,
       userId,
       sigDetails,
       errorMsg,
@@ -1167,22 +1174,34 @@ Enigmail.hdrView = {
           blockSeparation,
           encToDetails,
           null,
           mimePartNumber
         );
       }
 
       if (uriSpec && uriSpec.search(/^enigmail:message\//) === 0) {
-        // Display header for broken MS-Exchange message.
-        document.getElementById("brokenExchangeBox").removeAttribute("hidden");
-        document
-          .getElementById("brokenExchangeRepairButton")
-          .removeAttribute("hidden");
-        document.getElementById("brokenExchangeWait").hidden = true;
+        let buttons = [
+          {
+            "l10n-id": "openpgp-broken-exchange-repair",
+            popup: null,
+            callback(aNotification, aButton) {
+              Enigmail.msg.fixBuggyExchangeMail();
+              return false; // Close notification.
+            },
+          },
+        ];
+
+        Enigmail.msg.notificationBox.appendNotification(
+          await document.l10n.formatValue("openpgp-broken-exchange-info"),
+          "brokenExchange",
+          null,
+          Enigmail.msg.notificationBox.PRIORITY_WARNING_MEDIUM,
+          buttons
+        );
       }
     },
 
     processDecryptionResult(uri, actionType, processData, mimePartNumber) {
       EnigmailLog.DEBUG(
         "enigmailMsgHdrViewOverlay.js: EnigMimeHeaderSink.processDecryptionResult:\n"
       );
       EnigmailLog.DEBUG(
--- a/mail/test/browser/openpgp/browser_viewMessage.js
+++ b/mail/test/browser/openpgp/browser_viewMessage.js
@@ -11,16 +11,25 @@
 const { open_message_from_file } = ChromeUtils.import(
   "resource://testing-common/mozmill/FolderDisplayHelpers.jsm"
 );
 const {
   close_window,
   plan_for_modal_dialog,
   wait_for_modal_dialog,
 } = ChromeUtils.import("resource://testing-common/mozmill/WindowHelpers.jsm");
+const {
+  assert_notification_displayed,
+  get_notification_button,
+  wait_for_notification_to_show,
+  wait_for_notification_to_stop,
+} = ChromeUtils.import(
+  "resource://testing-common/mozmill/NotificationBoxHelpers.jsm"
+);
+
 const { OpenPGPTestUtils } = ChromeUtils.import(
   "resource://testing-common/mozmill/OpenPGPTestUtils.jsm"
 );
 const { FileUtils } = ChromeUtils.import(
   "resource://gre/modules/FileUtils.jsm"
 );
 const { MailServices } = ChromeUtils.import(
   "resource:///modules/MailServices.jsm"
@@ -293,12 +302,90 @@ add_task(async function testUpdateMessag
   // Verify the new acceptance level is correct.
   Assert.ok(
     OpenPGPTestUtils.hasSignedIconState(mc.window.document, "unverified"),
     "signed unverified icon is displayed"
   );
   close_window(mc);
 });
 
+/**
+ * Test the notification and decryption behavior for partially encrypted inline
+ * PGP messages.
+ */
+add_task(async function testPartialInlinePGPDecrypt() {
+  // Setup the message.
+  let mc = await open_message_from_file(
+    new FileUtils.File(
+      getTestFilePath("data/eml/alice-partially-encrypted.eml")
+    )
+  );
+
+  let notificationBox = "mail-notification-top";
+  let notificationValue = "decryptInlinePG";
+
+  // Assert the "partially encrypted notification" is visible.
+  assert_notification_displayed(mc, notificationBox, notificationValue, true);
+
+  // Click on the "decrypt" button.
+  let decryptButton = get_notification_button(
+    mc,
+    notificationBox,
+    notificationValue,
+    {
+      popup: null,
+    }
+  );
+  EventUtils.synthesizeMouseAtCenter(decryptButton, {}, mc.window);
+
+  // Assert that the message was decrypted and the partial decryption reminder
+  // notification is visible.
+  wait_for_notification_to_show(mc, notificationBox, "decryptInlinePGReminder");
+
+  Assert.ok(
+    OpenPGPTestUtils.hasEncryptedIconState(mc.window.document, "ok"),
+    "encrypted icon is displayed"
+  );
+
+  close_window(mc);
+});
+
+/**
+ * Test the notification and repairing of a message corrupted by MS-Exchange.
+ */
+add_task(async function testBrokenMSExchangeEncryption() {
+  // Setup the message.
+  let mc = await open_message_from_file(
+    new FileUtils.File(getTestFilePath("data/eml/alice-broken-exchange.eml"))
+  );
+  let notificationBox = "mail-notification-top";
+  let notificationValue = "brokenExchange";
+
+  // Assert the "corrupted by MS-Exchange" notification is visible.
+  assert_notification_displayed(mc, notificationBox, notificationValue, true);
+
+  // Click on the "repair" button.
+  let repairButton = get_notification_button(
+    mc,
+    notificationBox,
+    notificationValue,
+    {
+      popup: null,
+    }
+  );
+  EventUtils.synthesizeMouseAtCenter(repairButton, {}, mc.window);
+
+  // Wait for the "fixing in progress" notification to go away.
+  wait_for_notification_to_stop(mc, notificationBox, "brokenExchangeProgress");
+
+  // Assert that the message was repaired and decrypted.
+  Assert.ok(
+    OpenPGPTestUtils.hasEncryptedIconState(mc.window.document, "ok"),
+    "encrypted icon is displayed"
+  );
+
+  close_window(mc);
+}).skip(); // Bug 1674013
+
 registerCleanupFunction(function tearDown() {
   aliceIdentity.setUnicharAttribute("openpgp_key_id", initialKeyIdPref);
   MailServices.accounts.removeIncomingServer(aliceAcct.incomingServer, true);
 });
new file mode 100644
--- /dev/null
+++ b/mail/test/browser/openpgp/data/eml/alice-broken-exchange.eml
@@ -0,0 +1,64 @@
+From: "Alice Lovelace" <alice@openpgp.example>
+To: "Alice Lovelace" <alice@openpgp.example>
+Subject: broken exchange test message
+Date: Wed, 28 Oct 2020 01:23:45 +0000
+Content-Type: multipart/mixed;
+	boundary="_003_38fbdabc0a957344544c1642f2e764cd1234567890_"
+MIME-Version: 1.0
+
+--_003_38fbdabc0a957344544c1642f2e764cd1234567890_
+Content-Type: text/plain; charset="us-ascii"
+Content-Transfer-Encoding: quoted-printable
+
+
+--_003_38fbdabc0a957344544c1642f2e764cd1234567890_
+Content-Type: application/pgp-encrypted; name="PGPMIME version identification"
+Content-Description: PGP/MIME version identification
+Content-Disposition: attachment; filename="PGPMIME version identification";
+	size=12; creation-date="Wed, 28 Oct 2020 01:23:45 GMT";
+	modification-date="Wed, 28 Oct 2020 01:23:45 GMT"
+Content-ID: <ECC4CBEC65D5CD42A8BF99DC0A800A0F@namprd00.prod.outlook.com>
+Content-Transfer-Encoding: base64
+
+VmVyc2lvbjogMQ0K
+
+--_003_38fbdabc0a957344544c1642f2e764cd1234567890_
+Content-Type: application/octet-stream; name="encrypted.asc"
+Content-Description: OpenPGP encrypted message.asc
+Content-Disposition: attachment; filename="encrypted.asc";
+	creation-date="Wed, 20 May 2020 19:11:08 GMT";
+	modification-date="Wed, 20 May 2020 19:11:08 GMT"
+Content-ID: <3A449618AD0B6A43AE8979E0E971B0FF@namprd00.prod.outlook.com>
+Content-Transfer-Encoding: base64
+
+LS0tLS1CRUdJTiBQR1AgTUVTU0FHRS0tLS0tCgpoRjREUjJiMnVkWHlIcllTQVFk
+QS9ZM0VaZlp4SVM1WlRPcmx3NkVnTkFMKzhENnV2cGVpTWdSRUxzSDFQemd3Ck1N
+d3ZxMnFUT01DaGJUd0RQSGY0WDVETGpZbXowWnN5TWlJY3BzNXo4ZU5XQ0Jyd01k
+ODBtWGlKem8vM0ZCaGkKMHVrQmVaSkFTOUlvTE5DbnRZNUpqWWljbTZZSnBHSGVH
+azA3VUN5dENYUTFEZmF5OWk1cHc5Nk54UTZicE5PSgpFamttRmlEYk1JaExnM25C
+TjVRN2ZzZ01WbFBxMTNTbmJaMmVVZXdRVHVESG10dlByUzFHRmFROExBclFRNVdU
+CjJyampkUmZ1Q040MmNXeUszSHNUNVNMZDRkOTVjZm1CeTIrRnkwQ09tZU5obmRq
+aGxPQWlEL2dOUWE4TVAzYW4KWlQ0OVlmbDdYdHp2Yjh0V0dSL1IzSDFBMU9wYnEz
+d3BrQm1hNlpXMzBvcG9rRlNkYkRqK3pTNlhmNWVmTkVLWApJZFpVQTg1U1hYRjAw
+RWFSZU9tdFQ1WTJLZWMyS1dlUXYybmlVMGRNN0FMNjFGQmw3VXVvaUJDc1IwaGRa
+OCtTClR1UElQa0daWnlSdk1nU3ZTMjduV0lNSUZvdThzeVlTdTg0MElzeHRWSThk
+SEFqM0RvbUNXa20rMnJXaDZ3cWkKM29xZDhOK1QvdGZ3ZFdoMUJnNVpRTm40SFF6
+WXhuYVlqZk1mNFVYMS9RbjFVUmtISnErMXkwYm1reDI4c1RXTwpXVS9mZndQZjBB
+WGVyNG9LNEdtMDNtanFQbkd2UFFYa2xSb2NDQ0prMG9sdmZ6M0dQRDZSNGNiNkc4
+SlFvTyt5CmNMRmhMdytCZFdqd0ZCWU1POTl5TFhJM3lEbDFGdWd5R2JvU3dNNFRT
+aHA3T1ZwYUlXSE1oa2VhVFJIQlVQUnMKNXNqUzR5QVI0OVppTDNMU1lHR2FJbmQr
+QUlzR2I0NTB0enZENWJPT1dUM2JBK0pRWXZ4dzRxa240WnI0OVdCYgptZE9nWmhD
+eUxET3BsVUo1VFJtY0ZkdFdrRFI2MUsyN1VPeno4RVcxcExYazZjQ1RFcU5ZanlT
+bDJCeE53R283ClpGV3IwTW1qbVJydnQvSmNvcm56cjl3WW9HdXJ3bERnc2cxNmpW
+aFZpY09qTUg2ellqcXN0amJRejREeDJqU2wKbGdabjdPUXY5dTR1SGZ0SXNaQk50
+RnFjUlJaSFZHVWpXMjNQK0xUWGNkQzhDSWFaZzMvNUN2blZyTU9jcGd0MwpWdmdB
+TDloeUJRNHV6UWVacVlpRCtHRmhDQmp6QUxtMGNRSzJQZnE3N1AwTjJBS1k2NUNP
+SWVnbjFubXJvNTF4CldIS09jKy9FdURmNU5VY0lieW5sQ0NycUJXenAzZWt4bE5D
+eFZNTkdJeThObEhyS1oxb0RQSlNMZi9vc0Jyb2EKTExCd0V1bllqRlY1czE1NVkv
+TFUybnQzUUY5NzBxTGdHMEVTWm1mV2ZTcEZzaEhuSVZBMXdXV3lMM3o3Ukd2YwpJ
+ZVFnYk9LSklDWlNSTDVlNDBpSHBMcEpERWlyUGpTeEQrYUpuazd1VVZGUDk4aTJu
+Z3FNVjNadUtlM0paV0VpCnc3ZDJvbmEwditscWVUUUp5OHcydUIvaGNpTFBOR0tN
+QUpBeEpSeUFFUDRTaWpXWmNDQjMKPUR2RkwKLS0tLS1FTkQgUEdQIE1FU1NBR0Ut
+LS0tLQo=
+
+--_003_38fbdabc0a957344544c1642f2e764cd1234567890_--
new file mode 100644
--- /dev/null
+++ b/mail/test/browser/openpgp/data/eml/alice-partially-encrypted.eml
@@ -0,0 +1,17 @@
+From: "Alice Lovelace" <alice@openpgp.example>
+To: "Alice Lovelace" <alice@openpgp.example>
+Subject: partially encrypted test message
+Date: Wed, 29 Oct 2020 02:34:56 +0000
+Content-Type: text/plain
+MIME-Version: 1.0
+
+-----BEGIN PGP MESSAGE-----
+
+hF4DR2b2udXyHrYSAQdACSB+uDWodBJ2WRuPm53gxq/RUAc9TSEa1pbow+HTWU8w
+5SomxZAC/JN0vnLqgLWL7JKgmQyYD9d7HI76iTThvJf2cLZXp1+HRyHN2c9gCHjE
+0lIB7FhFzhbBPoT+4Nhla6ISM/KNTkbkebJ4VtZL05/hFn98qKJq61hn9iBvLQ5/
+URrmx+0iVgA1jUTUDuZSt5PRFHYFJqGoOvdHD2mccmIhawet
+=lIZv
+-----END PGP MESSAGE-----
+
+Additional unexpected text