Source code

Revision control

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* 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/AbstractThread.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ContentPrincipal.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/dom/ContentParent.h"
#include "mozilla/ExpandedPrincipal.h"
#include "mozilla/net/NeckoMessageUtils.h"
#include "mozilla/Permission.h"
#include "mozilla/PermissionManager.h"
#include "mozilla/Preferences.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_permissions.h"
#include "mozilla/Telemetry.h"
#include "mozIStorageService.h"
#include "mozIStorageConnection.h"
#include "mozIStorageStatement.h"
#include "mozStorageCID.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsComponentManagerUtils.h"
#include "nsContentUtils.h"
#include "nsCRT.h"
#include "nsEffectiveTLDService.h"
#include "nsIConsoleService.h"
#include "nsIUserIdleService.h"
#include "nsIInputStream.h"
#include "nsINavHistoryService.h"
#include "nsIObserverService.h"
#include "nsIPrefBranch.h"
#include "nsIPrincipal.h"
#include "nsIURIMutator.h"
#include "nsIWritablePropertyBag2.h"
#include "nsReadLine.h"
#include "nsTHashSet.h"
#include "nsToolkitCompsCID.h"
using namespace mozilla::dom;
namespace mozilla {
#define PERMISSIONS_FILE_NAME "permissions.sqlite"
#define HOSTS_SCHEMA_VERSION 12
// 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.
constexpr char kDefaultsUrlPrefName[] = "permissions.manager.defaultsUrl";
constexpr char kPermissionChangeNotification[] = PERM_CHANGE_NOTIFICATION;
// A special value for a permission ID that indicates the ID was loaded as
// a default value. These will never be written to the database, but may
// be overridden with an explicit permission (including UNKNOWN_ACTION)
constexpr int64_t cIDPermissionIsDefault = -1;
static StaticRefPtr<PermissionManager> gPermissionManager;
#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_(;)
#define EXPIRY_NOW PR_Now() / 1000
////////////////////////////////////////////////////////////////////////////////
namespace {
bool IsChildProcess() { return XRE_IsContentProcess(); }
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());
}
// NOTE: an empty string can be passed as aType - if it is this function will
// return "false" unconditionally.
bool HasDefaultPref(const nsACString& aType) {
// A list of permissions that can have a fallback default permission
// set under the permissions.default.* pref.
static const nsLiteralCString kPermissionsWithDefaults[] = {
"camera"_ns, "microphone"_ns, "geo"_ns, "desktop-notification"_ns,
"shortcuts"_ns};
if (!aType.IsEmpty()) {
for (const auto& perm : kPermissionsWithDefaults) {
if (perm.Equals(aType)) {
return true;
}
}
}
return false;
}
// These permissions are special permissions which must be transmitted to the
// content process before documents with their principals have loaded within
// that process.
//
// Permissions which are in this list are considered to have a "" permission
// key, even if their principal would not normally have that key.
static const nsLiteralCString kPreloadPermissions[] = {
// This permission is preloaded to support properly blocking service worker
// interception when a user has disabled storage for a specific site. Once
// service worker interception moves to the parent process this should be
// removed. See bug 1428130.
"cookie"_ns};
// NOTE: nullptr can be passed as aType - if it is this function will return
// "false" unconditionally.
bool IsPreloadPermission(const nsACString& aType) {
if (!aType.IsEmpty()) {
for (const auto& perm : kPreloadPermissions) {
if (perm.Equals(aType)) {
return true;
}
}
}
return false;
}
// Array of permission types which should not be isolated by origin attributes,
// for user context and private browsing.
// Keep this array in sync with 'STRIPPED_PERMS' in
// 'test_permmanager_oa_strip.js'
// Currently only preloaded permissions are supported.
// This is because perms are sent to the content process in bulk by perm key.
// Non-preloaded, but OA stripped permissions would not be accessible by sites
// in private browsing / non-default user context.
static constexpr std::array<nsLiteralCString, 1> kStripOAPermissions = {
{"cookie"_ns}};
bool IsOAForceStripPermission(const nsACString& aType) {
if (aType.IsEmpty()) {
return false;
}
for (const auto& perm : kStripOAPermissions) {
if (perm.Equals(aType)) {
return true;
}
}
return false;
}
// Array of permission prefixes which should be isolated only by site.
// These site-scoped permissions are stored under their site's principal.
// GetAllForPrincipal also needs to look for these especially.
static constexpr std::array<nsLiteralCString, 1> kSiteScopedPermissions = {
{"3rdPartyStorage^"_ns}};
bool IsSiteScopedPermission(const nsACString& aType) {
if (aType.IsEmpty()) {
return false;
}
for (const auto& perm : kSiteScopedPermissions) {
if (aType.Length() >= perm.Length() &&
Substring(aType, 0, perm.Length()) == perm) {
return true;
}
}
return false;
}
void OriginAppendOASuffix(OriginAttributes aOriginAttributes,
bool aForceStripOA, nsACString& aOrigin) {
PermissionManager::MaybeStripOriginAttributes(aForceStripOA,
aOriginAttributes);
nsAutoCString oaSuffix;
aOriginAttributes.CreateSuffix(oaSuffix);
aOrigin.Append(oaSuffix);
}
nsresult GetOriginFromPrincipal(nsIPrincipal* aPrincipal, bool aForceStripOA,
nsACString& aOrigin) {
nsresult rv = aPrincipal->GetOriginNoSuffix(aOrigin);
// The principal may belong to the about:blank content viewer, so this can be
// expected to fail.
if (NS_FAILED(rv)) {
return rv;
}
nsAutoCString suffix;
rv = aPrincipal->GetOriginSuffix(suffix);
NS_ENSURE_SUCCESS(rv, rv);
OriginAttributes attrs;
NS_ENSURE_TRUE(attrs.PopulateFromSuffix(suffix), NS_ERROR_FAILURE);
OriginAppendOASuffix(attrs, aForceStripOA, aOrigin);
return NS_OK;
}
// Returns the site of the principal, including OA, given a principal.
nsresult GetSiteFromPrincipal(nsIPrincipal* aPrincipal, bool aForceStripOA,
nsACString& aSite) {
nsresult rv = aPrincipal->GetSiteOriginNoSuffix(aSite);
// The principal may belong to the about:blank content viewer, so this can be
// expected to fail.
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString suffix;
rv = aPrincipal->GetOriginSuffix(suffix);
NS_ENSURE_SUCCESS(rv, rv);
OriginAttributes attrs;
NS_ENSURE_TRUE(attrs.PopulateFromSuffix(suffix), NS_ERROR_FAILURE);
OriginAppendOASuffix(attrs, aForceStripOA, aSite);
return NS_OK;
}
nsresult GetOriginFromURIAndOA(nsIURI* aURI,
const OriginAttributes* aOriginAttributes,
bool aForceStripOA, nsACString& aOrigin) {
nsAutoCString origin(aOrigin);
nsresult rv = ContentPrincipal::GenerateOriginNoSuffixFromURI(aURI, origin);
NS_ENSURE_SUCCESS(rv, rv);
OriginAppendOASuffix(*aOriginAttributes, aForceStripOA, origin);
aOrigin = origin;
return NS_OK;
}
nsresult GetPrincipalFromOrigin(const nsACString& aOrigin, bool aForceStripOA,
nsIPrincipal** aPrincipal) {
nsAutoCString originNoSuffix;
OriginAttributes attrs;
if (!attrs.PopulateFromOrigin(aOrigin, originNoSuffix)) {
return NS_ERROR_FAILURE;
}
PermissionManager::MaybeStripOriginAttributes(aForceStripOA, attrs);
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), originNoSuffix);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIPrincipal> principal =
BasePrincipal::CreateContentPrincipal(uri, attrs);
principal.forget(aPrincipal);
return NS_OK;
}
nsresult GetPrincipal(nsIURI* aURI, bool aIsInIsolatedMozBrowserElement,
nsIPrincipal** aPrincipal) {
OriginAttributes attrs(aIsInIsolatedMozBrowserElement);
nsCOMPtr<nsIPrincipal> principal =
BasePrincipal::CreateContentPrincipal(aURI, attrs);
NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
principal.forget(aPrincipal);
return NS_OK;
}
nsresult GetPrincipal(nsIURI* aURI, nsIPrincipal** aPrincipal) {
OriginAttributes attrs;
nsCOMPtr<nsIPrincipal> principal =
BasePrincipal::CreateContentPrincipal(aURI, attrs);
NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
principal.forget(aPrincipal);
return NS_OK;
}
nsCString GetNextSubDomainForHost(const nsACString& aHost) {
nsCString subDomain;
nsresult rv =
nsEffectiveTLDService::GetInstance()->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 ""_ns;
}
return subDomain;
}
// This function produces a nsIURI which is identical to the current
// nsIURI, except that it has one less subdomain segment. It returns
// `nullptr` if there are no more segments to remove.
already_AddRefed<nsIURI> GetNextSubDomainURI(nsIURI* aURI) {
nsAutoCString host;
nsresult rv = aURI->GetHost(host);
if (NS_FAILED(rv)) {
return nullptr;
}
nsCString domain = GetNextSubDomainForHost(host);
if (domain.IsEmpty()) {
return nullptr;
}
nsCOMPtr<nsIURI> uri;
rv = NS_MutateURI(aURI).SetHost(domain).Finalize(uri);
if (NS_FAILED(rv) || !uri) {
return nullptr;
}
return uri.forget();
}
nsresult UpgradeHostToOriginAndInsert(
const nsACString& aHost, const nsCString& aType, uint32_t aPermission,
uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime,
bool aIsInIsolatedMozBrowserElement,
std::function<nsresult(const nsACString& aOrigin, const nsCString& aType,
uint32_t aPermission, uint32_t aExpireType,
int64_t aExpireTime, int64_t aModificationTime)>&&
aCallback) {
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
if (uri->SchemeIs("moz-nullprincipal")) {
NS_WARNING("A moz-nullprincipal: permission is being discarded.");
return NS_OK;
}
nsCOMPtr<nsIPrincipal> principal;
rv = GetPrincipal(uri, aIsInIsolatedMozBrowserElement,
getter_AddRefs(principal));
NS_ENSURE_SUCCESS(rv, rv);
nsAutoCString origin;
rv = GetOriginFromPrincipal(principal, IsOAForceStripPermission(aType),
origin);
NS_ENSURE_SUCCESS(rv, rv);
aCallback(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;
rv = nsEffectiveTLDService::GetInstance()->GetBaseDomainFromHost(aHost, 0,
eTLD1);
if (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);
nsTHashSet<nsCString> 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 = NS_MutateURI(uri).SetHost(aHost).Finalize(uri);
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, aIsInIsolatedMozBrowserElement,
getter_AddRefs(principal));
if (NS_WARN_IF(NS_FAILED(rv))) continue;
nsAutoCString origin;
rv = GetOriginFromPrincipal(principal, IsOAForceStripPermission(aType),
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 = aCallback(origin, aType, aPermission, aExpireType, aExpireTime,
aModificationTime);
NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "Insert failed");
insertedOrigins.Insert(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 likelihood 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.AssignLiteral("[");
hostSegment.Append(aHost);
hostSegment.AppendLiteral("]");
} else {
hostSegment.Assign(aHost);
}
// http:// URI default
rv = NS_NewURI(getter_AddRefs(uri), "http://"_ns + hostSegment);
NS_ENSURE_SUCCESS(rv, rv);
rv = GetPrincipal(uri, aIsInIsolatedMozBrowserElement,
getter_AddRefs(principal));
NS_ENSURE_SUCCESS(rv, rv);
rv = GetOriginFromPrincipal(principal, IsOAForceStripPermission(aType),
origin);
NS_ENSURE_SUCCESS(rv, rv);
aCallback(origin, aType, aPermission, aExpireType, aExpireTime,
aModificationTime);
// https:// URI default
rv = NS_NewURI(getter_AddRefs(uri), "https://"_ns + hostSegment);
NS_ENSURE_SUCCESS(rv, rv);
rv = GetPrincipal(uri, aIsInIsolatedMozBrowserElement,
getter_AddRefs(principal));
NS_ENSURE_SUCCESS(rv, rv);
rv = GetOriginFromPrincipal(principal, IsOAForceStripPermission(aType),
origin);
NS_ENSURE_SUCCESS(rv, rv);
aCallback(origin, aType, aPermission, aExpireType, aExpireTime,
aModificationTime);
}
return NS_OK;
}
bool IsExpandedPrincipal(nsIPrincipal* aPrincipal) {
nsCOMPtr<nsIExpandedPrincipal> ep = do_QueryInterface(aPrincipal);
return !!ep;
}
// We only want to persist permissions which don't have session or policy
// expiration.
bool IsPersistentExpire(uint32_t aExpire, const nsACString& aType) {
bool res = (aExpire != nsIPermissionManager::EXPIRE_SESSION &&
aExpire != nsIPermissionManager::EXPIRE_POLICY);
return res;
}
} // namespace
////////////////////////////////////////////////////////////////////////////////
PermissionManager::PermissionKey*
PermissionManager::PermissionKey::CreateFromPrincipal(nsIPrincipal* aPrincipal,
bool aForceStripOA,
bool scopeToSite,
nsresult& aResult) {
nsAutoCString keyString;
if (scopeToSite) {
aResult = GetSiteFromPrincipal(aPrincipal, aForceStripOA, keyString);
} else {
aResult = GetOriginFromPrincipal(aPrincipal, aForceStripOA, keyString);
}
if (NS_WARN_IF(NS_FAILED(aResult))) {
return nullptr;
}
return new PermissionKey(keyString);
}
PermissionManager::PermissionKey*
PermissionManager::PermissionKey::CreateFromURIAndOriginAttributes(
nsIURI* aURI, const OriginAttributes* aOriginAttributes, bool aForceStripOA,
nsresult& aResult) {
nsAutoCString origin;
aResult =
GetOriginFromURIAndOA(aURI, aOriginAttributes, aForceStripOA, origin);
if (NS_WARN_IF(NS_FAILED(aResult))) {
return nullptr;
}
return new PermissionKey(origin);
}
PermissionManager::PermissionKey*
PermissionManager::PermissionKey::CreateFromURI(nsIURI* aURI,
nsresult& aResult) {
nsAutoCString origin;
aResult = ContentPrincipal::GenerateOriginNoSuffixFromURI(aURI, origin);
if (NS_WARN_IF(NS_FAILED(aResult))) {
return nullptr;
}
return new PermissionKey(origin);
}
/* static */
void PermissionManager::Startup() {
nsCOMPtr<nsIPermissionManager> permManager =
do_GetService("@mozilla.org/permissionmanager;1");
}
////////////////////////////////////////////////////////////////////////////////
// PermissionManager Implementation
NS_IMPL_ISUPPORTS(PermissionManager, nsIPermissionManager, nsIObserver,
nsISupportsWeakReference, nsIAsyncShutdownBlocker)
PermissionManager::PermissionManager()
: mMonitor("PermissionManager::mMonitor"),
mState(eInitializing),
mMemoryOnlyDB(false),
mBlockerAdded(false),
mLargestID(0) {}
PermissionManager::~PermissionManager() {
// NOTE: Make sure to reject each of the promises in mPermissionKeyPromiseMap
// before destroying.
for (const auto& promise : mPermissionKeyPromiseMap.Values()) {
if (promise) {
promise->Reject(NS_ERROR_FAILURE, __func__);
}
}
mPermissionKeyPromiseMap.Clear();
if (mThread) {
mThread->Shutdown();
mThread = nullptr;
}
}
// static
already_AddRefed<nsIPermissionManager> PermissionManager::GetXPCOMSingleton() {
if (gPermissionManager) {
return do_AddRef(gPermissionManager);
}
// Create a new singleton PermissionManager.
// 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.
auto permManager = MakeRefPtr<PermissionManager>();
if (NS_SUCCEEDED(permManager->Init())) {
gPermissionManager = permManager.get();
return permManager.forget();
}
return nullptr;
}
// static
PermissionManager* PermissionManager::GetInstance() {
if (!gPermissionManager) {
// Hand off the creation of the permission manager to GetXPCOMSingleton.
nsCOMPtr<nsIPermissionManager> permManager = GetXPCOMSingleton();
}
return gPermissionManager;
}
nsresult PermissionManager::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 = Preferences::GetBool("permissions.memory_only", false);
nsresult rv;
nsCOMPtr<nsIPrefService> prefService =
do_GetService(NS_PREFSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = prefService->GetBranch("permissions.default.",
getter_AddRefs(mDefaultPrefBranch));
NS_ENSURE_SUCCESS(rv, rv);
if (IsChildProcess()) {
// Stop here; we don't need the DB in the child process. Instead we will be
// sent permissions as we need them by our parent process.
mState = eReady;
// We use ClearOnShutdown on the content process only because on the parent
// process we need to block the shutdown for the final closeDB() call.
ClearOnShutdown(&gPermissionManager);
return NS_OK;
}
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
if (observerService) {
observerService->AddObserver(this, "profile-before-change", true);
observerService->AddObserver(this, "profile-do-change", true);
observerService->AddObserver(this, "testonly-reload-permissions-from-disk",
true);
}
if (XRE_IsParentProcess()) {
nsCOMPtr<nsIAsyncShutdownClient> asc = GetShutdownPhase();
if (asc) {
nsAutoString blockerName;
MOZ_ALWAYS_SUCCEEDS(GetName(blockerName));
// This method can fail during some xpcshell-tests.
nsresult rv =
asc->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__),
__LINE__, blockerName);
Unused << NS_WARN_IF(NS_FAILED(rv));
if (NS_SUCCEEDED(rv)) {
mBlockerAdded = true;
}
}
if (!mBlockerAdded) {
ClearOnShutdown(&gPermissionManager);
}
}
AddIdleDailyMaintenanceJob();
MOZ_ASSERT(!mThread);
NS_ENSURE_SUCCESS(NS_NewNamedThread("Permission", getter_AddRefs(mThread)),
NS_ERROR_FAILURE);
PRThread* prThread;
MOZ_ALWAYS_SUCCEEDS(mThread->GetPRThread(&prThread));
MOZ_ASSERT(prThread);
mThreadBoundData.Transfer(prThread);
InitDB(false);
return NS_OK;
}
nsresult PermissionManager::OpenDatabase(nsIFile* aPermissionsFile) {
MOZ_ASSERT(!NS_IsMainThread());
auto data = mThreadBoundData.Access();
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(kMozStorageMemoryStorageKey, VoidCString(),
getter_AddRefs(data->mDBConn));
} else {
rv = storage->OpenDatabase(aPermissionsFile, getter_AddRefs(data->mDBConn));
}
return rv;
}
void PermissionManager::InitDB(bool aRemoveFile) {
mState = eInitializing;
{
MonitorAutoLock lock(mMonitor);
mReadEntries.Clear();
}
auto readyIfFailed = MakeScopeExit([&]() {
// 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?
mState = eReady;
});
if (!mPermissionsFile) {
nsresult rv = NS_GetSpecialDirectory(NS_APP_PERMISSION_PARENT_DIR,
getter_AddRefs(mPermissionsFile));
if (NS_FAILED(rv)) {
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
getter_AddRefs(mPermissionsFile));
if (NS_FAILED(rv)) {
return;
}
}
rv =
mPermissionsFile->AppendNative(nsLiteralCString(PERMISSIONS_FILE_NAME));
NS_ENSURE_SUCCESS_VOID(rv);
}
nsCOMPtr<nsIInputStream> defaultsInputStream = GetDefaultsInputStream();
RefPtr<PermissionManager> self = this;
mThread->Dispatch(NS_NewRunnableFunction(
"PermissionManager::InitDB", [self, aRemoveFile, defaultsInputStream] {
nsresult rv = self->TryInitDB(aRemoveFile, defaultsInputStream);
Unused << NS_WARN_IF(NS_FAILED(rv));
// This extra runnable calls EnsureReadCompleted to finialize the
// initialization. If there is something blocked by the monitor, it will
// be NOP.
NS_DispatchToMainThread(
NS_NewRunnableFunction("PermissionManager::InitDB-MainThread",
[self] { self->EnsureReadCompleted(); }));
self->mMonitor.Notify();
}));
readyIfFailed.release();
}
nsresult PermissionManager::TryInitDB(bool aRemoveFile,
nsIInputStream* aDefaultsInputStream) {
MOZ_ASSERT(!NS_IsMainThread());
MonitorAutoLock lock(mMonitor);
auto raii = MakeScopeExit([&]() {
if (aDefaultsInputStream) {
aDefaultsInputStream->Close();
}
mState = eDBInitialized;
});
auto data = mThreadBoundData.Access();
auto raiiFailure = MakeScopeExit([&]() {
if (data->mDBConn) {
DebugOnly<nsresult> rv = data->mDBConn->Close();
MOZ_ASSERT(NS_SUCCEEDED(rv));
data->mDBConn = nullptr;
}
});
nsresult rv;
if (aRemoveFile) {
bool exists = false;
rv = mPermissionsFile->Exists(&exists);
NS_ENSURE_SUCCESS(rv, rv);
if (exists) {
rv = mPermissionsFile->Remove(false);
NS_ENSURE_SUCCESS(rv, rv);
}
}
rv = OpenDatabase(mPermissionsFile);
if (rv == NS_ERROR_FILE_CORRUPTED) {
LogToConsole(u"permissions.sqlite is corrupted! Try again!"_ns);
// Add telemetry probe
Telemetry::Accumulate(Telemetry::PERMISSIONS_SQL_CORRUPTED, 1);
// delete corrupted permissions.sqlite and try again
rv = mPermissionsFile->Remove(false);
NS_ENSURE_SUCCESS(rv, rv);
LogToConsole(u"Corrupted permissions.sqlite has been removed."_ns);
rv = OpenDatabase(mPermissionsFile);
NS_ENSURE_SUCCESS(rv, rv);
LogToConsole(u"OpenDatabase to permissions.sqlite is successful!"_ns);
}
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
bool ready;
data->mDBConn->GetConnectionReady(&ready);
if (!ready) {
LogToConsole(nsLiteralString(
u"Fail to get connection to permissions.sqlite! Try again!"));
// delete and try again
rv = mPermissionsFile->Remove(false);
NS_ENSURE_SUCCESS(rv, rv);
LogToConsole(u"Defective permissions.sqlite has been removed."_ns);
// Add telemetry probe
Telemetry::Accumulate(Telemetry::DEFECTIVE_PERMISSIONS_SQL_REMOVED, 1);
rv = OpenDatabase(mPermissionsFile);
NS_ENSURE_SUCCESS(rv, rv);
LogToConsole(u"OpenDatabase to permissions.sqlite is successful!"_ns);
data->mDBConn->GetConnectionReady(&ready);
if (!ready) return NS_ERROR_UNEXPECTED;
}
bool tableExists = false;
data->mDBConn->TableExists("moz_perms"_ns, &tableExists);
if (!tableExists) {
data->mDBConn->TableExists("moz_hosts"_ns, &tableExists);
}
if (!tableExists) {
rv = CreateTable();
NS_ENSURE_SUCCESS(rv, rv);
} else {
// table already exists; check the schema version before reading
int32_t dbSchemaVersion;
rv = data->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 = data->mDBConn->ExecuteSimpleSQL(
"ALTER TABLE moz_hosts ADD expireType INTEGER"_ns);
NS_ENSURE_SUCCESS(rv, rv);
rv = data->mDBConn->ExecuteSimpleSQL(
"ALTER TABLE moz_hosts ADD expireTime INTEGER"_ns);
NS_ENSURE_SUCCESS(rv, rv);
}
// fall through to the next upgrade
[[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 = data->mDBConn->ExecuteSimpleSQL(
"ALTER TABLE moz_hosts ADD appId INTEGER"_ns);
NS_ENSURE_SUCCESS(rv, rv);
rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
"ALTER TABLE moz_hosts ADD isInBrowserElement INTEGER"));
NS_ENSURE_SUCCESS(rv, rv);
rv = data->mDBConn->SetSchemaVersion(3);
NS_ENSURE_SUCCESS(rv, rv);
}
// fall through to the next upgrade
[[fallthrough]];
// Version 3->4 is the creation of the modificationTime field.
case 3: {
rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
"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 = data->mDBConn->SetSchemaVersion(4);
NS_ENSURE_SUCCESS(rv, rv);
}
// fall through to the next upgrade
[[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;
data->mDBConn->TableExists("moz_perms"_ns, &permsTableExists);
if (!permsTableExists) {
// Move the upgraded database to moz_perms
rv = data->mDBConn->ExecuteSimpleSQL(
"ALTER TABLE moz_hosts RENAME TO moz_perms"_ns);
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 = data->mDBConn->ExecuteSimpleSQL("DROP TABLE moz_hosts"_ns);
NS_ENSURE_SUCCESS(rv, rv);
}
#ifdef DEBUG
// The moz_hosts table shouldn't exist anymore
bool hostsTableExists = false;
data->mDBConn->TableExists("moz_hosts"_ns, &hostsTableExists);
MOZ_ASSERT(!hostsTableExists);
#endif
// Rename moz_hosts_v4 back to it's original location, if it
// exists
bool v4TableExists = false;
data->mDBConn->TableExists("moz_hosts_v4"_ns, &v4TableExists);
if (v4TableExists) {
rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
"ALTER TABLE moz_hosts_v4 RENAME TO moz_hosts"));
NS_ENSURE_SUCCESS(rv, rv);
}
rv = data->mDBConn->SetSchemaVersion(6);
NS_ENSURE_SUCCESS(rv, rv);
}
// fall through to the next upgrade
[[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;
data->mDBConn->TableExists("moz_hosts"_ns, &hostsTableExists);
if (hostsTableExists) {
// 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 = data->mDBConn->BeginTransaction();
NS_ENSURE_SUCCESS(rv, rv);
bool tableExists = false;
data->mDBConn->TableExists("moz_hosts_new"_ns, &tableExists);
if (tableExists) {
NS_WARNING(
"The temporary database moz_hosts_new already exists, "
"dropping "
"it.");
rv = data->mDBConn->ExecuteSimpleSQL("DROP TABLE moz_hosts_new"_ns);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = data->mDBConn->ExecuteSimpleSQL(
nsLiteralCString("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 = data->mDBConn->CreateStatement(
nsLiteralCString(
"SELECT host, type, permission, expireType, "
"expireTime, "
"modificationTime, isInBrowserElement FROM moz_hosts"),
getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
int64_t id = 0;
bool hasResult;
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
MigrationEntry entry;
// Read in the old row
rv = stmt->GetUTF8String(0, entry.mHost);
if (NS_WARN_IF(NS_FAILED(rv))) {
continue;
}
rv = stmt->GetUTF8String(1, entry.mType);
if (NS_WARN_IF(NS_FAILED(rv))) {
continue;
}
entry.mId = id++;
entry.mPermission = stmt->AsInt32(2);
entry.mExpireType = stmt->AsInt32(3);
entry.mExpireTime = stmt->AsInt64(4);
entry.mModificationTime = stmt->AsInt64(5);
entry.mIsInBrowserElement = static_cast<bool>(stmt->AsInt32(6));
mMigrationEntries.AppendElement(entry);
}
// We don't drop the moz_hosts table such that it is available
// 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 = data->mDBConn->ExecuteSimpleSQL(
nsLiteralCString("CREATE TABLE moz_hosts_is_backup (dummy "
"INTEGER PRIMARY KEY)"));
NS_ENSURE_SUCCESS(rv, rv);
bool permsTableExists = false;
data->mDBConn->TableExists("moz_perms"_ns, &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 = data->mDBConn->CreateStatement(
"SELECT COUNT(*) FROM moz_perms"_ns, getter_AddRefs(countStmt));
bool hasResult = false;
if (NS_FAILED(rv) ||
NS_FAILED(countStmt->ExecuteStep(&hasResult)) || !hasResult) {
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 = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
"ALTER TABLE moz_perms RENAME TO moz_perms_v6"));
NS_ENSURE_SUCCESS(rv, rv);
}
rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
"ALTER TABLE moz_hosts_new RENAME TO moz_perms"));
NS_ENSURE_SUCCESS(rv, rv);
rv = data->mDBConn->CommitTransaction();
NS_ENSURE_SUCCESS(rv, rv);
} else {
// We don't have a moz_hosts table, so we create one for
// downgrading purposes. This table is empty.
rv = data->mDBConn->ExecuteSimpleSQL(
nsLiteralCString("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;
data->mDBConn->TableExists("moz_hosts"_ns, &hostsTableExists);
data->mDBConn->TableExists("moz_perms"_ns, &permsTableExists);
MOZ_ASSERT(hostsTableExists && permsTableExists);
}
#endif
rv = data->mDBConn->SetSchemaVersion(7);
NS_ENSURE_SUCCESS(rv, rv);
}
// fall through to the next upgrade
[[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;
data->mDBConn->TableExists("moz_hosts_is_backup"_ns,
&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<mozIStorageStatement> stmt;
rv = data->mDBConn->CreateStatement(
nsLiteralCString(
"SELECT host, type, permission, expireType, "
"expireTime, "
"modificationTime, isInBrowserElement FROM moz_hosts"),
getter_AddRefs(stmt));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageStatement> idStmt;
rv = data->mDBConn->CreateStatement(
"SELECT MAX(id) FROM moz_hosts"_ns, 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;
}
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
MigrationEntry entry;
// Read in the old row
rv = stmt->GetUTF8String(0, entry.mHost);
if (NS_WARN_IF(NS_FAILED(rv))) {
continue;
}
nsAutoCString eTLD1;
rv = nsEffectiveTLDService::GetInstance()->GetBaseDomainFromHost(
entry.mHost, 0, eTLD1);
if (NS_SUCCEEDED(rv)) {
// We only care about entries which the tldService can't
// handle
continue;
}
rv = stmt->GetUTF8String(1, entry.mType);
if (NS_WARN_IF(NS_FAILED(rv))) {
continue;
}
entry.mId = id++;
entry.mPermission = stmt->AsInt32(2);
entry.mExpireType = stmt->AsInt32(3);
entry.mExpireTime = stmt->AsInt64(4);
entry.mModificationTime = stmt->AsInt64(5);
entry.mIsInBrowserElement = static_cast<bool>(stmt->AsInt32(6));
mMigrationEntries.AppendElement(entry);
}
}
// Even if we didn't perform the migration, we want to bump the
// schema version to 8.
rv = data->mDBConn->SetSchemaVersion(8);
NS_ENSURE_SUCCESS(rv, rv);
}
// fall through to the next upgrade
[[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;
data->mDBConn->TableExists("moz_hosts_is_backup"_ns,
&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 = data->mDBConn->ExecuteSimpleSQL("DELETE FROM moz_hosts"_ns);
NS_ENSURE_SUCCESS(rv, rv);
// The table is no longer a backup, so get rid of it.
rv = data->mDBConn->ExecuteSimpleSQL(
"DROP TABLE moz_hosts_is_backup"_ns);
NS_ENSURE_SUCCESS(rv, rv);
}
rv = data->mDBConn->SetSchemaVersion(9);
NS_ENSURE_SUCCESS(rv, rv);
}
// fall through to the next upgrade
[[fallthrough]];
case 9: {
rv = data->mDBConn->SetSchemaVersion(10);
NS_ENSURE_SUCCESS(rv, rv);
}
// fall through to the next upgrade
[[fallthrough]];
case 10: {
// Filter out the rows with storage access API permissions with a
// granted origin, and remove the granted origin part from the
// permission type.
rv = data->mDBConn->ExecuteSimpleSQL(nsLiteralCString(
"UPDATE moz_perms "
"SET type=SUBSTR(type, 0, INSTR(SUBSTR(type, INSTR(type, "
"'^') + "
"1), '^') + INSTR(type, '^')) "
"WHERE INSTR(SUBSTR(type, INSTR(type, '^') + 1), '^') AND "
"SUBSTR(type, 0, 18) == \"storageAccessAPI^\";"));
NS_ENSURE_SUCCESS(rv, rv);
rv = data->mDBConn->SetSchemaVersion(11);
NS_ENSURE_SUCCESS(rv, rv);
}
// fall through to the next upgrade
[[fallthrough]];
case 11: {
// Migrate 3rdPartyStorage keys to a site scope
rv = data->mDBConn->BeginTransaction();
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageStatement> updateStmt;
rv = data->mDBConn->CreateStatement(
nsLiteralCString("UPDATE moz_perms SET origin = ?2 WHERE id = ?1"),
getter_AddRefs(updateStmt));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageStatement> deleteStmt;
rv = data->mDBConn->CreateStatement(
nsLiteralCString("DELETE FROM moz_perms WHERE id = ?1"),
getter_AddRefs(deleteStmt));
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<mozIStorageStatement> selectStmt;
rv = data->mDBConn->CreateStatement(
nsLiteralCString("SELECT id, origin, type FROM moz_perms WHERE "
" SUBSTR(type, 0, 17) == \"3rdPartyStorage^\""),
getter_AddRefs(selectStmt));
NS_ENSURE_SUCCESS(rv, rv);
nsTHashSet<nsCStringHashKey> deduplicationSet;
bool hasResult;
while (NS_SUCCEEDED(selectStmt->ExecuteStep(&hasResult)) && hasResult) {
int64_t id;
rv = selectStmt->GetInt64(0, &id);
NS_ENSURE_SUCCESS(rv, rv);
nsCString origin;
rv = selectStmt->GetUTF8String(1, origin);
NS_ENSURE_SUCCESS(rv, rv);
nsCString type;
rv = selectStmt->GetUTF8String(2, type);
NS_ENSURE_SUCCESS(rv, rv);
nsCOMPtr<nsIURI> uri;
rv = NS_NewURI(getter_AddRefs(uri), origin);
if (NS_FAILED(rv)) {
continue;
}
nsCString site;
rv = nsEffectiveTLDService::GetInstance()->GetSite(uri, site);
if (NS_WARN_IF(NS_FAILED(rv))) {
continue;
}
nsCString deduplicationKey =
nsPrintfCString("%s,%s", site.get(), type.get());
if (deduplicationSet.Contains(deduplicationKey)) {
rv = deleteStmt->BindInt64ByIndex(0, id);
NS_ENSURE_SUCCESS(rv, rv);
rv = deleteStmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
} else {
deduplicationSet.Insert(deduplicationKey);
rv = updateStmt->BindInt64ByIndex(0, id);
NS_ENSURE_SUCCESS(rv, rv);
rv = updateStmt->BindUTF8StringByIndex(1, site);
NS_ENSURE_SUCCESS(rv, rv);
rv = updateStmt->Execute();
NS_ENSURE_SUCCESS(rv, rv);
}
}
rv = data->mDBConn->CommitTransaction();
NS_ENSURE_SUCCESS(rv, rv);
rv = data->mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
NS_ENSURE_SUCCESS(rv, rv);
}
// fall through to the next upgrade
[[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 = data->mDBConn->CreateStatement(
nsLiteralCString("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 = data->mDBConn->ExecuteSimpleSQL("DROP TABLE moz_perms"_ns);
NS_ENSURE_SUCCESS(rv, rv);
rv = CreateTable();
NS_ENSURE_SUCCESS(rv, rv);
} break;
}
}
// cache frequently used statements (for insertion, deletion, and
// updating)
rv = data->mDBConn->CreateStatement(
nsLiteralCString("INSERT INTO moz_perms "