Bug 1411592 - prevent remote content for encrypted S/MIME messages. r=jorgk a=jorgk
authorMagnus Melin <mkmelin+mozilla@iki.fi>
Sun, 22 Apr 2018 23:53:31 +0300
changeset 31416 6f5d2abfedc93296ff6ea0a8dc9705822fb69d11
parent 31415 2ed7c90c8ab0e9c3bc5fe621300092f1d15d0e28
child 31417 5461aa9fc88ca52b75e830cca8ca6ca2a9e05ff8
push id383
push userclokep@gmail.com
push dateMon, 07 May 2018 21:52:48 +0000
reviewersjorgk, jorgk
bugs1411592
Bug 1411592 - prevent remote content for encrypted S/MIME messages. r=jorgk a=jorgk
mail/base/content/mailWindowOverlay.js
mail/base/content/msgHdrViewOverlay.js
mail/extensions/smime/content/msgHdrViewSMIMEOverlay.js
mailnews/base/src/nsMsgContentPolicy.cpp
mailnews/base/src/nsMsgContentPolicy.h
mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilter.cpp
mailnews/extensions/smime/public/nsIEncryptedSMIMEURIsSrvc.idl
mailnews/mime/public/nsIMimeMiscStatus.idl
--- a/mail/base/content/mailWindowOverlay.js
+++ b/mail/base/content/mailWindowOverlay.js
@@ -3070,17 +3070,17 @@ var gMessageNotificationBar =
         buttons);
     }
   },
 
   isShowingJunkNotification: function() {
     return !!this.msgNotificationBar.getNotificationWithValue("junkContent");
   },
 
-  setRemoteContentMsg: function(aMsgHdr, aContentURI)
+  setRemoteContentMsg: function(aMsgHdr, aContentURI, aCanOverride)
   {
     // update the allow remote content for sender string
     let emailAddress = MailServices.headerParser.extractHeaderAddressMailboxes(aMsgHdr.author);
     var desc = document.getElementById("bundle_messenger")
                        .getFormattedString("alwaysLoadRemoteContentForSender2",
                                            [emailAddress ? emailAddress : aMsgHdr.author]);
     let displayName = MailServices.headerParser.extractHeaderAddressName(aMsgHdr.author);
     let brandName = this.brandBundle.getString("brandShortName");
@@ -3098,17 +3098,17 @@ var gMessageNotificationBar =
       popup: "remoteContentOptions",
       callback: function() { }
     }];
 
     if (!this.isShowingRemoteContentNotification()) {
       this.msgNotificationBar.appendNotification(remoteContentMsg, "remoteContent",
         "chrome://messenger/skin/icons/remote-blocked.svg",
         this.msgNotificationBar.PRIORITY_WARNING_MEDIUM,
-        buttons);
+        (aCanOverride ? buttons : []));
     }
 
     // The popup value is a space separated list of all the blocked origins.
     let popup = document.getElementById("remoteContentOptions");
     let principal = Services.scriptSecurityManager
       .createCodebasePrincipal(aContentURI, {});
     let origins = popup.value ? popup.value.split(" ") : [];
     if (!origins.includes(principal.origin))
--- a/mail/base/content/msgHdrViewOverlay.js
+++ b/mail/base/content/msgHdrViewOverlay.js
@@ -734,19 +734,19 @@ var messageHeaderSink = {
       OnMsgParsed(url);
     },
 
     onEndMsgHeaders: function(url)
     {
       OnMsgLoaded(url);
     },
 
-    onMsgHasRemoteContent: function(aMsgHdr, aContentURI)
+    onMsgHasRemoteContent: function(aMsgHdr, aContentURI, aCanOverride)
     {
-      gMessageNotificationBar.setRemoteContentMsg(aMsgHdr, aContentURI);
+      gMessageNotificationBar.setRemoteContentMsg(aMsgHdr, aContentURI, aCanOverride);
     },
 
     mSecurityInfo  : null,
     mSaveHdr: null,
     get securityInfo()
     {
       return this.mSecurityInfo;
     },
--- a/mail/extensions/smime/content/msgHdrViewSMIMEOverlay.js
+++ b/mail/extensions/smime/content/msgHdrViewSMIMEOverlay.js
@@ -12,16 +12,27 @@ var gEncryptedURIService = null;
 var gMyLastEncryptedURI = null;
 
 var gSMIMEBundle = null;
 
 // manipulates some globals from msgReadSMIMEOverlay.js
 
 var nsICMSMessageErrors = Ci.nsICMSMessageErrors;
 
