Bug 1858509 add thread-safety annotations around MediaSourceDemuxer::mMonitor r=alwu
[gecko.git] / extensions / permissions / PermissionManager.cpp
blob9905104baffdb414fb240eaddf10d5b732721425
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/AbstractThread.h"
8 #include "mozilla/AppShutdown.h"
9 #include "mozilla/BasePrincipal.h"
10 #include "mozilla/ClearOnShutdown.h"
11 #include "mozilla/ContentPrincipal.h"
12 #include "mozilla/DebugOnly.h"
13 #include "mozilla/dom/CanonicalBrowsingContext.h"
14 #include "mozilla/dom/ContentParent.h"
15 #include "mozilla/dom/Document.h"
16 #include "mozilla/dom/WindowGlobalParent.h"
17 #include "mozilla/ExpandedPrincipal.h"
18 #include "mozilla/net/NeckoMessageUtils.h"
19 #include "mozilla/Permission.h"
20 #include "mozilla/PermissionManager.h"
21 #include "mozilla/Preferences.h"
22 #include "mozilla/ScopeExit.h"
23 #include "mozilla/StaticPrefs_permissions.h"
24 #include "mozilla/Telemetry.h"
26 #include "mozIStorageService.h"
27 #include "mozIStorageConnection.h"
28 #include "mozIStorageStatement.h"
29 #include "mozStorageCID.h"
31 #include "nsAppDirectoryServiceDefs.h"
32 #include "nsComponentManagerUtils.h"
33 #include "nsContentUtils.h"
34 #include "nsCRT.h"
35 #include "nsDebug.h"
36 #include "nsEffectiveTLDService.h"
37 #include "nsIConsoleService.h"
38 #include "nsIUserIdleService.h"
39 #include "nsIInputStream.h"
40 #include "nsINavHistoryService.h"
41 #include "nsIObserverService.h"
42 #include "nsIPrefBranch.h"
43 #include "nsIPrincipal.h"
44 #include "nsIURIMutator.h"
45 #include "nsIWritablePropertyBag2.h"
46 #include "nsReadLine.h"
47 #include "nsStringFwd.h"
48 #include "nsTHashSet.h"
49 #include "nsToolkitCompsCID.h"
51 using namespace mozilla::dom;
53 namespace mozilla {
55 #define PERMISSIONS_FILE_NAME "permissions.sqlite"
56 #define HOSTS_SCHEMA_VERSION 12
58 // Default permissions are read from a URL - this is the preference we read
59 // to find that URL. If not set, don't use any default permissions.
60 constexpr char kDefaultsUrlPrefName[] = "permissions.manager.defaultsUrl";
62 constexpr char kPermissionChangeNotification[] = PERM_CHANGE_NOTIFICATION;
64 // A special value for a permission ID that indicates the ID was loaded as
65 // a default value. These will never be written to the database, but may
66 // be overridden with an explicit permission (including UNKNOWN_ACTION)
67 constexpr int64_t cIDPermissionIsDefault = -1;
69 static StaticRefPtr<PermissionManager> gPermissionManager;
71 #define ENSURE_NOT_CHILD_PROCESS_(onError) \
72 PR_BEGIN_MACRO \
73 if (IsChildProcess()) { \
74 NS_ERROR("Cannot perform action in content process!"); \
75 onError \
76 } \
77 PR_END_MACRO
79 #define ENSURE_NOT_CHILD_PROCESS \
80 ENSURE_NOT_CHILD_PROCESS_({ return NS_ERROR_NOT_AVAILABLE; })
82 #define ENSURE_NOT_CHILD_PROCESS_NORET ENSURE_NOT_CHILD_PROCESS_(;)
84 #define EXPIRY_NOW PR_Now() / 1000
86 ////////////////////////////////////////////////////////////////////////////////
88 namespace {
90 bool IsChildProcess() { return XRE_IsContentProcess(); }
92 void LogToConsole(const nsAString& aMsg) {
93 nsCOMPtr<nsIConsoleService> console(
94 do_GetService("@mozilla.org/consoleservice;1"));
95 if (!console) {
96 NS_WARNING("Failed to log message to console.");
97 return;
100 nsAutoString msg(aMsg);
101 console->LogStringMessage(msg.get());
104 // NOTE: an empty string can be passed as aType - if it is this function will
105 // return "false" unconditionally.
106 bool HasDefaultPref(const nsACString& aType) {
107 // A list of permissions that can have a fallback default permission
108 // set under the permissions.default.* pref.
109 static const nsLiteralCString kPermissionsWithDefaults[] = {
110 "camera"_ns, "microphone"_ns, "geo"_ns, "desktop-notification"_ns,
111 "shortcuts"_ns};
113 if (!aType.IsEmpty()) {
114 for (const auto& perm : kPermissionsWithDefaults) {
115 if (perm.Equals(aType)) {
116 return true;
121 return false;
124 // These permissions are special permissions which must be transmitted to the
125 // content process before documents with their principals have loaded within
126 // that process.
128 // Permissions which are in this list are considered to have a "" permission
129 // key, even if their principal would not normally have that key.
130 static const nsLiteralCString kPreloadPermissions[] = {
131 // This permission is preloaded to support properly blocking service worker
132 // interception when a user has disabled storage for a specific site. Once
133 // service worker interception moves to the parent process this should be
134 // removed. See bug 1428130.
135 "cookie"_ns, "https-only-load-insecure"_ns};
137 // NOTE: nullptr can be passed as aType - if it is this function will return
138 // "false" unconditionally.
139 bool IsPreloadPermission(const nsACString& aType) {
140 if (!aType.IsEmpty()) {
141 for (const auto& perm : kPreloadPermissions) {
142 if (perm.Equals(aType)) {
143 return true;
148 return false;
151 // Array of permission types which should not be isolated by origin attributes,
152 // for user context and private browsing.
153 // Keep this array in sync with 'STRIPPED_PERMS' in
154 // 'test_permmanager_oa_strip.js'
155 // Currently only preloaded permissions are supported.
156 // This is because perms are sent to the content process in bulk by perm key.
157 // Non-preloaded, but OA stripped permissions would not be accessible by sites
158 // in private browsing / non-default user context.
159 static constexpr std::array<nsLiteralCString, 2> kStripOAPermissions = {
160 {"cookie"_ns, "https-only-load-insecure"_ns}};
162 bool IsOAForceStripPermission(const nsACString& aType) {
163 if (aType.IsEmpty()) {
164 return false;
166 for (const auto& perm : kStripOAPermissions) {
167 if (perm.Equals(aType)) {
168 return true;
171 return false;
174 // Array of permission prefixes which should be isolated only by site.
175 // These site-scoped permissions are stored under their site's principal.
176 // GetAllForPrincipal also needs to look for these especially.
177 static constexpr std::array<nsLiteralCString, 3> kSiteScopedPermissions = {
178 {"3rdPartyStorage^"_ns, "AllowStorageAccessRequest^"_ns,
179 "3rdPartyFrameStorage^"_ns}};
181 bool IsSiteScopedPermission(const nsACString& aType) {
182 if (aType.IsEmpty()) {
183 return false;
185 for (const auto& perm : kSiteScopedPermissions) {
186 if (aType.Length() >= perm.Length() &&
187 Substring(aType, 0, perm.Length()) == perm) {
188 return true;
191 return false;
194 // Array of permission type prefixes which have a secondary key encoded in the
195 // permission type. These permissions will not be stored in-process with the
196 // secondary key, but updates to them will cause "perm-changed" notifications on
197 // processes for that key.
198 static constexpr std::array<nsLiteralCString, 3> kSecondaryKeyedPermissions = {
199 {"3rdPartyStorage^"_ns, "AllowStorageAccessRequest^"_ns,
200 "3rdPartyFrameStorage^"_ns}};
202 bool GetSecondaryKey(const nsACString& aType, nsACString& aSecondaryKey) {
203 aSecondaryKey.Truncate();
204 if (aType.IsEmpty()) {
205 return false;
207 for (const auto& perm : kSecondaryKeyedPermissions) {
208 if (aType.Length() > perm.Length() &&
209 Substring(aType, 0, perm.Length()) == perm) {
210 aSecondaryKey = Substring(aType, perm.Length());
211 return true;
214 return false;
217 void OriginAppendOASuffix(OriginAttributes aOriginAttributes,
218 bool aForceStripOA, nsACString& aOrigin) {
219 PermissionManager::MaybeStripOriginAttributes(aForceStripOA,
220 aOriginAttributes);
222 nsAutoCString oaSuffix;
223 aOriginAttributes.CreateSuffix(oaSuffix);
224 aOrigin.Append(oaSuffix);
227 nsresult GetOriginFromPrincipal(nsIPrincipal* aPrincipal, bool aForceStripOA,
228 nsACString& aOrigin) {
229 nsresult rv = aPrincipal->GetOriginNoSuffix(aOrigin);
230 // The principal may belong to the about:blank content viewer, so this can be
231 // expected to fail.
232 if (NS_FAILED(rv)) {
233 return rv;
236 nsAutoCString suffix;
237 rv = aPrincipal->GetOriginSuffix(suffix);
238 NS_ENSURE_SUCCESS(rv, rv);
240 OriginAttributes attrs;
241 NS_ENSURE_TRUE(attrs.PopulateFromSuffix(suffix), NS_ERROR_FAILURE);
243 OriginAppendOASuffix(attrs, aForceStripOA, aOrigin);
245 return NS_OK;
248 // Returns the site of the principal, including OA, given a principal.
249 nsresult GetSiteFromPrincipal(nsIPrincipal* aPrincipal, bool aForceStripOA,
250 nsACString& aSite) {
251 nsCOMPtr<nsIURI> uri = aPrincipal->GetURI();
252 nsEffectiveTLDService* etld = nsEffectiveTLDService::GetInstance();
253 NS_ENSURE_TRUE(etld, NS_ERROR_FAILURE);
254 NS_ENSURE_TRUE(uri, NS_ERROR_FAILURE);
255 nsresult rv = etld->GetSite(uri, aSite);
257 // The principal may belong to the about:blank content viewer, so this can be
258 // expected to fail.
259 if (NS_FAILED(rv)) {
260 rv = aPrincipal->GetOrigin(aSite);
261 NS_ENSURE_SUCCESS(rv, rv);
262 return NS_OK;
265 nsAutoCString suffix;
266 rv = aPrincipal->GetOriginSuffix(suffix);
267 NS_ENSURE_SUCCESS(rv, rv);
269 OriginAttributes attrs;
270 NS_ENSURE_TRUE(attrs.PopulateFromSuffix(suffix), NS_ERROR_FAILURE);
272 OriginAppendOASuffix(attrs, aForceStripOA, aSite);
274 return NS_OK;
277 nsresult GetOriginFromURIAndOA(nsIURI* aURI,
278 const OriginAttributes* aOriginAttributes,
279 bool aForceStripOA, nsACString& aOrigin) {
280 nsAutoCString origin(aOrigin);
281 nsresult rv = ContentPrincipal::GenerateOriginNoSuffixFromURI(aURI, origin);
282 NS_ENSURE_SUCCESS(rv, rv);
284 OriginAppendOASuffix(*aOriginAttributes, aForceStripOA, origin);
286 aOrigin = origin;
288 return NS_OK;
291 nsresult GetPrincipalFromOrigin(const nsACString& aOrigin, bool aForceStripOA,
292 nsIPrincipal** aPrincipal) {
293 nsAutoCString originNoSuffix;
294 OriginAttributes attrs;
295 if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) {
296 return NS_ERROR_FAILURE;
299 PermissionManager::MaybeStripOriginAttributes(aForceStripOA, attrs);
301 nsCOMPtr<nsIURI> uri;
302 nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
303 NS_ENSURE_SUCCESS(rv, rv);
305 nsCOMPtr<nsIPrincipal> principal =
306 BasePrincipal::CreateContentPrincipal(uri, attrs);
307 principal.forget(aPrincipal);
308 return NS_OK;
311 nsresult GetPrincipal(nsIURI* aURI, bool aIsInIsolatedMozBrowserElement,
312 nsIPrincipal** aPrincipal) {
313 OriginAttributes attrs(aIsInIsolatedMozBrowserElement);
314 nsCOMPtr<nsIPrincipal> principal =
315 BasePrincipal::CreateContentPrincipal(aURI, attrs);
316 NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
318 principal.forget(aPrincipal);
319 return NS_OK;
322 nsresult GetPrincipal(nsIURI* aURI, nsIPrincipal** aPrincipal) {
323 OriginAttributes attrs;
324 nsCOMPtr<nsIPrincipal> principal =
325 BasePrincipal::CreateContentPrincipal(aURI, attrs);
326 NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
328 principal.forget(aPrincipal);
329 return NS_OK;
332 nsCString GetNextSubDomainForHost(const nsACString& aHost) {
333 nsCString subDomain;
334 nsresult rv =
335 nsEffectiveTLDService::GetInstance()->GetNextSubDomain(aHost, subDomain);
336 // We can fail if there is no more subdomain or if the host can't have a
337 // subdomain.
338 if (NS_FAILED(rv)) {
339 return ""_ns;
342 return subDomain;
345 // This function produces a nsIURI which is identical to the current
346 // nsIURI, except that it has one less subdomain segment. It returns
347 // `nullptr` if there are no more segments to remove.
348 already_AddRefed<nsIURI> GetNextSubDomainURI(nsIURI* aURI) {
349 nsAutoCString host;
350 nsresult rv = aURI->GetHost(host);
351 if (NS_FAILED(rv)) {
352 return nullptr;
355 nsCString domain = GetNextSubDomainForHost(host);
356 if (domain.IsEmpty()) {
357 return nullptr;
360 nsCOMPtr<nsIURI> uri;
361 rv = NS_MutateURI(aURI).SetHost(domain).Finalize(uri);
362 if (NS_FAILED(rv) || !uri) {
363 return nullptr;
366 return uri.forget();
369 nsresult UpgradeHostToOriginAndInsert(
370 const nsACString& aHost, const nsCString& aType, uint32_t aPermission,
371 uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime,
372 bool aIsInIsolatedMozBrowserElement,
373 std::function<nsresult(const nsACString& aOrigin, const nsCString& aType,
374 uint32_t aPermission, uint32_t aExpireType,
375 int64_t aExpireTime, int64_t aModificationTime)>&&
376 aCallback) {
377 if (aHost.EqualsLiteral("<file>")) {
378 // We no longer support the magic host <file>
379 NS_WARNING(
380 "The magic host <file> is no longer supported. "
381 "It is being removed from the permissions database.");
382 return NS_OK;
385 // First, we check to see if the host is a valid URI. If it is, it can be
386 // imported directly
387 nsCOMPtr<nsIURI> uri;
388 nsresult rv = NS_NewURI(getter_AddRefs(uri), aHost);
389 if (NS_SUCCEEDED(rv)) {
390 // It was previously possible to insert useless entries to your permissions
391 // database for URIs which have a null principal. This acts as a cleanup,
392 // getting rid of these useless database entries
393 if (uri->SchemeIs("moz-nullprincipal")) {
394 NS_WARNING("A moz-nullprincipal: permission is being discarded.");
395 return NS_OK;
398 nsCOMPtr<nsIPrincipal> principal;
399 rv = GetPrincipal(uri, aIsInIsolatedMozBrowserElement,
400 getter_AddRefs(principal));
401 NS_ENSURE_SUCCESS(rv, rv);
403 nsAutoCString origin;
404 rv = GetOriginFromPrincipal(principal, IsOAForceStripPermission(aType),
405 origin);
406 NS_ENSURE_SUCCESS(rv, rv);
408 aCallback(origin, aType, aPermission, aExpireType, aExpireTime,
409 aModificationTime);
410 return NS_OK;
413 // The user may use this host at non-standard ports or protocols, we can use
414 // their history to guess what ports and protocols we want to add permissions
415 // for. We find every URI which they have visited with this host (or a
416 // subdomain of this host), and try to add it as a principal.
417 bool foundHistory = false;
419 nsCOMPtr<nsINavHistoryService> histSrv =
420 do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
422 if (histSrv) {
423 nsCOMPtr<nsINavHistoryQuery> histQuery;
424 rv = histSrv->GetNewQuery(getter_AddRefs(histQuery));
425 NS_ENSURE_SUCCESS(rv, rv);
427 // Get the eTLD+1 of the domain
428 nsAutoCString eTLD1;
429 rv = nsEffectiveTLDService::GetInstance()->GetBaseDomainFromHost(aHost, 0,
430 eTLD1);
432 if (NS_FAILED(rv)) {
433 // If the lookup on the tldService for the base domain for the host
434 // failed, that means that we just want to directly use the host as the
435 // host name for the lookup.
436 eTLD1 = aHost;
439 // We want to only find history items for this particular eTLD+1, and
440 // subdomains
441 rv = histQuery->SetDomain(eTLD1);
442 NS_ENSURE_SUCCESS(rv, rv);
444 rv = histQuery->SetDomainIsHost(false);
445 NS_ENSURE_SUCCESS(rv, rv);
447 nsCOMPtr<nsINavHistoryQueryOptions> histQueryOpts;
448 rv = histSrv->GetNewQueryOptions(getter_AddRefs(histQueryOpts));
449 NS_ENSURE_SUCCESS(rv, rv);
451 // We want to get the URIs for every item in the user's history with the
452 // given host
453 rv =
454 histQueryOpts->SetResultType(nsINavHistoryQueryOptions::RESULTS_AS_URI);
455 NS_ENSURE_SUCCESS(rv, rv);
457 // We only search history, because searching both bookmarks and history
458 // is not supported, and history tends to be more comprehensive.
459 rv = histQueryOpts->SetQueryType(
460 nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY);
461 NS_ENSURE_SUCCESS(rv, rv);
463 // We include hidden URIs (such as those visited via iFrames) as they may
464 // have permissions too
465 rv = histQueryOpts->SetIncludeHidden(true);
466 NS_ENSURE_SUCCESS(rv, rv);
468 nsCOMPtr<nsINavHistoryResult> histResult;
469 rv = histSrv->ExecuteQuery(histQuery, histQueryOpts,
470 getter_AddRefs(histResult));
471 NS_ENSURE_SUCCESS(rv, rv);
473 nsCOMPtr<nsINavHistoryContainerResultNode> histResultContainer;
474 rv = histResult->GetRoot(getter_AddRefs(histResultContainer));
475 NS_ENSURE_SUCCESS(rv, rv);
477 rv = histResultContainer->SetContainerOpen(true);
478 NS_ENSURE_SUCCESS(rv, rv);
480 uint32_t childCount = 0;
481 rv = histResultContainer->GetChildCount(&childCount);
482 NS_ENSURE_SUCCESS(rv, rv);
484 nsTHashSet<nsCString> insertedOrigins;
485 for (uint32_t i = 0; i < childCount; i++) {
486 nsCOMPtr<nsINavHistoryResultNode> child;
487 histResultContainer->GetChild(i, getter_AddRefs(child));
488 if (NS_WARN_IF(NS_FAILED(rv))) continue;
490 uint32_t type;
491 rv = child->GetType(&type);
492 if (NS_WARN_IF(NS_FAILED(rv)) ||
493 type != nsINavHistoryResultNode::RESULT_TYPE_URI) {
494 NS_WARNING(
495 "Unexpected non-RESULT_TYPE_URI node in "
496 "UpgradeHostToOriginAndInsert()");
497 continue;
500 nsAutoCString uriSpec;
501 rv = child->GetUri(uriSpec);
502 if (NS_WARN_IF(NS_FAILED(rv))) continue;
504 nsCOMPtr<nsIURI> uri;
505 rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
506 if (NS_WARN_IF(NS_FAILED(rv))) continue;
508 // Use the provided host - this URI may be for a subdomain, rather than
509 // the host we care about.
510 rv = NS_MutateURI(uri).SetHost(aHost).Finalize(uri);
511 if (NS_WARN_IF(NS_FAILED(rv))) continue;
513 // We now have a URI which we can make a nsIPrincipal out of
514 nsCOMPtr<nsIPrincipal> principal;
515 rv = GetPrincipal(uri, aIsInIsolatedMozBrowserElement,
516 getter_AddRefs(principal));
517 if (NS_WARN_IF(NS_FAILED(rv))) continue;
519 nsAutoCString origin;
520 rv = GetOriginFromPrincipal(principal, IsOAForceStripPermission(aType),
521 origin);
522 if (NS_WARN_IF(NS_FAILED(rv))) continue;
524 // Ensure that we don't insert the same origin repeatedly
525 if (insertedOrigins.Contains(origin)) {
526 continue;
529 foundHistory = true;
530 rv = aCallback(origin, aType, aPermission, aExpireType, aExpireTime,
531 aModificationTime);
532 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Insert failed");
533 insertedOrigins.Insert(origin);
536 rv = histResultContainer->SetContainerOpen(false);
537 NS_ENSURE_SUCCESS(rv, rv);
540 // If we didn't find any origins for this host in the poermissions database,
541 // we can insert the default http:// and https:// permissions into the
542 // database. This has a relatively high likelihood of applying the permission
543 // to the correct origin.
544 if (!foundHistory) {
545 nsAutoCString hostSegment;
546 nsCOMPtr<nsIPrincipal> principal;
547 nsAutoCString origin;
549 // If this is an ipv6 URI, we need to surround it in '[', ']' before trying
550 // to parse it as a URI.
551 if (aHost.FindChar(':') != -1) {
552 hostSegment.AssignLiteral("[");
553 hostSegment.Append(aHost);
554 hostSegment.AppendLiteral("]");
555 } else {
556 hostSegment.Assign(aHost);
559 // http:// URI default
560 rv = NS_NewURI(getter_AddRefs(uri), "http://"_ns + hostSegment);
561 NS_ENSURE_SUCCESS(rv, rv);
563 rv = GetPrincipal(uri, aIsInIsolatedMozBrowserElement,
564 getter_AddRefs(principal));
565 NS_ENSURE_SUCCESS(rv, rv);
567 rv = GetOriginFromPrincipal(principal, IsOAForceStripPermission(aType),
568 origin);
569 NS_ENSURE_SUCCESS(rv, rv);
571 aCallback(origin, aType, aPermission, aExpireType, aExpireTime,
572 aModificationTime);
574 // https:// URI default
575 rv = NS_NewURI(getter_AddRefs(uri), "https://"_ns + hostSegment);
576 NS_ENSURE_SUCCESS(rv, rv);
578 rv = GetPrincipal(uri, aIsInIsolatedMozBrowserElement,
579 getter_AddRefs(principal));
580 NS_ENSURE_SUCCESS(rv, rv);
582 rv = GetOriginFromPrincipal(principal, IsOAForceStripPermission(aType),
583 origin);
584 NS_ENSURE_SUCCESS(rv, rv);
586 aCallback(origin, aType, aPermission, aExpireType, aExpireTime,
587 aModificationTime);
590 return NS_OK;
593 bool IsExpandedPrincipal(nsIPrincipal* aPrincipal) {
594 nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
595 return !!ep;
598 // We only want to persist permissions which don't have session or policy
599 // expiration.
600 bool IsPersistentExpire(uint32_t aExpire, const nsACString& aType) {
601 bool res = (aExpire != nsIPermissionManager::EXPIRE_SESSION &&
602 aExpire != nsIPermissionManager::EXPIRE_POLICY);
603 return res;
606 nsresult NotifySecondaryKeyPermissionUpdateInContentProcess(
607 const nsACString& aType, uint32_t aPermission,
608 const nsACString& aSecondaryKey, nsIPrincipal* aTopPrincipal) {
609 NS_ENSURE_ARG_POINTER(aTopPrincipal);
610 MOZ_ASSERT(XRE_IsParentProcess());
611 AutoTArray<RefPtr<BrowsingContextGroup>, 5> bcGroups;
612 BrowsingContextGroup::GetAllGroups(bcGroups);
613 for (const auto& bcGroup : bcGroups) {
614 for (const auto& topBC : bcGroup->Toplevels()) {
615 CanonicalBrowsingContext* topCBC = topBC->Canonical();
616 RefPtr<nsIURI> topURI = topCBC->GetCurrentURI();
617 if (!topURI) {
618 continue;
620 bool thirdParty;
621 nsresult rv = aTopPrincipal->IsThirdPartyURI(topURI, &thirdParty);
622 if (NS_FAILED(rv)) {
623 continue;
625 if (!thirdParty) {
626 AutoTArray<RefPtr<BrowsingContext>, 5> bcs;
627 topBC->GetAllBrowsingContextsInSubtree(bcs);
628 for (const auto& bc : bcs) {
629 CanonicalBrowsingContext* cbc = bc->Canonical();
630 ContentParent* cp = cbc->GetContentParent();
631 if (!cp) {
632 continue;
634 if (cp->NeedsSecondaryKeyPermissionsUpdate(aSecondaryKey)) {
635 WindowGlobalParent* wgp = cbc->GetCurrentWindowGlobal();
636 if (!wgp) {
637 continue;
639 bool success = wgp->SendNotifyPermissionChange(aType, aPermission);
640 Unused << NS_WARN_IF(!success);
646 return NS_OK;
649 } // namespace
651 ////////////////////////////////////////////////////////////////////////////////
653 PermissionManager::PermissionKey*
654 PermissionManager::PermissionKey::CreateFromPrincipal(nsIPrincipal* aPrincipal,
655 bool aForceStripOA,
656 bool aScopeToSite,
657 nsresult& aResult) {
658 nsAutoCString keyString;
659 if (aScopeToSite) {
660 aResult = GetSiteFromPrincipal(aPrincipal, aForceStripOA, keyString);
661 } else {
662 aResult = GetOriginFromPrincipal(aPrincipal, aForceStripOA, keyString);
664 if (NS_WARN_IF(NS_FAILED(aResult))) {
665 return nullptr;
667 return new PermissionKey(keyString);
670 PermissionManager::PermissionKey*
671 PermissionManager::PermissionKey::CreateFromURIAndOriginAttributes(
672 nsIURI* aURI, const OriginAttributes* aOriginAttributes, bool aForceStripOA,
673 nsresult& aResult) {
674 nsAutoCString origin;
675 aResult =
676 GetOriginFromURIAndOA(aURI, aOriginAttributes, aForceStripOA, origin);
677 if (NS_WARN_IF(NS_FAILED(aResult))) {
678 return nullptr;
681 return new PermissionKey(origin);
684 PermissionManager::PermissionKey*
685 PermissionManager::PermissionKey::CreateFromURI(nsIURI* aURI,
686 nsresult& aResult) {
687 nsAutoCString origin;
688 aResult = ContentPrincipal::GenerateOriginNoSuffixFromURI(aURI, origin);
689 if (NS_WARN_IF(NS_FAILED(aResult))) {
690 return nullptr;
693 return new PermissionKey(origin);
696 ////////////////////////////////////////////////////////////////////////////////
697 // PermissionManager Implementation
699 NS_IMPL_ISUPPORTS(PermissionManager, nsIPermissionManager, nsIObserver,
700 nsISupportsWeakReference, nsIAsyncShutdownBlocker)
702 PermissionManager::PermissionManager()
703 : mMonitor("PermissionManager::mMonitor"),
704 mState(eInitializing),
705 mMemoryOnlyDB(false),
706 mLargestID(0) {}
708 PermissionManager::~PermissionManager() {
709 // NOTE: Make sure to reject each of the promises in mPermissionKeyPromiseMap
710 // before destroying.
711 for (const auto& promise : mPermissionKeyPromiseMap.Values()) {
712 if (promise) {
713 promise->Reject(NS_ERROR_FAILURE, __func__);
716 mPermissionKeyPromiseMap.Clear();
718 if (mThread) {
719 mThread->Shutdown();
720 mThread = nullptr;
724 /* static */
725 StaticMutex PermissionManager::sCreationMutex;
727 // static
728 already_AddRefed<nsIPermissionManager> PermissionManager::GetXPCOMSingleton() {
729 // The lazy initialization could race.
730 StaticMutexAutoLock lock(sCreationMutex);
732 if (gPermissionManager) {
733 return do_AddRef(gPermissionManager);
736 // Create a new singleton PermissionManager.
737 // We AddRef only once since XPCOM has rules about the ordering of module
738 // teardowns - by the time our module destructor is called, it's too late to
739 // Release our members, since GC cycles have already been completed and
740 // would result in serious leaks.
741 // See bug 209571.
742 auto permManager = MakeRefPtr<PermissionManager>();
743 if (NS_SUCCEEDED(permManager->Init())) {
744 gPermissionManager = permManager.get();
745 return permManager.forget();
748 return nullptr;
751 // static
752 PermissionManager* PermissionManager::GetInstance() {
753 // TODO: There is a minimal chance that we can race here with a
754 // GetXPCOMSingleton call that did not yet set gPermissionManager.
755 // See bug 1745056.
756 if (!gPermissionManager) {
757 // Hand off the creation of the permission manager to GetXPCOMSingleton.
758 nsCOMPtr<nsIPermissionManager> permManager = GetXPCOMSingleton();
761 return gPermissionManager;
764 nsresult PermissionManager::Init() {
765 // If we are already shutting down, do not permit a creation.
766 // This must match the phase in GetAsyncShutdownBarrier.
767 if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) {
768 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
771 // If the 'permissions.memory_only' pref is set to true, then don't write any
772 // permission settings to disk, but keep them in a memory-only database.
773 mMemoryOnlyDB = Preferences::GetBool("permissions.memory_only", false);
775 nsresult rv;
776 nsCOMPtr<nsIPrefService> prefService =
777 do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
778 NS_ENSURE_SUCCESS(rv, rv);
780 rv = prefService->GetBranch("permissions.default.",
781 getter_AddRefs(mDefaultPrefBranch));
782 NS_ENSURE_SUCCESS(rv, rv);
784 if (IsChildProcess()) {
785 // Stop here; we don't need the DB in the child process. Instead we will be
786 // sent permissions as we need them by our parent process.
787 mState = eReady;
789 // We use ClearOnShutdown on the content process only because on the parent
790 // process we need to block the shutdown for the final closeDB() call.
791 ClearOnShutdown(&gPermissionManager);
792 return NS_OK;
795 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
796 if (observerService) {
797 observerService->AddObserver(this, "profile-do-change", true);
798 observerService->AddObserver(this, "testonly-reload-permissions-from-disk",
799 true);
802 if (XRE_IsParentProcess()) {
803 nsCOMPtr<nsIAsyncShutdownClient> asc = GetAsyncShutdownBarrier();
804 if (!asc) {
805 return NS_ERROR_NOT_AVAILABLE;
807 nsAutoString blockerName;
808 MOZ_ALWAYS_SUCCEEDS(GetName(blockerName));
810 nsresult rv = asc->AddBlocker(
811 this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, blockerName);
812 NS_ENSURE_SUCCESS(rv, rv);
815 AddIdleDailyMaintenanceJob();
817 MOZ_ASSERT(!mThread);
818 NS_ENSURE_SUCCESS(NS_NewNamedThread("Permission", getter_AddRefs(mThread)),
819 NS_ERROR_FAILURE);
821 PRThread* prThread;
822 MOZ_ALWAYS_SUCCEEDS(mThread->GetPRThread(&prThread));
823 MOZ_ASSERT(prThread);
825 mThreadBoundData.Transfer(prThread);
827 InitDB(false);
829 return NS_OK;
832 nsresult PermissionManager::OpenDatabase(nsIFile* aPermissionsFile) {
833 MOZ_ASSERT(!NS_IsMainThread());
834 auto data = mThreadBoundData.Access();
836 nsresult rv;
837 nsCOMPtr<mozIStorageService> storage =
838 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
839 if (!storage) {
840 return NS_ERROR_UNEXPECTED;
842 // cache a connection to the hosts database
843 if (mMemoryOnlyDB) {
844 rv = storage->OpenSpecialDatabase(
845 kMozStorageMemoryStorageKey, VoidCString(),
846 mozIStorageService::CONNECTION_DEFAULT, getter_AddRefs(data->mDBConn));
847 } else {
848 rv = storage->OpenDatabase(aPermissionsFile,
849 mozIStorageService::CONNECTION_DEFAULT,
850 getter_AddRefs(data->mDBConn));
852 return rv;
855 void PermissionManager::InitDB(bool aRemoveFile) {
856 mState = eInitializing;
859 MonitorAutoLock lock(mMonitor);
860 mReadEntries.Clear();
863 auto readyIfFailed = MakeScopeExit([&]() {
864 // ignore failure here, since it's non-fatal (we can run fine without
865 // persistent storage - e.g. if there's no profile).
866 // XXX should we tell the user about this?
867 mState = eReady;
870 if (!mPermissionsFile) {
871 nsresult rv = NS_GetSpecialDirectory(NS_APP_PERMISSION_PARENT_DIR,
872 getter_AddRefs(mPermissionsFile));
873 if (NS_FAILED(rv)) {
874 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
875 getter_AddRefs(mPermissionsFile));
876 if (NS_FAILED(rv)) {
877 return;
881 rv =
882 mPermissionsFile->AppendNative(nsLiteralCString(PERMISSIONS_FILE_NAME));
883 NS_ENSURE_SUCCESS_VOID(rv);
886 nsCOMPtr<nsIInputStream> defaultsInputStream = GetDefaultsInputStream();
888 RefPtr<PermissionManager> self = this;
889 mThread->Dispatch(NS_NewRunnableFunction(
890 "PermissionManager::InitDB", [self, aRemoveFile, defaultsInputStream] {
891 nsresult rv = self->TryInitDB(aRemoveFile, defaultsInputStream);
892 Unused << NS_WARN_IF(NS_FAILED(rv));
894 // This extra runnable calls EnsureReadCompleted to finialize the
895 // initialization. If there is something blocked by the monitor, it will
896 // be NOP.
897 NS_DispatchToMainThread(
898 NS_NewRunnableFunction("PermissionManager::InitDB-MainThread",
899 [self] { self->EnsureReadCompleted(); }));
901 self->mMonitor.Notify();
902 }));
904 readyIfFailed.release();
907 nsresult PermissionManager::TryInitDB(bool aRemoveFile,
908 nsIInputStream* aDefaultsInputStream) {
909 MOZ_ASSERT(!NS_IsMainThread());
911 MonitorAutoLock lock(mMonitor);
913 auto raii = MakeScopeExit([&]() {
914 if (aDefaultsInputStream) {
915 aDefaultsInputStream->Close();
918 mState = eDBInitialized;
921 auto data = mThreadBoundData.Access();
923 auto raiiFailure = MakeScopeExit([&]() {
924 if (data->mDBConn) {
925 DebugOnly<nsresult> rv = data->mDBConn->Close();
926 MOZ_ASSERT(NS_SUCCEEDED(rv));
927 data->mDBConn = nullptr;
931 nsresult rv;
933 if (aRemoveFile) {
934 bool exists = false;
935 rv = mPermissionsFile->Exists(&exists);
936 NS_ENSURE_SUCCESS(rv, rv);
937 if (exists) {
938 rv = mPermissionsFile->Remove(false);
939 NS_ENSURE_SUCCESS(rv, rv);
943 rv = OpenDatabase(mPermissionsFile);
944 if (rv == NS_ERROR_FILE_CORRUPTED) {
945 LogToConsole(u"permissions.sqlite is corrupted! Try again!"_ns);
947 // Add telemetry probe
948 Telemetry::Accumulate(Telemetry::PERMISSIONS_SQL_CORRUPTED, 1);
950 // delete corrupted permissions.sqlite and try again
951 rv = mPermissionsFile->Remove(false);
952 NS_ENSURE_SUCCESS(rv, rv);
953 LogToConsole(u"Corrupted permissions.sqlite has been removed."_ns);
955 rv = OpenDatabase(mPermissionsFile);
956 NS_ENSURE_SUCCESS(rv, rv);
957 LogToConsole(u"OpenDatabase to permissions.sqlite is successful!"_ns);
960 if (NS_WARN_IF(NS_FAILED(rv))) {
961 return rv;
964 bool ready;
965 data->mDBConn->GetConnectionReady(&ready);
966 if (!ready) {
967 LogToConsole(nsLiteralString(
968 u"Fail to get connection to permissions.sqlite! Try again!"));
970 // delete and try again
971 rv = mPermissionsFile->Remove(false);
972 NS_ENSURE_SUCCESS(rv, rv);
973 LogToConsole(u"Defective permissions.sqlite has been removed."_ns);
975 // Add telemetry probe
976 Telemetry::Accumulate(Telemetry::DEFECTIVE_PERMISSIONS_SQL_REMOVED, 1);
978 rv = OpenDatabase(mPermissionsFile);
979 NS_ENSURE_SUCCESS(rv, rv);
980 LogToConsole(u"OpenDatabase to permissions.sqlite is successful!"_ns);
982 data->mDBConn->GetConnectionReady(&ready);
983 if (!ready) return NS_ERROR_UNEXPECTED;
986 bool tableExists = false;
987 data->mDBConn->TableExists("moz_perms"_ns, &tableExists);
988 if (!tableExists) {
989 data->mDBConn->TableExists("moz_hosts"_ns, &tableExists);
991 if (!tableExists) {
992 rv = CreateTable();
993 NS_ENSURE_SUCCESS(rv, rv);
994 } else {
995 // table already exists; check the schema version before reading
996 int32_t dbSchemaVersion;
997 rv = data->mDBConn->GetSchemaVersion(&dbSchemaVersion);
998 NS_ENSURE_SUCCESS(rv, rv);
1000 switch (dbSchemaVersion) {
1001 // upgrading.
1002 // every time you increment the database schema, you need to
1003 // implement the upgrading code from the previous version to the
1004 // new one. fall through to current version
1006 case 1: {
1007 // previous non-expiry version of database. Upgrade it by adding
1008 // the expiration columns
1009 rv = data->mDBConn->ExecuteSimpleSQL(
1010 "ALTER TABLE moz_hosts ADD expireType INTEGER"_ns);
1011 NS_ENSURE_SUCCESS(rv, rv);
1013 rv = data->mDBConn->ExecuteSimpleSQL(
1014 "ALTER TABLE moz_hosts ADD expireTime INTEGER"_ns);
1015 NS_ENSURE_SUCCESS(rv, rv);
1018 // fall through to the next upgrade
1019 [[fallthrough]];
1021 // TODO: we want to make default version as version 2 in order to
1022 // fix bug 784875.
1023 case 0:
1024 case 2: {
1025 // Add appId/isInBrowserElement fields.
1026 rv = data->mDBConn->ExecuteSimpleSQL(
1027 "ALTER TABLE moz_hosts ADD appId INTEGER"_ns);
1028 NS_ENSURE_SUCCESS(rv, rv);
1030 rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
1031 "ALTER TABLE moz_hosts ADD isInBrowserElement INTEGER"));
1032 NS_ENSURE_SUCCESS(rv, rv);
1034 rv = data->mDBConn->SetSchemaVersion(3);
1035 NS_ENSURE_SUCCESS(rv, rv);
1038 // fall through to the next upgrade
1039 [[fallthrough]];
1041 // Version 3->4 is the creation of the modificationTime field.
1042 case 3: {
1043 rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
1044 "ALTER TABLE moz_hosts ADD modificationTime INTEGER"));
1045 NS_ENSURE_SUCCESS(rv, rv);
1047 // We leave the modificationTime at zero for all existing records;
1048 // using now() would mean, eg, that doing "remove all from the
1049 // last hour" within the first hour after migration would remove
1050 // all permissions.
1052 rv = data->mDBConn->SetSchemaVersion(4);
1053 NS_ENSURE_SUCCESS(rv, rv);
1056 // fall through to the next upgrade
1057 [[fallthrough]];
1059 // In version 5, host appId, and isInBrowserElement were merged into
1060 // a single origin entry
1062 // In version 6, the tables were renamed for backwards compatability
1063 // reasons with version 4 and earlier.
1065 // In version 7, a bug in the migration used for version 4->5 was
1066 // discovered which could have triggered data-loss. Because of that,
1067 // all users with a version 4, 5, or 6 database will be re-migrated
1068 // from the backup database. (bug 1186034). This migration bug is
1069 // not present after bug 1185340, and the re-migration ensures that
1070 // all users have the fix.
1071 case 5:
1072 // This branch could also be reached via dbSchemaVersion == 3, in
1073 // which case we want to fall through to the dbSchemaVersion == 4
1074 // case. The easiest way to do that is to perform this extra check
1075 // here to make sure that we didn't get here via a fallthrough
1076 // from v3
1077 if (dbSchemaVersion == 5) {
1078 // In version 5, the backup database is named moz_hosts_v4. We
1079 // perform the version 5->6 migration to get the tables to have
1080 // consistent naming conventions.
1082 // Version 5->6 is the renaming of moz_hosts to moz_perms, and
1083 // moz_hosts_v4 to moz_hosts (bug 1185343)
1085 // In version 5, we performed the modifications to the
1086 // permissions database in place, this meant that if you
1087 // upgraded to a version which used V5, and then downgraded to a
1088 // version which used v4 or earlier, the fallback path would
1089 // drop the table, and your permissions data would be lost. This
1090 // migration undoes that mistake, by restoring the old moz_hosts
1091 // table (if it was present), and instead using the new table
1092 // moz_perms for the new permissions schema.
1094 // NOTE: If you downgrade, store new permissions, and then
1095 // upgrade again, these new permissions won't be migrated or
1096 // reflected in the updated database. This migration only occurs
1097 // once, as if moz_perms exists, it will skip creating it. In
1098 // addition, permissions added after the migration will not be
1099 // visible in previous versions of firefox.
1101 bool permsTableExists = false;
1102 data->mDBConn->TableExists("moz_perms"_ns, &permsTableExists);
1103 if (!permsTableExists) {
1104 // Move the upgraded database to moz_perms
1105 rv = data->mDBConn->ExecuteSimpleSQL(
1106 "ALTER TABLE moz_hosts RENAME TO moz_perms"_ns);
1107 NS_ENSURE_SUCCESS(rv, rv);
1108 } else {
1109 NS_WARNING(
1110 "moz_hosts was not renamed to moz_perms, "
1111 "as a moz_perms table already exists");
1113 // In the situation where a moz_perms table already exists,
1114 // but the schema is lower than 6, a migration has already
1115 // previously occured to V6, but a downgrade has caused the
1116 // moz_hosts table to be dropped. This should only occur in
1117 // the case of a downgrade to a V5 database, which was only
1118 // present in a few day's nightlies. As that version was
1119 // likely used only on a temporary basis, we assume that the
1120 // database from the previous V6 has the permissions which the
1121 // user actually wants to use. We have to get rid of moz_hosts
1122 // such that moz_hosts_v4 can be moved into its place if it
1123 // exists.
1124 rv = data->mDBConn->ExecuteSimpleSQL("DROP TABLE moz_hosts"_ns);
1125 NS_ENSURE_SUCCESS(rv, rv);
1128 #ifdef DEBUG
1129 // The moz_hosts table shouldn't exist anymore
1130 bool hostsTableExists = false;
1131 data->mDBConn->TableExists("moz_hosts"_ns, &hostsTableExists);
1132 MOZ_ASSERT(!hostsTableExists);
1133 #endif
1135 // Rename moz_hosts_v4 back to it's original location, if it
1136 // exists
1137 bool v4TableExists = false;
1138 data->mDBConn->TableExists("moz_hosts_v4"_ns, &v4TableExists);
1139 if (v4TableExists) {
1140 rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
1141 "ALTER TABLE moz_hosts_v4 RENAME TO moz_hosts"));
1142 NS_ENSURE_SUCCESS(rv, rv);
1145 rv = data->mDBConn->SetSchemaVersion(6);
1146 NS_ENSURE_SUCCESS(rv, rv);
1149 // fall through to the next upgrade
1150 [[fallthrough]];
1152 // At this point, the version 5 table has been migrated to a version
1153 // 6 table We are guaranteed to have at least one of moz_hosts and
1154 // moz_perms. If we have moz_hosts, we will migrate moz_hosts into
1155 // moz_perms (even if we already have a moz_perms, as we need a
1156 // re-migration due to bug 1186034).
1158 // After this migration, we are guaranteed to have both a moz_hosts
1159 // (for backwards compatability), and a moz_perms table. The
1160 // moz_hosts table will have a v4 schema, and the moz_perms table
1161 // will have a v6 schema.
1162 case 4:
1163 case 6: {
1164 bool hostsTableExists = false;
1165 data->mDBConn->TableExists("moz_hosts"_ns, &hostsTableExists);
1166 if (hostsTableExists) {
1167 // Both versions 4 and 6 have a version 4 formatted hosts table
1168 // named moz_hosts. We can migrate this table to our version 7
1169 // table moz_perms. If moz_perms is present, then we can use it
1170 // as a basis for comparison.
1172 rv = data->mDBConn->BeginTransaction();
1173 NS_ENSURE_SUCCESS(rv, rv);
1175 bool tableExists = false;
1176 data->mDBConn->TableExists("moz_hosts_new"_ns, &tableExists);
1177 if (tableExists) {
1178 NS_WARNING(
1179 "The temporary database moz_hosts_new already exists, "
1180 "dropping "
1181 "it.");
1182 rv = data->mDBConn->ExecuteSimpleSQL("DROP TABLE moz_hosts_new"_ns);
1183 NS_ENSURE_SUCCESS(rv, rv);
1185 rv = data->mDBConn->ExecuteSimpleSQL(
1186 nsLiteralCString("CREATE TABLE moz_hosts_new ("
1187 " id INTEGER PRIMARY KEY"
1188 ",origin TEXT"
1189 ",type TEXT"
1190 ",permission INTEGER"
1191 ",expireType INTEGER"
1192 ",expireTime INTEGER"
1193 ",modificationTime INTEGER"
1194 ")"));
1195 NS_ENSURE_SUCCESS(rv, rv);
1197 nsCOMPtr<mozIStorageStatement> stmt;
1198 rv = data->mDBConn->CreateStatement(
1199 nsLiteralCString(
1200 "SELECT host, type, permission, expireType, "
1201 "expireTime, "
1202 "modificationTime, isInBrowserElement FROM moz_hosts"),
1203 getter_AddRefs(stmt));
1204 NS_ENSURE_SUCCESS(rv, rv);
1206 int64_t id = 0;
1207 bool hasResult;
1209 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1210 MigrationEntry entry;
1212 // Read in the old row
1213 rv = stmt->GetUTF8String(0, entry.mHost);
1214 if (NS_WARN_IF(NS_FAILED(rv))) {
1215 continue;
1217 rv = stmt->GetUTF8String(1, entry.mType);
1218 if (NS_WARN_IF(NS_FAILED(rv))) {
1219 continue;
1222 entry.mId = id++;
1223 entry.mPermission = stmt->AsInt32(2);
1224 entry.mExpireType = stmt->AsInt32(3);
1225 entry.mExpireTime = stmt->AsInt64(4);
1226 entry.mModificationTime = stmt->AsInt64(5);
1227 entry.mIsInBrowserElement = static_cast<bool>(stmt->AsInt32(6));
1229 mMigrationEntries.AppendElement(entry);
1232 // We don't drop the moz_hosts table such that it is available
1233 // for backwards-compatability and for future migrations in case
1234 // of migration errors in the current code. Create a marker
1235 // empty table which will indicate that the moz_hosts table is
1236 // intended to act as a backup. If this table is not present,
1237 // then the moz_hosts table was created as a random empty table.
1238 rv = data->mDBConn->ExecuteSimpleSQL(
1239 nsLiteralCString("CREATE TABLE moz_hosts_is_backup (dummy "
1240 "INTEGER PRIMARY KEY)"));
1241 NS_ENSURE_SUCCESS(rv, rv);
1243 bool permsTableExists = false;
1244 data->mDBConn->TableExists("moz_perms"_ns, &permsTableExists);
1245 if (permsTableExists) {
1246 // The user already had a moz_perms table, and we are
1247 // performing a re-migration. We count the rows in the old
1248 // table for telemetry, and then back up their old database as
1249 // moz_perms_v6
1251 nsCOMPtr<mozIStorageStatement> countStmt;
1252 rv = data->mDBConn->CreateStatement(
1253 "SELECT COUNT(*) FROM moz_perms"_ns, getter_AddRefs(countStmt));
1254 bool hasResult = false;
1255 if (NS_FAILED(rv) ||
1256 NS_FAILED(countStmt->ExecuteStep(&hasResult)) || !hasResult) {
1257 NS_WARNING("Could not count the rows in moz_perms");
1260 // Back up the old moz_perms database as moz_perms_v6 before
1261 // we move the new table into its position
1262 rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
1263 "ALTER TABLE moz_perms RENAME TO moz_perms_v6"));
1264 NS_ENSURE_SUCCESS(rv, rv);
1267 rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
1268 "ALTER TABLE moz_hosts_new RENAME TO moz_perms"));
1269 NS_ENSURE_SUCCESS(rv, rv);
1271 rv = data->mDBConn->CommitTransaction();
1272 NS_ENSURE_SUCCESS(rv, rv);
1273 } else {
1274 // We don't have a moz_hosts table, so we create one for
1275 // downgrading purposes. This table is empty.
1276 rv = data->mDBConn->ExecuteSimpleSQL(
1277 nsLiteralCString("CREATE TABLE moz_hosts ("
1278 " id INTEGER PRIMARY KEY"
1279 ",host TEXT"
1280 ",type TEXT"
1281 ",permission INTEGER"
1282 ",expireType INTEGER"
1283 ",expireTime INTEGER"
1284 ",modificationTime INTEGER"
1285 ",appId INTEGER"
1286 ",isInBrowserElement INTEGER"
1287 ")"));
1288 NS_ENSURE_SUCCESS(rv, rv);
1290 // We are guaranteed to have a moz_perms table at this point.
1293 #ifdef DEBUG
1295 // At this point, both the moz_hosts and moz_perms tables should
1296 // exist
1297 bool hostsTableExists = false;
1298 bool permsTableExists = false;
1299 data->mDBConn->TableExists("moz_hosts"_ns, &hostsTableExists);
1300 data->mDBConn->TableExists("moz_perms"_ns, &permsTableExists);
1301 MOZ_ASSERT(hostsTableExists && permsTableExists);
1303 #endif
1305 rv = data->mDBConn->SetSchemaVersion(7);
1306 NS_ENSURE_SUCCESS(rv, rv);
1309 // fall through to the next upgrade
1310 [[fallthrough]];
1312 // The version 7-8 migration is the re-migration of localhost and
1313 // ip-address entries due to errors in the previous version 7
1314 // migration which caused localhost and ip-address entries to be
1315 // incorrectly discarded. The version 7 migration logic has been
1316 // corrected, and thus this logic only needs to execute if the user
1317 // is currently on version 7.
1318 case 7: {
1319 // This migration will be relatively expensive as we need to
1320 // perform database lookups for each origin which we want to
1321 // insert. Fortunately, it shouldn't be too expensive as we only
1322 // want to insert a small number of entries created for localhost
1323 // or IP addresses.
1325 // We only want to perform the re-migration if moz_hosts is a
1326 // backup
1327 bool hostsIsBackupExists = false;
1328 data->mDBConn->TableExists("moz_hosts_is_backup"_ns,
1329 &hostsIsBackupExists);
1331 // Only perform this migration if the original schema version was
1332 // 7, and the moz_hosts table is a backup.
1333 if (dbSchemaVersion == 7 && hostsIsBackupExists) {
1334 nsCOMPtr<mozIStorageStatement> stmt;
1335 rv = data->mDBConn->CreateStatement(
1336 nsLiteralCString(
1337 "SELECT host, type, permission, expireType, "
1338 "expireTime, "
1339 "modificationTime, isInBrowserElement FROM moz_hosts"),
1340 getter_AddRefs(stmt));
1341 NS_ENSURE_SUCCESS(rv, rv);
1343 nsCOMPtr<mozIStorageStatement> idStmt;
1344 rv = data->mDBConn->CreateStatement(
1345 "SELECT MAX(id) FROM moz_hosts"_ns, getter_AddRefs(idStmt));
1347 int64_t id = 0;
1348 bool hasResult = false;
1349 if (NS_SUCCEEDED(rv) &&
1350 NS_SUCCEEDED(idStmt->ExecuteStep(&hasResult)) && hasResult) {
1351 id = idStmt->AsInt32(0) + 1;
1354 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1355 MigrationEntry entry;
1357 // Read in the old row
1358 rv = stmt->GetUTF8String(0, entry.mHost);
1359 if (NS_WARN_IF(NS_FAILED(rv))) {
1360 continue;
1363 nsAutoCString eTLD1;
1364 rv = nsEffectiveTLDService::GetInstance()->GetBaseDomainFromHost(
1365 entry.mHost, 0, eTLD1);
1366 if (NS_SUCCEEDED(rv)) {
1367 // We only care about entries which the tldService can't
1368 // handle
1369 continue;
1372 rv = stmt->GetUTF8String(1, entry.mType);
1373 if (NS_WARN_IF(NS_FAILED(rv))) {
1374 continue;
1377 entry.mId = id++;
1378 entry.mPermission = stmt->AsInt32(2);
1379 entry.mExpireType = stmt->AsInt32(3);
1380 entry.mExpireTime = stmt->AsInt64(4);
1381 entry.mModificationTime = stmt->AsInt64(5);
1382 entry.mIsInBrowserElement = static_cast<bool>(stmt->AsInt32(6));
1384 mMigrationEntries.AppendElement(entry);
1388 // Even if we didn't perform the migration, we want to bump the
1389 // schema version to 8.
1390 rv = data->mDBConn->SetSchemaVersion(8);
1391 NS_ENSURE_SUCCESS(rv, rv);
1394 // fall through to the next upgrade
1395 [[fallthrough]];
1397 // The version 8-9 migration removes the unnecessary backup
1398 // moz-hosts database contents. as the data no longer needs to be
1399 // migrated
1400 case 8: {
1401 // We only want to clear out the old table if it is a backup. If
1402 // it isn't a backup, we don't need to touch it.
1403 bool hostsIsBackupExists = false;
1404 data->mDBConn->TableExists("moz_hosts_is_backup"_ns,
1405 &hostsIsBackupExists);
1406 if (hostsIsBackupExists) {
1407 // Delete everything from the backup, we want to keep around the
1408 // table so that you can still downgrade and not break things,
1409 // but we don't need to keep the rows around.
1410 rv = data->mDBConn->ExecuteSimpleSQL("DELETE FROM moz_hosts"_ns);
1411 NS_ENSURE_SUCCESS(rv, rv);
1413 // The table is no longer a backup, so get rid of it.
1414 rv = data->mDBConn->ExecuteSimpleSQL(
1415 "DROP TABLE moz_hosts_is_backup"_ns);
1416 NS_ENSURE_SUCCESS(rv, rv);
1419 rv = data->mDBConn->SetSchemaVersion(9);
1420 NS_ENSURE_SUCCESS(rv, rv);
1423 // fall through to the next upgrade
1424 [[fallthrough]];
1426 case 9: {
1427 rv = data->mDBConn->SetSchemaVersion(10);
1428 NS_ENSURE_SUCCESS(rv, rv);
1431 // fall through to the next upgrade
1432 [[fallthrough]];
1434 case 10: {
1435 // Filter out the rows with storage access API permissions with a
1436 // granted origin, and remove the granted origin part from the
1437 // permission type.
1438 rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
1439 "UPDATE moz_perms "
1440 "SET type=SUBSTR(type, 0, INSTR(SUBSTR(type, INSTR(type, "
1441 "'^') + "
1442 "1), '^') + INSTR(type, '^')) "
1443 "WHERE INSTR(SUBSTR(type, INSTR(type, '^') + 1), '^') AND "
1444 "SUBSTR(type, 0, 18) == \"storageAccessAPI^\";"));
1445 NS_ENSURE_SUCCESS(rv, rv);
1447 rv = data->mDBConn->SetSchemaVersion(11);
1448 NS_ENSURE_SUCCESS(rv, rv);
1451 // fall through to the next upgrade
1452 [[fallthrough]];
1454 case 11: {
1455 // Migrate 3rdPartyStorage keys to a site scope
1456 rv = data->mDBConn->BeginTransaction();
1457 NS_ENSURE_SUCCESS(rv, rv);
1458 nsCOMPtr<mozIStorageStatement> updateStmt;
1459 rv = data->mDBConn->CreateStatement(
1460 nsLiteralCString("UPDATE moz_perms SET origin = ?2 WHERE id = ?1"),
1461 getter_AddRefs(updateStmt));
1462 NS_ENSURE_SUCCESS(rv, rv);
1464 nsCOMPtr<mozIStorageStatement> deleteStmt;
1465 rv = data->mDBConn->CreateStatement(
1466 nsLiteralCString("DELETE FROM moz_perms WHERE id = ?1"),
1467 getter_AddRefs(deleteStmt));
1468 NS_ENSURE_SUCCESS(rv, rv);
1470 nsCOMPtr<mozIStorageStatement> selectStmt;
1471 rv = data->mDBConn->CreateStatement(
1472 nsLiteralCString("SELECT id, origin, type FROM moz_perms WHERE "
1473 " SUBSTR(type, 0, 17) == \"3rdPartyStorage^\""),
1474 getter_AddRefs(selectStmt));
1475 NS_ENSURE_SUCCESS(rv, rv);
1477 nsTHashSet<nsCStringHashKey> deduplicationSet;
1478 bool hasResult;
1479 while (NS_SUCCEEDED(selectStmt->ExecuteStep(&hasResult)) && hasResult) {
1480 int64_t id;
1481 rv = selectStmt->GetInt64(0, &id);
1482 NS_ENSURE_SUCCESS(rv, rv);
1484 nsCString origin;
1485 rv = selectStmt->GetUTF8String(1, origin);
1486 NS_ENSURE_SUCCESS(rv, rv);
1488 nsCString type;
1489 rv = selectStmt->GetUTF8String(2, type);
1490 NS_ENSURE_SUCCESS(rv, rv);
1492 nsCOMPtr<nsIURI> uri;
1493 rv = NS_NewURI(getter_AddRefs(uri), origin);
1494 if (NS_FAILED(rv)) {
1495 continue;
1497 nsCString site;
1498 rv = nsEffectiveTLDService::GetInstance()->GetSite(uri, site);
1499 if (NS_WARN_IF(NS_FAILED(rv))) {
1500 continue;
1503 nsCString deduplicationKey =
1504 nsPrintfCString("%s,%s", site.get(), type.get());
1505 if (deduplicationSet.Contains(deduplicationKey)) {
1506 rv = deleteStmt->BindInt64ByIndex(0, id);
1507 NS_ENSURE_SUCCESS(rv, rv);
1509 rv = deleteStmt->Execute();
1510 NS_ENSURE_SUCCESS(rv, rv);
1511 } else {
1512 deduplicationSet.Insert(deduplicationKey);
1513 rv = updateStmt->BindInt64ByIndex(0, id);
1514 NS_ENSURE_SUCCESS(rv, rv);
1515 rv = updateStmt->BindUTF8StringByIndex(1, site);
1516 NS_ENSURE_SUCCESS(rv, rv);
1518 rv = updateStmt->Execute();
1519 NS_ENSURE_SUCCESS(rv, rv);
1522 rv = data->mDBConn->CommitTransaction();
1523 NS_ENSURE_SUCCESS(rv, rv);
1525 rv = data->mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
1526 NS_ENSURE_SUCCESS(rv, rv);
1529 // fall through to the next upgrade
1530 [[fallthrough]];
1532 // current version.
1533 case HOSTS_SCHEMA_VERSION:
1534 break;
1536 // downgrading.
1537 // if columns have been added to the table, we can still use the
1538 // ones we understand safely. if columns have been deleted or
1539 // altered, just blow away the table and start from scratch! if you
1540 // change the way a column is interpreted, make sure you also change
1541 // its name so this check will catch it.
1542 default: {
1543 // check if all the expected columns exist
1544 nsCOMPtr<mozIStorageStatement> stmt;
1545 rv = data->mDBConn->CreateStatement(
1546 nsLiteralCString("SELECT origin, type, permission, "
1547 "expireType, expireTime, "
1548 "modificationTime FROM moz_perms"),
1549 getter_AddRefs(stmt));
1550 if (NS_SUCCEEDED(rv)) break;
1552 // our columns aren't there - drop the table!
1553 rv = data->mDBConn->ExecuteSimpleSQL("DROP TABLE moz_perms"_ns);
1554 NS_ENSURE_SUCCESS(rv, rv);
1556 rv = CreateTable();
1557 NS_ENSURE_SUCCESS(rv, rv);
1558 } break;
1562 // cache frequently used statements (for insertion, deletion, and
1563 // updating)
1564 rv = data->mDBConn->CreateStatement(
1565 nsLiteralCString("INSERT INTO moz_perms "
1566 "(id, origin, type, permission, expireType, "
1567 "expireTime, modificationTime) "
1568 "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"),
1569 getter_AddRefs(data->mStmtInsert));
1570 NS_ENSURE_SUCCESS(rv, rv);
1572 rv = data->mDBConn->CreateStatement(nsLiteralCString("DELETE FROM moz_perms "
1573 "WHERE id = ?1"),
1574 getter_AddRefs(data->mStmtDelete));
1575 NS_ENSURE_SUCCESS(rv, rv);
1577 rv = data->mDBConn->CreateStatement(
1578 nsLiteralCString("UPDATE moz_perms "
1579 "SET permission = ?2, expireType= ?3, expireTime = "
1580 "?4, modificationTime = ?5 WHERE id = ?1"),
1581 getter_AddRefs(data->mStmtUpdate));
1582 NS_ENSURE_SUCCESS(rv, rv);
1584 // Always import default permissions.
1585 ConsumeDefaultsInputStream(aDefaultsInputStream, lock);
1587 // check whether to import or just read in the db
1588 if (tableExists) {
1589 rv = Read(lock);
1590 NS_ENSURE_SUCCESS(rv, rv);
1593 raiiFailure.release();
1595 return NS_OK;
1598 void PermissionManager::AddIdleDailyMaintenanceJob() {
1599 MOZ_ASSERT(NS_IsMainThread());
1601 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
1602 NS_ENSURE_TRUE_VOID(observerService);
1604 nsresult rv =
1605 observerService->AddObserver(this, OBSERVER_TOPIC_IDLE_DAILY, false);
1606 NS_ENSURE_SUCCESS_VOID(rv);
1609 void PermissionManager::RemoveIdleDailyMaintenanceJob() {
1610 MOZ_ASSERT(NS_IsMainThread());
1612 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
1613 NS_ENSURE_TRUE_VOID(observerService);
1615 nsresult rv =
1616 observerService->RemoveObserver(this, OBSERVER_TOPIC_IDLE_DAILY);
1617 NS_ENSURE_SUCCESS_VOID(rv);
1620 void PermissionManager::PerformIdleDailyMaintenance() {
1621 MOZ_ASSERT(NS_IsMainThread());
1623 RefPtr<PermissionManager> self = this;
1624 mThread->Dispatch(NS_NewRunnableFunction(
1625 "PermissionManager::PerformIdleDailyMaintenance", [self] {
1626 auto data = self->mThreadBoundData.Access();
1628 if (self->mState == eClosed || !data->mDBConn) {
1629 return;
1632 nsCOMPtr<mozIStorageStatement> stmtDeleteExpired;
1633 nsresult rv = data->mDBConn->CreateStatement(
1634 nsLiteralCString("DELETE FROM moz_perms WHERE expireType = "
1635 "?1 AND expireTime <= ?2"),
1636 getter_AddRefs(stmtDeleteExpired));
1637 NS_ENSURE_SUCCESS_VOID(rv);
1639 rv = stmtDeleteExpired->BindInt32ByIndex(
1640 0, nsIPermissionManager::EXPIRE_TIME);
1641 NS_ENSURE_SUCCESS_VOID(rv);
1643 rv = stmtDeleteExpired->BindInt64ByIndex(1, EXPIRY_NOW);
1644 NS_ENSURE_SUCCESS_VOID(rv);
1646 rv = stmtDeleteExpired->Execute();
1647 NS_ENSURE_SUCCESS_VOID(rv);
1648 }));
1651 // sets the schema version and creates the moz_perms table.
1652 nsresult PermissionManager::CreateTable() {
1653 MOZ_ASSERT(!NS_IsMainThread());
1654 auto data = mThreadBoundData.Access();
1656 // set the schema version, before creating the table
1657 nsresult rv = data->mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
1658 if (NS_FAILED(rv)) return rv;
1660 // create the table
1661 // SQL also lives in automation.py.in. If you change this SQL change that
1662 // one too
1663 rv = data->mDBConn->ExecuteSimpleSQL(
1664 nsLiteralCString("CREATE TABLE moz_perms ("
1665 " id INTEGER PRIMARY KEY"
1666 ",origin TEXT"
1667 ",type TEXT"
1668 ",permission INTEGER"
1669 ",expireType INTEGER"
1670 ",expireTime INTEGER"
1671 ",modificationTime INTEGER"
1672 ")"));
1673 if (NS_FAILED(rv)) return rv;
1675 // We also create a legacy V4 table, for backwards compatability,
1676 // and to ensure that downgrades don't trigger a schema version change.
1677 return data->mDBConn->ExecuteSimpleSQL(
1678 nsLiteralCString("CREATE TABLE moz_hosts ("
1679 " id INTEGER PRIMARY KEY"
1680 ",host TEXT"
1681 ",type TEXT"
1682 ",permission INTEGER"
1683 ",expireType INTEGER"
1684 ",expireTime INTEGER"
1685 ",modificationTime INTEGER"
1686 ",isInBrowserElement INTEGER"
1687 ")"));
1690 // Returns whether the given combination of expire type and expire time are
1691 // expired. Note that EXPIRE_SESSION only honors expireTime if it is nonzero.
1692 bool PermissionManager::HasExpired(uint32_t aExpireType, int64_t aExpireTime) {
1693 return (aExpireType == nsIPermissionManager::EXPIRE_TIME ||
1694 (aExpireType == nsIPermissionManager::EXPIRE_SESSION &&
1695 aExpireTime != 0)) &&
1696 aExpireTime <= EXPIRY_NOW;
1699 NS_IMETHODIMP
1700 PermissionManager::AddFromPrincipalAndPersistInPrivateBrowsing(
1701 nsIPrincipal* aPrincipal, const nsACString& aType, uint32_t aPermission) {
1702 ENSURE_NOT_CHILD_PROCESS;
1703 NS_ENSURE_ARG_POINTER(aPrincipal);
1704 // We don't add the system principal because it actually has no URI and we
1705 // always allow action for them.
1706 if (aPrincipal->IsSystemPrincipal()) {
1707 return NS_OK;
1710 // Null principals can't meaningfully have persisted permissions attached to
1711 // them, so we don't allow adding permissions for them.
1712 if (aPrincipal->GetIsNullPrincipal()) {
1713 return NS_OK;
1716 // Permissions may not be added to expanded principals.
1717 if (IsExpandedPrincipal(aPrincipal)) {
1718 return NS_ERROR_INVALID_ARG;
1721 // A modificationTime of zero will cause AddInternal to use now().
1722 int64_t modificationTime = 0;
1724 return AddInternal(aPrincipal, aType, aPermission, 0,
1725 nsIPermissionManager::EXPIRE_NEVER,
1726 /* aExpireTime */ 0, modificationTime, eNotify, eWriteToDB,
1727 /* aIgnoreSessionPermissions */ false,
1728 /* aOriginString*/ nullptr,
1729 /* aAllowPersistInPrivateBrowsing */ true);
1732 NS_IMETHODIMP
1733 PermissionManager::AddFromPrincipal(nsIPrincipal* aPrincipal,
1734 const nsACString& aType,
1735 uint32_t aPermission, uint32_t aExpireType,
1736 int64_t aExpireTime) {
1737 ENSURE_NOT_CHILD_PROCESS;
1738 NS_ENSURE_ARG_POINTER(aPrincipal);
1739 NS_ENSURE_TRUE(aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
1740 aExpireType == nsIPermissionManager::EXPIRE_TIME ||
1741 aExpireType == nsIPermissionManager::EXPIRE_SESSION ||
1742 aExpireType == nsIPermissionManager::EXPIRE_POLICY,
1743 NS_ERROR_INVALID_ARG);
1745 // Skip addition if the permission is already expired.
1746 if (HasExpired(aExpireType, aExpireTime)) {
1747 return NS_OK;
1750 // We don't add the system principal because it actually has no URI and we
1751 // always allow action for them.
1752 if (aPrincipal->IsSystemPrincipal()) {
1753 return NS_OK;
1756 // Null principals can't meaningfully have persisted permissions attached to
1757 // them, so we don't allow adding permissions for them.
1758 if (aPrincipal->GetIsNullPrincipal()) {
1759 return NS_OK;
1762 // Permissions may not be added to expanded principals.
1763 if (IsExpandedPrincipal(aPrincipal)) {
1764 return NS_ERROR_INVALID_ARG;
1767 // A modificationTime of zero will cause AddInternal to use now().
1768 int64_t modificationTime = 0;
1770 return AddInternal(aPrincipal, aType, aPermission, 0, aExpireType,
1771 aExpireTime, modificationTime, eNotify, eWriteToDB);
1774 nsresult PermissionManager::AddInternal(
1775 nsIPrincipal* aPrincipal, const nsACString& aType, uint32_t aPermission,
1776 int64_t aID, uint32_t aExpireType, int64_t aExpireTime,
1777 int64_t aModificationTime, NotifyOperationType aNotifyOperation,
1778 DBOperationType aDBOperation, const bool aIgnoreSessionPermissions,
1779 const nsACString* aOriginString,
1780 const bool aAllowPersistInPrivateBrowsing) {
1781 MOZ_ASSERT(NS_IsMainThread());
1783 EnsureReadCompleted();
1785 nsresult rv = NS_OK;
1786 nsAutoCString origin;
1787 // Only attempt to compute the origin string when it is going to be needed
1788 // later on in the function.
1789 if (!IsChildProcess() ||
1790 (aDBOperation == eWriteToDB && IsPersistentExpire(aExpireType, aType))) {
1791 if (aOriginString) {
1792 // Use the origin string provided by the caller.
1793 origin = *aOriginString;
1794 } else {
1795 if (IsSiteScopedPermission(aType)) {
1796 rv = GetSiteFromPrincipal(aPrincipal, IsOAForceStripPermission(aType),
1797 origin);
1798 } else {
1799 // Compute it from the principal provided.
1800 rv = GetOriginFromPrincipal(aPrincipal, IsOAForceStripPermission(aType),
1801 origin);
1803 NS_ENSURE_SUCCESS(rv, rv);
1807 // Unless the caller sets aAllowPersistInPrivateBrowsing, only store
1808 // permissions for the session in Private Browsing. Except for default
1809 // permissions which are stored in-memory only and imported each startup. We
1810 // also allow setting persistent UKNOWN_ACTION, to support removing default
1811 // private browsing permissions.
1812 if (!aAllowPersistInPrivateBrowsing && aID != cIDPermissionIsDefault &&
1813 aPermission != UNKNOWN_ACTION && aExpireType != EXPIRE_SESSION) {
1814 uint32_t privateBrowsingId =
1815 nsScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID;
1816 nsresult rv = aPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
1817 if (NS_SUCCEEDED(rv) &&
1818 privateBrowsingId !=
1819 nsScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID) {
1820 aExpireType = EXPIRE_SESSION;
1824 // Let's send the new permission to the content process only if it has to be
1825 // notified.
1826 if (!IsChildProcess() && aNotifyOperation == eNotify) {
1827 IPC::Permission permission(origin, aType, aPermission, aExpireType,
1828 aExpireTime);
1830 nsAutoCString permissionKey;
1831 GetKeyForPermission(aPrincipal, aType, permissionKey);
1832 bool isSecondaryKeyed;
1833 nsAutoCString secondaryKey;
1834 isSecondaryKeyed = GetSecondaryKey(aType, secondaryKey);
1835 if (isSecondaryKeyed) {
1836 NotifySecondaryKeyPermissionUpdateInContentProcess(
1837 aType, aPermission, secondaryKey, aPrincipal);
1840 nsTArray<ContentParent*> cplist;
1841 ContentParent::GetAll(cplist);
1842 for (uint32_t i = 0; i < cplist.Length(); ++i) {
1843 ContentParent* cp = cplist[i];
1844 if (cp->NeedsPermissionsUpdate(permissionKey)) {
1845 Unused << cp->SendAddPermission(permission);
1850 MOZ_ASSERT(PermissionAvailable(aPrincipal, aType));
1852 // look up the type index
1853 int32_t typeIndex = GetTypeIndex(aType, true);
1854 NS_ENSURE_TRUE(typeIndex != -1, NS_ERROR_OUT_OF_MEMORY);
1856 // When an entry already exists, PutEntry will return that, instead
1857 // of adding a new one
1858 RefPtr<PermissionKey> key = PermissionKey::CreateFromPrincipal(
1859 aPrincipal, IsOAForceStripPermission(aType),
1860 IsSiteScopedPermission(aType), rv);
1861 if (!key) {
1862 MOZ_ASSERT(NS_FAILED(rv));
1863 return rv;
1866 PermissionHashKey* entry = mPermissionTable.PutEntry(key);
1867 if (!entry) return NS_ERROR_FAILURE;
1868 if (!entry->GetKey()) {
1869 mPermissionTable.RemoveEntry(entry);
1870 return NS_ERROR_OUT_OF_MEMORY;
1873 // figure out the transaction type, and get any existing permission value
1874 OperationType op;
1875 int32_t index = entry->GetPermissionIndex(typeIndex);
1876 if (index == -1) {
1877 if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
1878 op = eOperationNone;
1879 else
1880 op = eOperationAdding;
1882 } else {
1883 PermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
1885 // remove the permission if the permission is UNKNOWN, update the
1886 // permission if its value or expire type have changed OR if the time has
1887 // changed and the expire type is time, otherwise, don't modify. There's
1888 // no need to modify a permission that doesn't expire with time when the
1889 // only thing changed is the expire time.
1890 if (aPermission == oldPermissionEntry.mPermission &&
1891 aExpireType == oldPermissionEntry.mExpireType &&
1892 (aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
1893 aExpireTime == oldPermissionEntry.mExpireTime))
1894 op = eOperationNone;
1895 else if (oldPermissionEntry.mID == cIDPermissionIsDefault)
1896 // The existing permission is one added as a default and the new
1897 // permission doesn't exactly match so we are replacing the default. This
1898 // is true even if the new permission is UNKNOWN_ACTION (which means a
1899 // "logical remove" of the default)
1900 op = eOperationReplacingDefault;
1901 else if (aID == cIDPermissionIsDefault)
1902 // We are adding a default permission but a "real" permission already
1903 // exists. This almost-certainly means we just did a removeAllSince and
1904 // are re-importing defaults - so we can ignore this.
1905 op = eOperationNone;
1906 else if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
1907 op = eOperationRemoving;
1908 else
1909 op = eOperationChanging;
1912 // child processes should *always* be passed a modificationTime of zero.
1913 MOZ_ASSERT(!IsChildProcess() || aModificationTime == 0);
1915 // do the work for adding, deleting, or changing a permission:
1916 // update the in-memory list, write to the db, and notify consumers.
1917 int64_t id;
1918 if (aModificationTime == 0) {
1919 aModificationTime = EXPIRY_NOW;
1922 switch (op) {
1923 case eOperationNone: {
1924 // nothing to do
1925 return NS_OK;
1928 case eOperationAdding: {
1929 if (aDBOperation == eWriteToDB) {
1930 // we'll be writing to the database - generate a known unique id
1931 id = ++mLargestID;
1932 } else {
1933 // we're reading from the database - use the id already assigned
1934 id = aID;
1937 entry->GetPermissions().AppendElement(
1938 PermissionEntry(id, typeIndex, aPermission, aExpireType, aExpireTime,
1939 aModificationTime));
1941 if (aDBOperation == eWriteToDB &&
1942 IsPersistentExpire(aExpireType, aType)) {
1943 UpdateDB(op, id, origin, aType, aPermission, aExpireType, aExpireTime,
1944 aModificationTime);
1947 if (aNotifyOperation == eNotify) {
1948 NotifyObserversWithPermission(aPrincipal, mTypeArray[typeIndex],
1949 aPermission, aExpireType, aExpireTime,
1950 aModificationTime, u"added");
1953 break;
1956 case eOperationRemoving: {
1957 PermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
1958 id = oldPermissionEntry.mID;
1960 // If the type we want to remove is EXPIRE_POLICY, we need to reject
1961 // attempts to change the permission.
1962 if (entry->GetPermissions()[index].mExpireType == EXPIRE_POLICY) {
1963 NS_WARNING("Attempting to remove EXPIRE_POLICY permission");
1964 break;
1967 entry->GetPermissions().RemoveElementAt(index);
1969 if (aDBOperation == eWriteToDB)
1970 // We care only about the id here so we pass dummy values for all other
1971 // parameters.
1972 UpdateDB(op, id, ""_ns, ""_ns, 0, nsIPermissionManager::EXPIRE_NEVER, 0,
1975 if (aNotifyOperation == eNotify) {
1976 NotifyObserversWithPermission(
1977 aPrincipal, mTypeArray[typeIndex], oldPermissionEntry.mPermission,
1978 oldPermissionEntry.mExpireType, oldPermissionEntry.mExpireTime,
1979 oldPermissionEntry.mModificationTime, u"deleted");
1982 // If there are no more permissions stored for that entry, clear it.
1983 if (entry->GetPermissions().IsEmpty()) {
1984 mPermissionTable.RemoveEntry(entry);
1987 break;
1990 case eOperationChanging: {
1991 id = entry->GetPermissions()[index].mID;
1993 // If the existing type is EXPIRE_POLICY, we need to reject attempts to
1994 // change the permission.
1995 if (entry->GetPermissions()[index].mExpireType == EXPIRE_POLICY) {
1996 NS_WARNING("Attempting to modify EXPIRE_POLICY permission");
1997 break;
2000 PermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
2002 // If the new expireType is EXPIRE_SESSION, then we have to keep a
2003 // copy of the previous permission/expireType values. This cached value
2004 // will be used when restoring the permissions of an app.
2005 if (entry->GetPermissions()[index].mExpireType !=
2006 nsIPermissionManager::EXPIRE_SESSION &&
2007 aExpireType == nsIPermissionManager::EXPIRE_SESSION) {
2008 entry->GetPermissions()[index].mNonSessionPermission =
2009 entry->GetPermissions()[index].mPermission;
2010 entry->GetPermissions()[index].mNonSessionExpireType =
2011 entry->GetPermissions()[index].mExpireType;
2012 entry->GetPermissions()[index].mNonSessionExpireTime =
2013 entry->GetPermissions()[index].mExpireTime;
2014 } else if (aExpireType != nsIPermissionManager::EXPIRE_SESSION) {
2015 entry->GetPermissions()[index].mNonSessionPermission = aPermission;
2016 entry->GetPermissions()[index].mNonSessionExpireType = aExpireType;
2017 entry->GetPermissions()[index].mNonSessionExpireTime = aExpireTime;
2020 entry->GetPermissions()[index].mPermission = aPermission;
2021 entry->GetPermissions()[index].mExpireType = aExpireType;
2022 entry->GetPermissions()[index].mExpireTime = aExpireTime;
2023 entry->GetPermissions()[index].mModificationTime = aModificationTime;
2025 if (aDBOperation == eWriteToDB) {
2026 bool newIsPersistentExpire = IsPersistentExpire(aExpireType, aType);
2027 bool oldIsPersistentExpire =
2028 IsPersistentExpire(oldPermissionEntry.mExpireType, aType);
2030 if (!newIsPersistentExpire && oldIsPersistentExpire) {
2031 // Maybe we have to remove the previous permission if that was
2032 // persistent.
2033 UpdateDB(eOperationRemoving, id, ""_ns, ""_ns, 0,
2034 nsIPermissionManager::EXPIRE_NEVER, 0, 0);
2035 } else if (newIsPersistentExpire && !oldIsPersistentExpire) {
2036 // It could also be that the previous permission was session-only but
2037 // this needs to be written into the DB. In this case, we have to run
2038 // an Adding operation.
2039 UpdateDB(eOperationAdding, id, origin, aType, aPermission,
2040 aExpireType, aExpireTime, aModificationTime);
2041 } else if (newIsPersistentExpire) {
2042 // This is the a simple update. We care only about the id, the
2043 // permission and expireType/expireTime/modificationTime here. We pass
2044 // dummy values for all other parameters.
2045 UpdateDB(op, id, ""_ns, ""_ns, aPermission, aExpireType, aExpireTime,
2046 aModificationTime);
2050 if (aNotifyOperation == eNotify) {
2051 NotifyObserversWithPermission(aPrincipal, mTypeArray[typeIndex],
2052 aPermission, aExpireType, aExpireTime,
2053 aModificationTime, u"changed");
2056 break;
2058 case eOperationReplacingDefault: {
2059 // this is handling the case when we have an existing permission
2060 // entry that was created as a "default" (and thus isn't in the DB) with
2061 // an explicit permission (that may include UNKNOWN_ACTION.)
2062 // Note we will *not* get here if we are replacing an already replaced
2063 // default value - that is handled as eOperationChanging.
2065 // So this is a hybrid of eOperationAdding (as we are writing a new entry
2066 // to the DB) and eOperationChanging (as we are replacing the in-memory
2067 // repr and sending a "changed" notification).
2069 // We want a new ID even if not writing to the DB, so the modified entry
2070 // in memory doesn't have the magic cIDPermissionIsDefault value.
2071 id = ++mLargestID;
2073 // The default permission being replaced can't have session expiry or
2074 // policy expiry.
2075 NS_ENSURE_TRUE(entry->GetPermissions()[index].mExpireType !=
2076 nsIPermissionManager::EXPIRE_SESSION,
2077 NS_ERROR_UNEXPECTED);
2078 NS_ENSURE_TRUE(entry->GetPermissions()[index].mExpireType !=
2079 nsIPermissionManager::EXPIRE_POLICY,
2080 NS_ERROR_UNEXPECTED);
2081 // We don't support the new entry having any expiry - supporting that
2082 // would make things far more complex and none of the permissions we set
2083 // as a default support that.
2084 NS_ENSURE_TRUE(aExpireType == EXPIRE_NEVER, NS_ERROR_UNEXPECTED);
2086 // update the existing entry in memory.
2087 entry->GetPermissions()[index].mID = id;
2088 entry->GetPermissions()[index].mPermission = aPermission;
2089 entry->GetPermissions()[index].mExpireType = aExpireType;
2090 entry->GetPermissions()[index].mExpireTime = aExpireTime;
2091 entry->GetPermissions()[index].mModificationTime = aModificationTime;
2093 // If requested, create the entry in the DB.
2094 if (aDBOperation == eWriteToDB &&
2095 IsPersistentExpire(aExpireType, aType)) {
2096 UpdateDB(eOperationAdding, id, origin, aType, aPermission, aExpireType,
2097 aExpireTime, aModificationTime);
2100 if (aNotifyOperation == eNotify) {
2101 NotifyObserversWithPermission(aPrincipal, mTypeArray[typeIndex],
2102 aPermission, aExpireType, aExpireTime,
2103 aModificationTime, u"changed");
2106 } break;
2109 return NS_OK;
2112 NS_IMETHODIMP
2113 PermissionManager::RemoveFromPrincipal(nsIPrincipal* aPrincipal,
2114 const nsACString& aType) {
2115 ENSURE_NOT_CHILD_PROCESS;
2116 NS_ENSURE_ARG_POINTER(aPrincipal);
2118 // System principals are never added to the database, no need to remove them.
2119 if (aPrincipal->IsSystemPrincipal()) {
2120 return NS_OK;
2123 // Permissions may not be added to expanded principals.
2124 if (IsExpandedPrincipal(aPrincipal)) {
2125 return NS_ERROR_INVALID_ARG;
2128 // AddInternal() handles removal, just let it do the work
2129 return AddInternal(aPrincipal, aType, nsIPermissionManager::UNKNOWN_ACTION, 0,
2130 nsIPermissionManager::EXPIRE_NEVER, 0, 0, eNotify,
2131 eWriteToDB);
2134 NS_IMETHODIMP
2135 PermissionManager::RemovePermission(nsIPermission* aPerm) {
2136 if (!aPerm) {
2137 return NS_OK;
2139 nsCOMPtr<nsIPrincipal> principal;
2140 nsresult rv = aPerm->GetPrincipal(getter_AddRefs(principal));
2141 NS_ENSURE_SUCCESS(rv, rv);
2143 nsAutoCString type;
2144 rv = aPerm->GetType(type);
2145 NS_ENSURE_SUCCESS(rv, rv);
2147 // Permissions are uniquely identified by their principal and type.
2148 // We remove the permission using these two pieces of data.
2149 return RemoveFromPrincipal(principal, type);
2152 NS_IMETHODIMP
2153 PermissionManager::RemoveAll() {
2154 ENSURE_NOT_CHILD_PROCESS;
2155 return RemoveAllInternal(true);
2158 NS_IMETHODIMP
2159 PermissionManager::RemoveAllSince(int64_t aSince) {
2160 ENSURE_NOT_CHILD_PROCESS;
2161 return RemoveAllModifiedSince(aSince);
2164 template <class T>
2165 nsresult PermissionManager::RemovePermissionEntries(T aCondition) {
2166 EnsureReadCompleted();
2168 Vector<std::tuple<nsCOMPtr<nsIPrincipal>, nsCString, nsCString>, 10> array;
2169 for (const PermissionHashKey& entry : mPermissionTable) {
2170 for (const auto& permEntry : entry.GetPermissions()) {
2171 if (!aCondition(permEntry)) {
2172 continue;
2175 nsCOMPtr<nsIPrincipal> principal;
2176 nsresult rv = GetPrincipalFromOrigin(
2177 entry.GetKey()->mOrigin,
2178 IsOAForceStripPermission(mTypeArray[permEntry.mType]),
2179 getter_AddRefs(principal));
2180 if (NS_FAILED(rv)) {
2181 continue;
2184 if (!array.emplaceBack(principal, mTypeArray[permEntry.mType],
2185 entry.GetKey()->mOrigin)) {
2186 continue;
2191 for (auto& i : array) {
2192 // AddInternal handles removal, so let it do the work...
2193 AddInternal(
2194 std::get<0>(i), std::get<1>(i), nsIPermissionManager::UNKNOWN_ACTION, 0,
2195 nsIPermissionManager::EXPIRE_NEVER, 0, 0, PermissionManager::eNotify,
2196 PermissionManager::eWriteToDB, false, &std::get<2>(i));
2199 // now re-import any defaults as they may now be required if we just deleted
2200 // an override.
2201 ImportLatestDefaults();
2202 return NS_OK;
2205 NS_IMETHODIMP
2206 PermissionManager::RemoveByType(const nsACString& aType) {
2207 ENSURE_NOT_CHILD_PROCESS;
2209 int32_t typeIndex = GetTypeIndex(aType, false);
2210 // If type == -1, the type isn't known,
2211 // so just return NS_OK
2212 if (typeIndex == -1) {
2213 return NS_OK;
2216 return RemovePermissionEntries(
2217 [typeIndex](const PermissionEntry& aPermEntry) {
2218 return static_cast<uint32_t>(typeIndex) == aPermEntry.mType;
2222 NS_IMETHODIMP
2223 PermissionManager::RemoveByTypeSince(const nsACString& aType,
2224 int64_t aModificationTime) {
2225 ENSURE_NOT_CHILD_PROCESS;
2227 int32_t typeIndex = GetTypeIndex(aType, false);
2228 // If type == -1, the type isn't known,
2229 // so just return NS_OK
2230 if (typeIndex == -1) {
2231 return NS_OK;
2234 return RemovePermissionEntries(
2235 [typeIndex, aModificationTime](const PermissionEntry& aPermEntry) {
2236 return uint32_t(typeIndex) == aPermEntry.mType &&
2237 aModificationTime <= aPermEntry.mModificationTime;
2241 void PermissionManager::CloseDB(CloseDBNextOp aNextOp) {
2242 EnsureReadCompleted();
2244 mState = eClosed;
2246 nsCOMPtr<nsIInputStream> defaultsInputStream;
2247 if (aNextOp == eRebuldOnSuccess) {
2248 defaultsInputStream = GetDefaultsInputStream();
2251 RefPtr<PermissionManager> self = this;
2252 mThread->Dispatch(NS_NewRunnableFunction(
2253 "PermissionManager::CloseDB", [self, aNextOp, defaultsInputStream] {
2254 auto data = self->mThreadBoundData.Access();
2255 // Null the statements, this will finalize them.
2256 data->mStmtInsert = nullptr;
2257 data->mStmtDelete = nullptr;
2258 data->mStmtUpdate = nullptr;
2259 if (data->mDBConn) {
2260 DebugOnly<nsresult> rv = data->mDBConn->Close();
2261 MOZ_ASSERT(NS_SUCCEEDED(rv));
2262 data->mDBConn = nullptr;
2264 if (aNextOp == eRebuldOnSuccess) {
2265 self->TryInitDB(true, defaultsInputStream);
2269 if (aNextOp == eShutdown) {
2270 NS_DispatchToMainThread(NS_NewRunnableFunction(
2271 "PermissionManager::MaybeCompleteShutdown",
2272 [self] { self->MaybeCompleteShutdown(); }));
2274 }));
2277 nsresult PermissionManager::RemoveAllFromIPC() {
2278 MOZ_ASSERT(IsChildProcess());
2280 // Remove from memory and notify immediately. Since the in-memory
2281 // database is authoritative, we do not need confirmation from the
2282 // on-disk database to notify observers.
2283 RemoveAllFromMemory();
2285 return NS_OK;
2288 nsresult PermissionManager::RemoveAllInternal(bool aNotifyObservers) {
2289 ENSURE_NOT_CHILD_PROCESS;
2291 EnsureReadCompleted();
2293 // Let's broadcast the removeAll() to any content process.
2294 nsTArray<ContentParent*> parents;
2295 ContentParent::GetAll(parents);
2296 for (ContentParent* parent : parents) {
2297 Unused << parent->SendRemoveAllPermissions();
2300 // Remove from memory and notify immediately. Since the in-memory
2301 // database is authoritative, we do not need confirmation from the
2302 // on-disk database to notify observers.
2303 RemoveAllFromMemory();
2305 // Re-import the defaults
2306 ImportLatestDefaults();
2308 if (aNotifyObservers) {
2309 NotifyObservers(nullptr, u"cleared");
2312 RefPtr<PermissionManager> self = this;
2313 mThread->Dispatch(
2314 NS_NewRunnableFunction("PermissionManager::RemoveAllInternal", [self] {
2315 auto data = self->mThreadBoundData.Access();
2317 if (self->mState == eClosed || !data->mDBConn) {
2318 return;
2321 // clear the db
2322 nsresult rv =
2323 data->mDBConn->ExecuteSimpleSQL("DELETE FROM moz_perms"_ns);
2324 if (NS_WARN_IF(NS_FAILED(rv))) {
2325 NS_DispatchToMainThread(NS_NewRunnableFunction(
2326 "PermissionManager::RemoveAllInternal-Failure",
2327 [self] { self->CloseDB(eRebuldOnSuccess); }));
2329 }));
2331 return NS_OK;
2334 NS_IMETHODIMP
2335 PermissionManager::TestExactPermissionFromPrincipal(nsIPrincipal* aPrincipal,
2336 const nsACString& aType,
2337 uint32_t* aPermission) {
2338 return CommonTestPermission(aPrincipal, -1, aType, aPermission,
2339 nsIPermissionManager::UNKNOWN_ACTION, false, true,
2340 true);
2343 NS_IMETHODIMP
2344 PermissionManager::TestExactPermanentPermission(nsIPrincipal* aPrincipal,
2345 const nsACString& aType,
2346 uint32_t* aPermission) {
2347 return CommonTestPermission(aPrincipal, -1, aType, aPermission,
2348 nsIPermissionManager::UNKNOWN_ACTION, false, true,
2349 false);
2352 nsresult PermissionManager::LegacyTestPermissionFromURI(
2353 nsIURI* aURI, const OriginAttributes* aOriginAttributes,
2354 const nsACString& aType, uint32_t* aPermission) {
2355 return CommonTestPermission(aURI, aOriginAttributes, -1, aType, aPermission,
2356 nsIPermissionManager::UNKNOWN_ACTION, false,
2357 false, true);
2360 NS_IMETHODIMP
2361 PermissionManager::TestPermissionFromPrincipal(nsIPrincipal* aPrincipal,
2362 const nsACString& aType,
2363 uint32_t* aPermission) {
2364 return CommonTestPermission(aPrincipal, -1, aType, aPermission,
2365 nsIPermissionManager::UNKNOWN_ACTION, false,
2366 false, true);
2369 NS_IMETHODIMP
2370 PermissionManager::GetPermissionObject(nsIPrincipal* aPrincipal,
2371 const nsACString& aType,
2372 bool aExactHostMatch,
2373 nsIPermission** aResult) {
2374 NS_ENSURE_ARG_POINTER(aPrincipal);
2375 *aResult = nullptr;
2377 EnsureReadCompleted();
2379 if (aPrincipal->IsSystemPrincipal()) {
2380 return NS_OK;
2383 // Querying the permission object of an nsEP is non-sensical.
2384 if (IsExpandedPrincipal(aPrincipal)) {
2385 return NS_ERROR_INVALID_ARG;
2388 MOZ_ASSERT(PermissionAvailable(aPrincipal, aType));
2390 int32_t typeIndex = GetTypeIndex(aType, false);
2391 // If type == -1, the type isn't known,
2392 // so just return NS_OK
2393 if (typeIndex == -1) return NS_OK;
2395 PermissionHashKey* entry =
2396 GetPermissionHashKey(aPrincipal, typeIndex, aExactHostMatch);
2397 if (!entry) {
2398 return NS_OK;
2401 // We don't call GetPermission(typeIndex) because that returns a fake
2402 // UNKNOWN_ACTION entry if there is no match.
2403 int32_t idx = entry->GetPermissionIndex(typeIndex);
2404 if (-1 == idx) {
2405 return NS_OK;
2408 nsCOMPtr<nsIPrincipal> principal;
2409 nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin,
2410 IsOAForceStripPermission(aType),
2411 getter_AddRefs(principal));
2412 NS_ENSURE_SUCCESS(rv, rv);
2414 PermissionEntry& perm = entry->GetPermissions()[idx];
2415 nsCOMPtr<nsIPermission> r = Permission::Create(
2416 principal, mTypeArray[perm.mType], perm.mPermission, perm.mExpireType,
2417 perm.mExpireTime, perm.mModificationTime);
2418 if (NS_WARN_IF(!r)) {
2419 return NS_ERROR_FAILURE;
2421 r.forget(aResult);
2422 return NS_OK;
2425 nsresult PermissionManager::CommonTestPermissionInternal(
2426 nsIPrincipal* aPrincipal, nsIURI* aURI,
2427 const OriginAttributes* aOriginAttributes, int32_t aTypeIndex,
2428 const nsACString& aType, uint32_t* aPermission, bool aExactHostMatch,
2429 bool aIncludingSession) {
2430 MOZ_ASSERT(aPrincipal || aURI);
2431 NS_ENSURE_ARG_POINTER(aPrincipal || aURI);
2432 MOZ_ASSERT_IF(aPrincipal, !aURI && !aOriginAttributes);
2433 MOZ_ASSERT_IF(aURI || aOriginAttributes, !aPrincipal);
2435 EnsureReadCompleted();
2437 #ifdef DEBUG
2439 nsCOMPtr<nsIPrincipal> prin = aPrincipal;
2440 if (!prin) {
2441 if (aURI) {
2442 prin = BasePrincipal::CreateContentPrincipal(aURI, OriginAttributes());
2445 MOZ_ASSERT(prin);
2446 MOZ_ASSERT(PermissionAvailable(prin, aType));
2448 #endif
2450 PermissionHashKey* entry =
2451 aPrincipal ? GetPermissionHashKey(aPrincipal, aTypeIndex, aExactHostMatch)
2452 : GetPermissionHashKey(aURI, aOriginAttributes, aTypeIndex,
2453 aExactHostMatch);
2454 if (!entry || (!aIncludingSession &&
2455 entry->GetPermission(aTypeIndex).mNonSessionExpireType ==
2456 nsIPermissionManager::EXPIRE_SESSION)) {
2457 return NS_OK;
2460 *aPermission = aIncludingSession
2461 ? entry->GetPermission(aTypeIndex).mPermission
2462 : entry->GetPermission(aTypeIndex).mNonSessionPermission;
2464 return NS_OK;
2467 // Helper function to filter permissions using a condition function.
2468 template <class T>
2469 nsresult PermissionManager::GetPermissionEntries(
2470 T aCondition, nsTArray<RefPtr<nsIPermission>>& aResult) {
2471 aResult.Clear();
2472 if (XRE_IsContentProcess()) {
2473 NS_WARNING(
2474 "Iterating over all permissions is not available in the "
2475 "content process, as not all permissions may be available.");
2476 return NS_ERROR_NOT_AVAILABLE;
2479 EnsureReadCompleted();
2481 for (const PermissionHashKey& entry : mPermissionTable) {
2482 for (const auto& permEntry : entry.GetPermissions()) {
2483 // Given how "default" permissions work and the possibility of them being
2484 // overridden with UNKNOWN_ACTION, we might see this value here - but we
2485 // do *not* want to return them via the enumerator.
2486 if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
2487 continue;
2490 // If the permission is expired, skip it. We're not deleting it here
2491 // because we're iterating over a lot of permissions.
2492 // It will be removed as part of the daily maintenance later.
2493 if (HasExpired(permEntry.mExpireType, permEntry.mExpireTime)) {
2494 continue;
2497 if (!aCondition(permEntry)) {
2498 continue;
2501 nsCOMPtr<nsIPrincipal> principal;
2502 nsresult rv = GetPrincipalFromOrigin(
2503 entry.GetKey()->mOrigin,
2504 IsOAForceStripPermission(mTypeArray[permEntry.mType]),
2505 getter_AddRefs(principal));
2506 if (NS_FAILED(rv)) {
2507 continue;
2510 RefPtr<nsIPermission> permission = Permission::Create(
2511 principal, mTypeArray[permEntry.mType], permEntry.mPermission,
2512 permEntry.mExpireType, permEntry.mExpireTime,
2513 permEntry.mModificationTime);
2514 if (NS_WARN_IF(!permission)) {
2515 continue;
2517 aResult.AppendElement(std::move(permission));
2521 return NS_OK;
2524 NS_IMETHODIMP PermissionManager::GetAll(
2525 nsTArray<RefPtr<nsIPermission>>& aResult) {
2526 return GetPermissionEntries(
2527 [](const PermissionEntry& aPermEntry) { return true; }, aResult);
2530 NS_IMETHODIMP PermissionManager::GetAllByTypeSince(
2531 const nsACString& aPrefix, int64_t aSince,
2532 nsTArray<RefPtr<nsIPermission>>& aResult) {
2533 // Check that aSince is a reasonable point in time, not in the future
2534 if (aSince > (PR_Now() / PR_USEC_PER_MSEC)) {
2535 return NS_ERROR_INVALID_ARG;
2537 return GetPermissionEntries(
2538 [&](const PermissionEntry& aPermEntry) {
2539 return mTypeArray[aPermEntry.mType].Equals(aPrefix) &&
2540 aSince <= aPermEntry.mModificationTime;
2542 aResult);
2545 NS_IMETHODIMP PermissionManager::GetAllWithTypePrefix(
2546 const nsACString& aPrefix, nsTArray<RefPtr<nsIPermission>>& aResult) {
2547 return GetPermissionEntries(
2548 [&](const PermissionEntry& aPermEntry) {
2549 return StringBeginsWith(mTypeArray[aPermEntry.mType], aPrefix);
2551 aResult);
2554 NS_IMETHODIMP PermissionManager::GetAllByTypes(
2555 const nsTArray<nsCString>& aTypes,
2556 nsTArray<RefPtr<nsIPermission>>& aResult) {
2557 if (aTypes.IsEmpty()) {
2558 return NS_OK;
2561 return GetPermissionEntries(
2562 [&](const PermissionEntry& aPermEntry) {
2563 return aTypes.Contains(mTypeArray[aPermEntry.mType]);
2565 aResult);
2568 nsresult PermissionManager::GetAllForPrincipalHelper(
2569 nsIPrincipal* aPrincipal, bool aSiteScopePermissions,
2570 nsTArray<RefPtr<nsIPermission>>& aResult) {
2571 nsresult rv;
2572 RefPtr<PermissionKey> key = PermissionKey::CreateFromPrincipal(
2573 aPrincipal, false, aSiteScopePermissions, rv);
2574 if (!key) {
2575 MOZ_ASSERT(NS_FAILED(rv));
2576 return rv;
2578 PermissionHashKey* entry = mPermissionTable.GetEntry(key);
2580 nsTArray<PermissionEntry> strippedPerms;
2581 rv = GetStripPermsForPrincipal(aPrincipal, aSiteScopePermissions,
2582 strippedPerms);
2583 if (NS_FAILED(rv)) {
2584 return rv;
2587 if (entry) {
2588 for (const auto& permEntry : entry->GetPermissions()) {
2589 // Only return custom permissions
2590 if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
2591 continue;
2594 // If the permission is expired, skip it. We're not deleting it here
2595 // because we're iterating over a lot of permissions.
2596 // It will be removed as part of the daily maintenance later.
2597 if (HasExpired(permEntry.mExpireType, permEntry.mExpireTime)) {
2598 continue;
2601 // Make sure that we only get site scoped permissions if this
2602 // helper is being invoked for that purpose.
2603 if (aSiteScopePermissions !=
2604 IsSiteScopedPermission(mTypeArray[permEntry.mType])) {
2605 continue;
2608 // Stripped principal permissions overwrite regular ones
2609 // For each permission check if there is a stripped permission we should
2610 // use instead
2611 PermissionEntry perm = permEntry;
2612 nsTArray<PermissionEntry>::index_type index = 0;
2613 for (const auto& strippedPerm : strippedPerms) {
2614 if (strippedPerm.mType == permEntry.mType) {
2615 perm = strippedPerm;
2616 strippedPerms.RemoveElementAt(index);
2617 break;
2619 index++;
2622 RefPtr<nsIPermission> permission = Permission::Create(
2623 aPrincipal, mTypeArray[perm.mType], perm.mPermission,
2624 perm.mExpireType, perm.mExpireTime, perm.mModificationTime);
2625 if (NS_WARN_IF(!permission)) {
2626 continue;
2628 aResult.AppendElement(permission);
2632 for (const auto& perm : strippedPerms) {
2633 RefPtr<nsIPermission> permission = Permission::Create(
2634 aPrincipal, mTypeArray[perm.mType], perm.mPermission, perm.mExpireType,
2635 perm.mExpireTime, perm.mModificationTime);
2636 if (NS_WARN_IF(!permission)) {
2637 continue;
2639 aResult.AppendElement(permission);
2642 return NS_OK;
2645 NS_IMETHODIMP
2646 PermissionManager::GetAllForPrincipal(
2647 nsIPrincipal* aPrincipal, nsTArray<RefPtr<nsIPermission>>& aResult) {
2648 nsresult rv;
2649 aResult.Clear();
2650 EnsureReadCompleted();
2652 MOZ_ASSERT(PermissionAvailable(aPrincipal, ""_ns));
2654 // First, append the non-site-scoped permissions.
2655 rv = GetAllForPrincipalHelper(aPrincipal, false, aResult);
2656 NS_ENSURE_SUCCESS(rv, rv);
2658 // Second, append the site-scoped permissions.
2659 return GetAllForPrincipalHelper(aPrincipal, true, aResult);
2662 NS_IMETHODIMP PermissionManager::Observe(nsISupports* aSubject,
2663 const char* aTopic,
2664 const char16_t* someData) {
2665 ENSURE_NOT_CHILD_PROCESS;
2667 if (!nsCRT::strcmp(aTopic, "profile-do-change") && !mPermissionsFile) {
2668 // profile startup is complete, and we didn't have the permissions file
2669 // before; init the db from the new location
2670 InitDB(false);
2671 } else if (!nsCRT::strcmp(aTopic, "testonly-reload-permissions-from-disk")) {
2672 // Testing mechanism to reload all permissions from disk. Because the
2673 // permission manager automatically initializes itself at startup, tests
2674 // that directly manipulate the permissions database need some way to reload
2675 // the database for their changes to have any effect. This mechanism was
2676 // introduced when moving the permissions manager from on-demand startup to
2677 // always being initialized. This is not guarded by a pref because it's not
2678 // dangerous to reload permissions from disk, just bad for performance.
2679 RemoveAllFromMemory();
2680 CloseDB(eNone);
2681 InitDB(false);
2682 } else if (!nsCRT::strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY)) {
2683 PerformIdleDailyMaintenance();
2686 return NS_OK;
2689 nsresult PermissionManager::RemoveAllModifiedSince(int64_t aModificationTime) {
2690 ENSURE_NOT_CHILD_PROCESS;
2691 // Skip remove calls for default permissions to avoid
2692 // creating UNKNOWN_ACTION overrides in AddInternal
2693 return RemovePermissionEntries(
2694 [aModificationTime](const PermissionEntry& aPermEntry) {
2695 return aModificationTime <= aPermEntry.mModificationTime &&
2696 aPermEntry.mID != cIDPermissionIsDefault;
2700 NS_IMETHODIMP
2701 PermissionManager::RemovePermissionsWithAttributes(const nsAString& aPattern) {
2702 ENSURE_NOT_CHILD_PROCESS;
2703 OriginAttributesPattern pattern;
2704 if (!pattern.Init(aPattern)) {
2705 return NS_ERROR_INVALID_ARG;
2708 return RemovePermissionsWithAttributes(pattern);
2711 nsresult PermissionManager::RemovePermissionsWithAttributes(
2712 OriginAttributesPattern& aPattern) {
2713 EnsureReadCompleted();
2715 Vector<std::tuple<nsCOMPtr<nsIPrincipal>, nsCString, nsCString>, 10>
2716 permissions;
2717 for (const PermissionHashKey& entry : mPermissionTable) {
2718 nsCOMPtr<nsIPrincipal> principal;
2719 nsresult rv = GetPrincipalFromOrigin(entry.GetKey()->mOrigin, false,
2720 getter_AddRefs(principal));
2721 if (NS_FAILED(rv)) {
2722 continue;
2725 if (!aPattern.Matches(principal->OriginAttributesRef())) {
2726 continue;
2729 for (const auto& permEntry : entry.GetPermissions()) {
2730 if (!permissions.emplaceBack(principal, mTypeArray[permEntry.mType],
2731 entry.GetKey()->mOrigin)) {
2732 continue;
2737 for (auto& i : permissions) {
2738 AddInternal(
2739 std::get<0>(i), std::get<1>(i), nsIPermissionManager::UNKNOWN_ACTION, 0,
2740 nsIPermissionManager::EXPIRE_NEVER, 0, 0, PermissionManager::eNotify,
2741 PermissionManager::eWriteToDB, false, &std::get<2>(i));
2744 return NS_OK;
2747 nsresult PermissionManager::GetStripPermsForPrincipal(
2748 nsIPrincipal* aPrincipal, bool aSiteScopePermissions,
2749 nsTArray<PermissionEntry>& aResult) {
2750 aResult.Clear();
2751 aResult.SetCapacity(kStripOAPermissions.size());
2753 #ifdef __clang__
2754 # pragma clang diagnostic push
2755 # pragma clang diagnostic ignored "-Wunreachable-code-return"
2756 #endif
2757 // No special strip permissions
2758 if (kStripOAPermissions.empty()) {
2759 return NS_OK;
2761 #ifdef __clang__
2762 # pragma clang diagnostic pop
2763 #endif
2765 nsresult rv;
2766 // Create a key for the principal, but strip any origin attributes.
2767 // The key must be created aware of whether or not we are scoping to site.
2768 RefPtr<PermissionKey> key = PermissionKey::CreateFromPrincipal(
2769 aPrincipal, true, aSiteScopePermissions, rv);
2770 if (!key) {
2771 MOZ_ASSERT(NS_FAILED(rv));
2772 return rv;
2775 PermissionHashKey* hashKey = mPermissionTable.GetEntry(key);
2776 if (!hashKey) {
2777 return NS_OK;
2780 for (const auto& permType : kStripOAPermissions) {
2781 // if the permission type's site scoping does not match this function call,
2782 // we don't care about it, so continue.
2783 // As of time of writing, this never happens when aSiteScopePermissions
2784 // is true because there is no common permission between kStripOAPermissions
2785 // and kSiteScopedPermissions
2786 if (aSiteScopePermissions != IsSiteScopedPermission(permType)) {
2787 continue;
2789 int32_t index = GetTypeIndex(permType, false);
2790 if (index == -1) {
2791 continue;
2793 PermissionEntry perm = hashKey->GetPermission(index);
2794 if (perm.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
2795 continue;
2797 aResult.AppendElement(perm);
2800 return NS_OK;
2803 int32_t PermissionManager::GetTypeIndex(const nsACString& aType, bool aAdd) {
2804 for (uint32_t i = 0; i < mTypeArray.length(); ++i) {
2805 if (mTypeArray[i].Equals(aType)) {
2806 return i;
2810 if (!aAdd) {
2811 // Not found, but that is ok - we were just looking.
2812 return -1;
2815 // This type was not registered before.
2816 // append it to the array, without copy-constructing the string
2817 if (!mTypeArray.emplaceBack(aType)) {
2818 return -1;
2821 return mTypeArray.length() - 1;
2824 PermissionManager::PermissionHashKey* PermissionManager::GetPermissionHashKey(
2825 nsIPrincipal* aPrincipal, uint32_t aType, bool aExactHostMatch) {
2826 EnsureReadCompleted();
2828 MOZ_ASSERT(PermissionAvailable(aPrincipal, mTypeArray[aType]));
2830 nsresult rv;
2831 RefPtr<PermissionKey> key = PermissionKey::CreateFromPrincipal(
2832 aPrincipal, IsOAForceStripPermission(mTypeArray[aType]),
2833 IsSiteScopedPermission(mTypeArray[aType]), rv);
2834 if (!key) {
2835 return nullptr;
2838 PermissionHashKey* entry = mPermissionTable.GetEntry(key);
2840 if (entry) {
2841 PermissionEntry permEntry = entry->GetPermission(aType);
2843 // if the entry is expired, remove and keep looking for others.
2844 if (HasExpired(permEntry.mExpireType, permEntry.mExpireTime)) {
2845 entry = nullptr;
2846 RemoveFromPrincipal(aPrincipal, mTypeArray[aType]);
2847 } else if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
2848 entry = nullptr;
2852 if (entry) {
2853 return entry;
2856 // If aExactHostMatch wasn't true, we can check if the base domain has a
2857 // permission entry.
2858 if (!aExactHostMatch) {
2859 nsCOMPtr<nsIPrincipal> principal = aPrincipal->GetNextSubDomainPrincipal();
2860 if (principal) {
2861 return GetPermissionHashKey(principal, aType, aExactHostMatch);
2865 // No entry, really...
2866 return nullptr;
2869 PermissionManager::PermissionHashKey* PermissionManager::GetPermissionHashKey(
2870 nsIURI* aURI, const OriginAttributes* aOriginAttributes, uint32_t aType,
2871 bool aExactHostMatch) {
2872 MOZ_ASSERT(aURI);
2874 #ifdef DEBUG
2876 nsCOMPtr<nsIPrincipal> principal;
2877 nsresult rv = NS_OK;
2878 if (aURI) {
2879 rv = GetPrincipal(aURI, getter_AddRefs(principal));
2881 MOZ_ASSERT_IF(NS_SUCCEEDED(rv),
2882 PermissionAvailable(principal, mTypeArray[aType]));
2884 #endif
2886 nsresult rv;
2887 RefPtr<PermissionKey> key;
2889 if (aOriginAttributes) {
2890 key = PermissionKey::CreateFromURIAndOriginAttributes(
2891 aURI, aOriginAttributes, IsOAForceStripPermission(mTypeArray[aType]),
2892 rv);
2893 } else {
2894 key = PermissionKey::CreateFromURI(aURI, rv);
2897 if (!key) {
2898 return nullptr;
2901 PermissionHashKey* entry = mPermissionTable.GetEntry(key);
2903 if (entry) {
2904 PermissionEntry permEntry = entry->GetPermission(aType);
2906 // if the entry is expired, remove and keep looking for others.
2907 if (HasExpired(permEntry.mExpireType, permEntry.mExpireTime)) {
2908 entry = nullptr;
2909 // If we need to remove a permission we mint a principal. This is a bit
2910 // inefficient, but hopefully this code path isn't super common.
2911 nsCOMPtr<nsIPrincipal> principal;
2912 if (aURI) {
2913 nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
2914 if (NS_WARN_IF(NS_FAILED(rv))) {
2915 return nullptr;
2918 RemoveFromPrincipal(principal, mTypeArray[aType]);
2919 } else if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
2920 entry = nullptr;
2924 if (entry) {
2925 return entry;
2928 // If aExactHostMatch wasn't true, we can check if the base domain has a
2929 // permission entry.
2930 if (!aExactHostMatch) {
2931 nsCOMPtr<nsIURI> uri;
2932 if (aURI) {
2933 uri = GetNextSubDomainURI(aURI);
2935 if (uri) {
2936 return GetPermissionHashKey(uri, aOriginAttributes, aType,
2937 aExactHostMatch);
2941 // No entry, really...
2942 return nullptr;
2945 nsresult PermissionManager::RemoveAllFromMemory() {
2946 mLargestID = 0;
2947 mTypeArray.clear();
2948 mPermissionTable.Clear();
2950 return NS_OK;
2953 // wrapper function for mangling (host,type,perm,expireType,expireTime)
2954 // set into an nsIPermission.
2955 void PermissionManager::NotifyObserversWithPermission(
2956 nsIPrincipal* aPrincipal, const nsACString& aType, uint32_t aPermission,
2957 uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime,
2958 const char16_t* aData) {
2959 nsCOMPtr<nsIPermission> permission =
2960 Permission::Create(aPrincipal, aType, aPermission, aExpireType,
2961 aExpireTime, aModificationTime);
2962 if (permission) NotifyObservers(permission, aData);
2965 // notify observers that the permission list changed. there are four possible
2966 // values for aData:
2967 // "deleted" means a permission was deleted. aPermission is the deleted
2968 // permission. "added" means a permission was added. aPermission is the added
2969 // permission. "changed" means a permission was altered. aPermission is the new
2970 // permission. "cleared" means the entire permission list was cleared.
2971 // aPermission is null.
2972 void PermissionManager::NotifyObservers(nsIPermission* aPermission,
2973 const char16_t* aData) {
2974 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
2975 if (observerService)
2976 observerService->NotifyObservers(aPermission, kPermissionChangeNotification,
2977 aData);
2980 nsresult PermissionManager::Read(const MonitorAutoLock& aProofOfLock) {
2981 ENSURE_NOT_CHILD_PROCESS;
2983 MOZ_ASSERT(!NS_IsMainThread());
2984 auto data = mThreadBoundData.Access();
2986 nsresult rv;
2987 bool hasResult;
2988 nsCOMPtr<mozIStorageStatement> stmt;
2990 // Let's retrieve the last used ID.
2991 rv = data->mDBConn->CreateStatement(
2992 nsLiteralCString("SELECT MAX(id) FROM moz_perms"), getter_AddRefs(stmt));
2993 NS_ENSURE_SUCCESS(rv, rv);
2995 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
2996 int64_t id = stmt->AsInt64(0);
2997 mLargestID = id;
3000 rv = data->mDBConn->CreateStatement(
3001 nsLiteralCString(
3002 "SELECT id, origin, type, permission, expireType, "
3003 "expireTime, modificationTime "
3004 "FROM moz_perms WHERE expireType != ?1 OR expireTime > ?2"),
3005 getter_AddRefs(stmt));
3006 NS_ENSURE_SUCCESS(rv, rv);
3008 rv = stmt->BindInt32ByIndex(0, nsIPermissionManager::EXPIRE_TIME);
3009 NS_ENSURE_SUCCESS(rv, rv);
3011 rv = stmt->BindInt64ByIndex(1, EXPIRY_NOW);
3012 NS_ENSURE_SUCCESS(rv, rv);
3014 bool readError = false;
3016 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
3017 ReadEntry entry;
3019 // explicitly set our entry id counter for use in AddInternal(),
3020 // and keep track of the largest id so we know where to pick up.
3021 entry.mId = stmt->AsInt64(0);
3022 MOZ_ASSERT(entry.mId <= mLargestID);
3024 rv = stmt->GetUTF8String(1, entry.mOrigin);
3025 if (NS_FAILED(rv)) {
3026 readError = true;
3027 continue;
3030 rv = stmt->GetUTF8String(2, entry.mType);
3031 if (NS_FAILED(rv)) {
3032 readError = true;
3033 continue;
3036 entry.mPermission = stmt->AsInt32(3);
3037 entry.mExpireType = stmt->AsInt32(4);
3039 // convert into int64_t values (milliseconds)
3040 entry.mExpireTime = stmt->AsInt64(5);
3041 entry.mModificationTime = stmt->AsInt64(6);
3043 entry.mFromMigration = false;
3045 mReadEntries.AppendElement(entry);
3048 if (readError) {
3049 NS_ERROR("Error occured while reading the permissions database!");
3050 return NS_ERROR_FAILURE;
3053 return NS_OK;
3056 void PermissionManager::CompleteMigrations() {
3057 MOZ_ASSERT(NS_IsMainThread());
3058 MOZ_ASSERT(mState == eReady);
3060 nsresult rv;
3062 nsTArray<MigrationEntry> entries;
3064 MonitorAutoLock lock(mMonitor);
3065 entries = std::move(mMigrationEntries);
3068 for (const MigrationEntry& entry : entries) {
3069 rv = UpgradeHostToOriginAndInsert(
3070 entry.mHost, entry.mType, entry.mPermission, entry.mExpireType,
3071 entry.mExpireTime, entry.mModificationTime, entry.mIsInBrowserElement,
3072 [&](const nsACString& aOrigin, const nsCString& aType,
3073 uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
3074 int64_t aModificationTime) {
3075 MaybeAddReadEntryFromMigration(aOrigin, aType, aPermission,
3076 aExpireType, aExpireTime,
3077 aModificationTime, entry.mId);
3078 return NS_OK;
3080 Unused << NS_WARN_IF(NS_FAILED(rv));
3084 void PermissionManager::CompleteRead() {
3085 MOZ_ASSERT(NS_IsMainThread());
3086 MOZ_ASSERT(mState == eReady);
3088 nsresult rv;
3090 nsTArray<ReadEntry> entries;
3092 MonitorAutoLock lock(mMonitor);
3093 entries = std::move(mReadEntries);
3096 for (const ReadEntry& entry : entries) {
3097 nsCOMPtr<nsIPrincipal> principal;
3098 rv = GetPrincipalFromOrigin(entry.mOrigin,
3099 IsOAForceStripPermission(entry.mType),
3100 getter_AddRefs(principal));
3101 if (NS_WARN_IF(NS_FAILED(rv))) {
3102 continue;
3105 DBOperationType op = entry.mFromMigration ? eWriteToDB : eNoDBOperation;
3107 rv = AddInternal(principal, entry.mType, entry.mPermission, entry.mId,
3108 entry.mExpireType, entry.mExpireTime,
3109 entry.mModificationTime, eDontNotify, op, false,
3110 &entry.mOrigin);
3111 Unused << NS_WARN_IF(NS_FAILED(rv));
3115 void PermissionManager::MaybeAddReadEntryFromMigration(
3116 const nsACString& aOrigin, const nsCString& aType, uint32_t aPermission,
3117 uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime,
3118 int64_t aId) {
3119 MonitorAutoLock lock(mMonitor);
3121 // We convert a migration to a ReadEntry only if we don't have an existing
3122 // ReadEntry for the same origin + type.
3123 for (const ReadEntry& entry : mReadEntries) {
3124 if (entry.mOrigin == aOrigin && entry.mType == aType) {
3125 return;
3129 ReadEntry entry;
3130 entry.mId = aId;
3131 entry.mOrigin = aOrigin;
3132 entry.mType = aType;
3133 entry.mPermission = aPermission;
3134 entry.mExpireType = aExpireType;
3135 entry.mExpireTime = aExpireTime;
3136 entry.mModificationTime = aModificationTime;
3137 entry.mFromMigration = true;
3139 mReadEntries.AppendElement(entry);
3142 void PermissionManager::UpdateDB(OperationType aOp, int64_t aID,
3143 const nsACString& aOrigin,
3144 const nsACString& aType, uint32_t aPermission,
3145 uint32_t aExpireType, int64_t aExpireTime,
3146 int64_t aModificationTime) {
3147 ENSURE_NOT_CHILD_PROCESS_NORET;
3149 MOZ_ASSERT(NS_IsMainThread());
3150 EnsureReadCompleted();
3152 nsCString origin(aOrigin);
3153 nsCString type(aType);
3155 RefPtr<PermissionManager> self = this;
3156 mThread->Dispatch(NS_NewRunnableFunction(
3157 "PermissionManager::UpdateDB",
3158 [self, aOp, aID, origin, type, aPermission, aExpireType, aExpireTime,
3159 aModificationTime] {
3160 nsresult rv;
3162 auto data = self->mThreadBoundData.Access();
3164 if (self->mState == eClosed || !data->mDBConn) {
3165 // no statement is ok - just means we don't have a profile
3166 return;
3169 mozIStorageStatement* stmt = nullptr;
3170 switch (aOp) {
3171 case eOperationAdding: {
3172 stmt = data->mStmtInsert;
3174 rv = stmt->BindInt64ByIndex(0, aID);
3175 if (NS_FAILED(rv)) break;
3177 rv = stmt->BindUTF8StringByIndex(1, origin);
3178 if (NS_FAILED(rv)) break;
3180 rv = stmt->BindUTF8StringByIndex(2, type);
3181 if (NS_FAILED(rv)) break;
3183 rv = stmt->BindInt32ByIndex(3, aPermission);
3184 if (NS_FAILED(rv)) break;
3186 rv = stmt->BindInt32ByIndex(4, aExpireType);
3187 if (NS_FAILED(rv)) break;
3189 rv = stmt->BindInt64ByIndex(5, aExpireTime);
3190 if (NS_FAILED(rv)) break;
3192 rv = stmt->BindInt64ByIndex(6, aModificationTime);
3193 break;
3196 case eOperationRemoving: {
3197 stmt = data->mStmtDelete;
3198 rv = stmt->BindInt64ByIndex(0, aID);
3199 break;
3202 case eOperationChanging: {
3203 stmt = data->mStmtUpdate;
3205 rv = stmt->BindInt64ByIndex(0, aID);
3206 if (NS_FAILED(rv)) break;
3208 rv = stmt->BindInt32ByIndex(1, aPermission);
3209 if (NS_FAILED(rv)) break;
3211 rv = stmt->BindInt32ByIndex(2, aExpireType);
3212 if (NS_FAILED(rv)) break;
3214 rv = stmt->BindInt64ByIndex(3, aExpireTime);
3215 if (NS_FAILED(rv)) break;
3217 rv = stmt->BindInt64ByIndex(4, aModificationTime);
3218 break;
3221 default: {
3222 MOZ_ASSERT_UNREACHABLE("need a valid operation in UpdateDB()!");
3223 rv = NS_ERROR_UNEXPECTED;
3224 break;
3228 if (NS_FAILED(rv)) {
3229 NS_WARNING("db change failed!");
3230 return;
3233 rv = stmt->Execute();
3234 MOZ_ASSERT(NS_SUCCEEDED(rv));
3235 }));
3238 bool PermissionManager::GetPermissionsFromOriginOrKey(
3239 const nsACString& aOrigin, const nsACString& aKey,
3240 nsTArray<IPC::Permission>& aPerms) {
3241 EnsureReadCompleted();
3243 aPerms.Clear();
3244 if (NS_WARN_IF(XRE_IsContentProcess())) {
3245 return false;
3248 for (const PermissionHashKey& entry : mPermissionTable) {
3249 nsAutoCString permissionKey;
3250 if (aOrigin.IsEmpty()) {
3251 // We can't check for individual OA strip perms here.
3252 // Don't force strip origin attributes.
3253 GetKeyForOrigin(entry.GetKey()->mOrigin, false, false, permissionKey);
3255 // If the keys don't match, and we aren't getting the default "" key, then
3256 // we can exit early. We have to keep looking if we're getting the default
3257 // key, as we may see a preload permission which should be transmitted.
3258 if (aKey != permissionKey && !aKey.IsEmpty()) {
3259 continue;
3261 } else if (aOrigin != entry.GetKey()->mOrigin) {
3262 // If the origins don't match, then we can exit early. We have to keep
3263 // looking if we're getting the default origin, as we may see a preload
3264 // permission which should be transmitted.
3265 continue;
3268 for (const auto& permEntry : entry.GetPermissions()) {
3269 // Given how "default" permissions work and the possibility of them
3270 // being overridden with UNKNOWN_ACTION, we might see this value here -
3271 // but we do not want to send it to the content process.
3272 if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
3273 continue;
3276 bool isPreload = IsPreloadPermission(mTypeArray[permEntry.mType]);
3277 bool shouldAppend;
3278 if (aOrigin.IsEmpty()) {
3279 shouldAppend = (isPreload && aKey.IsEmpty()) ||
3280 (!isPreload && aKey == permissionKey);
3281 } else {
3282 shouldAppend = (!isPreload && aOrigin == entry.GetKey()->mOrigin);
3284 if (shouldAppend) {
3285 aPerms.AppendElement(
3286 IPC::Permission(entry.GetKey()->mOrigin,
3287 mTypeArray[permEntry.mType], permEntry.mPermission,
3288 permEntry.mExpireType, permEntry.mExpireTime));
3293 return true;
3296 void PermissionManager::SetPermissionsWithKey(
3297 const nsACString& aPermissionKey, nsTArray<IPC::Permission>& aPerms) {
3298 if (NS_WARN_IF(XRE_IsParentProcess())) {
3299 return;
3302 RefPtr<GenericNonExclusivePromise::Private> promise;
3303 bool foundKey =
3304 mPermissionKeyPromiseMap.Get(aPermissionKey, getter_AddRefs(promise));
3305 if (promise) {
3306 MOZ_ASSERT(foundKey);
3307 // NOTE: This will resolve asynchronously, so we can mark it as resolved
3308 // now, and be confident that we will have filled in the database before any
3309 // callbacks run.
3310 promise->Resolve(true, __func__);
3311 } else if (foundKey) {
3312 // NOTE: We shouldn't be sent two InitializePermissionsWithKey for the same
3313 // key, but it's possible.
3314 return;
3316 mPermissionKeyPromiseMap.InsertOrUpdate(
3317 aPermissionKey, RefPtr<GenericNonExclusivePromise::Private>{});
3319 // Add the permissions locally to our process
3320 for (IPC::Permission& perm : aPerms) {
3321 nsCOMPtr<nsIPrincipal> principal;
3322 nsresult rv =
3323 GetPrincipalFromOrigin(perm.origin, IsOAForceStripPermission(perm.type),
3324 getter_AddRefs(principal));
3325 if (NS_WARN_IF(NS_FAILED(rv))) {
3326 continue;
3329 #ifdef DEBUG
3330 nsAutoCString permissionKey;
3331 GetKeyForPermission(principal, perm.type, permissionKey);
3332 MOZ_ASSERT(permissionKey == aPermissionKey,
3333 "The permission keys which were sent over should match!");
3334 #endif
3336 // The child process doesn't care about modification times - it neither
3337 // reads nor writes, nor removes them based on the date - so 0 (which
3338 // will end up as now()) is fine.
3339 uint64_t modificationTime = 0;
3340 AddInternal(principal, perm.type, perm.capability, 0, perm.expireType,
3341 perm.expireTime, modificationTime, eNotify, eNoDBOperation,
3342 true /* ignoreSessionPermissions */);
3346 /* static */
3347 nsresult PermissionManager::GetKeyForOrigin(const nsACString& aOrigin,
3348 bool aForceStripOA,
3349 bool aSiteScopePermissions,
3350 nsACString& aKey) {
3351 aKey.Truncate();
3353 // We only key origins for http, https, and ftp URIs. All origins begin with
3354 // the URL which they apply to, which means that they should begin with their
3355 // scheme in the case where they are one of these interesting URIs. We don't
3356 // want to actually parse the URL here however, because this can be called on
3357 // hot paths.
3358 if (!StringBeginsWith(aOrigin, "http:"_ns) &&
3359 !StringBeginsWith(aOrigin, "https:"_ns) &&
3360 !StringBeginsWith(aOrigin, "ftp:"_ns)) {
3361 return NS_OK;
3364 // We need to look at the originAttributes if they are present, to make sure
3365 // to remove any which we don't want. We put the rest of the origin, not
3366 // including the attributes, into the key.
3367 OriginAttributes attrs;
3368 if (!attrs.PopulateFromOrigin(aOrigin, aKey)) {
3369 aKey.Truncate();
3370 return NS_OK;
3373 MaybeStripOriginAttributes(aForceStripOA, attrs);
3375 #ifdef DEBUG
3376 // Parse the origin string into a principal, and extract some useful
3377 // information from it for assertions.
3378 nsCOMPtr<nsIPrincipal> dbgPrincipal;
3379 MOZ_ALWAYS_SUCCEEDS(GetPrincipalFromOrigin(aOrigin, aForceStripOA,
3380 getter_AddRefs(dbgPrincipal)));
3381 MOZ_ASSERT(dbgPrincipal->SchemeIs("http") ||
3382 dbgPrincipal->SchemeIs("https") || dbgPrincipal->SchemeIs("ftp"));
3383 MOZ_ASSERT(dbgPrincipal->OriginAttributesRef() == attrs);
3384 #endif
3386 // If it is needed, turn the origin into its site-origin
3387 if (aSiteScopePermissions) {
3388 nsCOMPtr<nsIURI> uri;
3389 nsresult rv = NS_NewURI(getter_AddRefs(uri), aKey);
3390 if (!NS_WARN_IF(NS_FAILED(rv))) {
3391 nsCString site;
3392 rv = nsEffectiveTLDService::GetInstance()->GetSite(uri, site);
3393 if (!NS_WARN_IF(NS_FAILED(rv))) {
3394 aKey = site;
3399 // Append the stripped suffix to the output origin key.
3400 nsAutoCString suffix;
3401 attrs.CreateSuffix(suffix);
3402 aKey.Append(suffix);
3404 return NS_OK;
3407 /* static */
3408 nsresult PermissionManager::GetKeyForPrincipal(nsIPrincipal* aPrincipal,
3409 bool aForceStripOA,
3410 bool aSiteScopePermissions,
3411 nsACString& aKey) {
3412 nsAutoCString origin;
3413 nsresult rv = aPrincipal->GetOrigin(origin);
3414 if (NS_WARN_IF(NS_FAILED(rv))) {
3415 aKey.Truncate();
3416 return rv;
3418 return GetKeyForOrigin(origin, aForceStripOA, aSiteScopePermissions, aKey);
3421 /* static */
3422 nsresult PermissionManager::GetKeyForPermission(nsIPrincipal* aPrincipal,
3423 const nsACString& aType,
3424 nsACString& aKey) {
3425 // Preload permissions have the "" key.
3426 if (IsPreloadPermission(aType)) {
3427 aKey.Truncate();
3428 return NS_OK;
3431 return GetKeyForPrincipal(aPrincipal, IsOAForceStripPermission(aType),
3432 IsSiteScopedPermission(aType), aKey);
3435 /* static */
3436 nsTArray<std::pair<nsCString, nsCString>>
3437 PermissionManager::GetAllKeysForPrincipal(nsIPrincipal* aPrincipal) {
3438 MOZ_ASSERT(aPrincipal);
3440 nsTArray<std::pair<nsCString, nsCString>> pairs;
3441 nsCOMPtr<nsIPrincipal> prin = aPrincipal;
3443 while (prin) {
3444 // Add the pair to the list
3445 std::pair<nsCString, nsCString>* pair =
3446 pairs.AppendElement(std::make_pair(""_ns, ""_ns));
3447 // We can't check for individual OA strip perms here.
3448 // Don't force strip origin attributes.
3449 GetKeyForPrincipal(prin, false, false, pair->first);
3451 // On origins with a derived key set to an empty string
3452 // (basically any non-web URI scheme), we want to make sure
3453 // to return earlier, and leave [("", "")] as the resulting
3454 // pairs (but still run the same debug assertions near the
3455 // end of this method).
3456 if (pair->first.IsEmpty()) {
3457 break;
3460 Unused << GetOriginFromPrincipal(prin, false, pair->second);
3461 prin = prin->GetNextSubDomainPrincipal();
3462 // Get the next subdomain principal and loop back around.
3465 MOZ_ASSERT(pairs.Length() >= 1,
3466 "Every principal should have at least one pair item.");
3467 return pairs;
3470 bool PermissionManager::PermissionAvailable(nsIPrincipal* aPrincipal,
3471 const nsACString& aType) {
3472 EnsureReadCompleted();
3474 if (XRE_IsContentProcess()) {
3475 nsAutoCString permissionKey;
3476 // NOTE: GetKeyForPermission accepts a null aType.
3477 GetKeyForPermission(aPrincipal, aType, permissionKey);
3479 // If we have a pending promise for the permission key in question, we don't
3480 // have the permission available, so report a warning and return false.
3481 RefPtr<GenericNonExclusivePromise::Private> promise;
3482 if (!mPermissionKeyPromiseMap.Get(permissionKey, getter_AddRefs(promise)) ||
3483 promise) {
3484 // Emit a useful diagnostic warning with the permissionKey for the process
3485 // which hasn't received permissions yet.
3486 NS_WARNING(nsPrintfCString("This content process hasn't received the "
3487 "permissions for %s yet",
3488 permissionKey.get())
3489 .get());
3490 return false;
3493 return true;
3496 void PermissionManager::WhenPermissionsAvailable(nsIPrincipal* aPrincipal,
3497 nsIRunnable* aRunnable) {
3498 MOZ_ASSERT(aRunnable);
3500 if (!XRE_IsContentProcess()) {
3501 aRunnable->Run();
3502 return;
3505 nsTArray<RefPtr<GenericNonExclusivePromise>> promises;
3506 for (auto& pair : GetAllKeysForPrincipal(aPrincipal)) {
3507 RefPtr<GenericNonExclusivePromise::Private> promise;
3508 if (!mPermissionKeyPromiseMap.Get(pair.first, getter_AddRefs(promise))) {
3509 // In this case we have found a permission which isn't available in the
3510 // content process and hasn't been requested yet. We need to create a new
3511 // promise, and send the request to the parent (if we have not already
3512 // done so).
3513 promise = new GenericNonExclusivePromise::Private(__func__);
3514 mPermissionKeyPromiseMap.InsertOrUpdate(pair.first, RefPtr{promise});
3517 if (promise) {
3518 promises.AppendElement(std::move(promise));
3522 // If all of our permissions are available, immediately run the runnable. This
3523 // avoids any extra overhead during fetch interception which is performance
3524 // sensitive.
3525 if (promises.IsEmpty()) {
3526 aRunnable->Run();
3527 return;
3530 auto* thread = AbstractThread::MainThread();
3532 RefPtr<nsIRunnable> runnable = aRunnable;
3533 GenericNonExclusivePromise::All(thread, promises)
3534 ->Then(
3535 thread, __func__, [runnable]() { runnable->Run(); },
3536 []() {
3537 NS_WARNING(
3538 "PermissionManager permission promise rejected. We're "
3539 "probably shutting down.");
3543 void PermissionManager::EnsureReadCompleted() {
3544 MOZ_ASSERT(NS_IsMainThread());
3546 if (mState == eInitializing) {
3547 MonitorAutoLock lock(mMonitor);
3549 while (mState == eInitializing) {
3550 mMonitor.Wait();
3554 switch (mState) {
3555 case eInitializing:
3556 MOZ_CRASH("This state is impossible!");
3558 case eDBInitialized:
3559 mState = eReady;
3561 CompleteMigrations();
3562 ImportLatestDefaults();
3563 CompleteRead();
3565 [[fallthrough]];
3567 case eReady:
3568 [[fallthrough]];
3570 case eClosed:
3571 return;
3573 default:
3574 MOZ_CRASH("Invalid state");
3578 already_AddRefed<nsIInputStream> PermissionManager::GetDefaultsInputStream() {
3579 MOZ_ASSERT(NS_IsMainThread());
3581 nsAutoCString defaultsURL;
3582 Preferences::GetCString(kDefaultsUrlPrefName, defaultsURL);
3583 if (defaultsURL.IsEmpty()) { // == Don't use built-in permissions.
3584 return nullptr;
3587 nsCOMPtr<nsIURI> defaultsURI;
3588 nsresult rv = NS_NewURI(getter_AddRefs(defaultsURI), defaultsURL);
3589 NS_ENSURE_SUCCESS(rv, nullptr);
3591 nsCOMPtr<nsIChannel> channel;
3592 rv = NS_NewChannel(getter_AddRefs(channel), defaultsURI,
3593 nsContentUtils::GetSystemPrincipal(),
3594 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
3595 nsIContentPolicy::TYPE_OTHER);
3596 NS_ENSURE_SUCCESS(rv, nullptr);
3598 nsCOMPtr<nsIInputStream> inputStream;
3599 rv = channel->Open(getter_AddRefs(inputStream));
3600 NS_ENSURE_SUCCESS(rv, nullptr);
3602 return inputStream.forget();
3605 void PermissionManager::ConsumeDefaultsInputStream(
3606 nsIInputStream* aInputStream, const MonitorAutoLock& aProofOfLock) {
3607 MOZ_ASSERT(!NS_IsMainThread());
3609 constexpr char kMatchTypeHost[] = "host";
3610 constexpr char kMatchTypeOrigin[] = "origin";
3612 mDefaultEntries.Clear();
3614 if (!aInputStream) {
3615 return;
3618 nsresult rv;
3620 /* format is:
3621 * matchtype \t type \t permission \t host
3622 * Only "host" is supported for matchtype
3623 * type is a string that identifies the type of permission (e.g. "cookie")
3624 * permission is an integer between 1 and 15
3627 // Ideally we'd do this with nsILineInputString, but this is called with an
3628 // nsIInputStream that comes from a resource:// URI, which doesn't support
3629 // that interface. So NS_ReadLine to the rescue...
3630 nsLineBuffer<char> lineBuffer;
3631 nsCString line;
3632 bool isMore = true;
3633 do {
3634 rv = NS_ReadLine(aInputStream, &lineBuffer, line, &isMore);
3635 NS_ENSURE_SUCCESS_VOID(rv);
3637 if (line.IsEmpty() || line.First() == '#') {
3638 continue;
3641 nsTArray<nsCString> lineArray;
3643 // Split the line at tabs
3644 ParseString(line, '\t', lineArray);
3646 if (lineArray.Length() != 4) {
3647 continue;
3650 nsresult error = NS_OK;
3651 uint32_t permission = lineArray[2].ToInteger(&error);
3652 if (NS_FAILED(error)) {
3653 continue;
3656 DefaultEntry::Op op;
3658 if (lineArray[0].EqualsLiteral(kMatchTypeHost)) {
3659 op = DefaultEntry::eImportMatchTypeHost;
3660 } else if (lineArray[0].EqualsLiteral(kMatchTypeOrigin)) {
3661 op = DefaultEntry::eImportMatchTypeOrigin;
3662 } else {
3663 continue;
3666 DefaultEntry* entry = mDefaultEntries.AppendElement();
3667 MOZ_ASSERT(entry);
3669 entry->mOp = op;
3670 entry->mPermission = permission;
3671 entry->mHostOrOrigin = lineArray[3];
3672 entry->mType = lineArray[1];
3673 } while (isMore);
3676 // ImportLatestDefaults will import the latest default cookies read during the
3677 // last DB initialization.
3678 nsresult PermissionManager::ImportLatestDefaults() {
3679 MOZ_ASSERT(NS_IsMainThread());
3680 MOZ_ASSERT(mState == eReady);
3682 nsresult rv;
3684 MonitorAutoLock lock(mMonitor);
3686 for (const DefaultEntry& entry : mDefaultEntries) {
3687 if (entry.mOp == DefaultEntry::eImportMatchTypeHost) {
3688 // the import file format doesn't handle modification times, so we use
3689 // 0, which AddInternal will convert to now()
3690 int64_t modificationTime = 0;
3692 rv = UpgradeHostToOriginAndInsert(
3693 entry.mHostOrOrigin, entry.mType, entry.mPermission,
3694 nsIPermissionManager::EXPIRE_NEVER, 0, modificationTime, false,
3695 [&](const nsACString& aOrigin, const nsCString& aType,
3696 uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
3697 int64_t aModificationTime) {
3698 nsCOMPtr<nsIPrincipal> principal;
3699 nsresult rv =
3700 GetPrincipalFromOrigin(aOrigin, IsOAForceStripPermission(aType),
3701 getter_AddRefs(principal));
3702 NS_ENSURE_SUCCESS(rv, rv);
3703 rv =
3704 AddInternal(principal, aType, aPermission,
3705 cIDPermissionIsDefault, aExpireType, aExpireTime,
3706 aModificationTime, PermissionManager::eDontNotify,
3707 PermissionManager::eNoDBOperation, false, &aOrigin);
3708 NS_ENSURE_SUCCESS(rv, rv);
3710 if (StaticPrefs::permissions_isolateBy_privateBrowsing()) {
3711 // Also import the permission for private browsing.
3712 OriginAttributes attrs =
3713 OriginAttributes(principal->OriginAttributesRef());
3714 attrs.mPrivateBrowsingId = 1;
3715 nsCOMPtr<nsIPrincipal> pbPrincipal =
3716 BasePrincipal::Cast(principal)->CloneForcingOriginAttributes(
3717 attrs);
3719 rv = AddInternal(
3720 pbPrincipal, aType, aPermission, cIDPermissionIsDefault,
3721 aExpireType, aExpireTime, aModificationTime,
3722 PermissionManager::eDontNotify,
3723 PermissionManager::eNoDBOperation, false, &aOrigin);
3724 NS_ENSURE_SUCCESS(rv, rv);
3727 return NS_OK;
3730 if (NS_FAILED(rv)) {
3731 NS_WARNING("There was a problem importing a host permission");
3733 continue;
3736 MOZ_ASSERT(entry.mOp == DefaultEntry::eImportMatchTypeOrigin);
3738 nsCOMPtr<nsIPrincipal> principal;
3739 rv = GetPrincipalFromOrigin(entry.mHostOrOrigin,
3740 IsOAForceStripPermission(entry.mType),
3741 getter_AddRefs(principal));
3742 if (NS_FAILED(rv)) {
3743 NS_WARNING("Couldn't import an origin permission - malformed origin");
3744 continue;
3747 // the import file format doesn't handle modification times, so we use
3748 // 0, which AddInternal will convert to now()
3749 int64_t modificationTime = 0;
3751 rv = AddInternal(principal, entry.mType, entry.mPermission,
3752 cIDPermissionIsDefault, nsIPermissionManager::EXPIRE_NEVER,
3753 0, modificationTime, eDontNotify, eNoDBOperation);
3754 if (NS_FAILED(rv)) {
3755 NS_WARNING("There was a problem importing an origin permission");
3758 if (StaticPrefs::permissions_isolateBy_privateBrowsing()) {
3759 // Also import the permission for private browsing.
3760 OriginAttributes attrs =
3761 OriginAttributes(principal->OriginAttributesRef());
3762 attrs.mPrivateBrowsingId = 1;
3763 nsCOMPtr<nsIPrincipal> pbPrincipal =
3764 BasePrincipal::Cast(principal)->CloneForcingOriginAttributes(attrs);
3765 // May return nullptr if clone fails.
3766 NS_ENSURE_TRUE(pbPrincipal, NS_ERROR_FAILURE);
3768 rv = AddInternal(pbPrincipal, entry.mType, entry.mPermission,
3769 cIDPermissionIsDefault,
3770 nsIPermissionManager::EXPIRE_NEVER, 0, modificationTime,
3771 eDontNotify, eNoDBOperation);
3772 if (NS_FAILED(rv)) {
3773 NS_WARNING(
3774 "There was a problem importing an origin permission for private "
3775 "browsing");
3780 return NS_OK;
3784 * Perform the early steps of a permission check and determine whether we need
3785 * to call CommonTestPermissionInternal() for the actual permission check.
3787 * @param aPrincipal optional principal argument to check the permission for,
3788 * can be nullptr if we aren't performing a principal-based
3789 * check.
3790 * @param aTypeIndex if the caller isn't sure what the index of the permission
3791 * type to check for is in the mTypeArray member variable,
3792 * it should pass -1, otherwise this would be the index of
3793 * the type inside mTypeArray. This would only be something
3794 * other than -1 in recursive invocations of this function.
3795 * @param aType the permission type to test.
3796 * @param aPermission out argument which will be a permission type that we
3797 * will return from this function once the function is
3798 * done.
3799 * @param aDefaultPermission the default permission to be used if we can't
3800 * determine the result of the permission check.
3801 * @param aDefaultPermissionIsValid whether the previous argument contains a
3802 * valid value.
3803 * @param aExactHostMatch whether to look for the exact host name or also for
3804 * subdomains that can have the same permission.
3805 * @param aIncludingSession whether to include session permissions when
3806 * testing for the permission.
3808 PermissionManager::TestPreparationResult
3809 PermissionManager::CommonPrepareToTestPermission(
3810 nsIPrincipal* aPrincipal, int32_t aTypeIndex, const nsACString& aType,
3811 uint32_t* aPermission, uint32_t aDefaultPermission,
3812 bool aDefaultPermissionIsValid, bool aExactHostMatch,
3813 bool aIncludingSession) {
3814 auto* basePrin = BasePrincipal::Cast(aPrincipal);
3815 if (basePrin && basePrin->IsSystemPrincipal()) {
3816 *aPermission = ALLOW_ACTION;
3817 return AsVariant(NS_OK);
3820 EnsureReadCompleted();
3822 // For some permissions, query the default from a pref. We want to avoid
3823 // doing this for all permissions so that permissions can opt into having
3824 // the pref lookup overhead on each call.
3825 int32_t defaultPermission =
3826 aDefaultPermissionIsValid ? aDefaultPermission : UNKNOWN_ACTION;
3827 if (!aDefaultPermissionIsValid && HasDefaultPref(aType)) {
3828 Unused << mDefaultPrefBranch->GetIntPref(PromiseFlatCString(aType).get(),
3829 &defaultPermission);
3832 // Set the default.
3833 *aPermission = defaultPermission;
3835 int32_t typeIndex =
3836 aTypeIndex == -1 ? GetTypeIndex(aType, false) : aTypeIndex;
3838 // For expanded principals, we want to iterate over the allowlist and see
3839 // if the permission is granted for any of them.
3840 if (basePrin && basePrin->Is<ExpandedPrincipal>()) {
3841 auto ep = basePrin->As<ExpandedPrincipal>();
3842 for (auto& prin : ep->AllowList()) {
3843 uint32_t perm;
3844 nsresult rv =
3845 CommonTestPermission(prin, typeIndex, aType, &perm, defaultPermission,
3846 true, aExactHostMatch, aIncludingSession);
3847 if (NS_WARN_IF(NS_FAILED(rv))) {
3848 return AsVariant(rv);
3851 if (perm == nsIPermissionManager::ALLOW_ACTION) {
3852 *aPermission = perm;
3853 return AsVariant(NS_OK);
3855 if (perm == nsIPermissionManager::PROMPT_ACTION) {
3856 // Store it, but keep going to see if we can do better.
3857 *aPermission = perm;
3861 return AsVariant(NS_OK);
3864 // If type == -1, the type isn't known, just signal that we are done.
3865 if (typeIndex == -1) {
3866 return AsVariant(NS_OK);
3869 return AsVariant(typeIndex);
3872 // If aTypeIndex is passed -1, we try to inder the type index from aType.
3873 nsresult PermissionManager::CommonTestPermission(
3874 nsIPrincipal* aPrincipal, int32_t aTypeIndex, const nsACString& aType,
3875 uint32_t* aPermission, uint32_t aDefaultPermission,
3876 bool aDefaultPermissionIsValid, bool aExactHostMatch,
3877 bool aIncludingSession) {
3878 auto preparationResult = CommonPrepareToTestPermission(
3879 aPrincipal, aTypeIndex, aType, aPermission, aDefaultPermission,
3880 aDefaultPermissionIsValid, aExactHostMatch, aIncludingSession);
3881 if (preparationResult.is<nsresult>()) {
3882 return preparationResult.as<nsresult>();
3885 return CommonTestPermissionInternal(
3886 aPrincipal, nullptr, nullptr, preparationResult.as<int32_t>(), aType,
3887 aPermission, aExactHostMatch, aIncludingSession);
3890 // If aTypeIndex is passed -1, we try to inder the type index from aType.
3891 nsresult PermissionManager::CommonTestPermission(
3892 nsIURI* aURI, int32_t aTypeIndex, const nsACString& aType,
3893 uint32_t* aPermission, uint32_t aDefaultPermission,
3894 bool aDefaultPermissionIsValid, bool aExactHostMatch,
3895 bool aIncludingSession) {
3896 auto preparationResult = CommonPrepareToTestPermission(
3897 nullptr, aTypeIndex, aType, aPermission, aDefaultPermission,
3898 aDefaultPermissionIsValid, aExactHostMatch, aIncludingSession);
3899 if (preparationResult.is<nsresult>()) {
3900 return preparationResult.as<nsresult>();
3903 return CommonTestPermissionInternal(
3904 nullptr, aURI, nullptr, preparationResult.as<int32_t>(), aType,
3905 aPermission, aExactHostMatch, aIncludingSession);
3908 nsresult PermissionManager::CommonTestPermission(
3909 nsIURI* aURI, const OriginAttributes* aOriginAttributes, int32_t aTypeIndex,
3910 const nsACString& aType, uint32_t* aPermission, uint32_t aDefaultPermission,
3911 bool aDefaultPermissionIsValid, bool aExactHostMatch,
3912 bool aIncludingSession) {
3913 auto preparationResult = CommonPrepareToTestPermission(
3914 nullptr, aTypeIndex, aType, aPermission, aDefaultPermission,
3915 aDefaultPermissionIsValid, aExactHostMatch, aIncludingSession);
3916 if (preparationResult.is<nsresult>()) {
3917 return preparationResult.as<nsresult>();
3920 return CommonTestPermissionInternal(
3921 nullptr, aURI, aOriginAttributes, preparationResult.as<int32_t>(), aType,
3922 aPermission, aExactHostMatch, aIncludingSession);
3925 nsresult PermissionManager::TestPermissionWithoutDefaultsFromPrincipal(
3926 nsIPrincipal* aPrincipal, const nsACString& aType, uint32_t* aPermission) {
3927 MOZ_ASSERT(!HasDefaultPref(aType));
3929 return CommonTestPermission(aPrincipal, -1, aType, aPermission,
3930 nsIPermissionManager::UNKNOWN_ACTION, true, false,
3931 true);
3934 void PermissionManager::MaybeCompleteShutdown() {
3935 MOZ_ASSERT(NS_IsMainThread());
3937 nsCOMPtr<nsIAsyncShutdownClient> asc = GetAsyncShutdownBarrier();
3938 MOZ_ASSERT(asc);
3940 DebugOnly<nsresult> rv = asc->RemoveBlocker(this);
3941 MOZ_ASSERT(NS_SUCCEEDED(rv));
3944 // Async shutdown blocker methods
3946 NS_IMETHODIMP PermissionManager::GetName(nsAString& aName) {
3947 aName = u"PermissionManager: Flushing data"_ns;
3948 return NS_OK;
3951 NS_IMETHODIMP PermissionManager::BlockShutdown(
3952 nsIAsyncShutdownClient* aClient) {
3953 RemoveIdleDailyMaintenanceJob();
3954 RemoveAllFromMemory();
3955 CloseDB(eShutdown);
3957 gPermissionManager = nullptr;
3958 return NS_OK;
3961 NS_IMETHODIMP
3962 PermissionManager::GetState(nsIPropertyBag** aBagOut) {
3963 nsCOMPtr<nsIWritablePropertyBag2> propertyBag =
3964 do_CreateInstance("@mozilla.org/hash-property-bag;1");
3966 nsresult rv = propertyBag->SetPropertyAsInt32(u"state"_ns, mState);
3967 if (NS_WARN_IF(NS_FAILED(rv))) {
3968 return rv;
3971 propertyBag.forget(aBagOut);
3973 return NS_OK;
3976 nsCOMPtr<nsIAsyncShutdownClient> PermissionManager::GetAsyncShutdownBarrier()
3977 const {
3978 nsresult rv;
3979 nsCOMPtr<nsIAsyncShutdownService> svc =
3980 do_GetService("@mozilla.org/async-shutdown-service;1", &rv);
3981 if (NS_FAILED(rv)) {
3982 return nullptr;
3985 nsCOMPtr<nsIAsyncShutdownClient> client;
3986 // This feels very late but there seem to be other services that rely on
3987 // us later than "profile-before-change".
3988 rv = svc->GetXpcomWillShutdown(getter_AddRefs(client));
3989 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
3991 return client;
3994 void PermissionManager::MaybeStripOriginAttributes(
3995 bool aForceStrip, OriginAttributes& aOriginAttributes) {
3996 uint32_t flags = 0;
3998 if (aForceStrip || !StaticPrefs::permissions_isolateBy_privateBrowsing()) {
3999 flags |= OriginAttributes::STRIP_PRIVATE_BROWSING_ID;
4002 if (aForceStrip || !StaticPrefs::permissions_isolateBy_userContext()) {
4003 flags |= OriginAttributes::STRIP_USER_CONTEXT_ID;
4006 if (flags != 0) {
4007 aOriginAttributes.StripAttributes(flags);
4011 } // namespace mozilla