Files
tubestation/toolkit/components/extensions/ExtensionPolicyService.cpp
Ryan VanderMeulen 0980b8abd2 Backed out 8 changesets (bug 1356334) for frequent test_ext_contentscript_async_loading.html failures on Android debug on a CLOSED TREE.
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)
2017-08-18 11:32:18 -04:00

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