+/// Get the necko URL for the message URI.
+function neckoURLForMessageURI(aMessageURI)
+{
+  let msgSvc = Components.classes["@mozilla.org/messenger;1"]
+    .createInstance(Components.interfaces.nsIMessenger)
+    .messageServiceFromURI(aMessageURI);
+  let neckoURI = {};
+  msgSvc.GetUrlForUri(aMessageURI, neckoURI, null);
+  return neckoURI.value.spec;
+}
+
 var smimeHeaderSink =
 {
   maxWantedNesting: function()
   {
     return 1;
   },
 
   signedStatus: function(aNestingLevel, aSignatureStatus, aSignerCert)
@@ -112,18 +123,21 @@ var smimeHeaderSink =
     else
     {
       gEncryptedUINode.setAttribute("encrypted", "notok");
       gStatusBar.setAttribute("encrypted", "notok");
     }
 
     if (gEncryptedURIService)
     {
+      // Remember the message URI and the corresponding necko URI.
       gMyLastEncryptedURI = gFolderDisplay.selectedMessageUris[0];
       gEncryptedURIService.rememberEncrypted(gMyLastEncryptedURI);
+      gEncryptedURIService.rememberEncrypted(
+        neckoURLForMessageURI(gMyLastEncryptedURI));
     }
 
     switch (aEncryptionStatus)
     {
     case nsICMSMessageErrors.SUCCESS:
     case nsICMSMessageErrors.ENCRYPT_INCOMPLETE:
       break;
     default:
@@ -155,16 +169,18 @@ var smimeHeaderSink =
   }
 };
 
 function forgetEncryptedURI()
 {
   if (gMyLastEncryptedURI && gEncryptedURIService)
   {
     gEncryptedURIService.forgetEncrypted(gMyLastEncryptedURI);
+    gEncryptedURIService.forgetEncrypted(
+      neckoURLForMessageURI(gMyLastEncryptedURI));
     gMyLastEncryptedURI = null;
   }
 }
 
 function onSMIMEStartHeaders()
 {
   gEncryptionStatus = -1;
   gSignatureStatus = -1;
--- a/mailnews/base/src/nsMsgContentPolicy.cpp
+++ b/mailnews/base/src/nsMsgContentPolicy.cpp
@@ -8,16 +8,17 @@
 #include "nsIPrefService.h"
 #include "nsIPrefBranch.h"
 #include "nsIAbManager.h"
 #include "nsIAbDirectory.h"
 #include "nsIAbCard.h"
 #include "nsIMsgWindow.h"
 #include "nsIMimeMiscStatus.h"
 #include "nsIMsgHdr.h"
+#include "nsIEncryptedSMIMEURIsSrvc.h"
 #include "nsNetUtil.h"
 #include "nsIMsgComposeService.h"
 #include "nsMsgCompCID.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIWebNavigation.h"
 #include "nsContentPolicyUtils.h"
 #include "nsIFrameLoader.h"
 #include "nsIWebProgress.h"
@@ -290,36 +291,16 @@ nsMsgContentPolicy::ShouldLoad(uint32_t 
     return NS_OK;
   }
 
   // never load unexposed protocols except for http, https and file.
   // Protocols like ftp are always blocked.
   if (ShouldBlockUnexposedProtocol(aContentLocation))
     return NS_OK;
 
-  // If we are allowing all remote content...
-  if (!mBlockRemoteImages)
-  {
-    *aDecision = nsIContentPolicy::ACCEPT;
-    return NS_OK;
-  }
-
-  // Extract the windowtype to handle compose windows separately from mail
-  if (aRequestingContext)
-  {
-    nsCOMPtr<nsIMsgCompose> msgCompose =
-      GetMsgComposeForContext(aRequestingContext);
-    // Work out if we're in a compose window or not.
-    if (msgCompose)
-    {
-      ComposeShouldLoad(msgCompose, aRequestingContext, aContentLocation,
-                        aDecision);
-      return NS_OK;
-    }
-  }
 
   // Find out the URI that originally initiated the set of requests for this
   // context.
   nsCOMPtr<nsIURI> originatorLocation;
   if (!aRequestingContext && aRequestPrincipal)
   {
     // Can get the URI directly from the principal.
     rv = aRequestPrincipal->GetURI(getter_AddRefs(originatorLocation));
@@ -332,16 +313,51 @@ nsMsgContentPolicy::ShouldLoad(uint32_t 
       return NS_OK;
   }
   NS_ENSURE_SUCCESS(rv, NS_OK);
 
 #ifdef DEBUG_MsgContentPolicy
   fprintf(stderr, "originatorLocation = %s\n", originatorLocation->GetSpecOrDefault().get());
 #endif
 
+  // Don't load remote content for encrypted messages.
+  nsCOMPtr<nsIEncryptedSMIMEURIsService> encryptedURIService =
+    do_GetService("@mozilla.org/messenger-smime/smime-encrypted-uris-service;1", &rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+  bool isEncrypted;
+  rv = encryptedURIService->IsEncrypted(aRequestingLocation->GetSpecOrDefault(), &isEncrypted);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (isEncrypted)
+  {
+    *aDecision = nsIContentPolicy::REJECT_REQUEST;
+    NotifyContentWasBlocked(originatorLocation, aContentLocation, false);
+    return NS_OK;
+  }
+
+  // If we are allowing all remote content...
+  if (!mBlockRemoteImages)
+  {
+    *aDecision = nsIContentPolicy::ACCEPT;
+    return NS_OK;
+  }
+
+  // Extract the windowtype to handle compose windows separately from mail
+  if (aRequestingContext)
+  {
+    nsCOMPtr<nsIMsgCompose> msgCompose =
+      GetMsgComposeForContext(aRequestingContext);
+    // Work out if we're in a compose window or not.
+    if (msgCompose)
+    {
+      ComposeShouldLoad(msgCompose, aRequestingContext, aContentLocation,
+                        aDecision);
+      return NS_OK;
+    }
+  }
+
   // Allow content when using a remote page.
   bool isHttp;
   bool isHttps;
   rv = originatorLocation->SchemeIs("http", &isHttp);
   nsresult rv2 = originatorLocation->SchemeIs("https", &isHttps);
   if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2) && (isHttp || isHttps))
   {
     *aDecision = nsIContentPolicy::ACCEPT;
@@ -528,40 +544,95 @@ nsMsgContentPolicy::ShouldAcceptRemoteCo
 
   return result;
 }
 
 class RemoteContentNotifierEvent : public mozilla::Runnable
 {
 public:
   RemoteContentNotifierEvent(nsIMsgWindow *aMsgWindow, nsIMsgDBHdr *aMsgHdr,
-                             nsIURI *aContentURI)
+                             nsIURI *aContentURI, bool aCanOverride = true)
     : mozilla::Runnable("RemoteContentNotifierEvent")
-    , mMsgWindow(aMsgWindow), mMsgHdr(aMsgHdr), mContentURI(aContentURI)
+    , mMsgWindow(aMsgWindow), mMsgHdr(aMsgHdr), mContentURI(aContentURI),
+    mCanOverride(aCanOverride)
   {}
 
   NS_IMETHOD Run()
   {
     if (mMsgWindow)
     {
       nsCOMPtr<nsIMsgHeaderSink> msgHdrSink;
       (void)mMsgWindow->GetMsgHeaderSink(getter_AddRefs(msgHdrSink));
       if (msgHdrSink)
-        msgHdrSink->OnMsgHasRemoteContent(mMsgHdr, mContentURI);
+        msgHdrSink->OnMsgHasRemoteContent(mMsgHdr, mContentURI, mCanOverride);
     }
     return NS_OK;
   }
 
 private:
   nsCOMPtr<nsIMsgWindow> mMsgWindow;
   nsCOMPtr<nsIMsgDBHdr> mMsgHdr;
   nsCOMPtr<nsIURI> mContentURI;
+  bool mCanOverride;
 };
 
 /**
+ * This function is used to show a blocked remote content notification.
+ */
+void
+nsMsgContentPolicy::NotifyContentWasBlocked(nsIURI *aOriginatorLocation,
+                                            nsIURI *aContentLocation,
+                                            bool aCanOverride)
+{
+  // Is it a mailnews url?
+  nsresult rv;
+  nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(aOriginatorLocation,
+                                                      &rv));
+  if (NS_FAILED(rv))
+  {
+    return;
+  }
+
+  nsCString resourceURI;
+  rv = msgUrl->GetUri(getter_Copies(resourceURI));
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl(do_QueryInterface(aOriginatorLocation, &rv));
+  NS_ENSURE_SUCCESS_VOID(rv);
+
+  nsCOMPtr<nsIMsgDBHdr> msgHdr;
+  rv = GetMsgDBHdrFromURI(resourceURI.get(), getter_AddRefs(msgHdr));
+  if (NS_FAILED(rv))
+  {
+    // Maybe we can get a dummy header.
+    nsCOMPtr<nsIMsgWindow> msgWindow;
+    rv = mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+    if (msgWindow)
+    {
+      nsCOMPtr<nsIMsgHeaderSink> msgHdrSink;
+      rv = msgWindow->GetMsgHeaderSink(getter_AddRefs(msgHdrSink));
+      if (msgHdrSink)
+        rv = msgHdrSink->GetDummyMsgHeader(getter_AddRefs(msgHdr));
+    }
+  }
+
+  nsCOMPtr<nsIMsgWindow> msgWindow;
+  (void)mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
+  if (msgWindow)
+  {
+    nsCOMPtr<nsIRunnable> event =
+      new RemoteContentNotifierEvent(msgWindow, msgHdr, aContentLocation, aCanOverride);
+    // Post this as an event because it can cause dom mutations, and we
+    // get called at a bad time to be causing dom mutations.
+    if (event)
+      NS_DispatchToCurrentThread(event);
+  }
+}
+
+/**
  * This function is used to determine if we allow content for a remote message.
  * If we reject loading remote content, then we'll inform the message window
  * that this message has remote content (and hence we are not loading it).
  *
  * See ShouldAcceptRemoteContentForMsgHdr for the actual decisions that
  * determine if we are going to allow remote content.
  */
 void
