1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4  * You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "mozilla/ExtensionPolicyService.h"
7 #include "mozilla/extensions/DocumentObserver.h"
8 #include "mozilla/extensions/WebExtensionContentScript.h"
9 #include "mozilla/extensions/WebExtensionPolicy.h"
10 
11 #include "mozilla/AddonManagerWebAPI.h"
12 #include "mozilla/dom/WindowGlobalChild.h"
13 #include "mozilla/ResultExtensions.h"
14 #include "mozilla/StaticPrefs_extensions.h"
15 #include "nsContentUtils.h"
16 #include "nsEscape.h"
17 #include "nsIObserver.h"
18 #include "nsISubstitutingProtocolHandler.h"
19 #include "nsNetUtil.h"
20 #include "nsPrintfCString.h"
21 
22 namespace mozilla {
23 namespace extensions {
24 
25 using namespace dom;
26 
27 static const char kProto[] = "moz-extension";
28 
29 static const char kBackgroundPageHTMLStart[] =
30     "<!DOCTYPE html>\n\
31 <html>\n\
32   <head><meta charset=\"utf-8\"></head>\n\
33   <body>";
34 
35 static const char kBackgroundPageHTMLScript[] =
36     "\n\
37     <script type=\"text/javascript\" src=\"%s\"></script>";
38 
39 static const char kBackgroundPageHTMLEnd[] =
40     "\n\
41   </body>\n\
42 </html>";
43 
44 #define BASE_CSP_PREF_V2 "extensions.webextensions.base-content-security-policy"
45 #define DEFAULT_BASE_CSP_V2                                       \
46   "script-src 'self' https://* moz-extension: blob: filesystem: " \
47   "'unsafe-eval' 'unsafe-inline'; "                               \
48   "object-src 'self' https://* moz-extension: blob: filesystem:;"
49 
50 #define BASE_CSP_PREF_V3 \
51   "extensions.webextensions.base-content-security-policy.v3"
52 #define DEFAULT_BASE_CSP_V3                \
53   "script-src 'self'; object-src 'self'; " \
54   "style-src 'self'; worker-src 'self';"
55 
56 static const char kRestrictedDomainPref[] =
57     "extensions.webextensions.restrictedDomains";
58 
EPS()59 static inline ExtensionPolicyService& EPS() {
60   return ExtensionPolicyService::GetSingleton();
61 }
62 
Proto()63 static nsISubstitutingProtocolHandler* Proto() {
64   static nsCOMPtr<nsISubstitutingProtocolHandler> sHandler;
65 
66   if (MOZ_UNLIKELY(!sHandler)) {
67     nsCOMPtr<nsIIOService> ios = do_GetIOService();
68     MOZ_RELEASE_ASSERT(ios);
69 
70     nsCOMPtr<nsIProtocolHandler> handler;
71     ios->GetProtocolHandler(kProto, getter_AddRefs(handler));
72 
73     sHandler = do_QueryInterface(handler);
74     MOZ_RELEASE_ASSERT(sHandler);
75 
76     ClearOnShutdown(&sHandler);
77   }
78 
79   return sHandler;
80 }
81 
ParseGlobs(GlobalObject & aGlobal,Sequence<OwningMatchGlobOrString> aGlobs,nsTArray<RefPtr<MatchGlob>> & aResult,ErrorResult & aRv)82 bool ParseGlobs(GlobalObject& aGlobal, Sequence<OwningMatchGlobOrString> aGlobs,
83                 nsTArray<RefPtr<MatchGlob>>& aResult, ErrorResult& aRv) {
84   for (auto& elem : aGlobs) {
85     if (elem.IsMatchGlob()) {
86       aResult.AppendElement(elem.GetAsMatchGlob());
87     } else {
88       RefPtr<MatchGlob> glob =
89           MatchGlob::Constructor(aGlobal, elem.GetAsString(), true, aRv);
90       if (aRv.Failed()) {
91         return false;
92       }
93       aResult.AppendElement(glob);
94     }
95   }
96   return true;
97 }
98 
99 enum class ErrorBehavior {
100   CreateEmptyPattern,
101   Fail,
102 };
103 
ParseMatches(GlobalObject & aGlobal,const OwningMatchPatternSetOrStringSequence & aMatches,const MatchPatternOptions & aOptions,ErrorBehavior aErrorBehavior,ErrorResult & aRv)104 already_AddRefed<MatchPatternSet> ParseMatches(
105     GlobalObject& aGlobal,
106     const OwningMatchPatternSetOrStringSequence& aMatches,
107     const MatchPatternOptions& aOptions, ErrorBehavior aErrorBehavior,
108     ErrorResult& aRv) {
109   if (aMatches.IsMatchPatternSet()) {
110     return do_AddRef(aMatches.GetAsMatchPatternSet().get());
111   }
112 
113   const auto& strings = aMatches.GetAsStringSequence();
114 
115   nsTArray<OwningStringOrMatchPattern> patterns;
116   if (!patterns.SetCapacity(strings.Length(), fallible)) {
117     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
118     return nullptr;
119   }
120 
121   for (auto& string : strings) {
122     OwningStringOrMatchPattern elt;
123     elt.SetAsString() = string;
124     patterns.AppendElement(elt);
125   }
126 
127   RefPtr<MatchPatternSet> result =
128       MatchPatternSet::Constructor(aGlobal, patterns, aOptions, aRv);
129 
130   if (aRv.Failed() && aErrorBehavior == ErrorBehavior::CreateEmptyPattern) {
131     aRv.SuppressException();
132     result = MatchPatternSet::Constructor(aGlobal, {}, aOptions, aRv);
133   }
134 
135   return result.forget();
136 }
137 
WebAccessibleResource(GlobalObject & aGlobal,const WebAccessibleResourceInit & aInit,ErrorResult & aRv)138 WebAccessibleResource::WebAccessibleResource(
139     GlobalObject& aGlobal, const WebAccessibleResourceInit& aInit,
140     ErrorResult& aRv) {
141   ParseGlobs(aGlobal, aInit.mResources, mWebAccessiblePaths, aRv);
142   if (aRv.Failed()) {
143     return;
144   }
145 
146   if (aInit.mMatches.WasPassed()) {
147     MatchPatternOptions options;
148     options.mRestrictSchemes = true;
149     mMatches = ParseMatches(aGlobal, aInit.mMatches.Value(), options,
150                             ErrorBehavior::CreateEmptyPattern, aRv);
151   }
152 }
153 
154 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAccessibleResource)
NS_INTERFACE_MAP_ENTRY(nsISupports)155   NS_INTERFACE_MAP_ENTRY(nsISupports)
156 NS_INTERFACE_MAP_END
157 
158 NS_IMPL_CYCLE_COLLECTION(WebAccessibleResource)
159 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebAccessibleResource)
160 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebAccessibleResource)
161 
162 /*****************************************************************************
163  * WebExtensionPolicy
164  *****************************************************************************/
165 
166 WebExtensionPolicy::WebExtensionPolicy(GlobalObject& aGlobal,
167                                        const WebExtensionInit& aInit,
168                                        ErrorResult& aRv)
169     : mId(NS_AtomizeMainThread(aInit.mId)),
170       mHostname(aInit.mMozExtensionHostname),
171       mName(aInit.mName),
172       mManifestVersion(aInit.mManifestVersion),
173       mExtensionPageCSP(aInit.mExtensionPageCSP),
174       mLocalizeCallback(aInit.mLocalizeCallback),
175       mIsPrivileged(aInit.mIsPrivileged),
176       mPermissions(new AtomSet(aInit.mPermissions)) {
177   MatchPatternOptions options;
178   options.mRestrictSchemes = !HasPermission(nsGkAtoms::mozillaAddons);
179 
180   mHostPermissions = ParseMatches(aGlobal, aInit.mAllowedOrigins, options,
181                                   ErrorBehavior::CreateEmptyPattern, aRv);
182   if (aRv.Failed()) {
183     return;
184   }
185 
186   if (!aInit.mBackgroundScripts.IsNull()) {
187     mBackgroundScripts.SetValue().AppendElements(
188         aInit.mBackgroundScripts.Value());
189   }
190 
191   if (!aInit.mBackgroundWorkerScript.IsEmpty()) {
192     mBackgroundWorkerScript.Assign(aInit.mBackgroundWorkerScript);
193   }
194 
195   InitializeBaseCSP();
196 
197   if (mExtensionPageCSP.IsVoid()) {
198     EPS().GetDefaultCSP(mExtensionPageCSP);
199   }
200 
201   mWebAccessibleResources.SetCapacity(aInit.mWebAccessibleResources.Length());
202   for (const auto& resourceInit : aInit.mWebAccessibleResources) {
203     RefPtr<WebAccessibleResource> resource =
204         new WebAccessibleResource(aGlobal, resourceInit, aRv);
205     if (aRv.Failed()) {
206       return;
207     }
208     mWebAccessibleResources.AppendElement(std::move(resource));
209   }
210 
211   mContentScripts.SetCapacity(aInit.mContentScripts.Length());
212   for (const auto& scriptInit : aInit.mContentScripts) {
213     // The activeTab permission is only for dynamically injected scripts,
214     // it cannot be used for declarative content scripts.
215     if (scriptInit.mHasActiveTabPermission) {
216       aRv.Throw(NS_ERROR_INVALID_ARG);
217       return;
218     }
219 
220     RefPtr<WebExtensionContentScript> contentScript =
221         new WebExtensionContentScript(aGlobal, *this, scriptInit, aRv);
222     if (aRv.Failed()) {
223       return;
224     }
225     mContentScripts.AppendElement(std::move(contentScript));
226   }
227 
228   if (aInit.mReadyPromise.WasPassed()) {
229     mReadyPromise = &aInit.mReadyPromise.Value();
230   }
231 
232   nsresult rv = NS_NewURI(getter_AddRefs(mBaseURI), aInit.mBaseURL);
233   if (NS_FAILED(rv)) {
234     aRv.Throw(rv);
235   }
236 }
237 
Constructor(GlobalObject & aGlobal,const WebExtensionInit & aInit,ErrorResult & aRv)238 already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::Constructor(
239     GlobalObject& aGlobal, const WebExtensionInit& aInit, ErrorResult& aRv) {
240   RefPtr<WebExtensionPolicy> policy =
241       new WebExtensionPolicy(aGlobal, aInit, aRv);
242   if (aRv.Failed()) {
243     return nullptr;
244   }
245   return policy.forget();
246 }
247 
InitializeBaseCSP()248 void WebExtensionPolicy::InitializeBaseCSP() {
249   if (mManifestVersion < 3) {
250     nsresult rv = Preferences::GetString(BASE_CSP_PREF_V2, mBaseCSP);
251     if (NS_FAILED(rv)) {
252       mBaseCSP.AssignLiteral(DEFAULT_BASE_CSP_V2);
253     }
254     return;
255   }
256   // Version 3 or higher.
257   nsresult rv = Preferences::GetString(BASE_CSP_PREF_V3, mBaseCSP);
258   if (NS_FAILED(rv)) {
259     mBaseCSP.AssignLiteral(DEFAULT_BASE_CSP_V3);
260   }
261 }
262 
263 /* static */
GetActiveExtensions(dom::GlobalObject & aGlobal,nsTArray<RefPtr<WebExtensionPolicy>> & aResults)264 void WebExtensionPolicy::GetActiveExtensions(
265     dom::GlobalObject& aGlobal,
266     nsTArray<RefPtr<WebExtensionPolicy>>& aResults) {
267   EPS().GetAll(aResults);
268 }
269 
270 /* static */
GetByID(dom::GlobalObject & aGlobal,const nsAString & aID)271 already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::GetByID(
272     dom::GlobalObject& aGlobal, const nsAString& aID) {
273   return do_AddRef(EPS().GetByID(aID));
274 }
275 
276 /* static */
GetByHostname(dom::GlobalObject & aGlobal,const nsACString & aHostname)277 already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::GetByHostname(
278     dom::GlobalObject& aGlobal, const nsACString& aHostname) {
279   return do_AddRef(EPS().GetByHost(aHostname));
280 }
281 
282 /* static */
GetByURI(dom::GlobalObject & aGlobal,nsIURI * aURI)283 already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::GetByURI(
284     dom::GlobalObject& aGlobal, nsIURI* aURI) {
285   return do_AddRef(EPS().GetByURL(aURI));
286 }
287 
SetActive(bool aActive,ErrorResult & aRv)288 void WebExtensionPolicy::SetActive(bool aActive, ErrorResult& aRv) {
289   if (aActive == mActive) {
290     return;
291   }
292 
293   bool ok = aActive ? Enable() : Disable();
294 
295   if (!ok) {
296     aRv.Throw(NS_ERROR_UNEXPECTED);
297   }
298 }
299 
Enable()300 bool WebExtensionPolicy::Enable() {
301   MOZ_ASSERT(!mActive);
302 
303   if (!EPS().RegisterExtension(*this)) {
304     return false;
305   }
306 
307   if (XRE_IsParentProcess()) {
308     // Reserve a BrowsingContextGroup for use by this WebExtensionPolicy.
309     RefPtr<BrowsingContextGroup> group = BrowsingContextGroup::Create();
310     mBrowsingContextGroup = group->MakeKeepAlivePtr();
311   }
312 
313   Unused << Proto()->SetSubstitution(MozExtensionHostname(), mBaseURI);
314 
315   mActive = true;
316   return true;
317 }
318 
Disable()319 bool WebExtensionPolicy::Disable() {
320   MOZ_ASSERT(mActive);
321   MOZ_ASSERT(EPS().GetByID(Id()) == this);
322 
323   if (!EPS().UnregisterExtension(*this)) {
324     return false;
325   }
326 
327   if (XRE_IsParentProcess()) {
328     // Clear our BrowsingContextGroup reference. A new instance will be created
329     // when the extension is next activated.
330     mBrowsingContextGroup = nullptr;
331   }
332 
333   Unused << Proto()->SetSubstitution(MozExtensionHostname(), nullptr);
334 
335   mActive = false;
336   return true;
337 }
338 
GetURL(const nsAString & aPath,nsAString & aResult,ErrorResult & aRv) const339 void WebExtensionPolicy::GetURL(const nsAString& aPath, nsAString& aResult,
340                                 ErrorResult& aRv) const {
341   auto result = GetURL(aPath);
342   if (result.isOk()) {
343     aResult = result.unwrap();
344   } else {
345     aRv.Throw(result.unwrapErr());
346   }
347 }
348 
GetURL(const nsAString & aPath) const349 Result<nsString, nsresult> WebExtensionPolicy::GetURL(
350     const nsAString& aPath) const {
351   nsPrintfCString spec("%s://%s/", kProto, mHostname.get());
352 
353   nsCOMPtr<nsIURI> uri;
354   MOZ_TRY(NS_NewURI(getter_AddRefs(uri), spec));
355 
356   MOZ_TRY(uri->Resolve(NS_ConvertUTF16toUTF8(aPath), spec));
357 
358   return NS_ConvertUTF8toUTF16(spec);
359 }
360 
RegisterContentScript(WebExtensionContentScript & script,ErrorResult & aRv)361 void WebExtensionPolicy::RegisterContentScript(
362     WebExtensionContentScript& script, ErrorResult& aRv) {
363   // Raise an "invalid argument" error if the script is not related to
364   // the expected extension or if it is already registered.
365   if (script.mExtension != this || mContentScripts.Contains(&script)) {
366     aRv.Throw(NS_ERROR_INVALID_ARG);
367     return;
368   }
369 
370   RefPtr<WebExtensionContentScript> newScript = &script;
371 
372   if (!mContentScripts.AppendElement(std::move(newScript), fallible)) {
373     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
374     return;
375   }
376 
377   WebExtensionPolicy_Binding::ClearCachedContentScriptsValue(this);
378 }
379 
UnregisterContentScript(const WebExtensionContentScript & script,ErrorResult & aRv)380 void WebExtensionPolicy::UnregisterContentScript(
381     const WebExtensionContentScript& script, ErrorResult& aRv) {
382   if (script.mExtension != this || !mContentScripts.RemoveElement(&script)) {
383     aRv.Throw(NS_ERROR_INVALID_ARG);
384     return;
385   }
386 
387   WebExtensionPolicy_Binding::ClearCachedContentScriptsValue(this);
388 }
389 
CanAccessURI(const URLInfo & aURI,bool aExplicit,bool aCheckRestricted,bool aAllowFilePermission) const390 bool WebExtensionPolicy::CanAccessURI(const URLInfo& aURI, bool aExplicit,
391                                       bool aCheckRestricted,
392                                       bool aAllowFilePermission) const {
393   return (!aCheckRestricted || !IsRestrictedURI(aURI)) && mHostPermissions &&
394          mHostPermissions->Matches(aURI, aExplicit) &&
395          (aURI.Scheme() != nsGkAtoms::file || aAllowFilePermission);
396 }
397 
InjectContentScripts(ErrorResult & aRv)398 void WebExtensionPolicy::InjectContentScripts(ErrorResult& aRv) {
399   nsresult rv = EPS().InjectContentScripts(this);
400   if (NS_FAILED(rv)) {
401     aRv.Throw(rv);
402   }
403 }
404 
405 /* static */
UseRemoteWebExtensions(GlobalObject & aGlobal)406 bool WebExtensionPolicy::UseRemoteWebExtensions(GlobalObject& aGlobal) {
407   return EPS().UseRemoteExtensions();
408 }
409 
410 /* static */
IsExtensionProcess(GlobalObject & aGlobal)411 bool WebExtensionPolicy::IsExtensionProcess(GlobalObject& aGlobal) {
412   return EPS().IsExtensionProcess();
413 }
414 
415 /* static */
BackgroundServiceWorkerEnabled(GlobalObject & aGlobal)416 bool WebExtensionPolicy::BackgroundServiceWorkerEnabled(GlobalObject& aGlobal) {
417   return StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup();
418 }
419 
420 namespace {
421 /**
422  * Maintains a dynamically updated AtomSet based on the comma-separated
423  * values in the given string pref.
424  */
425 class AtomSetPref : public nsIObserver, public nsSupportsWeakReference {
426  public:
427   NS_DECL_ISUPPORTS
428   NS_DECL_NSIOBSERVER
429 
Create(const nsCString & aPref)430   static already_AddRefed<AtomSetPref> Create(const nsCString& aPref) {
431     RefPtr<AtomSetPref> self = new AtomSetPref(aPref.get());
432     Preferences::AddWeakObserver(self, aPref);
433     return self.forget();
434   }
435 
436   const AtomSet& Get() const;
437 
Contains(const nsAtom * aAtom) const438   bool Contains(const nsAtom* aAtom) const { return Get().Contains(aAtom); }
439 
440  protected:
441   virtual ~AtomSetPref() = default;
442 
AtomSetPref(const char * aPref)443   explicit AtomSetPref(const char* aPref) : mPref(aPref) {}
444 
445  private:
446   mutable RefPtr<AtomSet> mAtomSet;
447   const char* mPref;
448 };
449 
Get() const450 const AtomSet& AtomSetPref::Get() const {
451   if (!mAtomSet) {
452     nsAutoCString eltsString;
453     Unused << Preferences::GetCString(mPref, eltsString);
454 
455     AutoTArray<nsString, 32> elts;
456     for (const nsACString& elt : eltsString.Split(',')) {
457       elts.AppendElement(NS_ConvertUTF8toUTF16(elt));
458       elts.LastElement().StripWhitespace();
459     }
460     mAtomSet = new AtomSet(elts);
461   }
462 
463   return *mAtomSet;
464 }
465 
466 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)467 AtomSetPref::Observe(nsISupports* aSubject, const char* aTopic,
468                      const char16_t* aData) {
469   mAtomSet = nullptr;
470   return NS_OK;
471 }
472 
473 NS_IMPL_ISUPPORTS(AtomSetPref, nsIObserver, nsISupportsWeakReference)
474 };  // namespace
475 
476 /* static */
IsRestrictedDoc(const DocInfo & aDoc)477 bool WebExtensionPolicy::IsRestrictedDoc(const DocInfo& aDoc) {
478   // With the exception of top-level about:blank documents with null
479   // principals, we never match documents that have non-content principals,
480   // including those with null principals or system principals.
481   if (aDoc.Principal() && !aDoc.Principal()->GetIsContentPrincipal()) {
482     return true;
483   }
484 
485   return IsRestrictedURI(aDoc.PrincipalURL());
486 }
487 
488 /* static */
IsRestrictedURI(const URLInfo & aURI)489 bool WebExtensionPolicy::IsRestrictedURI(const URLInfo& aURI) {
490   static RefPtr<AtomSetPref> domains;
491   if (!domains) {
492     domains = AtomSetPref::Create(nsLiteralCString(kRestrictedDomainPref));
493     ClearOnShutdown(&domains);
494   }
495 
496   if (domains->Contains(aURI.HostAtom())) {
497     return true;
498   }
499 
500   if (AddonManagerWebAPI::IsValidSite(aURI.URI())) {
501     return true;
502   }
503 
504   return false;
505 }
506 
BackgroundPageHTML() const507 nsCString WebExtensionPolicy::BackgroundPageHTML() const {
508   nsCString result;
509 
510   if (mBackgroundScripts.IsNull()) {
511     result.SetIsVoid(true);
512     return result;
513   }
514 
515   result.AppendLiteral(kBackgroundPageHTMLStart);
516 
517   for (auto& script : mBackgroundScripts.Value()) {
518     nsCString escaped;
519     nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(script), escaped);
520 
521     result.AppendPrintf(kBackgroundPageHTMLScript, escaped.get());
522   }
523 
524   result.AppendLiteral(kBackgroundPageHTMLEnd);
525   return result;
526 }
527 
Localize(const nsAString & aInput,nsString & aOutput) const528 void WebExtensionPolicy::Localize(const nsAString& aInput,
529                                   nsString& aOutput) const {
530   RefPtr<WebExtensionLocalizeCallback> callback(mLocalizeCallback);
531   callback->Call(aInput, aOutput);
532 }
533 
WrapObject(JSContext * aCx,JS::HandleObject aGivenProto)534 JSObject* WebExtensionPolicy::WrapObject(JSContext* aCx,
535                                          JS::HandleObject aGivenProto) {
536   return WebExtensionPolicy_Binding::Wrap(aCx, this, aGivenProto);
537 }
538 
GetContentScripts(nsTArray<RefPtr<WebExtensionContentScript>> & aScripts) const539 void WebExtensionPolicy::GetContentScripts(
540     nsTArray<RefPtr<WebExtensionContentScript>>& aScripts) const {
541   aScripts.AppendElements(mContentScripts);
542 }
543 
PrivateBrowsingAllowed() const544 bool WebExtensionPolicy::PrivateBrowsingAllowed() const {
545   return HasPermission(nsGkAtoms::privateBrowsingAllowedPermission);
546 }
547 
CanAccessContext(nsILoadContext * aContext) const548 bool WebExtensionPolicy::CanAccessContext(nsILoadContext* aContext) const {
549   MOZ_ASSERT(aContext);
550   return PrivateBrowsingAllowed() || !aContext->UsePrivateBrowsing();
551 }
552 
CanAccessWindow(const dom::WindowProxyHolder & aWindow) const553 bool WebExtensionPolicy::CanAccessWindow(
554     const dom::WindowProxyHolder& aWindow) const {
555   if (PrivateBrowsingAllowed()) {
556     return true;
557   }
558   // match browsing mode with policy
559   nsIDocShell* docShell = aWindow.get()->GetDocShell();
560   nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
561   return !(loadContext && loadContext->UsePrivateBrowsing());
562 }
563 
GetReadyPromise(JSContext * aCx,JS::MutableHandleObject aResult) const564 void WebExtensionPolicy::GetReadyPromise(
565     JSContext* aCx, JS::MutableHandleObject aResult) const {
566   if (mReadyPromise) {
567     aResult.set(mReadyPromise->PromiseObj());
568   } else {
569     aResult.set(nullptr);
570   }
571 }
572 
GetBrowsingContextGroupId() const573 uint64_t WebExtensionPolicy::GetBrowsingContextGroupId() const {
574   MOZ_ASSERT(XRE_IsParentProcess() && mActive);
575   return mBrowsingContextGroup ? mBrowsingContextGroup->Id() : 0;
576 }
577 
GetBrowsingContextGroupId(ErrorResult & aRv)578 uint64_t WebExtensionPolicy::GetBrowsingContextGroupId(ErrorResult& aRv) {
579   if (XRE_IsParentProcess() && mActive) {
580     return GetBrowsingContextGroupId();
581   }
582   aRv.ThrowInvalidAccessError(
583       "browsingContextGroupId only available for active policies in the "
584       "parent process");
585   return 0;
586 }
587 
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(WebExtensionPolicy,mParent,mBrowsingContextGroup,mLocalizeCallback,mHostPermissions,mWebAccessibleResources,mContentScripts)588 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(
589     WebExtensionPolicy, mParent, mBrowsingContextGroup, mLocalizeCallback,
590     mHostPermissions, mWebAccessibleResources, mContentScripts)
591 
592 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebExtensionPolicy)
593   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
594   NS_INTERFACE_MAP_ENTRY(nsISupports)
595 NS_INTERFACE_MAP_END
596 
597 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebExtensionPolicy)
598 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebExtensionPolicy)
599 
600 /*****************************************************************************
601  * WebExtensionContentScript / MozDocumentMatcher
602  *****************************************************************************/
603 
604 /* static */
605 already_AddRefed<MozDocumentMatcher> MozDocumentMatcher::Constructor(
606     GlobalObject& aGlobal, const dom::MozDocumentMatcherInit& aInit,
607     ErrorResult& aRv) {
608   RefPtr<MozDocumentMatcher> matcher =
609       new MozDocumentMatcher(aGlobal, aInit, false, aRv);
610   if (aRv.Failed()) {
611     return nullptr;
612   }
613   return matcher.forget();
614 }
615 
616 /* static */
617 already_AddRefed<WebExtensionContentScript>
Constructor(GlobalObject & aGlobal,WebExtensionPolicy & aExtension,const ContentScriptInit & aInit,ErrorResult & aRv)618 WebExtensionContentScript::Constructor(GlobalObject& aGlobal,
619                                        WebExtensionPolicy& aExtension,
620                                        const ContentScriptInit& aInit,
621                                        ErrorResult& aRv) {
622   RefPtr<WebExtensionContentScript> script =
623       new WebExtensionContentScript(aGlobal, aExtension, aInit, aRv);
624   if (aRv.Failed()) {
625     return nullptr;
626   }
627   return script.forget();
628 }
629 
MozDocumentMatcher(GlobalObject & aGlobal,const dom::MozDocumentMatcherInit & aInit,bool aRestricted,ErrorResult & aRv)630 MozDocumentMatcher::MozDocumentMatcher(GlobalObject& aGlobal,
631                                        const dom::MozDocumentMatcherInit& aInit,
632                                        bool aRestricted, ErrorResult& aRv)
633     : mHasActiveTabPermission(aInit.mHasActiveTabPermission),
634       mRestricted(aRestricted),
635       mAllFrames(aInit.mAllFrames),
636       mFrameID(aInit.mFrameID),
637       mMatchAboutBlank(aInit.mMatchAboutBlank) {
638   MatchPatternOptions options;
639   options.mRestrictSchemes = mRestricted;
640 
641   mMatches = ParseMatches(aGlobal, aInit.mMatches, options,
642                           ErrorBehavior::CreateEmptyPattern, aRv);
643   if (aRv.Failed()) {
644     return;
645   }
646 
647   if (!aInit.mExcludeMatches.IsNull()) {
648     mExcludeMatches =
649         ParseMatches(aGlobal, aInit.mExcludeMatches.Value(), options,
650                      ErrorBehavior::CreateEmptyPattern, aRv);
651     if (aRv.Failed()) {
652       return;
653     }
654   }
655 
656   if (!aInit.mIncludeGlobs.IsNull()) {
657     if (!ParseGlobs(aGlobal, aInit.mIncludeGlobs.Value(),
658                     mIncludeGlobs.SetValue(), aRv)) {
659       return;
660     }
661   }
662 
663   if (!aInit.mExcludeGlobs.IsNull()) {
664     if (!ParseGlobs(aGlobal, aInit.mExcludeGlobs.Value(),
665                     mExcludeGlobs.SetValue(), aRv)) {
666       return;
667     }
668   }
669 }
670 
WebExtensionContentScript(GlobalObject & aGlobal,WebExtensionPolicy & aExtension,const ContentScriptInit & aInit,ErrorResult & aRv)671 WebExtensionContentScript::WebExtensionContentScript(
672     GlobalObject& aGlobal, WebExtensionPolicy& aExtension,
673     const ContentScriptInit& aInit, ErrorResult& aRv)
674     : MozDocumentMatcher(aGlobal, aInit,
675                          !aExtension.HasPermission(nsGkAtoms::mozillaAddons),
676                          aRv),
677       mRunAt(aInit.mRunAt) {
678   mCssPaths.Assign(aInit.mCssPaths);
679   mJsPaths.Assign(aInit.mJsPaths);
680   mExtension = &aExtension;
681 }
682 
Matches(const DocInfo & aDoc) const683 bool MozDocumentMatcher::Matches(const DocInfo& aDoc) const {
684   if (!mFrameID.IsNull()) {
685     if (aDoc.FrameID() != mFrameID.Value()) {
686       return false;
687     }
688   } else {
689     if (!mAllFrames && !aDoc.IsTopLevel()) {
690       return false;
691     }
692   }
693 
694   // match browsing mode with policy
695   nsCOMPtr<nsILoadContext> loadContext = aDoc.GetLoadContext();
696   if (loadContext && mExtension && !mExtension->CanAccessContext(loadContext)) {
697     return false;
698   }
699 
700   if (!mMatchAboutBlank && aDoc.URL().InheritsPrincipal()) {
701     return false;
702   }
703 
704   // Top-level about:blank is a special case. We treat it as a match if
705   // matchAboutBlank is true and it has the null principal. In all other
706   // cases, we test the URL of the principal that it inherits.
707   if (mMatchAboutBlank && aDoc.IsTopLevel() &&
708       (aDoc.URL().Spec().EqualsLiteral("about:blank") ||
709        aDoc.URL().Scheme() == nsGkAtoms::data) &&
710       aDoc.Principal() && aDoc.Principal()->GetIsNullPrincipal()) {
711     return true;
712   }
713 
714   if (mRestricted && mExtension->IsRestrictedDoc(aDoc)) {
715     return false;
716   }
717 
718   auto& urlinfo = aDoc.PrincipalURL();
719   if (mHasActiveTabPermission && aDoc.ShouldMatchActiveTabPermission() &&
720       MatchPattern::MatchesAllURLs(urlinfo)) {
721     return true;
722   }
723 
724   return MatchesURI(urlinfo);
725 }
726 
MatchesURI(const URLInfo & aURL) const727 bool MozDocumentMatcher::MatchesURI(const URLInfo& aURL) const {
728   if (!mMatches->Matches(aURL)) {
729     return false;
730   }
731 
732   if (mExcludeMatches && mExcludeMatches->Matches(aURL)) {
733     return false;
734   }
735 
736   if (!mIncludeGlobs.IsNull() && !mIncludeGlobs.Value().Matches(aURL.Spec())) {
737     return false;
738   }
739 
740   if (!mExcludeGlobs.IsNull() && mExcludeGlobs.Value().Matches(aURL.Spec())) {
741     return false;
742   }
743 
744   if (mRestricted && mExtension->IsRestrictedURI(aURL)) {
745     return false;
746   }
747 
748   return true;
749 }
750 
MatchesWindowGlobal(WindowGlobalChild & aWindow) const751 bool MozDocumentMatcher::MatchesWindowGlobal(WindowGlobalChild& aWindow) const {
752   if (aWindow.IsClosed() || !aWindow.IsCurrentGlobal()) {
753     return false;
754   }
755   nsGlobalWindowInner* inner = aWindow.GetWindowGlobal();
756   if (!inner || !inner->GetDocShell()) {
757     return false;
758   }
759   return Matches(inner->GetOuterWindow());
760 }
761 
WrapObject(JSContext * aCx,JS::HandleObject aGivenProto)762 JSObject* MozDocumentMatcher::WrapObject(JSContext* aCx,
763                                          JS::HandleObject aGivenProto) {
764   return MozDocumentMatcher_Binding::Wrap(aCx, this, aGivenProto);
765 }
766 
WrapObject(JSContext * aCx,JS::HandleObject aGivenProto)767 JSObject* WebExtensionContentScript::WrapObject(JSContext* aCx,
768                                                 JS::HandleObject aGivenProto) {
769   return WebExtensionContentScript_Binding::Wrap(aCx, this, aGivenProto);
770 }
771 
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MozDocumentMatcher,mMatches,mExcludeMatches,mIncludeGlobs,mExcludeGlobs,mExtension)772 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MozDocumentMatcher, mMatches,
773                                       mExcludeMatches, mIncludeGlobs,
774                                       mExcludeGlobs, mExtension)
775 
776 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MozDocumentMatcher)
777   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
778   NS_INTERFACE_MAP_ENTRY(nsISupports)
779 NS_INTERFACE_MAP_END
780 
781 NS_IMPL_CYCLE_COLLECTING_ADDREF(MozDocumentMatcher)
782 NS_IMPL_CYCLE_COLLECTING_RELEASE(MozDocumentMatcher)
783 
784 /*****************************************************************************
785  * MozDocumentObserver
786  *****************************************************************************/
787 
788 /* static */
789 already_AddRefed<DocumentObserver> DocumentObserver::Constructor(
790     GlobalObject& aGlobal, dom::MozDocumentCallback& aCallbacks) {
791   RefPtr<DocumentObserver> matcher =
792       new DocumentObserver(aGlobal.GetAsSupports(), aCallbacks);
793   return matcher.forget();
794 }
795 
Observe(const dom::Sequence<OwningNonNull<MozDocumentMatcher>> & matchers,ErrorResult & aRv)796 void DocumentObserver::Observe(
797     const dom::Sequence<OwningNonNull<MozDocumentMatcher>>& matchers,
798     ErrorResult& aRv) {
799   if (!EPS().RegisterObserver(*this)) {
800     aRv.Throw(NS_ERROR_FAILURE);
801     return;
802   }
803   mMatchers.Clear();
804   for (auto& matcher : matchers) {
805     if (!mMatchers.AppendElement(matcher, fallible)) {
806       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
807       return;
808     }
809   }
810 }
811 
Disconnect()812 void DocumentObserver::Disconnect() {
813   Unused << EPS().UnregisterObserver(*this);
814 }
815 
NotifyMatch(MozDocumentMatcher & aMatcher,nsPIDOMWindowOuter * aWindow)816 void DocumentObserver::NotifyMatch(MozDocumentMatcher& aMatcher,
817                                    nsPIDOMWindowOuter* aWindow) {
818   IgnoredErrorResult rv;
819   mCallbacks->OnNewDocument(
820       aMatcher, WindowProxyHolder(aWindow->GetBrowsingContext()), rv);
821 }
822 
NotifyMatch(MozDocumentMatcher & aMatcher,nsILoadInfo * aLoadInfo)823 void DocumentObserver::NotifyMatch(MozDocumentMatcher& aMatcher,
824                                    nsILoadInfo* aLoadInfo) {
825   IgnoredErrorResult rv;
826   mCallbacks->OnPreloadDocument(aMatcher, aLoadInfo, rv);
827 }
828 
WrapObject(JSContext * aCx,JS::HandleObject aGivenProto)829 JSObject* DocumentObserver::WrapObject(JSContext* aCx,
830                                        JS::HandleObject aGivenProto) {
831   return MozDocumentObserver_Binding::Wrap(aCx, this, aGivenProto);
832 }
833 
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DocumentObserver,mCallbacks,mMatchers,mParent)834 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DocumentObserver, mCallbacks, mMatchers,
835                                       mParent)
836 
837 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentObserver)
838   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
839   NS_INTERFACE_MAP_ENTRY(nsISupports)
840 NS_INTERFACE_MAP_END
841 
842 NS_IMPL_CYCLE_COLLECTING_ADDREF(DocumentObserver)
843 NS_IMPL_CYCLE_COLLECTING_RELEASE(DocumentObserver)
844 
845 /*****************************************************************************
846  * DocInfo
847  *****************************************************************************/
848 
849 DocInfo::DocInfo(const URLInfo& aURL, nsILoadInfo* aLoadInfo)
850     : mURL(aURL), mObj(AsVariant(aLoadInfo)) {}
851 
DocInfo(nsPIDOMWindowOuter * aWindow)852 DocInfo::DocInfo(nsPIDOMWindowOuter* aWindow)
853     : mURL(aWindow->GetDocumentURI()), mObj(AsVariant(aWindow)) {}
854 
IsTopLevel() const855 bool DocInfo::IsTopLevel() const {
856   if (mIsTopLevel.isNothing()) {
857     struct Matcher {
858       bool operator()(Window aWin) {
859         return aWin->GetBrowsingContext()->IsTop();
860       }
861       bool operator()(LoadInfo aLoadInfo) {
862         return aLoadInfo->GetIsTopLevelLoad();
863       }
864     };
865     mIsTopLevel.emplace(mObj.match(Matcher()));
866   }
867   return mIsTopLevel.ref();
868 }
869 
WindowShouldMatchActiveTab(nsPIDOMWindowOuter * aWin)870 bool WindowShouldMatchActiveTab(nsPIDOMWindowOuter* aWin) {
871   for (WindowContext* wc = aWin->GetCurrentInnerWindow()->GetWindowContext();
872        wc; wc = wc->GetParentWindowContext()) {
873     BrowsingContext* bc = wc->GetBrowsingContext();
874     if (bc->IsTopContent()) {
875       return true;
876     }
877 
878     if (bc->CreatedDynamically() || !wc->GetIsOriginalFrameSource()) {
879       return false;
880     }
881   }
882   MOZ_ASSERT_UNREACHABLE("Should reach top content before end of loop");
883   return false;
884 }
885 
ShouldMatchActiveTabPermission() const886 bool DocInfo::ShouldMatchActiveTabPermission() const {
887   struct Matcher {
888     bool operator()(Window aWin) { return WindowShouldMatchActiveTab(aWin); }
889     bool operator()(LoadInfo aLoadInfo) { return false; }
890   };
891   return mObj.match(Matcher());
892 }
893 
FrameID() const894 uint64_t DocInfo::FrameID() const {
895   if (mFrameID.isNothing()) {
896     if (IsTopLevel()) {
897       mFrameID.emplace(0);
898     } else {
899       struct Matcher {
900         uint64_t operator()(Window aWin) {
901           return aWin->GetBrowsingContext()->Id();
902         }
903         uint64_t operator()(LoadInfo aLoadInfo) {
904           return aLoadInfo->GetBrowsingContextID();
905         }
906       };
907       mFrameID.emplace(mObj.match(Matcher()));
908     }
909   }
910   return mFrameID.ref();
911 }
912 
Principal() const913 nsIPrincipal* DocInfo::Principal() const {
914   if (mPrincipal.isNothing()) {
915     struct Matcher {
916       explicit Matcher(const DocInfo& aThis) : mThis(aThis) {}
917       const DocInfo& mThis;
918 
919       nsIPrincipal* operator()(Window aWin) {
920         RefPtr<Document> doc = aWin->GetDoc();
921         return doc->NodePrincipal();
922       }
923       nsIPrincipal* operator()(LoadInfo aLoadInfo) {
924         if (!(mThis.URL().InheritsPrincipal() ||
925               aLoadInfo->GetForceInheritPrincipal())) {
926           return nullptr;
927         }
928         if (auto principal = aLoadInfo->PrincipalToInherit()) {
929           return principal;
930         }
931         return aLoadInfo->TriggeringPrincipal();
932       }
933     };
934     mPrincipal.emplace(mObj.match(Matcher(*this)));
935   }
936   return mPrincipal.ref();
937 }
938 
PrincipalURL() const939 const URLInfo& DocInfo::PrincipalURL() const {
940   if (!(Principal() && Principal()->GetIsContentPrincipal())) {
941     return URL();
942   }
943 
944   if (mPrincipalURL.isNothing()) {
945     nsIPrincipal* prin = Principal();
946     auto* basePrin = BasePrincipal::Cast(prin);
947     nsCOMPtr<nsIURI> uri;
948     if (NS_SUCCEEDED(basePrin->GetURI(getter_AddRefs(uri)))) {
949       MOZ_DIAGNOSTIC_ASSERT(uri);
950       mPrincipalURL.emplace(uri);
951     } else {
952       mPrincipalURL.emplace(URL());
953     }
954   }
955 
956   return mPrincipalURL.ref();
957 }
958 
959 }  // namespace extensions
960 }  // namespace mozilla
961