Bug 1403814 - Block toplevel data: URI navigations only if openend in the browser. r=smaug
authorChristoph Kerschbaumer <ckerschb@christophkerschbaumer.com>
Fri, 03 Nov 2017 13:23:11 +0100
changeset 443315 0c4ecb84046395afd7c5ed4a250b64991c9b0da7
parent 443314 5d25efb36d34042e37075af204e109d29bc9bded
child 443316 9eb29f6c93a36d7357ae72c03a6fc0ab57ca5baf
push id1618
push userCallek@gmail.com
push dateThu, 11 Jan 2018 17:45:48 +0000
treeherdermozilla-release@882ca853e05a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1403814
milestone58.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1403814 - Block toplevel data: URI navigations only if openend in the browser. r=smaug
docshell/base/nsDSURIContentListener.cpp
docshell/base/nsDocShell.cpp
dom/security/nsContentSecurityManager.cpp
dom/security/nsContentSecurityManager.h
ipc/glue/BackgroundUtils.cpp
netwerk/base/LoadInfo.cpp
netwerk/base/LoadInfo.h
netwerk/base/nsILoadInfo.idl
netwerk/ipc/NeckoChannelParams.ipdlh
--- a/docshell/base/nsDSURIContentListener.cpp
+++ b/docshell/base/nsDSURIContentListener.cpp
@@ -9,16 +9,17 @@
 #include "nsIChannel.h"
 #include "nsServiceManagerUtils.h"
 #include "nsDocShellCID.h"
 #include "nsIWebNavigationInfo.h"
 #include "nsIDocument.h"
 #include "nsIDOMWindow.h"
 #include "nsIHttpChannel.h"
 #include "nsError.h"
+#include "nsContentSecurityManager.h"
 #include "nsDocShellLoadTypes.h"
 #include "nsIMultiPartChannel.h"
 
 using namespace mozilla;
 
 nsDSURIContentListener::nsDSURIContentListener(nsDocShell* aDocShell)
   : mDocShell(aDocShell)
   , mExistingJPEGRequest(nullptr)
@@ -81,16 +82,24 @@ nsDSURIContentListener::DoContent(const 
   *aAbortProcess = false;
 
   // determine if the channel has just been retargeted to us...
   nsLoadFlags loadFlags = 0;
   nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest);
 
   if (aOpenedChannel) {
     aOpenedChannel->GetLoadFlags(&loadFlags);
+
+    // block top-level data URI navigations if triggered by the web
+    if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(aOpenedChannel)) {
+      // logging to console happens within AllowTopLevelNavigationToDataURI
+      aRequest->Cancel(NS_ERROR_DOM_BAD_URI);
+      *aAbortProcess = true;
+      return NS_OK; 
+    }
   }
 
   if (loadFlags & nsIChannel::LOAD_RETARGETED_DOCUMENT_URI) {
     // XXX: Why does this not stop the content too?
     mDocShell->Stop(nsIWebNavigation::STOP_NETWORK);
 
     mDocShell->SetLoadType(aIsContentPreferred ? LOAD_LINK : LOAD_NORMAL);
   }
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -9962,29 +9962,16 @@ nsDocShell::InternalLoad(nsIURI* aURI,
       // an iframe since that's more common.
       contentType = nsIContentPolicy::TYPE_INTERNAL_IFRAME;
     }
   } else {
     contentType = nsIContentPolicy::TYPE_DOCUMENT;
     isTargetTopLevelDocShell = true;
   }
 
-  nsIDocument* doc = mContentViewer ? mContentViewer->GetDocument()
-                                    : nullptr;
-  if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
-        aURI,
-        contentType,
-        aTriggeringPrincipal,
-        doc,
-        (aLoadType == LOAD_NORMAL_EXTERNAL),
-        !aFileName.IsVoid())) {
-    // logging to console happens within AllowTopLevelNavigationToDataURI
-    return NS_OK;
-  }
-
   // If there's no targetDocShell, that means we are about to create a new
   // window (or aWindowTarget is empty). Perform a content policy check before
   // creating the window. Please note for all other docshell loads
   // content policy checks are performed within the contentSecurityManager
   // when the channel is about to be openend.
   if (!targetDocShell && !aWindowTarget.IsEmpty()) {
     MOZ_ASSERT(contentType == nsIContentPolicy::TYPE_DOCUMENT,
                "opening a new window requires type to be TYPE_DOCUMENT");
@@ -10103,16 +10090,19 @@ nsDocShell::InternalLoad(nsIURI* aURI,
 
         nsCOMPtr<nsIDocShellTreeItem> parent;
         treeItem->GetSameTypeParent(getter_AddRefs(parent));
         parent.swap(treeItem);
       } while (treeItem);
     }
   }
 
