summaryrefslogtreecommitdiffstats
path: root/extensions/cookie/nsPermissionManager.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'extensions/cookie/nsPermissionManager.cpp')
-rw-r--r--extensions/cookie/nsPermissionManager.cpp2918
1 files changed, 2918 insertions, 0 deletions
diff --git a/extensions/cookie/nsPermissionManager.cpp b/extensions/cookie/nsPermissionManager.cpp
new file mode 100644
index 000000000..d3696dd94
--- /dev/null
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -0,0 +1,2918 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Attributes.h"
+#include "mozilla/DebugOnly.h"
+
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/Services.h"
+#include "mozilla/Unused.h"
+#include "nsPermissionManager.h"
+#include "nsPermission.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
+#include "nsTArray.h"
+#include "nsReadableUtils.h"
+#include "nsILineInputStream.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceDefs.h"
+#include "prprf.h"
+#include "mozilla/storage.h"
+#include "mozilla/Attributes.h"
+#include "nsXULAppAPI.h"
+#include "nsIPrincipal.h"
+#include "nsContentUtils.h"
+#include "nsIScriptSecurityManager.h"
+#include "nsIAppsService.h"
+#include "mozIApplication.h"
+#include "nsIEffectiveTLDService.h"
+#include "nsPIDOMWindow.h"
+#include "nsIDocument.h"
+#include "mozilla/net/NeckoMessageUtils.h"
+#include "mozilla/Preferences.h"
+#include "nsReadLine.h"
+#include "mozilla/Telemetry.h"
+#include "nsIConsoleService.h"
+#include "nsINavHistoryService.h"
+#include "nsToolkitCompsCID.h"
+#include "nsIObserverService.h"
+
+static nsPermissionManager *gPermissionManager = nullptr;
+
+using mozilla::dom::ContentParent;
+using mozilla::dom::ContentChild;
+using mozilla::Unused; // ha!
+
+static bool
+IsChildProcess()
+{
+ return XRE_IsContentProcess();
+}
+
+/**
+ * @returns The child process object, or if we are not in the child
+ * process, nullptr.
+ */
+static ContentChild*
+ChildProcess()
+{
+ if (IsChildProcess()) {
+ ContentChild* cpc = ContentChild::GetSingleton();
+ if (!cpc)
+ NS_RUNTIMEABORT("Content Process is nullptr!");
+ return cpc;
+ }
+
+ return nullptr;
+}
+
+static void
+LogToConsole(const nsAString& aMsg)
+{
+ nsCOMPtr<nsIConsoleService> console(do_GetService("@mozilla.org/consoleservice;1"));
+ if (!console) {
+ NS_WARNING("Failed to log message to console.");
+ return;
+ }
+
+ nsAutoString msg(aMsg);
+ console->LogStringMessage(msg.get());
+}
+
+#define ENSURE_NOT_CHILD_PROCESS_(onError) \
+ PR_BEGIN_MACRO \
+ if (IsChildProcess()) { \
+ NS_ERROR("Cannot perform action in content process!"); \
+ onError \
+ } \
+ PR_END_MACRO
+
+#define ENSURE_NOT_CHILD_PROCESS \
+ ENSURE_NOT_CHILD_PROCESS_({ return NS_ERROR_NOT_AVAILABLE; })
+
+#define ENSURE_NOT_CHILD_PROCESS_NORET \
+ ENSURE_NOT_CHILD_PROCESS_(;)
+
+////////////////////////////////////////////////////////////////////////////////
+
+namespace {
+
+nsresult
+GetOriginFromPrincipal(nsIPrincipal* aPrincipal, nsACString& aOrigin)
+{
+ nsresult rv = aPrincipal->GetOriginNoSuffix(aOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString suffix;
+ rv = aPrincipal->GetOriginSuffix(suffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozilla::PrincipalOriginAttributes attrs;
+ if (!attrs.PopulateFromSuffix(suffix)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // mPrivateBrowsingId must be set to false because PermissionManager is not supposed to have
+ // any knowledge of private browsing. Allowing it to be true changes the suffix being hashed.
+ attrs.mPrivateBrowsingId = 0;
+
+ // Disable userContext and firstParty isolation for permissions.
+ attrs.StripUserContextIdAndFirstPartyDomain();
+
+ attrs.CreateSuffix(suffix);
+ aOrigin.Append(suffix);
+ return NS_OK;
+}
+
+nsresult
+GetPrincipalFromOrigin(const nsACString& aOrigin, nsIPrincipal** aPrincipal)
+{
+ nsAutoCString originNoSuffix;
+ mozilla::PrincipalOriginAttributes attrs;
+ if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Disable userContext and firstParty isolation for permissions.
+ attrs.StripUserContextIdAndFirstPartyDomain();
+
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::CreateCodebasePrincipal(uri, attrs);
+ principal.forget(aPrincipal);
+ return NS_OK;
+}
+
+nsresult
+GetPrincipal(nsIURI* aURI, uint32_t aAppId, bool aIsInIsolatedMozBrowserElement, nsIPrincipal** aPrincipal)
+{
+ mozilla::PrincipalOriginAttributes attrs(aAppId, aIsInIsolatedMozBrowserElement);
+ nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
+ NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
+
+ principal.forget(aPrincipal);
+ return NS_OK;
+}
+
+nsresult
+GetPrincipal(nsIURI* aURI, nsIPrincipal** aPrincipal)
+{
+ mozilla::PrincipalOriginAttributes attrs;
+ nsCOMPtr<nsIPrincipal> principal = mozilla::BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
+ NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
+
+ principal.forget(aPrincipal);
+ return NS_OK;
+}
+
+nsCString
+GetNextSubDomainForHost(const nsACString& aHost)
+{
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ if (!tldService) {
+ NS_ERROR("Should have a tld service!");
+ return EmptyCString();
+ }
+
+ nsCString subDomain;
+ nsresult rv = tldService->GetNextSubDomain(aHost, subDomain);
+ // We can fail if there is no more subdomain or if the host can't have a
+ // subdomain.
+ if (NS_FAILED(rv)) {
+ return EmptyCString();
+ }
+
+ return subDomain;
+}
+
+class ClearOriginDataObserver final : public nsIObserver {
+ ~ClearOriginDataObserver() {}
+
+public:
+ NS_DECL_ISUPPORTS
+
+ // nsIObserver implementation.
+ NS_IMETHOD
+ Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) override
+ {
+ MOZ_ASSERT(!nsCRT::strcmp(aTopic, "clear-origin-attributes-data"));
+
+ nsCOMPtr<nsIPermissionManager> permManager = do_GetService("@mozilla.org/permissionmanager;1");
+ return permManager->RemovePermissionsWithAttributes(nsDependentString(aData));
+ }
+};
+
+NS_IMPL_ISUPPORTS(ClearOriginDataObserver, nsIObserver)
+
+class MOZ_STACK_CLASS UpgradeHostToOriginHelper {
+public:
+ virtual nsresult Insert(const nsACString& aOrigin, const nsAFlatCString& aType,
+ uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime) = 0;
+};
+
+class MOZ_STACK_CLASS UpgradeHostToOriginDBMigration final : public UpgradeHostToOriginHelper {
+public:
+ UpgradeHostToOriginDBMigration(mozIStorageConnection* aDBConn, int64_t* aID) : mDBConn(aDBConn)
+ , mID(aID)
+ {
+ mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_hosts_new "
+ "(id, origin, type, permission, expireType, expireTime, modificationTime) "
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"), getter_AddRefs(mStmt));
+ }
+
+ nsresult
+ Insert(const nsACString& aOrigin, const nsAFlatCString& aType,
+ uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime) final
+ {
+ nsresult rv = mStmt->BindInt64ByIndex(0, *mID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindUTF8StringByIndex(1, aOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindUTF8StringByIndex(2, aType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt32ByIndex(3, aPermission);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt32ByIndex(4, aExpireType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt64ByIndex(5, aExpireTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt64ByIndex(6, aModificationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Increment the working identifier, as we are about to use this one
+ (*mID)++;
+
+ rv = mStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<mozIStorageStatement> mStmt;
+ nsCOMPtr<mozIStorageConnection> mDBConn;
+ int64_t* mID;
+};
+
+class MOZ_STACK_CLASS UpgradeHostToOriginHostfileImport final : public UpgradeHostToOriginHelper {
+public:
+ UpgradeHostToOriginHostfileImport(nsPermissionManager* aPm,
+ nsPermissionManager::DBOperationType aOperation,
+ int64_t aID) : mPm(aPm)
+ , mOperation(aOperation)
+ , mID(aID)
+ {}
+
+ nsresult
+ Insert(const nsACString& aOrigin, const nsAFlatCString& aType,
+ uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime) final
+ {
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(aOrigin, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return mPm->AddInternal(principal, aType, aPermission, mID,
+ aExpireType, aExpireTime, aModificationTime,
+ nsPermissionManager::eDontNotify, mOperation);
+ }
+
+private:
+ RefPtr<nsPermissionManager> mPm;
+ nsPermissionManager::DBOperationType mOperation;
+ int64_t mID;
+};
+
+class MOZ_STACK_CLASS UpgradeIPHostToOriginDB final : public UpgradeHostToOriginHelper {
+public:
+ UpgradeIPHostToOriginDB(mozIStorageConnection* aDBConn, int64_t* aID) : mDBConn(aDBConn)
+ , mID(aID)
+ {
+ mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_perms"
+ "(id, origin, type, permission, expireType, expireTime, modificationTime) "
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"), getter_AddRefs(mStmt));
+
+ mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id FROM moz_perms WHERE origin = ?1 AND type = ?2"),
+ getter_AddRefs(mLookupStmt));
+ }
+
+ nsresult
+ Insert(const nsACString& aOrigin, const nsAFlatCString& aType,
+ uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime) final
+ {
+ // Every time the migration code wants to insert an origin into
+ // the database we need to check to see if someone has already
+ // created a permissions entry for that permission. If they have,
+ // we don't want to insert a duplicate row.
+ //
+ // We can afford to do this lookup unconditionally and not perform
+ // caching, as a origin type pair should only be attempted to be
+ // inserted once.
+
+ nsresult rv = mLookupStmt->Reset();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mLookupStmt->BindUTF8StringByIndex(0, aOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mLookupStmt->BindUTF8StringByIndex(1, aType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Check if we already have the row in the database, if we do, then
+ // we don't want to be inserting it again.
+ bool moreStmts = false;
+ if (NS_FAILED(mLookupStmt->ExecuteStep(&moreStmts)) || moreStmts) {
+ mLookupStmt->Reset();
+ NS_WARNING("A permissions entry was going to be re-migrated, "
+ "but was already found in the permissions database.");
+ return NS_OK;
+ }
+
+ // Actually insert the statement into the database.
+ rv = mStmt->BindInt64ByIndex(0, *mID);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindUTF8StringByIndex(1, aOrigin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindUTF8StringByIndex(2, aType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt32ByIndex(3, aPermission);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt32ByIndex(4, aExpireType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt64ByIndex(5, aExpireTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mStmt->BindInt64ByIndex(6, aModificationTime);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Increment the working identifier, as we are about to use this one
+ (*mID)++;
+
+ rv = mStmt->Execute();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+ }
+
+private:
+ nsCOMPtr<mozIStorageStatement> mStmt;
+ nsCOMPtr<mozIStorageStatement> mLookupStmt;
+ nsCOMPtr<mozIStorageConnection> mDBConn;
+ int64_t* mID;
+};
+
+
+nsresult
+UpgradeHostToOriginAndInsert(const nsACString& aHost, const nsAFlatCString& aType,
+ uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+ int64_t aModificationTime, uint32_t aAppId, bool aIsInIsolatedMozBrowserElement,
+ UpgradeHostToOriginHelper* aHelper)
+{
+ if (aHost.EqualsLiteral("<file>")) {
+ // We no longer support the magic host <file>
+ NS_WARNING("The magic host <file> is no longer supported. "
+ "It is being removed from the permissions database.");
+ return NS_OK;
+ }
+
+ // First, we check to see if the host is a valid URI. If it is, it can be imported directly
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), aHost);
+ if (NS_SUCCEEDED(rv)) {
+ // It was previously possible to insert useless entries to your permissions database
+ // for URIs which have a null principal. This acts as a cleanup, getting rid of
+ // these useless database entries
+ bool nullpScheme = false;
+ if (NS_SUCCEEDED(uri->SchemeIs("moz-nullprincipal", &nullpScheme)) && nullpScheme) {
+ NS_WARNING("A moz-nullprincipal: permission is being discarded.");
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString origin;
+ rv = GetOriginFromPrincipal(principal, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aHelper->Insert(origin, aType, aPermission,
+ aExpireType, aExpireTime, aModificationTime);
+ return NS_OK;
+ }
+
+ // The user may use this host at non-standard ports or protocols, we can use their history
+ // to guess what ports and protocols we want to add permissions for.
+ // We find every URI which they have visited with this host (or a subdomain of this host),
+ // and try to add it as a principal.
+ bool foundHistory = false;
+
+ nsCOMPtr<nsINavHistoryService> histSrv = do_GetService(NS_NAVHISTORYSERVICE_CONTRACTID);
+
+ if (histSrv) {
+ nsCOMPtr<nsINavHistoryQuery> histQuery;
+ rv = histSrv->GetNewQuery(getter_AddRefs(histQuery));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Get the eTLD+1 of the domain
+ nsAutoCString eTLD1;
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ MOZ_ASSERT(tldService); // We should always have a tldService
+ if (tldService) {
+ rv = tldService->GetBaseDomainFromHost(aHost, 0, eTLD1);
+ }
+
+ if (!tldService || NS_FAILED(rv)) {
+ // If the lookup on the tldService for the base domain for the host failed,
+ // that means that we just want to directly use the host as the host name
+ // for the lookup.
+ eTLD1 = aHost;
+ }
+
+ // We want to only find history items for this particular eTLD+1, and subdomains
+ rv = histQuery->SetDomain(eTLD1);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = histQuery->SetDomainIsHost(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsINavHistoryQueryOptions> histQueryOpts;
+ rv = histSrv->GetNewQueryOptions(getter_AddRefs(histQueryOpts));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We want to get the URIs for every item in the user's history with the given host
+ rv = histQueryOpts->SetResultType(nsINavHistoryQueryOptions::RESULTS_AS_URI);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We only search history, because searching both bookmarks and history
+ // is not supported, and history tends to be more comprehensive.
+ rv = histQueryOpts->SetQueryType(nsINavHistoryQueryOptions::QUERY_TYPE_HISTORY);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We include hidden URIs (such as those visited via iFrames) as they may have permissions too
+ rv = histQueryOpts->SetIncludeHidden(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsINavHistoryResult> histResult;
+ rv = histSrv->ExecuteQuery(histQuery, histQueryOpts, getter_AddRefs(histResult));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsINavHistoryContainerResultNode> histResultContainer;
+ rv = histResult->GetRoot(getter_AddRefs(histResultContainer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = histResultContainer->SetContainerOpen(true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t childCount = 0;
+ rv = histResultContainer->GetChildCount(&childCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTHashtable<nsCStringHashKey> insertedOrigins;
+ for (uint32_t i = 0; i < childCount; i++) {
+ nsCOMPtr<nsINavHistoryResultNode> child;
+ histResultContainer->GetChild(i, getter_AddRefs(child));
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ uint32_t type;
+ rv = child->GetType(&type);
+ if (NS_WARN_IF(NS_FAILED(rv)) || type != nsINavHistoryResultNode::RESULT_TYPE_URI) {
+ NS_WARNING("Unexpected non-RESULT_TYPE_URI node in "
+ "UpgradeHostToOriginAndInsert()");
+ continue;
+ }
+
+ nsAutoCString uriSpec;
+ rv = child->GetUri(uriSpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewURI(getter_AddRefs(uri), uriSpec);
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ // Use the provided host - this URI may be for a subdomain, rather than the host we care about.
+ rv = uri->SetHost(aHost);
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ // We now have a URI which we can make a nsIPrincipal out of
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement, getter_AddRefs(principal));
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ nsAutoCString origin;
+ rv = GetOriginFromPrincipal(principal, origin);
+ if (NS_WARN_IF(NS_FAILED(rv))) continue;
+
+ // Ensure that we don't insert the same origin repeatedly
+ if (insertedOrigins.Contains(origin)) {
+ continue;
+ }
+
+ foundHistory = true;
+ rv = aHelper->Insert(origin, aType, aPermission,
+ aExpireType, aExpireTime, aModificationTime);
+ NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Insert failed");
+ insertedOrigins.PutEntry(origin);
+ }
+
+ rv = histResultContainer->SetContainerOpen(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If we didn't find any origins for this host in the poermissions database,
+ // we can insert the default http:// and https:// permissions into the database.
+ // This has a relatively high liklihood of applying the permission to the correct
+ // origin.
+ if (!foundHistory) {
+ nsAutoCString hostSegment;
+ nsCOMPtr<nsIPrincipal> principal;
+ nsAutoCString origin;
+
+ // If this is an ipv6 URI, we need to surround it in '[', ']' before trying to
+ // parse it as a URI.
+ if (aHost.FindChar(':') != -1) {
+ hostSegment.Assign("[");
+ hostSegment.Append(aHost);
+ hostSegment.Append("]");
+ } else {
+ hostSegment.Assign(aHost);
+ }
+
+ // http:// URI default
+ rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("http://") + hostSegment);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetOriginFromPrincipal(principal, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHelper->Insert(origin, aType, aPermission,
+ aExpireType, aExpireTime, aModificationTime);
+
+ // https:// URI default
+ rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://") + hostSegment);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = GetOriginFromPrincipal(principal, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ aHelper->Insert(origin, aType, aPermission,
+ aExpireType, aExpireTime, aModificationTime);
+ }
+
+ return NS_OK;
+}
+
+static bool
+IsExpandedPrincipal(nsIPrincipal* aPrincipal)
+{
+ nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
+ return !!ep;
+}
+
+} // namespace
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsPermissionManager::PermissionKey::PermissionKey(nsIPrincipal* aPrincipal)
+{
+ MOZ_ALWAYS_SUCCEEDS(GetOriginFromPrincipal(aPrincipal, mOrigin));
+}
+
+/**
+ * Simple callback used by |AsyncClose| to trigger a treatment once
+ * the database is closed.
+ *
+ * Note: Beware that, if you hold onto a |CloseDatabaseListener| from a
+ * |nsPermissionManager|, this will create a cycle.
+ *
+ * Note: Once the callback has been called this DeleteFromMozHostListener cannot
+ * be reused.
+ */
+class CloseDatabaseListener final : public mozIStorageCompletionCallback
+{
+ ~CloseDatabaseListener() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGECOMPLETIONCALLBACK
+ /**
+ * @param aManager The owning manager.
+ * @param aRebuildOnSuccess If |true|, reinitialize the database once
+ * it has been closed. Otherwise, do nothing such.
+ */
+ CloseDatabaseListener(nsPermissionManager* aManager,
+ bool aRebuildOnSuccess);
+
+protected:
+ RefPtr<nsPermissionManager> mManager;
+ bool mRebuildOnSuccess;
+};
+
+NS_IMPL_ISUPPORTS(CloseDatabaseListener, mozIStorageCompletionCallback)
+
+CloseDatabaseListener::CloseDatabaseListener(nsPermissionManager* aManager,
+ bool aRebuildOnSuccess)
+ : mManager(aManager)
+ , mRebuildOnSuccess(aRebuildOnSuccess)
+{
+}
+
+NS_IMETHODIMP
+CloseDatabaseListener::Complete(nsresult, nsISupports*)
+{
+ // Help breaking cycles
+ RefPtr<nsPermissionManager> manager = mManager.forget();
+ if (mRebuildOnSuccess && !manager->mIsShuttingDown) {
+ return manager->InitDB(true);
+ }
+ return NS_OK;
+}
+
+
+/**
+ * Simple callback used by |RemoveAllInternal| to trigger closing
+ * the database and reinitializing it.
+ *
+ * Note: Beware that, if you hold onto a |DeleteFromMozHostListener| from a
+ * |nsPermissionManager|, this will create a cycle.
+ *
+ * Note: Once the callback has been called this DeleteFromMozHostListener cannot
+ * be reused.
+ */
+class DeleteFromMozHostListener final : public mozIStorageStatementCallback
+{
+ ~DeleteFromMozHostListener() {}
+
+public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGESTATEMENTCALLBACK
+
+ /**
+ * @param aManager The owning manager.
+ */
+ explicit DeleteFromMozHostListener(nsPermissionManager* aManager);
+
+protected:
+ RefPtr<nsPermissionManager> mManager;
+};
+
+NS_IMPL_ISUPPORTS(DeleteFromMozHostListener, mozIStorageStatementCallback)
+
+DeleteFromMozHostListener::
+DeleteFromMozHostListener(nsPermissionManager* aManager)
+ : mManager(aManager)
+{
+}
+
+NS_IMETHODIMP DeleteFromMozHostListener::HandleResult(mozIStorageResultSet *)
+{
+ MOZ_CRASH("Should not get any results");
+}
+
+NS_IMETHODIMP DeleteFromMozHostListener::HandleError(mozIStorageError *)
+{
+ // Errors are handled in |HandleCompletion|
+ return NS_OK;
+}
+
+NS_IMETHODIMP DeleteFromMozHostListener::HandleCompletion(uint16_t aReason)
+{
+ // Help breaking cycles
+ RefPtr<nsPermissionManager> manager = mManager.forget();
+
+ if (aReason == REASON_ERROR) {
+ manager->CloseDB(true);
+ }
+
+ return NS_OK;
+}
+
+/* static */ void
+nsPermissionManager::ClearOriginDataObserverInit()
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ observerService->AddObserver(new ClearOriginDataObserver(), "clear-origin-attributes-data", /* ownsWeak= */ false);
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// nsPermissionManager Implementation
+
+#define PERMISSIONS_FILE_NAME "permissions.sqlite"
+#define HOSTS_SCHEMA_VERSION 9
+
+#define HOSTPERM_FILE_NAME "hostperm.1"
+
+// Default permissions are read from a URL - this is the preference we read
+// to find that URL. If not set, don't use any default permissions.
+static const char kDefaultsUrlPrefName[] = "permissions.manager.defaultsUrl";
+
+static const char kPermissionChangeNotification[] = PERM_CHANGE_NOTIFICATION;
+
+NS_IMPL_ISUPPORTS(nsPermissionManager, nsIPermissionManager, nsIObserver, nsISupportsWeakReference)
+
+nsPermissionManager::nsPermissionManager()
+ : mMemoryOnlyDB(false)
+ , mLargestID(0)
+ , mIsShuttingDown(false)
+{
+}
+
+nsPermissionManager::~nsPermissionManager()
+{
+ RemoveAllFromMemory();
+ gPermissionManager = nullptr;
+}
+
+// static
+nsIPermissionManager*
+nsPermissionManager::GetXPCOMSingleton()
+{
+ if (gPermissionManager) {
+ NS_ADDREF(gPermissionManager);
+ return gPermissionManager;
+ }
+
+ // Create a new singleton nsPermissionManager.
+ // We AddRef only once since XPCOM has rules about the ordering of module
+ // teardowns - by the time our module destructor is called, it's too late to
+ // Release our members, since GC cycles have already been completed and
+ // would result in serious leaks.
+ // See bug 209571.
+ gPermissionManager = new nsPermissionManager();
+ if (gPermissionManager) {
+ NS_ADDREF(gPermissionManager);
+ if (NS_FAILED(gPermissionManager->Init())) {
+ NS_RELEASE(gPermissionManager);
+ }
+ }
+
+ return gPermissionManager;
+}
+
+nsresult
+nsPermissionManager::Init()
+{
+ // If the 'permissions.memory_only' pref is set to true, then don't write any
+ // permission settings to disk, but keep them in a memory-only database.
+ mMemoryOnlyDB = mozilla::Preferences::GetBool("permissions.memory_only", false);
+
+ if (IsChildProcess()) {
+ // Stop here; we don't need the DB in the child process
+ return FetchPermissions();
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, "profile-before-change", true);
+ observerService->AddObserver(this, "profile-do-change", true);
+ }
+
+ // ignore failure here, since it's non-fatal (we can run fine without
+ // persistent storage - e.g. if there's no profile).
+ // XXX should we tell the user about this?
+ InitDB(false);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RefreshPermission() {
+ NS_ENSURE_TRUE(IsChildProcess(), NS_ERROR_FAILURE);
+
+ nsresult rv = RemoveAllFromMemory();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = FetchPermissions();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult
+nsPermissionManager::OpenDatabase(nsIFile* aPermissionsFile)
+{
+ nsresult rv;
+ nsCOMPtr<mozIStorageService> storage = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
+ if (!storage) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ // cache a connection to the hosts database
+ if (mMemoryOnlyDB) {
+ rv = storage->OpenSpecialDatabase("memory", getter_AddRefs(mDBConn));
+ } else {
+ rv = storage->OpenDatabase(aPermissionsFile, getter_AddRefs(mDBConn));
+ }
+ return rv;
+}
+
+nsresult
+nsPermissionManager::InitDB(bool aRemoveFile)
+{
+ nsCOMPtr<nsIFile> permissionsFile;
+ nsresult rv = NS_GetSpecialDirectory(NS_APP_PERMISSION_PARENT_DIR, getter_AddRefs(permissionsFile));
+ if (NS_FAILED(rv)) {
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(permissionsFile));
+ }
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
+
+ rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(PERMISSIONS_FILE_NAME));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aRemoveFile) {
+ bool exists = false;
+ rv = permissionsFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (exists) {
+ rv = permissionsFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ }
+
+ rv = OpenDatabase(permissionsFile);
+ if (rv == NS_ERROR_FILE_CORRUPTED) {
+ LogToConsole(NS_LITERAL_STRING("permissions.sqlite is corrupted! Try again!"));
+
+ // Add telemetry probe
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::PERMISSIONS_SQL_CORRUPTED, 1);
+
+ // delete corrupted permissions.sqlite and try again
+ rv = permissionsFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LogToConsole(NS_LITERAL_STRING("Corrupted permissions.sqlite has been removed."));
+
+ rv = OpenDatabase(permissionsFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LogToConsole(NS_LITERAL_STRING("OpenDatabase to permissions.sqlite is successful!"));
+ } else if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ bool ready;
+ mDBConn->GetConnectionReady(&ready);
+ if (!ready) {
+ LogToConsole(NS_LITERAL_STRING("Fail to get connection to permissions.sqlite! Try again!"));
+
+ // delete and try again
+ rv = permissionsFile->Remove(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LogToConsole(NS_LITERAL_STRING("Defective permissions.sqlite has been removed."));
+
+ // Add telemetry probe
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::DEFECTIVE_PERMISSIONS_SQL_REMOVED, 1);
+
+ rv = OpenDatabase(permissionsFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ LogToConsole(NS_LITERAL_STRING("OpenDatabase to permissions.sqlite is successful!"));
+
+ mDBConn->GetConnectionReady(&ready);
+ if (!ready)
+ return NS_ERROR_UNEXPECTED;
+ }
+
+
+ bool tableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_perms"), &tableExists);
+ if (!tableExists) {
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &tableExists);
+ }
+ if (!tableExists) {
+ rv = CreateTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ // table already exists; check the schema version before reading
+ int32_t dbSchemaVersion;
+ rv = mDBConn->GetSchemaVersion(&dbSchemaVersion);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ switch (dbSchemaVersion) {
+ // upgrading.
+ // every time you increment the database schema, you need to implement
+ // the upgrading code from the previous version to the new one.
+ // fall through to current version
+
+ case 1:
+ {
+ // previous non-expiry version of database. Upgrade it by adding the
+ // expiration columns
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD expireType INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD expireTime INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // TODO: we want to make default version as version 2 in order to fix bug 784875.
+ case 0:
+ case 2:
+ {
+ // Add appId/isInBrowserElement fields.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD appId INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD isInBrowserElement INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->SetSchemaVersion(3);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // Version 3->4 is the creation of the modificationTime field.
+ case 3:
+ {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts ADD modificationTime INTEGER"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We leave the modificationTime at zero for all existing records; using
+ // now() would mean, eg, that doing "remove all from the last hour"
+ // within the first hour after migration would remove all permissions.
+
+ rv = mDBConn->SetSchemaVersion(4);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // In version 5, host appId, and isInBrowserElement were merged into a
+ // single origin entry
+ //
+ // In version 6, the tables were renamed for backwards compatability reasons
+ // with version 4 and earlier.
+ //
+ // In version 7, a bug in the migration used for version 4->5 was discovered
+ // which could have triggered data-loss. Because of that, all users with a
+ // version 4, 5, or 6 database will be re-migrated from the backup database.
+ // (bug 1186034). This migration bug is not present after bug 1185340, and the
+ // re-migration ensures that all users have the fix.
+ case 5:
+ // This branch could also be reached via dbSchemaVersion == 3, in which case
+ // we want to fall through to the dbSchemaVersion == 4 case.
+ // The easiest way to do that is to perform this extra check here to make
+ // sure that we didn't get here via a fallthrough from v3
+ if (dbSchemaVersion == 5) {
+ // In version 5, the backup database is named moz_hosts_v4. We perform
+ // the version 5->6 migration to get the tables to have consistent
+ // naming conventions.
+
+ // Version 5->6 is the renaming of moz_hosts to moz_perms, and
+ // moz_hosts_v4 to moz_hosts (bug 1185343)
+ //
+ // In version 5, we performed the modifications to the permissions
+ // database in place, this meant that if you upgraded to a version which
+ // used V5, and then downgraded to a version which used v4 or earlier,
+ // the fallback path would drop the table, and your permissions data
+ // would be lost. This migration undoes that mistake, by restoring the
+ // old moz_hosts table (if it was present), and instead using the new
+ // table moz_perms for the new permissions schema.
+ //
+ // NOTE: If you downgrade, store new permissions, and then upgrade
+ // again, these new permissions won't be migrated or reflected in the
+ // updated database. This migration only occurs once, as if moz_perms
+ // exists, it will skip creating it. In addition, permissions added
+ // after the migration will not be visible in previous versions of
+ // firefox.
+
+ bool permsTableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_perms"), &permsTableExists);
+ if (!permsTableExists) {
+ // Move the upgraded database to moz_perms
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts RENAME TO moz_perms"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ NS_WARNING("moz_hosts was not renamed to moz_perms, "
+ "as a moz_perms table already exists");
+
+ // In the situation where a moz_perms table already exists, but the
+ // schema is lower than 6, a migration has already previously occured
+ // to V6, but a downgrade has caused the moz_hosts table to be
+ // dropped. This should only occur in the case of a downgrade to a V5
+ // database, which was only present in a few day's nightlies. As that
+ // version was likely used only on a temporary basis, we assume that
+ // the database from the previous V6 has the permissions which the
+ // user actually wants to use. We have to get rid of moz_hosts such
+ // that moz_hosts_v4 can be moved into its place if it exists.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+#ifdef DEBUG
+ // The moz_hosts table shouldn't exist anymore
+ bool hostsTableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &hostsTableExists);
+ MOZ_ASSERT(!hostsTableExists);
+#endif
+
+ // Rename moz_hosts_v4 back to it's original location, if it exists
+ bool v4TableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_v4"), &v4TableExists);
+ if (v4TableExists) {
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts_v4 RENAME TO moz_hosts"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mDBConn->SetSchemaVersion(6);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // At this point, the version 5 table has been migrated to a version 6 table
+ // We are guaranteed to have at least one of moz_hosts and moz_perms. If
+ // we have moz_hosts, we will migrate moz_hosts into moz_perms (even if
+ // we already have a moz_perms, as we need a re-migration due to bug 1186034).
+ //
+ // After this migration, we are guaranteed to have both a moz_hosts (for backwards
+ // compatability), and a moz_perms table. The moz_hosts table will have a v4 schema,
+ // and the moz_perms table will have a v6 schema.
+ case 4:
+ case 6:
+ {
+ bool hostsTableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &hostsTableExists);
+ if (hostsTableExists) {
+ bool migrationError = false;
+
+ // Both versions 4 and 6 have a version 4 formatted hosts table named
+ // moz_hosts. We can migrate this table to our version 7 table moz_perms.
+ // If moz_perms is present, then we can use it as a basis for comparison.
+
+ rv = mDBConn->BeginTransaction();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool tableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_new"), &tableExists);
+ if (tableExists) {
+ NS_WARNING("The temporary database moz_hosts_new already exists, dropping it.");
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts_new"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_hosts_new ("
+ " id INTEGER PRIMARY KEY"
+ ",origin TEXT"
+ ",type TEXT"
+ ",permission INTEGER"
+ ",expireType INTEGER"
+ ",expireTime INTEGER"
+ ",modificationTime INTEGER"
+ ")"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT host, type, permission, expireType, expireTime, "
+ "modificationTime, appId, isInBrowserElement FROM moz_hosts"),
+ getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t id = 0;
+ nsAutoCString host, type;
+ uint32_t permission;
+ uint32_t expireType;
+ int64_t expireTime;
+ int64_t modificationTime;
+ uint32_t appId;
+ bool isInBrowserElement;
+ bool hasResult;
+
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ // Read in the old row
+ rv = stmt->GetUTF8String(0, host);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ migrationError = true;
+ continue;
+ }
+ rv = stmt->GetUTF8String(1, type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ migrationError = true;
+ continue;
+ }
+ permission = stmt->AsInt32(2);
+ expireType = stmt->AsInt32(3);
+ expireTime = stmt->AsInt64(4);
+ modificationTime = stmt->AsInt64(5);
+ if (NS_WARN_IF(stmt->AsInt64(6) < 0)) {
+ migrationError = true;
+ continue;
+ }
+ appId = static_cast<uint32_t>(stmt->AsInt64(6));
+ isInBrowserElement = static_cast<bool>(stmt->AsInt32(7));
+
+ // Perform the meat of the migration by deferring to the
+ // UpgradeHostToOriginAndInsert function.
+ UpgradeHostToOriginDBMigration upHelper(mDBConn, &id);
+ rv = UpgradeHostToOriginAndInsert(host, type, permission,
+ expireType, expireTime,
+ modificationTime, appId,
+ isInBrowserElement,
+ &upHelper);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unexpected failure when upgrading migrating permission "
+ "from host to origin");
+ migrationError = true;
+ }
+ }
+
+ // We don't drop the moz_hosts table such that it is avaliable for
+ // backwards-compatability and for future migrations in case of
+ // migration errors in the current code.
+ // Create a marker empty table which will indicate that the moz_hosts
+ // table is intended to act as a backup. If this table is not present,
+ // then the moz_hosts table was created as a random empty table.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_hosts_is_backup (dummy INTEGER PRIMARY KEY)"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool permsTableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_perms"), &permsTableExists);
+ if (permsTableExists) {
+ // The user already had a moz_perms table, and we are performing a
+ // re-migration. We count the rows in the old table for telemetry,
+ // and then back up their old database as moz_perms_v6
+
+ nsCOMPtr<mozIStorageStatement> countStmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("SELECT COUNT(*) FROM moz_perms"),
+ getter_AddRefs(countStmt));
+ bool hasResult = false;
+ if (NS_SUCCEEDED(rv) &&
+ NS_SUCCEEDED(countStmt->ExecuteStep(&hasResult)) &&
+ hasResult) {
+ int32_t permsCount = countStmt->AsInt32(0);
+
+ // The id variable contains the number of rows inserted into the
+ // moz_hosts_new table (as one ID was used per entry)
+ uint32_t telemetryValue;
+ if (permsCount > id) {
+ telemetryValue = 3; // NEW > OLD
+ } else if (permsCount == id) {
+ telemetryValue = 2; // NEW == OLD
+ } else if (permsCount == 0) {
+ telemetryValue = 0; // NEW = 0
+ } else {
+ telemetryValue = 1; // NEW < OLD
+ }
+
+ // Report the telemetry value to telemetry
+ mozilla::Telemetry::Accumulate(
+ mozilla::Telemetry::PERMISSIONS_REMIGRATION_COMPARISON,
+ telemetryValue);
+ } else {
+ NS_WARNING("Could not count the rows in moz_perms");
+ }
+
+ // Back up the old moz_perms database as moz_perms_v6 before we
+ // move the new table into its position
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_perms RENAME TO moz_perms_v6"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "ALTER TABLE moz_hosts_new RENAME TO moz_perms"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->CommitTransaction();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::PERMISSIONS_MIGRATION_7_ERROR,
+ NS_WARN_IF(migrationError));
+ } else {
+ // We don't have a moz_hosts table, so we create one for downgrading purposes.
+ // This table is empty.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_hosts ("
+ " id INTEGER PRIMARY KEY"
+ ",host TEXT"
+ ",type TEXT"
+ ",permission INTEGER"
+ ",expireType INTEGER"
+ ",expireTime INTEGER"
+ ",modificationTime INTEGER"
+ ",appId INTEGER"
+ ",isInBrowserElement INTEGER"
+ ")"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We are guaranteed to have a moz_perms table at this point.
+ }
+
+#ifdef DEBUG
+ {
+ // At this point, both the moz_hosts and moz_perms tables should exist
+ bool hostsTableExists = false;
+ bool permsTableExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &hostsTableExists);
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_perms"), &permsTableExists);
+ MOZ_ASSERT(hostsTableExists && permsTableExists);
+ }
+#endif
+
+ rv = mDBConn->SetSchemaVersion(7);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // The version 7-8 migration is the re-migration of localhost and ip-address
+ // entries due to errors in the previous version 7 migration which caused
+ // localhost and ip-address entries to be incorrectly discarded.
+ // The version 7 migration logic has been corrected, and thus this logic only
+ // needs to execute if the user is currently on version 7.
+ case 7:
+ {
+ // This migration will be relatively expensive as we need to perform
+ // database lookups for each origin which we want to insert. Fortunately,
+ // it shouldn't be too expensive as we only want to insert a small number
+ // of entries created for localhost or IP addresses.
+
+ // We only want to perform the re-migration if moz_hosts is a backup
+ bool hostsIsBackupExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_is_backup"),
+ &hostsIsBackupExists);
+
+ // Only perform this migration if the original schema version was 7, and
+ // the moz_hosts table is a backup.
+ if (dbSchemaVersion == 7 && hostsIsBackupExists) {
+ nsCOMPtr<nsIEffectiveTLDService> tldService =
+ do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+ MOZ_ASSERT(tldService); // We should always have a tldService
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT host, type, permission, expireType, expireTime, "
+ "modificationTime, appId, isInBrowserElement FROM moz_hosts"),
+ getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<mozIStorageStatement> idStmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT MAX(id) FROM moz_hosts"), getter_AddRefs(idStmt));
+ int64_t id = 0;
+ bool hasResult = false;
+ if (NS_SUCCEEDED(rv) &&
+ NS_SUCCEEDED(idStmt->ExecuteStep(&hasResult)) &&
+ hasResult) {
+ id = idStmt->AsInt32(0) + 1;
+ }
+
+ nsAutoCString host, type;
+ uint32_t permission;
+ uint32_t expireType;
+ int64_t expireTime;
+ int64_t modificationTime;
+ uint32_t appId;
+ bool isInBrowserElement;
+
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ // Read in the old row
+ rv = stmt->GetUTF8String(0, host);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+
+ nsAutoCString eTLD1;
+ rv = tldService->GetBaseDomainFromHost(host, 0, eTLD1);
+ if (NS_SUCCEEDED(rv)) {
+ // We only care about entries which the tldService can't handle
+ continue;
+ }
+
+ rv = stmt->GetUTF8String(1, type);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ continue;
+ }
+ permission = stmt->AsInt32(2);
+ expireType = stmt->AsInt32(3);
+ expireTime = stmt->AsInt64(4);
+ modificationTime = stmt->AsInt64(5);
+ if (NS_WARN_IF(stmt->AsInt64(6) < 0)) {
+ continue;
+ }
+ appId = static_cast<uint32_t>(stmt->AsInt64(6));
+ isInBrowserElement = static_cast<bool>(stmt->AsInt32(7));
+
+ // Perform the meat of the migration by deferring to the
+ // UpgradeHostToOriginAndInsert function.
+ UpgradeIPHostToOriginDB upHelper(mDBConn, &id);
+ rv = UpgradeHostToOriginAndInsert(host, type, permission,
+ expireType, expireTime,
+ modificationTime, appId,
+ isInBrowserElement,
+ &upHelper);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Unexpected failure when upgrading migrating permission "
+ "from host to origin");
+ }
+ }
+ }
+
+ // Even if we didn't perform the migration, we want to bump the schema
+ // version to 8.
+ rv = mDBConn->SetSchemaVersion(8);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // The version 8-9 migration removes the unnecessary backup moz-hosts database contents.
+ // as the data no longer needs to be migrated
+ case 8:
+ {
+ // We only want to clear out the old table if it is a backup. If it isn't a backup,
+ // we don't need to touch it.
+ bool hostsIsBackupExists = false;
+ mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_is_backup"),
+ &hostsIsBackupExists);
+ if (hostsIsBackupExists) {
+ // Delete everything from the backup, we want to keep around the table so that
+ // you can still downgrade and not break things, but we don't need to keep the
+ // rows around.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_hosts"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The table is no longer a backup, so get rid of it.
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts_is_backup"));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = mDBConn->SetSchemaVersion(9);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // fall through to the next upgrade
+ MOZ_FALLTHROUGH;
+
+ // current version.
+ case HOSTS_SCHEMA_VERSION:
+ break;
+
+ // downgrading.
+ // if columns have been added to the table, we can still use the ones we
+ // understand safely. if columns have been deleted or altered, just
+ // blow away the table and start from scratch! if you change the way
+ // a column is interpreted, make sure you also change its name so this
+ // check will catch it.
+ default:
+ {
+ // check if all the expected columns exist
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT origin, type, permission, expireType, expireTime, "
+ "modificationTime FROM moz_perms"),
+ getter_AddRefs(stmt));
+ if (NS_SUCCEEDED(rv))
+ break;
+
+ // our columns aren't there - drop the table!
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_perms"));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = CreateTable();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ break;
+ }
+ }
+
+ // cache frequently used statements (for insertion, deletion, and updating)
+ rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "INSERT INTO moz_perms "
+ "(id, origin, type, permission, expireType, expireTime, modificationTime) "
+ "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"), getter_AddRefs(mStmtInsert));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_perms "
+ "WHERE id = ?1"), getter_AddRefs(mStmtDelete));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDBConn->CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "UPDATE moz_perms "
+ "SET permission = ?2, expireType= ?3, expireTime = ?4, modificationTime = ?5 WHERE id = ?1"),
+ getter_AddRefs(mStmtUpdate));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Always import default permissions.
+ ImportDefaults();
+ // check whether to import or just read in the db
+ if (tableExists)
+ return Read();
+
+ return Import();
+}
+
+// sets the schema version and creates the moz_perms table.
+nsresult
+nsPermissionManager::CreateTable()
+{
+ // set the schema version, before creating the table
+ nsresult rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
+ if (NS_FAILED(rv)) return rv;
+
+ // create the table
+ // SQL also lives in automation.py.in. If you change this SQL change that
+ // one too
+ rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_perms ("
+ " id INTEGER PRIMARY KEY"
+ ",origin TEXT"
+ ",type TEXT"
+ ",permission INTEGER"
+ ",expireType INTEGER"
+ ",expireTime INTEGER"
+ ",modificationTime INTEGER"
+ ")"));
+ if (NS_FAILED(rv)) return rv;
+
+ // We also create a legacy V4 table, for backwards compatability,
+ // and to ensure that downgrades don't trigger a schema version change.
+ return mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+ "CREATE TABLE moz_hosts ("
+ " id INTEGER PRIMARY KEY"
+ ",host TEXT"
+ ",type TEXT"
+ ",permission INTEGER"
+ ",expireType INTEGER"
+ ",expireTime INTEGER"
+ ",modificationTime INTEGER"
+ ",appId INTEGER"
+ ",isInBrowserElement INTEGER"
+ ")"));
+}
+
+NS_IMETHODIMP
+nsPermissionManager::Add(nsIURI *aURI,
+ const char *aType,
+ uint32_t aPermission,
+ uint32_t aExpireType,
+ int64_t aExpireTime)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return AddFromPrincipal(principal, aType, aPermission, aExpireType, aExpireTime);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::AddFromPrincipal(nsIPrincipal* aPrincipal,
+ const char* aType, uint32_t aPermission,
+ uint32_t aExpireType, int64_t aExpireTime)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aType);
+ NS_ENSURE_TRUE(aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
+ aExpireType == nsIPermissionManager::EXPIRE_TIME ||
+ aExpireType == nsIPermissionManager::EXPIRE_SESSION,
+ NS_ERROR_INVALID_ARG);
+
+ // Skip addition if the permission is already expired. Note that EXPIRE_SESSION only
+ // honors expireTime if it is nonzero.
+ if ((aExpireType == nsIPermissionManager::EXPIRE_TIME ||
+ (aExpireType == nsIPermissionManager::EXPIRE_SESSION && aExpireTime != 0)) &&
+ aExpireTime <= (PR_Now() / 1000)) {
+ return NS_OK;
+ }
+
+ // We don't add the system principal because it actually has no URI and we
+ // always allow action for them.
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ return NS_OK;
+ }
+
+ // Null principals can't meaningfully have persisted permissions attached to
+ // them, so we don't allow adding permissions for them.
+ if (aPrincipal->GetIsNullPrincipal()) {
+ return NS_OK;
+ }
+
+ // Permissions may not be added to expanded principals.
+ if (IsExpandedPrincipal(aPrincipal)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // A modificationTime of zero will cause AddInternal to use now().
+ int64_t modificationTime = 0;
+
+ return AddInternal(aPrincipal, nsDependentCString(aType), aPermission, 0,
+ aExpireType, aExpireTime, modificationTime, eNotify, eWriteToDB);
+}
+
+nsresult
+nsPermissionManager::AddInternal(nsIPrincipal* aPrincipal,
+ const nsAFlatCString &aType,
+ uint32_t aPermission,
+ int64_t aID,
+ uint32_t aExpireType,
+ int64_t aExpireTime,
+ int64_t aModificationTime,
+ NotifyOperationType aNotifyOperation,
+ DBOperationType aDBOperation,
+ const bool aIgnoreSessionPermissions)
+{
+ nsAutoCString origin;
+ nsresult rv = GetOriginFromPrincipal(aPrincipal, origin);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!IsChildProcess()) {
+ IPC::Permission permission(origin, aType, aPermission,
+ aExpireType, aExpireTime);
+
+ nsTArray<ContentParent*> cplist;
+ ContentParent::GetAll(cplist);
+ for (uint32_t i = 0; i < cplist.Length(); ++i) {
+ ContentParent* cp = cplist[i];
+ // On platforms where we use a preallocated template process we don't
+ // want to notify this process about session specific permissions so
+ // new tabs or apps created on it won't inherit the session permissions.
+ if (cp->IsPreallocated() &&
+ aExpireType == nsIPermissionManager::EXPIRE_SESSION)
+ continue;
+ if (cp->NeedsPermissionsUpdate())
+ Unused << cp->SendAddPermission(permission);
+ }
+ }
+
+ // look up the type index
+ int32_t typeIndex = GetTypeIndex(aType.get(), true);
+ NS_ENSURE_TRUE(typeIndex != -1, NS_ERROR_OUT_OF_MEMORY);
+
+ // When an entry already exists, PutEntry will return that, instead
+ // of adding a new one
+ RefPtr<PermissionKey> key = new PermissionKey(aPrincipal);
+ PermissionHashKey* entry = mPermissionTable.PutEntry(key);
+ if (!entry) return NS_ERROR_FAILURE;
+ if (!entry->GetKey()) {
+ mPermissionTable.RemoveEntry(entry);
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // figure out the transaction type, and get any existing permission value
+ OperationType op;
+ int32_t index = entry->GetPermissionIndex(typeIndex);
+ if (index == -1) {
+ if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
+ op = eOperationNone;
+ else
+ op = eOperationAdding;
+
+ } else {
+ PermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
+
+ // remove the permission if the permission is UNKNOWN, update the
+ // permission if its value or expire type have changed OR if the time has
+ // changed and the expire type is time, otherwise, don't modify. There's
+ // no need to modify a permission that doesn't expire with time when the
+ // only thing changed is the expire time.
+ if (aPermission == oldPermissionEntry.mPermission &&
+ aExpireType == oldPermissionEntry.mExpireType &&
+ (aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
+ aExpireTime == oldPermissionEntry.mExpireTime))
+ op = eOperationNone;
+ else if (oldPermissionEntry.mID == cIDPermissionIsDefault)
+ // The existing permission is one added as a default and the new permission
+ // doesn't exactly match so we are replacing the default. This is true
+ // even if the new permission is UNKNOWN_ACTION (which means a "logical
+ // remove" of the default)
+ op = eOperationReplacingDefault;
+ else if (aID == cIDPermissionIsDefault)
+ // We are adding a default permission but a "real" permission already
+ // exists. This almost-certainly means we just did a removeAllSince and
+ // are re-importing defaults - so we can ignore this.
+ op = eOperationNone;
+ else if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
+ op = eOperationRemoving;
+ else
+ op = eOperationChanging;
+ }
+
+ // child processes should *always* be passed a modificationTime of zero.
+ MOZ_ASSERT(!IsChildProcess() || aModificationTime == 0);
+
+ // do the work for adding, deleting, or changing a permission:
+ // update the in-memory list, write to the db, and notify consumers.
+ int64_t id;
+ if (aModificationTime == 0) {
+ aModificationTime = PR_Now() / 1000;
+ }
+
+ switch (op) {
+ case eOperationNone:
+ {
+ // nothing to do
+ return NS_OK;
+ }
+
+ case eOperationAdding:
+ {
+ if (aDBOperation == eWriteToDB) {
+ // we'll be writing to the database - generate a known unique id
+ id = ++mLargestID;
+ } else {
+ // we're reading from the database - use the id already assigned
+ id = aID;
+ }
+
+#ifdef MOZ_B2G
+ // When we do the initial addition of the permissions we don't want to
+ // inherit session specific permissions from other tabs or apps
+ // so we ignore them and set the permission to PROMPT_ACTION if it was
+ // previously allowed or denied by the user.
+ if (aIgnoreSessionPermissions &&
+ aExpireType == nsIPermissionManager::EXPIRE_SESSION) {
+ aPermission = nsIPermissionManager::PROMPT_ACTION;
+ aExpireType = nsIPermissionManager::EXPIRE_NEVER;
+ }
+#endif // MOZ_B2G
+
+ entry->GetPermissions().AppendElement(PermissionEntry(id, typeIndex, aPermission,
+ aExpireType, aExpireTime,
+ aModificationTime));
+
+ if (aDBOperation == eWriteToDB && aExpireType != nsIPermissionManager::EXPIRE_SESSION) {
+ UpdateDB(op, mStmtInsert, id, origin, aType, aPermission, aExpireType, aExpireTime, aModificationTime);
+ }
+
+ if (aNotifyOperation == eNotify) {
+ NotifyObserversWithPermission(aPrincipal,
+ mTypeArray[typeIndex],
+ aPermission,
+ aExpireType,
+ aExpireTime,
+ u"added");
+ }
+
+ break;
+ }
+
+ case eOperationRemoving:
+ {
+ PermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
+ id = oldPermissionEntry.mID;
+ entry->GetPermissions().RemoveElementAt(index);
+
+ if (aDBOperation == eWriteToDB)
+ // We care only about the id here so we pass dummy values for all other
+ // parameters.
+ UpdateDB(op, mStmtDelete, id, EmptyCString(), EmptyCString(), 0,
+ nsIPermissionManager::EXPIRE_NEVER, 0, 0);
+
+ if (aNotifyOperation == eNotify) {
+ NotifyObserversWithPermission(aPrincipal,
+ mTypeArray[typeIndex],
+ oldPermissionEntry.mPermission,
+ oldPermissionEntry.mExpireType,
+ oldPermissionEntry.mExpireTime,
+ u"deleted");
+ }
+
+ // If there are no more permissions stored for that entry, clear it.
+ if (entry->GetPermissions().IsEmpty()) {
+ mPermissionTable.RemoveEntry(entry);
+ }
+
+ break;
+ }
+
+ case eOperationChanging:
+ {
+ id = entry->GetPermissions()[index].mID;
+
+ // If the new expireType is EXPIRE_SESSION, then we have to keep a
+ // copy of the previous permission/expireType values. This cached value will be
+ // used when restoring the permissions of an app.
+ if (entry->GetPermissions()[index].mExpireType != nsIPermissionManager::EXPIRE_SESSION &&
+ aExpireType == nsIPermissionManager::EXPIRE_SESSION) {
+ entry->GetPermissions()[index].mNonSessionPermission = entry->GetPermissions()[index].mPermission;
+ entry->GetPermissions()[index].mNonSessionExpireType = entry->GetPermissions()[index].mExpireType;
+ entry->GetPermissions()[index].mNonSessionExpireTime = entry->GetPermissions()[index].mExpireTime;
+ } else if (aExpireType != nsIPermissionManager::EXPIRE_SESSION) {
+ entry->GetPermissions()[index].mNonSessionPermission = aPermission;
+ entry->GetPermissions()[index].mNonSessionExpireType = aExpireType;
+ entry->GetPermissions()[index].mNonSessionExpireTime = aExpireTime;
+ }
+
+ entry->GetPermissions()[index].mPermission = aPermission;
+ entry->GetPermissions()[index].mExpireType = aExpireType;
+ entry->GetPermissions()[index].mExpireTime = aExpireTime;
+ entry->GetPermissions()[index].mModificationTime = aModificationTime;
+
+ if (aDBOperation == eWriteToDB && aExpireType != nsIPermissionManager::EXPIRE_SESSION)
+ // We care only about the id, the permission and expireType/expireTime/modificationTime here.
+ // We pass dummy values for all other parameters.
+ UpdateDB(op, mStmtUpdate, id, EmptyCString(), EmptyCString(),
+ aPermission, aExpireType, aExpireTime, aModificationTime);
+
+ if (aNotifyOperation == eNotify) {
+ NotifyObserversWithPermission(aPrincipal,
+ mTypeArray[typeIndex],
+ aPermission,
+ aExpireType,
+ aExpireTime,
+ u"changed");
+ }
+
+ break;
+ }
+ case eOperationReplacingDefault:
+ {
+ // this is handling the case when we have an existing permission
+ // entry that was created as a "default" (and thus isn't in the DB) with
+ // an explicit permission (that may include UNKNOWN_ACTION.)
+ // Note we will *not* get here if we are replacing an already replaced
+ // default value - that is handled as eOperationChanging.
+
+ // So this is a hybrid of eOperationAdding (as we are writing a new entry
+ // to the DB) and eOperationChanging (as we are replacing the in-memory
+ // repr and sending a "changed" notification).
+
+ // We want a new ID even if not writing to the DB, so the modified entry
+ // in memory doesn't have the magic cIDPermissionIsDefault value.
+ id = ++mLargestID;
+
+ // The default permission being replaced can't have session expiry.
+ NS_ENSURE_TRUE(entry->GetPermissions()[index].mExpireType != nsIPermissionManager::EXPIRE_SESSION,
+ NS_ERROR_UNEXPECTED);
+ // We don't support the new entry having any expiry - supporting that would
+ // make things far more complex and none of the permissions we set as a
+ // default support that.
+ NS_ENSURE_TRUE(aExpireType == EXPIRE_NEVER, NS_ERROR_UNEXPECTED);
+
+ // update the existing entry in memory.
+ entry->GetPermissions()[index].mID = id;
+ entry->GetPermissions()[index].mPermission = aPermission;
+ entry->GetPermissions()[index].mExpireType = aExpireType;
+ entry->GetPermissions()[index].mExpireTime = aExpireTime;
+ entry->GetPermissions()[index].mModificationTime = aModificationTime;
+
+ // If requested, create the entry in the DB.
+ if (aDBOperation == eWriteToDB) {
+ UpdateDB(eOperationAdding, mStmtInsert, id, origin, aType, aPermission,
+ aExpireType, aExpireTime, aModificationTime);
+ }
+
+ if (aNotifyOperation == eNotify) {
+ NotifyObserversWithPermission(aPrincipal,
+ mTypeArray[typeIndex],
+ aPermission,
+ aExpireType,
+ aExpireTime,
+ u"changed");
+ }
+
+ }
+ break;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermissionManager::Remove(nsIURI* aURI,
+ const char* aType)
+{
+ NS_ENSURE_ARG_POINTER(aURI);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return RemoveFromPrincipal(principal, aType);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemoveFromPrincipal(nsIPrincipal* aPrincipal,
+ const char* aType)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aType);
+
+ // System principals are never added to the database, no need to remove them.
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ return NS_OK;
+ }
+
+ // Permissions may not be added to expanded principals.
+ if (IsExpandedPrincipal(aPrincipal)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // AddInternal() handles removal, just let it do the work
+ return AddInternal(aPrincipal,
+ nsDependentCString(aType),
+ nsIPermissionManager::UNKNOWN_ACTION,
+ 0,
+ nsIPermissionManager::EXPIRE_NEVER,
+ 0,
+ 0,
+ eNotify,
+ eWriteToDB);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemovePermission(nsIPermission* aPerm)
+{
+ if (!aPerm) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = aPerm->GetPrincipal(getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString type;
+ rv = aPerm->GetType(type);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Permissions are uniquely identified by their principal and type.
+ // We remove the permission using these two pieces of data.
+ return RemoveFromPrincipal(principal, type.get());
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemoveAll()
+{
+ ENSURE_NOT_CHILD_PROCESS;
+ return RemoveAllInternal(true);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemoveAllSince(int64_t aSince)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+ return RemoveAllModifiedSince(aSince);
+}
+
+void
+nsPermissionManager::CloseDB(bool aRebuildOnSuccess)
+{
+ // Null the statements, this will finalize them.
+ mStmtInsert = nullptr;
+ mStmtDelete = nullptr;
+ mStmtUpdate = nullptr;
+ if (mDBConn) {
+ mozIStorageCompletionCallback* cb = new CloseDatabaseListener(this,
+ aRebuildOnSuccess);
+ mozilla::DebugOnly<nsresult> rv = mDBConn->AsyncClose(cb);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ mDBConn = nullptr; // Avoid race conditions
+ }
+}
+
+nsresult
+nsPermissionManager::RemoveAllInternal(bool aNotifyObservers)
+{
+ // Remove from memory and notify immediately. Since the in-memory
+ // database is authoritative, we do not need confirmation from the
+ // on-disk database to notify observers.
+ RemoveAllFromMemory();
+
+ // Re-import the defaults
+ ImportDefaults();
+
+ if (aNotifyObservers) {
+ NotifyObservers(nullptr, u"cleared");
+ }
+
+ // clear the db
+ if (mDBConn) {
+ nsCOMPtr<mozIStorageAsyncStatement> removeStmt;
+ nsresult rv = mDBConn->
+ CreateAsyncStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_perms"
+ ), getter_AddRefs(removeStmt));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ if (!removeStmt) {
+ return NS_ERROR_UNEXPECTED;
+ }
+ nsCOMPtr<mozIStoragePendingStatement> pending;
+ mozIStorageStatementCallback* cb = new DeleteFromMozHostListener(this);
+ rv = removeStmt->ExecuteAsync(cb, getter_AddRefs(pending));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestExactPermission(nsIURI *aURI,
+ const char *aType,
+ uint32_t *aPermission)
+{
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return TestExactPermissionFromPrincipal(principal, aType, aPermission);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestExactPermissionFromPrincipal(nsIPrincipal* aPrincipal,
+ const char* aType,
+ uint32_t* aPermission)
+{
+ return CommonTestPermission(aPrincipal, aType, aPermission, true, true);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestExactPermanentPermission(nsIPrincipal* aPrincipal,
+ const char* aType,
+ uint32_t* aPermission)
+{
+ return CommonTestPermission(aPrincipal, aType, aPermission, true, false);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestPermission(nsIURI *aURI,
+ const char *aType,
+ uint32_t *aPermission)
+{
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return TestPermissionFromPrincipal(principal, aType, aPermission);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestPermissionFromWindow(mozIDOMWindow* aWindow,
+ const char* aType,
+ uint32_t* aPermission)
+{
+ NS_ENSURE_ARG(aWindow);
+ nsCOMPtr<nsPIDOMWindowInner> window = nsPIDOMWindowInner::From(aWindow);
+
+ // Get the document for security check
+ nsCOMPtr<nsIDocument> document = window->GetExtantDoc();
+ NS_ENSURE_TRUE(document, NS_NOINTERFACE);
+
+ nsCOMPtr<nsIPrincipal> principal = document->NodePrincipal();
+ return TestPermissionFromPrincipal(principal, aType, aPermission);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::TestPermissionFromPrincipal(nsIPrincipal* aPrincipal,
+ const char* aType,
+ uint32_t* aPermission)
+{
+ return CommonTestPermission(aPrincipal, aType, aPermission, false, true);
+}
+
+NS_IMETHODIMP
+nsPermissionManager::GetPermissionObject(nsIPrincipal* aPrincipal,
+ const char* aType,
+ bool aExactHostMatch,
+ nsIPermission** aResult)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aType);
+
+ *aResult = nullptr;
+
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ return NS_OK;
+ }
+
+ // Querying the permission object of an nsEP is non-sensical.
+ if (IsExpandedPrincipal(aPrincipal)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ int32_t typeIndex = GetTypeIndex(aType, false);
+ // If type == -1, the type isn't known,
+ // so just return NS_OK
+ if (typeIndex == -1) return NS_OK;
+
+ PermissionHashKey* entry = GetPermissionHashKey(aPrincipal, typeIndex, aExactHostMatch);
+ if (!entry) {
+ return NS_OK;
+ }
+
+ // We don't call GetPermission(typeIndex) because that returns a fake
+ // UNKNOWN_ACTION entry if there is no match.
+ int32_t idx = entry->GetPermissionIndex(typeIndex);
+ if (-1 == idx) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PermissionEntry& perm = entry->GetPermissions()[idx];
+ nsCOMPtr<nsIPermission> r = nsPermission::Create(principal,
+ mTypeArray.ElementAt(perm.mType),
+ perm.mPermission,
+ perm.mExpireType,
+ perm.mExpireTime);
+ if (NS_WARN_IF(!r)) {
+ return NS_ERROR_FAILURE;
+ }
+ r.forget(aResult);
+ return NS_OK;
+}
+
+nsresult
+nsPermissionManager::CommonTestPermission(nsIPrincipal* aPrincipal,
+ const char *aType,
+ uint32_t *aPermission,
+ bool aExactHostMatch,
+ bool aIncludingSession)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aType);
+
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ *aPermission = nsIPermissionManager::ALLOW_ACTION;
+ return NS_OK;
+ }
+
+ // Set the default.
+ *aPermission = nsIPermissionManager::UNKNOWN_ACTION;
+
+ // For expanded principals, we want to iterate over the whitelist and see
+ // if the permission is granted for any of them.
+ nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
+ if (ep) {
+ nsTArray<nsCOMPtr<nsIPrincipal>>* whitelist;
+ nsresult rv = ep->GetWhiteList(&whitelist);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (size_t i = 0; i < whitelist->Length(); ++i) {
+ uint32_t perm;
+ rv = CommonTestPermission(whitelist->ElementAt(i), aType, &perm, aExactHostMatch,
+ aIncludingSession);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (perm == nsIPermissionManager::ALLOW_ACTION) {
+ *aPermission = perm;
+ return NS_OK;
+ } else if (perm == nsIPermissionManager::PROMPT_ACTION) {
+ // Store it, but keep going to see if we can do better.
+ *aPermission = perm;
+ }
+ }
+
+ return NS_OK;
+ }
+
+ int32_t typeIndex = GetTypeIndex(aType, false);
+ // If type == -1, the type isn't known,
+ // so just return NS_OK
+ if (typeIndex == -1) return NS_OK;
+
+ PermissionHashKey* entry = GetPermissionHashKey(aPrincipal, typeIndex, aExactHostMatch);
+ if (!entry ||
+ (!aIncludingSession &&
+ entry->GetPermission(typeIndex).mNonSessionExpireType ==
+ nsIPermissionManager::EXPIRE_SESSION)) {
+ return NS_OK;
+ }
+
+ *aPermission = aIncludingSession
+ ? entry->GetPermission(typeIndex).mPermission
+ : entry->GetPermission(typeIndex).mNonSessionPermission;
+
+ return NS_OK;
+}
+
+// Returns PermissionHashKey for a given { host, appId, isInBrowserElement } tuple.
+// This is not simply using PermissionKey because we will walk-up domains in
+// case of |host| contains sub-domains.
+// Returns null if nothing found.
+// Also accepts host on the format "<foo>". This will perform an exact match
+// lookup as the string doesn't contain any dots.
+nsPermissionManager::PermissionHashKey*
+nsPermissionManager::GetPermissionHashKey(nsIPrincipal* aPrincipal,
+ uint32_t aType,
+ bool aExactHostMatch)
+{
+ PermissionHashKey* entry = nullptr;
+
+ RefPtr<PermissionKey> key = new PermissionKey(aPrincipal);
+ entry = mPermissionTable.GetEntry(key);
+
+ if (entry) {
+ PermissionEntry permEntry = entry->GetPermission(aType);
+
+ // if the entry is expired, remove and keep looking for others.
+ // Note that EXPIRE_SESSION only honors expireTime if it is nonzero.
+ if ((permEntry.mExpireType == nsIPermissionManager::EXPIRE_TIME ||
+ (permEntry.mExpireType == nsIPermissionManager::EXPIRE_SESSION &&
+ permEntry.mExpireTime != 0)) &&
+ permEntry.mExpireTime <= (PR_Now() / 1000)) {
+ entry = nullptr;
+ RemoveFromPrincipal(aPrincipal, mTypeArray[aType].get());
+ } else if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
+ entry = nullptr;
+ }
+ }
+
+ if (entry) {
+ return entry;
+ }
+
+ // If aExactHostMatch wasn't true, we can check if the base domain has a permission entry.
+ if (!aExactHostMatch) {
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsAutoCString host;
+ rv = uri->GetHost(host);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ nsCString domain = GetNextSubDomainForHost(host);
+ if (domain.IsEmpty()) {
+ return nullptr;
+ }
+
+ // Create a new principal which is identical to the current one, but with the new host
+ nsCOMPtr<nsIURI> newURI;
+ rv = uri->Clone(getter_AddRefs(newURI));
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ rv = newURI->SetHost(domain);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ // Copy the attributes over
+ mozilla::PrincipalOriginAttributes attrs =
+ mozilla::BasePrincipal::Cast(aPrincipal)->OriginAttributesRef();
+
+ // Disable userContext and firstParty isolation for permissions.
+ attrs.StripUserContextIdAndFirstPartyDomain();
+
+ nsCOMPtr<nsIPrincipal> principal =
+ mozilla::BasePrincipal::CreateCodebasePrincipal(newURI, attrs);
+
+ return GetPermissionHashKey(principal, aType, aExactHostMatch);
+ }
+
+ // No entry, really...
+ return nullptr;
+}
+
+NS_IMETHODIMP nsPermissionManager::GetEnumerator(nsISimpleEnumerator **aEnum)
+{
+ // roll an nsCOMArray of all our permissions, then hand out an enumerator
+ nsCOMArray<nsIPermission> array;
+
+ for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) {
+ PermissionHashKey* entry = iter.Get();
+ for (const auto& permEntry : entry->GetPermissions()) {
+ // Given how "default" permissions work and the possibility of them being
+ // overridden with UNKNOWN_ACTION, we might see this value here - but we
+ // do *not* want to return them via the enumerator.
+ if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin,
+ getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPermission> permission =
+ nsPermission::Create(principal,
+ mTypeArray.ElementAt(permEntry.mType),
+ permEntry.mPermission,
+ permEntry.mExpireType,
+ permEntry.mExpireTime);
+ if (NS_WARN_IF(!permission)) {
+ continue;
+ }
+ array.AppendObject(permission);
+ }
+ }
+
+ return NS_NewArrayEnumerator(aEnum, array);
+}
+
+NS_IMETHODIMP nsPermissionManager::GetAllForURI(nsIURI* aURI, nsISimpleEnumerator **aEnum)
+{
+ nsCOMArray<nsIPermission> array;
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<PermissionKey> key = new PermissionKey(principal);
+ PermissionHashKey* entry = mPermissionTable.GetEntry(key);
+
+ if (entry) {
+ for (const auto& permEntry : entry->GetPermissions()) {
+ // Only return custom permissions
+ if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPermission> permission =
+ nsPermission::Create(principal,
+ mTypeArray.ElementAt(permEntry.mType),
+ permEntry.mPermission,
+ permEntry.mExpireType,
+ permEntry.mExpireTime);
+ if (NS_WARN_IF(!permission)) {
+ continue;
+ }
+ array.AppendObject(permission);
+ }
+ }
+
+ return NS_NewArrayEnumerator(aEnum, array);
+}
+
+NS_IMETHODIMP nsPermissionManager::Observe(nsISupports *aSubject, const char *aTopic, const char16_t *someData)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+
+ if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
+ // The profile is about to change,
+ // or is going away because the application is shutting down.
+ mIsShuttingDown = true;
+ RemoveAllFromMemory();
+ CloseDB(false);
+ } else if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
+ // the profile has already changed; init the db from the new location
+ InitDB(false);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsPermissionManager::RemoveAllModifiedSince(int64_t aModificationTime)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+
+ nsCOMArray<nsIPermission> array;
+ for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) {
+ PermissionHashKey* entry = iter.Get();
+ for (const auto& permEntry : entry->GetPermissions()) {
+ if (aModificationTime > permEntry.mModificationTime) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin,
+ getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIPermission> permission =
+ nsPermission::Create(principal,
+ mTypeArray.ElementAt(permEntry.mType),
+ permEntry.mPermission,
+ permEntry.mExpireType,
+ permEntry.mExpireTime);
+ if (NS_WARN_IF(!permission)) {
+ continue;
+ }
+ array.AppendObject(permission);
+ }
+ }
+
+ for (int32_t i = 0; i<array.Count(); ++i) {
+ nsCOMPtr<nsIPrincipal> principal;
+ nsAutoCString type;
+
+ nsresult rv = array[i]->GetPrincipal(getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ NS_ERROR("GetPrincipal() failed!");
+ continue;
+ }
+
+ rv = array[i]->GetType(type);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("GetType() failed!");
+ continue;
+ }
+
+ // AddInternal handles removal, so let it do the work...
+ AddInternal(
+ principal,
+ type,
+ nsIPermissionManager::UNKNOWN_ACTION,
+ 0,
+ nsIPermissionManager::EXPIRE_NEVER, 0, 0,
+ nsPermissionManager::eNotify,
+ nsPermissionManager::eWriteToDB);
+ }
+ // now re-import any defaults as they may now be required if we just deleted
+ // an override.
+ ImportDefaults();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermissionManager::RemovePermissionsWithAttributes(const nsAString& aPattern)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+ mozilla::OriginAttributesPattern pattern;
+ if (!pattern.Init(aPattern)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ return RemovePermissionsWithAttributes(pattern);
+}
+
+nsresult
+nsPermissionManager::RemovePermissionsWithAttributes(mozilla::OriginAttributesPattern& aPattern)
+{
+ nsCOMArray<nsIPermission> permissions;
+ for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) {
+ PermissionHashKey* entry = iter.Get();
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin,
+ getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ if (!aPattern.Matches(mozilla::BasePrincipal::Cast(principal)->OriginAttributesRef())) {
+ continue;
+ }
+
+ for (const auto& permEntry : entry->GetPermissions()) {
+ nsCOMPtr<nsIPermission> permission =
+ nsPermission::Create(principal,
+ mTypeArray.ElementAt(permEntry.mType),
+ permEntry.mPermission,
+ permEntry.mExpireType,
+ permEntry.mExpireTime);
+ if (NS_WARN_IF(!permission)) {
+ continue;
+ }
+ permissions.AppendObject(permission);
+ }
+ }
+
+ for (int32_t i = 0; i < permissions.Count(); ++i) {
+ nsCOMPtr<nsIPrincipal> principal;
+ nsAutoCString type;
+
+ permissions[i]->GetPrincipal(getter_AddRefs(principal));
+ permissions[i]->GetType(type);
+
+ AddInternal(principal,
+ type,
+ nsIPermissionManager::UNKNOWN_ACTION,
+ 0,
+ nsIPermissionManager::EXPIRE_NEVER,
+ 0,
+ 0,
+ nsPermissionManager::eNotify,
+ nsPermissionManager::eWriteToDB);
+ }
+
+ return NS_OK;
+}
+
+//*****************************************************************************
+//*** nsPermissionManager private methods
+//*****************************************************************************
+
+nsresult
+nsPermissionManager::RemoveAllFromMemory()
+{
+ mLargestID = 0;
+ mTypeArray.Clear();
+ mPermissionTable.Clear();
+
+ return NS_OK;
+}
+
+// Returns -1 on failure
+int32_t
+nsPermissionManager::GetTypeIndex(const char *aType,
+ bool aAdd)
+{
+ for (uint32_t i = 0; i < mTypeArray.Length(); ++i)
+ if (mTypeArray[i].Equals(aType))
+ return i;
+
+ if (!aAdd) {
+ // Not found, but that is ok - we were just looking.
+ return -1;
+ }
+
+ // This type was not registered before.
+ // append it to the array, without copy-constructing the string
+ nsCString *elem = mTypeArray.AppendElement();
+ if (!elem)
+ return -1;
+
+ elem->Assign(aType);
+ return mTypeArray.Length() - 1;
+}
+
+// wrapper function for mangling (host,type,perm,expireType,expireTime)
+// set into an nsIPermission.
+void
+nsPermissionManager::NotifyObserversWithPermission(nsIPrincipal* aPrincipal,
+ const nsCString &aType,
+ uint32_t aPermission,
+ uint32_t aExpireType,
+ int64_t aExpireTime,
+ const char16_t *aData)
+{
+ nsCOMPtr<nsIPermission> permission =
+ nsPermission::Create(aPrincipal, aType, aPermission,
+ aExpireType, aExpireTime);
+ if (permission)
+ NotifyObservers(permission, aData);
+}
+
+// notify observers that the permission list changed. there are four possible
+// values for aData:
+// "deleted" means a permission was deleted. aPermission is the deleted permission.
+// "added" means a permission was added. aPermission is the added permission.
+// "changed" means a permission was altered. aPermission is the new permission.
+// "cleared" means the entire permission list was cleared. aPermission is null.
+void
+nsPermissionManager::NotifyObservers(nsIPermission *aPermission,
+ const char16_t *aData)
+{
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService)
+ observerService->NotifyObservers(aPermission,
+ kPermissionChangeNotification,
+ aData);
+}
+
+nsresult
+nsPermissionManager::Read()
+{
+ ENSURE_NOT_CHILD_PROCESS;
+
+ nsresult rv;
+
+ // delete expired permissions before we read in the db
+ {
+ // this deletion has its own scope so the write lock is released when done.
+ nsCOMPtr<mozIStorageStatement> stmtDeleteExpired;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "DELETE FROM moz_perms WHERE expireType = ?1 AND expireTime <= ?2"),
+ getter_AddRefs(stmtDeleteExpired));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmtDeleteExpired->BindInt32ByIndex(0, nsIPermissionManager::EXPIRE_TIME);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stmtDeleteExpired->BindInt64ByIndex(1, PR_Now() / 1000);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool hasResult;
+ rv = stmtDeleteExpired->ExecuteStep(&hasResult);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ nsCOMPtr<mozIStorageStatement> stmt;
+ rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+ "SELECT id, origin, type, permission, expireType, expireTime, modificationTime "
+ "FROM moz_perms"), getter_AddRefs(stmt));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t id;
+ nsAutoCString origin, type;
+ uint32_t permission;
+ uint32_t expireType;
+ int64_t expireTime;
+ int64_t modificationTime;
+ bool hasResult;
+ bool readError = false;
+
+ while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+ // explicitly set our entry id counter for use in AddInternal(),
+ // and keep track of the largest id so we know where to pick up.
+ id = stmt->AsInt64(0);
+ if (id > mLargestID)
+ mLargestID = id;
+
+ rv = stmt->GetUTF8String(1, origin);
+ if (NS_FAILED(rv)) {
+ readError = true;
+ continue;
+ }
+
+ rv = stmt->GetUTF8String(2, type);
+ if (NS_FAILED(rv)) {
+ readError = true;
+ continue;
+ }
+
+ permission = stmt->AsInt32(3);
+ expireType = stmt->AsInt32(4);
+
+ // convert into int64_t values (milliseconds)
+ expireTime = stmt->AsInt64(5);
+ modificationTime = stmt->AsInt64(6);
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(origin, getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ readError = true;
+ continue;
+ }
+
+ rv = AddInternal(principal, type, permission, id, expireType, expireTime,
+ modificationTime, eDontNotify, eNoDBOperation);
+ if (NS_FAILED(rv)) {
+ readError = true;
+ continue;
+ }
+ }
+
+ if (readError) {
+ NS_ERROR("Error occured while reading the permissions database!");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+static const char kMatchTypeHost[] = "host";
+static const char kMatchTypeOrigin[] = "origin";
+
+// Import() will read a file from the profile directory and add them to the
+// database before deleting the file - ie, this is a one-shot operation that
+// will not succeed on subsequent runs as the file imported from is removed.
+nsresult
+nsPermissionManager::Import()
+{
+ nsresult rv;
+
+ nsCOMPtr<nsIFile> permissionsFile;
+ rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(permissionsFile));
+ if (NS_FAILED(rv)) return rv;
+
+ rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(HOSTPERM_FILE_NAME));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> fileInputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
+ permissionsFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = _DoImport(fileInputStream, mDBConn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // we successfully imported and wrote to the DB - delete the old file.
+ permissionsFile->Remove(false);
+ return NS_OK;
+}
+
+// ImportDefaults will read a URL with default permissions and add them to the
+// in-memory copy of permissions. The database is *not* written to.
+nsresult
+nsPermissionManager::ImportDefaults()
+{
+ nsCString defaultsURL = mozilla::Preferences::GetCString(kDefaultsUrlPrefName);
+ if (defaultsURL.IsEmpty()) { // == Don't use built-in permissions.
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIURI> defaultsURI;
+ nsresult rv = NS_NewURI(getter_AddRefs(defaultsURI), defaultsURL);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIChannel> channel;
+ rv = NS_NewChannel(getter_AddRefs(channel),
+ defaultsURI,
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = channel->Open2(getter_AddRefs(inputStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = _DoImport(inputStream, nullptr);
+ inputStream->Close();
+ return rv;
+}
+
+// _DoImport reads the specified stream and adds the parsed elements. If
+// |conn| is passed, the imported data will be written to the database, but if
+// |conn| is null the data will be added only to the in-memory copy of the
+// database.
+nsresult
+nsPermissionManager::_DoImport(nsIInputStream *inputStream, mozIStorageConnection *conn)
+{
+ ENSURE_NOT_CHILD_PROCESS;
+
+ nsresult rv;
+ // start a transaction on the storage db, to optimize insertions.
+ // transaction will automically commit on completion
+ // (note the transaction is a no-op if a null connection is passed)
+ mozStorageTransaction transaction(conn, true);
+
+ // The DB operation - we only try and write if a connection was passed.
+ DBOperationType operation = conn ? eWriteToDB : eNoDBOperation;
+ // and if no DB connection was passed we assume this is a "default" permission,
+ // so use the special ID which indicates this.
+ int64_t id = conn ? 0 : cIDPermissionIsDefault;
+
+ /* format is:
+ * matchtype \t type \t permission \t host
+ * Only "host" is supported for matchtype
+ * type is a string that identifies the type of permission (e.g. "cookie")
+ * permission is an integer between 1 and 15
+ */
+
+ // Ideally we'd do this with nsILineInputString, but this is called with an
+ // nsIInputStream that comes from a resource:// URI, which doesn't support
+ // that interface. So NS_ReadLine to the rescue...
+ nsLineBuffer<char> lineBuffer;
+ nsCString line;
+ bool isMore = true;
+ do {
+ rv = NS_ReadLine(inputStream, &lineBuffer, line, &isMore);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (line.IsEmpty() || line.First() == '#') {
+ continue;
+ }
+
+ nsTArray<nsCString> lineArray;
+
+ // Split the line at tabs
+ ParseString(line, '\t', lineArray);
+
+ if (lineArray[0].EqualsLiteral(kMatchTypeHost) &&
+ lineArray.Length() == 4) {
+ nsresult error = NS_OK;
+ uint32_t permission = lineArray[2].ToInteger(&error);
+ if (NS_FAILED(error))
+ continue;
+
+ // the import file format doesn't handle modification times, so we use
+ // 0, which AddInternal will convert to now()
+ int64_t modificationTime = 0;
+
+ UpgradeHostToOriginHostfileImport upHelper(this, operation, id);
+ error = UpgradeHostToOriginAndInsert(lineArray[3], lineArray[1], permission,
+ nsIPermissionManager::EXPIRE_NEVER, 0,
+ modificationTime, nsIScriptSecurityManager::NO_APP_ID,
+ false, &upHelper);
+ if (NS_FAILED(error)) {
+ NS_WARNING("There was a problem importing a host permission");
+ }
+ } else if (lineArray[0].EqualsLiteral(kMatchTypeOrigin) &&
+ lineArray.Length() == 4) {
+ nsresult error = NS_OK;
+ uint32_t permission = lineArray[2].ToInteger(&error);
+ if (NS_FAILED(error))
+ continue;
+
+ nsCOMPtr<nsIPrincipal> principal;
+ error = GetPrincipalFromOrigin(lineArray[3], getter_AddRefs(principal));
+ if (NS_FAILED(error)) {
+ NS_WARNING("Couldn't import an origin permission - malformed origin");
+ continue;
+ }
+
+ // the import file format doesn't handle modification times, so we use
+ // 0, which AddInternal will convert to now()
+ int64_t modificationTime = 0;
+
+ error = AddInternal(principal, lineArray[1], permission, id,
+ nsIPermissionManager::EXPIRE_NEVER, 0,
+ modificationTime,
+ eDontNotify, operation);
+ if (NS_FAILED(error)) {
+ NS_WARNING("There was a problem importing an origin permission");
+ }
+ }
+
+ } while (isMore);
+
+ return NS_OK;
+}
+
+void
+nsPermissionManager::UpdateDB(OperationType aOp,
+ mozIStorageAsyncStatement* aStmt,
+ int64_t aID,
+ const nsACString &aOrigin,
+ const nsACString &aType,
+ uint32_t aPermission,
+ uint32_t aExpireType,
+ int64_t aExpireTime,
+ int64_t aModificationTime)
+{
+ ENSURE_NOT_CHILD_PROCESS_NORET;
+
+ nsresult rv;
+
+ // no statement is ok - just means we don't have a profile
+ if (!aStmt)
+ return;
+
+ switch (aOp) {
+ case eOperationAdding:
+ {
+ rv = aStmt->BindInt64ByIndex(0, aID);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindUTF8StringByIndex(1, aOrigin);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindUTF8StringByIndex(2, aType);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt32ByIndex(3, aPermission);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt32ByIndex(4, aExpireType);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt64ByIndex(5, aExpireTime);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt64ByIndex(6, aModificationTime);
+ break;
+ }
+
+ case eOperationRemoving:
+ {
+ rv = aStmt->BindInt64ByIndex(0, aID);
+ break;
+ }
+
+ case eOperationChanging:
+ {
+ rv = aStmt->BindInt64ByIndex(0, aID);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt32ByIndex(1, aPermission);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt32ByIndex(2, aExpireType);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt64ByIndex(3, aExpireTime);
+ if (NS_FAILED(rv)) break;
+
+ rv = aStmt->BindInt64ByIndex(4, aModificationTime);
+ break;
+ }
+
+ default:
+ {
+ NS_NOTREACHED("need a valid operation in UpdateDB()!");
+ rv = NS_ERROR_UNEXPECTED;
+ break;
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ NS_WARNING("db change failed!");
+ return;
+ }
+
+ nsCOMPtr<mozIStoragePendingStatement> pending;
+ rv = aStmt->ExecuteAsync(nullptr, getter_AddRefs(pending));
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+}
+
+NS_IMETHODIMP
+nsPermissionManager::UpdateExpireTime(nsIPrincipal* aPrincipal,
+ const char* aType,
+ bool aExactHostMatch,
+ uint64_t aSessionExpireTime,
+ uint64_t aPersistentExpireTime)
+{
+ NS_ENSURE_ARG_POINTER(aPrincipal);
+ NS_ENSURE_ARG_POINTER(aType);
+
+ uint64_t nowms = PR_Now() / 1000;
+ if (aSessionExpireTime < nowms || aPersistentExpireTime < nowms) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (nsContentUtils::IsSystemPrincipal(aPrincipal)) {
+ return NS_OK;
+ }
+
+ // Setting the expire time of an nsEP is non-sensical.
+ if (IsExpandedPrincipal(aPrincipal)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ int32_t typeIndex = GetTypeIndex(aType, false);
+ // If type == -1, the type isn't known,
+ // so just return NS_OK
+ if (typeIndex == -1) return NS_OK;
+
+ PermissionHashKey* entry = GetPermissionHashKey(aPrincipal, typeIndex, aExactHostMatch);
+ if (!entry) {
+ return NS_OK;
+ }
+
+ int32_t idx = entry->GetPermissionIndex(typeIndex);
+ if (-1 == idx) {
+ return NS_OK;
+ }
+
+ PermissionEntry& perm = entry->GetPermissions()[idx];
+ if (perm.mExpireType == EXPIRE_TIME) {
+ perm.mExpireTime = aPersistentExpireTime;
+ } else if (perm.mExpireType == EXPIRE_SESSION && perm.mExpireTime != 0) {
+ perm.mExpireTime = aSessionExpireTime;
+ }
+ return NS_OK;
+}
+
+nsresult
+nsPermissionManager::FetchPermissions() {
+ MOZ_ASSERT(IsChildProcess(), "FetchPermissions can only be invoked in child process");
+ // Get the permissions from the parent process
+ InfallibleTArray<IPC::Permission> perms;
+ ChildProcess()->SendReadPermissions(&perms);
+
+ for (uint32_t i = 0; i < perms.Length(); i++) {
+ const IPC::Permission &perm = perms[i];
+
+ nsCOMPtr<nsIPrincipal> principal;
+ nsresult rv = GetPrincipalFromOrigin(perm.origin, getter_AddRefs(principal));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // The child process doesn't care about modification times - it neither
+ // reads nor writes, nor removes them based on the date - so 0 (which
+ // will end up as now()) is fine.
+ uint64_t modificationTime = 0;
+ AddInternal(principal, perm.type, perm.capability, 0, perm.expireType,
+ perm.expireTime, modificationTime, eNotify, eNoDBOperation,
+ true /* ignoreSessionPermissions */);
+ }
+ return NS_OK;
+}