Bug 764987 - "Allow extensions to add exposed protocols in nsMsgContentPolicy". r=rkent
authorKent James <rkent>
Wed, 16 Sep 2015 14:08:00 +0200
changeset 23311 6203f811717fb2c7c2c10eebce4ebb3a466ca4af
parent 23310 d8297b57fec96d7735db58823e77ed38e9bc4993
child 23312 e9fe39e33a27ca7274180dd570c74b79162d35b2
push id1510
push usermbanner@mozilla.com
push dateThu, 29 Oct 2015 19:29:37 +0000
treeherdercomm-beta@d1522aaa8689 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrkent
bugs764987
Bug 764987 - "Allow extensions to add exposed protocols in nsMsgContentPolicy". r=rkent
mailnews/base/public/moz.build
mailnews/base/public/nsIMsgContentPolicy.idl
mailnews/base/src/nsMsgContentPolicy.cpp
mailnews/base/src/nsMsgContentPolicy.h
mailnews/base/test/unit/test_nsIMsgContentPolicy.js
mailnews/base/test/unit/xpcshell.ini
--- a/mailnews/base/public/moz.build
+++ b/mailnews/base/public/moz.build
@@ -17,16 +17,17 @@ XPIDL_SOURCES += [
     'nsIMessenger.idl',
     'nsIMessengerMigrator.idl',
     'nsIMessengerOSIntegration.idl',
     'nsIMessengerWindowService.idl',
     'nsIMsgAccount.idl',
     'nsIMsgAccountManager.idl',
     'nsIMsgAsyncPrompter.idl',
     'nsIMsgBiffManager.idl',
+    'nsIMsgContentPolicy.idl',
     'nsIMsgCopyService.idl',
     'nsIMsgCopyServiceListener.idl',
     'nsIMsgCustomColumnHandler.idl',
     'nsIMsgDBView.idl',
     'nsIMsgFolder.idl',
     'nsIMsgFolderCache.idl',
     'nsIMsgFolderCacheElement.idl',
     'nsIMsgFolderCompactor.idl',
new file mode 100644
--- /dev/null
+++ b/mailnews/base/public/nsIMsgContentPolicy.idl
@@ -0,0 +1,36 @@
+/* -*- mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(c29b2fd3-64d0-4083-a096-c20a9b847a99)]
+
+/**
+ * This interface provide functions which help extension developers
+ * add their customized schema to the exposed protocls of nsMsgContentPolicy.
+ * By default, a list of existing protocols (such as imap and nntp)
+ * are allowed to process urls locally, while non-matching urls are required
+ * to be processed as external.
+ * This interface allows additional protocols to be added to
+ * the list of protocols that are processed locally.
+ * Typically this would be used in cases where a new messaging protocol
+ * is being added by an extension.
+ */
+interface nsIMsgContentPolicy : nsISupports {
+  /**
+   * Add the specific aScheme to nsMsgContentPolicy's exposed protocols.
+   *
+   * @param aScheme scheme who will be added to nsMsgContentPolicy's exposed protocols
+   */
+  void addExposedProtocol(in ACString aScheme);
+
+  /**
+   * Remove the specific aScheme from nsMsgContentPolicy's exposed protocols.
+   *
+   * @param aScheme scheme who will be removed from nsMsgContentPolicy's exposed protocols
+   */
+  void removeExposedProtocol(in ACString aScheme);
+};
+
--- a/mailnews/base/src/nsMsgContentPolicy.cpp
+++ b/mailnews/base/src/nsMsgContentPolicy.cpp
@@ -28,28 +28,29 @@
 
 static const char kBlockRemoteImages[] = "mailnews.message_display.disable_remote_image";
 static const char kAllowPlugins[] = "mailnews.message_display.allow_plugins";
 static const char kTrustedDomains[] =  "mail.trusteddomains";
 
 using namespace mozilla::mailnews;
 
 // Per message headder flags to keep track of whether the user is allowing remote
-// content for a particular message. 
+// content for a particular message.
 // if you change or add more values to these constants, be sure to modify
 // the corresponding definitions in mailWindowOverlay.js
 #define kNoRemoteContentPolicy 0
 #define kBlockRemoteContent 1
 #define kAllowRemoteContent 2
 
-NS_IMPL_ISUPPORTS(nsMsgContentPolicy, 
-                   nsIContentPolicy,
-                   nsIWebProgressListener,
-                   nsIObserver,
-                   nsISupportsWeakReference)
+NS_IMPL_ISUPPORTS(nsMsgContentPolicy,
+                  nsIContentPolicy,
+                  nsIWebProgressListener,
+                  nsIMsgContentPolicy,
+                  nsIObserver,
+                  nsISupportsWeakReference)
 
 nsMsgContentPolicy::nsMsgContentPolicy()
 {
   mAllowPlugins = false;
   mBlockRemoteImages = true;
 }
 
 nsMsgContentPolicy::~nsMsgContentPolicy()
@@ -82,32 +83,32 @@ nsresult nsMsgContentPolicy::Init()
   // Grab a handle on the PermissionManager service for managing allowed remote
   // content senders.
   mPermissionManager = do_GetService(NS_PERMISSIONMANAGER_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
-/** 
+/**
  * @returns true if the sender referenced by aMsgHdr is explicitly allowed to
  *          load remote images according to the PermissionManager
  */
 bool
 nsMsgContentPolicy::ShouldAcceptRemoteContentForSender(nsIMsgDBHdr *aMsgHdr)
 {
   if (!aMsgHdr)
     return false;
 
   // extract the e-mail address from the msg hdr
   nsCString author;
   nsresult rv = aMsgHdr->GetAuthor(getter_Copies(author));
   NS_ENSURE_SUCCESS(rv, false);
 
-  nsCString emailAddress; 
+  nsCString emailAddress;
   ExtractEmail(EncodedHeader(author), emailAddress);
   if (emailAddress.IsEmpty())
     return false;
 
   nsCOMPtr<nsIIOService> ios = do_GetService("@mozilla.org/network/io-service;1", &rv);
   NS_ENSURE_SUCCESS(rv, false);
   nsCOMPtr<nsIURI> mailURI;
   emailAddress.Insert("mailto:", 0);
@@ -129,17 +130,17 @@ nsMsgContentPolicy::ShouldAcceptRemoteCo
  */
 bool nsMsgContentPolicy::IsTrustedDomain(nsIURI * aContentLocation)
 {
   bool trustedDomain = false;
   // get the host name of the server hosting the remote image
   nsAutoCString host;
   nsresult rv = aContentLocation->GetHost(host);
 
-  if (NS_SUCCEEDED(rv) && !mTrustedMailDomains.IsEmpty()) 
+  if (NS_SUCCEEDED(rv) && !mTrustedMailDomains.IsEmpty())
     trustedDomain = MsgHostDomainIsTrusted(host, mTrustedMailDomains);
 
   return trustedDomain;
 }
 
 NS_IMETHODIMP
 nsMsgContentPolicy::ShouldLoad(uint32_t          aContentType,
                                nsIURI           *aContentLocation,
@@ -188,24 +189,24 @@ nsMsgContentPolicy::ShouldLoad(uint32_t 
     return NS_OK;
 #endif
 
   switch(aContentType) {
     // Plugins (nsIContentPolicy::TYPE_OBJECT) are blocked on document load.
   case nsIContentPolicy::TYPE_DOCUMENT:
     // At this point, we have no intention of supporting a different JS
     // setting on a subdocument, so we don't worry about TYPE_SUBDOCUMENT here.
-   
+
     // If the timing were right, we'd enable JavaScript on the docshell
     // for non mailnews URIs here.  However, at this point, the
-    // old document may still be around, so we can't do any enabling just yet.  
-    // Instead, we apply the policy in nsIWebProgressListener::OnLocationChange. 
+    // old document may still be around, so we can't do any enabling just yet.
+    // Instead, we apply the policy in nsIWebProgressListener::OnLocationChange.
     // For now, we explicitly disable JavaScript in order to be safe rather than
     // sorry, because OnLocationChange isn't guaranteed to necessarily be called
-    // soon enough to disable it in time (though bz says it _should_ be called 
+    // soon enough to disable it in time (though bz says it _should_ be called
     // soon enough "in all sane cases").
     rv = SetDisableItemsOnMailNewsUrlDocshells(aContentLocation,
                                                aRequestingContext);
     // if something went wrong during the tweaking, reject this content
     if (NS_FAILED(rv)) {
       NS_WARNING("Failed to set disable items on docShells");
       *aDecision = nsIContentPolicy::REJECT_TYPE;
       return NS_OK;
@@ -216,46 +217,46 @@ nsMsgContentPolicy::ShouldLoad(uint32_t 
     // We cannot block CSP reports.
     *aDecision = nsIContentPolicy::ACCEPT;
     return NS_OK;
     break;
 
   default:
     break;
   }
-  
+
   // NOTE: Not using NS_ENSURE_ARG_POINTER because this is a legitimate case
   // that can happen.  Also keep in mind that the default policy used for a
   // failure code is ACCEPT.
   if (!aRequestingLocation)
     return NS_ERROR_INVALID_POINTER;
 
 #ifdef DEBUG_MsgContentPolicy
   (void)aRequestingLocation->GetSpec(spec);
   fprintf(stderr, "aRequestingLocation = %s\n", spec.get());
 #endif
 
   // If the requesting location is safe, accept the content location request.
   if (IsSafeRequestingLocation(aRequestingLocation))
     return rv;
 
-  // Now default to reject so early returns via NS_ENSURE_SUCCESS 
+  // Now default to reject so early returns via NS_ENSURE_SUCCESS
   // cause content to be rejected.
   *aDecision = nsIContentPolicy::REJECT_REQUEST;
 
   // if aContentLocation is a protocol we handle (imap, pop3, mailbox, etc)
   // or is a chrome url, then allow the load
 
   if (IsExposedProtocol(aContentLocation))
   {
     *aDecision = nsIContentPolicy::ACCEPT;
     return NS_OK;
   }
 
-  // never load unexposed protocols except for http, https and file. 
+  // 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;
@@ -385,16 +386,20 @@ nsMsgContentPolicy::IsExposedProtocol(ns
       MsgLowerCaseEqualsLiteral(contentScheme, "nntp") ||
       MsgLowerCaseEqualsLiteral(contentScheme, "imap") ||
       MsgLowerCaseEqualsLiteral(contentScheme, "addbook") ||
       MsgLowerCaseEqualsLiteral(contentScheme, "pop") ||
       MsgLowerCaseEqualsLiteral(contentScheme, "mailbox") ||
       MsgLowerCaseEqualsLiteral(contentScheme, "about"))
     return true;
 
+  // check if customized exposed scheme
+  if (mCustomExposedProtocols.Contains(contentScheme))
+    return true;
+
   bool isData;
   bool isChrome;
   bool isRes;
   rv = aContentLocation->SchemeIs("chrome", &isChrome);
   NS_ENSURE_SUCCESS(rv, false);
   rv = aContentLocation->SchemeIs("resource", &isRes);
   NS_ENSURE_SUCCESS(rv, false);
   rv = aContentLocation->SchemeIs("data", &isData);
@@ -463,17 +468,17 @@ nsMsgContentPolicy::ShouldAcceptRemoteCo
 
   int16_t result = allowForSender ?
     static_cast<int16_t>(nsIContentPolicy::ACCEPT) :
     static_cast<int16_t>(nsIContentPolicy::REJECT_REQUEST);
 
   // kNoRemoteContentPolicy means we have never set a value on the message
   if (result == nsIContentPolicy::REJECT_REQUEST && !remoteContentPolicy)
     aMsgHdr->SetUint32Property("remoteContentPolicy", kBlockRemoteContent);
-  
+
   return result;
 }
 
 class RemoteContentNotifierEvent : public nsRunnable
 {
 public:
   RemoteContentNotifierEvent(nsIMsgWindow *aMsgWindow, nsIMsgDBHdr *aMsgHdr,
                              nsIURI *aContentURI)
@@ -493,17 +498,17 @@ public:
   }
 
 private:
   nsCOMPtr<nsIMsgWindow> mMsgWindow;
   nsCOMPtr<nsIMsgDBHdr> mMsgHdr;
   nsCOMPtr<nsIURI> mContentURI;
 };
 
-/** 
+/**
  * 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
@@ -543,32 +548,32 @@ nsMsgContentPolicy::ShouldAcceptContentF
                                                   aContentLocation);
 
   // If we're not allowing the remote content, tell the nsIMsgWindow loading
   // this url that this is the case, so that the UI knows to show the remote
   // content header bar, so the user can override if they wish.
   if (*aDecision == nsIContentPolicy::REJECT_REQUEST)
   {
     nsCOMPtr<nsIMsgWindow> msgWindow;
-    (void)mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow)); 
+    (void)mailnewsUrl->GetMsgWindow(getter_AddRefs(msgWindow));
     if (msgWindow)
     {
       nsCOMPtr<nsIRunnable> event =
         new RemoteContentNotifierEvent(msgWindow, msgHdr, aContentLocation);
       // 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);
     }
   }
 }
 
-/** 
+/**
  * Content policy logic for compose windows
- * 
+ *
  */
 void nsMsgContentPolicy::ComposeShouldLoad(nsIMsgCompose *aMsgCompose,
                                            nsISupports *aRequestingContext,
                                            nsIURI *aContentLocation,
                                            int16_t *aDecision)
 {
   NS_PRECONDITION(*aDecision == nsIContentPolicy::REJECT_REQUEST,
                   "ComposeShouldLoad expects default decision to be reject!");
@@ -596,29 +601,29 @@ void nsMsgContentPolicy::ComposeShouldLo
     *aDecision = ShouldAcceptRemoteContentForMsgHdr(msgHdr, nullptr,
                                                     aContentLocation);
 
     // Special case image elements. When replying to a message, we want to allow
     // the user to add remote images to the message. But we don't want remote
     // images that are a part of the quoted content to load. Fortunately, after
     // the quoted message has been inserted into the document, mail compose
     // flags remote content elements that came from the original message with a
-    // moz-do-not-send attribute. 
+    // moz-do-not-send attribute.
     if (*aDecision == nsIContentPolicy::REJECT_REQUEST)
     {
       bool insertingQuotedContent = true;
       aMsgCompose->GetInsertingQuotedContent(&insertingQuotedContent);
       nsCOMPtr<nsIDOMHTMLImageElement> imageElement(do_QueryInterface(aRequestingContext));
       if (!insertingQuotedContent && imageElement)
       {
         nsCOMPtr<nsIDOMElement> element(do_QueryInterface(imageElement));
         if (element)
         {
           bool doNotSendAttrib;
-          if (NS_SUCCEEDED(element->HasAttribute(NS_LITERAL_STRING("moz-do-not-send"), &doNotSendAttrib)) && 
+          if (NS_SUCCEEDED(element->HasAttribute(NS_LITERAL_STRING("moz-do-not-send"), &doNotSendAttrib)) &&
               !doNotSendAttrib)
             *aDecision = nsIContentPolicy::ACCEPT;
         }
       }
     }
   }
 }
 
@@ -650,18 +655,18 @@ already_AddRefed<nsIMsgCompose> nsMsgCon
 
 nsresult nsMsgContentPolicy::SetDisableItemsOnMailNewsUrlDocshells(
   nsIURI *aContentLocation, nsISupports *aRequestingContext)
 {
   // XXX if this class changes so that this method can be called from
   // ShouldProcess, and if it's possible for this to be null when called from
   // ShouldLoad, but not in the corresponding ShouldProcess call,
   // we need to re-think the assumptions underlying this code.
-  
-  // If there's no docshell to get to, there's nowhere for the JavaScript to 
+
+  // If there's no docshell to get to, there's nowhere for the JavaScript to
   // run, so we're already safe and don't need to disable anything.
   if (!aRequestingContext) {
     return NS_OK;
   }
 
   nsresult rv;
   bool isAllowedContent = !ShouldBlockUnexposedProtocol(aContentLocation);
   nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(aContentLocation, &rv);
@@ -676,17 +681,17 @@ nsresult nsMsgContentPolicy::SetDisableI
   nsCOMPtr<nsIFrameLoaderOwner> flOwner = do_QueryInterface(aRequestingContext,
                                                             &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIFrameLoader> frameLoader;
   rv = flOwner->GetFrameLoader(getter_AddRefs(frameLoader));
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(frameLoader, NS_ERROR_INVALID_POINTER);
-  
+
   nsCOMPtr<nsIDocShell> docShell;
   rv = frameLoader->GetDocShell(getter_AddRefs(docShell));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIDocShellTreeItem> docshellTreeItem(do_QueryInterface(docShell, &rv));
   NS_ENSURE_SUCCESS(rv, rv);
 
   // what sort of docshell is this?
@@ -744,17 +749,17 @@ nsMsgContentPolicy::GetRootDocShellForCo
 
 /**
  * Gets the originating URI that started off a set of requests, accounting
  * for multiple iframes.
  *
  * Navigates up the docshell tree from aRequestingContext and finds the
  * highest parent with the same type docshell as aRequestingContext, then
  * returns the URI associated with that docshell.
- */ 
+ */
 nsresult
 nsMsgContentPolicy::GetOriginatingURIForContext(nsISupports *aRequestingContext,
                                                 nsIURI **aURI)
 {
   NS_ENSURE_ARG_POINTER(aRequestingContext);
   nsresult rv;
 
   nsIDocShell *shell = NS_CP_GetDocShellFromContext(aRequestingContext);
@@ -778,26 +783,26 @@ nsMsgContentPolicy::ShouldProcess(uint32
                                   nsISupports      *aRequestingContext,
                                   const nsACString &aMimeGuess,
                                   nsISupports      *aExtra,
                                   nsIPrincipal     *aRequestPrincipal,
                                   int16_t          *aDecision)
 {
   // XXX Returning ACCEPT is presumably only a reasonable thing to do if we
   // think that ShouldLoad is going to catch all possible cases (i.e. that
-  // everything we use to make decisions is going to be available at 
+  // everything we use to make decisions is going to be available at
   // ShouldLoad time, and not only become available in time for ShouldProcess).
   // Do we think that's actually the case?
   *aDecision = nsIContentPolicy::ACCEPT;
   return NS_OK;
 }
 
 NS_IMETHODIMP nsMsgContentPolicy::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData)
 {
-  if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic)) 
+  if (!strcmp(NS_PREFBRANCH_PREFCHANGE_TOPIC_ID, aTopic))
   {
     NS_LossyConvertUTF16toASCII pref(aData);
 
     nsresult rv;
 
     nsCOMPtr<nsIPrefBranch> prefBranchInt = do_QueryInterface(aSubject, &rv);
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -805,21 +810,21 @@ NS_IMETHODIMP nsMsgContentPolicy::Observ
       prefBranchInt->GetBoolPref(kBlockRemoteImages, &mBlockRemoteImages);
     if (pref.Equals(kAllowPlugins))
       prefBranchInt->GetBoolPref(kAllowPlugins, &mAllowPlugins);
   }
 
   return NS_OK;
 }
 
-/** 
+/**
  * We implement the nsIWebProgressListener interface in order to enforce
  * settings at onLocationChange time.
  */
-NS_IMETHODIMP 
+NS_IMETHODIMP
 nsMsgContentPolicy::OnStateChange(nsIWebProgress *aWebProgress,
                                   nsIRequest *aRequest, uint32_t aStateFlags,
                                   nsresult aStatus)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -828,44 +833,44 @@ nsMsgContentPolicy::OnProgressChange(nsI
                                      int32_t aCurSelfProgress,
                                      int32_t aMaxSelfProgress,
                                      int32_t aCurTotalProgress,
                                      int32_t aMaxTotalProgress)
 {
   return NS_OK;
 }
 
-NS_IMETHODIMP 
+NS_IMETHODIMP
 nsMsgContentPolicy::OnLocationChange(nsIWebProgress *aWebProgress,
                                      nsIRequest *aRequest, nsIURI *aLocation,
                                      uint32_t aFlags)
 {
   nsresult rv;
 
   // If anything goes wrong and/or there's no docshell associated with this
-  // request, just give up.  The behavior ends up being "don't consider 
+  // request, just give up.  The behavior ends up being "don't consider
   // re-enabling JS on the docshell", which is the safe thing to do (and if
-  // the problem was that there's no docshell, that means that there was 
+  // the problem was that there's no docshell, that means that there was
   // nowhere for any JavaScript to run, so we're already safe
-  
+
   nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(aWebProgress, &rv);
   if (NS_FAILED(rv)) {
     return NS_OK;
   }
 
 #ifdef DEBUG
   nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest, &rv);
   if (NS_SUCCEEDED(rv)) {
     nsCOMPtr<nsIDocShell> docShell2;
     NS_QueryNotificationCallbacks(channel, docShell2);
     NS_ASSERTION(docShell == docShell2, "aWebProgress and channel callbacks"
                                         " do not point to the same docshell");
   }
 #endif
-  
+
   nsCOMPtr<nsIMsgMessageUrl> messageUrl = do_QueryInterface(aLocation, &rv);
 
   if (NS_SUCCEEDED(rv)) {
     // Disable javascript on message URLs.
     rv = docShell->SetAllowJavascript(false);
     NS_ASSERTION(NS_SUCCEEDED(rv),
                  "Failed to set javascript disabled on docShell");
     // Also disable plugins if the preference requires it.
@@ -895,8 +900,32 @@ nsMsgContentPolicy::OnStatusChange(nsIWe
 }
 
 NS_IMETHODIMP
 nsMsgContentPolicy::OnSecurityChange(nsIWebProgress *aWebProgress,
                                      nsIRequest *aRequest, uint32_t aState)
 {
   return NS_OK;
 }
+
+/**
+ * Implementation of nsIMsgContentPolicy
+ *
+ */
+NS_IMETHODIMP
+nsMsgContentPolicy::AddExposedProtocol(const nsACString &aScheme)
+{
+  if (mCustomExposedProtocols.Contains(nsCString(aScheme)))
+    return NS_OK;
+
+  mCustomExposedProtocols.AppendElement(aScheme);
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgContentPolicy::RemoveExposedProtocol(const nsACString &aScheme)
+{
+  mCustomExposedProtocols.RemoveElement(nsCString(aScheme));
+
+  return NS_OK;
+}
+
--- a/mailnews/base/src/nsMsgContentPolicy.h
+++ b/mailnews/base/src/nsMsgContentPolicy.h
@@ -17,40 +17,44 @@
 #include "nsIObserver.h"
 #include "nsWeakReference.h"
 #include "nsStringGlue.h"
 #include "nsIMsgMailNewsUrl.h"
 #include "nsIWebProgressListener.h"
 #include "nsIMsgCompose.h"
 #include "nsIDocShell.h"
 #include "nsIPermissionManager.h"
+#include "nsIMsgContentPolicy.h"
+#include "nsTArray.h"
 
 /* DBFCFDF0-4489-4faa-8122-190FD1EFA16C */
 #define NS_MSGCONTENTPOLICY_CID \
 { 0xdbfcfdf0, 0x4489, 0x4faa, { 0x81, 0x22, 0x19, 0xf, 0xd1, 0xef, 0xa1, 0x6c } }
 
 #define NS_MSGCONTENTPOLICY_CONTRACTID "@mozilla.org/messenger/content-policy;1"
 
 class nsIMsgDBHdr;
 class nsIDocShell;
 
 class nsMsgContentPolicy : public nsIContentPolicy,
                            public nsIObserver,
                            public nsIWebProgressListener,
+                           public nsIMsgContentPolicy,
                            public nsSupportsWeakReference
 {
 public:
   nsMsgContentPolicy();
 
   nsresult Init();
     
   NS_DECL_ISUPPORTS
   NS_DECL_NSICONTENTPOLICY
   NS_DECL_NSIOBSERVER
   NS_DECL_NSIWEBPROGRESSLISTENER
+  NS_DECL_NSIMSGCONTENTPOLICY
   
 protected:
   virtual ~nsMsgContentPolicy();
 
   bool     mBlockRemoteImages;
   bool     mAllowPlugins;
   nsCString mTrustedMailDomains;
   nsCOMPtr<nsIPermissionManager> mPermissionManager;
@@ -74,11 +78,13 @@ protected:
   already_AddRefed<nsIMsgCompose> GetMsgComposeForContext(nsISupports *aRequestingContext);
 
   nsresult GetRootDocShellForContext(nsISupports *aRequestingContext,
                                      nsIDocShell **aDocShell);
   nsresult GetOriginatingURIForContext(nsISupports *aRequestingContext,
                                        nsIURI **aURI);
   nsresult SetDisableItemsOnMailNewsUrlDocshells(nsIURI *aContentLocation,
                                                  nsISupports *aRequestingContext);
+
+  nsTArray<nsCString> mCustomExposedProtocols;
 };
 
 #endif // _nsMsgContentPolicy_H_
new file mode 100644
--- /dev/null
+++ b/mailnews/base/test/unit/test_nsIMsgContentPolicy.js
@@ -0,0 +1,63 @@
+/* -*- mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ * Test suite for nsIMsgContentPolicy to check we could add/remove customized protocol to
+ * nsMsgContentPolicy.
+ */
+
+function makeURI(aURL) {
+  var ioService = Cc["@mozilla.org/network/io-service;1"]
+                    .getService(Ci.nsIIOService);
+  return ioService.newURI(aURL, null, null);
+}
+
+function run_test() {
+  var content_policy = Cc["@mozilla.org/messenger/content-policy;1"]
+                         .getService(Ci.nsIContentPolicy);
+
+  Assert.ok(content_policy);
+
+  var msg_content_policy = content_policy.QueryInterface(Ci.nsIMsgContentPolicy);
+
+  Assert.ok(msg_content_policy);
+
+  var req_uri = makeURI("custom-scheme://custom_url/1.emal");
+  Assert.ok(req_uri);
+
+  var content_uri = makeURI("custom-scheme://custom_content_url/1.jsp");
+  Assert.ok(content_uri);
+
+  var decision = content_policy.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE,
+                                           content_uri,
+                                           req_uri,
+                                           null,
+                                           "img/jpeg",
+                                           null);
+  Assert.notEqual(decision,
+                  Ci.nsIContentPolicy.ACCEPT,
+                  "customized protocol should not load");
+
+  msg_content_policy.addExposedProtocol("custom-scheme");
+
+  decision = content_policy.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE,
+                                       content_uri,
+                                       req_uri,
+                                       null,
+                                       "img/jpeg",
+                                       null);
+  Assert.equal(decision,
+               Ci.nsIContentPolicy.ACCEPT,
+               "customized protocol should load");
+
+  msg_content_policy.removeExposedProtocol("custom-scheme");
+
+  decision = content_policy.shouldLoad(Ci.nsIContentPolicy.TYPE_IMAGE,
+                                       content_uri,
+                                       req_uri,
+                                       null,
+                                       "img/jpeg",
+                                       null);
+  Assert.notEqual(decision,
+                  Ci.nsIContentPolicy.ACCEPT,
+                  "customized protocol should not load");
+};
+
--- a/mailnews/base/test/unit/xpcshell.ini
+++ b/mailnews/base/test/unit/xpcshell.ini
@@ -38,16 +38,17 @@ support-files = nodelist_test.xml data/*
 [test_junkWhitelisting.js]
 [test_loadVirtualFolders.js]
 [test_mailServices.js]
 [test_mimemaltdetach.js]
 [test_newMailNotification.js]
 # Not yet working for non-Mac OS
 skip-if = os != 'mac'
 
+[test_nsIMsgContentPolicy.js]
 [test_nsIMsgFolder.js]
 [test_nsIMsgFolderListener.js]
 [test_nsIMsgFolderListenerLocal.js]
 [test_nsIMsgTagService.js]
 [test_nsMailDirProvider.js]
 [test_nsMsgDBView.js]
 [test_nsMsgDBView_headerValues.js]
 [test_nsMsgMailSession_Alerts.js]