--- a/mailnews/base/src/nsMsgContentPolicy.h
+++ b/mailnews/base/src/nsMsgContentPolicy.h
@@ -63,16 +63,19 @@ protected:
   bool IsExposedProtocol(nsIURI *aContentLocation);
   bool IsExposedChromeProtocol(nsIURI *aContentLocation);
   bool ShouldBlockUnexposedProtocol(nsIURI *aContentLocation);
 
   bool ShouldAcceptRemoteContentForSender(nsIMsgDBHdr *aMsgHdr);
   int16_t ShouldAcceptRemoteContentForMsgHdr(nsIMsgDBHdr *aMsgHdr,
                                              nsIURI *aRequestingLocation,
                                              nsIURI *aContentLocation);
+  void NotifyContentWasBlocked(nsIURI *aOriginatorLocation,
+                               nsIURI *aContentLocation,
+                               bool aCanOverride);
   void ShouldAcceptContentForPotentialMsg(nsIURI *aOriginatorLocation,
                                           nsIURI *aContentLocation,
                                           int16_t *aDecision);
   void ComposeShouldLoad(nsIMsgCompose *aMsgCompose,
                          nsISupports *aRequestingContext,
                          nsIURI *aContentLocation, int16_t *aDecision);
   already_AddRefed<nsIMsgCompose> GetMsgComposeForContext(nsISupports *aRequestingContext);
 
