/* -*- 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 "MainThreadUtils.h" #include "mozilla/ExtensionPolicyService.h" #include "mozilla/extensions/DocumentObserver.h" #include "mozilla/extensions/WebExtensionContentScript.h" #include "mozilla/extensions/WebExtensionPolicy.h" #include "mozilla/AddonManagerWebAPI.h" #include "mozilla/BasePrincipal.h" #include "mozilla/dom/WindowGlobalChild.h" #include "mozilla/ResultExtensions.h" #include "mozilla/StaticPrefs_extensions.h" #include "mozilla/Try.h" #include "nsContentUtils.h" #include "nsEscape.h" #include "nsGlobalWindowInner.h" #include "nsIObserver.h" #include "nsISubstitutingProtocolHandler.h" #include "nsLiteralString.h" #include "nsNetUtil.h" #include "nsPrintfCString.h" namespace mozilla { namespace extensions { using namespace dom; static const char kProto[] = "moz-extension"; static const char kBackgroundScriptTypeDefault[] = "text/javascript"; static const char kBackgroundScriptTypeModule[] = "module"; static const char kBackgroundPageHTMLStart[] = "\n\ \n\ \n\ "; static const char kBackgroundPageHTMLScript[] = "\n\ "; static const char kBackgroundPageHTMLEnd[] = "\n\ \n\ "; #define BASE_CSP_PREF_V2 "extensions.webextensions.base-content-security-policy" #define DEFAULT_BASE_CSP_V2 \ "script-src 'self' https://* http://localhost:* http://127.0.0.1:* " \ "moz-extension: blob: filesystem: 'unsafe-eval' 'wasm-unsafe-eval' " \ "'unsafe-inline';" #define BASE_CSP_PREF_V3 \ "extensions.webextensions.base-content-security-policy.v3" #define DEFAULT_BASE_CSP_V3 "script-src 'self' 'wasm-unsafe-eval';" static inline ExtensionPolicyService& EPS() { return ExtensionPolicyService::GetSingleton(); } static nsISubstitutingProtocolHandler* Proto() { static nsCOMPtr sHandler; if (MOZ_UNLIKELY(!sHandler)) { nsCOMPtr ios = do_GetIOService(); MOZ_RELEASE_ASSERT(ios); nsCOMPtr handler; ios->GetProtocolHandler(kProto, getter_AddRefs(handler)); sHandler = do_QueryInterface(handler); MOZ_RELEASE_ASSERT(sHandler); ClearOnShutdown(&sHandler); } return sHandler; } bool ParseGlobs(GlobalObject& aGlobal, Sequence aGlobs, nsTArray>& aResult, ErrorResult& aRv) { for (auto& elem : aGlobs) { if (elem.IsMatchGlob()) { aResult.AppendElement(elem.GetAsMatchGlob()->Core()); } else { RefPtr glob = new MatchGlobCore(elem.GetAsUTF8String(), true, false, aRv); if (aRv.Failed()) { return false; } aResult.AppendElement(glob); } } return true; } enum class ErrorBehavior { CreateEmptyPattern, Fail, }; already_AddRefed ParseMatches( GlobalObject& aGlobal, const OwningMatchPatternSetOrStringSequence& aMatches, const MatchPatternOptions& aOptions, ErrorBehavior aErrorBehavior, ErrorResult& aRv) { if (aMatches.IsMatchPatternSet()) { return do_AddRef(aMatches.GetAsMatchPatternSet().get()); } const auto& strings = aMatches.GetAsStringSequence(); nsTArray patterns; if (!patterns.SetCapacity(strings.Length(), fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return nullptr; } for (auto& string : strings) { OwningStringOrMatchPattern elt; elt.SetAsString() = string; patterns.AppendElement(elt); } RefPtr result = MatchPatternSet::Constructor(aGlobal, patterns, aOptions, aRv); if (aRv.Failed() && aErrorBehavior == ErrorBehavior::CreateEmptyPattern) { aRv.SuppressException(); result = MatchPatternSet::Constructor(aGlobal, {}, aOptions, aRv); } return result.forget(); } WebAccessibleResource::WebAccessibleResource( GlobalObject& aGlobal, const WebAccessibleResourceInit& aInit, ErrorResult& aRv) { ParseGlobs(aGlobal, aInit.mResources, mWebAccessiblePaths, aRv); if (aRv.Failed()) { return; } if (!aInit.mMatches.IsNull()) { MatchPatternOptions options; options.mRestrictSchemes = true; RefPtr matches = ParseMatches(aGlobal, aInit.mMatches.Value(), options, ErrorBehavior::CreateEmptyPattern, aRv); MOZ_DIAGNOSTIC_ASSERT(!aRv.Failed()); mMatches = matches->Core(); } if (!aInit.mExtension_ids.IsNull()) { mExtensionIDs = new AtomSet(aInit.mExtension_ids.Value()); } } bool WebAccessibleResource::IsExtensionMatch(const URLInfo& aURI) { if (!mExtensionIDs) { return false; } RefPtr policy = ExtensionPolicyService::GetCoreByHost(aURI.Host()); return policy && (mExtensionIDs->Contains(nsGkAtoms::_asterisk) || mExtensionIDs->Contains(policy->Id())); } /***************************************************************************** * WebExtensionPolicyCore *****************************************************************************/ WebExtensionPolicyCore::WebExtensionPolicyCore(GlobalObject& aGlobal, WebExtensionPolicy* aPolicy, const WebExtensionInit& aInit, ErrorResult& aRv) : mPolicy(aPolicy), mId(NS_AtomizeMainThread(aInit.mId)), mName(aInit.mName), mType(NS_AtomizeMainThread(aInit.mType)), mManifestVersion(aInit.mManifestVersion), mExtensionPageCSP(aInit.mExtensionPageCSP), mIsPrivileged(aInit.mIsPrivileged), mTemporarilyInstalled(aInit.mTemporarilyInstalled), mBackgroundWorkerScript(aInit.mBackgroundWorkerScript), mIgnoreQuarantine(aInit.mIsPrivileged || aInit.mIgnoreQuarantine), mPermissions(new AtomSet(aInit.mPermissions)) { // In practice this is not necessary, but in tests where the uuid // passed in is not lowercased various tests can fail. ToLowerCase(aInit.mMozExtensionHostname, mHostname); // Initialize the base CSP and extension page CSP if (mManifestVersion < 3) { nsresult rv = Preferences::GetString(BASE_CSP_PREF_V2, mBaseCSP); if (NS_FAILED(rv)) { mBaseCSP = NS_LITERAL_STRING_FROM_CSTRING(DEFAULT_BASE_CSP_V2); } } else { nsresult rv = Preferences::GetString(BASE_CSP_PREF_V3, mBaseCSP); if (NS_FAILED(rv)) { mBaseCSP = NS_LITERAL_STRING_FROM_CSTRING(DEFAULT_BASE_CSP_V3); } } if (mExtensionPageCSP.IsVoid()) { if (mManifestVersion < 3) { EPS().GetDefaultCSP(mExtensionPageCSP); } else { EPS().GetDefaultCSPV3(mExtensionPageCSP); } } mWebAccessibleResources.SetCapacity(aInit.mWebAccessibleResources.Length()); for (const auto& resourceInit : aInit.mWebAccessibleResources) { RefPtr resource = new WebAccessibleResource(aGlobal, resourceInit, aRv); if (aRv.Failed()) { return; } mWebAccessibleResources.AppendElement(std::move(resource)); } nsresult rv = NS_NewURI(getter_AddRefs(mBaseURI), aInit.mBaseURL); if (NS_FAILED(rv)) { aRv.Throw(rv); } } bool WebExtensionPolicyCore::SourceMayAccessPath( const URLInfo& aURI, const nsACString& aPath) const { if (aURI.Scheme() == nsGkAtoms::moz_extension && MozExtensionHostname().Equals(aURI.Host())) { // An extension can always access it's own paths. return true; } // Bug 1786564 Static themes need to allow access to theme resources. if (Type() == nsGkAtoms::theme) { RefPtr policyCore = ExtensionPolicyService::GetCoreByHost(aURI.Host()); return policyCore != nullptr; } if (ManifestVersion() < 3) { return IsWebAccessiblePath(aPath); } for (const auto& resource : mWebAccessibleResources) { if (resource->SourceMayAccessPath(aURI, aPath)) { return true; } } return false; } bool WebExtensionPolicyCore::CanAccessURI(const URLInfo& aURI, bool aExplicit, bool aCheckRestricted, bool aAllowFilePermission) const { if (aCheckRestricted && WebExtensionPolicy::IsRestrictedURI(aURI)) { return false; } if (aCheckRestricted && QuarantinedFromURI(aURI)) { return false; } if (!aAllowFilePermission && aURI.Scheme() == nsGkAtoms::file) { return false; } AutoReadLock lock(mLock); return mHostPermissions && mHostPermissions->Matches(aURI, aExplicit); } bool WebExtensionPolicyCore::QuarantinedFromDoc(const DocInfo& aDoc) const { return QuarantinedFromURI(aDoc.PrincipalURL()); } bool WebExtensionPolicyCore::QuarantinedFromURI(const URLInfo& aURI) const { return !IgnoreQuarantine() && WebExtensionPolicy::IsQuarantinedURI(aURI); } /***************************************************************************** * WebExtensionPolicy *****************************************************************************/ WebExtensionPolicy::WebExtensionPolicy(GlobalObject& aGlobal, const WebExtensionInit& aInit, ErrorResult& aRv) : mCore(new WebExtensionPolicyCore(aGlobal, this, aInit, aRv)), mLocalizeCallback(aInit.mLocalizeCallback) { if (aRv.Failed()) { return; } MatchPatternOptions options; options.mRestrictSchemes = !HasPermission(nsGkAtoms::mozillaAddons); // Set host permissions with SetAllowedOrigins to make sure the copy in core // and WebExtensionPolicy stay in sync. RefPtr hostPermissions = ParseMatches(aGlobal, aInit.mAllowedOrigins, options, ErrorBehavior::CreateEmptyPattern, aRv); if (aRv.Failed()) { return; } SetAllowedOrigins(*hostPermissions); if (!aInit.mBackgroundScripts.IsNull()) { mBackgroundScripts.SetValue().AppendElements( aInit.mBackgroundScripts.Value()); } mBackgroundTypeModule = aInit.mBackgroundTypeModule; mContentScripts.SetCapacity(aInit.mContentScripts.Length()); for (const auto& scriptInit : aInit.mContentScripts) { // The activeTab permission is only for dynamically injected scripts, // it cannot be used for declarative content scripts. if (scriptInit.mHasActiveTabPermission) { aRv.Throw(NS_ERROR_INVALID_ARG); return; } RefPtr contentScript = new WebExtensionContentScript(aGlobal, *this, scriptInit, aRv); if (aRv.Failed()) { return; } mContentScripts.AppendElement(std::move(contentScript)); } if (aInit.mReadyPromise.WasPassed()) { mReadyPromise = &aInit.mReadyPromise.Value(); } } already_AddRefed WebExtensionPolicy::Constructor( GlobalObject& aGlobal, const WebExtensionInit& aInit, ErrorResult& aRv) { RefPtr policy = new WebExtensionPolicy(aGlobal, aInit, aRv); if (aRv.Failed()) { return nullptr; } return policy.forget(); } /* static */ void WebExtensionPolicy::GetActiveExtensions( dom::GlobalObject& aGlobal, nsTArray>& aResults) { EPS().GetAll(aResults); } /* static */ already_AddRefed WebExtensionPolicy::GetByID( dom::GlobalObject& aGlobal, const nsAString& aID) { return do_AddRef(EPS().GetByID(aID)); } /* static */ already_AddRefed WebExtensionPolicy::GetByHostname( dom::GlobalObject& aGlobal, const nsACString& aHostname) { return do_AddRef(EPS().GetByHost(aHostname)); } /* static */ already_AddRefed WebExtensionPolicy::GetByURI( dom::GlobalObject& aGlobal, nsIURI* aURI) { return do_AddRef(EPS().GetByURL(aURI)); } void WebExtensionPolicy::SetActive(bool aActive, ErrorResult& aRv) { if (aActive == mActive) { return; } bool ok = aActive ? Enable() : Disable(); if (!ok) { aRv.Throw(NS_ERROR_UNEXPECTED); } } bool WebExtensionPolicy::Enable() { MOZ_ASSERT(!mActive); if (!EPS().RegisterExtension(*this)) { return false; } if (XRE_IsParentProcess()) { // Reserve a BrowsingContextGroup for use by this WebExtensionPolicy. RefPtr group = BrowsingContextGroup::Create(); mBrowsingContextGroup = group->MakeKeepAlivePtr(); } Unused << Proto()->SetSubstitution(MozExtensionHostname(), BaseURI()); mActive = true; return true; } bool WebExtensionPolicy::Disable() { MOZ_ASSERT(mActive); MOZ_ASSERT(EPS().GetByID(Id()) == this); if (!EPS().UnregisterExtension(*this)) { return false; } if (XRE_IsParentProcess()) { // Clear our BrowsingContextGroup reference. A new instance will be created // when the extension is next activated. mBrowsingContextGroup = nullptr; } Unused << Proto()->SetSubstitution(MozExtensionHostname(), nullptr); mActive = false; return true; } void WebExtensionPolicy::GetURL(const nsAString& aPath, nsAString& aResult, ErrorResult& aRv) const { auto result = GetURL(aPath); if (result.isOk()) { aResult = result.unwrap(); } else { aRv.Throw(result.unwrapErr()); } } Result WebExtensionPolicy::GetURL( const nsAString& aPath) const { nsPrintfCString spec("%s://%s/", kProto, MozExtensionHostname().get()); nsCOMPtr uri; MOZ_TRY(NS_NewURI(getter_AddRefs(uri), spec)); MOZ_TRY(uri->Resolve(NS_ConvertUTF16toUTF8(aPath), spec)); return NS_ConvertUTF8toUTF16(spec); } void WebExtensionPolicy::SetIgnoreQuarantine(bool aIgnore) { WebExtensionPolicy_Binding::ClearCachedIgnoreQuarantineValue(this); mCore->SetIgnoreQuarantine(aIgnore); } void WebExtensionPolicy::RegisterContentScript( WebExtensionContentScript& script, ErrorResult& aRv) { // Raise an "invalid argument" error if the script is not related to // the expected extension or if it is already registered. if (script.mExtension != this || mContentScripts.Contains(&script)) { aRv.Throw(NS_ERROR_INVALID_ARG); return; } RefPtr newScript = &script; if (!mContentScripts.AppendElement(std::move(newScript), fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } WebExtensionPolicy_Binding::ClearCachedContentScriptsValue(this); } void WebExtensionPolicy::UnregisterContentScript( const WebExtensionContentScript& script, ErrorResult& aRv) { if (script.mExtension != this || !mContentScripts.RemoveElement(&script)) { aRv.Throw(NS_ERROR_INVALID_ARG); return; } WebExtensionPolicy_Binding::ClearCachedContentScriptsValue(this); } void WebExtensionPolicy::SetAllowedOrigins(MatchPatternSet& aAllowedOrigins) { // Make sure to keep the version in `WebExtensionPolicy` (which can be exposed // back to script using AllowedOrigins()), and the version in // `WebExtensionPolicyCore` (which is threadsafe) in sync. AutoWriteLock lock(mCore->mLock); mHostPermissions = &aAllowedOrigins; mCore->mHostPermissions = aAllowedOrigins.Core(); } void WebExtensionPolicy::InjectContentScripts(ErrorResult& aRv) { nsresult rv = EPS().InjectContentScripts(this); if (NS_FAILED(rv)) { aRv.Throw(rv); } } /* static */ bool WebExtensionPolicy::UseRemoteWebExtensions(GlobalObject& aGlobal) { return EPS().UseRemoteExtensions(); } /* static */ bool WebExtensionPolicy::IsExtensionProcess(GlobalObject& aGlobal) { return EPS().IsExtensionProcess(); } /* static */ bool WebExtensionPolicy::BackgroundServiceWorkerEnabled(GlobalObject& aGlobal) { // When MOZ_WEBEXT_WEBIDL_ENABLED is not set at compile time, extension APIs // are not available to extension service workers. To avoid confusion, the // extensions.backgroundServiceWorkerEnabled.enabled pref is locked to false // in modules/libpref/init/all.js when MOZ_WEBEXT_WEBIDL_ENABLED is not set. return StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup(); } /* static */ bool WebExtensionPolicy::QuarantinedDomainsEnabled(GlobalObject& aGlobal) { return EPS().GetQuarantinedDomainsEnabled(); } /* static */ bool WebExtensionPolicy::IsRestrictedDoc(const DocInfo& aDoc) { // With the exception of top-level about:blank documents with null // principals, we never match documents that have non-content principals, // including those with null principals or system principals. if (aDoc.Principal() && !aDoc.Principal()->GetIsContentPrincipal()) { return true; } return IsRestrictedURI(aDoc.PrincipalURL()); } /* static */ bool WebExtensionPolicy::IsRestrictedURI(const URLInfo& aURI) { RefPtr restrictedDomains = ExtensionPolicyService::RestrictedDomains(); if (restrictedDomains && restrictedDomains->Contains(aURI.HostAtom())) { return true; } if (AddonManagerWebAPI::IsValidSite(aURI.URI())) { return true; } return false; } /* static */ bool WebExtensionPolicy::IsQuarantinedDoc(const DocInfo& aDoc) { return IsQuarantinedURI(aDoc.PrincipalURL()); } /* static */ bool WebExtensionPolicy::IsQuarantinedURI(const URLInfo& aURI) { // Ensure EPS is initialized before asking it about quarantined domains. Unused << EPS(); RefPtr quarantinedDomains = ExtensionPolicyService::QuarantinedDomains(); return quarantinedDomains && quarantinedDomains->Contains(aURI.HostAtom()); } nsCString WebExtensionPolicy::BackgroundPageHTML() const { nsCString result; if (mBackgroundScripts.IsNull()) { result.SetIsVoid(true); return result; } result.AppendLiteral(kBackgroundPageHTMLStart); const char* scriptType = mBackgroundTypeModule ? kBackgroundScriptTypeModule : kBackgroundScriptTypeDefault; for (auto& script : mBackgroundScripts.Value()) { nsCString escaped; nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(script), escaped); result.AppendPrintf(kBackgroundPageHTMLScript, scriptType, escaped.get()); } result.AppendLiteral(kBackgroundPageHTMLEnd); return result; } void WebExtensionPolicy::Localize(const nsAString& aInput, nsString& aOutput) const { RefPtr callback(mLocalizeCallback); callback->Call(aInput, aOutput); } JSObject* WebExtensionPolicy::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return WebExtensionPolicy_Binding::Wrap(aCx, this, aGivenProto); } void WebExtensionPolicy::GetContentScripts( nsTArray>& aScripts) const { aScripts.AppendElements(mContentScripts); } bool WebExtensionPolicy::PrivateBrowsingAllowed() const { return HasPermission(nsGkAtoms::privateBrowsingAllowedPermission); } bool WebExtensionPolicy::CanAccessContext(nsILoadContext* aContext) const { MOZ_ASSERT(aContext); return PrivateBrowsingAllowed() || !aContext->UsePrivateBrowsing(); } bool WebExtensionPolicy::CanAccessWindow( const dom::WindowProxyHolder& aWindow) const { if (PrivateBrowsingAllowed()) { return true; } // match browsing mode with policy nsIDocShell* docShell = aWindow.get()->GetDocShell(); nsCOMPtr loadContext = do_QueryInterface(docShell); return !(loadContext && loadContext->UsePrivateBrowsing()); } void WebExtensionPolicy::GetReadyPromise( JSContext* aCx, JS::MutableHandle aResult) const { if (mReadyPromise) { aResult.set(mReadyPromise->PromiseObj()); } else { aResult.set(nullptr); } } uint64_t WebExtensionPolicy::GetBrowsingContextGroupId() const { MOZ_ASSERT(XRE_IsParentProcess() && mActive); return mBrowsingContextGroup ? mBrowsingContextGroup->Id() : 0; } uint64_t WebExtensionPolicy::GetBrowsingContextGroupId(ErrorResult& aRv) { if (XRE_IsParentProcess() && mActive) { return GetBrowsingContextGroupId(); } aRv.ThrowInvalidAccessError( "browsingContextGroupId only available for active policies in the " "parent process"); return 0; } WebExtensionPolicy::~WebExtensionPolicy() { mCore->ClearPolicyWeakRef(); } NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_CLASS(WebExtensionPolicy) NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebExtensionPolicy) NS_IMPL_CYCLE_COLLECTION_UNLINK(mParent) NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContextGroup) NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalizeCallback) NS_IMPL_CYCLE_COLLECTION_UNLINK(mHostPermissions) NS_IMPL_CYCLE_COLLECTION_UNLINK(mContentScripts) NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER AssertIsOnMainThread(); tmp->mCore->ClearPolicyWeakRef(); NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(WebExtensionPolicy) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContextGroup) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalizeCallback) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHostPermissions) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContentScripts) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebExtensionPolicy) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(WebExtensionPolicy) NS_IMPL_CYCLE_COLLECTING_RELEASE(WebExtensionPolicy) /***************************************************************************** * WebExtensionContentScript / MozDocumentMatcher *****************************************************************************/ /* static */ already_AddRefed MozDocumentMatcher::Constructor( GlobalObject& aGlobal, const dom::MozDocumentMatcherInit& aInit, ErrorResult& aRv) { RefPtr matcher = new MozDocumentMatcher(aGlobal, aInit, false, aRv); if (aRv.Failed()) { return nullptr; } return matcher.forget(); } /* static */ already_AddRefed WebExtensionContentScript::Constructor(GlobalObject& aGlobal, WebExtensionPolicy& aExtension, const ContentScriptInit& aInit, ErrorResult& aRv) { RefPtr script = new WebExtensionContentScript(aGlobal, aExtension, aInit, aRv); if (aRv.Failed()) { return nullptr; } return script.forget(); } MozDocumentMatcher::MozDocumentMatcher(GlobalObject& aGlobal, const dom::MozDocumentMatcherInit& aInit, bool aRestricted, ErrorResult& aRv) : mHasActiveTabPermission(aInit.mHasActiveTabPermission), mRestricted(aRestricted), mAllFrames(aInit.mAllFrames), mCheckPermissions(aInit.mCheckPermissions), mFrameID(aInit.mFrameID), mMatchAboutBlank(aInit.mMatchAboutBlank) { MatchPatternOptions options; options.mRestrictSchemes = mRestricted; mMatches = ParseMatches(aGlobal, aInit.mMatches, options, ErrorBehavior::CreateEmptyPattern, aRv); if (aRv.Failed()) { return; } if (!aInit.mExcludeMatches.IsNull()) { mExcludeMatches = ParseMatches(aGlobal, aInit.mExcludeMatches.Value(), options, ErrorBehavior::CreateEmptyPattern, aRv); if (aRv.Failed()) { return; } } if (!aInit.mIncludeGlobs.IsNull()) { if (!ParseGlobs(aGlobal, aInit.mIncludeGlobs.Value(), mIncludeGlobs.SetValue(), aRv)) { return; } } if (!aInit.mExcludeGlobs.IsNull()) { if (!ParseGlobs(aGlobal, aInit.mExcludeGlobs.Value(), mExcludeGlobs.SetValue(), aRv)) { return; } } if (!aInit.mOriginAttributesPatterns.IsNull()) { Sequence& arr = mOriginAttributesPatterns.SetValue(); for (const auto& pattern : aInit.mOriginAttributesPatterns.Value()) { if (!arr.AppendElement(OriginAttributesPattern(pattern), fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } } } } WebExtensionContentScript::WebExtensionContentScript( GlobalObject& aGlobal, WebExtensionPolicy& aExtension, const ContentScriptInit& aInit, ErrorResult& aRv) : MozDocumentMatcher(aGlobal, aInit, !aExtension.HasPermission(nsGkAtoms::mozillaAddons), aRv), mRunAt(aInit.mRunAt) { mCssPaths.Assign(aInit.mCssPaths); mJsPaths.Assign(aInit.mJsPaths); mExtension = &aExtension; // Origin permissions are optional in mv3, so always check them at runtime. if (mExtension->ManifestVersion() >= 3) { mCheckPermissions = true; } } bool MozDocumentMatcher::Matches(const DocInfo& aDoc, bool aIgnorePermissions) const { if (!mFrameID.IsNull()) { if (aDoc.FrameID() != mFrameID.Value()) { return false; } } else { if (!mAllFrames && !aDoc.IsTopLevel()) { return false; } } // match browsing mode with policy nsCOMPtr loadContext = aDoc.GetLoadContext(); if (loadContext && mExtension && !mExtension->CanAccessContext(loadContext)) { return false; } if (loadContext && !mOriginAttributesPatterns.IsNull()) { OriginAttributes docShellAttrs; loadContext->GetOriginAttributes(docShellAttrs); bool patternMatch = false; for (const auto& pattern : mOriginAttributesPatterns.Value()) { if (pattern.Matches(docShellAttrs)) { patternMatch = true; break; } } if (!patternMatch) { return false; } } // TODO bug 1411641: we should account for precursorPrincipal if // match_origin_as_fallback is specified (see also bug 1853411). if (!mMatchAboutBlank && aDoc.URL().InheritsPrincipal()) { return false; } // Top-level about:blank is a special case. Unlike about:blank frames/windows // opened by web pages, these do not have an origin that could be matched by // a match pattern (they have a null principal instead). To allow extensions // that intend to run scripts "everywhere", consider the document matched if // the match pattern describe a very broad pattern (such as ""). if (mMatchAboutBlank && aDoc.IsTopLevel() && (aDoc.URL().Spec().EqualsLiteral("about:blank") || aDoc.URL().Scheme() == nsGkAtoms::data) && aDoc.Principal() && aDoc.Principal()->GetIsNullPrincipal()) { if (StaticPrefs::extensions_script_about_blank_without_permission()) { return true; } if (mHasActiveTabPermission) { return true; } if (mMatches->MatchesAllWebUrls() && mIncludeGlobs.IsNull()) { // When mIncludeGlobs is present, mMatches does not necessarily match // everything (except possibly if include_globs is just ["*"]). So we // only match if mMatches is present without mIncludeGlobs. return true; } // Null principal is never going to match, so we may as well return now. return false; } if (mRestricted && WebExtensionPolicy::IsRestrictedDoc(aDoc)) { return false; } if (mRestricted && mExtension && mExtension->QuarantinedFromDoc(aDoc)) { return false; } auto& urlinfo = aDoc.PrincipalURL(); if (mExtension && mExtension->ManifestVersion() >= 3) { // In MV3, activeTab only allows access to same-origin iframes. if (mHasActiveTabPermission && aDoc.IsSameOriginWithTop() && MatchPattern::MatchesAllURLs(urlinfo)) { return true; } } else { if (mHasActiveTabPermission && aDoc.ShouldMatchActiveTabPermission() && MatchPattern::MatchesAllURLs(urlinfo)) { return true; } } return MatchesURI(urlinfo, aIgnorePermissions); } bool MozDocumentMatcher::MatchesURI(const URLInfo& aURL, bool aIgnorePermissions) const { MOZ_ASSERT((!mRestricted && !mCheckPermissions) || mExtension); if (!mMatches->Matches(aURL)) { return false; } if (mExcludeMatches && mExcludeMatches->Matches(aURL)) { return false; } if (!mIncludeGlobs.IsNull() && !mIncludeGlobs.Value().Matches(aURL.CSpec())) { return false; } if (!mExcludeGlobs.IsNull() && mExcludeGlobs.Value().Matches(aURL.CSpec())) { return false; } if (mRestricted && WebExtensionPolicy::IsRestrictedURI(aURL)) { return false; } if (mRestricted && mExtension->QuarantinedFromURI(aURL)) { return false; } if (mCheckPermissions && !aIgnorePermissions && !mExtension->CanAccessURI(aURL, false, false, true)) { return false; } return true; } bool MozDocumentMatcher::MatchesWindowGlobal(WindowGlobalChild& aWindow, bool aIgnorePermissions) const { if (aWindow.IsClosed() || !aWindow.IsCurrentGlobal()) { return false; } nsGlobalWindowInner* inner = aWindow.GetWindowGlobal(); if (!inner || !inner->GetDocShell()) { return false; } return Matches(inner->GetOuterWindow(), aIgnorePermissions); } void MozDocumentMatcher::GetOriginAttributesPatterns( JSContext* aCx, JS::MutableHandle aVal, ErrorResult& aError) const { if (!ToJSValue(aCx, mOriginAttributesPatterns, aVal)) { aError.NoteJSContextException(aCx); } } JSObject* MozDocumentMatcher::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return MozDocumentMatcher_Binding::Wrap(aCx, this, aGivenProto); } JSObject* WebExtensionContentScript::WrapObject( JSContext* aCx, JS::Handle aGivenProto) { return WebExtensionContentScript_Binding::Wrap(aCx, this, aGivenProto); } NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MozDocumentMatcher, mMatches, mExcludeMatches, mExtension) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MozDocumentMatcher) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(MozDocumentMatcher) NS_IMPL_CYCLE_COLLECTING_RELEASE(MozDocumentMatcher) /***************************************************************************** * MozDocumentObserver *****************************************************************************/ /* static */ already_AddRefed DocumentObserver::Constructor( GlobalObject& aGlobal, dom::MozDocumentCallback& aCallbacks) { RefPtr matcher = new DocumentObserver(aGlobal.GetAsSupports(), aCallbacks); return matcher.forget(); } void DocumentObserver::Observe( const dom::Sequence>& matchers, ErrorResult& aRv) { if (!EPS().RegisterObserver(*this)) { aRv.Throw(NS_ERROR_FAILURE); return; } mMatchers.Clear(); for (auto& matcher : matchers) { if (!mMatchers.AppendElement(matcher, fallible)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return; } } } void DocumentObserver::Disconnect() { Unused << EPS().UnregisterObserver(*this); } void DocumentObserver::NotifyMatch(MozDocumentMatcher& aMatcher, nsPIDOMWindowOuter* aWindow) { IgnoredErrorResult rv; mCallbacks->OnNewDocument( aMatcher, WindowProxyHolder(aWindow->GetBrowsingContext()), rv); } void DocumentObserver::NotifyMatch(MozDocumentMatcher& aMatcher, nsILoadInfo* aLoadInfo) { IgnoredErrorResult rv; mCallbacks->OnPreloadDocument(aMatcher, aLoadInfo, rv); } JSObject* DocumentObserver::WrapObject(JSContext* aCx, JS::Handle aGivenProto) { return MozDocumentObserver_Binding::Wrap(aCx, this, aGivenProto); } NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DocumentObserver, mCallbacks, mMatchers, mParent) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentObserver) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY(nsISupports) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(DocumentObserver) NS_IMPL_CYCLE_COLLECTING_RELEASE(DocumentObserver) /***************************************************************************** * DocInfo *****************************************************************************/ DocInfo::DocInfo(const URLInfo& aURL, nsILoadInfo* aLoadInfo) : mURL(aURL), mObj(AsVariant(aLoadInfo)) {} DocInfo::DocInfo(nsPIDOMWindowOuter* aWindow) : mURL(aWindow->GetDocumentURI()), mObj(AsVariant(aWindow)) {} bool DocInfo::IsTopLevel() const { if (mIsTopLevel.isNothing()) { struct Matcher { bool operator()(Window aWin) { return aWin->GetBrowsingContext()->IsTop(); } bool operator()(LoadInfo aLoadInfo) { return aLoadInfo->GetIsTopLevelLoad(); } }; mIsTopLevel.emplace(mObj.match(Matcher())); } return mIsTopLevel.ref(); } bool WindowShouldMatchActiveTab(nsPIDOMWindowOuter* aWin) { WindowContext* wc = aWin->GetCurrentInnerWindow()->GetWindowContext(); if (wc && wc->SameOriginWithTop()) { // If the frame is same-origin to top, accept the match regardless of // whether the frame was populated dynamically. return true; } for (; wc; wc = wc->GetParentWindowContext()) { BrowsingContext* bc = wc->GetBrowsingContext(); if (bc->IsTopContent()) { return true; } if (bc->CreatedDynamically() || !wc->GetIsOriginalFrameSource()) { return false; } } MOZ_ASSERT_UNREACHABLE("Should reach top content before end of loop"); return false; } bool DocInfo::ShouldMatchActiveTabPermission() const { struct Matcher { bool operator()(Window aWin) { return WindowShouldMatchActiveTab(aWin); } bool operator()(LoadInfo aLoadInfo) { return false; } }; return mObj.match(Matcher()); } bool DocInfo::IsSameOriginWithTop() const { struct Matcher { bool operator()(Window aWin) { WindowContext* wc = aWin->GetCurrentInnerWindow()->GetWindowContext(); return wc && wc->SameOriginWithTop(); } bool operator()(LoadInfo aLoadInfo) { return false; } }; return mObj.match(Matcher()); } uint64_t DocInfo::FrameID() const { if (mFrameID.isNothing()) { if (IsTopLevel()) { mFrameID.emplace(0); } else { struct Matcher { uint64_t operator()(Window aWin) { return aWin->GetBrowsingContext()->Id(); } uint64_t operator()(LoadInfo aLoadInfo) { return aLoadInfo->GetBrowsingContextID(); } }; mFrameID.emplace(mObj.match(Matcher())); } } return mFrameID.ref(); } nsIPrincipal* DocInfo::Principal() const { if (mPrincipal.isNothing()) { struct Matcher { explicit Matcher(const DocInfo& aThis) : mThis(aThis) {} const DocInfo& mThis; nsIPrincipal* operator()(Window aWin) { RefPtr doc = aWin->GetDoc(); return doc->NodePrincipal(); } nsIPrincipal* operator()(LoadInfo aLoadInfo) { if (!(mThis.URL().InheritsPrincipal() || aLoadInfo->GetForceInheritPrincipal())) { return nullptr; } if (auto principal = aLoadInfo->PrincipalToInherit()) { return principal; } return aLoadInfo->TriggeringPrincipal(); } }; mPrincipal.emplace(mObj.match(Matcher(*this))); } return mPrincipal.ref(); } const URLInfo& DocInfo::PrincipalURL() const { if (!(Principal() && Principal()->GetIsContentPrincipal())) { return URL(); } if (mPrincipalURL.isNothing()) { nsIPrincipal* prin = Principal(); auto* basePrin = BasePrincipal::Cast(prin); nsCOMPtr uri; if (NS_SUCCEEDED(basePrin->GetURI(getter_AddRefs(uri)))) { MOZ_DIAGNOSTIC_ASSERT(uri); mPrincipalURL.emplace(uri); } else { mPrincipalURL.emplace(URL()); } } return mPrincipalURL.ref(); } } // namespace extensions } // namespace mozilla