Previously, the WebExtension protocol used dynamic protocol flags which were based on the WebExtension policy in order to enforce things such as availability in private browsing and the accessibility of certain resources. Since the shift to MV3, these checks have required more complex checks than what was possible to specify with protocol flags, which required the addition of WEBEXT_URI_WEB_ACCESSIBLE - a security flag which would trigger further checks with the EPS to determine if the URI can be loaded. This was somewhat inefficient, as fetching the URI flags would require looking up the policy each time dynamic flags were looked up, as well as when policy specifics were being checked after loading flags. In addition, it lead to a number of flags which were very specific to extension protocols. This patch changes extensions to no longer have dynamic flags, instead specifying the static `URI_IS_WEBEXTENSION_RESOURCE` security flag. When this flag is specified, security checks are made by querying the ExtensionPolicyService to ask if the load should be permitted, combining the specific security checks for Extension resources into a simpler code-path, and avoids redundant checks. Differential Revision: https://phabricator.services.mozilla.com/D216076
773 lines
25 KiB
C++
773 lines
25 KiB
C++
/* -*- 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 "mozilla/ExtensionPolicyService.h"
|
|
#include "mozilla/extensions/DocumentObserver.h"
|
|
#include "mozilla/extensions/WebExtensionContentScript.h"
|
|
#include "mozilla/extensions/WebExtensionPolicy.h"
|
|
|
|
#include "mozilla/BasePrincipal.h"
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/ResultExtensions.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/SimpleEnumerator.h"
|
|
#include "mozilla/StaticPrefs_extensions.h"
|
|
#include "mozilla/Try.h"
|
|
#include "mozilla/dom/BrowsingContext.h"
|
|
#include "mozilla/dom/BrowsingContextGroup.h"
|
|
#include "mozilla/dom/ContentChild.h"
|
|
#include "mozilla/dom/ContentParent.h"
|
|
#include "mozilla/dom/Promise.h"
|
|
#include "mozilla/dom/Promise-inl.h"
|
|
#include "mozIExtensionProcessScript.h"
|
|
#include "nsEscape.h"
|
|
#include "nsGkAtoms.h"
|
|
#include "nsHashKeys.h"
|
|
#include "nsIChannel.h"
|
|
#include "nsIContentPolicy.h"
|
|
#include "mozilla/dom/Document.h"
|
|
#include "nsGlobalWindowInner.h"
|
|
#include "nsILoadInfo.h"
|
|
#include "nsIXULRuntime.h"
|
|
#include "nsImportModule.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsXULAppAPI.h"
|
|
#include "nsQueryObject.h"
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace extensions;
|
|
|
|
using dom::AutoJSAPI;
|
|
using dom::Document;
|
|
using dom::Promise;
|
|
|
|
#define DEFAULT_CSP_PREF \
|
|
"extensions.webextensions.default-content-security-policy"
|
|
#define DEFAULT_DEFAULT_CSP "script-src 'self' 'wasm-unsafe-eval';"
|
|
|
|
#define DEFAULT_CSP_PREF_V3 \
|
|
"extensions.webextensions.default-content-security-policy.v3"
|
|
#define DEFAULT_DEFAULT_CSP_V3 "script-src 'self'; upgrade-insecure-requests;"
|
|
|
|
#define RESTRICTED_DOMAINS_PREF "extensions.webextensions.restrictedDomains"
|
|
|
|
#define QUARANTINED_DOMAINS_PREF "extensions.quarantinedDomains.list"
|
|
#define QUARANTINED_DOMAINS_ENABLED "extensions.quarantinedDomains.enabled"
|
|
|
|
#define OBS_TOPIC_PRELOAD_SCRIPT "web-extension-preload-content-script"
|
|
#define OBS_TOPIC_LOAD_SCRIPT "web-extension-load-content-script"
|
|
|
|
static const char kDocElementInserted[] = "initial-document-element-inserted";
|
|
|
|
/*****************************************************************************
|
|
* ExtensionPolicyService
|
|
*****************************************************************************/
|
|
|
|
using CoreByHostMap = nsTHashMap<nsCStringASCIICaseInsensitiveHashKey,
|
|
RefPtr<extensions::WebExtensionPolicyCore>>;
|
|
|
|
static StaticRWLock sEPSLock;
|
|
static StaticAutoPtr<CoreByHostMap> sCoreByHost MOZ_GUARDED_BY(sEPSLock);
|
|
static StaticRefPtr<AtomSet> sRestrictedDomains MOZ_GUARDED_BY(sEPSLock);
|
|
static StaticRefPtr<AtomSet> sQuarantinedDomains MOZ_GUARDED_BY(sEPSLock);
|
|
|
|
/* static */
|
|
mozIExtensionProcessScript& ExtensionPolicyService::ProcessScript() {
|
|
static nsCOMPtr<mozIExtensionProcessScript> sProcessScript;
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (MOZ_UNLIKELY(!sProcessScript)) {
|
|
sProcessScript = do_ImportESModule(
|
|
"resource://gre/modules/ExtensionProcessScript.sys.mjs",
|
|
"ExtensionProcessScript");
|
|
ClearOnShutdown(&sProcessScript);
|
|
}
|
|
return *sProcessScript;
|
|
}
|
|
|
|
/* static */ ExtensionPolicyService& ExtensionPolicyService::GetSingleton() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
static RefPtr<ExtensionPolicyService> sExtensionPolicyService;
|
|
|
|
if (MOZ_UNLIKELY(!sExtensionPolicyService)) {
|
|
sExtensionPolicyService = new ExtensionPolicyService();
|
|
RegisterWeakMemoryReporter(sExtensionPolicyService);
|
|
ClearOnShutdown(&sExtensionPolicyService);
|
|
}
|
|
return *sExtensionPolicyService.get();
|
|
}
|
|
|
|
/* static */
|
|
RefPtr<extensions::WebExtensionPolicyCore>
|
|
ExtensionPolicyService::GetCoreByHost(const nsACString& aHost) {
|
|
StaticAutoReadLock lock(sEPSLock);
|
|
return sCoreByHost ? sCoreByHost->Get(aHost) : nullptr;
|
|
}
|
|
|
|
/* static */
|
|
RefPtr<extensions::WebExtensionPolicyCore> ExtensionPolicyService::GetCoreByURL(
|
|
const URLInfo& aURL) {
|
|
if (aURL.Scheme() == nsGkAtoms::moz_extension) {
|
|
return GetCoreByHost(aURL.Host());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
ExtensionPolicyService::ExtensionPolicyService() {
|
|
mObs = services::GetObserverService();
|
|
MOZ_RELEASE_ASSERT(mObs);
|
|
|
|
mDefaultCSP.SetIsVoid(true);
|
|
mDefaultCSPV3.SetIsVoid(true);
|
|
|
|
RegisterObservers();
|
|
|
|
{
|
|
StaticAutoWriteLock lock(sEPSLock);
|
|
MOZ_DIAGNOSTIC_ASSERT(!sCoreByHost,
|
|
"ExtensionPolicyService created twice?");
|
|
sCoreByHost = new CoreByHostMap();
|
|
}
|
|
|
|
UpdateRestrictedDomains();
|
|
UpdateQuarantinedDomains();
|
|
}
|
|
|
|
ExtensionPolicyService::~ExtensionPolicyService() {
|
|
UnregisterWeakMemoryReporter(this);
|
|
|
|
{
|
|
StaticAutoWriteLock lock(sEPSLock);
|
|
sCoreByHost = nullptr;
|
|
sRestrictedDomains = nullptr;
|
|
sQuarantinedDomains = nullptr;
|
|
}
|
|
}
|
|
|
|
bool ExtensionPolicyService::UseRemoteExtensions() const {
|
|
static Maybe<bool> sRemoteExtensions;
|
|
if (MOZ_UNLIKELY(sRemoteExtensions.isNothing())) {
|
|
sRemoteExtensions = Some(StaticPrefs::extensions_webextensions_remote());
|
|
}
|
|
return sRemoteExtensions.value() && BrowserTabsRemoteAutostart();
|
|
}
|
|
|
|
bool ExtensionPolicyService::IsExtensionProcess() const {
|
|
bool isRemote = UseRemoteExtensions();
|
|
|
|
if (isRemote && XRE_IsContentProcess()) {
|
|
auto& remoteType = dom::ContentChild::GetSingleton()->GetRemoteType();
|
|
return remoteType == EXTENSION_REMOTE_TYPE;
|
|
}
|
|
return !isRemote && XRE_IsParentProcess();
|
|
}
|
|
|
|
bool ExtensionPolicyService::GetQuarantinedDomainsEnabled() const {
|
|
StaticAutoReadLock lock(sEPSLock);
|
|
return sQuarantinedDomains != nullptr;
|
|
}
|
|
|
|
WebExtensionPolicy* ExtensionPolicyService::GetByURL(const URLInfo& aURL) {
|
|
if (aURL.Scheme() == nsGkAtoms::moz_extension) {
|
|
return GetByHost(aURL.Host());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
WebExtensionPolicy* ExtensionPolicyService::GetByHost(
|
|
const nsACString& aHost) const {
|
|
AssertIsOnMainThread();
|
|
RefPtr<WebExtensionPolicyCore> core = GetCoreByHost(aHost);
|
|
return core ? core->GetMainThreadPolicy() : nullptr;
|
|
}
|
|
|
|
void ExtensionPolicyService::GetAll(
|
|
nsTArray<RefPtr<WebExtensionPolicy>>& aResult) {
|
|
AppendToArray(aResult, mExtensions.Values());
|
|
}
|
|
|
|
bool ExtensionPolicyService::RegisterExtension(WebExtensionPolicy& aPolicy) {
|
|
bool ok =
|
|
(!GetByID(aPolicy.Id()) && !GetByHost(aPolicy.MozExtensionHostname()));
|
|
MOZ_ASSERT(ok);
|
|
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
|
|
mExtensions.InsertOrUpdate(aPolicy.Id(), RefPtr{&aPolicy});
|
|
|
|
{
|
|
StaticAutoWriteLock lock(sEPSLock);
|
|
sCoreByHost->InsertOrUpdate(aPolicy.MozExtensionHostname(), aPolicy.Core());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ExtensionPolicyService::UnregisterExtension(WebExtensionPolicy& aPolicy) {
|
|
bool ok = (GetByID(aPolicy.Id()) == &aPolicy &&
|
|
GetByHost(aPolicy.MozExtensionHostname()) == &aPolicy);
|
|
MOZ_ASSERT(ok);
|
|
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
|
|
mExtensions.Remove(aPolicy.Id());
|
|
|
|
{
|
|
StaticAutoWriteLock lock(sEPSLock);
|
|
sCoreByHost->Remove(aPolicy.MozExtensionHostname());
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ExtensionPolicyService::RegisterObserver(DocumentObserver& aObserver) {
|
|
bool inserted = false;
|
|
mObservers.LookupOrInsertWith(&aObserver, [&] {
|
|
inserted = true;
|
|
return RefPtr{&aObserver};
|
|
});
|
|
return inserted;
|
|
}
|
|
|
|
bool ExtensionPolicyService::UnregisterObserver(DocumentObserver& aObserver) {
|
|
return mObservers.Remove(&aObserver);
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* nsIMemoryReporter
|
|
*****************************************************************************/
|
|
|
|
NS_IMETHODIMP
|
|
ExtensionPolicyService::CollectReports(nsIHandleReportCallback* aHandleReport,
|
|
nsISupports* aData, bool aAnonymize) {
|
|
for (const auto& ext : mExtensions.Values()) {
|
|
nsAtomCString id(ext->Id());
|
|
|
|
NS_ConvertUTF16toUTF8 name(ext->Name());
|
|
name.ReplaceSubstring("\"", "");
|
|
name.ReplaceSubstring("\\", "");
|
|
|
|
nsString url;
|
|
MOZ_TRY_VAR(url, ext->GetURL(u""_ns));
|
|
|
|
nsPrintfCString desc("Extension(id=%s, name=\"%s\", baseURL=%s)", id.get(),
|
|
name.get(), NS_ConvertUTF16toUTF8(url).get());
|
|
desc.ReplaceChar('/', '\\');
|
|
|
|
nsCString path("extensions/");
|
|
path.Append(desc);
|
|
|
|
aHandleReport->Callback(""_ns, path, KIND_NONHEAP, UNITS_COUNT, 1,
|
|
"WebExtensions that are active in this session"_ns,
|
|
aData);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* Content script management
|
|
*****************************************************************************/
|
|
|
|
void ExtensionPolicyService::RegisterObservers() {
|
|
mObs->AddObserver(this, kDocElementInserted, false);
|
|
if (XRE_IsContentProcess()) {
|
|
mObs->AddObserver(this, "http-on-opening-request", false);
|
|
mObs->AddObserver(this, "document-on-opening-request", false);
|
|
}
|
|
|
|
Preferences::AddStrongObserver(this, DEFAULT_CSP_PREF);
|
|
Preferences::AddStrongObserver(this, DEFAULT_CSP_PREF_V3);
|
|
Preferences::AddStrongObserver(this, RESTRICTED_DOMAINS_PREF);
|
|
Preferences::AddStrongObserver(this, QUARANTINED_DOMAINS_PREF);
|
|
Preferences::AddStrongObserver(this, QUARANTINED_DOMAINS_ENABLED);
|
|
}
|
|
|
|
void ExtensionPolicyService::UnregisterObservers() {
|
|
mObs->RemoveObserver(this, kDocElementInserted);
|
|
if (XRE_IsContentProcess()) {
|
|
mObs->RemoveObserver(this, "http-on-opening-request");
|
|
mObs->RemoveObserver(this, "document-on-opening-request");
|
|
}
|
|
|
|
Preferences::RemoveObserver(this, DEFAULT_CSP_PREF);
|
|
Preferences::RemoveObserver(this, DEFAULT_CSP_PREF_V3);
|
|
Preferences::RemoveObserver(this, RESTRICTED_DOMAINS_PREF);
|
|
Preferences::RemoveObserver(this, QUARANTINED_DOMAINS_PREF);
|
|
Preferences::RemoveObserver(this, QUARANTINED_DOMAINS_ENABLED);
|
|
}
|
|
|
|
nsresult ExtensionPolicyService::Observe(nsISupports* aSubject,
|
|
const char* aTopic,
|
|
const char16_t* aData) {
|
|
if (!strcmp(aTopic, kDocElementInserted)) {
|
|
nsCOMPtr<Document> doc = do_QueryInterface(aSubject);
|
|
if (doc) {
|
|
CheckDocument(doc);
|
|
}
|
|
} else if (!strcmp(aTopic, "http-on-opening-request") ||
|
|
!strcmp(aTopic, "document-on-opening-request")) {
|
|
nsCOMPtr<nsIChannel> chan = do_QueryInterface(aSubject);
|
|
if (chan) {
|
|
CheckRequest(chan);
|
|
}
|
|
} else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
|
|
const nsCString converted = NS_ConvertUTF16toUTF8(aData);
|
|
const char* pref = converted.get();
|
|
if (!strcmp(pref, DEFAULT_CSP_PREF)) {
|
|
mDefaultCSP.SetIsVoid(true);
|
|
} else if (!strcmp(pref, DEFAULT_CSP_PREF_V3)) {
|
|
mDefaultCSPV3.SetIsVoid(true);
|
|
} else if (!strcmp(pref, RESTRICTED_DOMAINS_PREF)) {
|
|
UpdateRestrictedDomains();
|
|
} else if (!strcmp(pref, QUARANTINED_DOMAINS_PREF) ||
|
|
!strcmp(pref, QUARANTINED_DOMAINS_ENABLED)) {
|
|
UpdateQuarantinedDomains();
|
|
}
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
already_AddRefed<Promise> ExtensionPolicyService::ExecuteContentScript(
|
|
nsPIDOMWindowInner* aWindow, WebExtensionContentScript& aScript) {
|
|
if (!aWindow->IsCurrentInnerWindow()) {
|
|
return nullptr;
|
|
}
|
|
|
|
RefPtr<Promise> promise;
|
|
ProcessScript().LoadContentScript(&aScript, aWindow, getter_AddRefs(promise));
|
|
return promise.forget();
|
|
}
|
|
|
|
RefPtr<Promise> ExtensionPolicyService::ExecuteContentScripts(
|
|
JSContext* aCx, nsPIDOMWindowInner* aWindow,
|
|
const nsTArray<RefPtr<WebExtensionContentScript>>& aScripts) {
|
|
AutoTArray<RefPtr<Promise>, 8> promises;
|
|
|
|
for (auto& script : aScripts) {
|
|
if (RefPtr<Promise> promise = ExecuteContentScript(aWindow, *script)) {
|
|
promises.AppendElement(std::move(promise));
|
|
}
|
|
}
|
|
|
|
RefPtr<Promise> promise = Promise::All(aCx, promises, IgnoreErrors());
|
|
MOZ_RELEASE_ASSERT(promise);
|
|
return promise;
|
|
}
|
|
|
|
// Use browser's MessageManagerGroup to decide if we care about it, to inject
|
|
// extension APIs or content scripts. Tabs use "browsers", and all custom
|
|
// extension browsers use "webext-browsers", including popups & sidebars,
|
|
// background & options pages, and xpcshell tests.
|
|
static bool IsTabOrExtensionBrowser(dom::BrowsingContext* aBC) {
|
|
const auto& group = aBC->Top()->GetMessageManagerGroup();
|
|
bool rv = group == u"browsers"_ns || group == u"webext-browsers"_ns;
|
|
|
|
#ifdef MOZ_THUNDERBIRD
|
|
// ...unless it's Thunderbird, which has extra groups for unrelated reasons.
|
|
rv = rv || group == u"single-site"_ns || group == u"single-page"_ns;
|
|
#endif
|
|
|
|
return rv;
|
|
}
|
|
|
|
static nsTArray<RefPtr<dom::BrowsingContext>> GetAllInProcessContentBCs() {
|
|
nsTArray<RefPtr<dom::BrowsingContext>> contentBCs;
|
|
nsTArray<RefPtr<dom::BrowsingContextGroup>> groups;
|
|
dom::BrowsingContextGroup::GetAllGroups(groups);
|
|
for (const auto& group : groups) {
|
|
for (const auto& toplevel : group->Toplevels()) {
|
|
if (!toplevel->IsContent() || toplevel->IsDiscarded() ||
|
|
!IsTabOrExtensionBrowser(toplevel)) {
|
|
continue;
|
|
}
|
|
|
|
toplevel->PreOrderWalk([&](dom::BrowsingContext* aContext) {
|
|
contentBCs.AppendElement(aContext);
|
|
});
|
|
}
|
|
}
|
|
return contentBCs;
|
|
}
|
|
|
|
nsresult ExtensionPolicyService::InjectContentScripts(
|
|
WebExtensionPolicy* aExtension) {
|
|
AutoJSAPI jsapi;
|
|
MOZ_ALWAYS_TRUE(jsapi.Init(xpc::PrivilegedJunkScope()));
|
|
|
|
auto contentBCs = GetAllInProcessContentBCs();
|
|
for (dom::BrowsingContext* bc : contentBCs) {
|
|
auto* win = bc->GetDOMWindow();
|
|
|
|
if (bc->Top()->IsDiscarded() || !win || !win->GetDocumentURI()) {
|
|
continue;
|
|
}
|
|
DocInfo docInfo(win);
|
|
|
|
using RunAt = dom::ContentScriptRunAt;
|
|
using Scripts = AutoTArray<RefPtr<WebExtensionContentScript>, 8>;
|
|
|
|
Scripts scripts[ContiguousEnumSize<RunAt>::value];
|
|
|
|
auto GetScripts = [&](RunAt aRunAt) -> Scripts&& {
|
|
static_assert(sizeof(aRunAt) == 1, "Our cast is wrong");
|
|
return std::move(scripts[uint8_t(aRunAt)]);
|
|
};
|
|
|
|
for (const auto& script : aExtension->ContentScripts()) {
|
|
if (script->Matches(docInfo)) {
|
|
GetScripts(script->RunAt()).AppendElement(script);
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowInner> inner = win->GetCurrentInnerWindow();
|
|
|
|
MOZ_TRY(ExecuteContentScripts(jsapi.cx(), inner,
|
|
GetScripts(RunAt::Document_start))
|
|
->ThenWithCycleCollectedArgs(
|
|
[](JSContext* aCx, JS::Handle<JS::Value> aValue,
|
|
ErrorResult& aRv, ExtensionPolicyService* aSelf,
|
|
nsPIDOMWindowInner* aInner, Scripts&& aScripts) {
|
|
return aSelf->ExecuteContentScripts(aCx, aInner, aScripts)
|
|
.forget();
|
|
},
|
|
this, inner, GetScripts(RunAt::Document_end))
|
|
.andThen([&](auto aPromise) {
|
|
return aPromise->ThenWithCycleCollectedArgs(
|
|
[](JSContext* aCx, JS::Handle<JS::Value> aValue,
|
|
ErrorResult& aRv, ExtensionPolicyService* aSelf,
|
|
nsPIDOMWindowInner* aInner, Scripts&& aScripts) {
|
|
return aSelf
|
|
->ExecuteContentScripts(aCx, aInner, aScripts)
|
|
.forget();
|
|
},
|
|
this, inner, GetScripts(RunAt::Document_idle));
|
|
}));
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
// Checks a request for matching content scripts, and begins pre-loading them
|
|
// if necessary.
|
|
void ExtensionPolicyService::CheckRequest(nsIChannel* aChannel) {
|
|
nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo();
|
|
auto loadType = loadInfo->GetExternalContentPolicyType();
|
|
if (loadType != ExtContentPolicy::TYPE_DOCUMENT &&
|
|
loadType != ExtContentPolicy::TYPE_SUBDOCUMENT) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
if (NS_FAILED(aChannel->GetURI(getter_AddRefs(uri)))) {
|
|
return;
|
|
}
|
|
|
|
CheckContentScripts({uri.get(), loadInfo}, true);
|
|
}
|
|
|
|
static bool CheckParentFrames(nsPIDOMWindowOuter* aWindow,
|
|
WebExtensionPolicy& aPolicy) {
|
|
nsCOMPtr<nsIURI> aboutAddons;
|
|
if (NS_FAILED(NS_NewURI(getter_AddRefs(aboutAddons), "about:addons"))) {
|
|
return false;
|
|
}
|
|
nsCOMPtr<nsIURI> htmlAboutAddons;
|
|
if (NS_FAILED(
|
|
NS_NewURI(getter_AddRefs(htmlAboutAddons),
|
|
"chrome://mozapps/content/extensions/aboutaddons.html"))) {
|
|
return false;
|
|
}
|
|
|
|
dom::WindowContext* wc = aWindow->GetCurrentInnerWindow()->GetWindowContext();
|
|
while ((wc = wc->GetParentWindowContext())) {
|
|
if (!wc->IsInProcess()) {
|
|
return false;
|
|
}
|
|
|
|
nsGlobalWindowInner* win = wc->GetInnerWindow();
|
|
|
|
auto* principal = BasePrincipal::Cast(win->GetPrincipal());
|
|
if (principal->IsSystemPrincipal()) {
|
|
// The add-on manager is a special case, since it contains extension
|
|
// options pages in same-type <browser> frames.
|
|
nsIURI* uri = win->GetDocumentURI();
|
|
bool equals;
|
|
if ((NS_SUCCEEDED(uri->Equals(aboutAddons, &equals)) && equals) ||
|
|
(NS_SUCCEEDED(uri->Equals(htmlAboutAddons, &equals)) && equals)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
if (principal->AddonPolicy() != &aPolicy) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Checks a document, just after the document element has been inserted, for
|
|
// matching content scripts or extension principals, and loads them if
|
|
// necessary.
|
|
void ExtensionPolicyService::CheckDocument(Document* aDocument) {
|
|
nsCOMPtr<nsPIDOMWindowOuter> win = aDocument->GetWindow();
|
|
if (win) {
|
|
if (!IsTabOrExtensionBrowser(win->GetBrowsingContext())) {
|
|
return;
|
|
}
|
|
|
|
if (win->GetDocumentURI()) {
|
|
CheckContentScripts(win.get(), false);
|
|
}
|
|
|
|
nsIPrincipal* principal = aDocument->NodePrincipal();
|
|
|
|
RefPtr<WebExtensionPolicy> policy =
|
|
BasePrincipal::Cast(principal)->AddonPolicy();
|
|
if (policy) {
|
|
bool privileged = IsExtensionProcess() && CheckParentFrames(win, *policy);
|
|
|
|
ProcessScript().InitExtensionDocument(policy, aDocument, privileged);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ExtensionPolicyService::CheckContentScripts(const DocInfo& aDocInfo,
|
|
bool aIsPreload) {
|
|
nsCOMPtr<nsPIDOMWindowInner> win;
|
|
if (!aIsPreload) {
|
|
win = aDocInfo.GetWindow()->GetCurrentInnerWindow();
|
|
}
|
|
|
|
nsTArray<RefPtr<WebExtensionContentScript>> scriptsToLoad;
|
|
|
|
for (RefPtr<WebExtensionPolicy> policy : mExtensions.Values()) {
|
|
for (auto& script : policy->ContentScripts()) {
|
|
if (script->Matches(aDocInfo)) {
|
|
if (aIsPreload) {
|
|
ProcessScript().PreloadContentScript(script);
|
|
} else {
|
|
// Collect the content scripts to load instead of loading them
|
|
// right away (to prevent a loaded content script from being
|
|
// able to invalidate the iterator by triggering a call to
|
|
// policy->UnregisterContentScript while we are still iterating
|
|
// over all its content scripts). See Bug 1593240.
|
|
scriptsToLoad.AppendElement(script);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto& script : scriptsToLoad) {
|
|
if (!win->IsCurrentInnerWindow()) {
|
|
break;
|
|
}
|
|
|
|
RefPtr<Promise> promise;
|
|
ProcessScript().LoadContentScript(script, win, getter_AddRefs(promise));
|
|
}
|
|
|
|
scriptsToLoad.ClearAndRetainStorage();
|
|
}
|
|
|
|
for (RefPtr<DocumentObserver> observer : mObservers.Values()) {
|
|
for (auto& matcher : observer->Matchers()) {
|
|
if (matcher->Matches(aDocInfo)) {
|
|
if (aIsPreload) {
|
|
observer->NotifyMatch(*matcher, aDocInfo.GetLoadInfo());
|
|
} else {
|
|
observer->NotifyMatch(*matcher, aDocInfo.GetWindow());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
RefPtr<AtomSet> ExtensionPolicyService::RestrictedDomains() {
|
|
StaticAutoReadLock lock(sEPSLock);
|
|
return sRestrictedDomains;
|
|
}
|
|
|
|
/* static */
|
|
RefPtr<AtomSet> ExtensionPolicyService::QuarantinedDomains() {
|
|
StaticAutoReadLock lock(sEPSLock);
|
|
return sQuarantinedDomains;
|
|
}
|
|
|
|
void ExtensionPolicyService::UpdateRestrictedDomains() {
|
|
nsAutoCString eltsString;
|
|
Unused << Preferences::GetCString(RESTRICTED_DOMAINS_PREF, eltsString);
|
|
|
|
AutoTArray<nsString, 32> elts;
|
|
for (const nsACString& elt : eltsString.Split(',')) {
|
|
elts.AppendElement(NS_ConvertUTF8toUTF16(elt));
|
|
elts.LastElement().StripWhitespace();
|
|
}
|
|
RefPtr<AtomSet> atomSet = new AtomSet(elts);
|
|
|
|
StaticAutoWriteLock lock(sEPSLock);
|
|
sRestrictedDomains = atomSet;
|
|
}
|
|
|
|
void ExtensionPolicyService::UpdateQuarantinedDomains() {
|
|
if (!Preferences::GetBool(QUARANTINED_DOMAINS_ENABLED)) {
|
|
StaticAutoWriteLock lock(sEPSLock);
|
|
sQuarantinedDomains = nullptr;
|
|
return;
|
|
}
|
|
|
|
nsAutoCString eltsString;
|
|
AutoTArray<nsString, 32> elts;
|
|
if (NS_SUCCEEDED(
|
|
Preferences::GetCString(QUARANTINED_DOMAINS_PREF, eltsString))) {
|
|
for (const nsACString& elt : eltsString.Split(',')) {
|
|
elts.AppendElement(NS_ConvertUTF8toUTF16(elt));
|
|
elts.LastElement().StripWhitespace();
|
|
}
|
|
}
|
|
RefPtr<AtomSet> atomSet = new AtomSet(elts);
|
|
|
|
StaticAutoWriteLock lock(sEPSLock);
|
|
sQuarantinedDomains = atomSet;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* nsIAddonPolicyService
|
|
*****************************************************************************/
|
|
|
|
nsresult ExtensionPolicyService::GetDefaultCSP(nsAString& aDefaultCSP) {
|
|
if (mDefaultCSP.IsVoid()) {
|
|
nsresult rv = Preferences::GetString(DEFAULT_CSP_PREF, mDefaultCSP);
|
|
if (NS_FAILED(rv)) {
|
|
mDefaultCSP.AssignLiteral(DEFAULT_DEFAULT_CSP);
|
|
}
|
|
mDefaultCSP.SetIsVoid(false);
|
|
}
|
|
|
|
aDefaultCSP.Assign(mDefaultCSP);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult ExtensionPolicyService::GetDefaultCSPV3(nsAString& aDefaultCSP) {
|
|
if (mDefaultCSPV3.IsVoid()) {
|
|
nsresult rv = Preferences::GetString(DEFAULT_CSP_PREF_V3, mDefaultCSPV3);
|
|
if (NS_FAILED(rv)) {
|
|
mDefaultCSPV3.AssignLiteral(DEFAULT_DEFAULT_CSP_V3);
|
|
}
|
|
mDefaultCSPV3.SetIsVoid(false);
|
|
}
|
|
|
|
aDefaultCSP.Assign(mDefaultCSPV3);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult ExtensionPolicyService::GetBaseCSP(const nsAString& aAddonId,
|
|
nsAString& aResult) {
|
|
if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
|
|
policy->GetBaseCSP(aResult);
|
|
return NS_OK;
|
|
}
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsresult ExtensionPolicyService::GetExtensionPageCSP(const nsAString& aAddonId,
|
|
nsAString& aResult) {
|
|
if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
|
|
policy->GetExtensionPageCSP(aResult);
|
|
return NS_OK;
|
|
}
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsresult ExtensionPolicyService::GetGeneratedBackgroundPageUrl(
|
|
const nsACString& aHostname, nsACString& aResult) {
|
|
if (WebExtensionPolicy* policy = GetByHost(aHostname)) {
|
|
nsAutoCString url("data:text/html,");
|
|
|
|
nsCString html = policy->BackgroundPageHTML();
|
|
nsAutoCString escaped;
|
|
|
|
url.Append(NS_EscapeURL(html, esc_Minimal, escaped));
|
|
|
|
aResult = url;
|
|
return NS_OK;
|
|
}
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsresult ExtensionPolicyService::AddonHasPermission(const nsAString& aAddonId,
|
|
const nsAString& aPerm,
|
|
bool* aResult) {
|
|
if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
|
|
*aResult = policy->HasPermission(aPerm);
|
|
return NS_OK;
|
|
}
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsresult ExtensionPolicyService::AddonMayLoadURI(const nsAString& aAddonId,
|
|
nsIURI* aURI, bool aExplicit,
|
|
bool* aResult) {
|
|
if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
|
|
*aResult = policy->CanAccessURI(aURI, aExplicit);
|
|
return NS_OK;
|
|
}
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsresult ExtensionPolicyService::GetExtensionName(const nsAString& aAddonId,
|
|
nsAString& aName) {
|
|
if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
|
|
aName.Assign(policy->Name());
|
|
return NS_OK;
|
|
}
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsresult ExtensionPolicyService::SourceMayLoadExtensionURI(
|
|
nsIURI* aSourceURI, nsIURI* aExtensionURI, bool aFromPrivateWindow,
|
|
bool* aResult) {
|
|
URLInfo source(aSourceURI);
|
|
URLInfo url(aExtensionURI);
|
|
if (RefPtr<WebExtensionPolicyCore> policy = GetCoreByURL(url)) {
|
|
*aResult = (!aFromPrivateWindow || policy->PrivateBrowsingAllowed()) &&
|
|
policy->SourceMayAccessPath(source, url.FilePath());
|
|
return NS_OK;
|
|
}
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
nsresult ExtensionPolicyService::ExtensionURIToAddonId(nsIURI* aURI,
|
|
nsAString& aResult) {
|
|
if (WebExtensionPolicy* policy = GetByURL(aURI)) {
|
|
policy->GetId(aResult);
|
|
} else {
|
|
aResult.SetIsVoid(true);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTION(ExtensionPolicyService, mExtensions, mObservers)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionPolicyService)
|
|
NS_INTERFACE_MAP_ENTRY(nsIAddonPolicyService)
|
|
NS_INTERFACE_MAP_ENTRY(nsIObserver)
|
|
NS_INTERFACE_MAP_ENTRY(nsIMemoryReporter)
|
|
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAddonPolicyService)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(ExtensionPolicyService)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(ExtensionPolicyService)
|
|
|
|
} // namespace mozilla
|