+  nsIDocument* doc = mContentViewer ? mContentViewer->GetDocument()
+                                    : nullptr;
+
   const bool isDocumentAuxSandboxed = doc &&
     (doc->GetSandboxFlags() & SANDBOXED_AUXILIARY_NAVIGATION);
 
   if (aURI && mLoadURIDelegate &&
       (!targetDocShell || targetDocShell == static_cast<nsIDocShell*>(this))) {
     // Dispatch only load requests for the current or a new window to the
     // delegate, e.g., to allow for GeckoView apps to handle the load event
     // outside of Gecko.
@@ -11187,16 +11177,17 @@ nsDocShell::DoURILoad(nsIURI* aURI,
       new LoadInfo(loadingWindow, aTriggeringPrincipal, topLevelLoadingContext,
                    securityFlags) :
       new LoadInfo(loadingPrincipal, aTriggeringPrincipal, loadingNode,
                    securityFlags, aContentPolicyType);
 
   if (aPrincipalToInherit) {
     loadInfo->SetPrincipalToInherit(aPrincipalToInherit);
   }
+  loadInfo->SetLoadTriggeredFromExternal(aLoadFromExternal);
 
   // We have to do this in case our OriginAttributes are different from the
   // OriginAttributes of the parent document. Or in case there isn't a
   // parent document.
   bool isTopLevelDoc = mItemType == typeContent &&
                        (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT ||
                         GetIsMozBrowser());
 
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -14,78 +14,87 @@
 #include "nsContentUtils.h"
 #include "nsCORSListenerProxy.h"
 #include "nsIStreamListener.h"
 #include "nsIDocument.h"
 #include "nsMixedContentBlocker.h"
 #include "nsCDefaultURIFixup.h"
 #include "nsIURIFixup.h"
 #include "nsIImageLoadingContent.h"
-#include "NullPrincipal.h"
 
 #include "mozilla/dom/Element.h"
+#include "mozilla/dom/TabChild.h"
 
 NS_IMPL_ISUPPORTS(nsContentSecurityManager,
                   nsIContentSecurityManager,
                   nsIChannelEventSink)
 
 /* static */ bool
-nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
-  nsIURI* aURI,
-  nsContentPolicyType aContentPolicyType,
-  nsIPrincipal* aTriggeringPrincipal,
-  nsIDocument* aDoc,
-  bool aLoadFromExternal,
-  bool aIsDownLoad)
+nsContentSecurityManager::AllowTopLevelNavigationToDataURI(nsIChannel* aChannel)
 {
   // Let's block all toplevel document navigations to a data: URI.
   // In all cases where the toplevel document is navigated to a
   // data: URI the triggeringPrincipal is a codeBasePrincipal, or
   // a NullPrincipal. In other cases, e.g. typing a data: URL into
   // the URL-Bar, the triggeringPrincipal is a SystemPrincipal;
   // we don't want to block those loads. Only exception, loads coming
   // from an external applicaton (e.g. Thunderbird) don't load
   // using a codeBasePrincipal, but we want to block those loads.
   if (!mozilla::net::nsIOService::BlockToplevelDataUriNavigations()) {
     return true;
   }
-  if (aContentPolicyType != nsIContentPolicy::TYPE_DOCUMENT || aIsDownLoad) {
+  nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
+  if (!loadInfo) {
     return true;
   }
+  if (loadInfo->GetExternalContentPolicyType() != nsIContentPolicy::TYPE_DOCUMENT) {
+    return true;
+  }
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri));
+  NS_ENSURE_SUCCESS(rv, true);
   bool isDataURI =
-    (NS_SUCCEEDED(aURI->SchemeIs("data", &isDataURI)) && isDataURI);
+    (NS_SUCCEEDED(uri->SchemeIs("data", &isDataURI)) && isDataURI);
   if (!isDataURI) {
     return true;
   }
   // Whitelist data: images as long as they are not SVGs
   nsAutoCString filePath;
-  aURI->GetFilePath(filePath);
+  uri->GetFilePath(filePath);
   if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/")) &&
       !StringBeginsWith(filePath, NS_LITERAL_CSTRING("image/svg+xml"))) {
     return true;
   }
   // Whitelist data: PDFs
   if (StringBeginsWith(filePath, NS_LITERAL_CSTRING("application/pdf"))) {
     return true;
   }
