Bug 1888590 - Mark some subtests on trusted-types-event-handlers.html as failing...
[gecko.git] / extensions / permissions / PermissionManager.cpp
blobbe144e2dfe3d2e8abadee19d870dc46f4f06bc0c
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, "screen-wake-lock"_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, nsIPrincipal** aPrincipal) {
312 OriginAttributes attrs;
313 nsCOMPtr<nsIPrincipal> principal =
314 BasePrincipal::CreateContentPrincipal(aURI, attrs);
315 NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
317 principal.forget(aPrincipal);
318 return NS_OK;
321 nsCString GetNextSubDomainForHost(const nsACString& aHost) {
322 nsCString subDomain;
323 nsresult rv =
324 nsEffectiveTLDService::GetInstance()->GetNextSubDomain(aHost, subDomain);
325 // We can fail if there is no more subdomain or if the host can't have a
326 // subdomain.
327 if (NS_FAILED(rv)) {
328 return ""_ns;
331 return subDomain;
334 // This function produces a nsIURI which is identical to the current
335 // nsIURI, except that it has one less subdomain segment. It returns
336 // `nullptr` if there are no more segments to remove.
337 already_AddRefed<nsIURI> GetNextSubDomainURI(nsIURI* aURI) {
338 nsAutoCString host;
339 nsresult rv = aURI->GetHost(host);
340 if (NS_FAILED(rv)) {
341 return nullptr;
344 nsCString domain = GetNextSubDomainForHost(host);
345 if (domain.IsEmpty()) {
346 return nullptr;
349 nsCOMPtr<nsIURI> uri;
350 rv = NS_MutateURI(aURI).SetHost(domain).Finalize(uri);
351 if (NS_FAILED(rv) || !uri) {
352 return nullptr;
355 return uri.forget();
358 nsresult UpgradeHostToOriginAndInsert(
359 const nsACString& aHost, const nsCString& aType, uint32_t aPermission,
360 uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime,
361 std::function<nsresult(const nsACString& aOrigin, const nsCString& aType,
362 uint32_t aPermission, uint32_t aExpireType,
363 int64_t aExpireTime, int64_t aModificationTime)>&&
364 aCallback) {
365 if (aHost.EqualsLiteral("<file>")) {
366 // We no longer support the magic host <file>
367 NS_WARNING(
368 "The magic host <file> is no longer supported. "
369 "It is being removed from the permissions database.");
370 return NS_OK;
373 // First, we check to see if the host is a valid URI. If it is, it can be
374 // imported directly
375 nsCOMPtr<nsIURI> uri;
376 nsresult rv = NS_NewURI(getter_AddRefs(uri), aHost);
377 if (NS_SUCCEEDED(rv)) {
378 // It was previously possible to insert useless entries to your permissions
379 // database for URIs which have a null principal. This acts as a cleanup,
380 // getting rid of these useless database entries
381 if (uri->SchemeIs("moz-nullprincipal")) {
382 NS_WARNING("A moz-nullprincipal: permission is being discarded.");
383 return NS_OK;
386 nsCOMPtr<nsIPrincipal> principal;
387 rv = GetPrincipal(uri, getter_AddRefs(principal));
388 NS_ENSURE_SUCCESS(rv, rv);
390 nsAutoCString origin;
391 rv = GetOriginFromPrincipal(principal, IsOAForceStripPermission(aType),
392 origin);
393 NS_ENSURE_SUCCESS(rv, rv);
395 aCallback(origin, aType, aPermission, aExpireType, aExpireTime,
396 aModificationTime);
397 return NS_OK;
400 // The user may use this host at non-standard ports or protocols, we can use
401 // their history to guess what ports and protocols we want to add permissions
402 // for. We find every URI which they have visited with this host (or a
403 // subdomain of this host), and try to add it as a principal.
404 bool foundHistory = false;
406 nsCOMPtr<nsINavHistoryService> histSrv =
407 do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
409 if (histSrv) {
410 nsCOMPtr<nsINavHistoryQuery> histQuery;
411 rv = histSrv->GetNewQuery(getter_AddRefs(histQuery));
412 NS_ENSURE_SUCCESS(rv, rv);
414 // Get the eTLD+1 of the domain
415 nsAutoCString eTLD1;
416 rv = nsEffectiveTLDService::GetInstance()->GetBaseDomainFromHost(aHost, 0,
417 eTLD1);
419 if (NS_FAILED(rv)) {
420 // If the lookup on the tldService for the base domain for the host
421 // failed, that means that we just want to directly use the host as the
422 // host name for the lookup.
423 eTLD1 = aHost;
426 // We want to only find history items for this particular eTLD+1, and
427 // subdomains
428 rv = histQuery->SetDomain(eTLD1);
429 NS_ENSURE_SUCCESS(rv, rv);
431 rv = histQuery->SetDomainIsHost(false);
432 NS_ENSURE_SUCCESS(rv, rv);
434 nsCOMPtr<nsINavHistoryQueryOptions> histQueryOpts;
435 rv = histSrv->GetNewQueryOptions(getter_AddRefs(histQueryOpts));
436 NS_ENSURE_SUCCESS(rv, rv);
438 // We want to get the URIs for every item in the user's history with the
439 // given host
440 rv =
441 histQueryOpts->SetResultType(nsINavHistoryQueryOptions::RESULTS_AS_URI);
442 NS_ENSURE_SUCCESS(rv, rv);
444 // We only search history, because searching both bookmarks and history
445 // is not supported, and history tends to be more comprehensive.
446 rv = histQueryOpts->SetQueryType(
447 nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY);
448 NS_ENSURE_SUCCESS(rv, rv);
450 // We include hidden URIs (such as those visited via iFrames) as they may
451 // have permissions too
452 rv = histQueryOpts->SetIncludeHidden(true);
453 NS_ENSURE_SUCCESS(rv, rv);
455 nsCOMPtr<nsINavHistoryResult> histResult;
456 rv = histSrv->ExecuteQuery(histQuery, histQueryOpts,
457 getter_AddRefs(histResult));
458 NS_ENSURE_SUCCESS(rv, rv);
460 nsCOMPtr<nsINavHistoryContainerResultNode> histResultContainer;
461 rv = histResult->GetRoot(getter_AddRefs(histResultContainer));
462 NS_ENSURE_SUCCESS(rv, rv);
464 rv = histResultContainer->SetContainerOpen(true);
465 NS_ENSURE_SUCCESS(rv, rv);
467 uint32_t childCount = 0;
468 rv = histResultContainer->GetChildCount(&childCount);
469 NS_ENSURE_SUCCESS(rv, rv);
471 nsTHashSet<nsCString> insertedOrigins;
472 for (uint32_t i = 0; i < childCount; i++) {
473 nsCOMPtr<nsINavHistoryResultNode> child;
474 histResultContainer->GetChild(i, getter_AddRefs(child));
475 if (NS_WARN_IF(NS_FAILED(rv))) continue;
477 uint32_t type;
478 rv = child->GetType(&type);
479 if (NS_WARN_IF(NS_FAILED(rv)) ||
480 type != nsINavHistoryResultNode::RESULT_TYPE_URI) {
481 NS_WARNING(
482 "Unexpected non-RESULT_TYPE_URI node in "
483 "UpgradeHostToOriginAndInsert()");
484 continue;
487 nsAutoCString uriSpec;
488 rv = child->GetUri(uriSpec);
489 if (NS_WARN_IF(NS_FAILED(rv))) continue;
491 nsCOMPtr<nsIURI> uri;
492 rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
493 if (NS_WARN_IF(NS_FAILED(rv))) continue;
495 // Use the provided host - this URI may be for a subdomain, rather than
496 // the host we care about.
497 rv = NS_MutateURI(uri).SetHost(aHost).Finalize(uri);
498 if (NS_WARN_IF(NS_FAILED(rv))) continue;
500 // We now have a URI which we can make a nsIPrincipal out of
501 nsCOMPtr<nsIPrincipal> principal;
502 rv = GetPrincipal(uri, getter_AddRefs(principal));
503 if (NS_WARN_IF(NS_FAILED(rv))) continue;
505 nsAutoCString origin;
506 rv = GetOriginFromPrincipal(principal, IsOAForceStripPermission(aType),
507 origin);
508 if (NS_WARN_IF(NS_FAILED(rv))) continue;
510 // Ensure that we don't insert the same origin repeatedly
511 if (insertedOrigins.Contains(origin)) {
512 continue;
515 foundHistory = true;
516 rv = aCallback(origin, aType, aPermission, aExpireType, aExpireTime,
517 aModificationTime);
518 NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Insert failed");
519 insertedOrigins.Insert(origin);
522 rv = histResultContainer->SetContainerOpen(false);
523 NS_ENSURE_SUCCESS(rv, rv);
526 // If we didn't find any origins for this host in the poermissions database,
527 // we can insert the default http:// and https:// permissions into the
528 // database. This has a relatively high likelihood of applying the permission
529 // to the correct origin.
530 if (!foundHistory) {
531 nsAutoCString hostSegment;
532 nsCOMPtr<nsIPrincipal> principal;
533 nsAutoCString origin;
535 // If this is an ipv6 URI, we need to surround it in '[', ']' before trying
536 // to parse it as a URI.
537 if (aHost.FindChar(':') != -1) {
538 hostSegment.AssignLiteral("[");
539 hostSegment.Append(aHost);
540 hostSegment.AppendLiteral("]");
541 } else {
542 hostSegment.Assign(aHost);
545 // http:// URI default
546 rv = NS_NewURI(getter_AddRefs(uri), "http://"_ns + hostSegment);
547 NS_ENSURE_SUCCESS(rv, rv);
549 rv = GetPrincipal(uri, getter_AddRefs(principal));
550 NS_ENSURE_SUCCESS(rv, rv);
552 rv = GetOriginFromPrincipal(principal, IsOAForceStripPermission(aType),
553 origin);
554 NS_ENSURE_SUCCESS(rv, rv);
556 aCallback(origin, aType, aPermission, aExpireType, aExpireTime,
557 aModificationTime);
559 // https:// URI default
560 rv = NS_NewURI(getter_AddRefs(uri), "https://"_ns + hostSegment);
561 NS_ENSURE_SUCCESS(rv, rv);
563 rv = GetPrincipal(uri, getter_AddRefs(principal));
564 NS_ENSURE_SUCCESS(rv, rv);
566 rv = GetOriginFromPrincipal(principal, IsOAForceStripPermission(aType),
567 origin);
568 NS_ENSURE_SUCCESS(rv, rv);
570 aCallback(origin, aType, aPermission, aExpireType, aExpireTime,
571 aModificationTime);
574 return NS_OK;
577 bool IsExpandedPrincipal(nsIPrincipal* aPrincipal) {
578 nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
579 return !!ep;
582 // We only want to persist permissions which don't have session or policy
583 // expiration.
584 bool IsPersistentExpire(uint32_t aExpire, const nsACString& aType) {
585 bool res = (aExpire != nsIPermissionManager::EXPIRE_SESSION &&
586 aExpire != nsIPermissionManager::EXPIRE_POLICY);
587 return res;
590 nsresult NotifySecondaryKeyPermissionUpdateInContentProcess(
591 const nsACString& aType, uint32_t aPermission,
592 const nsACString& aSecondaryKey, nsIPrincipal* aTopPrincipal) {
593 NS_ENSURE_ARG_POINTER(aTopPrincipal);
594 MOZ_ASSERT(XRE_IsParentProcess());
595 AutoTArray<RefPtr<BrowsingContextGroup>, 5> bcGroups;
596 BrowsingContextGroup::GetAllGroups(bcGroups);
597 for (const auto& bcGroup : bcGroups) {
598 for (const auto& topBC : bcGroup->Toplevels()) {
599 CanonicalBrowsingContext* topCBC = topBC->Canonical();
600 RefPtr<nsIURI> topURI = topCBC->GetCurrentURI();
601 if (!topURI) {
602 continue;
604 bool thirdParty;
605 nsresult rv = aTopPrincipal->IsThirdPartyURI(topURI, &thirdParty);
606 if (NS_FAILED(rv)) {
607 continue;
609 if (!thirdParty) {
610 AutoTArray<RefPtr<BrowsingContext>, 5> bcs;
611 topBC->GetAllBrowsingContextsInSubtree(bcs);
612 for (const auto& bc : bcs) {
613 CanonicalBrowsingContext* cbc = bc->Canonical();
614 ContentParent* cp = cbc->GetContentParent();
615 if (!cp) {
616 continue;
618 if (cp->NeedsSecondaryKeyPermissionsUpdate(aSecondaryKey)) {
619 WindowGlobalParent* wgp = cbc->GetCurrentWindowGlobal();
620 if (!wgp) {
621 continue;
623 bool success = wgp->SendNotifyPermissionChange(aType, aPermission);
624 Unused << NS_WARN_IF(!success);
630 return NS_OK;
633 } // namespace
635 ////////////////////////////////////////////////////////////////////////////////
637 PermissionManager::PermissionKey*
638 PermissionManager::PermissionKey::CreateFromPrincipal(nsIPrincipal* aPrincipal,
639 bool aForceStripOA,
640 bool aScopeToSite,
641 nsresult& aResult) {
642 nsAutoCString keyString;
643 if (aScopeToSite) {
644 aResult = GetSiteFromPrincipal(aPrincipal, aForceStripOA, keyString);
645 } else {
646 aResult = GetOriginFromPrincipal(aPrincipal, aForceStripOA, keyString);
648 if (NS_WARN_IF(NS_FAILED(aResult))) {
649 return nullptr;
651 return new PermissionKey(keyString);
654 PermissionManager::PermissionKey*
655 PermissionManager::PermissionKey::CreateFromURIAndOriginAttributes(
656 nsIURI* aURI, const OriginAttributes* aOriginAttributes, bool aForceStripOA,
657 nsresult& aResult) {
658 nsAutoCString origin;
659 aResult =
660 GetOriginFromURIAndOA(aURI, aOriginAttributes, aForceStripOA, origin);
661 if (NS_WARN_IF(NS_FAILED(aResult))) {
662 return nullptr;
665 return new PermissionKey(origin);
668 PermissionManager::PermissionKey*
669 PermissionManager::PermissionKey::CreateFromURI(nsIURI* aURI,
670 nsresult& aResult) {
671 nsAutoCString origin;
672 aResult = ContentPrincipal::GenerateOriginNoSuffixFromURI(aURI, origin);
673 if (NS_WARN_IF(NS_FAILED(aResult))) {
674 return nullptr;
677 return new PermissionKey(origin);
680 ////////////////////////////////////////////////////////////////////////////////
681 // PermissionManager Implementation
683 NS_IMPL_ISUPPORTS(PermissionManager, nsIPermissionManager, nsIObserver,
684 nsISupportsWeakReference, nsIAsyncShutdownBlocker)
686 PermissionManager::PermissionManager()
687 : mMonitor("PermissionManager::mMonitor"),
688 mState(eInitializing),
689 mMemoryOnlyDB(false),
690 mLargestID(0) {}
692 PermissionManager::~PermissionManager() {
693 // NOTE: Make sure to reject each of the promises in mPermissionKeyPromiseMap
694 // before destroying.
695 for (const auto& promise : mPermissionKeyPromiseMap.Values()) {
696 if (promise) {
697 promise->Reject(NS_ERROR_FAILURE, __func__);
700 mPermissionKeyPromiseMap.Clear();
702 if (mThread) {
703 mThread->Shutdown();
704 mThread = nullptr;
708 /* static */
709 StaticMutex PermissionManager::sCreationMutex;
711 // static
712 already_AddRefed<nsIPermissionManager> PermissionManager::GetXPCOMSingleton() {
713 // The lazy initialization could race.
714 StaticMutexAutoLock lock(sCreationMutex);
716 if (gPermissionManager) {
717 return do_AddRef(gPermissionManager);
720 // Create a new singleton PermissionManager.
721 // We AddRef only once since XPCOM has rules about the ordering of module
722 // teardowns - by the time our module destructor is called, it's too late to
723 // Release our members, since GC cycles have already been completed and
724 // would result in serious leaks.
725 // See bug 209571.
726 auto permManager = MakeRefPtr<PermissionManager>();
727 if (NS_SUCCEEDED(permManager->Init())) {
728 gPermissionManager = permManager.get();
729 return permManager.forget();
732 return nullptr;
735 // static
736 PermissionManager* PermissionManager::GetInstance() {
737 // TODO: There is a minimal chance that we can race here with a
738 // GetXPCOMSingleton call that did not yet set gPermissionManager.
739 // See bug 1745056.
740 if (!gPermissionManager) {
741 // Hand off the creation of the permission manager to GetXPCOMSingleton.
742 nsCOMPtr<nsIPermissionManager> permManager = GetXPCOMSingleton();
745 return gPermissionManager;
748 nsresult PermissionManager::Init() {
749 // If we are already shutting down, do not permit a creation.
750 // This must match the phase in GetAsyncShutdownBarrier.
751 if (AppShutdown::IsInOrBeyond(ShutdownPhase::XPCOMWillShutdown)) {
752 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN;
755 // If the 'permissions.memory_only' pref is set to true, then don't write any
756 // permission settings to disk, but keep them in a memory-only database.
757 mMemoryOnlyDB = Preferences::GetBool("permissions.memory_only", false);
759 nsresult rv;
760 nsCOMPtr<nsIPrefService> prefService =
761 do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
762 NS_ENSURE_SUCCESS(rv, rv);
764 rv = prefService->GetBranch("permissions.default.",
765 getter_AddRefs(mDefaultPrefBranch));
766 NS_ENSURE_SUCCESS(rv, rv);
768 if (IsChildProcess()) {
769 // Stop here; we don't need the DB in the child process. Instead we will be
770 // sent permissions as we need them by our parent process.
771 mState = eReady;
773 // We use ClearOnShutdown on the content process only because on the parent
774 // process we need to block the shutdown for the final closeDB() call.
775 ClearOnShutdown(&gPermissionManager);
776 return NS_OK;
779 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
780 if (observerService) {
781 observerService->AddObserver(this, "profile-do-change", true);
782 observerService->AddObserver(this, "testonly-reload-permissions-from-disk",
783 true);
786 if (XRE_IsParentProcess()) {
787 nsCOMPtr<nsIAsyncShutdownClient> asc = GetAsyncShutdownBarrier();
788 if (!asc) {
789 return NS_ERROR_NOT_AVAILABLE;
791 nsAutoString blockerName;
792 MOZ_ALWAYS_SUCCEEDS(GetName(blockerName));
794 nsresult rv = asc->AddBlocker(
795 this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), __LINE__, blockerName);
796 NS_ENSURE_SUCCESS(rv, rv);
799 AddIdleDailyMaintenanceJob();
801 MOZ_ASSERT(!mThread);
802 NS_ENSURE_SUCCESS(NS_NewNamedThread("Permission", getter_AddRefs(mThread)),
803 NS_ERROR_FAILURE);
805 PRThread* prThread;
806 MOZ_ALWAYS_SUCCEEDS(mThread->GetPRThread(&prThread));
807 MOZ_ASSERT(prThread);
809 mThreadBoundData.Transfer(prThread);
811 InitDB(false);
813 return NS_OK;
816 nsresult PermissionManager::OpenDatabase(nsIFile* aPermissionsFile) {
817 MOZ_ASSERT(!NS_IsMainThread());
818 auto data = mThreadBoundData.Access();
820 nsresult rv;
821 nsCOMPtr<mozIStorageService> storage =
822 do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
823 if (!storage) {
824 return NS_ERROR_UNEXPECTED;
826 // cache a connection to the hosts database
827 if (mMemoryOnlyDB) {
828 rv = storage->OpenSpecialDatabase(
829 kMozStorageMemoryStorageKey, VoidCString(),
830 mozIStorageService::CONNECTION_DEFAULT, getter_AddRefs(data->mDBConn));
831 } else {
832 rv = storage->OpenDatabase(aPermissionsFile,
833 mozIStorageService::CONNECTION_DEFAULT,
834 getter_AddRefs(data->mDBConn));
836 return rv;
839 void PermissionManager::InitDB(bool aRemoveFile) {
840 mState = eInitializing;
843 MonitorAutoLock lock(mMonitor);
844 mReadEntries.Clear();
847 auto readyIfFailed = MakeScopeExit([&]() {
848 // ignore failure here, since it's non-fatal (we can run fine without
849 // persistent storage - e.g. if there's no profile).
850 // XXX should we tell the user about this?
851 mState = eReady;
854 if (!mPermissionsFile) {
855 nsresult rv = NS_GetSpecialDirectory(NS_APP_PERMISSION_PARENT_DIR,
856 getter_AddRefs(mPermissionsFile));
857 if (NS_FAILED(rv)) {
858 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
859 getter_AddRefs(mPermissionsFile));
860 if (NS_FAILED(rv)) {
861 return;
865 rv =
866 mPermissionsFile->AppendNative(nsLiteralCString(PERMISSIONS_FILE_NAME));
867 NS_ENSURE_SUCCESS_VOID(rv);
870 nsCOMPtr<nsIInputStream> defaultsInputStream = GetDefaultsInputStream();
872 RefPtr<PermissionManager> self = this;
873 mThread->Dispatch(NS_NewRunnableFunction(
874 "PermissionManager::InitDB", [self, aRemoveFile, defaultsInputStream] {
875 nsresult rv = self->TryInitDB(aRemoveFile, defaultsInputStream);
876 Unused << NS_WARN_IF(NS_FAILED(rv));
878 // This extra runnable calls EnsureReadCompleted to finialize the
879 // initialization. If there is something blocked by the monitor, it will
880 // be NOP.
881 NS_DispatchToMainThread(
882 NS_NewRunnableFunction("PermissionManager::InitDB-MainThread",
883 [self] { self->EnsureReadCompleted(); }));
885 self->mMonitor.Notify();
886 }));
888 readyIfFailed.release();
891 nsresult PermissionManager::TryInitDB(bool aRemoveFile,
892 nsIInputStream* aDefaultsInputStream) {
893 MOZ_ASSERT(!NS_IsMainThread());
895 MonitorAutoLock lock(mMonitor);
897 auto raii = MakeScopeExit([&]() {
898 if (aDefaultsInputStream) {
899 aDefaultsInputStream->Close();
902 mState = eDBInitialized;
905 auto data = mThreadBoundData.Access();
907 auto raiiFailure = MakeScopeExit([&]() {
908 if (data->mDBConn) {
909 DebugOnly<nsresult> rv = data->mDBConn->Close();
910 MOZ_ASSERT(NS_SUCCEEDED(rv));
911 data->mDBConn = nullptr;
915 nsresult rv;
917 if (aRemoveFile) {
918 bool exists = false;
919 rv = mPermissionsFile->Exists(&exists);
920 NS_ENSURE_SUCCESS(rv, rv);
921 if (exists) {
922 rv = mPermissionsFile->Remove(false);
923 NS_ENSURE_SUCCESS(rv, rv);
927 rv = OpenDatabase(mPermissionsFile);
928 if (rv == NS_ERROR_FILE_CORRUPTED) {
929 LogToConsole(u"permissions.sqlite is corrupted! Try again!"_ns);
931 // Add telemetry probe
932 Telemetry::Accumulate(Telemetry::PERMISSIONS_SQL_CORRUPTED, 1);
934 // delete corrupted permissions.sqlite and try again
935 rv = mPermissionsFile->Remove(false);
936 NS_ENSURE_SUCCESS(rv, rv);
937 LogToConsole(u"Corrupted permissions.sqlite has been removed."_ns);
939 rv = OpenDatabase(mPermissionsFile);
940 NS_ENSURE_SUCCESS(rv, rv);
941 LogToConsole(u"OpenDatabase to permissions.sqlite is successful!"_ns);
944 if (NS_WARN_IF(NS_FAILED(rv))) {
945 return rv;
948 bool ready;
949 data->mDBConn->GetConnectionReady(&ready);
950 if (!ready) {
951 LogToConsole(nsLiteralString(
952 u"Fail to get connection to permissions.sqlite! Try again!"));
954 // delete and try again
955 rv = mPermissionsFile->Remove(false);
956 NS_ENSURE_SUCCESS(rv, rv);
957 LogToConsole(u"Defective permissions.sqlite has been removed."_ns);
959 // Add telemetry probe
960 Telemetry::Accumulate(Telemetry::DEFECTIVE_PERMISSIONS_SQL_REMOVED, 1);
962 rv = OpenDatabase(mPermissionsFile);
963 NS_ENSURE_SUCCESS(rv, rv);
964 LogToConsole(u"OpenDatabase to permissions.sqlite is successful!"_ns);
966 data->mDBConn->GetConnectionReady(&ready);
967 if (!ready) return NS_ERROR_UNEXPECTED;
970 bool tableExists = false;
971 data->mDBConn->TableExists("moz_perms"_ns, &tableExists);
972 if (!tableExists) {
973 data->mDBConn->TableExists("moz_hosts"_ns, &tableExists);
975 if (!tableExists) {
976 rv = CreateTable();
977 NS_ENSURE_SUCCESS(rv, rv);
978 } else {
979 // table already exists; check the schema version before reading
980 int32_t dbSchemaVersion;
981 rv = data->mDBConn->GetSchemaVersion(&dbSchemaVersion);
982 NS_ENSURE_SUCCESS(rv, rv);
984 switch (dbSchemaVersion) {
985 // upgrading.
986 // every time you increment the database schema, you need to
987 // implement the upgrading code from the previous version to the
988 // new one. fall through to current version
990 case 1: {
991 // previous non-expiry version of database. Upgrade it by adding
992 // the expiration columns
993 rv = data->mDBConn->ExecuteSimpleSQL(
994 "ALTER TABLE moz_hosts ADD expireType INTEGER"_ns);
995 NS_ENSURE_SUCCESS(rv, rv);
997 rv = data->mDBConn->ExecuteSimpleSQL(
998 "ALTER TABLE moz_hosts ADD expireTime INTEGER"_ns);
999 NS_ENSURE_SUCCESS(rv, rv);
1002 // fall through to the next upgrade
1003 [[fallthrough]];
1005 // TODO: we want to make default version as version 2 in order to
1006 // fix bug 784875.
1007 case 0:
1008 case 2: {
1009 // Add appId/isInBrowserElement fields.
1010 rv = data->mDBConn->ExecuteSimpleSQL(
1011 "ALTER TABLE moz_hosts ADD appId INTEGER"_ns);
1012 NS_ENSURE_SUCCESS(rv, rv);
1014 rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
1015 "ALTER TABLE moz_hosts ADD isInBrowserElement INTEGER"));
1016 NS_ENSURE_SUCCESS(rv, rv);
1018 rv = data->mDBConn->SetSchemaVersion(3);
1019 NS_ENSURE_SUCCESS(rv, rv);
1022 // fall through to the next upgrade
1023 [[fallthrough]];
1025 // Version 3->4 is the creation of the modificationTime field.
1026 case 3: {
1027 rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
1028 "ALTER TABLE moz_hosts ADD modificationTime INTEGER"));
1029 NS_ENSURE_SUCCESS(rv, rv);
1031 // We leave the modificationTime at zero for all existing records;
1032 // using now() would mean, eg, that doing "remove all from the
1033 // last hour" within the first hour after migration would remove
1034 // all permissions.
1036 rv = data->mDBConn->SetSchemaVersion(4);
1037 NS_ENSURE_SUCCESS(rv, rv);
1040 // fall through to the next upgrade
1041 [[fallthrough]];
1043 // In version 5, host appId, and isInBrowserElement were merged into
1044 // a single origin entry
1046 // In version 6, the tables were renamed for backwards compatability
1047 // reasons with version 4 and earlier.
1049 // In version 7, a bug in the migration used for version 4->5 was
1050 // discovered which could have triggered data-loss. Because of that,
1051 // all users with a version 4, 5, or 6 database will be re-migrated
1052 // from the backup database. (bug 1186034). This migration bug is
1053 // not present after bug 1185340, and the re-migration ensures that
1054 // all users have the fix.
1055 case 5:
1056 // This branch could also be reached via dbSchemaVersion == 3, in
1057 // which case we want to fall through to the dbSchemaVersion == 4
1058 // case. The easiest way to do that is to perform this extra check
1059 // here to make sure that we didn't get here via a fallthrough
1060 // from v3
1061 if (dbSchemaVersion == 5) {
1062 // In version 5, the backup database is named moz_hosts_v4. We
1063 // perform the version 5->6 migration to get the tables to have
1064 // consistent naming conventions.
1066 // Version 5->6 is the renaming of moz_hosts to moz_perms, and
1067 // moz_hosts_v4 to moz_hosts (bug 1185343)
1069 // In version 5, we performed the modifications to the
1070 // permissions database in place, this meant that if you
1071 // upgraded to a version which used V5, and then downgraded to a
1072 // version which used v4 or earlier, the fallback path would
1073 // drop the table, and your permissions data would be lost. This
1074 // migration undoes that mistake, by restoring the old moz_hosts
1075 // table (if it was present), and instead using the new table
1076 // moz_perms for the new permissions schema.
1078 // NOTE: If you downgrade, store new permissions, and then
1079 // upgrade again, these new permissions won't be migrated or
1080 // reflected in the updated database. This migration only occurs
1081 // once, as if moz_perms exists, it will skip creating it. In
1082 // addition, permissions added after the migration will not be
1083 // visible in previous versions of firefox.
1085 bool permsTableExists = false;
1086 data->mDBConn->TableExists("moz_perms"_ns, &permsTableExists);
1087 if (!permsTableExists) {
1088 // Move the upgraded database to moz_perms
1089 rv = data->mDBConn->ExecuteSimpleSQL(
1090 "ALTER TABLE moz_hosts RENAME TO moz_perms"_ns);
1091 NS_ENSURE_SUCCESS(rv, rv);
1092 } else {
1093 NS_WARNING(
1094 "moz_hosts was not renamed to moz_perms, "
1095 "as a moz_perms table already exists");
1097 // In the situation where a moz_perms table already exists,
1098 // but the schema is lower than 6, a migration has already
1099 // previously occured to V6, but a downgrade has caused the
1100 // moz_hosts table to be dropped. This should only occur in
1101 // the case of a downgrade to a V5 database, which was only
1102 // present in a few day's nightlies. As that version was
1103 // likely used only on a temporary basis, we assume that the
1104 // database from the previous V6 has the permissions which the
1105 // user actually wants to use. We have to get rid of moz_hosts
1106 // such that moz_hosts_v4 can be moved into its place if it
1107 // exists.
1108 rv = data->mDBConn->ExecuteSimpleSQL("DROP TABLE moz_hosts"_ns);
1109 NS_ENSURE_SUCCESS(rv, rv);
1112 #ifdef DEBUG
1113 // The moz_hosts table shouldn't exist anymore
1114 bool hostsTableExists = false;
1115 data->mDBConn->TableExists("moz_hosts"_ns, &hostsTableExists);
1116 MOZ_ASSERT(!hostsTableExists);
1117 #endif
1119 // Rename moz_hosts_v4 back to it's original location, if it
1120 // exists
1121 bool v4TableExists = false;
1122 data->mDBConn->TableExists("moz_hosts_v4"_ns, &v4TableExists);
1123 if (v4TableExists) {
1124 rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
1125 "ALTER TABLE moz_hosts_v4 RENAME TO moz_hosts"));
1126 NS_ENSURE_SUCCESS(rv, rv);
1129 rv = data->mDBConn->SetSchemaVersion(6);
1130 NS_ENSURE_SUCCESS(rv, rv);
1133 // fall through to the next upgrade
1134 [[fallthrough]];
1136 // At this point, the version 5 table has been migrated to a version
1137 // 6 table We are guaranteed to have at least one of moz_hosts and
1138 // moz_perms. If we have moz_hosts, we will migrate moz_hosts into
1139 // moz_perms (even if we already have a moz_perms, as we need a
1140 // re-migration due to bug 1186034).
1142 // After this migration, we are guaranteed to have both a moz_hosts
1143 // (for backwards compatability), and a moz_perms table. The
1144 // moz_hosts table will have a v4 schema, and the moz_perms table
1145 // will have a v6 schema.
1146 case 4:
1147 case 6: {
1148 bool hostsTableExists = false;
1149 data->mDBConn->TableExists("moz_hosts"_ns, &hostsTableExists);
1150 if (hostsTableExists) {
1151 // Both versions 4 and 6 have a version 4 formatted hosts table
1152 // named moz_hosts. We can migrate this table to our version 7
1153 // table moz_perms. If moz_perms is present, then we can use it
1154 // as a basis for comparison.
1156 rv = data->mDBConn->BeginTransaction();
1157 NS_ENSURE_SUCCESS(rv, rv);
1159 bool tableExists = false;
1160 data->mDBConn->TableExists("moz_hosts_new"_ns, &tableExists);
1161 if (tableExists) {
1162 NS_WARNING(
1163 "The temporary database moz_hosts_new already exists, "
1164 "dropping "
1165 "it.");
1166 rv = data->mDBConn->ExecuteSimpleSQL("DROP TABLE moz_hosts_new"_ns);
1167 NS_ENSURE_SUCCESS(rv, rv);
1169 rv = data->mDBConn->ExecuteSimpleSQL(
1170 nsLiteralCString("CREATE TABLE moz_hosts_new ("
1171 " id INTEGER PRIMARY KEY"
1172 ",origin TEXT"
1173 ",type TEXT"
1174 ",permission INTEGER"
1175 ",expireType INTEGER"
1176 ",expireTime INTEGER"
1177 ",modificationTime INTEGER"
1178 ")"));
1179 NS_ENSURE_SUCCESS(rv, rv);
1181 nsCOMPtr<mozIStorageStatement> stmt;
1182 rv = data->mDBConn->CreateStatement(
1183 nsLiteralCString(
1184 "SELECT host, type, permission, expireType, "
1185 "expireTime, "
1186 "modificationTime, isInBrowserElement FROM moz_hosts"),
1187 getter_AddRefs(stmt));
1188 NS_ENSURE_SUCCESS(rv, rv);
1190 int64_t id = 0;
1191 bool hasResult;
1193 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1194 MigrationEntry entry;
1196 // Read in the old row
1197 rv = stmt->GetUTF8String(0, entry.mHost);
1198 if (NS_WARN_IF(NS_FAILED(rv))) {
1199 continue;
1201 rv = stmt->GetUTF8String(1, entry.mType);
1202 if (NS_WARN_IF(NS_FAILED(rv))) {
1203 continue;
1206 entry.mId = id++;
1207 entry.mPermission = stmt->AsInt32(2);
1208 entry.mExpireType = stmt->AsInt32(3);
1209 entry.mExpireTime = stmt->AsInt64(4);
1210 entry.mModificationTime = stmt->AsInt64(5);
1212 mMigrationEntries.AppendElement(entry);
1215 // We don't drop the moz_hosts table such that it is available
1216 // for backwards-compatability and for future migrations in case
1217 // of migration errors in the current code. Create a marker
1218 // empty table which will indicate that the moz_hosts table is
1219 // intended to act as a backup. If this table is not present,
1220 // then the moz_hosts table was created as a random empty table.
1221 rv = data->mDBConn->ExecuteSimpleSQL(
1222 nsLiteralCString("CREATE TABLE moz_hosts_is_backup (dummy "
1223 "INTEGER PRIMARY KEY)"));
1224 NS_ENSURE_SUCCESS(rv, rv);
1226 bool permsTableExists = false;
1227 data->mDBConn->TableExists("moz_perms"_ns, &permsTableExists);
1228 if (permsTableExists) {
1229 // The user already had a moz_perms table, and we are
1230 // performing a re-migration. We count the rows in the old
1231 // table for telemetry, and then back up their old database as
1232 // moz_perms_v6
1234 nsCOMPtr<mozIStorageStatement> countStmt;
1235 rv = data->mDBConn->CreateStatement(
1236 "SELECT COUNT(*) FROM moz_perms"_ns, getter_AddRefs(countStmt));
1237 bool hasResult = false;
1238 if (NS_FAILED(rv) ||
1239 NS_FAILED(countStmt->ExecuteStep(&hasResult)) || !hasResult) {
1240 NS_WARNING("Could not count the rows in moz_perms");
1243 // Back up the old moz_perms database as moz_perms_v6 before
1244 // we move the new table into its position
1245 rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
1246 "ALTER TABLE moz_perms RENAME TO moz_perms_v6"));
1247 NS_ENSURE_SUCCESS(rv, rv);
1250 rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
1251 "ALTER TABLE moz_hosts_new RENAME TO moz_perms"));
1252 NS_ENSURE_SUCCESS(rv, rv);
1254 rv = data->mDBConn->CommitTransaction();
1255 NS_ENSURE_SUCCESS(rv, rv);
1256 } else {
1257 // We don't have a moz_hosts table, so we create one for
1258 // downgrading purposes. This table is empty.
1259 rv = data->mDBConn->ExecuteSimpleSQL(
1260 nsLiteralCString("CREATE TABLE moz_hosts ("
1261 " id INTEGER PRIMARY KEY"
1262 ",host TEXT"
1263 ",type TEXT"
1264 ",permission INTEGER"
1265 ",expireType INTEGER"
1266 ",expireTime INTEGER"
1267 ",modificationTime INTEGER"
1268 ",appId INTEGER"
1269 ",isInBrowserElement INTEGER"
1270 ")"));
1271 NS_ENSURE_SUCCESS(rv, rv);
1273 // We are guaranteed to have a moz_perms table at this point.
1276 #ifdef DEBUG
1278 // At this point, both the moz_hosts and moz_perms tables should
1279 // exist
1280 bool hostsTableExists = false;
1281 bool permsTableExists = false;
1282 data->mDBConn->TableExists("moz_hosts"_ns, &hostsTableExists);
1283 data->mDBConn->TableExists("moz_perms"_ns, &permsTableExists);
1284 MOZ_ASSERT(hostsTableExists && permsTableExists);
1286 #endif
1288 rv = data->mDBConn->SetSchemaVersion(7);
1289 NS_ENSURE_SUCCESS(rv, rv);
1292 // fall through to the next upgrade
1293 [[fallthrough]];
1295 // The version 7-8 migration is the re-migration of localhost and
1296 // ip-address entries due to errors in the previous version 7
1297 // migration which caused localhost and ip-address entries to be
1298 // incorrectly discarded. The version 7 migration logic has been
1299 // corrected, and thus this logic only needs to execute if the user
1300 // is currently on version 7.
1301 case 7: {
1302 // This migration will be relatively expensive as we need to
1303 // perform database lookups for each origin which we want to
1304 // insert. Fortunately, it shouldn't be too expensive as we only
1305 // want to insert a small number of entries created for localhost
1306 // or IP addresses.
1308 // We only want to perform the re-migration if moz_hosts is a
1309 // backup
1310 bool hostsIsBackupExists = false;
1311 data->mDBConn->TableExists("moz_hosts_is_backup"_ns,
1312 &hostsIsBackupExists);
1314 // Only perform this migration if the original schema version was
1315 // 7, and the moz_hosts table is a backup.
1316 if (dbSchemaVersion == 7 && hostsIsBackupExists) {
1317 nsCOMPtr<mozIStorageStatement> stmt;
1318 rv = data->mDBConn->CreateStatement(
1319 nsLiteralCString(
1320 "SELECT host, type, permission, expireType, "
1321 "expireTime, "
1322 "modificationTime, isInBrowserElement FROM moz_hosts"),
1323 getter_AddRefs(stmt));
1324 NS_ENSURE_SUCCESS(rv, rv);
1326 nsCOMPtr<mozIStorageStatement> idStmt;
1327 rv = data->mDBConn->CreateStatement(
1328 "SELECT MAX(id) FROM moz_hosts"_ns, getter_AddRefs(idStmt));
1330 int64_t id = 0;
1331 bool hasResult = false;
1332 if (NS_SUCCEEDED(rv) &&
1333 NS_SUCCEEDED(idStmt->ExecuteStep(&hasResult)) && hasResult) {
1334 id = idStmt->AsInt32(0) + 1;
1337 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
1338 MigrationEntry entry;
1340 // Read in the old row
1341 rv = stmt->GetUTF8String(0, entry.mHost);
1342 if (NS_WARN_IF(NS_FAILED(rv))) {
1343 continue;
1346 nsAutoCString eTLD1;
1347 rv = nsEffectiveTLDService::GetInstance()->GetBaseDomainFromHost(
1348 entry.mHost, 0, eTLD1);
1349 if (NS_SUCCEEDED(rv)) {
1350 // We only care about entries which the tldService can't
1351 // handle
1352 continue;
1355 rv = stmt->GetUTF8String(1, entry.mType);
1356 if (NS_WARN_IF(NS_FAILED(rv))) {
1357 continue;
1360 entry.mId = id++;
1361 entry.mPermission = stmt->AsInt32(2);
1362 entry.mExpireType = stmt->AsInt32(3);
1363 entry.mExpireTime = stmt->AsInt64(4);
1364 entry.mModificationTime = stmt->AsInt64(5);
1366 mMigrationEntries.AppendElement(entry);
1370 // Even if we didn't perform the migration, we want to bump the
1371 // schema version to 8.
1372 rv = data->mDBConn->SetSchemaVersion(8);
1373 NS_ENSURE_SUCCESS(rv, rv);
1376 // fall through to the next upgrade
1377 [[fallthrough]];
1379 // The version 8-9 migration removes the unnecessary backup
1380 // moz-hosts database contents. as the data no longer needs to be
1381 // migrated
1382 case 8: {
1383 // We only want to clear out the old table if it is a backup. If
1384 // it isn't a backup, we don't need to touch it.
1385 bool hostsIsBackupExists = false;
1386 data->mDBConn->TableExists("moz_hosts_is_backup"_ns,
1387 &hostsIsBackupExists);
1388 if (hostsIsBackupExists) {
1389 // Delete everything from the backup, we want to keep around the
1390 // table so that you can still downgrade and not break things,
1391 // but we don't need to keep the rows around.
1392 rv = data->mDBConn->ExecuteSimpleSQL("DELETE FROM moz_hosts"_ns);
1393 NS_ENSURE_SUCCESS(rv, rv);
1395 // The table is no longer a backup, so get rid of it.
1396 rv = data->mDBConn->ExecuteSimpleSQL(
1397 "DROP TABLE moz_hosts_is_backup"_ns);
1398 NS_ENSURE_SUCCESS(rv, rv);
1401 rv = data->mDBConn->SetSchemaVersion(9);
1402 NS_ENSURE_SUCCESS(rv, rv);
1405 // fall through to the next upgrade
1406 [[fallthrough]];
1408 case 9: {
1409 rv = data->mDBConn->SetSchemaVersion(10);
1410 NS_ENSURE_SUCCESS(rv, rv);
1413 // fall through to the next upgrade
1414 [[fallthrough]];
1416 case 10: {
1417 // Filter out the rows with storage access API permissions with a
1418 // granted origin, and remove the granted origin part from the
1419 // permission type.
1420 rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
1421 "UPDATE moz_perms "
1422 "SET type=SUBSTR(type, 0, INSTR(SUBSTR(type, INSTR(type, "
1423 "'^') + "
1424 "1), '^') + INSTR(type, '^')) "
1425 "WHERE INSTR(SUBSTR(type, INSTR(type, '^') + 1), '^') AND "
1426 "SUBSTR(type, 0, 18) == \"storageAccessAPI^\";"));
1427 NS_ENSURE_SUCCESS(rv, rv);
1429 rv = data->mDBConn->SetSchemaVersion(11);
1430 NS_ENSURE_SUCCESS(rv, rv);
1433 // fall through to the next upgrade
1434 [[fallthrough]];
1436 case 11: {
1437 // Migrate 3rdPartyStorage keys to a site scope
1438 rv = data->mDBConn->BeginTransaction();
1439 NS_ENSURE_SUCCESS(rv, rv);
1440 nsCOMPtr<mozIStorageStatement> updateStmt;
1441 rv = data->mDBConn->CreateStatement(
1442 nsLiteralCString("UPDATE moz_perms SET origin = ?2 WHERE id = ?1"),
1443 getter_AddRefs(updateStmt));
1444 NS_ENSURE_SUCCESS(rv, rv);
1446 nsCOMPtr<mozIStorageStatement> deleteStmt;
1447 rv = data->mDBConn->CreateStatement(
1448 nsLiteralCString("DELETE FROM moz_perms WHERE id = ?1"),
1449 getter_AddRefs(deleteStmt));
1450 NS_ENSURE_SUCCESS(rv, rv);
1452 nsCOMPtr<mozIStorageStatement> selectStmt;
1453 rv = data->mDBConn->CreateStatement(
1454 nsLiteralCString("SELECT id, origin, type FROM moz_perms WHERE "
1455 " SUBSTR(type, 0, 17) == \"3rdPartyStorage^\""),
1456 getter_AddRefs(selectStmt));
1457 NS_ENSURE_SUCCESS(rv, rv);
1459 nsTHashSet<nsCStringHashKey> deduplicationSet;
1460 bool hasResult;
1461 while (NS_SUCCEEDED(selectStmt->ExecuteStep(&hasResult)) && hasResult) {
1462 int64_t id;
1463 rv = selectStmt->GetInt64(0, &id);
1464 NS_ENSURE_SUCCESS(rv, rv);
1466 nsCString origin;
1467 rv = selectStmt->GetUTF8String(1, origin);
1468 NS_ENSURE_SUCCESS(rv, rv);
1470 nsCString type;
1471 rv = selectStmt->GetUTF8String(2, type);
1472 NS_ENSURE_SUCCESS(rv, rv);
1474 nsCOMPtr<nsIURI> uri;
1475 rv = NS_NewURI(getter_AddRefs(uri), origin);
1476 if (NS_FAILED(rv)) {
1477 continue;
1479 nsCString site;
1480 rv = nsEffectiveTLDService::GetInstance()->GetSite(uri, site);
1481 if (NS_WARN_IF(NS_FAILED(rv))) {
1482 continue;
1485 nsCString deduplicationKey =
1486 nsPrintfCString("%s,%s", site.get(), type.get());
1487 if (deduplicationSet.Contains(deduplicationKey)) {
1488 rv = deleteStmt->BindInt64ByIndex(0, id);
1489 NS_ENSURE_SUCCESS(rv, rv);
1491 rv = deleteStmt->Execute();
1492 NS_ENSURE_SUCCESS(rv, rv);
1493 } else {
1494 deduplicationSet.Insert(deduplicationKey);
1495 rv = updateStmt->BindInt64ByIndex(0, id);
1496 NS_ENSURE_SUCCESS(rv, rv);
1497 rv = updateStmt->BindUTF8StringByIndex(1, site);
1498 NS_ENSURE_SUCCESS(rv, rv);
1500 rv = updateStmt->Execute();
1501 NS_ENSURE_SUCCESS(rv, rv);
1504 rv = data->mDBConn->CommitTransaction();
1505 NS_ENSURE_SUCCESS(rv, rv);
1507 rv = data->mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
1508 NS_ENSURE_SUCCESS(rv, rv);
1511 // fall through to the next upgrade
1512 [[fallthrough]];
1514 // current version.
1515 case HOSTS_SCHEMA_VERSION:
1516 break;
1518 // downgrading.
1519 // if columns have been added to the table, we can still use the
1520 // ones we understand safely. if columns have been deleted or
1521 // altered, just blow away the table and start from scratch! if you
1522 // change the way a column is interpreted, make sure you also change
1523 // its name so this check will catch it.
1524 default: {
1525 // check if all the expected columns exist
1526 nsCOMPtr<mozIStorageStatement> stmt;
1527 rv = data->mDBConn->CreateStatement(
1528 nsLiteralCString("SELECT origin, type, permission, "
1529 "expireType, expireTime, "
1530 "modificationTime FROM moz_perms"),
1531 getter_AddRefs(stmt));
1532 if (NS_SUCCEEDED(rv)) break;
1534 // our columns aren't there - drop the table!
1535 rv = data->mDBConn->ExecuteSimpleSQL("DROP TABLE moz_perms"_ns);
1536 NS_ENSURE_SUCCESS(rv, rv);
1538 rv = CreateTable();
1539 NS_ENSURE_SUCCESS(rv, rv);
1540 } break;
1544 // cache frequently used statements (for insertion, deletion, and
1545 // updating)
1546 rv = data->mDBConn->CreateStatement(
1547 nsLiteralCString("INSERT INTO moz_perms "
1548 "(id, origin, type, permission, expireType, "
1549 "expireTime, modificationTime) "
1550 "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"),
1551 getter_AddRefs(data->mStmtInsert));
1552 NS_ENSURE_SUCCESS(rv, rv);
1554 rv = data->mDBConn->CreateStatement(nsLiteralCString("DELETE FROM moz_perms "
1555 "WHERE id = ?1"),
1556 getter_AddRefs(data->mStmtDelete));
1557 NS_ENSURE_SUCCESS(rv, rv);
1559 rv = data->mDBConn->CreateStatement(
1560 nsLiteralCString("UPDATE moz_perms "
1561 "SET permission = ?2, expireType= ?3, expireTime = "
1562 "?4, modificationTime = ?5 WHERE id = ?1"),
1563 getter_AddRefs(data->mStmtUpdate));
1564 NS_ENSURE_SUCCESS(rv, rv);
1566 // Always import default permissions.
1567 ConsumeDefaultsInputStream(aDefaultsInputStream, lock);
1569 // check whether to import or just read in the db
1570 if (tableExists) {
1571 rv = Read(lock);
1572 NS_ENSURE_SUCCESS(rv, rv);
1575 raiiFailure.release();
1577 return NS_OK;
1580 void PermissionManager::AddIdleDailyMaintenanceJob() {
1581 MOZ_ASSERT(NS_IsMainThread());
1583 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
1584 NS_ENSURE_TRUE_VOID(observerService);
1586 nsresult rv =
1587 observerService->AddObserver(this, OBSERVER_TOPIC_IDLE_DAILY, false);
1588 NS_ENSURE_SUCCESS_VOID(rv);
1591 void PermissionManager::RemoveIdleDailyMaintenanceJob() {
1592 MOZ_ASSERT(NS_IsMainThread());
1594 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
1595 NS_ENSURE_TRUE_VOID(observerService);
1597 nsresult rv =
1598 observerService->RemoveObserver(this, OBSERVER_TOPIC_IDLE_DAILY);
1599 NS_ENSURE_SUCCESS_VOID(rv);
1602 void PermissionManager::PerformIdleDailyMaintenance() {
1603 MOZ_ASSERT(NS_IsMainThread());
1605 RefPtr<PermissionManager> self = this;
1606 mThread->Dispatch(NS_NewRunnableFunction(
1607 "PermissionManager::PerformIdleDailyMaintenance", [self] {
1608 auto data = self->mThreadBoundData.Access();
1610 if (self->mState == eClosed || !data->mDBConn) {
1611 return;
1614 nsCOMPtr<mozIStorageStatement> stmtDeleteExpired;
1615 nsresult rv = data->mDBConn->CreateStatement(
1616 nsLiteralCString("DELETE FROM moz_perms WHERE expireType = "
1617 "?1 AND expireTime <= ?2"),
1618 getter_AddRefs(stmtDeleteExpired));
1619 NS_ENSURE_SUCCESS_VOID(rv);
1621 rv = stmtDeleteExpired->BindInt32ByIndex(
1622 0, nsIPermissionManager::EXPIRE_TIME);
1623 NS_ENSURE_SUCCESS_VOID(rv);
1625 rv = stmtDeleteExpired->BindInt64ByIndex(1, EXPIRY_NOW);
1626 NS_ENSURE_SUCCESS_VOID(rv);
1628 rv = stmtDeleteExpired->Execute();
1629 NS_ENSURE_SUCCESS_VOID(rv);
1630 }));
1633 // sets the schema version and creates the moz_perms table.
1634 nsresult PermissionManager::CreateTable() {
1635 MOZ_ASSERT(!NS_IsMainThread());
1636 auto data = mThreadBoundData.Access();
1638 // set the schema version, before creating the table
1639 nsresult rv = data->mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
1640 if (NS_FAILED(rv)) return rv;
1642 // create the table
1643 // SQL also lives in automation.py.in. If you change this SQL change that
1644 // one too
1645 rv = data->mDBConn->ExecuteSimpleSQL(
1646 nsLiteralCString("CREATE TABLE moz_perms ("
1647 " id INTEGER PRIMARY KEY"
1648 ",origin TEXT"
1649 ",type TEXT"
1650 ",permission INTEGER"
1651 ",expireType INTEGER"
1652 ",expireTime INTEGER"
1653 ",modificationTime INTEGER"
1654 ")"));
1655 if (NS_FAILED(rv)) return rv;
1657 // We also create a legacy V4 table, for backwards compatability,
1658 // and to ensure that downgrades don't trigger a schema version change.
1659 return data->mDBConn->ExecuteSimpleSQL(
1660 nsLiteralCString("CREATE TABLE moz_hosts ("
1661 " id INTEGER PRIMARY KEY"
1662 ",host TEXT"
1663 ",type TEXT"
1664 ",permission INTEGER"
1665 ",expireType INTEGER"
1666 ",expireTime INTEGER"
1667 ",modificationTime INTEGER"
1668 ",isInBrowserElement INTEGER"
1669 ")"));
1672 // Returns whether the given combination of expire type and expire time are
1673 // expired. Note that EXPIRE_SESSION only honors expireTime if it is nonzero.
1674 bool PermissionManager::HasExpired(uint32_t aExpireType, int64_t aExpireTime) {
1675 return (aExpireType == nsIPermissionManager::EXPIRE_TIME ||
1676 (aExpireType == nsIPermissionManager::EXPIRE_SESSION &&
1677 aExpireTime != 0)) &&
1678 aExpireTime <= EXPIRY_NOW;
1681 NS_IMETHODIMP
1682 PermissionManager::AddFromPrincipalAndPersistInPrivateBrowsing(
1683 nsIPrincipal* aPrincipal, const nsACString& aType, uint32_t aPermission) {
1684 ENSURE_NOT_CHILD_PROCESS;
1685 NS_ENSURE_ARG_POINTER(aPrincipal);
1686 // We don't add the system principal because it actually has no URI and we
1687 // always allow action for them.
1688 if (aPrincipal->IsSystemPrincipal()) {
1689 return NS_OK;
1692 // Null principals can't meaningfully have persisted permissions attached to
1693 // them, so we don't allow adding permissions for them.
1694 if (aPrincipal->GetIsNullPrincipal()) {
1695 return NS_OK;
1698 // Permissions may not be added to expanded principals.
1699 if (IsExpandedPrincipal(aPrincipal)) {
1700 return NS_ERROR_INVALID_ARG;
1703 // A modificationTime of zero will cause AddInternal to use now().
1704 int64_t modificationTime = 0;
1706 return AddInternal(aPrincipal, aType, aPermission, 0,
1707 nsIPermissionManager::EXPIRE_NEVER,
1708 /* aExpireTime */ 0, modificationTime, eNotify, eWriteToDB,
1709 /* aIgnoreSessionPermissions */ false,
1710 /* aOriginString*/ nullptr,
1711 /* aAllowPersistInPrivateBrowsing */ true);
1714 NS_IMETHODIMP
1715 PermissionManager::AddFromPrincipal(nsIPrincipal* aPrincipal,
1716 const nsACString& aType,
1717 uint32_t aPermission, uint32_t aExpireType,
1718 int64_t aExpireTime) {
1719 ENSURE_NOT_CHILD_PROCESS;
1720 NS_ENSURE_ARG_POINTER(aPrincipal);
1721 NS_ENSURE_TRUE(aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
1722 aExpireType == nsIPermissionManager::EXPIRE_TIME ||
1723 aExpireType == nsIPermissionManager::EXPIRE_SESSION ||
1724 aExpireType == nsIPermissionManager::EXPIRE_POLICY,
1725 NS_ERROR_INVALID_ARG);
1727 // Skip addition if the permission is already expired.
1728 if (HasExpired(aExpireType, aExpireTime)) {
1729 return NS_OK;
1732 // We don't add the system principal because it actually has no URI and we
1733 // always allow action for them.
1734 if (aPrincipal->IsSystemPrincipal()) {
1735 return NS_OK;
1738 // Null principals can't meaningfully have persisted permissions attached to
1739 // them, so we don't allow adding permissions for them.
1740 if (aPrincipal->GetIsNullPrincipal()) {
1741 return NS_OK;
1744 // Permissions may not be added to expanded principals.
1745 if (IsExpandedPrincipal(aPrincipal)) {
1746 return NS_ERROR_INVALID_ARG;
1749 // A modificationTime of zero will cause AddInternal to use now().
1750 int64_t modificationTime = 0;
1752 return AddInternal(aPrincipal, aType, aPermission, 0, aExpireType,
1753 aExpireTime, modificationTime, eNotify, eWriteToDB);
1756 nsresult PermissionManager::AddInternal(
1757 nsIPrincipal* aPrincipal, const nsACString& aType, uint32_t aPermission,
1758 int64_t aID, uint32_t aExpireType, int64_t aExpireTime,
1759 int64_t aModificationTime, NotifyOperationType aNotifyOperation,
1760 DBOperationType aDBOperation, const bool aIgnoreSessionPermissions,
1761 const nsACString* aOriginString,
1762 const bool aAllowPersistInPrivateBrowsing) {
1763 MOZ_ASSERT(NS_IsMainThread());
1765 EnsureReadCompleted();
1767 nsresult rv = NS_OK;
1768 nsAutoCString origin;
1769 // Only attempt to compute the origin string when it is going to be needed
1770 // later on in the function.
1771 if (!IsChildProcess() ||
1772 (aDBOperation == eWriteToDB && IsPersistentExpire(aExpireType, aType))) {
1773 if (aOriginString) {
1774 // Use the origin string provided by the caller.
1775 origin = *aOriginString;
1776 } else {
1777 if (IsSiteScopedPermission(aType)) {
1778 rv = GetSiteFromPrincipal(aPrincipal, IsOAForceStripPermission(aType),
1779 origin);
1780 } else {
1781 // Compute it from the principal provided.
1782 rv = GetOriginFromPrincipal(aPrincipal, IsOAForceStripPermission(aType),
1783 origin);
1785 NS_ENSURE_SUCCESS(rv, rv);
1789 // Unless the caller sets aAllowPersistInPrivateBrowsing, only store
1790 // permissions for the session in Private Browsing. Except for default
1791 // permissions which are stored in-memory only and imported each startup. We
1792 // also allow setting persistent UKNOWN_ACTION, to support removing default
1793 // private browsing permissions.
1794 if (!aAllowPersistInPrivateBrowsing && aID != cIDPermissionIsDefault &&
1795 aPermission != UNKNOWN_ACTION && aExpireType != EXPIRE_SESSION) {
1796 uint32_t privateBrowsingId =
1797 nsScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID;
1798 nsresult rv = aPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
1799 if (NS_SUCCEEDED(rv) &&
1800 privateBrowsingId !=
1801 nsScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID) {
1802 aExpireType = EXPIRE_SESSION;
1806 // Let's send the new permission to the content process only if it has to be
1807 // notified.
1808 if (!IsChildProcess() && aNotifyOperation == eNotify) {
1809 IPC::Permission permission(origin, aType, aPermission, aExpireType,
1810 aExpireTime);
1812 nsAutoCString permissionKey;
1813 GetKeyForPermission(aPrincipal, aType, permissionKey);
1814 bool isSecondaryKeyed;
1815 nsAutoCString secondaryKey;
1816 isSecondaryKeyed = GetSecondaryKey(aType, secondaryKey);
1817 if (isSecondaryKeyed) {
1818 NotifySecondaryKeyPermissionUpdateInContentProcess(
1819 aType, aPermission, secondaryKey, aPrincipal);
1822 nsTArray<ContentParent*> cplist;
1823 ContentParent::GetAll(cplist);
1824 for (uint32_t i = 0; i < cplist.Length(); ++i) {
1825 ContentParent* cp = cplist[i];
1826 if (cp->NeedsPermissionsUpdate(permissionKey)) {
1827 Unused << cp->SendAddPermission(permission);
1832 MOZ_ASSERT(PermissionAvailable(aPrincipal, aType));
1834 // look up the type index
1835 int32_t typeIndex = GetTypeIndex(aType, true);
1836 NS_ENSURE_TRUE(typeIndex != -1, NS_ERROR_OUT_OF_MEMORY);
1838 // When an entry already exists, PutEntry will return that, instead
1839 // of adding a new one
1840 RefPtr<PermissionKey> key = PermissionKey::CreateFromPrincipal(
1841 aPrincipal, IsOAForceStripPermission(aType),
1842 IsSiteScopedPermission(aType), rv);
1843 if (!key) {
1844 MOZ_ASSERT(NS_FAILED(rv));
1845 return rv;
1848 PermissionHashKey* entry = mPermissionTable.PutEntry(key);
1849 if (!entry) return NS_ERROR_FAILURE;
1850 if (!entry->GetKey()) {
1851 mPermissionTable.RemoveEntry(entry);
1852 return NS_ERROR_OUT_OF_MEMORY;
1855 // figure out the transaction type, and get any existing permission value
1856 OperationType op;
1857 int32_t index = entry->GetPermissionIndex(typeIndex);
1858 if (index == -1) {
1859 if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
1860 op = eOperationNone;
1861 else
1862 op = eOperationAdding;
1864 } else {
1865 PermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
1867 // remove the permission if the permission is UNKNOWN, update the
1868 // permission if its value or expire type have changed OR if the time has
1869 // changed and the expire type is time, otherwise, don't modify. There's
1870 // no need to modify a permission that doesn't expire with time when the
1871 // only thing changed is the expire time.
1872 if (aPermission == oldPermissionEntry.mPermission &&
1873 aExpireType == oldPermissionEntry.mExpireType &&
1874 (aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
1875 aExpireTime == oldPermissionEntry.mExpireTime))
1876 op = eOperationNone;
1877 else if (oldPermissionEntry.mID == cIDPermissionIsDefault)
1878 // The existing permission is one added as a default and the new
1879 // permission doesn't exactly match so we are replacing the default. This
1880 // is true even if the new permission is UNKNOWN_ACTION (which means a
1881 // "logical remove" of the default)
1882 op = eOperationReplacingDefault;
1883 else if (aID == cIDPermissionIsDefault)
1884 // We are adding a default permission but a "real" permission already
1885 // exists. This almost-certainly means we just did a removeAllSince and
1886 // are re-importing defaults - so we can ignore this.
1887 op = eOperationNone;
1888 else if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
1889 op = eOperationRemoving;
1890 else
1891 op = eOperationChanging;
1894 // child processes should *always* be passed a modificationTime of zero.
1895 MOZ_ASSERT(!IsChildProcess() || aModificationTime == 0);
1897 // do the work for adding, deleting, or changing a permission:
1898 // update the in-memory list, write to the db, and notify consumers.
1899 int64_t id;
1900 if (aModificationTime == 0) {
1901 aModificationTime = EXPIRY_NOW;
1904 switch (op) {
1905 case eOperationNone: {
1906 // nothing to do
1907 return NS_OK;
1910 case eOperationAdding: {
1911 if (aDBOperation == eWriteToDB) {
1912 // we'll be writing to the database - generate a known unique id
1913 id = ++mLargestID;
1914 } else {
1915 // we're reading from the database - use the id already assigned
1916 id = aID;
1919 entry->GetPermissions().AppendElement(
1920 PermissionEntry(id, typeIndex, aPermission, aExpireType, aExpireTime,
1921 aModificationTime));
1923 if (aDBOperation == eWriteToDB &&
1924 IsPersistentExpire(aExpireType, aType)) {
1925 UpdateDB(op, id, origin, aType, aPermission, aExpireType, aExpireTime,
1926 aModificationTime);
1929 if (aNotifyOperation == eNotify) {
1930 NotifyObserversWithPermission(aPrincipal, mTypeArray[typeIndex],
1931 aPermission, aExpireType, aExpireTime,
1932 aModificationTime, u"added");
1935 break;
1938 case eOperationRemoving: {
1939 PermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
1940 id = oldPermissionEntry.mID;
1942 // If the type we want to remove is EXPIRE_POLICY, we need to reject
1943 // attempts to change the permission.
1944 if (entry->GetPermissions()[index].mExpireType == EXPIRE_POLICY) {
1945 NS_WARNING("Attempting to remove EXPIRE_POLICY permission");
1946 break;
1949 entry->GetPermissions().RemoveElementAt(index);
1951 if (aDBOperation == eWriteToDB)
1952 // We care only about the id here so we pass dummy values for all other
1953 // parameters.
1954 UpdateDB(op, id, ""_ns, ""_ns, 0, nsIPermissionManager::EXPIRE_NEVER, 0,
1957 if (aNotifyOperation == eNotify) {
1958 NotifyObserversWithPermission(
1959 aPrincipal, mTypeArray[typeIndex], oldPermissionEntry.mPermission,
1960 oldPermissionEntry.mExpireType, oldPermissionEntry.mExpireTime,
1961 oldPermissionEntry.mModificationTime, u"deleted");
1964 // If there are no more permissions stored for that entry, clear it.
1965 if (entry->GetPermissions().IsEmpty()) {
1966 mPermissionTable.RemoveEntry(entry);
1969 break;
1972 case eOperationChanging: {
1973 id = entry->GetPermissions()[index].mID;
1975 // If the existing type is EXPIRE_POLICY, we need to reject attempts to
1976 // change the permission.
1977 if (entry->GetPermissions()[index].mExpireType == EXPIRE_POLICY) {
1978 NS_WARNING("Attempting to modify EXPIRE_POLICY permission");
1979 break;
1982 PermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
1984 // If the new expireType is EXPIRE_SESSION, then we have to keep a
1985 // copy of the previous permission/expireType values. This cached value
1986 // will be used when restoring the permissions of an app.
1987 if (entry->GetPermissions()[index].mExpireType !=
1988 nsIPermissionManager::EXPIRE_SESSION &&
1989 aExpireType == nsIPermissionManager::EXPIRE_SESSION) {
1990 entry->GetPermissions()[index].mNonSessionPermission =
1991 entry->GetPermissions()[index].mPermission;
1992 entry->GetPermissions()[index].mNonSessionExpireType =
1993 entry->GetPermissions()[index].mExpireType;
1994 entry->GetPermissions()[index].mNonSessionExpireTime =
1995 entry->GetPermissions()[index].mExpireTime;
1996 } else if (aExpireType != nsIPermissionManager::EXPIRE_SESSION) {
1997 entry->GetPermissions()[index].mNonSessionPermission = aPermission;
1998 entry->GetPermissions()[index].mNonSessionExpireType = aExpireType;
1999 entry->GetPermissions()[index].mNonSessionExpireTime = aExpireTime;
2002 entry->GetPermissions()[index].mPermission = aPermission;
2003 entry->GetPermissions()[index].mExpireType = aExpireType;
2004 entry->GetPermissions()[index].mExpireTime = aExpireTime;
2005 entry->GetPermissions()[index].mModificationTime = aModificationTime;
2007 if (aDBOperation == eWriteToDB) {
2008 bool newIsPersistentExpire = IsPersistentExpire(aExpireType, aType);
2009 bool oldIsPersistentExpire =
2010 IsPersistentExpire(oldPermissionEntry.mExpireType, aType);
2012 if (!newIsPersistentExpire && oldIsPersistentExpire) {
2013 // Maybe we have to remove the previous permission if that was
2014 // persistent.
2015 UpdateDB(eOperationRemoving, id, ""_ns, ""_ns, 0,
2016 nsIPermissionManager::EXPIRE_NEVER, 0, 0);
2017 } else if (newIsPersistentExpire && !oldIsPersistentExpire) {
2018 // It could also be that the previous permission was session-only but
2019 // this needs to be written into the DB. In this case, we have to run
2020 // an Adding operation.
2021 UpdateDB(eOperationAdding, id, origin, aType, aPermission,
2022 aExpireType, aExpireTime, aModificationTime);
2023 } else if (newIsPersistentExpire) {
2024 // This is the a simple update. We care only about the id, the
2025 // permission and expireType/expireTime/modificationTime here. We pass
2026 // dummy values for all other parameters.
2027 UpdateDB(op, id, ""_ns, ""_ns, aPermission, aExpireType, aExpireTime,
2028 aModificationTime);
2032 if (aNotifyOperation == eNotify) {
2033 NotifyObserversWithPermission(aPrincipal, mTypeArray[typeIndex],
2034 aPermission, aExpireType, aExpireTime,
2035 aModificationTime, u"changed");
2038 break;
2040 case eOperationReplacingDefault: {
2041 // this is handling the case when we have an existing permission
2042 // entry that was created as a "default" (and thus isn't in the DB) with
2043 // an explicit permission (that may include UNKNOWN_ACTION.)
2044 // Note we will *not* get here if we are replacing an already replaced
2045 // default value - that is handled as eOperationChanging.
2047 // So this is a hybrid of eOperationAdding (as we are writing a new entry
2048 // to the DB) and eOperationChanging (as we are replacing the in-memory
2049 // repr and sending a "changed" notification).
2051 // We want a new ID even if not writing to the DB, so the modified entry
2052 // in memory doesn't have the magic cIDPermissionIsDefault value.
2053 id = ++mLargestID;
2055 // The default permission being replaced can't have session expiry or
2056 // policy expiry.
2057 NS_ENSURE_TRUE(entry->GetPermissions()[index].mExpireType !=
2058 nsIPermissionManager::EXPIRE_SESSION,
2059 NS_ERROR_UNEXPECTED);
2060 NS_ENSURE_TRUE(entry->GetPermissions()[index].mExpireType !=
2061 nsIPermissionManager::EXPIRE_POLICY,
2062 NS_ERROR_UNEXPECTED);
2063 // We don't support the new entry having any expiry - supporting that
2064 // would make things far more complex and none of the permissions we set
2065 // as a default support that.
2066 NS_ENSURE_TRUE(aExpireType == EXPIRE_NEVER, NS_ERROR_UNEXPECTED);
2068 // update the existing entry in memory.
2069 entry->GetPermissions()[index].mID = id;
2070 entry->GetPermissions()[index].mPermission = aPermission;
2071 entry->GetPermissions()[index].mExpireType = aExpireType;
2072 entry->GetPermissions()[index].mExpireTime = aExpireTime;
2073 entry->GetPermissions()[index].mModificationTime = aModificationTime;
2075 // If requested, create the entry in the DB.
2076 if (aDBOperation == eWriteToDB &&
2077 IsPersistentExpire(aExpireType, aType)) {
2078 UpdateDB(eOperationAdding, id, origin, aType, aPermission, aExpireType,
2079 aExpireTime, aModificationTime);
2082 if (aNotifyOperation == eNotify) {
2083 NotifyObserversWithPermission(aPrincipal, mTypeArray[typeIndex],
2084 aPermission, aExpireType, aExpireTime,
2085 aModificationTime, u"changed");
2088 } break;
2091 return NS_OK;
2094 NS_IMETHODIMP
2095 PermissionManager::RemoveFromPrincipal(nsIPrincipal* aPrincipal,
2096 const nsACString& aType) {
2097 ENSURE_NOT_CHILD_PROCESS;
2098 NS_ENSURE_ARG_POINTER(aPrincipal);
2100 // System principals are never added to the database, no need to remove them.
2101 if (aPrincipal->IsSystemPrincipal()) {
2102 return NS_OK;
2105 // Permissions may not be added to expanded principals.
2106 if (IsExpandedPrincipal(aPrincipal)) {
2107 return NS_ERROR_INVALID_ARG;
2110 // AddInternal() handles removal, just let it do the work
2111 return AddInternal(aPrincipal, aType, nsIPermissionManager::UNKNOWN_ACTION, 0,
2112 nsIPermissionManager::EXPIRE_NEVER, 0, 0, eNotify,
2113 eWriteToDB);
2116 NS_IMETHODIMP
2117 PermissionManager::RemovePermission(nsIPermission* aPerm) {
2118 if (!aPerm) {
2119 return NS_OK;
2121 nsCOMPtr<nsIPrincipal> principal;
2122 nsresult rv = aPerm->GetPrincipal(getter_AddRefs(principal));
2123 NS_ENSURE_SUCCESS(rv, rv);
2125 nsAutoCString type;
2126 rv = aPerm->GetType(type);
2127 NS_ENSURE_SUCCESS(rv, rv);
2129 // Permissions are uniquely identified by their principal and type.
2130 // We remove the permission using these two pieces of data.
2131 return RemoveFromPrincipal(principal, type);
2134 NS_IMETHODIMP
2135 PermissionManager::RemoveAll() {
2136 ENSURE_NOT_CHILD_PROCESS;
2137 return RemoveAllInternal(true);
2140 NS_IMETHODIMP
2141 PermissionManager::RemoveAllSince(int64_t aSince) {
2142 ENSURE_NOT_CHILD_PROCESS;
2143 return RemoveAllModifiedSince(aSince);
2146 template <class T>
2147 nsresult PermissionManager::RemovePermissionEntries(T aCondition) {
2148 EnsureReadCompleted();
2150 Vector<std::tuple<nsCOMPtr<nsIPrincipal>, nsCString, nsCString>, 10> array;
2151 for (const PermissionHashKey& entry : mPermissionTable) {
2152 for (const auto& permEntry : entry.GetPermissions()) {
2153 if (!aCondition(permEntry)) {
2154 continue;
2157 nsCOMPtr<nsIPrincipal> principal;
2158 nsresult rv = GetPrincipalFromOrigin(
2159 entry.GetKey()->mOrigin,
2160 IsOAForceStripPermission(mTypeArray[permEntry.mType]),
2161 getter_AddRefs(principal));
2162 if (NS_FAILED(rv)) {
2163 continue;
2166 if (!array.emplaceBack(principal, mTypeArray[permEntry.mType],
2167 entry.GetKey()->mOrigin)) {
2168 continue;
2173 for (auto& i : array) {
2174 // AddInternal handles removal, so let it do the work...
2175 AddInternal(
2176 std::get<0>(i), std::get<1>(i), nsIPermissionManager::UNKNOWN_ACTION, 0,
2177 nsIPermissionManager::EXPIRE_NEVER, 0, 0, PermissionManager::eNotify,
2178 PermissionManager::eWriteToDB, false, &std::get<2>(i));
2181 // now re-import any defaults as they may now be required if we just deleted
2182 // an override.
2183 ImportLatestDefaults();
2184 return NS_OK;
2187 NS_IMETHODIMP
2188 PermissionManager::RemoveByType(const nsACString& aType) {
2189 ENSURE_NOT_CHILD_PROCESS;
2191 int32_t typeIndex = GetTypeIndex(aType, false);
2192 // If type == -1, the type isn't known,
2193 // so just return NS_OK
2194 if (typeIndex == -1) {
2195 return NS_OK;
2198 return RemovePermissionEntries(
2199 [typeIndex](const PermissionEntry& aPermEntry) {
2200 return static_cast<uint32_t>(typeIndex) == aPermEntry.mType;
2204 NS_IMETHODIMP
2205 PermissionManager::RemoveByTypeSince(const nsACString& aType,
2206 int64_t aModificationTime) {
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, aModificationTime](const PermissionEntry& aPermEntry) {
2218 return uint32_t(typeIndex) == aPermEntry.mType &&
2219 aModificationTime <= aPermEntry.mModificationTime;
2223 void PermissionManager::CloseDB(CloseDBNextOp aNextOp) {
2224 EnsureReadCompleted();
2226 mState = eClosed;
2228 nsCOMPtr<nsIInputStream> defaultsInputStream;
2229 if (aNextOp == eRebuldOnSuccess) {
2230 defaultsInputStream = GetDefaultsInputStream();
2233 RefPtr<PermissionManager> self = this;
2234 mThread->Dispatch(NS_NewRunnableFunction(
2235 "PermissionManager::CloseDB", [self, aNextOp, defaultsInputStream] {
2236 auto data = self->mThreadBoundData.Access();
2237 // Null the statements, this will finalize them.
2238 data->mStmtInsert = nullptr;
2239 data->mStmtDelete = nullptr;
2240 data->mStmtUpdate = nullptr;
2241 if (data->mDBConn) {
2242 DebugOnly<nsresult> rv = data->mDBConn->Close();
2243 MOZ_ASSERT(NS_SUCCEEDED(rv));
2244 data->mDBConn = nullptr;
2246 if (aNextOp == eRebuldOnSuccess) {
2247 self->TryInitDB(true, defaultsInputStream);
2251 if (aNextOp == eShutdown) {
2252 NS_DispatchToMainThread(NS_NewRunnableFunction(
2253 "PermissionManager::MaybeCompleteShutdown",
2254 [self] { self->MaybeCompleteShutdown(); }));
2256 }));
2259 nsresult PermissionManager::RemoveAllFromIPC() {
2260 MOZ_ASSERT(IsChildProcess());
2262 // Remove from memory and notify immediately. Since the in-memory
2263 // database is authoritative, we do not need confirmation from the
2264 // on-disk database to notify observers.
2265 RemoveAllFromMemory();
2267 return NS_OK;
2270 nsresult PermissionManager::RemoveAllInternal(bool aNotifyObservers) {
2271 ENSURE_NOT_CHILD_PROCESS;
2273 EnsureReadCompleted();
2275 // Let's broadcast the removeAll() to any content process.
2276 nsTArray<ContentParent*> parents;
2277 ContentParent::GetAll(parents);
2278 for (ContentParent* parent : parents) {
2279 Unused << parent->SendRemoveAllPermissions();
2282 // Remove from memory and notify immediately. Since the in-memory
2283 // database is authoritative, we do not need confirmation from the
2284 // on-disk database to notify observers.
2285 RemoveAllFromMemory();
2287 // Re-import the defaults
2288 ImportLatestDefaults();
2290 if (aNotifyObservers) {
2291 NotifyObservers(nullptr, u"cleared");
2294 RefPtr<PermissionManager> self = this;
2295 mThread->Dispatch(
2296 NS_NewRunnableFunction("PermissionManager::RemoveAllInternal", [self] {
2297 auto data = self->mThreadBoundData.Access();
2299 if (self->mState == eClosed || !data->mDBConn) {
2300 return;
2303 // clear the db
2304 nsresult rv =
2305 data->mDBConn->ExecuteSimpleSQL("DELETE FROM moz_perms"_ns);
2306 if (NS_WARN_IF(NS_FAILED(rv))) {
2307 NS_DispatchToMainThread(NS_NewRunnableFunction(
2308 "PermissionManager::RemoveAllInternal-Failure",
2309 [self] { self->CloseDB(eRebuldOnSuccess); }));
2311 }));
2313 return NS_OK;
2316 NS_IMETHODIMP
2317 PermissionManager::TestExactPermissionFromPrincipal(nsIPrincipal* aPrincipal,
2318 const nsACString& aType,
2319 uint32_t* aPermission) {
2320 return CommonTestPermission(aPrincipal, -1, aType, aPermission,
2321 nsIPermissionManager::UNKNOWN_ACTION, false, true,
2322 true);
2325 NS_IMETHODIMP
2326 PermissionManager::TestExactPermanentPermission(nsIPrincipal* aPrincipal,
2327 const nsACString& aType,
2328 uint32_t* aPermission) {
2329 return CommonTestPermission(aPrincipal, -1, aType, aPermission,
2330 nsIPermissionManager::UNKNOWN_ACTION, false, true,
2331 false);
2334 nsresult PermissionManager::LegacyTestPermissionFromURI(
2335 nsIURI* aURI, const OriginAttributes* aOriginAttributes,
2336 const nsACString& aType, uint32_t* aPermission) {
2337 return CommonTestPermission(aURI, aOriginAttributes, -1, aType, aPermission,
2338 nsIPermissionManager::UNKNOWN_ACTION, false,
2339 false, true);
2342 NS_IMETHODIMP
2343 PermissionManager::TestPermissionFromPrincipal(nsIPrincipal* aPrincipal,
2344 const nsACString& aType,
2345 uint32_t* aPermission) {
2346 return CommonTestPermission(aPrincipal, -1, aType, aPermission,
2347 nsIPermissionManager::UNKNOWN_ACTION, false,
2348 false, true);
2351 NS_IMETHODIMP
2352 PermissionManager::GetPermissionObject(nsIPrincipal* aPrincipal,
2353 const nsACString& aType,
2354 bool aExactHostMatch,
2355 nsIPermission** aResult) {
2356 NS_ENSURE_ARG_POINTER(aPrincipal);
2357 *aResult = nullptr;
2359 EnsureReadCompleted();
2361 if (aPrincipal->IsSystemPrincipal()) {
2362 return NS_OK;
2365 // Querying the permission object of an nsEP is non-sensical.
2366 if (IsExpandedPrincipal(aPrincipal)) {
2367 return NS_ERROR_INVALID_ARG;
2370 MOZ_ASSERT(PermissionAvailable(aPrincipal, aType));
2372 int32_t typeIndex = GetTypeIndex(aType, false);
2373 // If type == -1, the type isn't known,
2374 // so just return NS_OK
2375 if (typeIndex == -1) return NS_OK;
2377 PermissionHashKey* entry =
2378 GetPermissionHashKey(aPrincipal, typeIndex, aExactHostMatch);
2379 if (!entry) {
2380 return NS_OK;
2383 // We don't call GetPermission(typeIndex) because that returns a fake
2384 // UNKNOWN_ACTION entry if there is no match.
2385 int32_t idx = entry->GetPermissionIndex(typeIndex);
2386 if (-1 == idx) {
2387 return NS_OK;
2390 nsCOMPtr<nsIPrincipal> principal;
2391 nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin,
2392 IsOAForceStripPermission(aType),
2393 getter_AddRefs(principal));
2394 NS_ENSURE_SUCCESS(rv, rv);
2396 PermissionEntry& perm = entry->GetPermissions()[idx];
2397 nsCOMPtr<nsIPermission> r = Permission::Create(
2398 principal, mTypeArray[perm.mType], perm.mPermission, perm.mExpireType,
2399 perm.mExpireTime, perm.mModificationTime);
2400 if (NS_WARN_IF(!r)) {
2401 return NS_ERROR_FAILURE;
2403 r.forget(aResult);
2404 return NS_OK;
2407 nsresult PermissionManager::CommonTestPermissionInternal(
2408 nsIPrincipal* aPrincipal, nsIURI* aURI,
2409 const OriginAttributes* aOriginAttributes, int32_t aTypeIndex,
2410 const nsACString& aType, uint32_t* aPermission, bool aExactHostMatch,
2411 bool aIncludingSession) {
2412 MOZ_ASSERT(aPrincipal || aURI);
2413 NS_ENSURE_ARG_POINTER(aPrincipal || aURI);
2414 MOZ_ASSERT_IF(aPrincipal, !aURI && !aOriginAttributes);
2415 MOZ_ASSERT_IF(aURI || aOriginAttributes, !aPrincipal);
2417 EnsureReadCompleted();
2419 #ifdef DEBUG
2421 nsCOMPtr<nsIPrincipal> prin = aPrincipal;
2422 if (!prin) {
2423 if (aURI) {
2424 prin = BasePrincipal::CreateContentPrincipal(aURI, OriginAttributes());
2427 MOZ_ASSERT(prin);
2428 MOZ_ASSERT(PermissionAvailable(prin, aType));
2430 #endif
2432 PermissionHashKey* entry =
2433 aPrincipal ? GetPermissionHashKey(aPrincipal, aTypeIndex, aExactHostMatch)
2434 : GetPermissionHashKey(aURI, aOriginAttributes, aTypeIndex,
2435 aExactHostMatch);
2436 if (!entry || (!aIncludingSession &&
2437 entry->GetPermission(aTypeIndex).mNonSessionExpireType ==
2438 nsIPermissionManager::EXPIRE_SESSION)) {
2439 return NS_OK;
2442 *aPermission = aIncludingSession
2443 ? entry->GetPermission(aTypeIndex).mPermission
2444 : entry->GetPermission(aTypeIndex).mNonSessionPermission;
2446 return NS_OK;
2449 // Helper function to filter permissions using a condition function.
2450 template <class T>
2451 nsresult PermissionManager::GetPermissionEntries(
2452 T aCondition, nsTArray<RefPtr<nsIPermission>>& aResult) {
2453 aResult.Clear();
2454 if (XRE_IsContentProcess()) {
2455 NS_WARNING(
2456 "Iterating over all permissions is not available in the "
2457 "content process, as not all permissions may be available.");
2458 return NS_ERROR_NOT_AVAILABLE;
2461 EnsureReadCompleted();
2463 for (const PermissionHashKey& entry : mPermissionTable) {
2464 for (const auto& permEntry : entry.GetPermissions()) {
2465 // Given how "default" permissions work and the possibility of them being
2466 // overridden with UNKNOWN_ACTION, we might see this value here - but we
2467 // do *not* want to return them via the enumerator.
2468 if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
2469 continue;
2472 // If the permission is expired, skip it. We're not deleting it here
2473 // because we're iterating over a lot of permissions.
2474 // It will be removed as part of the daily maintenance later.
2475 if (HasExpired(permEntry.mExpireType, permEntry.mExpireTime)) {
2476 continue;
2479 if (!aCondition(permEntry)) {
2480 continue;
2483 nsCOMPtr<nsIPrincipal> principal;
2484 nsresult rv = GetPrincipalFromOrigin(
2485 entry.GetKey()->mOrigin,
2486 IsOAForceStripPermission(mTypeArray[permEntry.mType]),
2487 getter_AddRefs(principal));
2488 if (NS_FAILED(rv)) {
2489 continue;
2492 RefPtr<nsIPermission> permission = Permission::Create(
2493 principal, mTypeArray[permEntry.mType], permEntry.mPermission,
2494 permEntry.mExpireType, permEntry.mExpireTime,
2495 permEntry.mModificationTime);
2496 if (NS_WARN_IF(!permission)) {
2497 continue;
2499 aResult.AppendElement(std::move(permission));
2503 return NS_OK;
2506 NS_IMETHODIMP PermissionManager::GetAll(
2507 nsTArray<RefPtr<nsIPermission>>& aResult) {
2508 return GetPermissionEntries(
2509 [](const PermissionEntry& aPermEntry) { return true; }, aResult);
2512 NS_IMETHODIMP PermissionManager::GetAllByTypeSince(
2513 const nsACString& aPrefix, int64_t aSince,
2514 nsTArray<RefPtr<nsIPermission>>& aResult) {
2515 // Check that aSince is a reasonable point in time, not in the future
2516 if (aSince > (PR_Now() / PR_USEC_PER_MSEC)) {
2517 return NS_ERROR_INVALID_ARG;
2519 return GetPermissionEntries(
2520 [&](const PermissionEntry& aPermEntry) {
2521 return mTypeArray[aPermEntry.mType].Equals(aPrefix) &&
2522 aSince <= aPermEntry.mModificationTime;
2524 aResult);
2527 NS_IMETHODIMP PermissionManager::GetAllWithTypePrefix(
2528 const nsACString& aPrefix, nsTArray<RefPtr<nsIPermission>>& aResult) {
2529 return GetPermissionEntries(
2530 [&](const PermissionEntry& aPermEntry) {
2531 return StringBeginsWith(mTypeArray[aPermEntry.mType], aPrefix);
2533 aResult);
2536 NS_IMETHODIMP PermissionManager::GetAllByTypes(
2537 const nsTArray<nsCString>& aTypes,
2538 nsTArray<RefPtr<nsIPermission>>& aResult) {
2539 if (aTypes.IsEmpty()) {
2540 return NS_OK;
2543 return GetPermissionEntries(
2544 [&](const PermissionEntry& aPermEntry) {
2545 return aTypes.Contains(mTypeArray[aPermEntry.mType]);
2547 aResult);
2550 nsresult PermissionManager::GetAllForPrincipalHelper(
2551 nsIPrincipal* aPrincipal, bool aSiteScopePermissions,
2552 nsTArray<RefPtr<nsIPermission>>& aResult) {
2553 nsresult rv;
2554 RefPtr<PermissionKey> key = PermissionKey::CreateFromPrincipal(
2555 aPrincipal, false, aSiteScopePermissions, rv);
2556 if (!key) {
2557 MOZ_ASSERT(NS_FAILED(rv));
2558 return rv;
2560 PermissionHashKey* entry = mPermissionTable.GetEntry(key);
2562 nsTArray<PermissionEntry> strippedPerms;
2563 rv = GetStripPermsForPrincipal(aPrincipal, aSiteScopePermissions,
2564 strippedPerms);
2565 if (NS_FAILED(rv)) {
2566 return rv;
2569 if (entry) {
2570 for (const auto& permEntry : entry->GetPermissions()) {
2571 // Only return custom permissions
2572 if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
2573 continue;
2576 // If the permission is expired, skip it. We're not deleting it here
2577 // because we're iterating over a lot of permissions.
2578 // It will be removed as part of the daily maintenance later.
2579 if (HasExpired(permEntry.mExpireType, permEntry.mExpireTime)) {
2580 continue;
2583 // Make sure that we only get site scoped permissions if this
2584 // helper is being invoked for that purpose.
2585 if (aSiteScopePermissions !=
2586 IsSiteScopedPermission(mTypeArray[permEntry.mType])) {
2587 continue;
2590 // Stripped principal permissions overwrite regular ones
2591 // For each permission check if there is a stripped permission we should
2592 // use instead
2593 PermissionEntry perm = permEntry;
2594 nsTArray<PermissionEntry>::index_type index = 0;
2595 for (const auto& strippedPerm : strippedPerms) {
2596 if (strippedPerm.mType == permEntry.mType) {
2597 perm = strippedPerm;
2598 strippedPerms.RemoveElementAt(index);
2599 break;
2601 index++;
2604 RefPtr<nsIPermission> permission = Permission::Create(
2605 aPrincipal, mTypeArray[perm.mType], perm.mPermission,
2606 perm.mExpireType, perm.mExpireTime, perm.mModificationTime);
2607 if (NS_WARN_IF(!permission)) {
2608 continue;
2610 aResult.AppendElement(permission);
2614 for (const auto& perm : strippedPerms) {
2615 RefPtr<nsIPermission> permission = Permission::Create(
2616 aPrincipal, mTypeArray[perm.mType], perm.mPermission, perm.mExpireType,
2617 perm.mExpireTime, perm.mModificationTime);
2618 if (NS_WARN_IF(!permission)) {
2619 continue;
2621 aResult.AppendElement(permission);
2624 return NS_OK;
2627 NS_IMETHODIMP
2628 PermissionManager::GetAllForPrincipal(
2629 nsIPrincipal* aPrincipal, nsTArray<RefPtr<nsIPermission>>& aResult) {
2630 nsresult rv;
2631 aResult.Clear();
2632 EnsureReadCompleted();
2634 MOZ_ASSERT(PermissionAvailable(aPrincipal, ""_ns));
2636 // First, append the non-site-scoped permissions.
2637 rv = GetAllForPrincipalHelper(aPrincipal, false, aResult);
2638 NS_ENSURE_SUCCESS(rv, rv);
2640 // Second, append the site-scoped permissions.
2641 return GetAllForPrincipalHelper(aPrincipal, true, aResult);
2644 NS_IMETHODIMP PermissionManager::Observe(nsISupports* aSubject,
2645 const char* aTopic,
2646 const char16_t* someData) {
2647 ENSURE_NOT_CHILD_PROCESS;
2649 if (!nsCRT::strcmp(aTopic, "profile-do-change") && !mPermissionsFile) {
2650 // profile startup is complete, and we didn't have the permissions file
2651 // before; init the db from the new location
2652 InitDB(false);
2653 } else if (!nsCRT::strcmp(aTopic, "testonly-reload-permissions-from-disk")) {
2654 // Testing mechanism to reload all permissions from disk. Because the
2655 // permission manager automatically initializes itself at startup, tests
2656 // that directly manipulate the permissions database need some way to reload
2657 // the database for their changes to have any effect. This mechanism was
2658 // introduced when moving the permissions manager from on-demand startup to
2659 // always being initialized. This is not guarded by a pref because it's not
2660 // dangerous to reload permissions from disk, just bad for performance.
2661 RemoveAllFromMemory();
2662 CloseDB(eNone);
2663 InitDB(false);
2664 } else if (!nsCRT::strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY)) {
2665 PerformIdleDailyMaintenance();
2668 return NS_OK;
2671 nsresult PermissionManager::RemoveAllModifiedSince(int64_t aModificationTime) {
2672 ENSURE_NOT_CHILD_PROCESS;
2673 // Skip remove calls for default permissions to avoid
2674 // creating UNKNOWN_ACTION overrides in AddInternal
2675 return RemovePermissionEntries(
2676 [aModificationTime](const PermissionEntry& aPermEntry) {
2677 return aModificationTime <= aPermEntry.mModificationTime &&
2678 aPermEntry.mID != cIDPermissionIsDefault;
2682 NS_IMETHODIMP
2683 PermissionManager::RemovePermissionsWithAttributes(const nsAString& aPattern) {
2684 ENSURE_NOT_CHILD_PROCESS;
2685 OriginAttributesPattern pattern;
2686 if (!pattern.Init(aPattern)) {
2687 return NS_ERROR_INVALID_ARG;
2690 return RemovePermissionsWithAttributes(pattern);
2693 nsresult PermissionManager::RemovePermissionsWithAttributes(
2694 OriginAttributesPattern& aPattern) {
2695 EnsureReadCompleted();
2697 Vector<std::tuple<nsCOMPtr<nsIPrincipal>, nsCString, nsCString>, 10>
2698 permissions;
2699 for (const PermissionHashKey& entry : mPermissionTable) {
2700 nsCOMPtr<nsIPrincipal> principal;
2701 nsresult rv = GetPrincipalFromOrigin(entry.GetKey()->mOrigin, false,
2702 getter_AddRefs(principal));
2703 if (NS_FAILED(rv)) {
2704 continue;
2707 if (!aPattern.Matches(principal->OriginAttributesRef())) {
2708 continue;
2711 for (const auto& permEntry : entry.GetPermissions()) {
2712 if (!permissions.emplaceBack(principal, mTypeArray[permEntry.mType],
2713 entry.GetKey()->mOrigin)) {
2714 continue;
2719 for (auto& i : permissions) {
2720 AddInternal(
2721 std::get<0>(i), std::get<1>(i), nsIPermissionManager::UNKNOWN_ACTION, 0,
2722 nsIPermissionManager::EXPIRE_NEVER, 0, 0, PermissionManager::eNotify,
2723 PermissionManager::eWriteToDB, false, &std::get<2>(i));
2726 return NS_OK;
2729 nsresult PermissionManager::GetStripPermsForPrincipal(
2730 nsIPrincipal* aPrincipal, bool aSiteScopePermissions,
2731 nsTArray<PermissionEntry>& aResult) {
2732 aResult.Clear();
2733 aResult.SetCapacity(kStripOAPermissions.size());
2735 #ifdef __clang__
2736 # pragma clang diagnostic push
2737 # pragma clang diagnostic ignored "-Wunreachable-code-return"
2738 #endif
2739 // No special strip permissions
2740 if (kStripOAPermissions.empty()) {
2741 return NS_OK;
2743 #ifdef __clang__
2744 # pragma clang diagnostic pop
2745 #endif
2747 nsresult rv;
2748 // Create a key for the principal, but strip any origin attributes.
2749 // The key must be created aware of whether or not we are scoping to site.
2750 RefPtr<PermissionKey> key = PermissionKey::CreateFromPrincipal(
2751 aPrincipal, true, aSiteScopePermissions, rv);
2752 if (!key) {
2753 MOZ_ASSERT(NS_FAILED(rv));
2754 return rv;
2757 PermissionHashKey* hashKey = mPermissionTable.GetEntry(key);
2758 if (!hashKey) {
2759 return NS_OK;
2762 for (const auto& permType : kStripOAPermissions) {
2763 // if the permission type's site scoping does not match this function call,
2764 // we don't care about it, so continue.
2765 // As of time of writing, this never happens when aSiteScopePermissions
2766 // is true because there is no common permission between kStripOAPermissions
2767 // and kSiteScopedPermissions
2768 if (aSiteScopePermissions != IsSiteScopedPermission(permType)) {
2769 continue;
2771 int32_t index = GetTypeIndex(permType, false);
2772 if (index == -1) {
2773 continue;
2775 PermissionEntry perm = hashKey->GetPermission(index);
2776 if (perm.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
2777 continue;
2779 aResult.AppendElement(perm);
2782 return NS_OK;
2785 int32_t PermissionManager::GetTypeIndex(const nsACString& aType, bool aAdd) {
2786 for (uint32_t i = 0; i < mTypeArray.length(); ++i) {
2787 if (mTypeArray[i].Equals(aType)) {
2788 return i;
2792 if (!aAdd) {
2793 // Not found, but that is ok - we were just looking.
2794 return -1;
2797 // This type was not registered before.
2798 // append it to the array, without copy-constructing the string
2799 if (!mTypeArray.emplaceBack(aType)) {
2800 return -1;
2803 return mTypeArray.length() - 1;
2806 PermissionManager::PermissionHashKey* PermissionManager::GetPermissionHashKey(
2807 nsIPrincipal* aPrincipal, uint32_t aType, bool aExactHostMatch) {
2808 EnsureReadCompleted();
2810 MOZ_ASSERT(PermissionAvailable(aPrincipal, mTypeArray[aType]));
2812 nsresult rv;
2813 RefPtr<PermissionKey> key = PermissionKey::CreateFromPrincipal(
2814 aPrincipal, IsOAForceStripPermission(mTypeArray[aType]),
2815 IsSiteScopedPermission(mTypeArray[aType]), rv);
2816 if (!key) {
2817 return nullptr;
2820 PermissionHashKey* entry = mPermissionTable.GetEntry(key);
2822 if (entry) {
2823 PermissionEntry permEntry = entry->GetPermission(aType);
2825 // if the entry is expired, remove and keep looking for others.
2826 if (HasExpired(permEntry.mExpireType, permEntry.mExpireTime)) {
2827 entry = nullptr;
2828 RemoveFromPrincipal(aPrincipal, mTypeArray[aType]);
2829 } else if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
2830 entry = nullptr;
2834 if (entry) {
2835 return entry;
2838 // If aExactHostMatch wasn't true, we can check if the base domain has a
2839 // permission entry.
2840 if (!aExactHostMatch) {
2841 nsCOMPtr<nsIPrincipal> principal = aPrincipal->GetNextSubDomainPrincipal();
2842 if (principal) {
2843 return GetPermissionHashKey(principal, aType, aExactHostMatch);
2847 // No entry, really...
2848 return nullptr;
2851 PermissionManager::PermissionHashKey* PermissionManager::GetPermissionHashKey(
2852 nsIURI* aURI, const OriginAttributes* aOriginAttributes, uint32_t aType,
2853 bool aExactHostMatch) {
2854 MOZ_ASSERT(aURI);
2856 #ifdef DEBUG
2858 nsCOMPtr<nsIPrincipal> principal;
2859 nsresult rv = NS_OK;
2860 if (aURI) {
2861 rv = GetPrincipal(aURI, getter_AddRefs(principal));
2863 MOZ_ASSERT_IF(NS_SUCCEEDED(rv),
2864 PermissionAvailable(principal, mTypeArray[aType]));
2866 #endif
2868 nsresult rv;
2869 RefPtr<PermissionKey> key;
2871 if (aOriginAttributes) {
2872 key = PermissionKey::CreateFromURIAndOriginAttributes(
2873 aURI, aOriginAttributes, IsOAForceStripPermission(mTypeArray[aType]),
2874 rv);
2875 } else {
2876 key = PermissionKey::CreateFromURI(aURI, rv);
2879 if (!key) {
2880 return nullptr;
2883 PermissionHashKey* entry = mPermissionTable.GetEntry(key);
2885 if (entry) {
2886 PermissionEntry permEntry = entry->GetPermission(aType);
2888 // if the entry is expired, remove and keep looking for others.
2889 if (HasExpired(permEntry.mExpireType, permEntry.mExpireTime)) {
2890 entry = nullptr;
2891 // If we need to remove a permission we mint a principal. This is a bit
2892 // inefficient, but hopefully this code path isn't super common.
2893 nsCOMPtr<nsIPrincipal> principal;
2894 if (aURI) {
2895 nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
2896 if (NS_WARN_IF(NS_FAILED(rv))) {
2897 return nullptr;
2900 RemoveFromPrincipal(principal, mTypeArray[aType]);
2901 } else if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
2902 entry = nullptr;
2906 if (entry) {
2907 return entry;
2910 // If aExactHostMatch wasn't true, we can check if the base domain has a
2911 // permission entry.
2912 if (!aExactHostMatch) {
2913 nsCOMPtr<nsIURI> uri;
2914 if (aURI) {
2915 uri = GetNextSubDomainURI(aURI);
2917 if (uri) {
2918 return GetPermissionHashKey(uri, aOriginAttributes, aType,
2919 aExactHostMatch);
2923 // No entry, really...
2924 return nullptr;
2927 nsresult PermissionManager::RemoveAllFromMemory() {
2928 mLargestID = 0;
2929 mTypeArray.clear();
2930 mPermissionTable.Clear();
2932 return NS_OK;
2935 // wrapper function for mangling (host,type,perm,expireType,expireTime)
2936 // set into an nsIPermission.
2937 void PermissionManager::NotifyObserversWithPermission(
2938 nsIPrincipal* aPrincipal, const nsACString& aType, uint32_t aPermission,
2939 uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime,
2940 const char16_t* aData) {
2941 nsCOMPtr<nsIPermission> permission =
2942 Permission::Create(aPrincipal, aType, aPermission, aExpireType,
2943 aExpireTime, aModificationTime);
2944 if (permission) NotifyObservers(permission, aData);
2947 // notify observers that the permission list changed. there are four possible
2948 // values for aData:
2949 // "deleted" means a permission was deleted. aPermission is the deleted
2950 // permission. "added" means a permission was added. aPermission is the added
2951 // permission. "changed" means a permission was altered. aPermission is the new
2952 // permission. "cleared" means the entire permission list was cleared.
2953 // aPermission is null.
2954 void PermissionManager::NotifyObservers(nsIPermission* aPermission,
2955 const char16_t* aData) {
2956 nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
2957 if (observerService)
2958 observerService->NotifyObservers(aPermission, kPermissionChangeNotification,
2959 aData);
2962 nsresult PermissionManager::Read(const MonitorAutoLock& aProofOfLock) {
2963 ENSURE_NOT_CHILD_PROCESS;
2965 MOZ_ASSERT(!NS_IsMainThread());
2966 auto data = mThreadBoundData.Access();
2968 nsresult rv;
2969 bool hasResult;
2970 nsCOMPtr<mozIStorageStatement> stmt;
2972 // Let's retrieve the last used ID.
2973 rv = data->mDBConn->CreateStatement(
2974 nsLiteralCString("SELECT MAX(id) FROM moz_perms"), getter_AddRefs(stmt));
2975 NS_ENSURE_SUCCESS(rv, rv);
2977 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
2978 int64_t id = stmt->AsInt64(0);
2979 mLargestID = id;
2982 rv = data->mDBConn->CreateStatement(
2983 nsLiteralCString(
2984 "SELECT id, origin, type, permission, expireType, "
2985 "expireTime, modificationTime "
2986 "FROM moz_perms WHERE expireType != ?1 OR expireTime > ?2"),
2987 getter_AddRefs(stmt));
2988 NS_ENSURE_SUCCESS(rv, rv);
2990 rv = stmt->BindInt32ByIndex(0, nsIPermissionManager::EXPIRE_TIME);
2991 NS_ENSURE_SUCCESS(rv, rv);
2993 rv = stmt->BindInt64ByIndex(1, EXPIRY_NOW);
2994 NS_ENSURE_SUCCESS(rv, rv);
2996 bool readError = false;
2998 while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
2999 ReadEntry entry;
3001 // explicitly set our entry id counter for use in AddInternal(),
3002 // and keep track of the largest id so we know where to pick up.
3003 entry.mId = stmt->AsInt64(0);
3004 MOZ_ASSERT(entry.mId <= mLargestID);
3006 rv = stmt->GetUTF8String(1, entry.mOrigin);
3007 if (NS_FAILED(rv)) {
3008 readError = true;
3009 continue;
3012 rv = stmt->GetUTF8String(2, entry.mType);
3013 if (NS_FAILED(rv)) {
3014 readError = true;
3015 continue;
3018 entry.mPermission = stmt->AsInt32(3);
3019 entry.mExpireType = stmt->AsInt32(4);
3021 // convert into int64_t values (milliseconds)
3022 entry.mExpireTime = stmt->AsInt64(5);
3023 entry.mModificationTime = stmt->AsInt64(6);
3025 entry.mFromMigration = false;
3027 mReadEntries.AppendElement(entry);
3030 if (readError) {
3031 NS_ERROR("Error occured while reading the permissions database!");
3032 return NS_ERROR_FAILURE;
3035 return NS_OK;
3038 void PermissionManager::CompleteMigrations() {
3039 MOZ_ASSERT(NS_IsMainThread());
3040 MOZ_ASSERT(mState == eReady);
3042 nsresult rv;
3044 nsTArray<MigrationEntry> entries;
3046 MonitorAutoLock lock(mMonitor);
3047 entries = std::move(mMigrationEntries);
3050 for (const MigrationEntry& entry : entries) {
3051 rv = UpgradeHostToOriginAndInsert(
3052 entry.mHost, entry.mType, entry.mPermission, entry.mExpireType,
3053 entry.mExpireTime, entry.mModificationTime,
3054 [&](const nsACString& aOrigin, const nsCString& aType,
3055 uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
3056 int64_t aModificationTime) {
3057 MaybeAddReadEntryFromMigration(aOrigin, aType, aPermission,
3058 aExpireType, aExpireTime,
3059 aModificationTime, entry.mId);
3060 return NS_OK;
3062 Unused << NS_WARN_IF(NS_FAILED(rv));
3066 void PermissionManager::CompleteRead() {
3067 MOZ_ASSERT(NS_IsMainThread());
3068 MOZ_ASSERT(mState == eReady);
3070 nsresult rv;
3072 nsTArray<ReadEntry> entries;
3074 MonitorAutoLock lock(mMonitor);
3075 entries = std::move(mReadEntries);
3078 for (const ReadEntry& entry : entries) {
3079 nsCOMPtr<nsIPrincipal> principal;
3080 rv = GetPrincipalFromOrigin(entry.mOrigin,
3081 IsOAForceStripPermission(entry.mType),
3082 getter_AddRefs(principal));
3083 if (NS_WARN_IF(NS_FAILED(rv))) {
3084 continue;
3087 DBOperationType op = entry.mFromMigration ? eWriteToDB : eNoDBOperation;
3089 rv = AddInternal(principal, entry.mType, entry.mPermission, entry.mId,
3090 entry.mExpireType, entry.mExpireTime,
3091 entry.mModificationTime, eDontNotify, op, false,
3092 &entry.mOrigin);
3093 Unused << NS_WARN_IF(NS_FAILED(rv));
3097 void PermissionManager::MaybeAddReadEntryFromMigration(
3098 const nsACString& aOrigin, const nsCString& aType, uint32_t aPermission,
3099 uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime,
3100 int64_t aId) {
3101 MonitorAutoLock lock(mMonitor);
3103 // We convert a migration to a ReadEntry only if we don't have an existing
3104 // ReadEntry for the same origin + type.
3105 for (const ReadEntry& entry : mReadEntries) {
3106 if (entry.mOrigin == aOrigin && entry.mType == aType) {
3107 return;
3111 ReadEntry entry;
3112 entry.mId = aId;
3113 entry.mOrigin = aOrigin;
3114 entry.mType = aType;
3115 entry.mPermission = aPermission;
3116 entry.mExpireType = aExpireType;
3117 entry.mExpireTime = aExpireTime;
3118 entry.mModificationTime = aModificationTime;
3119 entry.mFromMigration = true;
3121 mReadEntries.AppendElement(entry);
3124 void PermissionManager::UpdateDB(OperationType aOp, int64_t aID,
3125 const nsACString& aOrigin,
3126 const nsACString& aType, uint32_t aPermission,
3127 uint32_t aExpireType, int64_t aExpireTime,
3128 int64_t aModificationTime) {
3129 ENSURE_NOT_CHILD_PROCESS_NORET;
3131 MOZ_ASSERT(NS_IsMainThread());
3132 EnsureReadCompleted();
3134 nsCString origin(aOrigin);
3135 nsCString type(aType);
3137 RefPtr<PermissionManager> self = this;
3138 mThread->Dispatch(NS_NewRunnableFunction(
3139 "PermissionManager::UpdateDB",
3140 [self, aOp, aID, origin, type, aPermission, aExpireType, aExpireTime,
3141 aModificationTime] {
3142 nsresult rv;
3144 auto data = self->mThreadBoundData.Access();
3146 if (self->mState == eClosed || !data->mDBConn) {
3147 // no statement is ok - just means we don't have a profile
3148 return;
3151 mozIStorageStatement* stmt = nullptr;
3152 switch (aOp) {
3153 case eOperationAdding: {
3154 stmt = data->mStmtInsert;
3156 rv = stmt->BindInt64ByIndex(0, aID);
3157 if (NS_FAILED(rv)) break;
3159 rv = stmt->BindUTF8StringByIndex(1, origin);
3160 if (NS_FAILED(rv)) break;
3162 rv = stmt->BindUTF8StringByIndex(2, type);
3163 if (NS_FAILED(rv)) break;
3165 rv = stmt->BindInt32ByIndex(3, aPermission);
3166 if (NS_FAILED(rv)) break;
3168 rv = stmt->BindInt32ByIndex(4, aExpireType);
3169 if (NS_FAILED(rv)) break;
3171 rv = stmt->BindInt64ByIndex(5, aExpireTime);
3172 if (NS_FAILED(rv)) break;
3174 rv = stmt->BindInt64ByIndex(6, aModificationTime);
3175 break;
3178 case eOperationRemoving: {
3179 stmt = data->mStmtDelete;
3180 rv = stmt->BindInt64ByIndex(0, aID);
3181 break;
3184 case eOperationChanging: {
3185 stmt = data->mStmtUpdate;
3187 rv = stmt->BindInt64ByIndex(0, aID);
3188 if (NS_FAILED(rv)) break;
3190 rv = stmt->BindInt32ByIndex(1, aPermission);
3191 if (NS_FAILED(rv)) break;
3193 rv = stmt->BindInt32ByIndex(2, aExpireType);
3194 if (NS_FAILED(rv)) break;
3196 rv = stmt->BindInt64ByIndex(3, aExpireTime);
3197 if (NS_FAILED(rv)) break;
3199 rv = stmt->BindInt64ByIndex(4, aModificationTime);
3200 break;
3203 default: {
3204 MOZ_ASSERT_UNREACHABLE("need a valid operation in UpdateDB()!");
3205 rv = NS_ERROR_UNEXPECTED;
3206 break;
3210 if (NS_FAILED(rv)) {
3211 NS_WARNING("db change failed!");
3212 return;
3215 rv = stmt->Execute();
3216 MOZ_ASSERT(NS_SUCCEEDED(rv));
3217 }));
3220 bool PermissionManager::GetPermissionsFromOriginOrKey(
3221 const nsACString& aOrigin, const nsACString& aKey,
3222 nsTArray<IPC::Permission>& aPerms) {
3223 EnsureReadCompleted();
3225 aPerms.Clear();
3226 if (NS_WARN_IF(XRE_IsContentProcess())) {
3227 return false;
3230 for (const PermissionHashKey& entry : mPermissionTable) {
3231 nsAutoCString permissionKey;
3232 if (aOrigin.IsEmpty()) {
3233 // We can't check for individual OA strip perms here.
3234 // Don't force strip origin attributes.
3235 GetKeyForOrigin(entry.GetKey()->mOrigin, false, false, permissionKey);
3237 // If the keys don't match, and we aren't getting the default "" key, then
3238 // we can exit early. We have to keep looking if we're getting the default
3239 // key, as we may see a preload permission which should be transmitted.
3240 if (aKey != permissionKey && !aKey.IsEmpty()) {
3241 continue;
3243 } else if (aOrigin != entry.GetKey()->mOrigin) {
3244 // If the origins don't match, then we can exit early. We have to keep
3245 // looking if we're getting the default origin, as we may see a preload
3246 // permission which should be transmitted.
3247 continue;
3250 for (const auto& permEntry : entry.GetPermissions()) {
3251 // Given how "default" permissions work and the possibility of them
3252 // being overridden with UNKNOWN_ACTION, we might see this value here -
3253 // but we do not want to send it to the content process.
3254 if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
3255 continue;
3258 bool isPreload = IsPreloadPermission(mTypeArray[permEntry.mType]);
3259 bool shouldAppend;
3260 if (aOrigin.IsEmpty()) {
3261 shouldAppend = (isPreload && aKey.IsEmpty()) ||
3262 (!isPreload && aKey == permissionKey);
3263 } else {
3264 shouldAppend = (!isPreload && aOrigin == entry.GetKey()->mOrigin);
3266 if (shouldAppend) {
3267 aPerms.AppendElement(
3268 IPC::Permission(entry.GetKey()->mOrigin,
3269 mTypeArray[permEntry.mType], permEntry.mPermission,
3270 permEntry.mExpireType, permEntry.mExpireTime));
3275 return true;
3278 void PermissionManager::SetPermissionsWithKey(
3279 const nsACString& aPermissionKey, nsTArray<IPC::Permission>& aPerms) {
3280 if (NS_WARN_IF(XRE_IsParentProcess())) {
3281 return;
3284 RefPtr<GenericNonExclusivePromise::Private> promise;
3285 bool foundKey =
3286 mPermissionKeyPromiseMap.Get(aPermissionKey, getter_AddRefs(promise));
3287 if (promise) {
3288 MOZ_ASSERT(foundKey);
3289 // NOTE: This will resolve asynchronously, so we can mark it as resolved
3290 // now, and be confident that we will have filled in the database before any
3291 // callbacks run.
3292 promise->Resolve(true, __func__);
3293 } else if (foundKey) {
3294 // NOTE: We shouldn't be sent two InitializePermissionsWithKey for the same
3295 // key, but it's possible.
3296 return;
3298 mPermissionKeyPromiseMap.InsertOrUpdate(
3299 aPermissionKey, RefPtr<GenericNonExclusivePromise::Private>{});
3301 // Add the permissions locally to our process
3302 for (IPC::Permission& perm : aPerms) {
3303 nsCOMPtr<nsIPrincipal> principal;
3304 nsresult rv =
3305 GetPrincipalFromOrigin(perm.origin, IsOAForceStripPermission(perm.type),
3306 getter_AddRefs(principal));
3307 if (NS_WARN_IF(NS_FAILED(rv))) {
3308 continue;
3311 #ifdef DEBUG
3312 nsAutoCString permissionKey;
3313 GetKeyForPermission(principal, perm.type, permissionKey);
3314 MOZ_ASSERT(permissionKey == aPermissionKey,
3315 "The permission keys which were sent over should match!");
3316 #endif
3318 // The child process doesn't care about modification times - it neither
3319 // reads nor writes, nor removes them based on the date - so 0 (which
3320 // will end up as now()) is fine.
3321 uint64_t modificationTime = 0;
3322 AddInternal(principal, perm.type, perm.capability, 0, perm.expireType,
3323 perm.expireTime, modificationTime, eNotify, eNoDBOperation,
3324 true /* ignoreSessionPermissions */);
3328 /* static */
3329 nsresult PermissionManager::GetKeyForOrigin(const nsACString& aOrigin,
3330 bool aForceStripOA,
3331 bool aSiteScopePermissions,
3332 nsACString& aKey) {
3333 aKey.Truncate();
3335 // We only key origins for http, https URIs. All origins begin with
3336 // the URL which they apply to, which means that they should begin with their
3337 // scheme in the case where they are one of these interesting URIs. We don't
3338 // want to actually parse the URL here however, because this can be called on
3339 // hot paths.
3340 if (!StringBeginsWith(aOrigin, "http:"_ns) &&
3341 !StringBeginsWith(aOrigin, "https:"_ns)) {
3342 return NS_OK;
3345 // We need to look at the originAttributes if they are present, to make sure
3346 // to remove any which we don't want. We put the rest of the origin, not
3347 // including the attributes, into the key.
3348 OriginAttributes attrs;
3349 if (!attrs.PopulateFromOrigin(aOrigin, aKey)) {
3350 aKey.Truncate();
3351 return NS_OK;
3354 MaybeStripOriginAttributes(aForceStripOA, attrs);
3356 #ifdef DEBUG
3357 // Parse the origin string into a principal, and extract some useful
3358 // information from it for assertions.
3359 nsCOMPtr<nsIPrincipal> dbgPrincipal;
3360 MOZ_ALWAYS_SUCCEEDS(GetPrincipalFromOrigin(aOrigin, aForceStripOA,
3361 getter_AddRefs(dbgPrincipal)));
3362 MOZ_ASSERT(dbgPrincipal->SchemeIs("http") || dbgPrincipal->SchemeIs("https"));
3363 MOZ_ASSERT(dbgPrincipal->OriginAttributesRef() == attrs);
3364 #endif
3366 // If it is needed, turn the origin into its site-origin
3367 if (aSiteScopePermissions) {
3368 nsCOMPtr<nsIURI> uri;
3369 nsresult rv = NS_NewURI(getter_AddRefs(uri), aKey);
3370 if (!NS_WARN_IF(NS_FAILED(rv))) {
3371 nsCString site;
3372 rv = nsEffectiveTLDService::GetInstance()->GetSite(uri, site);
3373 if (!NS_WARN_IF(NS_FAILED(rv))) {
3374 aKey = site;
3379 // Append the stripped suffix to the output origin key.
3380 nsAutoCString suffix;
3381 attrs.CreateSuffix(suffix);
3382 aKey.Append(suffix);
3384 return NS_OK;
3387 /* static */
3388 nsresult PermissionManager::GetKeyForPrincipal(nsIPrincipal* aPrincipal,
3389 bool aForceStripOA,
3390 bool aSiteScopePermissions,
3391 nsACString& aKey) {
3392 nsAutoCString origin;
3393 nsresult rv = aPrincipal->GetOrigin(origin);
3394 if (NS_WARN_IF(NS_FAILED(rv))) {
3395 aKey.Truncate();
3396 return rv;
3398 return GetKeyForOrigin(origin, aForceStripOA, aSiteScopePermissions, aKey);
3401 /* static */
3402 nsresult PermissionManager::GetKeyForPermission(nsIPrincipal* aPrincipal,
3403 const nsACString& aType,
3404 nsACString& aKey) {
3405 // Preload permissions have the "" key.
3406 if (IsPreloadPermission(aType)) {
3407 aKey.Truncate();
3408 return NS_OK;
3411 return GetKeyForPrincipal(aPrincipal, IsOAForceStripPermission(aType),
3412 IsSiteScopedPermission(aType), aKey);
3415 /* static */
3416 nsTArray<std::pair<nsCString, nsCString>>
3417 PermissionManager::GetAllKeysForPrincipal(nsIPrincipal* aPrincipal) {
3418 MOZ_ASSERT(aPrincipal);
3420 nsTArray<std::pair<nsCString, nsCString>> pairs;
3421 nsCOMPtr<nsIPrincipal> prin = aPrincipal;
3423 while (prin) {
3424 // Add the pair to the list
3425 std::pair<nsCString, nsCString>* pair =
3426 pairs.AppendElement(std::make_pair(""_ns, ""_ns));
3427 // We can't check for individual OA strip perms here.
3428 // Don't force strip origin attributes.
3429 GetKeyForPrincipal(prin, false, false, pair->first);
3431 // On origins with a derived key set to an empty string
3432 // (basically any non-web URI scheme), we want to make sure
3433 // to return earlier, and leave [("", "")] as the resulting
3434 // pairs (but still run the same debug assertions near the
3435 // end of this method).
3436 if (pair->first.IsEmpty()) {
3437 break;
3440 Unused << GetOriginFromPrincipal(prin, false, pair->second);
3441 prin = prin->GetNextSubDomainPrincipal();
3442 // Get the next subdomain principal and loop back around.
3445 MOZ_ASSERT(pairs.Length() >= 1,
3446 "Every principal should have at least one pair item.");
3447 return pairs;
3450 bool PermissionManager::PermissionAvailable(nsIPrincipal* aPrincipal,
3451 const nsACString& aType) {
3452 EnsureReadCompleted();
3454 if (XRE_IsContentProcess()) {
3455 nsAutoCString permissionKey;
3456 // NOTE: GetKeyForPermission accepts a null aType.
3457 GetKeyForPermission(aPrincipal, aType, permissionKey);
3459 // If we have a pending promise for the permission key in question, we don't
3460 // have the permission available, so report a warning and return false.
3461 RefPtr<GenericNonExclusivePromise::Private> promise;
3462 if (!mPermissionKeyPromiseMap.Get(permissionKey, getter_AddRefs(promise)) ||
3463 promise) {
3464 // Emit a useful diagnostic warning with the permissionKey for the process
3465 // which hasn't received permissions yet.
3466 NS_WARNING(nsPrintfCString("This content process hasn't received the "
3467 "permissions for %s yet",
3468 permissionKey.get())
3469 .get());
3470 return false;
3473 return true;
3476 void PermissionManager::WhenPermissionsAvailable(nsIPrincipal* aPrincipal,
3477 nsIRunnable* aRunnable) {
3478 MOZ_ASSERT(aRunnable);
3480 if (!XRE_IsContentProcess()) {
3481 aRunnable->Run();
3482 return;
3485 nsTArray<RefPtr<GenericNonExclusivePromise>> promises;
3486 for (auto& pair : GetAllKeysForPrincipal(aPrincipal)) {
3487 RefPtr<GenericNonExclusivePromise::Private> promise;
3488 if (!mPermissionKeyPromiseMap.Get(pair.first, getter_AddRefs(promise))) {
3489 // In this case we have found a permission which isn't available in the
3490 // content process and hasn't been requested yet. We need to create a new
3491 // promise, and send the request to the parent (if we have not already
3492 // done so).
3493 promise = new GenericNonExclusivePromise::Private(__func__);
3494 mPermissionKeyPromiseMap.InsertOrUpdate(pair.first, RefPtr{promise});
3497 if (promise) {
3498 promises.AppendElement(std::move(promise));
3502 // If all of our permissions are available, immediately run the runnable. This
3503 // avoids any extra overhead during fetch interception which is performance
3504 // sensitive.
3505 if (promises.IsEmpty()) {
3506 aRunnable->Run();
3507 return;
3510 auto* thread = AbstractThread::MainThread();
3512 RefPtr<nsIRunnable> runnable = aRunnable;
3513 GenericNonExclusivePromise::All(thread, promises)
3514 ->Then(
3515 thread, __func__, [runnable]() { runnable->Run(); },
3516 []() {
3517 NS_WARNING(
3518 "PermissionManager permission promise rejected. We're "
3519 "probably shutting down.");
3523 void PermissionManager::EnsureReadCompleted() {
3524 MOZ_ASSERT(NS_IsMainThread());
3526 if (mState == eInitializing) {
3527 MonitorAutoLock lock(mMonitor);
3529 while (mState == eInitializing) {
3530 mMonitor.Wait();
3534 switch (mState) {
3535 case eInitializing:
3536 MOZ_CRASH("This state is impossible!");
3538 case eDBInitialized:
3539 mState = eReady;
3541 CompleteMigrations();
3542 ImportLatestDefaults();
3543 CompleteRead();
3545 [[fallthrough]];
3547 case eReady:
3548 [[fallthrough]];
3550 case eClosed:
3551 return;
3553 default:
3554 MOZ_CRASH("Invalid state");
3558 already_AddRefed<nsIInputStream> PermissionManager::GetDefaultsInputStream() {
3559 MOZ_ASSERT(NS_IsMainThread());
3561 nsAutoCString defaultsURL;
3562 Preferences::GetCString(kDefaultsUrlPrefName, defaultsURL);
3563 if (defaultsURL.IsEmpty()) { // == Don't use built-in permissions.
3564 return nullptr;
3567 nsCOMPtr<nsIURI> defaultsURI;
3568 nsresult rv = NS_NewURI(getter_AddRefs(defaultsURI), defaultsURL);
3569 NS_ENSURE_SUCCESS(rv, nullptr);
3571 nsCOMPtr<nsIChannel> channel;
3572 rv = NS_NewChannel(getter_AddRefs(channel), defaultsURI,
3573 nsContentUtils::GetSystemPrincipal(),
3574 nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
3575 nsIContentPolicy::TYPE_OTHER);
3576 NS_ENSURE_SUCCESS(rv, nullptr);
3578 nsCOMPtr<nsIInputStream> inputStream;
3579 rv = channel->Open(getter_AddRefs(inputStream));
3580 NS_ENSURE_SUCCESS(rv, nullptr);
3582 return inputStream.forget();
3585 void PermissionManager::ConsumeDefaultsInputStream(
3586 nsIInputStream* aInputStream, const MonitorAutoLock& aProofOfLock) {
3587 MOZ_ASSERT(!NS_IsMainThread());
3589 constexpr char kMatchTypeHost[] = "host";
3590 constexpr char kMatchTypeOrigin[] = "origin";
3592 mDefaultEntries.Clear();
3594 if (!aInputStream) {
3595 return;
3598 nsresult rv;
3600 /* format is:
3601 * matchtype \t type \t permission \t host
3602 * Only "host" is supported for matchtype
3603 * type is a string that identifies the type of permission (e.g. "cookie")
3604 * permission is an integer between 1 and 15
3607 // Ideally we'd do this with nsILineInputString, but this is called with an
3608 // nsIInputStream that comes from a resource:// URI, which doesn't support
3609 // that interface. So NS_ReadLine to the rescue...
3610 nsLineBuffer<char> lineBuffer;
3611 nsCString line;
3612 bool isMore = true;
3613 do {
3614 rv = NS_ReadLine(aInputStream, &lineBuffer, line, &isMore);
3615 NS_ENSURE_SUCCESS_VOID(rv);
3617 if (line.IsEmpty() || line.First() == '#') {
3618 continue;
3621 nsTArray<nsCString> lineArray;
3623 // Split the line at tabs
3624 ParseString(line, '\t', lineArray);
3626 if (lineArray.Length() != 4) {
3627 continue;
3630 nsresult error = NS_OK;
3631 uint32_t permission = lineArray[2].ToInteger(&error);
3632 if (NS_FAILED(error)) {
3633 continue;
3636 DefaultEntry::Op op;
3638 if (lineArray[0].EqualsLiteral(kMatchTypeHost)) {
3639 op = DefaultEntry::eImportMatchTypeHost;
3640 } else if (lineArray[0].EqualsLiteral(kMatchTypeOrigin)) {
3641 op = DefaultEntry::eImportMatchTypeOrigin;
3642 } else {
3643 continue;
3646 DefaultEntry* entry = mDefaultEntries.AppendElement();
3647 MOZ_ASSERT(entry);
3649 entry->mOp = op;
3650 entry->mPermission = permission;
3651 entry->mHostOrOrigin = lineArray[3];
3652 entry->mType = lineArray[1];
3653 } while (isMore);
3656 // ImportLatestDefaults will import the latest default cookies read during the
3657 // last DB initialization.
3658 nsresult PermissionManager::ImportLatestDefaults() {
3659 MOZ_ASSERT(NS_IsMainThread());
3660 MOZ_ASSERT(mState == eReady);
3662 nsresult rv;
3664 MonitorAutoLock lock(mMonitor);
3666 for (const DefaultEntry& entry : mDefaultEntries) {
3667 if (entry.mOp == DefaultEntry::eImportMatchTypeHost) {
3668 // the import file format doesn't handle modification times, so we use
3669 // 0, which AddInternal will convert to now()
3670 int64_t modificationTime = 0;
3672 rv = UpgradeHostToOriginAndInsert(
3673 entry.mHostOrOrigin, entry.mType, entry.mPermission,
3674 nsIPermissionManager::EXPIRE_NEVER, 0, modificationTime,
3675 [&](const nsACString& aOrigin, const nsCString& aType,
3676 uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
3677 int64_t aModificationTime) {
3678 nsCOMPtr<nsIPrincipal> principal;
3679 nsresult rv =
3680 GetPrincipalFromOrigin(aOrigin, IsOAForceStripPermission(aType),
3681 getter_AddRefs(principal));
3682 NS_ENSURE_SUCCESS(rv, rv);
3683 rv =
3684 AddInternal(principal, aType, aPermission,
3685 cIDPermissionIsDefault, aExpireType, aExpireTime,
3686 aModificationTime, PermissionManager::eDontNotify,
3687 PermissionManager::eNoDBOperation, false, &aOrigin);
3688 NS_ENSURE_SUCCESS(rv, rv);
3690 if (StaticPrefs::permissions_isolateBy_privateBrowsing()) {
3691 // Also import the permission for private browsing.
3692 OriginAttributes attrs =
3693 OriginAttributes(principal->OriginAttributesRef());
3694 attrs.mPrivateBrowsingId = 1;
3695 nsCOMPtr<nsIPrincipal> pbPrincipal =
3696 BasePrincipal::Cast(principal)->CloneForcingOriginAttributes(
3697 attrs);
3699 rv = AddInternal(
3700 pbPrincipal, aType, aPermission, cIDPermissionIsDefault,
3701 aExpireType, aExpireTime, aModificationTime,
3702 PermissionManager::eDontNotify,
3703 PermissionManager::eNoDBOperation, false, &aOrigin);
3704 NS_ENSURE_SUCCESS(rv, rv);
3707 return NS_OK;
3710 if (NS_FAILED(rv)) {
3711 NS_WARNING("There was a problem importing a host permission");
3713 continue;
3716 MOZ_ASSERT(entry.mOp == DefaultEntry::eImportMatchTypeOrigin);
3718 nsCOMPtr<nsIPrincipal> principal;
3719 rv = GetPrincipalFromOrigin(entry.mHostOrOrigin,
3720 IsOAForceStripPermission(entry.mType),
3721 getter_AddRefs(principal));
3722 if (NS_FAILED(rv)) {
3723 NS_WARNING("Couldn't import an origin permission - malformed origin");
3724 continue;
3727 // the import file format doesn't handle modification times, so we use
3728 // 0, which AddInternal will convert to now()
3729 int64_t modificationTime = 0;
3731 rv = AddInternal(principal, entry.mType, entry.mPermission,
3732 cIDPermissionIsDefault, nsIPermissionManager::EXPIRE_NEVER,
3733 0, modificationTime, eDontNotify, eNoDBOperation);
3734 if (NS_FAILED(rv)) {
3735 NS_WARNING("There was a problem importing an origin permission");
3738 if (StaticPrefs::permissions_isolateBy_privateBrowsing()) {
3739 // Also import the permission for private browsing.
3740 OriginAttributes attrs =
3741 OriginAttributes(principal->OriginAttributesRef());
3742 attrs.mPrivateBrowsingId = 1;
3743 nsCOMPtr<nsIPrincipal> pbPrincipal =
3744 BasePrincipal::Cast(principal)->CloneForcingOriginAttributes(attrs);
3745 // May return nullptr if clone fails.
3746 NS_ENSURE_TRUE(pbPrincipal, NS_ERROR_FAILURE);
3748 rv = AddInternal(pbPrincipal, entry.mType, entry.mPermission,
3749 cIDPermissionIsDefault,
3750 nsIPermissionManager::EXPIRE_NEVER, 0, modificationTime,
3751 eDontNotify, eNoDBOperation);
3752 if (NS_FAILED(rv)) {
3753 NS_WARNING(
3754 "There was a problem importing an origin permission for private "
3755 "browsing");
3760 return NS_OK;
3764 * Perform the early steps of a permission check and determine whether we need
3765 * to call CommonTestPermissionInternal() for the actual permission check.
3767 * @param aPrincipal optional principal argument to check the permission for,
3768 * can be nullptr if we aren't performing a principal-based
3769 * check.
3770 * @param aTypeIndex if the caller isn't sure what the index of the permission
3771 * type to check for is in the mTypeArray member variable,
3772 * it should pass -1, otherwise this would be the index of
3773 * the type inside mTypeArray. This would only be something
3774 * other than -1 in recursive invocations of this function.
3775 * @param aType the permission type to test.
3776 * @param aPermission out argument which will be a permission type that we
3777 * will return from this function once the function is
3778 * done.
3779 * @param aDefaultPermission the default permission to be used if we can't
3780 * determine the result of the permission check.
3781 * @param aDefaultPermissionIsValid whether the previous argument contains a
3782 * valid value.
3783 * @param aExactHostMatch whether to look for the exact host name or also for
3784 * subdomains that can have the same permission.
3785 * @param aIncludingSession whether to include session permissions when
3786 * testing for the permission.
3788 PermissionManager::TestPreparationResult
3789 PermissionManager::CommonPrepareToTestPermission(
3790 nsIPrincipal* aPrincipal, int32_t aTypeIndex, const nsACString& aType,
3791 uint32_t* aPermission, uint32_t aDefaultPermission,
3792 bool aDefaultPermissionIsValid, bool aExactHostMatch,
3793 bool aIncludingSession) {
3794 auto* basePrin = BasePrincipal::Cast(aPrincipal);
3795 if (basePrin && basePrin->IsSystemPrincipal()) {
3796 *aPermission = ALLOW_ACTION;
3797 return AsVariant(NS_OK);
3800 EnsureReadCompleted();
3802 // For some permissions, query the default from a pref. We want to avoid
3803 // doing this for all permissions so that permissions can opt into having
3804 // the pref lookup overhead on each call.
3805 int32_t defaultPermission =
3806 aDefaultPermissionIsValid ? aDefaultPermission : UNKNOWN_ACTION;
3807 if (!aDefaultPermissionIsValid && HasDefaultPref(aType)) {
3808 Unused << mDefaultPrefBranch->GetIntPref(PromiseFlatCString(aType).get(),
3809 &defaultPermission);
3812 // Set the default.
3813 *aPermission = defaultPermission;
3815 int32_t typeIndex =
3816 aTypeIndex == -1 ? GetTypeIndex(aType, false) : aTypeIndex;
3818 // For expanded principals, we want to iterate over the allowlist and see
3819 // if the permission is granted for any of them.
3820 if (basePrin && basePrin->Is<ExpandedPrincipal>()) {
3821 auto ep = basePrin->As<ExpandedPrincipal>();
3822 for (auto& prin : ep->AllowList()) {
3823 uint32_t perm;
3824 nsresult rv =
3825 CommonTestPermission(prin, typeIndex, aType, &perm, defaultPermission,
3826 true, aExactHostMatch, aIncludingSession);
3827 if (NS_WARN_IF(NS_FAILED(rv))) {
3828 return AsVariant(rv);
3831 if (perm == nsIPermissionManager::ALLOW_ACTION) {
3832 *aPermission = perm;
3833 return AsVariant(NS_OK);
3835 if (perm == nsIPermissionManager::PROMPT_ACTION) {
3836 // Store it, but keep going to see if we can do better.
3837 *aPermission = perm;
3841 return AsVariant(NS_OK);
3844 // If type == -1, the type isn't known, just signal that we are done.
3845 if (typeIndex == -1) {
3846 return AsVariant(NS_OK);
3849 return AsVariant(typeIndex);
3852 // If aTypeIndex is passed -1, we try to inder the type index from aType.
3853 nsresult PermissionManager::CommonTestPermission(
3854 nsIPrincipal* aPrincipal, int32_t aTypeIndex, const nsACString& aType,
3855 uint32_t* aPermission, uint32_t aDefaultPermission,
3856 bool aDefaultPermissionIsValid, bool aExactHostMatch,
3857 bool aIncludingSession) {
3858 auto preparationResult = CommonPrepareToTestPermission(
3859 aPrincipal, aTypeIndex, aType, aPermission, aDefaultPermission,
3860 aDefaultPermissionIsValid, aExactHostMatch, aIncludingSession);
3861 if (preparationResult.is<nsresult>()) {
3862 return preparationResult.as<nsresult>();
3865 return CommonTestPermissionInternal(
3866 aPrincipal, nullptr, nullptr, preparationResult.as<int32_t>(), aType,
3867 aPermission, aExactHostMatch, aIncludingSession);
3870 // If aTypeIndex is passed -1, we try to inder the type index from aType.
3871 nsresult PermissionManager::CommonTestPermission(
3872 nsIURI* aURI, int32_t aTypeIndex, const nsACString& aType,
3873 uint32_t* aPermission, uint32_t aDefaultPermission,
3874 bool aDefaultPermissionIsValid, bool aExactHostMatch,
3875 bool aIncludingSession) {
3876 auto preparationResult = CommonPrepareToTestPermission(
3877 nullptr, aTypeIndex, aType, aPermission, aDefaultPermission,
3878 aDefaultPermissionIsValid, aExactHostMatch, aIncludingSession);
3879 if (preparationResult.is<nsresult>()) {
3880 return preparationResult.as<nsresult>();
3883 return CommonTestPermissionInternal(
3884 nullptr, aURI, nullptr, preparationResult.as<int32_t>(), aType,
3885 aPermission, aExactHostMatch, aIncludingSession);
3888 nsresult PermissionManager::CommonTestPermission(
3889 nsIURI* aURI, const OriginAttributes* aOriginAttributes, int32_t aTypeIndex,
3890 const nsACString& aType, uint32_t* aPermission, uint32_t aDefaultPermission,
3891 bool aDefaultPermissionIsValid, bool aExactHostMatch,
3892 bool aIncludingSession) {
3893 auto preparationResult = CommonPrepareToTestPermission(
3894 nullptr, aTypeIndex, aType, aPermission, aDefaultPermission,
3895 aDefaultPermissionIsValid, aExactHostMatch, aIncludingSession);
3896 if (preparationResult.is<nsresult>()) {
3897 return preparationResult.as<nsresult>();
3900 return CommonTestPermissionInternal(
3901 nullptr, aURI, aOriginAttributes, preparationResult.as<int32_t>(), aType,
3902 aPermission, aExactHostMatch, aIncludingSession);
3905 nsresult PermissionManager::TestPermissionWithoutDefaultsFromPrincipal(
3906 nsIPrincipal* aPrincipal, const nsACString& aType, uint32_t* aPermission) {
3907 MOZ_ASSERT(!HasDefaultPref(aType));
3909 return CommonTestPermission(aPrincipal, -1, aType, aPermission,
3910 nsIPermissionManager::UNKNOWN_ACTION, true, false,
3911 true);
3914 void PermissionManager::MaybeCompleteShutdown() {
3915 MOZ_ASSERT(NS_IsMainThread());
3917 nsCOMPtr<nsIAsyncShutdownClient> asc = GetAsyncShutdownBarrier();
3918 MOZ_ASSERT(asc);
3920 DebugOnly<nsresult> rv = asc->RemoveBlocker(this);
3921 MOZ_ASSERT(NS_SUCCEEDED(rv));
3924 // Async shutdown blocker methods
3926 NS_IMETHODIMP PermissionManager::GetName(nsAString& aName) {
3927 aName = u"PermissionManager: Flushing data"_ns;
3928 return NS_OK;
3931 NS_IMETHODIMP PermissionManager::BlockShutdown(
3932 nsIAsyncShutdownClient* aClient) {
3933 RemoveIdleDailyMaintenanceJob();
3934 RemoveAllFromMemory();
3935 CloseDB(eShutdown);
3937 gPermissionManager = nullptr;
3938 return NS_OK;
3941 NS_IMETHODIMP
3942 PermissionManager::GetState(nsIPropertyBag** aBagOut) {
3943 nsCOMPtr<nsIWritablePropertyBag2> propertyBag =
3944 do_CreateInstance("@mozilla.org/hash-property-bag;1");
3946 nsresult rv = propertyBag->SetPropertyAsInt32(u"state"_ns, mState);
3947 if (NS_WARN_IF(NS_FAILED(rv))) {
3948 return rv;
3951 propertyBag.forget(aBagOut);
3953 return NS_OK;
3956 nsCOMPtr<nsIAsyncShutdownClient> PermissionManager::GetAsyncShutdownBarrier()
3957 const {
3958 nsresult rv;
3959 nsCOMPtr<nsIAsyncShutdownService> svc =
3960 do_GetService("@mozilla.org/async-shutdown-service;1", &rv);
3961 if (NS_FAILED(rv)) {
3962 return nullptr;
3965 nsCOMPtr<nsIAsyncShutdownClient> client;
3966 // This feels very late but there seem to be other services that rely on
3967 // us later than "profile-before-change".
3968 rv = svc->GetXpcomWillShutdown(getter_AddRefs(client));
3969 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
3971 return client;
3974 void PermissionManager::MaybeStripOriginAttributes(
3975 bool aForceStrip, OriginAttributes& aOriginAttributes) {
3976 uint32_t flags = 0;
3978 if (aForceStrip || !StaticPrefs::permissions_isolateBy_privateBrowsing()) {
3979 flags |= OriginAttributes::STRIP_PRIVATE_BROWSING_ID;
3982 if (aForceStrip || !StaticPrefs::permissions_isolateBy_userContext()) {
3983 flags |= OriginAttributes::STRIP_USER_CONTEXT_ID;
3986 if (flags != 0) {
3987 aOriginAttributes.StripAttributes(flags);
3991 } // namespace mozilla