mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 14:22:01 +00:00
1100 lines
32 KiB
C++
1100 lines
32 KiB
C++
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
#include "mozilla/dom/ContentParent.h"
|
|
#include "mozilla/dom/ContentChild.h"
|
|
#include "mozilla/unused.h"
|
|
#include "nsPermissionManager.h"
|
|
#include "nsPermission.h"
|
|
#include "nsCRT.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsCOMArray.h"
|
|
#include "nsArrayEnumerator.h"
|
|
#include "nsTArray.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsILineInputStream.h"
|
|
#include "nsIIDNService.h"
|
|
#include "nsAppDirectoryServiceDefs.h"
|
|
#include "prprf.h"
|
|
#include "mozIStorageService.h"
|
|
#include "mozIStorageStatement.h"
|
|
#include "mozIStorageConnection.h"
|
|
#include "mozStorageHelper.h"
|
|
#include "mozStorageCID.h"
|
|
#include "nsXULAppAPI.h"
|
|
|
|
static nsPermissionManager *gPermissionManager = nsnull;
|
|
|
|
using mozilla::dom::ContentParent;
|
|
using mozilla::dom::ContentChild;
|
|
using mozilla::unused; // ha!
|
|
|
|
static bool
|
|
IsChildProcess()
|
|
{
|
|
return XRE_GetProcessType() == GeckoProcessType_Content;
|
|
}
|
|
|
|
/**
|
|
* @returns The child process object, or if we are not in the child
|
|
* process, nsnull.
|
|
*/
|
|
static ContentChild*
|
|
ChildProcess()
|
|
{
|
|
if (IsChildProcess()) {
|
|
ContentChild* cpc = ContentChild::GetSingleton();
|
|
if (!cpc)
|
|
NS_RUNTIMEABORT("Content Process is NULL!");
|
|
return cpc;
|
|
}
|
|
|
|
return nsnull;
|
|
}
|
|
|
|
|
|
#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 PL_ARENA_CONST_ALIGN_MASK 3
|
|
#include "plarena.h"
|
|
|
|
static PLArenaPool *gHostArena = nsnull;
|
|
|
|
// making sHostArena 512b for nice allocation
|
|
// growing is quite cheap
|
|
#define HOST_ARENA_SIZE 512
|
|
|
|
// equivalent to strdup() - does no error checking,
|
|
// we're assuming we're only called with a valid pointer
|
|
static char *
|
|
ArenaStrDup(const char* str, PLArenaPool* aArena)
|
|
{
|
|
void* mem;
|
|
const PRUint32 size = strlen(str) + 1;
|
|
PL_ARENA_ALLOCATE(mem, aArena, size);
|
|
if (mem)
|
|
memcpy(mem, str, size);
|
|
return static_cast<char*>(mem);
|
|
}
|
|
|
|
nsHostEntry::nsHostEntry(const char* aHost)
|
|
{
|
|
mHost = ArenaStrDup(aHost, gHostArena);
|
|
}
|
|
|
|
// XXX this can fail on OOM
|
|
nsHostEntry::nsHostEntry(const nsHostEntry& toCopy)
|
|
: mHost(toCopy.mHost)
|
|
, mPermissions(toCopy.mPermissions)
|
|
{
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////////////////////
|
|
// nsPermissionManager Implementation
|
|
|
|
static const char kPermissionsFileName[] = "permissions.sqlite";
|
|
#define HOSTS_SCHEMA_VERSION 2
|
|
|
|
static const char kHostpermFileName[] = "hostperm.1";
|
|
|
|
static const char kPermissionChangeNotification[] = PERM_CHANGE_NOTIFICATION;
|
|
|
|
NS_IMPL_ISUPPORTS3(nsPermissionManager, nsIPermissionManager, nsIObserver, nsISupportsWeakReference)
|
|
|
|
nsPermissionManager::nsPermissionManager()
|
|
: mLargestID(0)
|
|
{
|
|
}
|
|
|
|
nsPermissionManager::~nsPermissionManager()
|
|
{
|
|
RemoveAllFromMemory();
|
|
gPermissionManager = nsnull;
|
|
}
|
|
|
|
// static
|
|
nsIPermissionManager*
|
|
nsPermissionManager::GetXPCOMSingleton()
|
|
{
|
|
if (gPermissionManager) {
|
|
NS_ADDREF(gPermissionManager);
|
|
return gPermissionManager;
|
|
}
|
|
|
|
// Create a new singleton nsPermissionManager.
|
|
// We AddRef only once since XPCOM has rules about the ordering of module
|
|
// teardowns - by the time our module destructor is called, it's too late to
|
|
// Release our members, since GC cycles have already been completed and
|
|
// would result in serious leaks.
|
|
// See bug 209571.
|
|
gPermissionManager = new nsPermissionManager();
|
|
if (gPermissionManager) {
|
|
NS_ADDREF(gPermissionManager);
|
|
if (NS_FAILED(gPermissionManager->Init())) {
|
|
NS_RELEASE(gPermissionManager);
|
|
}
|
|
}
|
|
|
|
return gPermissionManager;
|
|
}
|
|
|
|
nsresult
|
|
nsPermissionManager::Init()
|
|
{
|
|
nsresult rv;
|
|
|
|
mHostTable.Init();
|
|
|
|
mObserverService = do_GetService("@mozilla.org/observer-service;1", &rv);
|
|
if (NS_SUCCEEDED(rv)) {
|
|
mObserverService->AddObserver(this, "profile-before-change", true);
|
|
mObserverService->AddObserver(this, "profile-do-change", true);
|
|
}
|
|
|
|
if (IsChildProcess()) {
|
|
// Get the permissions from the parent process
|
|
InfallibleTArray<IPC::Permission> perms;
|
|
ChildProcess()->SendReadPermissions(&perms);
|
|
|
|
for (PRUint32 i = 0; i < perms.Length(); i++) {
|
|
const IPC::Permission &perm = perms[i];
|
|
AddInternal(perm.host, perm.type, perm.capability, 0, perm.expireType,
|
|
perm.expireTime, eNotify, eNoDBOperation);
|
|
}
|
|
|
|
// Stop here; we don't need the DB in the child process
|
|
return NS_OK;
|
|
}
|
|
|
|
// ignore failure here, since it's non-fatal (we can run fine without
|
|
// persistent storage - e.g. if there's no profile).
|
|
// XXX should we tell the user about this?
|
|
InitDB(false);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsPermissionManager::InitDB(bool aRemoveFile)
|
|
{
|
|
nsCOMPtr<nsIFile> permissionsFile;
|
|
NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(permissionsFile));
|
|
if (!permissionsFile)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
nsresult rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(kPermissionsFileName));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
if (aRemoveFile) {
|
|
bool exists = false;
|
|
rv = permissionsFile->Exists(&exists);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (exists) {
|
|
rv = permissionsFile->Remove(false);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageService> storage = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID);
|
|
if (!storage)
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
// cache a connection to the hosts database
|
|
rv = storage->OpenDatabase(permissionsFile, getter_AddRefs(mDBConn));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
bool ready;
|
|
mDBConn->GetConnectionReady(&ready);
|
|
if (!ready) {
|
|
// delete and try again
|
|
rv = permissionsFile->Remove(false);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = storage->OpenDatabase(permissionsFile, getter_AddRefs(mDBConn));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
mDBConn->GetConnectionReady(&ready);
|
|
if (!ready)
|
|
return NS_ERROR_UNEXPECTED;
|
|
}
|
|
|
|
bool tableExists = false;
|
|
mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts"), &tableExists);
|
|
if (!tableExists) {
|
|
rv = CreateTable();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
} else {
|
|
// table already exists; check the schema version before reading
|
|
PRInt32 dbSchemaVersion;
|
|
rv = mDBConn->GetSchemaVersion(&dbSchemaVersion);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
switch (dbSchemaVersion) {
|
|
// upgrading.
|
|
// every time you increment the database schema, you need to implement
|
|
// the upgrading code from the previous version to the new one.
|
|
// fall through to current version
|
|
|
|
case 1:
|
|
{
|
|
// previous non-expiry version of database. Upgrade it by adding the
|
|
// expiration columns
|
|
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"ALTER TABLE moz_hosts ADD expireType INTEGER"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"ALTER TABLE moz_hosts ADD expireTime INTEGER"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
// fall through to the next upgrade
|
|
|
|
// current version.
|
|
case HOSTS_SCHEMA_VERSION:
|
|
break;
|
|
|
|
case 0:
|
|
{
|
|
NS_WARNING("couldn't get schema version!");
|
|
|
|
// the table may be usable; someone might've just clobbered the schema
|
|
// version. we can treat this case like a downgrade using the codepath
|
|
// below, by verifying the columns we care about are all there. for now,
|
|
// re-set the schema version in the db, in case the checks succeed (if
|
|
// they don't, we're dropping the table anyway).
|
|
rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
// fall through to downgrade check
|
|
|
|
// downgrading.
|
|
// if columns have been added to the table, we can still use the ones we
|
|
// understand safely. if columns have been deleted or altered, just
|
|
// blow away the table and start from scratch! if you change the way
|
|
// a column is interpreted, make sure you also change its name so this
|
|
// check will catch it.
|
|
default:
|
|
{
|
|
// check if all the expected columns exist
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
|
|
"SELECT host, type, permission, expireType, expireTime FROM moz_hosts"),
|
|
getter_AddRefs(stmt));
|
|
if (NS_SUCCEEDED(rv))
|
|
break;
|
|
|
|
// our columns aren't there - drop the table!
|
|
rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DROP TABLE moz_hosts"));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = CreateTable();
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
// make operations on the table asynchronous, for performance
|
|
mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("PRAGMA synchronous = OFF"));
|
|
|
|
// cache frequently used statements (for insertion, deletion, and updating)
|
|
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
|
|
"INSERT INTO moz_hosts "
|
|
"(id, host, type, permission, expireType, expireTime) "
|
|
"VALUES (?1, ?2, ?3, ?4, ?5, ?6)"), getter_AddRefs(mStmtInsert));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM moz_hosts "
|
|
"WHERE id = ?1"), getter_AddRefs(mStmtDelete));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
|
|
"UPDATE moz_hosts "
|
|
"SET permission = ?2, expireType= ?3, expireTime = ?4 WHERE id = ?1"),
|
|
getter_AddRefs(mStmtUpdate));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// check whether to import or just read in the db
|
|
if (tableExists)
|
|
return Read();
|
|
|
|
return Import();
|
|
}
|
|
|
|
// sets the schema version and creates the moz_hosts table.
|
|
nsresult
|
|
nsPermissionManager::CreateTable()
|
|
{
|
|
// set the schema version, before creating the table
|
|
nsresult rv = mDBConn->SetSchemaVersion(HOSTS_SCHEMA_VERSION);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
// create the table
|
|
// SQL also lives in automation.py.in. If you change this SQL change that
|
|
// one too.
|
|
return mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
|
|
"CREATE TABLE moz_hosts ("
|
|
" id INTEGER PRIMARY KEY"
|
|
",host TEXT"
|
|
",type TEXT"
|
|
",permission INTEGER"
|
|
",expireType INTEGER"
|
|
",expireTime INTEGER"
|
|
")"));
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsPermissionManager::Add(nsIURI *aURI,
|
|
const char *aType,
|
|
PRUint32 aPermission,
|
|
PRUint32 aExpireType,
|
|
PRInt64 aExpireTime)
|
|
{
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
|
|
NS_ENSURE_ARG_POINTER(aURI);
|
|
NS_ENSURE_ARG_POINTER(aType);
|
|
NS_ENSURE_TRUE(aExpireType == nsIPermissionManager::EXPIRE_NEVER ||
|
|
aExpireType == nsIPermissionManager::EXPIRE_TIME ||
|
|
aExpireType == nsIPermissionManager::EXPIRE_SESSION,
|
|
NS_ERROR_INVALID_ARG);
|
|
|
|
nsresult rv;
|
|
|
|
// Skip addition if the permission is already expired.
|
|
if (aExpireType == nsIPermissionManager::EXPIRE_TIME &&
|
|
aExpireTime <= PR_Now() / 1000)
|
|
return NS_OK;
|
|
|
|
nsCAutoString host;
|
|
rv = GetHost(aURI, host);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
return AddInternal(host, nsDependentCString(aType), aPermission, 0,
|
|
aExpireType, aExpireTime, eNotify, eWriteToDB);
|
|
}
|
|
|
|
nsresult
|
|
nsPermissionManager::AddInternal(const nsAFlatCString &aHost,
|
|
const nsAFlatCString &aType,
|
|
PRUint32 aPermission,
|
|
PRInt64 aID,
|
|
PRUint32 aExpireType,
|
|
PRInt64 aExpireTime,
|
|
NotifyOperationType aNotifyOperation,
|
|
DBOperationType aDBOperation)
|
|
{
|
|
if (!IsChildProcess()) {
|
|
IPC::Permission permission((aHost),
|
|
(aType),
|
|
aPermission, aExpireType, aExpireTime);
|
|
|
|
nsTArray<ContentParent*> cplist;
|
|
ContentParent::GetAll(cplist);
|
|
for (PRUint32 i = 0; i < cplist.Length(); ++i) {
|
|
ContentParent* cp = cplist[i];
|
|
if (cp->NeedsPermissionsUpdate())
|
|
unused << cp->SendAddPermission(permission);
|
|
}
|
|
}
|
|
|
|
if (!gHostArena) {
|
|
gHostArena = new PLArenaPool;
|
|
if (!gHostArena)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
PL_INIT_ARENA_POOL(gHostArena, "PermissionHostArena", HOST_ARENA_SIZE);
|
|
}
|
|
|
|
// look up the type index
|
|
PRInt32 typeIndex = GetTypeIndex(aType.get(), true);
|
|
NS_ENSURE_TRUE(typeIndex != -1, NS_ERROR_OUT_OF_MEMORY);
|
|
|
|
// When an entry already exists, PutEntry will return that, instead
|
|
// of adding a new one
|
|
nsHostEntry *entry = mHostTable.PutEntry(aHost.get());
|
|
if (!entry) return NS_ERROR_FAILURE;
|
|
if (!entry->GetKey()) {
|
|
mHostTable.RawRemoveEntry(entry);
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
|
|
// figure out the transaction type, and get any existing permission value
|
|
OperationType op;
|
|
PRInt32 index = entry->GetPermissionIndex(typeIndex);
|
|
if (index == -1) {
|
|
if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
|
|
op = eOperationNone;
|
|
else
|
|
op = eOperationAdding;
|
|
|
|
} else {
|
|
nsPermissionEntry 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_TIME ||
|
|
aExpireTime == oldPermissionEntry.mExpireTime))
|
|
op = eOperationNone;
|
|
else if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
|
|
op = eOperationRemoving;
|
|
else
|
|
op = eOperationChanging;
|
|
}
|
|
|
|
// do the work for adding, deleting, or changing a permission:
|
|
// update the in-memory list, write to the db, and notify consumers.
|
|
PRInt64 id;
|
|
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(nsPermissionEntry(typeIndex, aPermission, id, aExpireType, aExpireTime));
|
|
|
|
if (aDBOperation == eWriteToDB && aExpireType != nsIPermissionManager::EXPIRE_SESSION)
|
|
UpdateDB(op, mStmtInsert, id, aHost, aType, aPermission, aExpireType, aExpireTime);
|
|
|
|
if (aNotifyOperation == eNotify) {
|
|
NotifyObserversWithPermission(aHost,
|
|
mTypeArray[typeIndex],
|
|
aPermission,
|
|
aExpireType,
|
|
aExpireTime,
|
|
NS_LITERAL_STRING("added").get());
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case eOperationRemoving:
|
|
{
|
|
nsPermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
|
|
id = oldPermissionEntry.mID;
|
|
entry->GetPermissions().RemoveElementAt(index);
|
|
|
|
// If no more types are present, remove the entry
|
|
if (entry->GetPermissions().IsEmpty())
|
|
mHostTable.RawRemoveEntry(entry);
|
|
|
|
if (aDBOperation == eWriteToDB)
|
|
UpdateDB(op, mStmtDelete, id, EmptyCString(), EmptyCString(), 0,
|
|
nsIPermissionManager::EXPIRE_NEVER, 0);
|
|
|
|
if (aNotifyOperation == eNotify) {
|
|
NotifyObserversWithPermission(aHost,
|
|
mTypeArray[typeIndex],
|
|
oldPermissionEntry.mPermission,
|
|
oldPermissionEntry.mExpireType,
|
|
oldPermissionEntry.mExpireTime,
|
|
NS_LITERAL_STRING("deleted").get());
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
case eOperationChanging:
|
|
{
|
|
id = entry->GetPermissions()[index].mID;
|
|
entry->GetPermissions()[index].mPermission = aPermission;
|
|
|
|
if (aDBOperation == eWriteToDB && aExpireType != nsIPermissionManager::EXPIRE_SESSION)
|
|
UpdateDB(op, mStmtUpdate, id, EmptyCString(), EmptyCString(), aPermission, aExpireType, aExpireTime);
|
|
|
|
if (aNotifyOperation == eNotify) {
|
|
NotifyObserversWithPermission(aHost,
|
|
mTypeArray[typeIndex],
|
|
aPermission,
|
|
aExpireType,
|
|
aExpireTime,
|
|
NS_LITERAL_STRING("changed").get());
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsPermissionManager::Remove(const nsACString &aHost,
|
|
const char *aType)
|
|
{
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
|
|
NS_ENSURE_ARG_POINTER(aType);
|
|
|
|
// AddInternal() handles removal, just let it do the work
|
|
return AddInternal(PromiseFlatCString(aHost),
|
|
nsDependentCString(aType),
|
|
nsIPermissionManager::UNKNOWN_ACTION,
|
|
0,
|
|
nsIPermissionManager::EXPIRE_NEVER,
|
|
0,
|
|
eNotify,
|
|
eWriteToDB);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsPermissionManager::RemoveAll()
|
|
{
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
|
|
nsresult rv = RemoveAllInternal();
|
|
NotifyObservers(nsnull, NS_LITERAL_STRING("cleared").get());
|
|
return rv;
|
|
}
|
|
|
|
void
|
|
nsPermissionManager::CloseDB()
|
|
{
|
|
// Null the statements, this will finalize them.
|
|
mStmtInsert = nsnull;
|
|
mStmtDelete = nsnull;
|
|
mStmtUpdate = nsnull;
|
|
if (mDBConn) {
|
|
mozilla::DebugOnly<nsresult> rv = mDBConn->Close();
|
|
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
|
mDBConn = nsnull;
|
|
}
|
|
}
|
|
|
|
nsresult
|
|
nsPermissionManager::RemoveAllInternal()
|
|
{
|
|
RemoveAllFromMemory();
|
|
|
|
// clear the db
|
|
if (mDBConn) {
|
|
nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_hosts"));
|
|
if (NS_FAILED(rv)) {
|
|
CloseDB();
|
|
rv = InitDB(true);
|
|
return rv;
|
|
}
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsPermissionManager::TestExactPermission(nsIURI *aURI,
|
|
const char *aType,
|
|
PRUint32 *aPermission)
|
|
{
|
|
return CommonTestPermission(aURI, aType, aPermission, true);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsPermissionManager::TestPermission(nsIURI *aURI,
|
|
const char *aType,
|
|
PRUint32 *aPermission)
|
|
{
|
|
return CommonTestPermission(aURI, aType, aPermission, false);
|
|
}
|
|
|
|
nsresult
|
|
nsPermissionManager::CommonTestPermission(nsIURI *aURI,
|
|
const char *aType,
|
|
PRUint32 *aPermission,
|
|
bool aExactHostMatch)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aURI);
|
|
NS_ENSURE_ARG_POINTER(aType);
|
|
|
|
// set the default
|
|
*aPermission = nsIPermissionManager::UNKNOWN_ACTION;
|
|
|
|
nsCAutoString host;
|
|
nsresult rv = GetHost(aURI, host);
|
|
// No host doesn't mean an error. Just return the default. Unless this is
|
|
// a file uri. In that case use a magic host.
|
|
if (NS_FAILED(rv)) {
|
|
bool isFile;
|
|
rv = aURI->SchemeIs("file", &isFile);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
if (isFile) {
|
|
host.AssignLiteral("<file>");
|
|
}
|
|
else {
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
PRInt32 typeIndex = GetTypeIndex(aType, false);
|
|
// If type == -1, the type isn't known,
|
|
// so just return NS_OK
|
|
if (typeIndex == -1) return NS_OK;
|
|
|
|
nsHostEntry *entry = GetHostEntry(host, typeIndex, aExactHostMatch);
|
|
if (entry)
|
|
*aPermission = entry->GetPermission(typeIndex).mPermission;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
// Get hostentry for given host string and permission type.
|
|
// walk up the domain if needed.
|
|
// return null if nothing found.
|
|
// Also accepts host on the format "<foo>". This will perform an exact match
|
|
// lookup as the string doesn't contain any dots.
|
|
nsHostEntry *
|
|
nsPermissionManager::GetHostEntry(const nsAFlatCString &aHost,
|
|
PRUint32 aType,
|
|
bool aExactHostMatch)
|
|
{
|
|
PRUint32 offset = 0;
|
|
nsHostEntry *entry;
|
|
PRInt64 now = PR_Now() / 1000;
|
|
|
|
do {
|
|
entry = mHostTable.GetEntry(aHost.get() + offset);
|
|
if (entry) {
|
|
nsPermissionEntry permEntry = entry->GetPermission(aType);
|
|
|
|
// if the entry is expired, remove and keep looking for others.
|
|
if (permEntry.mExpireType == nsIPermissionManager::EXPIRE_TIME &&
|
|
permEntry.mExpireTime <= now)
|
|
Remove(aHost, mTypeArray[aType].get());
|
|
else if (permEntry.mPermission != nsIPermissionManager::UNKNOWN_ACTION)
|
|
break;
|
|
|
|
// reset entry, to be able to return null on failure
|
|
entry = nsnull;
|
|
}
|
|
if (aExactHostMatch)
|
|
break; // do not try super domains
|
|
|
|
offset = aHost.FindChar('.', offset) + 1;
|
|
|
|
// walk up the domaintree (we stop as soon as we find a match,
|
|
// which will be the most specific domain we have an entry for).
|
|
} while (offset > 0);
|
|
return entry;
|
|
}
|
|
|
|
// helper struct for passing arguments into hash enumeration callback.
|
|
struct nsGetEnumeratorData
|
|
{
|
|
nsGetEnumeratorData(nsCOMArray<nsIPermission> *aArray, const nsTArray<nsCString> *aTypes)
|
|
: array(aArray)
|
|
, types(aTypes) {}
|
|
|
|
nsCOMArray<nsIPermission> *array;
|
|
const nsTArray<nsCString> *types;
|
|
};
|
|
|
|
static PLDHashOperator
|
|
AddPermissionsToList(nsHostEntry *entry, void *arg)
|
|
{
|
|
nsGetEnumeratorData *data = static_cast<nsGetEnumeratorData *>(arg);
|
|
|
|
for (PRUint32 i = 0; i < entry->GetPermissions().Length(); ++i) {
|
|
nsPermissionEntry &permEntry = entry->GetPermissions()[i];
|
|
|
|
nsPermission *perm = new nsPermission(entry->GetHost(),
|
|
data->types->ElementAt(permEntry.mType),
|
|
permEntry.mPermission,
|
|
permEntry.mExpireType,
|
|
permEntry.mExpireTime);
|
|
|
|
data->array->AppendObject(perm);
|
|
}
|
|
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
NS_IMETHODIMP nsPermissionManager::GetEnumerator(nsISimpleEnumerator **aEnum)
|
|
{
|
|
// roll an nsCOMArray of all our permissions, then hand out an enumerator
|
|
nsCOMArray<nsIPermission> array;
|
|
nsGetEnumeratorData data(&array, &mTypeArray);
|
|
|
|
mHostTable.EnumerateEntries(AddPermissionsToList, &data);
|
|
|
|
return NS_NewArrayEnumerator(aEnum, array);
|
|
}
|
|
|
|
NS_IMETHODIMP nsPermissionManager::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData)
|
|
{
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
|
|
if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
|
|
// The profile is about to change,
|
|
// or is going away because the application is shutting down.
|
|
if (!nsCRT::strcmp(someData, NS_LITERAL_STRING("shutdown-cleanse").get())) {
|
|
// clear the permissions file
|
|
RemoveAllInternal();
|
|
} else {
|
|
RemoveAllFromMemory();
|
|
}
|
|
CloseDB();
|
|
}
|
|
else if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
|
|
// the profile has already changed; init the db from the new location
|
|
InitDB(false);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
//*****************************************************************************
|
|
//*** nsPermissionManager private methods
|
|
//*****************************************************************************
|
|
|
|
nsresult
|
|
nsPermissionManager::RemoveAllFromMemory()
|
|
{
|
|
mLargestID = 0;
|
|
mTypeArray.Clear();
|
|
mHostTable.Clear();
|
|
if (gHostArena) {
|
|
PL_FinishArenaPool(gHostArena);
|
|
delete gHostArena;
|
|
}
|
|
gHostArena = nsnull;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Returns -1 on failure
|
|
PRInt32
|
|
nsPermissionManager::GetTypeIndex(const char *aType,
|
|
bool aAdd)
|
|
{
|
|
for (PRUint32 i = 0; i < mTypeArray.Length(); ++i)
|
|
if (mTypeArray[i].Equals(aType))
|
|
return i;
|
|
|
|
if (!aAdd) {
|
|
// Not found, but that is ok - we were just looking.
|
|
return -1;
|
|
}
|
|
|
|
// This type was not registered before.
|
|
// append it to the array, without copy-constructing the string
|
|
nsCString *elem = mTypeArray.AppendElement();
|
|
if (!elem)
|
|
return -1;
|
|
|
|
elem->Assign(aType);
|
|
return mTypeArray.Length() - 1;
|
|
}
|
|
|
|
// wrapper function for mangling (host,type,perm,expireType,expireTime)
|
|
// set into an nsIPermission.
|
|
void
|
|
nsPermissionManager::NotifyObserversWithPermission(const nsACString &aHost,
|
|
const nsCString &aType,
|
|
PRUint32 aPermission,
|
|
PRUint32 aExpireType,
|
|
PRInt64 aExpireTime,
|
|
const PRUnichar *aData)
|
|
{
|
|
nsCOMPtr<nsIPermission> permission =
|
|
new nsPermission(aHost, aType, aPermission, aExpireType, aExpireTime);
|
|
if (permission)
|
|
NotifyObservers(permission, aData);
|
|
}
|
|
|
|
// notify observers that the permission list changed. there are four possible
|
|
// values for aData:
|
|
// "deleted" means a permission was deleted. aPermission is the deleted permission.
|
|
// "added" means a permission was added. aPermission is the added permission.
|
|
// "changed" means a permission was altered. aPermission is the new permission.
|
|
// "cleared" means the entire permission list was cleared. aPermission is null.
|
|
void
|
|
nsPermissionManager::NotifyObservers(nsIPermission *aPermission,
|
|
const PRUnichar *aData)
|
|
{
|
|
if (mObserverService)
|
|
mObserverService->NotifyObservers(aPermission,
|
|
kPermissionChangeNotification,
|
|
aData);
|
|
}
|
|
|
|
nsresult
|
|
nsPermissionManager::Read()
|
|
{
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
|
|
nsresult rv;
|
|
|
|
// delete expired permissions before we read in the db
|
|
{
|
|
// this deletion has its own scope so the write lock is released when done.
|
|
nsCOMPtr<mozIStorageStatement> stmtDeleteExpired;
|
|
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
|
|
"DELETE FROM moz_hosts WHERE expireType = ?1 AND expireTime <= ?2"),
|
|
getter_AddRefs(stmtDeleteExpired));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = stmtDeleteExpired->BindInt32ByIndex(0, nsIPermissionManager::EXPIRE_TIME);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = stmtDeleteExpired->BindInt64ByIndex(1, PR_Now() / 1000);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
bool hasResult;
|
|
rv = stmtDeleteExpired->ExecuteStep(&hasResult);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
nsCOMPtr<mozIStorageStatement> stmt;
|
|
rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
|
|
"SELECT id, host, type, permission, expireType, expireTime "
|
|
"FROM moz_hosts"), getter_AddRefs(stmt));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
PRInt64 id;
|
|
nsCAutoString host, type;
|
|
PRUint32 permission;
|
|
PRUint32 expireType;
|
|
PRInt64 expireTime;
|
|
bool hasResult;
|
|
while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
|
|
// explicitly set our entry id counter for use in AddInternal(),
|
|
// and keep track of the largest id so we know where to pick up.
|
|
id = stmt->AsInt64(0);
|
|
if (id > mLargestID)
|
|
mLargestID = id;
|
|
|
|
rv = stmt->GetUTF8String(1, host);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
rv = stmt->GetUTF8String(2, type);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
permission = stmt->AsInt32(3);
|
|
expireType = stmt->AsInt32(4);
|
|
|
|
// convert into PRInt64 value (milliseconds)
|
|
expireTime = stmt->AsInt64(5);
|
|
|
|
rv = AddInternal(host, type, permission, id, expireType, expireTime,
|
|
eDontNotify, eNoDBOperation);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
static const char kMatchTypeHost[] = "host";
|
|
|
|
nsresult
|
|
nsPermissionManager::Import()
|
|
{
|
|
ENSURE_NOT_CHILD_PROCESS;
|
|
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIFile> permissionsFile;
|
|
rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(permissionsFile));
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(kHostpermFileName));
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
nsCOMPtr<nsIInputStream> fileInputStream;
|
|
rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
|
|
permissionsFile);
|
|
if (NS_FAILED(rv)) return rv;
|
|
|
|
nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
|
|
// start a transaction on the storage db, to optimize insertions.
|
|
// transaction will automically commit on completion
|
|
mozStorageTransaction transaction(mDBConn, true);
|
|
|
|
/* 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
|
|
*/
|
|
|
|
nsCAutoString buffer;
|
|
bool isMore = true;
|
|
while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
|
|
if (buffer.IsEmpty() || buffer.First() == '#') {
|
|
continue;
|
|
}
|
|
|
|
nsTArray<nsCString> lineArray;
|
|
|
|
// Split the line at tabs
|
|
ParseString(buffer, '\t', lineArray);
|
|
|
|
if (lineArray[0].EqualsLiteral(kMatchTypeHost) &&
|
|
lineArray.Length() == 4) {
|
|
|
|
PRInt32 error;
|
|
PRUint32 permission = lineArray[2].ToInteger(&error);
|
|
if (error)
|
|
continue;
|
|
|
|
// hosts might be encoded in UTF8; switch them to ACE to be consistent
|
|
if (!IsASCII(lineArray[3])) {
|
|
rv = NormalizeToACE(lineArray[3]);
|
|
if (NS_FAILED(rv))
|
|
continue;
|
|
}
|
|
|
|
rv = AddInternal(lineArray[3], lineArray[1], permission, 0,
|
|
nsIPermissionManager::EXPIRE_NEVER, 0, eDontNotify, eWriteToDB);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
}
|
|
|
|
// we're done importing - delete the old file
|
|
permissionsFile->Remove(false);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsPermissionManager::NormalizeToACE(nsCString &aHost)
|
|
{
|
|
// lazily init the IDN service
|
|
if (!mIDNService) {
|
|
nsresult rv;
|
|
mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
|
|
NS_ENSURE_SUCCESS(rv, rv);
|
|
}
|
|
|
|
return mIDNService->ConvertUTF8toACE(aHost, aHost);
|
|
}
|
|
|
|
nsresult
|
|
nsPermissionManager::GetHost(nsIURI *aURI, nsACString &aResult)
|
|
{
|
|
nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
|
|
if (!innerURI) return NS_ERROR_FAILURE;
|
|
|
|
nsresult rv = innerURI->GetAsciiHost(aResult);
|
|
|
|
if (NS_FAILED(rv) || aResult.IsEmpty())
|
|
return NS_ERROR_UNEXPECTED;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsPermissionManager::UpdateDB(OperationType aOp,
|
|
mozIStorageStatement* aStmt,
|
|
PRInt64 aID,
|
|
const nsACString &aHost,
|
|
const nsACString &aType,
|
|
PRUint32 aPermission,
|
|
PRUint32 aExpireType,
|
|
PRInt64 aExpireTime)
|
|
{
|
|
ENSURE_NOT_CHILD_PROCESS_NORET;
|
|
|
|
nsresult rv;
|
|
|
|
// no statement is ok - just means we don't have a profile
|
|
if (!aStmt)
|
|
return;
|
|
|
|
switch (aOp) {
|
|
case eOperationAdding:
|
|
{
|
|
rv = aStmt->BindInt64ByIndex(0, aID);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = aStmt->BindUTF8StringByIndex(1, aHost);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = aStmt->BindUTF8StringByIndex(2, aType);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = aStmt->BindInt32ByIndex(3, aPermission);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = aStmt->BindInt32ByIndex(4, aExpireType);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = aStmt->BindInt64ByIndex(5, aExpireTime);
|
|
break;
|
|
}
|
|
|
|
case eOperationRemoving:
|
|
{
|
|
rv = aStmt->BindInt64ByIndex(0, aID);
|
|
break;
|
|
}
|
|
|
|
case eOperationChanging:
|
|
{
|
|
rv = aStmt->BindInt64ByIndex(0, aID);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = aStmt->BindInt32ByIndex(1, aPermission);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = aStmt->BindInt32ByIndex(2, aExpireType);
|
|
if (NS_FAILED(rv)) break;
|
|
|
|
rv = aStmt->BindInt64ByIndex(3, aExpireTime);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
{
|
|
NS_NOTREACHED("need a valid operation in UpdateDB()!");
|
|
rv = NS_ERROR_UNEXPECTED;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (NS_SUCCEEDED(rv)) {
|
|
bool hasResult;
|
|
rv = aStmt->ExecuteStep(&hasResult);
|
|
aStmt->Reset();
|
|
}
|
|
|
|
if (NS_FAILED(rv))
|
|
NS_WARNING("db change failed!");
|
|
}
|
|
|