-  if (!aLoadFromExternal &&
-      nsContentUtils::IsSystemPrincipal(aTriggeringPrincipal)) {
+  // Redirecting to a toplevel data: URI is not allowed, hence we make
+  // sure the RedirectChain is empty.
+  if (!loadInfo->GetLoadTriggeredFromExternal() &&
+      nsContentUtils::IsSystemPrincipal(loadInfo->TriggeringPrincipal()) &&
+      loadInfo->RedirectChain().IsEmpty()) {
     return true;
   }
   nsAutoCString dataSpec;
-  aURI->GetSpec(dataSpec);
+  uri->GetSpec(dataSpec);
   if (dataSpec.Length() > 50) {
     dataSpec.Truncate(50);
     dataSpec.AppendLiteral("...");
   }
+  nsCOMPtr<nsITabChild> tabChild = do_QueryInterface(loadInfo->ContextForTopLevelLoad());
+  nsCOMPtr<nsIDocument> doc;
+  if (tabChild) {
+    doc = static_cast<mozilla::dom::TabChild*>(tabChild.get())->GetDocument();
+  }
   NS_ConvertUTF8toUTF16 specUTF16(NS_UnescapeURL(dataSpec));
   const char16_t* params[] = { specUTF16.get() };
   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                   NS_LITERAL_CSTRING("DATA_URI_BLOCKED"),
-                                  aDoc,
+                                  doc,
                                   nsContentUtils::eSECURITY_PROPERTIES,
                                   "BlockTopLevelDataURINavigation",
                                   params, ArrayLength(params));
   return false;
 }
 
 static nsresult
 ValidateSecurityFlags(nsILoadInfo* aLoadInfo)
@@ -571,39 +580,16 @@ nsContentSecurityManager::AsyncOnChannel
   if (loadInfo && loadInfo->GetEnforceSecurity()) {
     nsresult rv = CheckChannel(aNewChannel);
     if (NS_FAILED(rv)) {
       aOldChannel->Cancel(rv);
       return rv;
     }
   }
 
-  // Redirecting to a toplevel data: URI is not allowed, hence we pass
-  // a NullPrincipal as the TriggeringPrincipal to
-  // AllowTopLevelNavigationToDataURI() which definitely blocks any
-  // data: URI load.
-  nsCOMPtr<nsILoadInfo> newLoadInfo = aNewChannel->GetLoadInfo();
-  if (newLoadInfo) {
-    nsCOMPtr<nsIURI> uri;
-    nsresult rv = NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(uri));
-    NS_ENSURE_SUCCESS(rv, rv);
-    nsCOMPtr<nsIPrincipal> nullTriggeringPrincipal = NullPrincipal::Create();
-    if (!nsContentSecurityManager::AllowTopLevelNavigationToDataURI(
-          uri,
-          newLoadInfo->GetExternalContentPolicyType(),
-          nullTriggeringPrincipal,
-          nullptr, // no doc available, log to browser console
-          false,
-          false)) {
-        // logging to console happens within AllowTopLevelNavigationToDataURI
-      aOldChannel->Cancel(NS_ERROR_DOM_BAD_URI);
-      return NS_ERROR_DOM_BAD_URI;
-    }
-  }
-
   // Also verify that the redirecting server is allowed to redirect to the
   // given URI
   nsCOMPtr<nsIPrincipal> oldPrincipal;
   nsContentUtils::GetSecurityManager()->
     GetChannelResultPrincipal(aOldChannel, getter_AddRefs(oldPrincipal));
 
   nsCOMPtr<nsIURI> newURI;
   Unused << NS_GetFinalChannelURI(aNewChannel, getter_AddRefs(newURI));
--- a/dom/security/nsContentSecurityManager.h
+++ b/dom/security/nsContentSecurityManager.h
@@ -28,22 +28,17 @@ public:
   NS_DECL_NSICONTENTSECURITYMANAGER
   NS_DECL_NSICHANNELEVENTSINK
 
   nsContentSecurityManager() {}
 
   static nsresult doContentSecurityCheck(nsIChannel* aChannel,
                                          nsCOMPtr<nsIStreamListener>& aInAndOutListener);
 
