Source code

Revision control

Copy as Markdown

Other Tools

/* 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 "nsIPrefBranch.h"
#include "nsIMsgHdr.h"
#include "nsIEncryptedMsgURIsService.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 "nsILoadInfo.h"
#include "nsSandboxFlags.h"
#include "mozilla/Components.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/Preferences.h"
#include "mozilla/SyncRunnable.h"
#include "nsIObserverService.h"
using namespace mozilla;
using namespace mozilla::mailnews;
static const char kBlockRemoteImages[] =
"mailnews.message_display.disable_remote_image";
static const char kTrustedDomains[] = "mail.trusteddomains";
// Per message header 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
Preferences::RemoveObserver(this, kBlockRemoteImages);
}
nsresult nsMsgContentPolicy::Init() {
// register ourself as an observer on the mail preference to block remote
// images
Preferences::AddWeakObserver(this, kBlockRemoteImages);
Preferences::GetCString(kTrustedDomains, mTrustedMailDomains);
Preferences::GetBool(kBlockRemoteImages, &mBlockRemoteImages);
// Grab a handle on the PermissionManager service for managing allowed remote
// content senders.
mPermissionManager = mozilla::components::PermissionManager::Service();
return NS_OK;
}
/**
* Extracts the sender's email from aMsgHdr and checks the "image" permission
* for the corresponding chrome://messenger/content/email=… URI.
*
* @returns true if the stored permission matches aPermission.
*/
bool nsMsgContentPolicy::CheckRemoteContentPermissionForSender(
nsIMsgDBHdr* aMsgHdr, uint32_t aPermission) {
if (!aMsgHdr) return false;
// extract the e-mail address from the msg hdr
nsCString author;
nsresult rv = aMsgHdr->GetAuthor(author);
NS_ENSURE_SUCCESS(rv, false);
nsCString emailAddress;
ExtractEmail(EncodedHeader(author), emailAddress);
if (emailAddress.IsEmpty()) return false;
nsCOMPtr<nsIIOService> ios = mozilla::components::IO::Service();
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);
return (permission == aPermission);
}
/**
* 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);
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.
// Assert document nsIMsgMailNewsUrls are always loaded in the parent
// process.
nsCOMPtr<nsIMsgMailNewsUrl> msgURL(do_QueryInterface(aContentLocation));
if (msgURL) {
MOZ_RELEASE_ASSERT(
XRE_IsParentProcess(),
"nsIMsgMailNewsUrls need to be loaded in the parent process");
}
if (!XRE_IsParentProcess()) {
// For content process documents, do nothing.
// Notably clicking javascript: links end up run in the content process
// which would crash the process since
// SetDisableItemsOnMailNewsUrlDocshells calls SetAllowJavascript that
// is only allowed in the parent process.
} else {
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;
// If the requesting location is safe, accept the content location request.
if (IsSafeRequestingLocation(aRequestingLocation)) return rv;
auto acceptContent = [&]() {
*aDecision = nsIContentPolicy::ACCEPT;
return NS_OK;
};
// 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 if same | don't load
// | | server (5) | (6)
// -------------------------+---------------+--------------+------------------
// http(s)/data, etc. | (default) | (default) | (default)
// -------------------------+---------------+--------------+------------------
// Check news message content first — news URLs may not implement
// nsIMsgMessageUrl, but the scheme check works regardless.
nsAutoCString contentScheme;
if (NS_SUCCEEDED(aContentLocation->GetScheme(contentScheme)) &&
IsNewsScheme(contentScheme)) {
// News message content requested (4), (5), (6).
nsAutoCString requestScheme;
if (NS_SUCCEEDED(aRequestingLocation->GetScheme(requestScheme)) &&
IsNewsScheme(requestScheme)) {
// Only accept content from the news server the requesting message came
// from (5). News articles are public, so this isn't about tracking, but
// we shouldn't contact (and implicitly create) servers the user hasn't
// set up.
nsAutoCString contentHost, requestHost;
if (NS_SUCCEEDED(aContentLocation->GetAsciiHost(contentHost)) &&
NS_SUCCEEDED(aRequestingLocation->GetAsciiHost(requestHost)) &&
contentHost.Equals(requestHost, nsCaseInsensitiveCStringComparator) &&
NS_GetRealPort(aContentLocation) ==
NS_GetRealPort(aRequestingLocation)) {
return acceptContent();
}
return NS_OK;
}
// Don't accept request coming from a mail message since it would
// access the news server (4). Such a load would bypass remote content
// blocking and could be used to track when the message is read.
// Other requesters, like the compose window, don't get to load news
// message content either (6), same as for mail message content (3).
return NS_OK;
}
nsCOMPtr<nsIMsgMessageUrl> contentURL(do_QueryInterface(aContentLocation));
if (contentURL) {
// 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)
}
// 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)) {
return acceptContent();
}
// Never load unexposed protocols except for web protocols and file.
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()) {
return acceptContent();
}
// 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) {
return acceptContent();
}
// Helper to reject remote content and notify the UI.
auto rejectContentAndNotify = [&]() {
*aDecision = nsIContentPolicy::REJECT_REQUEST;
// Post as an event to avoid DOM mutations at a bad time.
uint64_t browsingContextId = targetContext->Id();
nsCOMPtr<nsIURI> contentURI = aContentLocation;
NS_DispatchToCurrentThread(NS_NewRunnableFunction(
"RemoteContentNotifierEvent",
[browsingContextId, contentURI = std::move(contentURI)]() {
nsAutoString data;
data.AppendInt(browsingContextId);
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
observerService->NotifyObservers(contentURI, "remote-content-blocked",
data.get());
}));
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?
return acceptContent();
}
// Don't load remote content for encrypted messages, unless the message is an
// integrity-protected message decrypted as its top-level part.
nsCOMPtr<nsIEncryptedMsgURIsService> encryptedURIService =
do_GetService("@mozilla.org/messenger/encrypted-msg-uris-service;1", &rv);
NS_ENSURE_SUCCESS(rv, rv);
bool encryptedWithoutIntegrity;
rv = encryptedURIService->IsEncryptedWithoutIntegrity(
aRequestingLocation->GetSpecOrDefault(), &encryptedWithoutIntegrity);
NS_ENSURE_SUCCESS(rv, rv);
if (encryptedWithoutIntegrity) {
return rejectContentAndNotify();
}
// Look up any content-location exception (Allow or Block) for this URL.
uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
mozilla::OriginAttributes attrs;
RefPtr<mozilla::BasePrincipal> principal =
mozilla::BasePrincipal::CreateContentPrincipal(aContentLocation, attrs);
rv = mPermissionManager->TestPermissionFromPrincipal(principal, "image"_ns,
&permission);
// If we are allowing all remote content, still check for block exceptions
// before accepting.
if (!mBlockRemoteImages) {
if (NS_SUCCEEDED(rv) && permission == nsIPermissionManager::DENY_ACTION) {
return rejectContentAndNotify();
}
// Check if the sender is explicitly blocked.
nsCOMPtr<nsIMsgDBHdr> msgHdr = GetPotentialMsgHdr(aRequestingLocation);
if (msgHdr && CheckRemoteContentPermissionForSender(
msgHdr, nsIPermissionManager::DENY_ACTION)) {
return rejectContentAndNotify();
}
return acceptContent();
}
switch (permission) {
case nsIPermissionManager::UNKNOWN_ACTION: {
// No exception was found for this location.
break;
}
case nsIPermissionManager::ALLOW_ACTION: {
return acceptContent();
}
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)) {
return acceptContent();
}
// The default decision is still to reject — check if the message header
// allows remote content for this message.
nsCOMPtr<nsIMsgDBHdr> msgHdr = GetPotentialMsgHdr(aRequestingLocation);
if (!msgHdr) {
// A message whose header can't be found, like a news article loaded by
// message-id, has no remote content permissions to check.
nsCOMPtr<nsIMsgMailNewsUrl> mailnewsUrl =
do_QueryInterface(aRequestingLocation);
if (mailnewsUrl) {
return rejectContentAndNotify();
}
return acceptContent();
}
*aDecision = ShouldAcceptRemoteContentForMsgHdr(msgHdr, aRequestingLocation,
aContentLocation);
if (*aDecision == nsIContentPolicy::REJECT_REQUEST) {
return rejectContentAndNotify();
}
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 isJavaScript;
rv = aContentLocation->SchemeIs("javascript", &isJavaScript);
NS_ENSURE_SUCCESS(rv, true);
bool isFile;
rv = aContentLocation->SchemeIs("file", &isFile);
NS_ENSURE_SUCCESS(rv, true);
return !isHttp && !isHttps && !isWs && !isWss && !isBlob && !isJavaScript &&
!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 = CheckRemoteContentPermissionForSender(
aMsgHdr, nsIPermissionManager::ALLOW_ACTION);
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;
}
/**
* Resolves aRequestingLocation to a message header, or nullptr if the URI
* does not correspond to a known message.
*/
already_AddRefed<nsIMsgDBHdr> nsMsgContentPolicy::GetPotentialMsgHdr(
nsIURI* aRequestingLocation) {
nsresult rv;
nsCOMPtr<nsIMsgMessageUrl> msgUrl(
do_QueryInterface(aRequestingLocation, &rv));
nsCString resourceURI;
if (NS_SUCCEEDED(rv)) {
rv = msgUrl->GetUri(resourceURI);
} else {
rv = aRequestingLocation->GetSpec(resourceURI);
}
NS_ENSURE_SUCCESS(rv, nullptr);
nsCOMPtr<nsIMsgDBHdr> msgHdr;
GetMsgDBHdrFromURI(resourceURI, getter_AddRefs(msgHdr));
return msgHdr.forget();
}
/**
* 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 =
mozilla::components::Compose::Service();
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<nsIMsgMailNewsUrl> mailnewsUrl = do_QueryInterface(aContentLocation);
if (!mailnewsUrl && !isAllowedContent) {
// If it's not an nsIMsgMailNewsUrl 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);
// See displayMessage() in aboutMessage.js.
// 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 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;
}