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       mTemporarilyInstalled(aInit.mTemporarilyInstalled),
177       mPermissions(new AtomSet(aInit.mPermissions)) {
178   MatchPatternOptions options;
179   options.mRestrictSchemes = !HasPermission(nsGkAtoms::mozillaAddons);
180 
181   mHostPermissions = ParseMatches(aGlobal, aInit.mAllowedOrigins, options,
182                                   ErrorBehavior::CreateEmptyPattern, aRv);
183   if (aRv.Failed()) {
184     return;
185   }
186 
187   if (!aInit.mBackgroundScripts.IsNull()) {
188     mBackgroundScripts.SetValue().AppendElements(
189         aInit.mBackgroundScripts.Value());
190   }
191 
192   if (!aInit.mBackgroundWorkerScript.IsEmpty()) {
193     mBackgroundWorkerScript.Assign(aInit.mBackgroundWorkerScript);
194   }
195 
196   InitializeBaseCSP();
197 
198   if (mExtensionPageCSP.IsVoid()) {
199     EPS().GetDefaultCSP(mExtensionPageCSP);
200   }
201 
202   mWebAccessibleResources.SetCapacity(aInit.mWebAccessibleResources.Length());
203   for (const auto& resourceInit : aInit.mWebAccessibleResources) {
204     RefPtr<WebAccessibleResource> resource =
205         new WebAccessibleResource(aGlobal, resourceInit, aRv);
206     if (aRv.Failed()) {
207       return;
208     }
209     mWebAccessibleResources.AppendElement(std::move(resource));
210   }
211 
212   mContentScripts.SetCapacity(aInit.mContentScripts.Length());
213   for (const auto& scriptInit : aInit.mContentScripts) {
214     // The activeTab permission is only for dynamically injected scripts,
215     // it cannot be used for declarative content scripts.
216     if (scriptInit.mHasActiveTabPermission) {
217       aRv.Throw(NS_ERROR_INVALID_ARG);
218       return;
219     }
220 
221     RefPtr<WebExtensionContentScript> contentScript =
222         new WebExtensionContentScript(aGlobal, *this, scriptInit, aRv);
223     if (aRv.Failed()) {
224       return;
225     }
226     mContentScripts.AppendElement(std::move(contentScript));
227   }
228 
229   if (aInit.mReadyPromise.WasPassed()) {
230     mReadyPromise = &aInit.mReadyPromise.Value();
231   }
232 
233   nsresult rv = NS_NewURI(getter_AddRefs(mBaseURI), aInit.mBaseURL);
234   if (NS_FAILED(rv)) {
235     aRv.Throw(rv);
236   }
237 }
238 
Constructor(GlobalObject & aGlobal,const WebExtensionInit & aInit,ErrorResult & aRv)239 already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::Constructor(
240     GlobalObject& aGlobal, const WebExtensionInit& aInit, ErrorResult& aRv) {
241   RefPtr<WebExtensionPolicy> policy =
242       new WebExtensionPolicy(aGlobal, aInit, aRv);
243   if (aRv.Failed()) {
244     return nullptr;
245   }
246   return policy.forget();
247 }
248 
InitializeBaseCSP()249 void WebExtensionPolicy::InitializeBaseCSP() {
250   if (mManifestVersion < 3) {
251     nsresult rv = Preferences::GetString(BASE_CSP_PREF_V2, mBaseCSP);
252     if (NS_FAILED(rv)) {
253       mBaseCSP.AssignLiteral(DEFAULT_BASE_CSP_V2);
254     }
255     return;
256   }
257   // Version 3 or higher.
258   nsresult rv = Preferences::GetString(BASE_CSP_PREF_V3, mBaseCSP);
259   if (NS_FAILED(rv)) {
260     mBaseCSP.AssignLiteral(DEFAULT_BASE_CSP_V3);
261   }
262 }
263 
264 /* static */
GetActiveExtensions(dom::GlobalObject & aGlobal,nsTArray<RefPtr<WebExtensionPolicy>> & aResults)265 void WebExtensionPolicy::GetActiveExtensions(
266     dom::GlobalObject& aGlobal,
267     nsTArray<RefPtr<WebExtensionPolicy>>& aResults) {
268   EPS().GetAll(aResults);
269 }
270 
271 /* static */
GetByID(dom::GlobalObject & aGlobal,const nsAString & aID)272 already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::GetByID(
273     dom::GlobalObject& aGlobal, const nsAString& aID) {
274   return do_AddRef(EPS().GetByID(aID));
275 }
276 
277 /* static */
GetByHostname(dom::GlobalObject & aGlobal,const nsACString & aHostname)278 already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::GetByHostname(
279     dom::GlobalObject& aGlobal, const nsACString& aHostname) {
280   return do_AddRef(EPS().GetByHost(aHostname));
281 }
282 
283 /* static */
GetByURI(dom::GlobalObject & aGlobal,nsIURI * aURI)284 already_AddRefed<WebExtensionPolicy> WebExtensionPolicy::GetByURI(
285     dom::GlobalObject& aGlobal, nsIURI* aURI) {
286   return do_AddRef(EPS().GetByURL(aURI));
287 }
288 
SetActive(bool aActive,ErrorResult & aRv)289 void WebExtensionPolicy::SetActive(bool aActive, ErrorResult& aRv) {
290   if (aActive == mActive) {
291     return;
292   }
293 
294   bool ok = aActive ? Enable() : Disable();
295 
296   if (!ok) {
297     aRv.Throw(NS_ERROR_UNEXPECTED);
298   }
299 }
300 
Enable()301 bool WebExtensionPolicy::Enable() {
302   MOZ_ASSERT(!mActive);
303 
304   if (!EPS().RegisterExtension(*this)) {
305     return false;
306   }
307 
308   if (XRE_IsParentProcess()) {
309     // Reserve a BrowsingContextGroup for use by this WebExtensionPolicy.
310     RefPtr<BrowsingContextGroup> group = BrowsingContextGroup::Create();
311     mBrowsingContextGroup = group->MakeKeepAlivePtr();
312   }
313 
314   Unused << Proto()->SetSubstitution(MozExtensionHostname(), mBaseURI);
315 
316   mActive = true;
317   return true;
318 }
319 
Disable()320 bool WebExtensionPolicy::Disable() {
321   MOZ_ASSERT(mActive);
322   MOZ_ASSERT(EPS().GetByID(Id()) == this);
323 
324   if (!EPS().UnregisterExtension(*this)) {
325     return false;
326   }
327 
328   if (XRE_IsParentProcess()) {
329     // Clear our BrowsingContextGroup reference. A new instance will be created
330     // when the extension is next activated.
331     mBrowsingContextGroup = nullptr;
332   }
333 
334   Unused << Proto()->SetSubstitution(MozExtensionHostname(), nullptr);
335 
336   mActive = false;
337   return true;
338 }
339 
GetURL(const nsAString & aPath,nsAString & aResult,ErrorResult & aRv) const340 void WebExtensionPolicy::GetURL(const nsAString& aPath, nsAString& aResult,
341                                 ErrorResult& aRv) const {
342   auto result = GetURL(aPath);
343   if (result.isOk()) {
344     aResult = result.unwrap();
345   } else {
346     aRv.Throw(result.unwrapErr());
347   }
348 }
349 
GetURL(const nsAString & aPath) const350 Result<nsString, nsresult> WebExtensionPolicy::GetURL(
351     const nsAString& aPath) const {
352   nsPrintfCString spec("%s://%s/", kProto, mHostname.get());
353 
354   nsCOMPtr<nsIURI> uri;
355   MOZ_TRY(NS_NewURI(getter_AddRefs(uri), spec));
356 
357   MOZ_TRY(uri->Resolve(NS_ConvertUTF16toUTF8(aPath), spec));
358 
359   return NS_ConvertUTF8toUTF16(spec);
360 }
361 
RegisterContentScript(WebExtensionContentScript & script,ErrorResult & aRv)362 void WebExtensionPolicy::RegisterContentScript(
363     WebExtensionContentScript& script, ErrorResult& aRv) {
364   // Raise an "invalid argument" error if the script is not related to
365   // the expected extension or if it is already registered.
366   if (script.mExtension != this || mContentScripts.Contains(&script)) {
367     aRv.Throw(NS_ERROR_INVALID_ARG);
368     return;
369   }
370 
371   RefPtr<WebExtensionContentScript> newScript = &script;
372 
373   if (!mContentScripts.AppendElement(std::move(newScript), fallible)) {
374     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
375     return;
376   }
377 
378   WebExtensionPolicy_Binding::ClearCachedContentScriptsValue(this);
379 }
380 
UnregisterContentScript(const WebExtensionContentScript & script,ErrorResult & aRv)381 void WebExtensionPolicy::UnregisterContentScript(
382     const WebExtensionContentScript& script, ErrorResult& aRv) {
383   if (script.mExtension != this || !mContentScripts.RemoveElement(&script)) {
384     aRv.Throw(NS_ERROR_INVALID_ARG);
385     return;
386   }
387 
388   WebExtensionPolicy_Binding::ClearCachedContentScriptsValue(this);
389 }
390 
CanAccessURI(const URLInfo & aURI,bool aExplicit,bool aCheckRestricted,bool aAllowFilePermission) const391 bool WebExtensionPolicy::CanAccessURI(const URLInfo& aURI, bool aExplicit,
392                                       bool aCheckRestricted,
393                                       bool aAllowFilePermission) const {
394   return (!aCheckRestricted || !IsRestrictedURI(aURI)) && mHostPermissions &&
395          mHostPermissions->Matches(aURI, aExplicit) &&
396          (aURI.Scheme() != nsGkAtoms::file || aAllowFilePermission);
397 }
398 
InjectContentScripts(ErrorResult & aRv)399 void WebExtensionPolicy::InjectContentScripts(ErrorResult& aRv) {
400   nsresult rv = EPS().InjectContentScripts(this);
401   if (NS_FAILED(rv)) {
402     aRv.Throw(rv);
403   }
404 }
405 
406 /* static */
UseRemoteWebExtensions(GlobalObject & aGlobal)407 bool WebExtensionPolicy::UseRemoteWebExtensions(GlobalObject& aGlobal) {
408   return EPS().UseRemoteExtensions();
409 }
410 
411 /* static */
IsExtensionProcess(GlobalObject & aGlobal)412 bool WebExtensionPolicy::IsExtensionProcess(GlobalObject& aGlobal) {
413   return EPS().IsExtensionProcess();
414 }
415 
416 /* static */
BackgroundServiceWorkerEnabled(GlobalObject & aGlobal)417 bool WebExtensionPolicy::BackgroundServiceWorkerEnabled(GlobalObject& aGlobal) {
418   return StaticPrefs::extensions_backgroundServiceWorker_enabled_AtStartup();
419 }
420 
421 namespace {
422 /**
423  * Maintains a dynamically updated AtomSet based on the comma-separated
424  * values in the given string pref.
425  */
426 class AtomSetPref : public nsIObserver, public nsSupportsWeakReference {
427  public:
428   NS_DECL_ISUPPORTS
429   NS_DECL_NSIOBSERVER
430 
Create(const nsCString & aPref)431   static already_AddRefed<AtomSetPref> Create(const nsCString& aPref) {
432     RefPtr<AtomSetPref> self = new AtomSetPref(aPref.get());
433     Preferences::AddWeakObserver(self, aPref);
434     return self.forget();
435   }
436 
437   const AtomSet& Get() const;
438 
Contains(const nsAtom * aAtom) const439   bool Contains(const nsAtom* aAtom) const { return Get().Contains(aAtom); }
440 
441  protected:
442   virtual ~AtomSetPref() = default;
443 
AtomSetPref(const char * aPref)444   explicit AtomSetPref(const char* aPref) : mPref(aPref) {}
445 
446  private:
447   mutable RefPtr<AtomSet> mAtomSet;
448   const char* mPref;
449 };
450 
Get() const451 const AtomSet& AtomSetPref::Get() const {
452   if (!mAtomSet) {
453     nsAutoCString eltsString;
454     Unused << Preferences::GetCString(mPref, eltsString);
455 
456     AutoTArray<nsString, 32> elts;
457     for (const nsACString& elt : eltsString.Split(',')) {
458       elts.AppendElement(NS_ConvertUTF8toUTF16(elt));
459       elts.LastElement().StripWhitespace();
460     }
461     mAtomSet = new AtomSet(elts);
462   }
463 
464   return *mAtomSet;
465 }
466 
467 NS_IMETHODIMP
Observe(nsISupports * aSubject,const char * aTopic,const char16_t * aData)468 AtomSetPref::Observe(nsISupports* aSubject, const char* aTopic,
469                      const char16_t* aData) {
470   mAtomSet = nullptr;
471   return NS_OK;
472 }
473 
474 NS_IMPL_ISUPPORTS(AtomSetPref, nsIObserver, nsISupportsWeakReference)
475 };  // namespace
476 
477 /* static */
IsRestrictedDoc(const DocInfo & aDoc)478 bool WebExtensionPolicy::IsRestrictedDoc(const DocInfo& aDoc) {
479   // With the exception of top-level about:blank documents with null
480   // principals, we never match documents that have non-content principals,
481   // including those with null principals or system principals.
482   if (aDoc.Principal() && !aDoc.Principal()->GetIsContentPrincipal()) {
483     return true;
484   }
485 
486   return IsRestrictedURI(aDoc.PrincipalURL());
487 }
488 
489 /* static */
IsRestrictedURI(const URLInfo & aURI)490 bool WebExtensionPolicy::IsRestrictedURI(const URLInfo& aURI) {
491   static RefPtr<AtomSetPref> domains;
492   if (!domains) {
493     domains = AtomSetPref::Create(nsLiteralCString(kRestrictedDomainPref));
494     ClearOnShutdown(&domains);
495   }
496 
497   if (domains->Contains(aURI.HostAtom())) {
498     return true;
499   }
500 
501   if (AddonManagerWebAPI::IsValidSite(aURI.URI())) {
502     return true;
503   }
504 
505   return false;
506 }
507 
BackgroundPageHTML() const508 nsCString WebExtensionPolicy::BackgroundPageHTML() const {
509   nsCString result;
510 
511   if (mBackgroundScripts.IsNull()) {
512     result.SetIsVoid(true);
513     return result;
514   }
515 
516   result.AppendLiteral(kBackgroundPageHTMLStart);
517 
518   for (auto& script : mBackgroundScripts.Value()) {
519     nsCString escaped;
520     nsAppendEscapedHTML(NS_ConvertUTF16toUTF8(script), escaped);
521 
522     result.AppendPrintf(kBackgroundPageHTMLScript, escaped.get());
523   }
524 
525   result.AppendLiteral(kBackgroundPageHTMLEnd);
526   return result;
527 }
528 
Localize(const nsAString & aInput,nsString & aOutput) const529 void WebExtensionPolicy::Localize(const nsAString& aInput,
530                                   nsString& aOutput) const {
531   RefPtr<WebExtensionLocalizeCallback> callback(mLocalizeCallback);
532   callback->Call(aInput, aOutput);
533 }
534 
WrapObject(JSContext * aCx,JS::HandleObject aGivenProto)535 JSObject* WebExtensionPolicy::WrapObject(JSContext* aCx,
536                                          JS::HandleObject aGivenProto) {
537   return WebExtensionPolicy_Binding::Wrap(aCx, this, aGivenProto);
538 }
539 
GetContentScripts(nsTArray<RefPtr<WebExtensionContentScript>> & aScripts) const540 void WebExtensionPolicy::GetContentScripts(
541     nsTArray<RefPtr<WebExtensionContentScript>>& aScripts) const {
542   aScripts.AppendElements(mContentScripts);
543 }
544 
PrivateBrowsingAllowed() const545 bool WebExtensionPolicy::PrivateBrowsingAllowed() const {
546   return HasPermission(nsGkAtoms::privateBrowsingAllowedPermission);
547 }
548 
CanAccessContext(nsILoadContext * aContext) const549 bool WebExtensionPolicy::CanAccessContext(nsILoadContext* aContext) const {
550   MOZ_ASSERT(aContext);
551   return PrivateBrowsingAllowed() || !aContext->UsePrivateBrowsing();
552 }
553 
CanAccessWindow(const dom::WindowProxyHolder & aWindow) const554 bool WebExtensionPolicy::CanAccessWindow(
555     const dom::WindowProxyHolder& aWindow) const {
556   if (PrivateBrowsingAllowed()) {
557     return true;
558   }
559   // match browsing mode with policy
560   nsIDocShell* docShell = aWindow.get()->GetDocShell();
561   nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
562   return !(loadContext && loadContext->UsePrivateBrowsing());
563 }
564 
GetReadyPromise(JSContext * aCx,JS::MutableHandleObject aResult) const565 void WebExtensionPolicy::GetReadyPromise(
566     JSContext* aCx, JS::MutableHandleObject aResult) const {
567   if (mReadyPromise) {
568     aResult.set(mReadyPromise->PromiseObj());
569   } else {
570     aResult.set(nullptr);
571   }
572 }
573 
GetBrowsingContextGroupId() const574 uint64_t WebExtensionPolicy::GetBrowsingContextGroupId() const {
575   MOZ_ASSERT(XRE_IsParentProcess() && mActive);
576   return mBrowsingContextGroup ? mBrowsingContextGroup->Id() : 0;
577 }
578 
GetBrowsingContextGroupId(ErrorResult & aRv)579 uint64_t WebExtensionPolicy::GetBrowsingContextGroupId(ErrorResult& aRv) {
580   if (XRE_IsParentProcess() && mActive) {
581     return GetBrowsingContextGroupId();
582   }
583   aRv.ThrowInvalidAccessError(
584       "browsingContextGroupId only available for active policies in the "
585       "parent process");
586   return 0;
587 }
588 
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(WebExtensionPolicy,mParent,mBrowsingContextGroup,mLocalizeCallback,mHostPermissions,mWebAccessibleResources,mContentScripts)589 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_WEAK_PTR(
590     WebExtensionPolicy, mParent, mBrowsingContextGroup, mLocalizeCallback,
591     mHostPermissions, mWebAccessibleResources, mContentScripts)
592 
593 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebExtensionPolicy)
594   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
595   NS_INTERFACE_MAP_ENTRY(nsISupports)
596 NS_INTERFACE_MAP_END
597 
598 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebExtensionPolicy)
599 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebExtensionPolicy)
600 
601 /*****************************************************************************
602  * WebExtensionContentScript / MozDocumentMatcher
603  *****************************************************************************/
604 
605 /* static */
606 already_AddRefed<MozDocumentMatcher> MozDocumentMatcher::Constructor(
607     GlobalObject& aGlobal, const dom::MozDocumentMatcherInit& aInit,
608     ErrorResult& aRv) {
609   RefPtr<MozDocumentMatcher> matcher =
610       new MozDocumentMatcher(aGlobal, aInit, false, aRv);
611   if (aRv.Failed()) {
612     return nullptr;
613   }
614   return matcher.forget();
615 }
616 
617 /* static */
618 already_AddRefed<WebExtensionContentScript>
Constructor(GlobalObject & aGlobal,WebExtensionPolicy & aExtension,const ContentScriptInit & aInit,ErrorResult & aRv)619 WebExtensionContentScript::Constructor(GlobalObject& aGlobal,
620                                        WebExtensionPolicy& aExtension,
621                                        const ContentScriptInit& aInit,
622                                        ErrorResult& aRv) {
623   RefPtr<WebExtensionContentScript> script =
624       new WebExtensionContentScript(aGlobal, aExtension, aInit, aRv);
625   if (aRv.Failed()) {
626     return nullptr;
627   }
628   return script.forget();
629 }
630 
MozDocumentMatcher(GlobalObject & aGlobal,const dom::MozDocumentMatcherInit & aInit,bool aRestricted,ErrorResult & aRv)631 MozDocumentMatcher::MozDocumentMatcher(GlobalObject& aGlobal,
632                                        const dom::MozDocumentMatcherInit& aInit,
633                                        bool aRestricted, ErrorResult& aRv)
634     : mHasActiveTabPermission(aInit.mHasActiveTabPermission),
635       mRestricted(aRestricted),
636       mAllFrames(aInit.mAllFrames),
637       mFrameID(aInit.mFrameID),
638       mMatchAboutBlank(aInit.mMatchAboutBlank) {
639   MatchPatternOptions options;
640   options.mRestrictSchemes = mRestricted;
641 
642   mMatches = ParseMatches(aGlobal, aInit.mMatches, options,
643                           ErrorBehavior::CreateEmptyPattern, aRv);
644   if (aRv.Failed()) {
645     return;
646   }
647 
648   if (!aInit.mExcludeMatches.IsNull()) {
649     mExcludeMatches =
650         ParseMatches(aGlobal, aInit.mExcludeMatches.Value(), options,
651                      ErrorBehavior::CreateEmptyPattern, aRv);
652     if (aRv.Failed()) {
653       return;
654     }
655   }
656 
657   if (!aInit.mIncludeGlobs.IsNull()) {
658     if (!ParseGlobs(aGlobal, aInit.mIncludeGlobs.Value(),
659                     mIncludeGlobs.SetValue(), aRv)) {
660       return;
661     }
662   }
663 
664   if (!aInit.mExcludeGlobs.IsNull()) {
665     if (!ParseGlobs(aGlobal, aInit.mExcludeGlobs.Value(),
666                     mExcludeGlobs.SetValue(), aRv)) {
667       return;
668     }
669   }
670 
671   if (!aInit.mOriginAttributesPatterns.IsNull()) {
672     Sequence<OriginAttributesPattern>& arr =
673         mOriginAttributesPatterns.SetValue();
674     for (const auto& pattern : aInit.mOriginAttributesPatterns.Value()) {
675       if (!arr.AppendElement(OriginAttributesPattern(pattern), fallible)) {
676         aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
677         return;
678       }
679     }
680   }
681 }
682 
WebExtensionContentScript(GlobalObject & aGlobal,WebExtensionPolicy & aExtension,const ContentScriptInit & aInit,ErrorResult & aRv)683 WebExtensionContentScript::WebExtensionContentScript(
684     GlobalObject& aGlobal, WebExtensionPolicy& aExtension,
685     const ContentScriptInit& aInit, ErrorResult& aRv)
686     : MozDocumentMatcher(aGlobal, aInit,
687                          !aExtension.HasPermission(nsGkAtoms::mozillaAddons),
688                          aRv),
689       mRunAt(aInit.mRunAt) {
690   mCssPaths.Assign(aInit.mCssPaths);
691   mJsPaths.Assign(aInit.mJsPaths);
692   mExtension = &aExtension;
693 }
694 
Matches(const DocInfo & aDoc) const695 bool MozDocumentMatcher::Matches(const DocInfo& aDoc) const {
696   if (!mFrameID.IsNull()) {
697     if (aDoc.FrameID() != mFrameID.Value()) {
698       return false;
699     }
700   } else {
701     if (!mAllFrames && !aDoc.IsTopLevel()) {
702       return false;
703     }
704   }
705 
706   // match browsing mode with policy
707   nsCOMPtr<nsILoadContext> loadContext = aDoc.GetLoadContext();
708   if (loadContext && mExtension && !mExtension->CanAccessContext(loadContext)) {
709     return false;
710   }
711 
712   if (loadContext && !mOriginAttributesPatterns.IsNull()) {
713     OriginAttributes docShellAttrs;
714     loadContext->GetOriginAttributes(docShellAttrs);
715     bool patternMatch = false;
716     for (const auto& pattern : mOriginAttributesPatterns.Value()) {
717       if (pattern.Matches(docShellAttrs)) {
718         patternMatch = true;
719         break;
720       }
721     }
722     if (!patternMatch) {
723       return false;
724     }
725   }
726 
727   if (!mMatchAboutBlank && aDoc.URL().InheritsPrincipal()) {
728     return false;
729   }
730 
731   // Top-level about:blank is a special case. We treat it as a match if
732   // matchAboutBlank is true and it has the null principal. In all other
733   // cases, we test the URL of the principal that it inherits.
734   if (mMatchAboutBlank && aDoc.IsTopLevel() &&
735       (aDoc.URL().Spec().EqualsLiteral("about:blank") ||
736        aDoc.URL().Scheme() == nsGkAtoms::data) &&
737       aDoc.Principal() && aDoc.Principal()->GetIsNullPrincipal()) {
738     return true;
739   }
740 
741   if (mRestricted && mExtension->IsRestrictedDoc(aDoc)) {
742     return false;
743   }
744 
745   auto& urlinfo = aDoc.PrincipalURL();
746   if (mHasActiveTabPermission && aDoc.ShouldMatchActiveTabPermission() &&
747       MatchPattern::MatchesAllURLs(urlinfo)) {
748     return true;
749   }
750 
751   return MatchesURI(urlinfo);
752 }
753 
MatchesURI(const URLInfo & aURL) const754 bool MozDocumentMatcher::MatchesURI(const URLInfo& aURL) const {
755   if (!mMatches->Matches(aURL)) {
756     return false;
757   }
758 
759   if (mExcludeMatches && mExcludeMatches->Matches(aURL)) {
760     return false;
761   }
762 
763   if (!mIncludeGlobs.IsNull() && !mIncludeGlobs.Value().Matches(aURL.Spec())) {
764     return false;
765   }
766 
767   if (!mExcludeGlobs.IsNull() && mExcludeGlobs.Value().Matches(aURL.Spec())) {
768     return false;
769   }
770 
771   if (mRestricted && mExtension->IsRestrictedURI(aURL)) {
772     return false;
773   }
774 
775   return true;
776 }
777 
MatchesWindowGlobal(WindowGlobalChild & aWindow) const778 bool MozDocumentMatcher::MatchesWindowGlobal(WindowGlobalChild& aWindow) const {
779   if (aWindow.IsClosed() || !aWindow.IsCurrentGlobal()) {
780     return false;
781   }
782   nsGlobalWindowInner* inner = aWindow.GetWindowGlobal();
783   if (!inner || !inner->GetDocShell()) {
784     return false;
785   }
786   return Matches(inner->GetOuterWindow());
787 }
788 
GetOriginAttributesPatterns(JSContext * aCx,JS::MutableHandle<JS::Value> aVal,ErrorResult & aError) const789 void MozDocumentMatcher::GetOriginAttributesPatterns(
790     JSContext* aCx, JS::MutableHandle<JS::Value> aVal,
791     ErrorResult& aError) const {
792   if (!ToJSValue(aCx, mOriginAttributesPatterns, aVal)) {
793     aError.NoteJSContextException(aCx);
794   }
795 }
796 
WrapObject(JSContext * aCx,JS::HandleObject aGivenProto)797 JSObject* MozDocumentMatcher::WrapObject(JSContext* aCx,
798                                          JS::HandleObject aGivenProto) {
799   return MozDocumentMatcher_Binding::Wrap(aCx, this, aGivenProto);
800 }
801 
WrapObject(JSContext * aCx,JS::HandleObject aGivenProto)802 JSObject* WebExtensionContentScript::WrapObject(JSContext* aCx,
803                                                 JS::HandleObject aGivenProto) {
804   return WebExtensionContentScript_Binding::Wrap(aCx, this, aGivenProto);
805 }
806 
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MozDocumentMatcher,mMatches,mExcludeMatches,mIncludeGlobs,mExcludeGlobs,mExtension)807 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MozDocumentMatcher, mMatches,
808                                       mExcludeMatches, mIncludeGlobs,
809                                       mExcludeGlobs, mExtension)
810 
811 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MozDocumentMatcher)
812   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
813   NS_INTERFACE_MAP_ENTRY(nsISupports)
814 NS_INTERFACE_MAP_END
815 
816 NS_IMPL_CYCLE_COLLECTING_ADDREF(MozDocumentMatcher)
817 NS_IMPL_CYCLE_COLLECTING_RELEASE(MozDocumentMatcher)
818 
819 /*****************************************************************************
820  * MozDocumentObserver
821  *****************************************************************************/
822 
823 /* static */
824 already_AddRefed<DocumentObserver> DocumentObserver::Constructor(
825     GlobalObject& aGlobal, dom::MozDocumentCallback& aCallbacks) {
826   RefPtr<DocumentObserver> matcher =
827       new DocumentObserver(aGlobal.GetAsSupports(), aCallbacks);
828   return matcher.forget();
829 }
830 
Observe(const dom::Sequence<OwningNonNull<MozDocumentMatcher>> & matchers,ErrorResult & aRv)831 void DocumentObserver::Observe(
832     const dom::Sequence<OwningNonNull<MozDocumentMatcher>>& matchers,
833     ErrorResult& aRv) {
834   if (!EPS().RegisterObserver(*this)) {
835     aRv.Throw(NS_ERROR_FAILURE);
836     return;
837   }
838   mMatchers.Clear();
839   for (auto& matcher : matchers) {
840     if (!mMatchers.AppendElement(matcher, fallible)) {
841       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
842       return;
843     }
844   }
845 }
846 
Disconnect()847 void DocumentObserver::Disconnect() {
848   Unused << EPS().UnregisterObserver(*this);
849 }
850 
NotifyMatch(MozDocumentMatcher & aMatcher,nsPIDOMWindowOuter * aWindow)851 void DocumentObserver::NotifyMatch(MozDocumentMatcher& aMatcher,
852                                    nsPIDOMWindowOuter* aWindow) {
853   IgnoredErrorResult rv;
854   mCallbacks->OnNewDocument(
855       aMatcher, WindowProxyHolder(aWindow->GetBrowsingContext()), rv);
856 }
857 
NotifyMatch(MozDocumentMatcher & aMatcher,nsILoadInfo * aLoadInfo)858 void DocumentObserver::NotifyMatch(MozDocumentMatcher& aMatcher,
859                                    nsILoadInfo* aLoadInfo) {
860   IgnoredErrorResult rv;
861   mCallbacks->OnPreloadDocument(aMatcher, aLoadInfo, rv);
862 }
863 
WrapObject(JSContext * aCx,JS::HandleObject aGivenProto)864 JSObject* DocumentObserver::WrapObject(JSContext* aCx,
865                                        JS::HandleObject aGivenProto) {
866   return MozDocumentObserver_Binding::Wrap(aCx, this, aGivenProto);
867 }
868 
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DocumentObserver,mCallbacks,mMatchers,mParent)869 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DocumentObserver, mCallbacks, mMatchers,
870                                       mParent)
871 
872 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DocumentObserver)
873   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
874   NS_INTERFACE_MAP_ENTRY(nsISupports)
875 NS_INTERFACE_MAP_END
876 
877 NS_IMPL_CYCLE_COLLECTING_ADDREF(DocumentObserver)
878 NS_IMPL_CYCLE_COLLECTING_RELEASE(DocumentObserver)
879 
880 /*****************************************************************************
881  * DocInfo
882  *****************************************************************************/
883 
884 DocInfo::DocInfo(const URLInfo& aURL, nsILoadInfo* aLoadInfo)
885     : mURL(aURL), mObj(AsVariant(aLoadInfo)) {}
886 
DocInfo(nsPIDOMWindowOuter * aWindow)887 DocInfo::DocInfo(nsPIDOMWindowOuter* aWindow)
888     : mURL(aWindow->GetDocumentURI()), mObj(AsVariant(aWindow)) {}
889 
IsTopLevel() const890 bool DocInfo::IsTopLevel() const {
891   if (mIsTopLevel.isNothing()) {
892     struct Matcher {
893       bool operator()(Window aWin) {
894         return aWin->GetBrowsingContext()->IsTop();
895       }
896       bool operator()(LoadInfo aLoadInfo) {
897         return aLoadInfo->GetIsTopLevelLoad();
898       }
899     };
900     mIsTopLevel.emplace(mObj.match(Matcher()));
901   }
902   return mIsTopLevel.ref();
903 }
904 
WindowShouldMatchActiveTab(nsPIDOMWindowOuter * aWin)905 bool WindowShouldMatchActiveTab(nsPIDOMWindowOuter* aWin) {
906   for (WindowContext* wc = aWin->GetCurrentInnerWindow()->GetWindowContext();
907        wc; wc = wc->GetParentWindowContext()) {
908     BrowsingContext* bc = wc->GetBrowsingContext();
909     if (bc->IsTopContent()) {
910       return true;
911     }
912 
913     if (bc->CreatedDynamically() || !wc->GetIsOriginalFrameSource()) {
914       return false;
915     }
916   }
917   MOZ_ASSERT_UNREACHABLE("Should reach top content before end of loop");
918   return false;
919 }
920 
ShouldMatchActiveTabPermission() const921 bool DocInfo::ShouldMatchActiveTabPermission() const {
922   struct Matcher {
923     bool operator()(Window aWin) { return WindowShouldMatchActiveTab(aWin); }
924     bool operator()(LoadInfo aLoadInfo) { return false; }
925   };
926   return mObj.match(Matcher());
927 }
928 
FrameID() const929 uint64_t DocInfo::FrameID() const {
930   if (mFrameID.isNothing()) {
931     if (IsTopLevel()) {
932       mFrameID.emplace(0);
933     } else {
934       struct Matcher {
935         uint64_t operator()(Window aWin) {
936           return aWin->GetBrowsingContext()->Id();
937         }
938         uint64_t operator()(LoadInfo aLoadInfo) {
939           return aLoadInfo->GetBrowsingContextID();
940         }
941       };
942       mFrameID.emplace(mObj.match(Matcher()));
943     }
944   }
945   return mFrameID.ref();
946 }
947 
Principal() const948 nsIPrincipal* DocInfo::Principal() const {
949   if (mPrincipal.isNothing()) {
950     struct Matcher {
951       explicit Matcher(const DocInfo& aThis) : mThis(aThis) {}
952       const DocInfo& mThis;
953 
954       nsIPrincipal* operator()(Window aWin) {
955         RefPtr<Document> doc = aWin->GetDoc();
956         return doc->NodePrincipal();
957       }
958       nsIPrincipal* operator()(LoadInfo aLoadInfo) {
959         if (!(mThis.URL().InheritsPrincipal() ||
960               aLoadInfo->GetForceInheritPrincipal())) {
961           return nullptr;
962         }
963         if (auto principal = aLoadInfo->PrincipalToInherit()) {
964           return principal;
965         }
966         return aLoadInfo->TriggeringPrincipal();
967       }
968     };
969     mPrincipal.emplace(mObj.match(Matcher(*this)));
970   }
971   return mPrincipal.ref();
972 }
973 
PrincipalURL() const974 const URLInfo& DocInfo::PrincipalURL() const {
975   if (!(Principal() && Principal()->GetIsContentPrincipal())) {
976     return URL();
977   }
978 
979   if (mPrincipalURL.isNothing()) {
980     nsIPrincipal* prin = Principal();
981     auto* basePrin = BasePrincipal::Cast(prin);
982     nsCOMPtr<nsIURI> uri;
983     if (NS_SUCCEEDED(basePrin->GetURI(getter_AddRefs(uri)))) {
984       MOZ_DIAGNOSTIC_ASSERT(uri);
985       mPrincipalURL.emplace(uri);
986     } else {
987       mPrincipalURL.emplace(URL());
988     }
989   }
990 
991   return mPrincipalURL.ref();
992 }
993 
994 }  // namespace extensions
995 }  // namespace mozilla
996