/* -*- 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 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 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>& 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 win = do_QueryInterface(aSubject); if (win) { CheckWindow(win); } } else if (!strcmp(aTopic, "document-element-inserted")) { nsCOMPtr doc = do_QueryInterface(aSubject); if (doc) { CheckDocument(doc); } } else if (!strcmp(aTopic, "http-on-opening-request")) { nsCOMPtr 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 loadInfo = aChannel->GetLoadInfo(); if (!loadInfo) { return; } auto loadType = loadInfo->GetExternalContentPolicyType(); if (loadType != nsIContentPolicy::TYPE_DOCUMENT && loadType != nsIContentPolicy::TYPE_SUBDOCUMENT) { return; } nsCOMPtr 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 win = aDocument->GetWindow(); if (win) { if (win->GetDocumentURI()) { CheckContentScripts(win.get(), false); } nsIPrincipal* principal = aDocument->NodePrincipal(); RefPtr policy = BasePrincipal::Cast(principal)->AddonPolicy(); if (policy) { nsCOMPtr 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 doc = aWindow->GetExtantDoc(); if (!doc || doc->IsInitialDocument() || doc->GetReadyStateEnum() == nsIDocument::READYSTATE_UNINITIALIZED) { return; } nsCOMPtr docUri = doc->GetDocumentURI(); nsCOMPtr 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 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