Backed out changeset 9c677ebfdda0 (bug 1356334) Backed out changeset 16b49fd1c38a (bug 1356334) Backed out changeset 4f5bcd2b2dc6 (bug 1356334) Backed out changeset d832803270ac (bug 1356334) Backed out changeset b83aea215a82 (bug 1356334) Backed out changeset b2a663ffd144 (bug 1356334) Backed out changeset 805c56806930 (bug 1356334) Backed out changeset 7c880eca810a (bug 1356334)
415 lines
11 KiB
C++
415 lines
11 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();
|
|
|
|
nsAutoString addonId;
|
|
Unused << principal->GetAddonId(addonId);
|
|
|
|
RefPtr<WebExtensionPolicy> policy = GetByID(addonId);
|
|
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::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
|