forked from mirrors/gecko-dev
Going through the extension policy service rather than using WebExtensionPolicy objects directly adds a lot of unnecessary overhead to common operations on extension principals, and also makes the code more complicated than it needs to be. We also use weak references to policy objects here, since principals should ideally lose as much of their elevated privileges as possible once the extension instance that created them has been destroyed (which is something we couldn't handle easily when we simply tracked ID strings). MozReview-Commit-ID: KDNvVdvLkIt --HG-- extra : rebase_source : 1b567919d2461bd0315d1a7d89f330cbd585f579
422 lines
12 KiB
C++
422 lines
12 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/WebExtensionContentScript.h"
|
|
#include "mozilla/extensions/WebExtensionPolicy.h"
|
|
|
|
#include "mozilla/ClearOnShutdown.h"
|
|
#include "mozilla/Preferences.h"
|
|
#include "mozilla/Services.h"
|
|
#include "mozilla/dom/ContentChild.h"
|
|
#include "mozilla/dom/ContentParent.h"
|
|
#include "mozIExtensionProcessScript.h"
|
|
#include "nsEscape.h"
|
|
#include "nsGkAtoms.h"
|
|
#include "nsIChannel.h"
|
|
#include "nsIContentPolicy.h"
|
|
#include "nsIDOMDocument.h"
|
|
#include "nsIDocument.h"
|
|
#include "nsILoadInfo.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsXULAppAPI.h"
|
|
|
|
namespace mozilla {
|
|
|
|
using namespace extensions;
|
|
|
|
#define DEFAULT_BASE_CSP \
|
|
"script-src 'self' https://* moz-extension: blob: filesystem: 'unsafe-eval' 'unsafe-inline'; " \
|
|
"object-src 'self' https://* moz-extension: blob: filesystem:;"
|
|
|
|
#define DEFAULT_DEFAULT_CSP \
|
|
"script-src 'self'; object-src 'self';"
|
|
|
|
|
|
#define OBS_TOPIC_PRELOAD_SCRIPT "web-extension-preload-content-script"
|
|
#define OBS_TOPIC_LOAD_SCRIPT "web-extension-load-content-script"
|
|
|
|
|
|
static mozIExtensionProcessScript&
|
|
ProcessScript()
|
|
{
|
|
static nsCOMPtr<mozIExtensionProcessScript> sProcessScript;
|
|
|
|
if (MOZ_UNLIKELY(!sProcessScript)) {
|
|
sProcessScript = do_GetService("@mozilla.org/webextensions/extension-process-script;1");
|
|
MOZ_RELEASE_ASSERT(sProcessScript);
|
|
ClearOnShutdown(&sProcessScript);
|
|
}
|
|
return *sProcessScript;
|
|
}
|
|
|
|
/*****************************************************************************
|
|
* ExtensionPolicyService
|
|
*****************************************************************************/
|
|
|
|
/* static */ bool ExtensionPolicyService::sRemoteExtensions;
|
|
|
|
/* static */ ExtensionPolicyService&
|
|
ExtensionPolicyService::GetSingleton()
|
|
{
|
|
static RefPtr<ExtensionPolicyService> sExtensionPolicyService;
|
|
|
|
if (MOZ_UNLIKELY(!sExtensionPolicyService)) {
|
|
sExtensionPolicyService = new ExtensionPolicyService();
|
|
ClearOnShutdown(&sExtensionPolicyService);
|
|
}
|
|
return *sExtensionPolicyService.get();
|
|
}
|
|
|
|
ExtensionPolicyService::ExtensionPolicyService()
|
|
{
|
|
mObs = services::GetObserverService();
|
|
MOZ_RELEASE_ASSERT(mObs);
|
|
|
|
Preferences::AddBoolVarCache(&sRemoteExtensions, "extensions.webextensions.remote", false);
|
|
|
|
RegisterObservers();
|
|
}
|
|
|
|
|
|
bool
|
|
ExtensionPolicyService::IsExtensionProcess() const
|
|
{
|
|
if (sRemoteExtensions && XRE_IsContentProcess()) {
|
|
auto& remoteType = dom::ContentChild::GetSingleton()->GetRemoteType();
|
|
return remoteType.EqualsLiteral(EXTENSION_REMOTE_TYPE);
|
|
}
|
|
return XRE_IsParentProcess();
|
|
}
|
|
|
|
|
|
WebExtensionPolicy*
|
|
ExtensionPolicyService::GetByURL(const URLInfo& aURL)
|
|
{
|
|
if (aURL.Scheme() == nsGkAtoms::moz_extension) {
|
|
return GetByHost(aURL.Host());
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
void
|
|
ExtensionPolicyService::GetAll(nsTArray<RefPtr<WebExtensionPolicy>>& aResult)
|
|
{
|
|
for (auto iter = mExtensions.Iter(); !iter.Done(); iter.Next()) {
|
|
aResult.AppendElement(iter.Data());
|
|
}
|
|
}
|
|
|
|
bool
|
|
ExtensionPolicyService::RegisterExtension(WebExtensionPolicy& aPolicy)
|
|
{
|
|
bool ok = (!GetByID(aPolicy.Id()) &&
|
|
!GetByHost(aPolicy.MozExtensionHostname()));
|
|
MOZ_ASSERT(ok);
|
|
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
|
|
mExtensions.Put(aPolicy.Id(), &aPolicy);
|
|
mExtensionHosts.Put(aPolicy.MozExtensionHostname(), &aPolicy);
|
|
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());
|
|
mExtensionHosts.Remove(aPolicy.MozExtensionHostname());
|
|
return true;
|
|
}
|
|
|
|
|
|
void
|
|
ExtensionPolicyService::BaseCSP(nsAString& aBaseCSP) const
|
|
{
|
|
nsresult rv;
|
|
|
|
rv = Preferences::GetString("extensions.webextensions.base-content-security-policy", aBaseCSP);
|
|
if (NS_FAILED(rv)) {
|
|
aBaseCSP.AssignLiteral(DEFAULT_BASE_CSP);
|
|
}
|
|
}
|
|
|
|
void
|
|
ExtensionPolicyService::DefaultCSP(nsAString& aDefaultCSP) const
|
|
{
|
|
nsresult rv;
|
|
|
|
rv = Preferences::GetString("extensions.webextensions.default-content-security-policy", aDefaultCSP);
|
|
if (NS_FAILED(rv)) {
|
|
aDefaultCSP.AssignLiteral(DEFAULT_DEFAULT_CSP);
|
|
}
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* Content script management
|
|
*****************************************************************************/
|
|
|
|
void
|
|
ExtensionPolicyService::RegisterObservers()
|
|
{
|
|
mObs->AddObserver(this, "content-document-global-created", false);
|
|
mObs->AddObserver(this, "document-element-inserted", false);
|
|
if (XRE_IsContentProcess()) {
|
|
mObs->AddObserver(this, "http-on-opening-request", false);
|
|
}
|
|
}
|
|
|
|
void
|
|
ExtensionPolicyService::UnregisterObservers()
|
|
{
|
|
mObs->RemoveObserver(this, "content-document-global-created");
|
|
mObs->RemoveObserver(this, "document-element-inserted");
|
|
if (XRE_IsContentProcess()) {
|
|
mObs->RemoveObserver(this, "http-on-opening-request");
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
ExtensionPolicyService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData)
|
|
{
|
|
if (!strcmp(aTopic, "content-document-global-created")) {
|
|
nsCOMPtr<nsPIDOMWindowOuter> win = do_QueryInterface(aSubject);
|
|
if (win) {
|
|
CheckWindow(win);
|
|
}
|
|
} else if (!strcmp(aTopic, "document-element-inserted")) {
|
|
nsCOMPtr<nsIDocument> doc = do_QueryInterface(aSubject);
|
|
if (doc) {
|
|
CheckDocument(doc);
|
|
}
|
|
} else if (!strcmp(aTopic, "http-on-opening-request")) {
|
|
nsCOMPtr<nsIChannel> chan = do_QueryInterface(aSubject);
|
|
if (chan) {
|
|
CheckRequest(chan);
|
|
}
|
|
}
|
|
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->GetLoadInfo();
|
|
if (!loadInfo) {
|
|
return;
|
|
}
|
|
|
|
auto loadType = loadInfo->GetExternalContentPolicyType();
|
|
if (loadType != nsIContentPolicy::TYPE_DOCUMENT &&
|
|
loadType != nsIContentPolicy::TYPE_SUBDOCUMENT) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> uri;
|
|
if (NS_FAILED(aChannel->GetURI(getter_AddRefs(uri)))) {
|
|
return;
|
|
}
|
|
|
|
CheckContentScripts({uri.get(), loadInfo}, 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(nsIDocument* aDocument)
|
|
{
|
|
nsCOMPtr<nsPIDOMWindowOuter> win = aDocument->GetWindow();
|
|
if (win) {
|
|
if (win->GetDocumentURI()) {
|
|
CheckContentScripts(win.get(), false);
|
|
}
|
|
|
|
nsIPrincipal* principal = aDocument->NodePrincipal();
|
|
|
|
RefPtr<WebExtensionPolicy> policy = BasePrincipal::Cast(principal)->AddonPolicy();
|
|
if (policy) {
|
|
nsCOMPtr<nsIDOMDocument> doc = do_QueryInterface(aDocument);
|
|
ProcessScript().InitExtensionDocument(policy, doc);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Checks for loads of about:blank into new window globals, and loads any
|
|
// matching content scripts. about:blank loads do not trigger document element
|
|
// inserted events, so they're the only load type that are special cased this
|
|
// way.
|
|
void
|
|
ExtensionPolicyService::CheckWindow(nsPIDOMWindowOuter* aWindow)
|
|
{
|
|
// We only care about non-initial document loads here. The initial
|
|
// about:blank document will usually be re-used to load another document.
|
|
nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
|
|
if (!doc || doc->IsInitialDocument() ||
|
|
doc->GetReadyStateEnum() == nsIDocument::READYSTATE_UNINITIALIZED) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> docUri = doc->GetDocumentURI();
|
|
nsCOMPtr<nsIURI> uri;
|
|
if (!docUri || NS_FAILED(docUri->CloneIgnoringRef(getter_AddRefs(uri))) ||
|
|
!NS_IsAboutBlank(uri)) {
|
|
return;
|
|
}
|
|
|
|
CheckContentScripts(aWindow, false);
|
|
}
|
|
|
|
void
|
|
ExtensionPolicyService::CheckContentScripts(const DocInfo& aDocInfo, bool aIsPreload)
|
|
{
|
|
for (auto iter = mExtensions.Iter(); !iter.Done(); iter.Next()) {
|
|
RefPtr<WebExtensionPolicy> policy = iter.Data();
|
|
|
|
for (auto& script : policy->ContentScripts()) {
|
|
if (script->Matches(aDocInfo)) {
|
|
if (aIsPreload) {
|
|
ProcessScript().PreloadContentScript(script);
|
|
} else {
|
|
ProcessScript().LoadContentScript(script, aDocInfo.GetWindow());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*****************************************************************************
|
|
* nsIAddonPolicyService
|
|
*****************************************************************************/
|
|
|
|
nsresult
|
|
ExtensionPolicyService::GetBaseCSP(nsAString& aBaseCSP)
|
|
{
|
|
BaseCSP(aBaseCSP);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
ExtensionPolicyService::GetDefaultCSP(nsAString& aDefaultCSP)
|
|
{
|
|
DefaultCSP(aDefaultCSP);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
ExtensionPolicyService::GetAddonCSP(const nsAString& aAddonId,
|
|
nsAString& aResult)
|
|
{
|
|
if (WebExtensionPolicy* policy = GetByID(aAddonId)) {
|
|
policy->GetContentSecurityPolicy(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::ExtensionURILoadableByAnyone(nsIURI* aURI, bool* aResult)
|
|
{
|
|
URLInfo url(aURI);
|
|
if (WebExtensionPolicy* policy = GetByURL(url)) {
|
|
*aResult = policy->IsPathWebAccessible(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, mExtensionHosts)
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionPolicyService)
|
|
NS_INTERFACE_MAP_ENTRY(nsIAddonPolicyService)
|
|
NS_INTERFACE_MAP_ENTRY(nsIObserver)
|
|
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
|