forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			761 lines
		
	
	
	
		
			24 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			761 lines
		
	
	
	
		
			24 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;
 | |
| }
 | |
| 
 | |
| 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* aResult) {
 | |
|   URLInfo source(aSourceURI);
 | |
|   URLInfo url(aExtensionURI);
 | |
|   if (WebExtensionPolicy* policy = GetByURL(url)) {
 | |
|     *aResult = 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
 | 
