/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Michiel van Leeuwen (mvl@exedo.nl) * Daniel Witte (dwitte@stanford.edu) * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #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 PRBool 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; } /** * @returns The parent process object, or if we are not in the parent * process, nsnull. */ static ContentParent* ParentProcess() { if (!IsChildProcess()) { ContentParent* cpc = ContentParent::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(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) , mUpdateChildProcess(PR_FALSE) { } nsPermissionManager::~nsPermissionManager() { RemoveAllFromMemory(); } // static nsIPermissionManager* nsPermissionManager::GetXPCOMSingleton() { return GetSingleton().get(); } // static already_AddRefed nsPermissionManager::GetSingleton() { 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; if (!mHostTable.Init()) { return NS_ERROR_OUT_OF_MEMORY; } mObserverService = do_GetService("@mozilla.org/observer-service;1", &rv); if (NS_SUCCEEDED(rv)) { mObserverService->AddObserver(this, "profile-before-change", PR_TRUE); mObserverService->AddObserver(this, "profile-do-change", PR_TRUE); } if (IsChildProcess()) { // Get the permissions from the parent process InfallibleTArray 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(PR_FALSE); return NS_OK; } nsresult nsPermissionManager::InitDB(PRBool aRemoveFile) { nsCOMPtr 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) { PRBool exists = PR_FALSE; rv = permissionsFile->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); if (exists) { rv = permissionsFile->Remove(PR_FALSE); NS_ENSURE_SUCCESS(rv, rv); } } nsCOMPtr 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); PRBool ready; mDBConn->GetConnectionReady(&ready); if (!ready) { // delete and try again rv = permissionsFile->Remove(PR_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; } PRBool tableExists = PR_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 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()) { // In the parent, send the update now, if the child is ready if (mUpdateChildProcess) { IPC::Permission permission((aHost), (aType), aPermission, aExpireType, aExpireTime); unused << ParentProcess()->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(), PR_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; } nsresult nsPermissionManager::RemoveAllInternal() { RemoveAllFromMemory(); // clear the db if (mDBConn) { nsresult rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING("DELETE FROM moz_hosts")); if (NS_FAILED(rv)) { mStmtInsert = nsnull; mStmtDelete = nsnull; mStmtUpdate = nsnull; mDBConn = nsnull; rv = InitDB(PR_TRUE); return rv; } } return NS_OK; } NS_IMETHODIMP nsPermissionManager::TestExactPermission(nsIURI *aURI, const char *aType, PRUint32 *aPermission) { return CommonTestPermission(aURI, aType, aPermission, PR_TRUE); } NS_IMETHODIMP nsPermissionManager::TestPermission(nsIURI *aURI, const char *aType, PRUint32 *aPermission) { return CommonTestPermission(aURI, aType, aPermission, PR_FALSE); } nsresult nsPermissionManager::CommonTestPermission(nsIURI *aURI, const char *aType, PRUint32 *aPermission, PRBool 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)) { PRBool isFile; rv = aURI->SchemeIs("file", &isFile); NS_ENSURE_SUCCESS(rv, rv); if (isFile) { host.AssignLiteral(""); } else { return NS_OK; } } PRInt32 typeIndex = GetTypeIndex(aType, PR_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 "". This will perform an exact match // lookup as the string doesn't contain any dots. nsHostEntry * nsPermissionManager::GetHostEntry(const nsAFlatCString &aHost, PRUint32 aType, PRBool 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 *aArray, const nsTArray *aTypes) : array(aArray) , types(aTypes) {} nsCOMArray *array; const nsTArray *types; }; static PLDHashOperator AddPermissionsToList(nsHostEntry *entry, void *arg) { nsGetEnumeratorData *data = static_cast(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 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(); } } else if (!nsCRT::strcmp(aTopic, "profile-do-change")) { // the profile has already changed; init the db from the new location InitDB(PR_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, PRBool 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 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 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); PRBool hasResult; rv = stmtDeleteExpired->ExecuteStep(&hasResult); NS_ENSURE_SUCCESS(rv, rv); } nsCOMPtr 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; PRBool 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 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 fileInputStream; rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), permissionsFile); if (NS_FAILED(rv)) return rv; nsCOMPtr 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, PR_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; PRBool isMore = PR_TRUE; while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) { if (buffer.IsEmpty() || buffer.First() == '#') { continue; } nsTArray 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(PR_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 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)) { PRBool hasResult; rv = aStmt->ExecuteStep(&hasResult); aStmt->Reset(); } if (NS_FAILED(rv)) NS_WARNING("db change failed!"); }