Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "nsIMsgMailSession.h"
#include "nsIPermissionManager.h"
#include "nsIPrefService.h"
#include "nsIPrefBranch.h"
#include "nsIAbManager.h"
#include "nsIAbDirectory.h"
#include "nsIAbCard.h"
#include "nsIMsgWindow.h"
#include "nsIMsgHdr.h"
#include "nsIEncryptedSMIMEURIsSrvc.h"
#include "nsNetUtil.h"
#include "nsIMsgComposeService.h"
#include "nsIDocShellTreeItem.h"
#include "nsIWebNavigation.h"
#include "nsContentPolicyUtils.h"
#include "nsFrameLoaderOwner.h"
#include "nsFrameLoader.h"
#include "nsMsgUtils.h"
#include "nsThreadUtils.h"
#include "mozilla/mailnews/MimeHeaderParser.h"
#include "mozilla/dom/HTMLImageElement.h"
#include "nsINntpUrl.h"
#include "nsILoadInfo.h"
#include "nsSandboxFlags.h"
#include "nsQueryObject.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/SyncRunnable.h"
#include "nsIObserverService.h"
static const char kBlockRemoteImages[] =
"mailnews.message_display.disable_remote_image";
static const char kTrustedDomains[] = "mail.trusteddomains";
using namespace mozilla;
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, nsIMsgContentPolicy,
nsIObserver, nsISupportsWeakReference)
nsMsgContentPolicy::nsMsgContentPolicy() { 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);
}
}
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->GetCharPref(kTrustedDomains, 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.InsertLiteral("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;
mozilla::OriginAttributes attrs;
RefPtr<mozilla::BasePrincipal> principal =
mozilla::BasePrincipal::CreateContentPrincipal(mailURI, attrs);
rv = mPermissionManager->TestPermissionFromPrincipal(principal, "image"_ns,
&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(nsIURI* aContentLocation, nsILoadInfo* aLoadInfo,
int16_t* aDecision) {
nsresult rv = NS_OK;
ExtContentPolicyType aContentType = aLoadInfo->GetExternalContentPolicyType();
nsCOMPtr<nsISupports> aRequestingContext;
if (aContentType == ExtContentPolicy::TYPE_DOCUMENT)
aRequestingContext = aLoadInfo->ContextForTopLevelLoad();
else
aRequestingContext = aLoadInfo->LoadingNode();
nsCOMPtr<nsIPrincipal> loadingPrincipal = aLoadInfo->GetLoadingPrincipal();
nsCOMPtr<nsIURI> aRequestingLocation;
if (loadingPrincipal) {
BasePrincipal::Cast(loadingPrincipal)
->GetURI(getter_AddRefs(aRequestingLocation));
}
// 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());
fprintf(stderr, "aRequestingContext is %s\n",
aRequestingContext ? "not null" : "null");
#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);
// We only want to deal with mailnews
if (rootDocShell->GetAppType() != nsIDocShell::APP_TYPE_MAIL) return NS_OK;
#endif
switch (aContentType) {
// Plugins (nsIContentPolicy::TYPE_OBJECT) are blocked on document load.
case ExtContentPolicy::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 (NS_IsMainThread()) {
rv = SetDisableItemsOnMailNewsUrlDocshells(aContentLocation, aLoadInfo);
} else {
auto SetDisabling = [&, location = nsCOMPtr(aContentLocation),
loadInfo = nsCOMPtr(aLoadInfo)]() -> auto {
rv = SetDisableItemsOnMailNewsUrlDocshells(location, loadInfo);
};
nsCOMPtr<nsIRunnable> task =
NS_NewRunnableFunction("SetDisabling", SetDisabling);
mozilla::SyncRunnable::DispatchToThread(
mozilla::GetMainThreadSerialEventTarget(), task);
}
// 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 ExtContentPolicy::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 "normalized" 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->GetNormalizedSpec(contentPrincipalSpec);
nsresult rv2 = requestURL->GetNormalizedSpec(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 web protocols and file.
// Protocols like ftp are always blocked.
if (ShouldBlockUnexposedProtocol(aContentLocation)) return NS_OK;
// Mailnews URIs are not loaded in child processes, so I think that beyond
// here, if we're in a child process, the decision will always be accept.
//
// targetContext->Canonical does not work in a child process, so we can't
// really move on anyway.
if (!XRE_IsParentProcess()) {
*aDecision = nsIContentPolicy::ACCEPT;
return NS_OK;
}
// Find out the URI that originally initiated the set of requests for this
// context.
RefPtr<mozilla::dom::BrowsingContext> targetContext;
rv = aLoadInfo->GetTargetBrowsingContext(getter_AddRefs(targetContext));
NS_ENSURE_SUCCESS(rv, NS_OK);
if (!targetContext) {
*aDecision = nsIContentPolicy::ACCEPT;
return NS_OK;
}
nsCOMPtr<nsIURI> originatorLocation;
dom::CanonicalBrowsingContext* cbc = targetContext->Canonical();
if (cbc) {
originatorLocation = cbc->GetCurrentURI();
}
if (!originatorLocation) {
// We end up here for the load of an iframe created by a script (at least).
// TODO: are there cases where this should block?
*aDecision = nsIContentPolicy::ACCEPT;
return 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(targetContext->Id(), aContentLocation);
return NS_OK;
}
// If we are allowing all remote content...
if (!mBlockRemoteImages) {
*aDecision = nsIContentPolicy::ACCEPT;
return NS_OK;
}
uint32_t permission;
mozilla::OriginAttributes attrs;
RefPtr<mozilla::BasePrincipal> principal =
mozilla::BasePrincipal::CreateContentPrincipal(aContentLocation, attrs);
mPermissionManager->TestPermissionFromPrincipal(principal, "image"_ns,
&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;
}
}
// Handle compose windows separately from mail. Work out if we're in a compose
// window or not.
nsCOMPtr<nsIMsgCompose> msgCompose =
GetMsgComposeForBrowsingContext(targetContext);
if (msgCompose) {
ComposeShouldLoad(msgCompose, aRequestingContext, originatorLocation,
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;
}
// The default decision is still to reject.
ShouldAcceptContentForPotentialMsg(targetContext->Id(), aRequestingLocation,
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 (contentScheme.LowerCaseEqualsLiteral("mailto")) return true;
if (contentScheme.LowerCaseEqualsLiteral("about")) {
// We want to allow about pages to load content freely. But not about:blank.
nsAutoCString fullSpec;
rv = aContentLocation->GetSpec(fullSpec);
NS_ENSURE_SUCCESS(rv, false);
if (fullSpec.EqualsLiteral("about:blank")) {
return false;
}
return true;
}
// check if customized exposed scheme
if (mCustomExposedProtocols.Contains(contentScheme)) return true;
bool isChrome;
rv = aContentLocation->SchemeIs("chrome", &isChrome);
NS_ENSURE_SUCCESS(rv, false);
bool isRes;
rv = aContentLocation->SchemeIs("resource", &isRes);
NS_ENSURE_SUCCESS(rv, false);
bool isData;
rv = aContentLocation->SchemeIs("data", &isData);
NS_ENSURE_SUCCESS(rv, false);
bool isMozExtension;
rv = aContentLocation->SchemeIs("moz-extension", &isMozExtension);
NS_ENSURE_SUCCESS(rv, false);
return isChrome || isRes || isData || isMozExtension;
}
/**
* We block most unexposed protocols that access remote data
* - apart from web protocols, and file.
*/
bool nsMsgContentPolicy::ShouldBlockUnexposedProtocol(
nsIURI* aContentLocation) {
// Error condition - we must return true so that we block.
// about:blank is "web", it should not be blocked.
nsAutoCString fullSpec;
nsresult rv = aContentLocation->GetSpec(fullSpec);
NS_ENSURE_SUCCESS(rv, true);
if (fullSpec.EqualsLiteral("about:blank")) {
return false;
}
bool isHttp;
rv = aContentLocation->SchemeIs("http", &isHttp);
NS_ENSURE_SUCCESS(rv, true);
bool isHttps;
rv = aContentLocation->SchemeIs("https", &isHttps);
NS_ENSURE_SUCCESS(rv, true);
bool isWs; // websocket
rv = aContentLocation->SchemeIs("ws", &isWs);
NS_ENSURE_SUCCESS(rv, true);
bool isWss; // secure websocket
rv = aContentLocation->SchemeIs("wss", &isWss);
NS_ENSURE_SUCCESS(rv, true);
bool isBlob;
rv = aContentLocation->SchemeIs("blob", &isBlob);
NS_ENSURE_SUCCESS(rv, true);
bool isFile;
rv = aContentLocation->SchemeIs("file", &isFile);
NS_ENSURE_SUCCESS(rv, true);
return !isHttp && !isHttps && !isWs && !isWss && !isBlob && !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(uint64_t aBrowsingContextId, nsIURI* aContentURI)
: mozilla::Runnable("RemoteContentNotifierEvent"),
mBrowsingContextId(aBrowsingContextId),
mContentURI(aContentURI) {}
NS_IMETHOD Run() {
nsAutoString data;
data.AppendInt(mBrowsingContextId);
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
observerService->NotifyObservers(mContentURI, "remote-content-blocked",
data.get());
return NS_OK;
}
private:
uint64_t mBrowsingContextId;
nsCOMPtr<nsIURI> mContentURI;
};
/**
* This function is used to show a blocked remote content notification.
*/
void nsMsgContentPolicy::NotifyContentWasBlocked(uint64_t aBrowsingContextId,
nsIURI* 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.
NS_DispatchToCurrentThread(
new RemoteContentNotifierEvent(aBrowsingContextId, aContentLocation));
}
/**
* 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(
uint64_t aBrowsingContextId, nsIURI* aRequestingLocation,
nsIURI* aContentLocation, int16_t* aDecision) {
NS_ASSERTION(
*aDecision == nsIContentPolicy::REJECT_REQUEST,
"AllowContentForPotentialMessage expects default decision to be reject!");
// Is it a mailnews url?
nsresult rv;
nsCOMPtr<nsIMsgMessageUrl> msgUrl(
do_QueryInterface(aRequestingLocation, &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(resourceURI);
NS_ENSURE_SUCCESS_VOID(rv);
nsCOMPtr<nsIMsgDBHdr> msgHdr;
rv = GetMsgDBHdrFromURI(resourceURI, getter_AddRefs(msgHdr));
// Get a decision on whether or not to allow remote content for this message
// header.
*aDecision = ShouldAcceptRemoteContentForMsgHdr(msgHdr, aRequestingLocation,
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) {
NotifyContentWasBlocked(aBrowsingContextId, aContentLocation);
}
}
/**
* Content policy logic for compose windows
*/
void nsMsgContentPolicy::ComposeShouldLoad(nsIMsgCompose* aMsgCompose,
nsISupports* aRequestingContext,
nsIURI* aOriginatorLocation,
nsIURI* aContentLocation,
int16_t* aDecision) {
NS_ASSERTION(*aDecision == nsIContentPolicy::REJECT_REQUEST,
"ComposeShouldLoad expects default decision to be reject!");
nsCString originalMsgURI;
nsresult rv = aMsgCompose->GetOriginalMsgURI(originalMsgURI);
NS_ENSURE_SUCCESS_VOID(rv);
if (!originalMsgURI.IsEmpty()) {
nsCOMPtr<nsIMsgDBHdr> msgHdr;
rv = GetMsgDBHdrFromURI(originalMsgURI, getter_AddRefs(msgHdr));
NS_ENSURE_SUCCESS_VOID(rv);
*aDecision =
ShouldAcceptRemoteContentForMsgHdr(msgHdr, nullptr, aContentLocation);
if (!aOriginatorLocation->GetSpecOrDefault().EqualsLiteral(
"about:blank?compose")) {
return;
}
}
// We want to allow the user to add remote content, but do that only when
// the allowRemoteContent was set. This way quoted remoted content won't
// automatically load, but e.g. pasted content will load because the UI
// code toggles the flag.
nsCOMPtr<mozilla::dom::Element> element =
do_QueryInterface(aRequestingContext);
RefPtr<mozilla::dom::HTMLImageElement> image =
mozilla::dom::HTMLImageElement::FromNodeOrNull(element);
if (image) {
// Special case image elements.
bool allowRemoteContent = false;
aMsgCompose->GetAllowRemoteContent(&allowRemoteContent);
if (allowRemoteContent) {
*aDecision = nsIContentPolicy::ACCEPT;
return;
}
}
}
already_AddRefed<nsIMsgCompose>
nsMsgContentPolicy::GetMsgComposeForBrowsingContext(
mozilla::dom::BrowsingContext* aBrowsingContext) {
nsresult rv;
nsIDocShell* shell = aBrowsingContext->GetDocShell();
if (!shell) return nullptr;
nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(shell);
nsCOMPtr<nsIDocShellTreeItem> rootItem;
rv = docShellTreeItem->GetInProcessSameTypeRootTreeItem(
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("@mozilla.org/messengercompose;1", &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, nsILoadInfo* aLoadInfo) {
// 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.
NS_ENSURE_ARG_POINTER(aContentLocation);
NS_ENSURE_ARG_POINTER(aLoadInfo);
RefPtr<mozilla::dom::BrowsingContext> browsingContext =
aLoadInfo->GetTargetBrowsingContext();
if (!browsingContext) {
return NS_OK;
}
// We're only worried about policy settings in content docshells.
if (!browsingContext->IsContent()) {
return NS_OK;
}
nsCOMPtr<nsIDocShell> docShell = browsingContext->GetDocShell();
if (!docShell) {
// 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.
return NS_OK;
}
// Ensure starting off unsandboxed. We sandbox later if needed.
MOZ_ALWAYS_SUCCEEDS(browsingContext->SetSandboxFlags(SANDBOXED_NONE));
nsresult rv;
bool isAllowedContent = !ShouldBlockUnexposedProtocol(aContentLocation);
nsCOMPtr<nsIMsgMessageUrl> msgUrl = do_QueryInterface(aContentLocation);
if (!msgUrl && !isAllowedContent) {
// If it's not a mailnews url or allowed content url (http[s]|file) then
// bail; otherwise set whether JavaScript is allowed.
return NS_OK;
}
if (!isAllowedContent) {
// Disable JavaScript on message URLs.
rv = browsingContext->SetAllowJavascript(false);
NS_ENSURE_SUCCESS(rv, rv);
rv = browsingContext->SetAllowContentRetargetingOnChildren(false);
NS_ENSURE_SUCCESS(rv, rv);
// NOTE! Do not set single sandboxing flags only. Sandboxing - when used -
// starts off with all things sandboxed, and individual sandbox keywords
// will *allow* the specific feature.
// Disabling by setting single flags without starting off with all things
// sandboxed would the normal assumptions about sandboxing.
// The flags - contrary to the keywords - *prevent* a given feature.
uint32_t sandboxFlags = SANDBOX_ALL_FLAGS;
// Do not block links with target attribute from opening (at all).
// xref bug 421310 - we would like to prevent using target, but *handle*
// links like the target wasn't there.
sandboxFlags &= ~SANDBOXED_AUXILIARY_NAVIGATION;
// For some unexplicable reason, when SANDBOXED_ORIGIN is in affect, then
// images will not work with test --verify. So unset it.
sandboxFlags &= ~SANDBOXED_ORIGIN;
// Having both SANDBOXED_TOPLEVEL_NAVIGATION and
// SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION will generate a warning,
// see BothAllowTopNavigationAndUserActivationPresent. So unset it.
sandboxFlags &= ~SANDBOXED_TOPLEVEL_NAVIGATION_USER_ACTIVATION;
rv = browsingContext->SetSandboxFlags(sandboxFlags);
NS_ENSURE_SUCCESS(rv, rv);
} else {
// JavaScript is allowed on non-message URLs.
rv = browsingContext->SetAllowJavascript(true);
NS_ENSURE_SUCCESS(rv, rv);
rv = browsingContext->SetAllowContentRetargetingOnChildren(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);
NS_ENSURE_TRUE(shell, NS_ERROR_NULL_POINTER);
nsCOMPtr<nsIDocShellTreeItem> docshellTreeItem(shell);
nsCOMPtr<nsIDocShellTreeItem> rootItem;
rv = docshellTreeItem->GetInProcessRootTreeItem(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);
if (!shell) {
*aURI = nullptr;
return NS_OK;
}
nsCOMPtr<nsIDocShellTreeItem> docshellTreeItem(shell);
nsCOMPtr<nsIDocShellTreeItem> rootItem;
rv = docshellTreeItem->GetInProcessSameTypeRootTreeItem(
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(nsIURI* aContentLocation,
nsILoadInfo* aLoadInfo, 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);
}
return NS_OK;
}
/**
* Implementation of nsIMsgContentPolicy
*
*/
NS_IMETHODIMP
nsMsgContentPolicy::AddExposedProtocol(const nsACString& aScheme) {
if (mCustomExposedProtocols.Contains(aScheme)) return NS_OK;
mCustomExposedProtocols.AppendElement(aScheme);
return NS_OK;
}
NS_IMETHODIMP
nsMsgContentPolicy::RemoveExposedProtocol(const nsACString& aScheme) {
mCustomExposedProtocols.RemoveElement(aScheme);
return NS_OK;
}