--- a/mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilter.cpp
+++ b/mailnews/extensions/bayesian-spam-filter/src/nsBayesianFilter.cpp
@@ -989,17 +989,18 @@ NS_IMETHODIMP TokenStreamListener::OnEnd
 
 NS_IMETHODIMP TokenStreamListener::OnEndMsgDownload(nsIMsgMailNewsUrl *url)
 {
     return NS_OK;
 }
 
 
 NS_IMETHODIMP TokenStreamListener::OnMsgHasRemoteContent(nsIMsgDBHdr *aMsgHdr,
-                                                         nsIURI *aContentURI)
+                                                         nsIURI *aContentURI,
+                                                         bool aCanOverride)
 {
     return NS_OK;
 }
 
 NS_IMETHODIMP TokenStreamListener::OnEndMsgHeaders(nsIMsgMailNewsUrl *url)
 {
     return NS_OK;
 }
--- a/mailnews/extensions/smime/public/nsIEncryptedSMIMEURIsSrvc.idl
+++ b/mailnews/extensions/smime/public/nsIEncryptedSMIMEURIsSrvc.idl
@@ -8,14 +8,17 @@
    that is only accessible from C++.
 */
 
 #include "nsISupports.idl"
 
 [scriptable, uuid(f86e55c9-530b-483f-91a7-10fb5b852488)]
 interface nsIEncryptedSMIMEURIsService : nsISupports
 {
+  /// Remember that this URI is encrypted.
   void rememberEncrypted(in AUTF8String uri);
 
+  /// Forget that this URI is encrypted.
   void forgetEncrypted(in AUTF8String uri);
 
+  /// Check if this URI is encrypted.
   boolean isEncrypted(in AUTF8String uri);
 };
--- a/mailnews/mime/public/nsIMimeMiscStatus.idl
+++ b/mailnews/mime/public/nsIMimeMiscStatus.idl
@@ -55,18 +55,19 @@ interface nsIMsgHeaderSink : nsISupports
 
   attribute nsISupports securityInfo;
 
   /**
    * onMsgHasRemoteContent is called each time content policy encounters remote
    * content that it will block from loading.
    * @param aMsgHdr header of the message the content is located in
    * @param aContentURI location that will be blocked.
+   * @param aCanOverride can the blocking be overridden or not
    */
-  void onMsgHasRemoteContent(in nsIMsgDBHdr aMsgHdr, in nsIURI aContentURI);
+  void onMsgHasRemoteContent(in nsIMsgDBHdr aMsgHdr, in nsIURI aContentURI, in boolean aCanOverride);
 
   readonly attribute nsIMsgDBHdr dummyMsgHeader;
 
   // used as a hook for extension mime content handlers to store data that can later
   // be accessed by other parts of the code, e.g., UI code.
   // TODO - Should replace securityInfo
   readonly attribute nsIWritablePropertyBag2 properties;
   // When streaming a new message, properties should be reset, so that there are