-  static bool AllowTopLevelNavigationToDataURI(nsIURI* aURI,
-                                               nsContentPolicyType aContentPolicyType,
-                                               nsIPrincipal* aTriggeringPrincipal,
-                                               nsIDocument* aDoc,
-                                               bool aLoadFromExternal,
-                                               bool aIsDownload);
+  static bool AllowTopLevelNavigationToDataURI(nsIChannel* aChannel);
 
 private:
   static nsresult CheckChannel(nsIChannel* aChannel);
 
   virtual ~nsContentSecurityManager() {}
 
 };
 
--- a/ipc/glue/BackgroundUtils.cpp
+++ b/ipc/glue/BackgroundUtils.cpp
@@ -399,16 +399,17 @@ LoadInfoToLoadInfoArgs(nsILoadInfo *aLoa
       aLoadInfo->GetOriginAttributes(),
       redirectChainIncludingInternalRedirects,
       redirectChain,
       ancestorPrincipals,
       aLoadInfo->AncestorOuterWindowIDs(),
       aLoadInfo->CorsUnsafeHeaders(),
       aLoadInfo->GetForcePreflight(),
       aLoadInfo->GetIsPreflight(),
+      aLoadInfo->GetLoadTriggeredFromExternal(),
       aLoadInfo->GetForceHSTSPriming(),
       aLoadInfo->GetMixedContentWouldBlock(),
       aLoadInfo->GetIsHSTSPriming(),
       aLoadInfo->GetIsHSTSPrimingUpgrade()
       );
 
   return NS_OK;
 }
@@ -506,16 +507,17 @@ LoadInfoArgsToLoadInfo(const OptionalLoa
                           loadInfoArgs.originAttributes(),
                           redirectChainIncludingInternalRedirects,
                           redirectChain,
                           Move(ancestorPrincipals),
                           loadInfoArgs.ancestorOuterWindowIDs(),
                           loadInfoArgs.corsUnsafeHeaders(),
                           loadInfoArgs.forcePreflight(),
                           loadInfoArgs.isPreflight(),
+                          loadInfoArgs.loadTriggeredFromExternal(),
                           loadInfoArgs.forceHSTSPriming(),
                           loadInfoArgs.mixedContentWouldBlock(),
                           loadInfoArgs.isHSTSPriming(),
                           loadInfoArgs.isHSTSPrimingUpgrade()
                           );
 
    loadInfo.forget(outLoadInfo);
    return NS_OK;
--- a/netwerk/base/LoadInfo.cpp
+++ b/netwerk/base/LoadInfo.cpp
@@ -2,16 +2,17 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* 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 "mozilla/LoadInfo.h"
 
 #include "mozilla/Assertions.h"
+#include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozIThirdPartyUtil.h"
 #include "nsFrameLoader.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIDocShell.h"
 #include "nsIDocument.h"
 #include "nsIDOMDocument.h"
 #include "nsIFrameLoader.h"
@@ -63,16 +64,17 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
   , mParentOuterWindowID(0)
   , mTopOuterWindowID(0)
   , mFrameOuterWindowID(0)
   , mEnforceSecurity(false)
   , mInitialSecurityCheckDone(false)
   , mIsThirdPartyContext(false)
   , mForcePreflight(false)
   , mIsPreflight(false)
