diff options
Diffstat (limited to 'mailnews/base/src/nsMsgContentPolicy.cpp')
-rw-r--r-- | mailnews/base/src/nsMsgContentPolicy.cpp | 1076 |
1 files changed, 1076 insertions, 0 deletions
diff --git a/mailnews/base/src/nsMsgContentPolicy.cpp b/mailnews/base/src/nsMsgContentPolicy.cpp new file mode 100644 index 000000000..ebc02635f --- /dev/null +++ b/mailnews/base/src/nsMsgContentPolicy.cpp @@ -0,0 +1,1076 @@ +/* -*- 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 "nsMsgContentPolicy.h" +#include "nsIPermissionManager.h" +#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 "nsIDOMHTMLImageElement.h" +#include "nsIFrameLoader.h" +#include "nsIWebProgress.h" +#include "nsMsgUtils.h" +#include "nsThreadUtils.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "nsINntpUrl.h" +#include "nsSandboxFlags.h" + +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. +// 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, + nsIMsgContentPolicy, + nsIObserver, + nsISupportsWeakReference) + +nsMsgContentPolicy::nsMsgContentPolicy() +{ + mAllowPlugins = false; + mBlockRemoteImages = true; +} + +nsMsgContentPolicy::~nsMsgContentPolicy() +{ + // hey, we are going away...clean up after ourself....unregister our observer + nsresult rv; + nsCOMPtr<nsIPrefBranch> prefInternal = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + if (NS_SUCCEEDED(rv)) + { + prefInternal->RemoveObserver(kBlockRemoteImages, this); + prefInternal->RemoveObserver(kAllowPlugins, this); + } +} + +nsresult nsMsgContentPolicy::Init() +{ + nsresult rv; + + // register ourself as an observer on the mail preference to block remote images + nsCOMPtr<nsIPrefBranch> prefInternal = do_GetService(NS_PREFSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + prefInternal->AddObserver(kBlockRemoteImages, this, true); + prefInternal->AddObserver(kAllowPlugins, this, true); + + prefInternal->GetBoolPref(kAllowPlugins, &mAllowPlugins); + prefInternal->GetCharPref(kTrustedDomains, getter_Copies(mTrustedMailDomains)); + prefInternal->GetBoolPref(kBlockRemoteImages, &mBlockRemoteImages); + + // 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; + 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("chrome://messenger/content/email=", 0); + rv = ios->NewURI(emailAddress, nullptr, nullptr, getter_AddRefs(mailURI)); + NS_ENSURE_SUCCESS(rv, false); + + // check with permission manager + uint32_t permission = 0; + rv = mPermissionManager->TestPermission(mailURI, "image", &permission); + NS_ENSURE_SUCCESS(rv, false); + + // Only return true if the permission manager has an explicit allow + return (permission == nsIPermissionManager::ALLOW_ACTION); +} + +/** + * Extract the host name from aContentLocation, and look it up in our list + * of trusted domains. + */ +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()) + trustedDomain = MsgHostDomainIsTrusted(host, mTrustedMailDomains); + + return trustedDomain; +} + +NS_IMETHODIMP +nsMsgContentPolicy::ShouldLoad(uint32_t aContentType, + nsIURI *aContentLocation, + nsIURI *aRequestingLocation, + nsISupports *aRequestingContext, + const nsACString &aMimeGuess, + nsISupports *aExtra, + nsIPrincipal *aRequestPrincipal, + int16_t *aDecision) +{ + nsresult rv = NS_OK; + // The default decision at the start of the function is to accept the load. + // Once we have checked the content type and the requesting location, then + // we switch it to reject. + // + // Be very careful about returning error codes - if this method returns an + // NS_ERROR_*, any decision made here will be ignored, and the document could + // be accepted when we don't want it to be. + // + // In most cases if an error occurs, its something we didn't expect so we + // should be rejecting the document anyway. + *aDecision = nsIContentPolicy::ACCEPT; + + NS_ENSURE_ARG_POINTER(aContentLocation); + +#ifdef DEBUG_MsgContentPolicy + fprintf(stderr, "aContentType: %d\naContentLocation = %s\n", + aContentType, + aContentLocation->GetSpecOrDefault().get()); +#endif + +#ifndef MOZ_THUNDERBIRD + // Go find out if we are dealing with mailnews. Anything else + // isn't our concern and we accept content. + nsCOMPtr<nsIDocShell> rootDocShell; + rv = GetRootDocShellForContext(aRequestingContext, + getter_AddRefs(rootDocShell)); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t appType; + rv = rootDocShell->GetAppType(&appType); + // We only want to deal with mailnews + if (NS_FAILED(rv) || appType != nsIDocShell::APP_TYPE_MAIL) + 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. + // 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 "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; + } + break; + + case nsIContentPolicy::TYPE_CSP_REPORT: + // 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 + fprintf(stderr, "aRequestingLocation = %s\n", aRequestingLocation->GetSpecOrDefault().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 + // cause content to be rejected. + *aDecision = nsIContentPolicy::REJECT_REQUEST; + + // We want to establish the following: + // \--------\ requester | | | + // content \------------\ | | | + // requested \| mail message | news message | http(s)/data etc. + // -------------------------+---------------+--------------+------------------ + // mail message content | load if same | don't load | don't load + // mailbox, imap, JsAccount | message (1) | (2) | (3) + // -------------------------+---------------+--------------+------------------ + // news message | don't load (4)| load (5) | load (6) + // -------------------------+---------------+--------------+------------------ + // http(s)/data, etc. | (default) | (default) | (default) + // -------------------------+---------------+--------------+------------------ + nsCOMPtr<nsIMsgMessageUrl> contentURL(do_QueryInterface(aContentLocation)); + if (contentURL) { + nsCOMPtr<nsINntpUrl> contentNntpURL(do_QueryInterface(aContentLocation)); + if (!contentNntpURL) { + // Mail message (mailbox, imap or JsAccount) content requested, for example + // a message part, like an image: + // To load mail message content the requester must have the same + // "normalised" principal. This is basically a "same origin" test, it + // protects against cross-loading of mail message content from + // other mail or news messages. + nsCOMPtr<nsIMsgMessageUrl> requestURL(do_QueryInterface(aRequestingLocation)); + // If the request URL is not also a message URL, then we don't accept. + if (requestURL) { + nsCString contentPrincipalSpec, requestPrincipalSpec; + nsresult rv1 = contentURL->GetPrincipalSpec(contentPrincipalSpec); + nsresult rv2 = requestURL->GetPrincipalSpec(requestPrincipalSpec); + if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2) && + contentPrincipalSpec.Equals(requestPrincipalSpec)) + *aDecision = nsIContentPolicy::ACCEPT; // (1) + } + return NS_OK; // (2) and (3) + } + + // News message content requested. Don't accept request coming + // from a mail message since it would access the news server. + nsCOMPtr<nsIMsgMessageUrl> requestURL(do_QueryInterface(aRequestingLocation)); + if (requestURL) { + nsCOMPtr<nsINntpUrl> requestNntpURL(do_QueryInterface(aRequestingLocation)); + if (!requestNntpURL) + return NS_OK; // (4) + } + *aDecision = nsIContentPolicy::ACCEPT; // (5) and (6) + return NS_OK; + } + + // If exposed protocol not covered by the test above or protocol that has been + // specifically exposed by an add-on, 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. + // Protocols like ftp are always blocked. + if (ShouldBlockUnexposedProtocol(aContentLocation)) + 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)); + } + else + { + rv = GetOriginatingURIForContext(aRequestingContext, + getter_AddRefs(originatorLocation)); + } + 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; + return NS_OK; + } + + uint32_t permission; + mPermissionManager->TestPermission(aContentLocation, "image", &permission); + switch (permission) { + case nsIPermissionManager::UNKNOWN_ACTION: + { + // No exception was found for this location. + break; + } + case nsIPermissionManager::ALLOW_ACTION: + { + *aDecision = nsIContentPolicy::ACCEPT; + return NS_OK; + } + case nsIPermissionManager::DENY_ACTION: + { + *aDecision = nsIContentPolicy::REJECT_REQUEST; + return NS_OK; + } + } + + // The default decision is still to reject. + ShouldAcceptContentForPotentialMsg(originatorLocation, aContentLocation, + aDecision); + return NS_OK; +} + +/** + * Determines if the requesting location is a safe one, i.e. its under the + * app/user's control - so file, about, chrome etc. + */ +bool +nsMsgContentPolicy::IsSafeRequestingLocation(nsIURI *aRequestingLocation) +{ + if (!aRequestingLocation) + return false; + + // If aRequestingLocation is one of chrome, resource, file or view-source, + // allow aContentLocation to load. + bool isChrome; + bool isRes; + bool isFile; + bool isViewSource; + + nsresult rv = aRequestingLocation->SchemeIs("chrome", &isChrome); + NS_ENSURE_SUCCESS(rv, false); + rv = aRequestingLocation->SchemeIs("resource", &isRes); + NS_ENSURE_SUCCESS(rv, false); + rv = aRequestingLocation->SchemeIs("file", &isFile); + NS_ENSURE_SUCCESS(rv, false); + rv = aRequestingLocation->SchemeIs("view-source", &isViewSource); + NS_ENSURE_SUCCESS(rv, false); + + if (isChrome || isRes || isFile || isViewSource) + return true; + + // Only allow about: to load anything if the requesting location is not the + // special about:blank one. + bool isAbout; + rv = aRequestingLocation->SchemeIs("about", &isAbout); + NS_ENSURE_SUCCESS(rv, false); + + if (!isAbout) + return false; + + nsCString fullSpec; + rv = aRequestingLocation->GetSpec(fullSpec); + NS_ENSURE_SUCCESS(rv, false); + + return !fullSpec.EqualsLiteral("about:blank"); +} + +/** + * Determines if the content location is a scheme that we're willing to expose + * for unlimited loading of content. + */ +bool +nsMsgContentPolicy::IsExposedProtocol(nsIURI *aContentLocation) +{ + nsAutoCString contentScheme; + nsresult rv = aContentLocation->GetScheme(contentScheme); + NS_ENSURE_SUCCESS(rv, false); + + // Check some exposed protocols. Not all protocols in the list of + // network.protocol-handler.expose.* prefs in all-thunderbird.js are + // admitted purely based on their scheme. + // news, snews, nntp, imap and mailbox are checked before the call + // to this function by matching content location and requesting location. + if (MsgLowerCaseEqualsLiteral(contentScheme, "mailto") || + MsgLowerCaseEqualsLiteral(contentScheme, "addbook") || + 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); + NS_ENSURE_SUCCESS(rv, false); + + return isChrome || isRes || isData; +} + +/** + * We block most unexposed protocols - apart from http(s) and file. + */ +bool +nsMsgContentPolicy::ShouldBlockUnexposedProtocol(nsIURI *aContentLocation) +{ + bool isHttp; + bool isHttps; + bool isFile; + // Error condition - we must return true so that we block. + nsresult rv = aContentLocation->SchemeIs("http", &isHttp); + NS_ENSURE_SUCCESS(rv, true); + rv = aContentLocation->SchemeIs("https", &isHttps); + NS_ENSURE_SUCCESS(rv, true); + rv = aContentLocation->SchemeIs("file", &isFile); + NS_ENSURE_SUCCESS(rv, true); + + return !isHttp && !isHttps && !isFile; +} + +/** + * The default for this function will be to reject the content request. + * When determining if to allow the request for a given msg hdr, the function + * will go through the list of remote content blocking criteria: + * + * #1 Allow if there is a db header for a manual override. + * #2 Allow if the message is in an RSS folder. + * #3 Allow if the domain for the remote image in our white list. + * #4 Allow if the author has been specifically white listed. + */ +int16_t +nsMsgContentPolicy::ShouldAcceptRemoteContentForMsgHdr(nsIMsgDBHdr *aMsgHdr, + nsIURI *aRequestingLocation, + nsIURI *aContentLocation) +{ + if (!aMsgHdr) + return static_cast<int16_t>(nsIContentPolicy::REJECT_REQUEST); + + // Case #1, check the db hdr for the remote content policy on this particular + // message. + uint32_t remoteContentPolicy = kNoRemoteContentPolicy; + aMsgHdr->GetUint32Property("remoteContentPolicy", &remoteContentPolicy); + + // Case #2, check if the message is in an RSS folder + bool isRSS = false; + IsRSSArticle(aRequestingLocation, &isRSS); + + // Case #3, the domain for the remote image is in our white list + bool trustedDomain = IsTrustedDomain(aContentLocation); + + // Case 4 means looking up items in the permissions database. So if + // either of the two previous items means we load the data, just do it. + if (isRSS || remoteContentPolicy == kAllowRemoteContent || trustedDomain) + return nsIContentPolicy::ACCEPT; + + // Case #4, author is in our white list.. + bool allowForSender = ShouldAcceptRemoteContentForSender(aMsgHdr); + + 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 mozilla::Runnable +{ +public: + RemoteContentNotifierEvent(nsIMsgWindow *aMsgWindow, nsIMsgDBHdr *aMsgHdr, + nsIURI *aContentURI, bool aCanOverride = true) + : 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, 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 +nsMsgContentPolicy::ShouldAcceptContentForPotentialMsg(nsIURI *aOriginatorLocation, + nsIURI *aContentLocation, + int16_t *aDecision) +{ + NS_PRECONDITION(*aDecision == nsIContentPolicy::REJECT_REQUEST, + "AllowContentForPotentialMessage expects default decision to be reject!"); + + // Is it a mailnews url? + nsresult rv; + nsCOMPtr<nsIMsgMessageUrl> msgUrl(do_QueryInterface(aOriginatorLocation, + &rv)); + if (NS_FAILED(rv)) + { + // It isn't a mailnews url - so we accept the load here, and let other + // content policies make the decision if we should be loading it or not. + *aDecision = nsIContentPolicy::ACCEPT; + 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)); + } + } + + // Get a decision on whether or not to allow remote content for this message + // header. + *aDecision = ShouldAcceptRemoteContentForMsgHdr(msgHdr, aOriginatorLocation, + 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)); + 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!"); + + nsCString originalMsgURI; + nsresult rv = aMsgCompose->GetOriginalMsgURI(getter_Copies(originalMsgURI)); + NS_ENSURE_SUCCESS_VOID(rv); + + MSG_ComposeType composeType; + rv = aMsgCompose->GetType(&composeType); + NS_ENSURE_SUCCESS_VOID(rv); + + // Only allow remote content for new mail compositions or mailto + // Block remote content for all other types (drafts, templates, forwards, replies, etc) + // unless there is an associated msgHdr which allows the load, or unless the image is being + // added by the user and not the quoted message content... + if (composeType == nsIMsgCompType::New || + composeType == nsIMsgCompType::MailToUrl) + *aDecision = nsIContentPolicy::ACCEPT; + else if (!originalMsgURI.IsEmpty()) + { + nsCOMPtr<nsIMsgDBHdr> msgHdr; + rv = GetMsgDBHdrFromURI(originalMsgURI.get(), getter_AddRefs(msgHdr)); + NS_ENSURE_SUCCESS_VOID(rv); + *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. Hence we block them + // while the reply is created (insertingQuotedContent==true), but allow them + // later when the user inserts them. + if (*aDecision == nsIContentPolicy::REJECT_REQUEST) + { + bool insertingQuotedContent = true; + aMsgCompose->GetInsertingQuotedContent(&insertingQuotedContent); + nsCOMPtr<nsIDOMHTMLImageElement> imageElement(do_QueryInterface(aRequestingContext)); + if (imageElement) + { + if (!insertingQuotedContent) + { + *aDecision = nsIContentPolicy::ACCEPT; + return; + } + + // Test whitelist. + uint32_t permission; + mPermissionManager->TestPermission(aContentLocation, "image", &permission); + if (permission == nsIPermissionManager::ALLOW_ACTION) + *aDecision = nsIContentPolicy::ACCEPT; + } + } + } +} + +already_AddRefed<nsIMsgCompose> nsMsgContentPolicy::GetMsgComposeForContext(nsISupports *aRequestingContext) +{ + nsresult rv; + + nsIDocShell *shell = NS_CP_GetDocShellFromContext(aRequestingContext); + nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(do_QueryInterface(shell, &rv)); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr<nsIDocShellTreeItem> rootItem; + rv = docShellTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(rootItem)); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(rootItem, &rv)); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr<nsIMsgComposeService> composeService(do_GetService(NS_MSGCOMPOSESERVICE_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, nullptr); + + nsCOMPtr<nsIMsgCompose> msgCompose; + // Don't bother checking rv, as GetMsgComposeForDocShell returns NS_ERROR_FAILURE + // for not found. + composeService->GetMsgComposeForDocShell(docShell, + getter_AddRefs(msgCompose)); + return msgCompose.forget(); +} + +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 + // 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); + if (NS_FAILED(rv) && !isAllowedContent) { + // If it's not a mailnews url or allowed content url (http[s]|file) then + // bail; otherwise set whether js and plugins are allowed. + return NS_OK; + } + + // since NS_CP_GetDocShellFromContext returns the containing docshell rather + // than the contained one we need, we can't use that here, so... + nsCOMPtr<nsIFrameLoaderOwner> flOwner = do_QueryInterface(aRequestingContext, + &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFrameLoader> frameLoader; + rv = flOwner->GetFrameLoaderXPCOM(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? + int32_t itemType; + rv = docshellTreeItem->GetItemType(&itemType); + NS_ENSURE_SUCCESS(rv, rv); + + // we're only worried about policy settings in content docshells + if (itemType != nsIDocShellTreeItem::typeContent) { + return NS_OK; + } + + if (!isAllowedContent) { + // Disable JavaScript on message URLs. + rv = docShell->SetAllowJavascript(false); + NS_ENSURE_SUCCESS(rv, rv); + rv = docShell->SetAllowContentRetargetingOnChildren(false); + NS_ENSURE_SUCCESS(rv, rv); + rv = docShell->SetAllowPlugins(mAllowPlugins); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t sandboxFlags; + rv = docShell->GetSandboxFlags(&sandboxFlags); + sandboxFlags |= SANDBOXED_FORMS; + NS_ENSURE_SUCCESS(rv, rv); + rv = docShell->SetSandboxFlags(sandboxFlags); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + // JavaScript and plugins are allowed on non-message URLs. + rv = docShell->SetAllowJavascript(true); + NS_ENSURE_SUCCESS(rv, rv); + rv = docShell->SetAllowContentRetargetingOnChildren(true); + NS_ENSURE_SUCCESS(rv, rv); + rv = docShell->SetAllowPlugins(true); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +/** + * Gets the root docshell from a requesting context. + */ +nsresult +nsMsgContentPolicy::GetRootDocShellForContext(nsISupports *aRequestingContext, + nsIDocShell **aDocShell) +{ + NS_ENSURE_ARG_POINTER(aRequestingContext); + nsresult rv; + + nsIDocShell *shell = NS_CP_GetDocShellFromContext(aRequestingContext); + nsCOMPtr<nsIDocShellTreeItem> docshellTreeItem(do_QueryInterface(shell, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocShellTreeItem> rootItem; + rv = docshellTreeItem->GetRootTreeItem(getter_AddRefs(rootItem)); + NS_ENSURE_SUCCESS(rv, rv); + + return CallQueryInterface(rootItem, aDocShell); +} + +/** + * 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); + nsCOMPtr<nsIDocShellTreeItem> docshellTreeItem(do_QueryInterface(shell, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocShellTreeItem> rootItem; + rv = docshellTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(rootItem)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIWebNavigation> webNavigation(do_QueryInterface(rootItem, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + return webNavigation->GetCurrentURI(aURI); +} + +NS_IMETHODIMP +nsMsgContentPolicy::ShouldProcess(uint32_t aContentType, + nsIURI *aContentLocation, + nsIURI *aRequestingLocation, + 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 + // 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)) + { + NS_LossyConvertUTF16toASCII pref(aData); + + nsresult rv; + + nsCOMPtr<nsIPrefBranch> prefBranchInt = do_QueryInterface(aSubject, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + if (pref.Equals(kBlockRemoteImages)) + 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 +nsMsgContentPolicy::OnStateChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, uint32_t aStateFlags, + nsresult aStatus) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsMsgContentPolicy::OnProgressChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + return NS_OK; +} + +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 + // 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 + // 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. + rv = docShell->SetAllowPlugins(mAllowPlugins); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Failed to set plugins disabled on docShell"); + } + else { + // Disable javascript and plugins are allowed on non-message URLs. + rv = docShell->SetAllowJavascript(true); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Failed to set javascript allowed on docShell"); + rv = docShell->SetAllowPlugins(true); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Failed to set plugins allowed on docShell"); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsMsgContentPolicy::OnStatusChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, nsresult aStatus, + const char16_t *aMessage) +{ + return NS_OK; +} + +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; +} + |