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