зеркало из https://github.com/mozilla/gecko-dev.git
3670 строки
124 KiB
C++
3670 строки
124 KiB
C++
/* -*- 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 "nsToolkitCompsCID.h"
|
|
|
|
using namespace mozilla::dom;
|
|
|
|
namespace mozilla {
|
|
|
|
#define PERMISSIONS_FILE_NAME "permissions.sqlite"
|
|
#define HOSTS_SCHEMA_VERSION 11
|
|
|
|
// 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};
|
|
|
|
// Certain permissions should never be persisted to disk under GeckoView; it's
|
|
// the responsibility of the app to manage storing these beyond the scope of
|
|
// a single session.
|
|
#ifdef ANDROID
|
|
static const nsLiteralCString kGeckoViewRestrictedPermissions[] = {
|
|
"MediaManagerVideo"_ns, "geolocation"_ns,
|
|
"desktop-notification"_ns, "persistent-storage"_ns,
|
|
"trackingprotection"_ns, "trackingprotection-pb"_ns};
|
|
#endif
|
|
|
|
// 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;
|
|
}
|
|
|
|
/**
|
|
* Strip origin attributes depending on pref state
|
|
* @param aForceStrip If true, strips user context and private browsing id,
|
|
* ignoring stripping prefs.
|
|
* @param aOriginAttributes object to strip.
|
|
*/
|
|
void MaybeStripOAs(bool aForceStrip, OriginAttributes& aOriginAttributes) {
|
|
uint32_t flags = 0;
|
|
|
|
if (aForceStrip || !StaticPrefs::permissions_isolateBy_privateBrowsing()) {
|
|
flags |= OriginAttributes::STRIP_PRIVATE_BROWSING_ID;
|
|
}
|
|
|
|
if (aForceStrip || !StaticPrefs::permissions_isolateBy_userContext()) {
|
|
flags |= OriginAttributes::STRIP_USER_CONTEXT_ID;
|
|
}
|
|
|
|
if (flags != 0) {
|
|
aOriginAttributes.StripAttributes(flags);
|
|
}
|
|
}
|
|
|
|
void OriginAppendOASuffix(OriginAttributes aOriginAttributes,
|
|
bool aForceStripOA, nsACString& aOrigin) {
|
|
MaybeStripOAs(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;
|
|
if (!attrs.PopulateFromSuffix(suffix)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
OriginAppendOASuffix(attrs, aForceStripOA, aOrigin);
|
|
|
|
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;
|
|
}
|
|
|
|
MaybeStripOAs(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);
|
|
|
|
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 = 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.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 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);
|
|
#ifdef ANDROID
|
|
for (const auto& perm : kGeckoViewRestrictedPermissions) {
|
|
res = res && !perm.Equals(aType);
|
|
}
|
|
#endif
|
|
return res;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
|
|
PermissionManager::PermissionKey*
|
|
PermissionManager::PermissionKey::CreateFromPrincipal(nsIPrincipal* aPrincipal,
|
|
bool aForceStripOA,
|
|
nsresult& aResult) {
|
|
nsAutoCString origin;
|
|
aResult = GetOriginFromPrincipal(aPrincipal, aForceStripOA, origin);
|
|
if (NS_WARN_IF(NS_FAILED(aResult))) {
|
|
return nullptr;
|
|
}
|
|
|
|
return new PermissionKey(origin);
|
|
}
|
|
|
|
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 (auto iter = mPermissionKeyPromiseMap.Iter(); !iter.Done(); iter.Next()) {
|
|
if (iter.Data()) {
|
|
iter.Data()->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(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 "
|
|
"(id, origin, type, permission, expireType, "
|
|
"expireTime, modificationTime) "
|
|
"VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"),
|
|
getter_AddRefs(data->mStmtInsert));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = data->mDBConn->CreateStatement(nsLiteralCString("DELETE FROM moz_perms "
|
|
"WHERE id = ?1"),
|
|
getter_AddRefs(data->mStmtDelete));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = data->mDBConn->CreateStatement(
|
|
nsLiteralCString("UPDATE moz_perms "
|
|
"SET permission = ?2, expireType= ?3, expireTime = "
|
|
"?4, modificationTime = ?5 WHERE id = ?1"),
|
|
getter_AddRefs(data->mStmtUpdate));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// Always import default permissions.
|
|
ConsumeDefaultsInputStream(aDefaultsInputStream, lock);
|
|
|
|
// check whether to import or just read in the db
|
|
if (tableExists) {
|
|
rv = Read(lock);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
raiiFailure.release();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void PermissionManager::AddIdleDailyMaintenanceJob() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
|
|
NS_ENSURE_TRUE_VOID(observerService);
|
|
|
|
nsresult rv =
|
|
observerService->AddObserver(this, OBSERVER_TOPIC_IDLE_DAILY, false);
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
}
|
|
|
|
void PermissionManager::RemoveIdleDailyMaintenanceJob() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
|
|
NS_ENSURE_TRUE_VOID(observerService);
|
|
|
|
nsresult rv =
|
|
observerService->RemoveObserver(this, OBSERVER_TOPIC_IDLE_DAILY);
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
}
|
|
|
|
void PermissionManager::PerformIdleDailyMaintenance() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
RefPtr<PermissionManager> self = this;
|
|
mThread->Dispatch(NS_NewRunnableFunction(
|
|
"PermissionManager::PerformIdleDailyMaintenance", [self] {
|
|
auto data = self->mThreadBoundData.Access();
|
|
|
|
if (self->mState == eClosed || !data->mDBConn) {
|
|
return;
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmtDeleteExpired;
|
|
nsresult rv = data->mDBConn->CreateStatement(
|
|
nsLiteralCString("DELETE FROM moz_perms WHERE expireType = "
|
|
"?1 AND expireTime <= ?2"),
|
|
getter_AddRefs(stmtDeleteExpired));
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
|
|
rv = stmtDeleteExpired->BindInt32ByIndex(
|
|
0, nsIPermissionManager::EXPIRE_TIME);
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
|
|
rv = stmtDeleteExpired->BindInt64ByIndex(1, EXPIRY_NOW);
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
|
|
rv = stmtDeleteExpired->Execute();
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
}));
|
|
}
|
|
|
|
// sets the schema version and creates the moz_perms table.
|
|
nsresult PermissionManager::CreateTable() {
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
auto data = mThreadBoundData.Access();
|
|
|
|
// set the schema version, before creating the table
|
|
nsresult rv = data->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 = data->mDBConn->ExecuteSimpleSQL(
|
|
nsLiteralCString("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 data->mDBConn->ExecuteSimpleSQL(
|
|
nsLiteralCString("CREATE TABLE moz_hosts ("
|
|
" id INTEGER PRIMARY KEY"
|
|
",host TEXT"
|
|
",type TEXT"
|
|
",permission INTEGER"
|
|
",expireType INTEGER"
|
|
",expireTime INTEGER"
|
|
",modificationTime INTEGER"
|
|
",isInBrowserElement INTEGER"
|
|
")"));
|
|
}
|
|
|
|
// Returns whether the given combination of expire type and expire time are
|
|
// expired. Note that EXPIRE_SESSION only honors expireTime if it is nonzero.
|
|
bool PermissionManager::HasExpired(uint32_t aExpireType, int64_t aExpireTime) {
|
|
return (aExpireType == nsIPermissionManager::EXPIRE_TIME ||
|
|
(aExpireType == nsIPermissionManager::EXPIRE_SESSION &&
|
|
aExpireTime != 0)) &&
|
|
aExpireTime <= EXPIRY_NOW;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PermissionManager::AddFromPrincipal(nsIPrincipal* aPrincipal,
|
|
const nsACString& aType,
|
|
uint32_t aPermission, uint32_t aExpireType,
|
|
int64_t aExpireTime) {
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
NS_ENSURE_ARG_POINTER(aPrincipal);
|
|
NS_ENSURE_TRUE(aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
|
|
aExpireType == nsIPermissionManager::EXPIRE_TIME ||
|
|
aExpireType == nsIPermissionManager::EXPIRE_SESSION ||
|
|
aExpireType == nsIPermissionManager::EXPIRE_POLICY,
|
|
NS_ERROR_INVALID_ARG);
|
|
|
|
// Skip addition if the permission is already expired.
|
|
if (HasExpired(aExpireType, aExpireTime)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// We don't add the system principal because it actually has no URI and we
|
|
// always allow action for them.
|
|
if (aPrincipal->IsSystemPrincipal()) {
|
|
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, aType, aPermission, 0, aExpireType,
|
|
aExpireTime, modificationTime, eNotify, eWriteToDB);
|
|
}
|
|
|
|
nsresult PermissionManager::AddInternal(
|
|
nsIPrincipal* aPrincipal, const nsACString& aType, uint32_t aPermission,
|
|
int64_t aID, uint32_t aExpireType, int64_t aExpireTime,
|
|
int64_t aModificationTime, NotifyOperationType aNotifyOperation,
|
|
DBOperationType aDBOperation, const bool aIgnoreSessionPermissions,
|
|
const nsACString* aOriginString) {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
EnsureReadCompleted();
|
|
|
|
nsresult rv = NS_OK;
|
|
nsAutoCString origin;
|
|
// Only attempt to compute the origin string when it is going to be needed
|
|
// later on in the function.
|
|
if (!IsChildProcess() ||
|
|
(aDBOperation == eWriteToDB && IsPersistentExpire(aExpireType, aType))) {
|
|
if (aOriginString) {
|
|
// Use the origin string provided by the caller.
|
|
origin = *aOriginString;
|
|
} else {
|
|
// Compute it from the principal provided.
|
|
rv = GetOriginFromPrincipal(aPrincipal, IsOAForceStripPermission(aType),
|
|
origin);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
// For private browsing only store permissions for the session
|
|
if (aExpireType != EXPIRE_SESSION) {
|
|
uint32_t privateBrowsingId =
|
|
nsScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID;
|
|
nsresult rv = aPrincipal->GetPrivateBrowsingId(&privateBrowsingId);
|
|
if (NS_SUCCEEDED(rv) &&
|
|
privateBrowsingId !=
|
|
nsScriptSecurityManager::DEFAULT_PRIVATE_BROWSING_ID) {
|
|
aExpireType = EXPIRE_SESSION;
|
|
}
|
|
}
|
|
|
|
// Let's send the new permission to the content process only if it has to be
|
|
// notified.
|
|
if (!IsChildProcess() && aNotifyOperation == eNotify) {
|
|
IPC::Permission permission(origin, aType, aPermission, aExpireType,
|
|
aExpireTime);
|
|
|
|
nsAutoCString permissionKey;
|
|
GetKeyForPermission(aPrincipal, aType, permissionKey);
|
|
|
|
nsTArray<ContentParent*> cplist;
|
|
ContentParent::GetAll(cplist);
|
|
for (uint32_t i = 0; i < cplist.Length(); ++i) {
|
|
ContentParent* cp = cplist[i];
|
|
if (cp->NeedsPermissionsUpdate(permissionKey))
|
|
Unused << cp->SendAddPermission(permission);
|
|
}
|
|
}
|
|
|
|
MOZ_ASSERT(PermissionAvailable(aPrincipal, aType));
|
|
|
|
// look up the type index
|
|
int32_t typeIndex = GetTypeIndex(aType, 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 = PermissionKey::CreateFromPrincipal(
|
|
aPrincipal, IsOAForceStripPermission(aType), rv);
|
|
if (!key) {
|
|
MOZ_ASSERT(NS_FAILED(rv));
|
|
return rv;
|
|
}
|
|
|
|
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 = EXPIRY_NOW;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
entry->GetPermissions().AppendElement(
|
|
PermissionEntry(id, typeIndex, aPermission, aExpireType, aExpireTime,
|
|
aModificationTime));
|
|
|
|
if (aDBOperation == eWriteToDB &&
|
|
IsPersistentExpire(aExpireType, aType)) {
|
|
UpdateDB(op, id, origin, aType, aPermission, aExpireType, aExpireTime,
|
|
aModificationTime);
|
|
}
|
|
|
|
if (aNotifyOperation == eNotify) {
|
|
NotifyObserversWithPermission(aPrincipal, mTypeArray[typeIndex],
|
|
aPermission, aExpireType, aExpireTime,
|
|
aModificationTime, u"added");
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case eOperationRemoving: {
|
|
PermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
|
|
id = oldPermissionEntry.mID;
|
|
|
|
// If the type we want to remove is EXPIRE_POLICY, we need to reject
|
|
// attempts to change the permission.
|
|
if (entry->GetPermissions()[index].mExpireType == EXPIRE_POLICY) {
|
|
NS_WARNING("Attempting to remove EXPIRE_POLICY permission");
|
|
break;
|
|
}
|
|
|
|
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, id, ""_ns, ""_ns, 0, nsIPermissionManager::EXPIRE_NEVER, 0,
|
|
0);
|
|
|
|
if (aNotifyOperation == eNotify) {
|
|
NotifyObserversWithPermission(
|
|
aPrincipal, mTypeArray[typeIndex], oldPermissionEntry.mPermission,
|
|
oldPermissionEntry.mExpireType, oldPermissionEntry.mExpireTime,
|
|
oldPermissionEntry.mModificationTime, 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 existing type is EXPIRE_POLICY, we need to reject attempts to
|
|
// change the permission.
|
|
if (entry->GetPermissions()[index].mExpireType == EXPIRE_POLICY) {
|
|
NS_WARNING("Attempting to modify EXPIRE_POLICY permission");
|
|
break;
|
|
}
|
|
|
|
PermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
|
|
|
|
// 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) {
|
|
bool newIsPersistentExpire = IsPersistentExpire(aExpireType, aType);
|
|
bool oldIsPersistentExpire =
|
|
IsPersistentExpire(oldPermissionEntry.mExpireType, aType);
|
|
|
|
if (!newIsPersistentExpire && oldIsPersistentExpire) {
|
|
// Maybe we have to remove the previous permission if that was
|
|
// persistent.
|
|
UpdateDB(eOperationRemoving, id, ""_ns, ""_ns, 0,
|
|
nsIPermissionManager::EXPIRE_NEVER, 0, 0);
|
|
} else if (newIsPersistentExpire && !oldIsPersistentExpire) {
|
|
// It could also be that the previous permission was session-only but
|
|
// this needs to be written into the DB. In this case, we have to run
|
|
// an Adding operation.
|
|
UpdateDB(eOperationAdding, id, origin, aType, aPermission,
|
|
aExpireType, aExpireTime, aModificationTime);
|
|
} else if (newIsPersistentExpire) {
|
|
// This is the a simple update. We care only about the id, the
|
|
// permission and expireType/expireTime/modificationTime here. We pass
|
|
// dummy values for all other parameters.
|
|
UpdateDB(op, id, ""_ns, ""_ns, aPermission, aExpireType, aExpireTime,
|
|
aModificationTime);
|
|
}
|
|
}
|
|
|
|
if (aNotifyOperation == eNotify) {
|
|
NotifyObserversWithPermission(aPrincipal, mTypeArray[typeIndex],
|
|
aPermission, aExpireType, aExpireTime,
|
|
aModificationTime, 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 or
|
|
// policy expiry.
|
|
NS_ENSURE_TRUE(entry->GetPermissions()[index].mExpireType !=
|
|
nsIPermissionManager::EXPIRE_SESSION,
|
|
NS_ERROR_UNEXPECTED);
|
|
NS_ENSURE_TRUE(entry->GetPermissions()[index].mExpireType !=
|
|
nsIPermissionManager::EXPIRE_POLICY,
|
|
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 &&
|
|
IsPersistentExpire(aExpireType, aType)) {
|
|
UpdateDB(eOperationAdding, id, origin, aType, aPermission, aExpireType,
|
|
aExpireTime, aModificationTime);
|
|
}
|
|
|
|
if (aNotifyOperation == eNotify) {
|
|
NotifyObserversWithPermission(aPrincipal, mTypeArray[typeIndex],
|
|
aPermission, aExpireType, aExpireTime,
|
|
aModificationTime, u"changed");
|
|
}
|
|
|
|
} break;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PermissionManager::RemoveFromPrincipal(nsIPrincipal* aPrincipal,
|
|
const nsACString& aType) {
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
NS_ENSURE_ARG_POINTER(aPrincipal);
|
|
|
|
// System principals are never added to the database, no need to remove them.
|
|
if (aPrincipal->IsSystemPrincipal()) {
|
|
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, aType, nsIPermissionManager::UNKNOWN_ACTION, 0,
|
|
nsIPermissionManager::EXPIRE_NEVER, 0, 0, eNotify,
|
|
eWriteToDB);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PermissionManager::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);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PermissionManager::RemoveAll() {
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
return RemoveAllInternal(true);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PermissionManager::RemoveAllSince(int64_t aSince) {
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
return RemoveAllModifiedSince(aSince);
|
|
}
|
|
|
|
template <class T>
|
|
nsresult PermissionManager::RemovePermissionEntries(T aCondition) {
|
|
EnsureReadCompleted();
|
|
|
|
Vector<Tuple<nsCOMPtr<nsIPrincipal>, nsCString, nsCString>, 10> array;
|
|
for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) {
|
|
PermissionHashKey* entry = iter.Get();
|
|
for (const auto& permEntry : entry->GetPermissions()) {
|
|
if (!aCondition(permEntry)) {
|
|
continue;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
nsresult rv = GetPrincipalFromOrigin(
|
|
entry->GetKey()->mOrigin,
|
|
IsOAForceStripPermission(mTypeArray[permEntry.mType]),
|
|
getter_AddRefs(principal));
|
|
if (NS_FAILED(rv)) {
|
|
continue;
|
|
}
|
|
|
|
if (!array.emplaceBack(principal, mTypeArray[permEntry.mType],
|
|
entry->GetKey()->mOrigin)) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto& i : array) {
|
|
// AddInternal handles removal, so let it do the work...
|
|
AddInternal(Get<0>(i), Get<1>(i), nsIPermissionManager::UNKNOWN_ACTION, 0,
|
|
nsIPermissionManager::EXPIRE_NEVER, 0, 0,
|
|
PermissionManager::eNotify, PermissionManager::eWriteToDB,
|
|
false, &Get<2>(i));
|
|
}
|
|
|
|
// now re-import any defaults as they may now be required if we just deleted
|
|
// an override.
|
|
ImportLatestDefaults();
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PermissionManager::RemoveByType(const nsACString& aType) {
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
|
|
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;
|
|
}
|
|
|
|
return RemovePermissionEntries(
|
|
[typeIndex](const PermissionEntry& aPermEntry) {
|
|
return static_cast<uint32_t>(typeIndex) == aPermEntry.mType;
|
|
});
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PermissionManager::RemoveByTypeSince(const nsACString& aType,
|
|
int64_t aModificationTime) {
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
|
|
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;
|
|
}
|
|
|
|
return RemovePermissionEntries(
|
|
[typeIndex, aModificationTime](const PermissionEntry& aPermEntry) {
|
|
return uint32_t(typeIndex) == aPermEntry.mType &&
|
|
aModificationTime <= aPermEntry.mModificationTime;
|
|
});
|
|
}
|
|
|
|
void PermissionManager::CloseDB(CloseDBNextOp aNextOp) {
|
|
EnsureReadCompleted();
|
|
|
|
mState = eClosed;
|
|
|
|
nsCOMPtr<nsIInputStream> defaultsInputStream;
|
|
if (aNextOp == eRebuldOnSuccess) {
|
|
defaultsInputStream = GetDefaultsInputStream();
|
|
}
|
|
|
|
RefPtr<PermissionManager> self = this;
|
|
mThread->Dispatch(NS_NewRunnableFunction(
|
|
"PermissionManager::CloseDB", [self, aNextOp, defaultsInputStream] {
|
|
auto data = self->mThreadBoundData.Access();
|
|
// Null the statements, this will finalize them.
|
|
data->mStmtInsert = nullptr;
|
|
data->mStmtDelete = nullptr;
|
|
data->mStmtUpdate = nullptr;
|
|
if (data->mDBConn) {
|
|
DebugOnly<nsresult> rv = data->mDBConn->Close();
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
data->mDBConn = nullptr;
|
|
|
|
if (aNextOp == eRebuldOnSuccess) {
|
|
self->TryInitDB(true, defaultsInputStream);
|
|
}
|
|
}
|
|
|
|
if (aNextOp == eShutdown) {
|
|
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
|
"PermissionManager::MaybeCompleteShutdown",
|
|
[self] { self->MaybeCompleteShutdown(); }));
|
|
}
|
|
}));
|
|
}
|
|
|
|
nsresult PermissionManager::RemoveAllFromIPC() {
|
|
MOZ_ASSERT(IsChildProcess());
|
|
|
|
// 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();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult PermissionManager::RemoveAllInternal(bool aNotifyObservers) {
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
|
|
EnsureReadCompleted();
|
|
|
|
// Let's broadcast the removeAll() to any content process.
|
|
nsTArray<ContentParent*> parents;
|
|
ContentParent::GetAll(parents);
|
|
for (ContentParent* parent : parents) {
|
|
Unused << parent->SendRemoveAllPermissions();
|
|
}
|
|
|
|
// 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
|
|
ImportLatestDefaults();
|
|
|
|
if (aNotifyObservers) {
|
|
NotifyObservers(nullptr, u"cleared");
|
|
}
|
|
|
|
RefPtr<PermissionManager> self = this;
|
|
mThread->Dispatch(
|
|
NS_NewRunnableFunction("PermissionManager::RemoveAllInternal", [self] {
|
|
auto data = self->mThreadBoundData.Access();
|
|
|
|
if (self->mState == eClosed || !data->mDBConn) {
|
|
return;
|
|
}
|
|
|
|
// clear the db
|
|
nsresult rv =
|
|
data->mDBConn->ExecuteSimpleSQL("DELETE FROM moz_perms"_ns);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
NS_DispatchToMainThread(NS_NewRunnableFunction(
|
|
"PermissionManager::RemoveAllInternal-Failure",
|
|
[self] { self->CloseDB(eRebuldOnSuccess); }));
|
|
}
|
|
}));
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PermissionManager::TestExactPermissionFromPrincipal(nsIPrincipal* aPrincipal,
|
|
const nsACString& aType,
|
|
uint32_t* aPermission) {
|
|
return CommonTestPermission(aPrincipal, -1, aType, aPermission,
|
|
nsIPermissionManager::UNKNOWN_ACTION, false, true,
|
|
true);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PermissionManager::TestExactPermanentPermission(nsIPrincipal* aPrincipal,
|
|
const nsACString& aType,
|
|
uint32_t* aPermission) {
|
|
return CommonTestPermission(aPrincipal, -1, aType, aPermission,
|
|
nsIPermissionManager::UNKNOWN_ACTION, false, true,
|
|
false);
|
|
}
|
|
|
|
nsresult PermissionManager::LegacyTestPermissionFromURI(
|
|
nsIURI* aURI, const OriginAttributes* aOriginAttributes,
|
|
const nsACString& aType, uint32_t* aPermission) {
|
|
return CommonTestPermission(aURI, aOriginAttributes, -1, aType, aPermission,
|
|
nsIPermissionManager::UNKNOWN_ACTION, false,
|
|
false, true);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PermissionManager::TestPermissionFromPrincipal(nsIPrincipal* aPrincipal,
|
|
const nsACString& aType,
|
|
uint32_t* aPermission) {
|
|
return CommonTestPermission(aPrincipal, -1, aType, aPermission,
|
|
nsIPermissionManager::UNKNOWN_ACTION, false,
|
|
false, true);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PermissionManager::GetPermissionObject(nsIPrincipal* aPrincipal,
|
|
const nsACString& aType,
|
|
bool aExactHostMatch,
|
|
nsIPermission** aResult) {
|
|
NS_ENSURE_ARG_POINTER(aPrincipal);
|
|
*aResult = nullptr;
|
|
|
|
EnsureReadCompleted();
|
|
|
|
if (aPrincipal->IsSystemPrincipal()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// Querying the permission object of an nsEP is non-sensical.
|
|
if (IsExpandedPrincipal(aPrincipal)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
MOZ_ASSERT(PermissionAvailable(aPrincipal, aType));
|
|
|
|
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,
|
|
IsOAForceStripPermission(aType),
|
|
getter_AddRefs(principal));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
PermissionEntry& perm = entry->GetPermissions()[idx];
|
|
nsCOMPtr<nsIPermission> r = Permission::Create(
|
|
principal, mTypeArray[perm.mType], perm.mPermission, perm.mExpireType,
|
|
perm.mExpireTime, perm.mModificationTime);
|
|
if (NS_WARN_IF(!r)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
r.forget(aResult);
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult PermissionManager::CommonTestPermissionInternal(
|
|
nsIPrincipal* aPrincipal, nsIURI* aURI,
|
|
const OriginAttributes* aOriginAttributes, int32_t aTypeIndex,
|
|
const nsACString& aType, uint32_t* aPermission, bool aExactHostMatch,
|
|
bool aIncludingSession) {
|
|
MOZ_ASSERT(aPrincipal || aURI);
|
|
NS_ENSURE_ARG_POINTER(aPrincipal || aURI);
|
|
MOZ_ASSERT_IF(aPrincipal, !aURI && !aOriginAttributes);
|
|
MOZ_ASSERT_IF(aURI || aOriginAttributes, !aPrincipal);
|
|
|
|
EnsureReadCompleted();
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
nsCOMPtr<nsIPrincipal> prin = aPrincipal;
|
|
if (!prin) {
|
|
if (aURI) {
|
|
prin = BasePrincipal::CreateContentPrincipal(aURI, OriginAttributes());
|
|
}
|
|
}
|
|
MOZ_ASSERT(prin);
|
|
MOZ_ASSERT(PermissionAvailable(prin, aType));
|
|
}
|
|
#endif
|
|
|
|
PermissionHashKey* entry =
|
|
aPrincipal ? GetPermissionHashKey(aPrincipal, aTypeIndex, aExactHostMatch)
|
|
: GetPermissionHashKey(aURI, aOriginAttributes, aTypeIndex,
|
|
aExactHostMatch);
|
|
if (!entry || (!aIncludingSession &&
|
|
entry->GetPermission(aTypeIndex).mNonSessionExpireType ==
|
|
nsIPermissionManager::EXPIRE_SESSION)) {
|
|
return NS_OK;
|
|
}
|
|
|
|
*aPermission = aIncludingSession
|
|
? entry->GetPermission(aTypeIndex).mPermission
|
|
: entry->GetPermission(aTypeIndex).mNonSessionPermission;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Helper function to filter permissions using a condition function.
|
|
template <class T>
|
|
nsresult PermissionManager::GetPermissionEntries(
|
|
T aCondition, nsTArray<RefPtr<nsIPermission>>& aResult) {
|
|
aResult.Clear();
|
|
if (XRE_IsContentProcess()) {
|
|
NS_WARNING(
|
|
"Iterating over all permissions is not available in the "
|
|
"content process, as not all permissions may be available.");
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
EnsureReadCompleted();
|
|
|
|
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;
|
|
}
|
|
|
|
// If the permission is expired, skip it. We're not deleting it here
|
|
// because we're iterating over a lot of permissions.
|
|
// It will be removed as part of the daily maintenance later.
|
|
if (HasExpired(permEntry.mExpireType, permEntry.mExpireTime)) {
|
|
continue;
|
|
}
|
|
|
|
if (!aCondition(permEntry)) {
|
|
continue;
|
|
}
|
|
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
nsresult rv = GetPrincipalFromOrigin(
|
|
entry->GetKey()->mOrigin,
|
|
IsOAForceStripPermission(mTypeArray[permEntry.mType]),
|
|
getter_AddRefs(principal));
|
|
if (NS_FAILED(rv)) {
|
|
continue;
|
|
}
|
|
|
|
RefPtr<nsIPermission> permission = Permission::Create(
|
|
principal, mTypeArray[permEntry.mType], permEntry.mPermission,
|
|
permEntry.mExpireType, permEntry.mExpireTime,
|
|
permEntry.mModificationTime);
|
|
if (NS_WARN_IF(!permission)) {
|
|
continue;
|
|
}
|
|
aResult.AppendElement(std::move(permission));
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP PermissionManager::GetAll(
|
|
nsTArray<RefPtr<nsIPermission>>& aResult) {
|
|
return GetPermissionEntries(
|
|
[](const PermissionEntry& aPermEntry) { return true; }, aResult);
|
|
}
|
|
|
|
NS_IMETHODIMP PermissionManager::GetAllByTypeSince(
|
|
const nsACString& aPrefix, int64_t aSince,
|
|
nsTArray<RefPtr<nsIPermission>>& aResult) {
|
|
return GetPermissionEntries(
|
|
[&](const PermissionEntry& aPermEntry) {
|
|
return mTypeArray[aPermEntry.mType].Equals(aPrefix) &&
|
|
aSince <= aPermEntry.mModificationTime;
|
|
},
|
|
aResult);
|
|
}
|
|
|
|
NS_IMETHODIMP PermissionManager::GetAllWithTypePrefix(
|
|
const nsACString& aPrefix, nsTArray<RefPtr<nsIPermission>>& aResult) {
|
|
return GetPermissionEntries(
|
|
[&](const PermissionEntry& aPermEntry) {
|
|
return StringBeginsWith(mTypeArray[aPermEntry.mType], aPrefix);
|
|
},
|
|
aResult);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PermissionManager::GetAllForPrincipal(
|
|
nsIPrincipal* aPrincipal, nsTArray<RefPtr<nsIPermission>>& aResult) {
|
|
aResult.Clear();
|
|
EnsureReadCompleted();
|
|
|
|
MOZ_ASSERT(PermissionAvailable(aPrincipal, ""_ns));
|
|
|
|
nsresult rv;
|
|
RefPtr<PermissionKey> key =
|
|
PermissionKey::CreateFromPrincipal(aPrincipal, false, rv);
|
|
if (!key) {
|
|
MOZ_ASSERT(NS_FAILED(rv));
|
|
return rv;
|
|
}
|
|
PermissionHashKey* entry = mPermissionTable.GetEntry(key);
|
|
|
|
nsTArray<PermissionEntry> strippedPerms;
|
|
rv = GetStripPermsForPrincipal(aPrincipal, strippedPerms);
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
if (entry) {
|
|
for (const auto& permEntry : entry->GetPermissions()) {
|
|
// Only return custom permissions
|
|
if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
|
|
continue;
|
|
}
|
|
|
|
// If the permission is expired, skip it. We're not deleting it here
|
|
// because we're iterating over a lot of permissions.
|
|
// It will be removed as part of the daily maintenance later.
|
|
if (HasExpired(permEntry.mExpireType, permEntry.mExpireTime)) {
|
|
continue;
|
|
}
|
|
|
|
// Stripped principal permissions overwrite regular ones
|
|
// For each permission check if there is a stripped permission we should
|
|
// use instead
|
|
PermissionEntry perm = permEntry;
|
|
nsTArray<PermissionEntry>::index_type index = 0;
|
|
for (const auto& strippedPerm : strippedPerms) {
|
|
if (strippedPerm.mType == permEntry.mType) {
|
|
perm = strippedPerm;
|
|
strippedPerms.RemoveElementAt(index);
|
|
break;
|
|
}
|
|
index++;
|
|
}
|
|
|
|
RefPtr<nsIPermission> permission = Permission::Create(
|
|
aPrincipal, mTypeArray[perm.mType], perm.mPermission,
|
|
perm.mExpireType, perm.mExpireTime, perm.mModificationTime);
|
|
if (NS_WARN_IF(!permission)) {
|
|
continue;
|
|
}
|
|
aResult.AppendElement(permission);
|
|
}
|
|
}
|
|
|
|
for (const auto& perm : strippedPerms) {
|
|
RefPtr<nsIPermission> permission = Permission::Create(
|
|
aPrincipal, mTypeArray[perm.mType], perm.mPermission, perm.mExpireType,
|
|
perm.mExpireTime, perm.mModificationTime);
|
|
if (NS_WARN_IF(!permission)) {
|
|
continue;
|
|
}
|
|
aResult.AppendElement(permission);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP PermissionManager::Observe(nsISupports* aSubject,
|
|
const char* aTopic,
|
|
const char16_t* someData) {
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
|
|
if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
|
|
if (!mBlockerAdded) {
|
|
// The profile is about to change and the shutdown blocker has not been
|
|
// added yet (we are probably in a xpcshell-test).
|
|
RemoveIdleDailyMaintenanceJob();
|
|
RemoveAllFromMemory();
|
|
CloseDB(eNone);
|
|
}
|
|
} else if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
|
|
// the profile has already changed; init the db from the new location
|
|
InitDB(false);
|
|
} else if (!nsCRT::strcmp(aTopic, "testonly-reload-permissions-from-disk")) {
|
|
// Testing mechanism to reload all permissions from disk. Because the
|
|
// permission manager automatically initializes itself at startup, tests
|
|
// that directly manipulate the permissions database need some way to reload
|
|
// the database for their changes to have any effect. This mechanism was
|
|
// introduced when moving the permissions manager from on-demand startup to
|
|
// always being initialized. This is not guarded by a pref because it's not
|
|
// dangerous to reload permissions from disk, just bad for performance.
|
|
RemoveAllFromMemory();
|
|
CloseDB(eNone);
|
|
InitDB(false);
|
|
} else if (!nsCRT::strcmp(aTopic, OBSERVER_TOPIC_IDLE_DAILY)) {
|
|
PerformIdleDailyMaintenance();
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult PermissionManager::RemoveAllModifiedSince(int64_t aModificationTime) {
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
|
|
return RemovePermissionEntries(
|
|
[aModificationTime](const PermissionEntry& aPermEntry) {
|
|
return aModificationTime <= aPermEntry.mModificationTime;
|
|
});
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PermissionManager::RemovePermissionsWithAttributes(const nsAString& aPattern) {
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
OriginAttributesPattern pattern;
|
|
if (!pattern.Init(aPattern)) {
|
|
return NS_ERROR_INVALID_ARG;
|
|
}
|
|
|
|
return RemovePermissionsWithAttributes(pattern);
|
|
}
|
|
|
|
nsresult PermissionManager::RemovePermissionsWithAttributes(
|
|
OriginAttributesPattern& aPattern) {
|
|
EnsureReadCompleted();
|
|
|
|
Vector<Tuple<nsCOMPtr<nsIPrincipal>, nsCString, nsCString>, 10> permissions;
|
|
for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) {
|
|
PermissionHashKey* entry = iter.Get();
|
|
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
nsresult rv = GetPrincipalFromOrigin(entry->GetKey()->mOrigin, false,
|
|
getter_AddRefs(principal));
|
|
if (NS_FAILED(rv)) {
|
|
continue;
|
|
}
|
|
|
|
if (!aPattern.Matches(principal->OriginAttributesRef())) {
|
|
continue;
|
|
}
|
|
|
|
for (const auto& permEntry : entry->GetPermissions()) {
|
|
if (!permissions.emplaceBack(principal, mTypeArray[permEntry.mType],
|
|
entry->GetKey()->mOrigin)) {
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (auto& i : permissions) {
|
|
AddInternal(Get<0>(i), Get<1>(i), nsIPermissionManager::UNKNOWN_ACTION, 0,
|
|
nsIPermissionManager::EXPIRE_NEVER, 0, 0,
|
|
PermissionManager::eNotify, PermissionManager::eWriteToDB,
|
|
false, &Get<2>(i));
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult PermissionManager::GetStripPermsForPrincipal(
|
|
nsIPrincipal* aPrincipal, nsTArray<PermissionEntry>& aResult) {
|
|
aResult.Clear();
|
|
aResult.SetCapacity(kStripOAPermissions.size());
|
|
|
|
// No special strip permissions
|
|
if (kStripOAPermissions.empty()) {
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult rv;
|
|
// Create a key for the principal, but strip any origin attributes
|
|
RefPtr<PermissionKey> key =
|
|
PermissionKey::CreateFromPrincipal(aPrincipal, true, rv);
|
|
if (!key) {
|
|
MOZ_ASSERT(NS_FAILED(rv));
|
|
return rv;
|
|
}
|
|
|
|
PermissionHashKey* hashKey = mPermissionTable.GetEntry(key);
|
|
if (!hashKey) {
|
|
return NS_OK;
|
|
}
|
|
|
|
for (const auto& permType : kStripOAPermissions) {
|
|
int32_t index = GetTypeIndex(permType, false);
|
|
if (index == -1) {
|
|
continue;
|
|
}
|
|
PermissionEntry perm = hashKey->GetPermission(index);
|
|
if (perm.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
|
|
continue;
|
|
}
|
|
aResult.AppendElement(perm);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
int32_t PermissionManager::GetTypeIndex(const nsACString& 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
|
|
if (!mTypeArray.emplaceBack(aType)) {
|
|
return -1;
|
|
}
|
|
|
|
return mTypeArray.length() - 1;
|
|
}
|
|
|
|
PermissionManager::PermissionHashKey* PermissionManager::GetPermissionHashKey(
|
|
nsIPrincipal* aPrincipal, uint32_t aType, bool aExactHostMatch) {
|
|
EnsureReadCompleted();
|
|
|
|
MOZ_ASSERT(PermissionAvailable(aPrincipal, mTypeArray[aType]));
|
|
|
|
nsresult rv;
|
|
RefPtr<PermissionKey> key = PermissionKey::CreateFromPrincipal(
|
|
aPrincipal, IsOAForceStripPermission(mTypeArray[aType]), rv);
|
|
if (!key) {
|
|
return nullptr;
|
|
}
|
|
|
|
PermissionHashKey* entry = mPermissionTable.GetEntry(key);
|
|
|
|
if (entry) {
|
|
PermissionEntry permEntry = entry->GetPermission(aType);
|
|
|
|
// if the entry is expired, remove and keep looking for others.
|
|
if (HasExpired(permEntry.mExpireType, permEntry.mExpireTime)) {
|
|
entry = nullptr;
|
|
RemoveFromPrincipal(aPrincipal, mTypeArray[aType]);
|
|
} 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<nsIPrincipal> principal = aPrincipal->GetNextSubDomainPrincipal();
|
|
if (principal) {
|
|
return GetPermissionHashKey(principal, aType, aExactHostMatch);
|
|
}
|
|
}
|
|
|
|
// No entry, really...
|
|
return nullptr;
|
|
}
|
|
|
|
PermissionManager::PermissionHashKey* PermissionManager::GetPermissionHashKey(
|
|
nsIURI* aURI, const OriginAttributes* aOriginAttributes, uint32_t aType,
|
|
bool aExactHostMatch) {
|
|
MOZ_ASSERT(aURI);
|
|
|
|
#ifdef DEBUG
|
|
{
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
nsresult rv = NS_OK;
|
|
if (aURI) {
|
|
rv = GetPrincipal(aURI, getter_AddRefs(principal));
|
|
}
|
|
MOZ_ASSERT_IF(NS_SUCCEEDED(rv),
|
|
PermissionAvailable(principal, mTypeArray[aType]));
|
|
}
|
|
#endif
|
|
|
|
nsresult rv;
|
|
RefPtr<PermissionKey> key;
|
|
|
|
if (aOriginAttributes) {
|
|
key = PermissionKey::CreateFromURIAndOriginAttributes(
|
|
aURI, aOriginAttributes, IsOAForceStripPermission(mTypeArray[aType]),
|
|
rv);
|
|
} else {
|
|
key = PermissionKey::CreateFromURI(aURI, rv);
|
|
}
|
|
|
|
if (!key) {
|
|
return nullptr;
|
|
}
|
|
|
|
PermissionHashKey* entry = mPermissionTable.GetEntry(key);
|
|
|
|
if (entry) {
|
|
PermissionEntry permEntry = entry->GetPermission(aType);
|
|
|
|
// if the entry is expired, remove and keep looking for others.
|
|
if (HasExpired(permEntry.mExpireType, permEntry.mExpireTime)) {
|
|
entry = nullptr;
|
|
// If we need to remove a permission we mint a principal. This is a bit
|
|
// inefficient, but hopefully this code path isn't super common.
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
if (aURI) {
|
|
nsresult rv = GetPrincipal(aURI, getter_AddRefs(principal));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return nullptr;
|
|
}
|
|
}
|
|
RemoveFromPrincipal(principal, mTypeArray[aType]);
|
|
} 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;
|
|
if (aURI) {
|
|
uri = GetNextSubDomainURI(aURI);
|
|
}
|
|
if (uri) {
|
|
return GetPermissionHashKey(uri, aOriginAttributes, aType,
|
|
aExactHostMatch);
|
|
}
|
|
}
|
|
|
|
// No entry, really...
|
|
return nullptr;
|
|
}
|
|
|
|
nsresult PermissionManager::RemoveAllFromMemory() {
|
|
mLargestID = 0;
|
|
mTypeArray.clear();
|
|
mPermissionTable.Clear();
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// wrapper function for mangling (host,type,perm,expireType,expireTime)
|
|
// set into an nsIPermission.
|
|
void PermissionManager::NotifyObserversWithPermission(
|
|
nsIPrincipal* aPrincipal, const nsACString& aType, uint32_t aPermission,
|
|
uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime,
|
|
const char16_t* aData) {
|
|
nsCOMPtr<nsIPermission> permission =
|
|
Permission::Create(aPrincipal, aType, aPermission, aExpireType,
|
|
aExpireTime, aModificationTime);
|
|
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 PermissionManager::NotifyObservers(nsIPermission* aPermission,
|
|
const char16_t* aData) {
|
|
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
|
|
if (observerService)
|
|
observerService->NotifyObservers(aPermission, kPermissionChangeNotification,
|
|
aData);
|
|
}
|
|
|
|
nsresult PermissionManager::Read(const MonitorAutoLock& aProofOfLock) {
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
auto data = mThreadBoundData.Access();
|
|
|
|
nsresult rv;
|
|
bool hasResult;
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
|
|
// Let's retrieve the last used ID.
|
|
rv = data->mDBConn->CreateStatement(
|
|
nsLiteralCString("SELECT MAX(id) FROM moz_perms"), getter_AddRefs(stmt));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
|
|
int64_t id = stmt->AsInt64(0);
|
|
mLargestID = id;
|
|
}
|
|
|
|
rv = data->mDBConn->CreateStatement(
|
|
nsLiteralCString(
|
|
"SELECT id, origin, type, permission, expireType, "
|
|
"expireTime, modificationTime "
|
|
"FROM moz_perms WHERE expireType != ?1 OR expireTime > ?2"),
|
|
getter_AddRefs(stmt));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = stmt->BindInt32ByIndex(0, nsIPermissionManager::EXPIRE_TIME);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = stmt->BindInt64ByIndex(1, EXPIRY_NOW);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
bool readError = false;
|
|
|
|
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
|
|
ReadEntry entry;
|
|
|
|
// explicitly set our entry id counter for use in AddInternal(),
|
|
// and keep track of the largest id so we know where to pick up.
|
|
entry.mId = stmt->AsInt64(0);
|
|
MOZ_ASSERT(entry.mId <= mLargestID);
|
|
|
|
rv = stmt->GetUTF8String(1, entry.mOrigin);
|
|
if (NS_FAILED(rv)) {
|
|
readError = true;
|
|
continue;
|
|
}
|
|
|
|
rv = stmt->GetUTF8String(2, entry.mType);
|
|
if (NS_FAILED(rv)) {
|
|
readError = true;
|
|
continue;
|
|
}
|
|
|
|
entry.mPermission = stmt->AsInt32(3);
|
|
entry.mExpireType = stmt->AsInt32(4);
|
|
|
|
// convert into int64_t values (milliseconds)
|
|
entry.mExpireTime = stmt->AsInt64(5);
|
|
entry.mModificationTime = stmt->AsInt64(6);
|
|
|
|
entry.mFromMigration = false;
|
|
|
|
mReadEntries.AppendElement(entry);
|
|
}
|
|
|
|
if (readError) {
|
|
NS_ERROR("Error occured while reading the permissions database!");
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void PermissionManager::CompleteMigrations() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mState == eReady);
|
|
|
|
nsresult rv;
|
|
|
|
nsTArray<MigrationEntry> entries;
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
entries = std::move(mMigrationEntries);
|
|
}
|
|
|
|
for (const MigrationEntry& entry : entries) {
|
|
rv = UpgradeHostToOriginAndInsert(
|
|
entry.mHost, entry.mType, entry.mPermission, entry.mExpireType,
|
|
entry.mExpireTime, entry.mModificationTime, entry.mIsInBrowserElement,
|
|
[&](const nsACString& aOrigin, const nsCString& aType,
|
|
uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
|
|
int64_t aModificationTime) {
|
|
MaybeAddReadEntryFromMigration(aOrigin, aType, aPermission,
|
|
aExpireType, aExpireTime,
|
|
aModificationTime, entry.mId);
|
|
return NS_OK;
|
|
});
|
|
Unused << NS_WARN_IF(NS_FAILED(rv));
|
|
}
|
|
}
|
|
|
|
void PermissionManager::CompleteRead() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mState == eReady);
|
|
|
|
nsresult rv;
|
|
|
|
nsTArray<ReadEntry> entries;
|
|
{
|
|
MonitorAutoLock lock(mMonitor);
|
|
entries = std::move(mReadEntries);
|
|
}
|
|
|
|
for (const ReadEntry& entry : entries) {
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
rv = GetPrincipalFromOrigin(entry.mOrigin,
|
|
IsOAForceStripPermission(entry.mType),
|
|
getter_AddRefs(principal));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
continue;
|
|
}
|
|
|
|
DBOperationType op = entry.mFromMigration ? eWriteToDB : eNoDBOperation;
|
|
|
|
rv = AddInternal(principal, entry.mType, entry.mPermission, entry.mId,
|
|
entry.mExpireType, entry.mExpireTime,
|
|
entry.mModificationTime, eDontNotify, op, false,
|
|
&entry.mOrigin);
|
|
Unused << NS_WARN_IF(NS_FAILED(rv));
|
|
}
|
|
}
|
|
|
|
void PermissionManager::MaybeAddReadEntryFromMigration(
|
|
const nsACString& aOrigin, const nsCString& aType, uint32_t aPermission,
|
|
uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime,
|
|
int64_t aId) {
|
|
MonitorAutoLock lock(mMonitor);
|
|
|
|
// We convert a migration to a ReadEntry only if we don't have an existing
|
|
// ReadEntry for the same origin + type.
|
|
for (const ReadEntry& entry : mReadEntries) {
|
|
if (entry.mOrigin == aOrigin && entry.mType == aType) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
ReadEntry entry;
|
|
entry.mId = aId;
|
|
entry.mOrigin = aOrigin;
|
|
entry.mType = aType;
|
|
entry.mPermission = aPermission;
|
|
entry.mExpireType = aExpireType;
|
|
entry.mExpireTime = aExpireTime;
|
|
entry.mModificationTime = aModificationTime;
|
|
entry.mFromMigration = true;
|
|
|
|
mReadEntries.AppendElement(entry);
|
|
}
|
|
|
|
void PermissionManager::UpdateDB(OperationType aOp, 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;
|
|
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
EnsureReadCompleted();
|
|
|
|
nsCString origin(aOrigin);
|
|
nsCString type(aType);
|
|
|
|
RefPtr<PermissionManager> self = this;
|
|
mThread->Dispatch(NS_NewRunnableFunction(
|
|
"PermissionManager::UpdateDB",
|
|
[self, aOp, aID, origin, type, aPermission, aExpireType, aExpireTime,
|
|
aModificationTime] {
|
|
nsresult rv;
|
|
|
|
auto data = self->mThreadBoundData.Access();
|
|
|
|
if (self->mState == eClosed || !data->mDBConn) {
|
|
// no statement is ok - just means we don't have a profile
|
|
return;
|
|
}
|
|
|
|
mozIStorageStatement* stmt = nullptr;
|
|
switch (aOp) {
|
|
case eOperationAdding: {
|
|
stmt = data->mStmtInsert;
|
|
|
|
rv = stmt->BindInt64ByIndex(0, aID);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = stmt->BindUTF8StringByIndex(1, origin);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = stmt->BindUTF8StringByIndex(2, type);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = stmt->BindInt32ByIndex(3, aPermission);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = stmt->BindInt32ByIndex(4, aExpireType);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = stmt->BindInt64ByIndex(5, aExpireTime);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = stmt->BindInt64ByIndex(6, aModificationTime);
|
|
break;
|
|
}
|
|
|
|
case eOperationRemoving: {
|
|
stmt = data->mStmtDelete;
|
|
rv = stmt->BindInt64ByIndex(0, aID);
|
|
break;
|
|
}
|
|
|
|
case eOperationChanging: {
|
|
stmt = data->mStmtUpdate;
|
|
|
|
rv = stmt->BindInt64ByIndex(0, aID);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = stmt->BindInt32ByIndex(1, aPermission);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = stmt->BindInt32ByIndex(2, aExpireType);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = stmt->BindInt64ByIndex(3, aExpireTime);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = stmt->BindInt64ByIndex(4, aModificationTime);
|
|
break;
|
|
}
|
|
|
|
default: {
|
|
MOZ_ASSERT_UNREACHABLE("need a valid operation in UpdateDB()!");
|
|
rv = NS_ERROR_UNEXPECTED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("db change failed!");
|
|
return;
|
|
}
|
|
|
|
rv = stmt->Execute();
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}));
|
|
}
|
|
|
|
bool PermissionManager::GetPermissionsFromOriginOrKey(
|
|
const nsACString& aOrigin, const nsACString& aKey,
|
|
nsTArray<IPC::Permission>& aPerms) {
|
|
EnsureReadCompleted();
|
|
|
|
aPerms.Clear();
|
|
if (NS_WARN_IF(XRE_IsContentProcess())) {
|
|
return false;
|
|
}
|
|
|
|
for (auto iter = mPermissionTable.Iter(); !iter.Done(); iter.Next()) {
|
|
PermissionHashKey* entry = iter.Get();
|
|
|
|
nsAutoCString permissionKey;
|
|
if (aOrigin.IsEmpty()) {
|
|
// We can't check for individual OA strip perms here.
|
|
// Don't force strip origin attributes.
|
|
GetKeyForOrigin(entry->GetKey()->mOrigin, false, permissionKey);
|
|
|
|
// If the keys don't match, and we aren't getting the default "" key, then
|
|
// we can exit early. We have to keep looking if we're getting the default
|
|
// key, as we may see a preload permission which should be transmitted.
|
|
if (aKey != permissionKey && !aKey.IsEmpty()) {
|
|
continue;
|
|
}
|
|
} else if (aOrigin != entry->GetKey()->mOrigin) {
|
|
// If the origins don't match, then we can exit early. We have to keep
|
|
// looking if we're getting the default origin, as we may see a preload
|
|
// permission which should be transmitted.
|
|
continue;
|
|
}
|
|
|
|
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 send it to the content process.
|
|
if (permEntry.mPermission == nsIPermissionManager::UNKNOWN_ACTION) {
|
|
continue;
|
|
}
|
|
|
|
bool isPreload = IsPreloadPermission(mTypeArray[permEntry.mType]);
|
|
bool shouldAppend;
|
|
if (aOrigin.IsEmpty()) {
|
|
shouldAppend = (isPreload && aKey.IsEmpty()) ||
|
|
(!isPreload && aKey == permissionKey);
|
|
} else {
|
|
shouldAppend = (!isPreload && aOrigin == entry->GetKey()->mOrigin);
|
|
}
|
|
if (shouldAppend) {
|
|
aPerms.AppendElement(
|
|
IPC::Permission(entry->GetKey()->mOrigin,
|
|
mTypeArray[permEntry.mType], permEntry.mPermission,
|
|
permEntry.mExpireType, permEntry.mExpireTime));
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void PermissionManager::SetPermissionsWithKey(
|
|
const nsACString& aPermissionKey, nsTArray<IPC::Permission>& aPerms) {
|
|
if (NS_WARN_IF(XRE_IsParentProcess())) {
|
|
return;
|
|
}
|
|
|
|
RefPtr<GenericNonExclusivePromise::Private> promise;
|
|
bool foundKey =
|
|
mPermissionKeyPromiseMap.Get(aPermissionKey, getter_AddRefs(promise));
|
|
if (promise) {
|
|
MOZ_ASSERT(foundKey);
|
|
// NOTE: This will resolve asynchronously, so we can mark it as resolved
|
|
// now, and be confident that we will have filled in the database before any
|
|
// callbacks run.
|
|
promise->Resolve(true, __func__);
|
|
} else if (foundKey) {
|
|
// NOTE: We shouldn't be sent two InitializePermissionsWithKey for the same
|
|
// key, but it's possible.
|
|
return;
|
|
}
|
|
mPermissionKeyPromiseMap.InsertOrUpdate(
|
|
aPermissionKey, RefPtr<GenericNonExclusivePromise::Private>{});
|
|
|
|
// Add the permissions locally to our process
|
|
for (IPC::Permission& perm : aPerms) {
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
nsresult rv =
|
|
GetPrincipalFromOrigin(perm.origin, IsOAForceStripPermission(perm.type),
|
|
getter_AddRefs(principal));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
continue;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
nsAutoCString permissionKey;
|
|
GetKeyForPermission(principal, perm.type, permissionKey);
|
|
MOZ_ASSERT(permissionKey == aPermissionKey,
|
|
"The permission keys which were sent over should match!");
|
|
#endif
|
|
|
|
// 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 */);
|
|
}
|
|
}
|
|
|
|
/* static */
|
|
void PermissionManager::GetKeyForOrigin(const nsACString& aOrigin,
|
|
bool aForceStripOA, nsACString& aKey) {
|
|
aKey.Truncate();
|
|
|
|
// We only key origins for http, https, and ftp URIs. All origins begin with
|
|
// the URL which they apply to, which means that they should begin with their
|
|
// scheme in the case where they are one of these interesting URIs. We don't
|
|
// want to actually parse the URL here however, because this can be called on
|
|
// hot paths.
|
|
if (!StringBeginsWith(aOrigin, "http:"_ns) &&
|
|
!StringBeginsWith(aOrigin, "https:"_ns) &&
|
|
!StringBeginsWith(aOrigin, "ftp:"_ns)) {
|
|
return;
|
|
}
|
|
|
|
// We need to look at the originAttributes if they are present, to make sure
|
|
// to remove any which we don't want. We put the rest of the origin, not
|
|
// including the attributes, into the key.
|
|
OriginAttributes attrs;
|
|
if (!attrs.PopulateFromOrigin(aOrigin, aKey)) {
|
|
aKey.Truncate();
|
|
return;
|
|
}
|
|
|
|
MaybeStripOAs(aForceStripOA, attrs);
|
|
|
|
#ifdef DEBUG
|
|
// Parse the origin string into a principal, and extract some useful
|
|
// information from it for assertions.
|
|
nsCOMPtr<nsIPrincipal> dbgPrincipal;
|
|
MOZ_ALWAYS_SUCCEEDS(GetPrincipalFromOrigin(aOrigin, aForceStripOA,
|
|
getter_AddRefs(dbgPrincipal)));
|
|
MOZ_ASSERT(dbgPrincipal->SchemeIs("http") ||
|
|
dbgPrincipal->SchemeIs("https") || dbgPrincipal->SchemeIs("ftp"));
|
|
MOZ_ASSERT(dbgPrincipal->OriginAttributesRef() == attrs);
|
|
#endif
|
|
|
|
// Append the stripped suffix to the output origin key.
|
|
nsAutoCString suffix;
|
|
attrs.CreateSuffix(suffix);
|
|
aKey.Append(suffix);
|
|
}
|
|
|
|
/* static */
|
|
void PermissionManager::GetKeyForPrincipal(nsIPrincipal* aPrincipal,
|
|
bool aForceStripOA,
|
|
nsACString& aKey) {
|
|
nsAutoCString origin;
|
|
nsresult rv = aPrincipal->GetOrigin(origin);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
aKey.Truncate();
|
|
return;
|
|
}
|
|
GetKeyForOrigin(origin, aForceStripOA, aKey);
|
|
}
|
|
|
|
/* static */
|
|
void PermissionManager::GetKeyForPermission(nsIPrincipal* aPrincipal,
|
|
const nsACString& aType,
|
|
nsACString& aKey) {
|
|
// Preload permissions have the "" key.
|
|
if (IsPreloadPermission(aType)) {
|
|
aKey.Truncate();
|
|
return;
|
|
}
|
|
|
|
GetKeyForPrincipal(aPrincipal, IsOAForceStripPermission(aType), aKey);
|
|
}
|
|
|
|
/* static */
|
|
nsTArray<std::pair<nsCString, nsCString>>
|
|
PermissionManager::GetAllKeysForPrincipal(nsIPrincipal* aPrincipal) {
|
|
MOZ_ASSERT(aPrincipal);
|
|
|
|
nsTArray<std::pair<nsCString, nsCString>> pairs;
|
|
nsCOMPtr<nsIPrincipal> prin = aPrincipal;
|
|
while (prin) {
|
|
// Add the pair to the list
|
|
std::pair<nsCString, nsCString>* pair =
|
|
pairs.AppendElement(std::make_pair(""_ns, ""_ns));
|
|
// We can't check for individual OA strip perms here.
|
|
// Don't force strip origin attributes.
|
|
GetKeyForPrincipal(prin, false, pair->first);
|
|
|
|
Unused << GetOriginFromPrincipal(prin, false, pair->second);
|
|
prin = prin->GetNextSubDomainPrincipal();
|
|
// Get the next subdomain principal and loop back around.
|
|
}
|
|
|
|
MOZ_ASSERT(pairs.Length() >= 1,
|
|
"Every principal should have at least one pair item.");
|
|
return pairs;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PermissionManager::BroadcastPermissionsForPrincipalToAllContentProcesses(
|
|
nsIPrincipal* aPrincipal) {
|
|
nsTArray<ContentParent*> cps;
|
|
ContentParent::GetAll(cps);
|
|
for (ContentParent* cp : cps) {
|
|
nsresult rv = cp->TransmitPermissionsForPrincipal(aPrincipal);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
bool PermissionManager::PermissionAvailable(nsIPrincipal* aPrincipal,
|
|
const nsACString& aType) {
|
|
EnsureReadCompleted();
|
|
|
|
if (XRE_IsContentProcess()) {
|
|
nsAutoCString permissionKey;
|
|
// NOTE: GetKeyForPermission accepts a null aType.
|
|
GetKeyForPermission(aPrincipal, aType, permissionKey);
|
|
|
|
// If we have a pending promise for the permission key in question, we don't
|
|
// have the permission available, so report a warning and return false.
|
|
RefPtr<GenericNonExclusivePromise::Private> promise;
|
|
if (!mPermissionKeyPromiseMap.Get(permissionKey, getter_AddRefs(promise)) ||
|
|
promise) {
|
|
// Emit a useful diagnostic warning with the permissionKey for the process
|
|
// which hasn't received permissions yet.
|
|
NS_WARNING(nsPrintfCString("This content process hasn't received the "
|
|
"permissions for %s yet",
|
|
permissionKey.get())
|
|
.get());
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void PermissionManager::WhenPermissionsAvailable(nsIPrincipal* aPrincipal,
|
|
nsIRunnable* aRunnable) {
|
|
MOZ_ASSERT(aRunnable);
|
|
|
|
if (!XRE_IsContentProcess()) {
|
|
aRunnable->Run();
|
|
return;
|
|
}
|
|
|
|
nsTArray<RefPtr<GenericNonExclusivePromise>> promises;
|
|
for (auto& pair : GetAllKeysForPrincipal(aPrincipal)) {
|
|
RefPtr<GenericNonExclusivePromise::Private> promise;
|
|
if (!mPermissionKeyPromiseMap.Get(pair.first, getter_AddRefs(promise))) {
|
|
// In this case we have found a permission which isn't available in the
|
|
// content process and hasn't been requested yet. We need to create a new
|
|
// promise, and send the request to the parent (if we have not already
|
|
// done so).
|
|
promise = new GenericNonExclusivePromise::Private(__func__);
|
|
mPermissionKeyPromiseMap.InsertOrUpdate(pair.first, RefPtr{promise});
|
|
}
|
|
|
|
if (promise) {
|
|
promises.AppendElement(std::move(promise));
|
|
}
|
|
}
|
|
|
|
// If all of our permissions are available, immediately run the runnable. This
|
|
// avoids any extra overhead during fetch interception which is performance
|
|
// sensitive.
|
|
if (promises.IsEmpty()) {
|
|
aRunnable->Run();
|
|
return;
|
|
}
|
|
|
|
auto* thread = AbstractThread::MainThread();
|
|
|
|
RefPtr<nsIRunnable> runnable = aRunnable;
|
|
GenericNonExclusivePromise::All(thread, promises)
|
|
->Then(
|
|
thread, __func__, [runnable]() { runnable->Run(); },
|
|
[]() {
|
|
NS_WARNING(
|
|
"PermissionManager permission promise rejected. We're "
|
|
"probably shutting down.");
|
|
});
|
|
}
|
|
|
|
void PermissionManager::EnsureReadCompleted() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
if (mState == eInitializing) {
|
|
MonitorAutoLock lock(mMonitor);
|
|
|
|
while (mState == eInitializing) {
|
|
mMonitor.Wait();
|
|
}
|
|
}
|
|
|
|
switch (mState) {
|
|
case eInitializing:
|
|
MOZ_CRASH("This state is impossible!");
|
|
|
|
case eDBInitialized:
|
|
mState = eReady;
|
|
|
|
CompleteMigrations();
|
|
ImportLatestDefaults();
|
|
CompleteRead();
|
|
|
|
[[fallthrough]];
|
|
|
|
case eReady:
|
|
[[fallthrough]];
|
|
|
|
case eClosed:
|
|
return;
|
|
|
|
default:
|
|
MOZ_CRASH("Invalid state");
|
|
}
|
|
}
|
|
|
|
already_AddRefed<nsIInputStream> PermissionManager::GetDefaultsInputStream() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsAutoCString defaultsURL;
|
|
Preferences::GetCString(kDefaultsUrlPrefName, defaultsURL);
|
|
if (defaultsURL.IsEmpty()) { // == Don't use built-in permissions.
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIURI> defaultsURI;
|
|
nsresult rv = NS_NewURI(getter_AddRefs(defaultsURI), defaultsURL);
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
|
|
nsCOMPtr<nsIChannel> channel;
|
|
rv = NS_NewChannel(getter_AddRefs(channel), defaultsURI,
|
|
nsContentUtils::GetSystemPrincipal(),
|
|
nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
|
|
nsIContentPolicy::TYPE_OTHER);
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
|
|
nsCOMPtr<nsIInputStream> inputStream;
|
|
rv = channel->Open(getter_AddRefs(inputStream));
|
|
NS_ENSURE_SUCCESS(rv, nullptr);
|
|
|
|
return inputStream.forget();
|
|
}
|
|
|
|
void PermissionManager::ConsumeDefaultsInputStream(
|
|
nsIInputStream* aInputStream, const MonitorAutoLock& aProofOfLock) {
|
|
MOZ_ASSERT(!NS_IsMainThread());
|
|
|
|
constexpr char kMatchTypeHost[] = "host";
|
|
constexpr char kMatchTypeOrigin[] = "origin";
|
|
|
|
mDefaultEntries.Clear();
|
|
|
|
if (!aInputStream) {
|
|
return;
|
|
}
|
|
|
|
nsresult rv;
|
|
|
|
/* 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(aInputStream, &lineBuffer, line, &isMore);
|
|
NS_ENSURE_SUCCESS_VOID(rv);
|
|
|
|
if (line.IsEmpty() || line.First() == '#') {
|
|
continue;
|
|
}
|
|
|
|
nsTArray<nsCString> lineArray;
|
|
|
|
// Split the line at tabs
|
|
ParseString(line, '\t', lineArray);
|
|
|
|
if (lineArray.Length() != 4) {
|
|
continue;
|
|
}
|
|
|
|
nsresult error = NS_OK;
|
|
uint32_t permission = lineArray[2].ToInteger(&error);
|
|
if (NS_FAILED(error)) {
|
|
continue;
|
|
}
|
|
|
|
DefaultEntry::Op op;
|
|
|
|
if (lineArray[0].EqualsLiteral(kMatchTypeHost)) {
|
|
op = DefaultEntry::eImportMatchTypeHost;
|
|
} else if (lineArray[0].EqualsLiteral(kMatchTypeOrigin)) {
|
|
op = DefaultEntry::eImportMatchTypeOrigin;
|
|
} else {
|
|
continue;
|
|
}
|
|
|
|
DefaultEntry* entry = mDefaultEntries.AppendElement();
|
|
MOZ_ASSERT(entry);
|
|
|
|
entry->mOp = op;
|
|
entry->mPermission = permission;
|
|
entry->mHostOrOrigin = lineArray[3];
|
|
entry->mType = lineArray[1];
|
|
} while (isMore);
|
|
}
|
|
|
|
// ImportLatestDefaults will import the latest default cookies read during the
|
|
// last DB initialization.
|
|
nsresult PermissionManager::ImportLatestDefaults() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
MOZ_ASSERT(mState == eReady);
|
|
|
|
nsresult rv;
|
|
|
|
MonitorAutoLock lock(mMonitor);
|
|
|
|
for (const DefaultEntry& entry : mDefaultEntries) {
|
|
if (entry.mOp == DefaultEntry::eImportMatchTypeHost) {
|
|
// the import file format doesn't handle modification times, so we use
|
|
// 0, which AddInternal will convert to now()
|
|
int64_t modificationTime = 0;
|
|
|
|
rv = UpgradeHostToOriginAndInsert(
|
|
entry.mHostOrOrigin, entry.mType, entry.mPermission,
|
|
nsIPermissionManager::EXPIRE_NEVER, 0, modificationTime, false,
|
|
[&](const nsACString& aOrigin, const nsCString& aType,
|
|
uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
|
|
int64_t aModificationTime) {
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
nsresult rv =
|
|
GetPrincipalFromOrigin(aOrigin, IsOAForceStripPermission(aType),
|
|
getter_AddRefs(principal));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return AddInternal(
|
|
principal, aType, aPermission, cIDPermissionIsDefault,
|
|
aExpireType, aExpireTime, aModificationTime,
|
|
PermissionManager::eDontNotify,
|
|
PermissionManager::eNoDBOperation, false, &aOrigin);
|
|
});
|
|
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("There was a problem importing a host permission");
|
|
}
|
|
continue;
|
|
}
|
|
|
|
MOZ_ASSERT(entry.mOp == DefaultEntry::eImportMatchTypeOrigin);
|
|
|
|
nsCOMPtr<nsIPrincipal> principal;
|
|
rv = GetPrincipalFromOrigin(entry.mHostOrOrigin,
|
|
IsOAForceStripPermission(entry.mType),
|
|
getter_AddRefs(principal));
|
|
if (NS_FAILED(rv)) {
|
|
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;
|
|
|
|
rv = AddInternal(principal, entry.mType, entry.mPermission,
|
|
cIDPermissionIsDefault, nsIPermissionManager::EXPIRE_NEVER,
|
|
0, modificationTime, eDontNotify, eNoDBOperation);
|
|
if (NS_FAILED(rv)) {
|
|
NS_WARNING("There was a problem importing an origin permission");
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
/**
|
|
* Perform the early steps of a permission check and determine whether we need
|
|
* to call CommonTestPermissionInternal() for the actual permission check.
|
|
*
|
|
* @param aPrincipal optional principal argument to check the permission for,
|
|
* can be nullptr if we aren't performing a principal-based
|
|
* check.
|
|
* @param aTypeIndex if the caller isn't sure what the index of the permission
|
|
* type to check for is in the mTypeArray member variable,
|
|
* it should pass -1, otherwise this would be the index of
|
|
* the type inside mTypeArray. This would only be something
|
|
* other than -1 in recursive invocations of this function.
|
|
* @param aType the permission type to test.
|
|
* @param aPermission out argument which will be a permission type that we
|
|
* will return from this function once the function is
|
|
* done.
|
|
* @param aDefaultPermission the default permission to be used if we can't
|
|
* determine the result of the permission check.
|
|
* @param aDefaultPermissionIsValid whether the previous argument contains a
|
|
* valid value.
|
|
* @param aExactHostMatch whether to look for the exact host name or also for
|
|
* subdomains that can have the same permission.
|
|
* @param aIncludingSession whether to include session permissions when
|
|
* testing for the permission.
|
|
*/
|
|
PermissionManager::TestPreparationResult
|
|
PermissionManager::CommonPrepareToTestPermission(
|
|
nsIPrincipal* aPrincipal, int32_t aTypeIndex, const nsACString& aType,
|
|
uint32_t* aPermission, uint32_t aDefaultPermission,
|
|
bool aDefaultPermissionIsValid, bool aExactHostMatch,
|
|
bool aIncludingSession) {
|
|
auto* basePrin = BasePrincipal::Cast(aPrincipal);
|
|
if (basePrin && basePrin->IsSystemPrincipal()) {
|
|
*aPermission = ALLOW_ACTION;
|
|
return AsVariant(NS_OK);
|
|
}
|
|
|
|
EnsureReadCompleted();
|
|
|
|
// For some permissions, query the default from a pref. We want to avoid
|
|
// doing this for all permissions so that permissions can opt into having
|
|
// the pref lookup overhead on each call.
|
|
int32_t defaultPermission =
|
|
aDefaultPermissionIsValid ? aDefaultPermission : UNKNOWN_ACTION;
|
|
if (!aDefaultPermissionIsValid && HasDefaultPref(aType)) {
|
|
Unused << mDefaultPrefBranch->GetIntPref(PromiseFlatCString(aType).get(),
|
|
&defaultPermission);
|
|
}
|
|
|
|
// Set the default.
|
|
*aPermission = defaultPermission;
|
|
|
|
int32_t typeIndex =
|
|
aTypeIndex == -1 ? GetTypeIndex(aType, false) : aTypeIndex;
|
|
|
|
// For expanded principals, we want to iterate over the allowlist and see
|
|
// if the permission is granted for any of them.
|
|
if (basePrin && basePrin->Is<ExpandedPrincipal>()) {
|
|
auto ep = basePrin->As<ExpandedPrincipal>();
|
|
for (auto& prin : ep->AllowList()) {
|
|
uint32_t perm;
|
|
nsresult rv =
|
|
CommonTestPermission(prin, typeIndex, aType, &perm, defaultPermission,
|
|
true, aExactHostMatch, aIncludingSession);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return AsVariant(rv);
|
|
}
|
|
|
|
if (perm == nsIPermissionManager::ALLOW_ACTION) {
|
|
*aPermission = perm;
|
|
return AsVariant(NS_OK);
|
|
}
|
|
if (perm == nsIPermissionManager::PROMPT_ACTION) {
|
|
// Store it, but keep going to see if we can do better.
|
|
*aPermission = perm;
|
|
}
|
|
}
|
|
|
|
return AsVariant(NS_OK);
|
|
}
|
|
|
|
// If type == -1, the type isn't known, just signal that we are done.
|
|
if (typeIndex == -1) {
|
|
return AsVariant(NS_OK);
|
|
}
|
|
|
|
return AsVariant(typeIndex);
|
|
}
|
|
|
|
// If aTypeIndex is passed -1, we try to inder the type index from aType.
|
|
nsresult PermissionManager::CommonTestPermission(
|
|
nsIPrincipal* aPrincipal, int32_t aTypeIndex, const nsACString& aType,
|
|
uint32_t* aPermission, uint32_t aDefaultPermission,
|
|
bool aDefaultPermissionIsValid, bool aExactHostMatch,
|
|
bool aIncludingSession) {
|
|
auto preparationResult = CommonPrepareToTestPermission(
|
|
aPrincipal, aTypeIndex, aType, aPermission, aDefaultPermission,
|
|
aDefaultPermissionIsValid, aExactHostMatch, aIncludingSession);
|
|
if (preparationResult.is<nsresult>()) {
|
|
return preparationResult.as<nsresult>();
|
|
}
|
|
|
|
return CommonTestPermissionInternal(
|
|
aPrincipal, nullptr, nullptr, preparationResult.as<int32_t>(), aType,
|
|
aPermission, aExactHostMatch, aIncludingSession);
|
|
}
|
|
|
|
// If aTypeIndex is passed -1, we try to inder the type index from aType.
|
|
nsresult PermissionManager::CommonTestPermission(
|
|
nsIURI* aURI, int32_t aTypeIndex, const nsACString& aType,
|
|
uint32_t* aPermission, uint32_t aDefaultPermission,
|
|
bool aDefaultPermissionIsValid, bool aExactHostMatch,
|
|
bool aIncludingSession) {
|
|
auto preparationResult = CommonPrepareToTestPermission(
|
|
nullptr, aTypeIndex, aType, aPermission, aDefaultPermission,
|
|
aDefaultPermissionIsValid, aExactHostMatch, aIncludingSession);
|
|
if (preparationResult.is<nsresult>()) {
|
|
return preparationResult.as<nsresult>();
|
|
}
|
|
|
|
return CommonTestPermissionInternal(
|
|
nullptr, aURI, nullptr, preparationResult.as<int32_t>(), aType,
|
|
aPermission, aExactHostMatch, aIncludingSession);
|
|
}
|
|
|
|
nsresult PermissionManager::CommonTestPermission(
|
|
nsIURI* aURI, const OriginAttributes* aOriginAttributes, int32_t aTypeIndex,
|
|
const nsACString& aType, uint32_t* aPermission, uint32_t aDefaultPermission,
|
|
bool aDefaultPermissionIsValid, bool aExactHostMatch,
|
|
bool aIncludingSession) {
|
|
auto preparationResult = CommonPrepareToTestPermission(
|
|
nullptr, aTypeIndex, aType, aPermission, aDefaultPermission,
|
|
aDefaultPermissionIsValid, aExactHostMatch, aIncludingSession);
|
|
if (preparationResult.is<nsresult>()) {
|
|
return preparationResult.as<nsresult>();
|
|
}
|
|
|
|
return CommonTestPermissionInternal(
|
|
nullptr, aURI, aOriginAttributes, preparationResult.as<int32_t>(), aType,
|
|
aPermission, aExactHostMatch, aIncludingSession);
|
|
}
|
|
|
|
nsresult PermissionManager::TestPermissionWithoutDefaultsFromPrincipal(
|
|
nsIPrincipal* aPrincipal, const nsACString& aType, uint32_t* aPermission) {
|
|
MOZ_ASSERT(!HasDefaultPref(aType));
|
|
|
|
return CommonTestPermission(aPrincipal, -1, aType, aPermission,
|
|
nsIPermissionManager::UNKNOWN_ACTION, true, false,
|
|
true);
|
|
}
|
|
|
|
void PermissionManager::MaybeCompleteShutdown() {
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
nsCOMPtr<nsIAsyncShutdownClient> asc = GetShutdownPhase();
|
|
MOZ_ASSERT(asc);
|
|
|
|
DebugOnly<nsresult> rv = asc->RemoveBlocker(this);
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
}
|
|
|
|
// Async shutdown blocker methods
|
|
|
|
NS_IMETHODIMP PermissionManager::GetName(nsAString& aName) {
|
|
aName = u"PermissionManager: Flushing data"_ns;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP PermissionManager::BlockShutdown(
|
|
nsIAsyncShutdownClient* aClient) {
|
|
RemoveIdleDailyMaintenanceJob();
|
|
RemoveAllFromMemory();
|
|
CloseDB(eShutdown);
|
|
|
|
gPermissionManager = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
PermissionManager::GetState(nsIPropertyBag** aBagOut) {
|
|
nsCOMPtr<nsIWritablePropertyBag2> propertyBag =
|
|
do_CreateInstance("@mozilla.org/hash-property-bag;1");
|
|
|
|
nsresult rv = propertyBag->SetPropertyAsInt32(u"state"_ns, mState);
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
return rv;
|
|
}
|
|
|
|
propertyBag.forget(aBagOut);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsCOMPtr<nsIAsyncShutdownClient> PermissionManager::GetShutdownPhase() const {
|
|
nsresult rv;
|
|
nsCOMPtr<nsIAsyncShutdownService> svc =
|
|
do_GetService("@mozilla.org/async-shutdown-service;1", &rv);
|
|
if (NS_FAILED(rv)) {
|
|
return nullptr;
|
|
}
|
|
|
|
nsCOMPtr<nsIAsyncShutdownClient> client;
|
|
rv = svc->GetProfileBeforeChange(getter_AddRefs(client));
|
|
MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
|
|
|
|
return client;
|
|
}
|
|
|
|
} // namespace mozilla
|