+  , mLoadTriggeredFromExternal(false)
   , mForceHSTSPriming(false)
   , mMixedContentWouldBlock(false)
   , mIsHSTSPriming(false)
   , mIsHSTSPrimingUpgrade(false)
 {
   MOZ_ASSERT(mLoadingPrincipal);
   MOZ_ASSERT(mTriggeringPrincipal);
 
@@ -245,16 +247,17 @@ LoadInfo::LoadInfo(nsPIDOMWindowOuter* a
   , mParentOuterWindowID(0)
   , mTopOuterWindowID(0)
   , mFrameOuterWindowID(0)
   , mEnforceSecurity(false)
   , mInitialSecurityCheckDone(false)
   , mIsThirdPartyContext(false) // NB: TYPE_DOCUMENT implies not third-party.
   , mForcePreflight(false)
   , mIsPreflight(false)
+  , mLoadTriggeredFromExternal(false)
   , mForceHSTSPriming(false)
   , mMixedContentWouldBlock(false)
   , mIsHSTSPriming(false)
   , mIsHSTSPrimingUpgrade(false)
 {
   // Top-level loads are never third-party
   // Grab the information we can out of the window.
   MOZ_ASSERT(aOuterWindow);
@@ -319,16 +322,17 @@ LoadInfo::LoadInfo(const LoadInfo& rhs)
   , mRedirectChainIncludingInternalRedirects(
       rhs.mRedirectChainIncludingInternalRedirects)
   , mRedirectChain(rhs.mRedirectChain)
   , mAncestorPrincipals(rhs.mAncestorPrincipals)
   , mAncestorOuterWindowIDs(rhs.mAncestorOuterWindowIDs)
   , mCorsUnsafeHeaders(rhs.mCorsUnsafeHeaders)
   , mForcePreflight(rhs.mForcePreflight)
   , mIsPreflight(rhs.mIsPreflight)
+  , mLoadTriggeredFromExternal(rhs.mLoadTriggeredFromExternal)
   , mForceHSTSPriming(rhs.mForceHSTSPriming)
   , mMixedContentWouldBlock(rhs.mMixedContentWouldBlock)
   , mIsHSTSPriming(rhs.mIsHSTSPriming)
   , mIsHSTSPrimingUpgrade(rhs.mIsHSTSPrimingUpgrade)
 {
 }
 
 LoadInfo::LoadInfo(nsIPrincipal* aLoadingPrincipal,
@@ -354,16 +358,17 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
                    const OriginAttributes& aOriginAttributes,
                    RedirectHistoryArray& aRedirectChainIncludingInternalRedirects,
                    RedirectHistoryArray& aRedirectChain,
                    nsTArray<nsCOMPtr<nsIPrincipal>>&& aAncestorPrincipals,
                    const nsTArray<uint64_t>& aAncestorOuterWindowIDs,
                    const nsTArray<nsCString>& aCorsUnsafeHeaders,
                    bool aForcePreflight,
                    bool aIsPreflight,
+                   bool aLoadTriggeredFromExternal,
                    bool aForceHSTSPriming,
                    bool aMixedContentWouldBlock,
                    bool aIsHSTSPriming,
                    bool aIsHSTSPrimingUpgrade)
   : mLoadingPrincipal(aLoadingPrincipal)
   , mTriggeringPrincipal(aTriggeringPrincipal)
   , mPrincipalToInherit(aPrincipalToInherit)
   , mResultPrincipalURI(aResultPrincipalURI)
@@ -383,16 +388,17 @@ LoadInfo::LoadInfo(nsIPrincipal* aLoadin
   , mInitialSecurityCheckDone(aInitialSecurityCheckDone)
   , mIsThirdPartyContext(aIsThirdPartyContext)
   , mOriginAttributes(aOriginAttributes)
   , mAncestorPrincipals(Move(aAncestorPrincipals))
   , mAncestorOuterWindowIDs(aAncestorOuterWindowIDs)
   , mCorsUnsafeHeaders(aCorsUnsafeHeaders)
   , mForcePreflight(aForcePreflight)
   , mIsPreflight(aIsPreflight)
+  , mLoadTriggeredFromExternal(aLoadTriggeredFromExternal)
   , mForceHSTSPriming (aForceHSTSPriming)
   , mMixedContentWouldBlock(aMixedContentWouldBlock)
   , mIsHSTSPriming(aIsHSTSPriming)
   , mIsHSTSPrimingUpgrade(aIsHSTSPrimingUpgrade)
 {
   // Only top level TYPE_DOCUMENT loads can have a null loadingPrincipal
   MOZ_ASSERT(mLoadingPrincipal || aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT);
   MOZ_ASSERT(mTriggeringPrincipal);
@@ -978,16 +984,33 @@ LoadInfo::SetUpgradeInsecureRequests()
 NS_IMETHODIMP
 LoadInfo::GetIsPreflight(bool* aIsPreflight)
 {
   *aIsPreflight = mIsPreflight;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+LoadInfo::SetLoadTriggeredFromExternal(bool aLoadTriggeredFromExternal)
+{
+  MOZ_ASSERT(!aLoadTriggeredFromExternal ||
+             mInternalContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT,
+             "can only set load triggered from external for TYPE_DOCUMENT");
+  mLoadTriggeredFromExternal = aLoadTriggeredFromExternal;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+LoadInfo::GetLoadTriggeredFromExternal(bool* aLoadTriggeredFromExternal)
+{
+  *aLoadTriggeredFromExternal = mLoadTriggeredFromExternal;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 LoadInfo::GetForceHSTSPriming(bool* aForceHSTSPriming)
 {
   *aForceHSTSPriming = mForceHSTSPriming;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 LoadInfo::GetMixedContentWouldBlock(bool *aMixedContentWouldBlock)
--- a/netwerk/base/LoadInfo.h
+++ b/netwerk/base/LoadInfo.h
@@ -115,16 +115,17 @@ private:
            const OriginAttributes& aOriginAttributes,
            RedirectHistoryArray& aRedirectChainIncludingInternalRedirects,
            RedirectHistoryArray& aRedirectChain,
            nsTArray<nsCOMPtr<nsIPrincipal>>&& aAncestorPrincipals,
            const nsTArray<uint64_t>& aAncestorOuterWindowIDs,
            const nsTArray<nsCString>& aUnsafeHeaders,
            bool aForcePreflight,
            bool aIsPreflight,
+           bool aLoadTriggeredFromExternal,
            bool aForceHSTSPriming,
            bool aMixedContentWouldBlock,
            bool aIsHSTSPriming,
            bool aIsHSTSPrimingUpgrade);
   LoadInfo(const LoadInfo& rhs);
 
   NS_IMETHOD GetRedirects(JSContext* aCx, JS::MutableHandle<JS::Value> aRedirects,
                           const RedirectHistoryArray& aArra);
@@ -170,16 +171,17 @@ private:
   OriginAttributes                 mOriginAttributes;
   RedirectHistoryArray             mRedirectChainIncludingInternalRedirects;
   RedirectHistoryArray             mRedirectChain;
   nsTArray<nsCOMPtr<nsIPrincipal>> mAncestorPrincipals;
   nsTArray<uint64_t>               mAncestorOuterWindowIDs;
   nsTArray<nsCString>              mCorsUnsafeHeaders;
   bool                             mForcePreflight;
   bool                             mIsPreflight;
+  bool                             mLoadTriggeredFromExternal;
 
   bool                             mForceHSTSPriming : 1;
   bool                             mMixedContentWouldBlock : 1;
   bool                             mIsHSTSPriming: 1;
   bool                             mIsHSTSPrimingUpgrade: 1;
 };
 
 } // namespace net
--- a/netwerk/base/nsILoadInfo.idl
+++ b/netwerk/base/nsILoadInfo.idl
@@ -594,16 +594,23 @@ interface nsILoadInfo : nsISupports
    * Please note, once the flag is set to true it must remain true
    * throughout the lifetime of the channel. Trying to set it
    * to anything else than true will be discarded.
    *
    */
   [infallible] attribute boolean initialSecurityCheckDone;
 
   /**
+   * Returns true if the load was triggered from an external application
+   * (e.g. Thunderbird). Please note that this flag will only ever be true
+   * if the load is of TYPE_DOCUMENT. 
+   */
+  [infallible] attribute boolean loadTriggeredFromExternal;
+
+  /**
    * Whenever a channel gets redirected, append the redirect history entry of
    * the channel which contains principal referrer and remote address [before
    * the channels got redirected] to the loadinfo, so that at every point this
    * array provides us information about all the redirects this channel went
    * through.
    * @param entry, the nsIRedirectHistoryEntry before the channel
    *         got redirected.
    * @param aIsInternalRedirect should be true if the channel is going
--- a/netwerk/ipc/NeckoChannelParams.ipdlh
+++ b/netwerk/ipc/NeckoChannelParams.ipdlh
@@ -65,16 +65,17 @@ struct LoadInfoArgs
    * See nsILoadInfo.idl for details.
    */
   PrincipalInfo[]             ancestorPrincipals;
   uint64_t[]                  ancestorOuterWindowIDs;
 
   nsCString[]                 corsUnsafeHeaders;
   bool                        forcePreflight;
   bool                        isPreflight;
+  bool                        loadTriggeredFromExternal;
   bool                        forceHSTSPriming;
   bool                        mixedContentWouldBlock;
   bool                        isHSTSPriming;
   bool                        isHSTSPrimingUpgrade;
 };
 
 /**
  * Not every channel necessarily has a loadInfo attached.