move permission manager persistent storage from hostperm.1 to permissions.sqlite, use nsIURI::GetAsciiHost() for consistent host normalization, and expand type/permission limits. bugs 400092 and 400097 (patch in 400097), sr=shaver, pending r=sdwilsh, blocking-1.9+.
authordwitte@stanford.edu
Tue, 04 Dec 2007 22:37:36 -0800
changeset 8748 b5ece59befc400cc7b73d6362cdd4951daf36089
parent 8747 dffbc7fda4d296b0503b0c82290f321110da11ac
child 8749 a24b80cfdec354817ab81218b0fcb4b04aa47cdf
push idunknown
push userunknown
push dateunknown
reviewersshaver, pending, sdwilsh, blocking-1.9
bugs400092, 400097
milestone1.9b2pre
move permission manager persistent storage from hostperm.1 to permissions.sqlite, use nsIURI::GetAsciiHost() for consistent host normalization, and expand type/permission limits. bugs 400092 and 400097 (patch in 400097), sr=shaver, pending r=sdwilsh, blocking-1.9+.
extensions/cookie/Makefile.in
extensions/cookie/nsPermissionManager.cpp
extensions/cookie/nsPermissionManager.h
netwerk/base/public/nsIPermissionManager.idl
--- a/extensions/cookie/Makefile.in
+++ b/extensions/cookie/Makefile.in
@@ -57,16 +57,17 @@ REQUIRES	= xpcom \
 		  necko \
 		  dom \
 		  widget \
 		  content \
 		  pref \
 		  windowwatcher \
 		  docshell \
 		  appshell \
+		  storage \
 		  $(NULL)
 
 CPPSRCS		= \
 		nsCookieModule.cpp \
 		nsPermission.cpp \
 		nsPermissionManager.cpp \
 		nsPopupWindowManager.cpp \
 		nsCookiePromptService.cpp \
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -16,16 +16,17 @@
  *
  * 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
@@ -35,20 +36,27 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsPermissionManager.h"
 #include "nsPermission.h"
 #include "nsCRT.h"
 #include "nsNetUtil.h"
+#include "nsCOMArray.h"
+#include "nsArrayEnumerator.h"
 #include "nsILineInputStream.h"
+#include "nsIIDNService.h"
 #include "nsAppDirectoryServiceDefs.h"
-#include "nsVoidArray.h"
 #include "prprf.h"
+#include "mozIStorageService.h"
+#include "mozIStorageStatement.h"
+#include "mozIStorageConnection.h"
+#include "mozStorageHelper.h"
+#include "mozStorageCID.h"
 
 ////////////////////////////////////////////////////////////////////////////////
 
 #define PL_ARENA_CONST_ALIGN_MASK 3
 #include "plarena.h"
 
 static PLArenaPool *gHostArena = nsnull;
 
@@ -67,312 +75,385 @@ ArenaStrDup(const char* str, PLArenaPool
   if (mem)
     memcpy(mem, str, size);
   return static_cast<char*>(mem);
 }
 
 nsHostEntry::nsHostEntry(const char* aHost)
 {
   mHost = ArenaStrDup(aHost, gHostArena);
-  mPermissions[0] = mPermissions[1] = 0;
-}
-
-nsHostEntry::nsHostEntry(const nsHostEntry& toCopy)
-{
-  // nsTHashtable shouldn't allow us to end up here, since we
-  // set ALLOW_MEMMOVE to true.
-  NS_NOTREACHED("nsHostEntry copy constructor is forbidden!");
 }
 
-////////////////////////////////////////////////////////////////////////////////
-
-class nsPermissionEnumerator : public nsISimpleEnumerator
+// XXX this can fail on OOM
+nsHostEntry::nsHostEntry(const nsHostEntry& toCopy)
+ : mHost(toCopy.mHost)
+ , mPermissions(toCopy.mPermissions)
 {
-  public:
-    NS_DECL_ISUPPORTS
- 
-    nsPermissionEnumerator(const nsTHashtable<nsHostEntry> *aHostTable,
-                           const char*   *aHostList,
-                           const PRUint32 aHostCount,
-                           const char*   *aTypeArray)
-      : mHostCount(aHostCount),
-        mHostIndex(0),
-        mTypeIndex(0),
-        mHostTable(aHostTable),
-        mHostList(aHostList),
-        mTypeArray(aTypeArray)
-    {
-      Prefetch();
-    }
-    
-    NS_IMETHOD HasMoreElements(PRBool *aResult) 
-    {
-      *aResult = (mNextPermission != nsnull);
-      return NS_OK;
-    }
-
-    NS_IMETHOD GetNext(nsISupports **aResult) 
-    {
-      *aResult = mNextPermission;
-      if (!mNextPermission)
-        return NS_ERROR_FAILURE;
-
-      NS_ADDREF(*aResult);
-      
-      Prefetch();
-
-      return NS_OK;
-    }
-
-    virtual ~nsPermissionEnumerator() 
-    {
-      delete[] mHostList;
-    }
-
-  protected:
-    void Prefetch();
-
-    PRInt32 mHostCount;
-    PRInt32 mHostIndex;
-    PRInt32 mTypeIndex;
-    
-    const nsTHashtable<nsHostEntry> *mHostTable;
-    const char*                     *mHostList;
-    nsCOMPtr<nsIPermission>          mNextPermission;
-    const char*                     *mTypeArray;
-};
-
-NS_IMPL_ISUPPORTS1(nsPermissionEnumerator, nsISimpleEnumerator)
-
-// Sets mNextPermission to a new nsIPermission on success,
-// to nsnull when no new permissions are present.
-void
-nsPermissionEnumerator::Prefetch() 
-{
-  // init to null, so we know when we've prefetched something
-  mNextPermission = nsnull;
-
-  // check we have something more to get
-  PRUint32 permission;
-  while (mHostIndex < mHostCount && !mNextPermission) {
-    // loop over the types to find it
-    nsHostEntry *entry = mHostTable->GetEntry(mHostList[mHostIndex]);
-    if (entry) {
-      // see if we've found it
-      permission = entry->GetPermission(mTypeIndex);
-      if (permission != nsIPermissionManager::UNKNOWN_ACTION && mTypeArray[mTypeIndex]) {
-        mNextPermission = new nsPermission(entry->GetHost(), 
-                                           nsDependentCString(mTypeArray[mTypeIndex]),
-                                           permission);
-      }
-    }
-
-    // increment mTypeIndex/mHostIndex as required
-    ++mTypeIndex;
-    if (mTypeIndex == NUMBER_OF_TYPES) {
-      mTypeIndex = 0;
-      ++mHostIndex;
-    }
-  }
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsPermissionManager Implementation
 
-static const char kPermissionsFileName[] = "hostperm.1";
-static const char kOldPermissionsFileName[] = "cookperm.txt";
+static const char kPermissionsFileName[] = "permissions.sqlite";
+#define HOSTS_SCHEMA_VERSION 1
+
+static const char kHostpermFileName[] = "hostperm.1";
+
 static const char kPermissionChangeNotification[] = PERM_CHANGE_NOTIFICATION;
 
-static const PRUint32 kLazyWriteTimeout = 2000; //msec
-
 NS_IMPL_ISUPPORTS3(nsPermissionManager, nsIPermissionManager, nsIObserver, nsISupportsWeakReference)
 
 nsPermissionManager::nsPermissionManager()
- : mHostCount(0),
-   mChangedList(PR_FALSE)
+ : mLargestID(0)
 {
 }
 
 nsPermissionManager::~nsPermissionManager()
 {
-  if (mWriteTimer)
-    mWriteTimer->Cancel();
-
-  RemoveTypeStrings();
   RemoveAllFromMemory();
 }
 
-nsresult nsPermissionManager::Init()
+nsresult
+nsPermissionManager::Init()
 {
   nsresult rv;
 
   if (!mHostTable.Init()) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  // Clear the array of type strings
-  memset(mTypeArray, nsnull, sizeof(mTypeArray));
-
-  // Cache the permissions file
-  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mPermissionsFile));
-  if (NS_SUCCEEDED(rv)) {
-    mPermissionsFile->AppendNative(NS_LITERAL_CSTRING(kPermissionsFileName));
-
-    // Ignore an error. That is not a problem. No cookperm.txt usually.
-    Read();
-  }
+  // 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();
 
   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);
   }
 
   return NS_OK;
 }
 
+nsresult
+nsPermissionManager::InitDB()
+{
+  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);
+
+  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));
+  if (rv == NS_ERROR_FILE_CORRUPTED) {
+    // 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);
+
+  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
+
+    // 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 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) "
+    "VALUES (?1, ?2, ?3, ?4)"), 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 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
+  return mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
+    "CREATE TABLE moz_hosts ("
+      " id INTEGER PRIMARY KEY"
+      ",host TEXT"
+      ",type TEXT"
+      ",permission INTEGER"
+    ")"));
+}
+
 NS_IMETHODIMP
 nsPermissionManager::Add(nsIURI     *aURI,
                          const char *aType,
                          PRUint32    aPermission)
 {
   NS_ENSURE_ARG_POINTER(aURI);
   NS_ENSURE_ARG_POINTER(aType);
 
   nsresult rv;
 
   nsCAutoString host;
   rv = GetHost(aURI, host);
-  // no host doesn't mean an error. just return the default
-  if (NS_FAILED(rv)) return NS_OK;
-
-  PRInt32 typeIndex = GetTypeIndex(aType, PR_TRUE);
-  if (typeIndex == -1 || aPermission >= NUMBER_OF_PERMISSIONS)
-    return NS_ERROR_FAILURE;
+  NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = AddInternal(host, typeIndex, aPermission, PR_TRUE);
-  if (NS_FAILED(rv)) return rv;
-
-  mChangedList = PR_TRUE;
-  LazyWrite();
-
-  return NS_OK;
+  return AddInternal(host, nsDependentCString(aType), aPermission, 0, eNotify, eWriteToDB);
 }
 
-// This only adds a permission to memory (it doesn't save to disk), and doesn't
-// bounds check aTypeIndex or aPermission. These are up to the caller.
 nsresult
 nsPermissionManager::AddInternal(const nsAFlatCString &aHost,
-                                 PRInt32               aTypeIndex,
+                                 const nsAFlatCString &aType,
                                  PRUint32              aPermission,
-                                 PRBool                aNotify)
+                                 PRInt64               aID,
+                                 NotifyOperationType   aNotifyOperation,
+                                 DBOperationType       aDBOperation)
 {
   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;
   }
 
-  if (entry->PermissionsAreEmpty()) {
-    ++mHostCount;
-  }
+  // figure out the transaction type, and get any existing permission value
+  OperationType op;
+  PRInt32 index = entry->GetPermissionIndex(typeIndex);
+  PRUint32 oldPermission;
+  if (index == -1) {
+    if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
+      op = eOperationNone;
+    else
+      op = eOperationAdding;
 
-  PRUint32 oldPermission = entry->GetPermission(aTypeIndex);
-  entry->SetPermission(aTypeIndex, aPermission);
+  } else {
+    oldPermission = entry->GetPermissions()[index].mPermission;
 
-  // If no more types are present, remove the entry
-  // Can happen if this add() is resetting the permission to default.
-  if (entry->PermissionsAreEmpty()) {
-    mHostTable.RawRemoveEntry(entry);
-    --mHostCount;
+    if (aPermission == oldPermission)
+      op = eOperationNone;
+    else if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
+      op = eOperationRemoving;
+    else
+      op = eOperationChanging;
   }
 
-  // check whether we are deleting, adding, or changing a permission,
-  // so we can notify observers. this would be neater to do in Add(),
-  // but we need to do it here because we only know what type of notification
-  // to send (removal, addition, or change) after we've done the hash
-  // lookup.
-  if (aNotify) {
-    if (aPermission == nsIPermissionManager::UNKNOWN_ACTION) {
-      if (oldPermission != nsIPermissionManager::UNKNOWN_ACTION)
-        // deleting
+  // 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));
+
+      if (aDBOperation == eWriteToDB)
+        UpdateDB(op, mStmtInsert, id, aHost, aType, aPermission);
+
+      if (aNotifyOperation == eNotify) {
         NotifyObserversWithPermission(aHost,
-                                      mTypeArray[aTypeIndex],
+                                      mTypeArray[typeIndex],
+                                      aPermission,
+                                      NS_LITERAL_STRING("added").get());
+      }
+
+      break;
+    }
+
+  case eOperationRemoving:
+    {
+      id = entry->GetPermissions()[index].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);
+
+      if (aNotifyOperation == eNotify) {
+        NotifyObserversWithPermission(aHost,
+                                      mTypeArray[typeIndex],
                                       oldPermission,
                                       NS_LITERAL_STRING("deleted").get());
-    } else {
-      if (oldPermission == nsIPermissionManager::UNKNOWN_ACTION)
-        // adding
+      }
+
+      break;
+    }
+
+  case eOperationChanging:
+    {
+      id = entry->GetPermissions()[index].mID;
+      entry->GetPermissions()[index].mPermission = aPermission;
+
+      if (aDBOperation == eWriteToDB)
+        UpdateDB(op, mStmtUpdate, id, EmptyCString(), EmptyCString(), aPermission);
+
+      if (aNotifyOperation == eNotify) {
         NotifyObserversWithPermission(aHost,
-                                      mTypeArray[aTypeIndex],
-                                      aPermission,
-                                      NS_LITERAL_STRING("added").get());
-      else
-        // changing
-        NotifyObserversWithPermission(aHost,
-                                      mTypeArray[aTypeIndex],
+                                      mTypeArray[typeIndex],
                                       aPermission,
                                       NS_LITERAL_STRING("changed").get());
+      }
+
+      break;
     }
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPermissionManager::Remove(const nsACString &aHost,
                             const char       *aType)
 {
   NS_ENSURE_ARG_POINTER(aType);
-  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(PromiseFlatCString(aHost), typeIndex, PR_FALSE);
-  if (entry) {
-    // cache the old permission before we delete it, to notify observers
-    PRUint32 oldPermission = entry->GetPermission(typeIndex);
-
-    entry->SetPermission(typeIndex, nsIPermissionManager::UNKNOWN_ACTION);
-
-    // If no more types are present, remove the entry
-    if (entry->PermissionsAreEmpty()) {
-      mHostTable.RawRemoveEntry(entry);
-      --mHostCount;
-    }
-    mChangedList = PR_TRUE;
-    LazyWrite();
-
-    // Notify Observers
-    if (oldPermission != nsIPermissionManager::UNKNOWN_ACTION)
-      NotifyObserversWithPermission(PromiseFlatCString(aHost),
-                                    aType,
-                                    oldPermission,
-                                    NS_LITERAL_STRING("deleted").get());
-  }
-  return NS_OK;
+  // AddInternal() handles removal, just let it do the work
+  return AddInternal(PromiseFlatCString(aHost),
+                     nsDependentCString(aType),
+                     nsIPermissionManager::UNKNOWN_ACTION,
+                     0,
+                     eNotify,
+                     eWriteToDB);
 }
 
 NS_IMETHODIMP
 nsPermissionManager::RemoveAll()
 {
-  RemoveAllFromMemory();
+  nsresult rv = RemoveAllInternal();
   NotifyObservers(nsnull, NS_LITERAL_STRING("cleared").get());
-  LazyWrite();
+  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)) {
+      NS_WARNING("db delete failed");
+      return rv;
+    }
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsPermissionManager::TestExactPermission(nsIURI     *aURI,
                                          const char *aType,
                                          PRUint32   *aPermission)
 {
@@ -441,171 +522,127 @@ nsPermissionManager::GetHostEntry(const 
     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;
 }
 
-// A little helper function to add the hostname to the list.
-// The hostname comes from an arena, and so it won't go away
-PR_STATIC_CALLBACK(PLDHashOperator)
-AddHostToList(nsHostEntry *entry, void *arg)
+// helper struct for passing arguments into hash enumeration callback.
+struct nsGetEnumeratorData
 {
-  // arg is a double-ptr to an string entry in the hostList array,
-  // so we dereference it twice to assign the |const char*| string
-  // and once so we can increment the |const char**| array location
-  // ready for the next assignment.
-  const char*** elementPtr = static_cast<const char***>(arg);
-  **elementPtr = entry->GetKey();
-  ++(*elementPtr);
+  nsGetEnumeratorData(nsCOMArray<nsIPermission> *aArray, const nsTArray<nsCString> *aTypes)
+   : array(aArray)
+   , types(aTypes) {}
+
+  nsCOMArray<nsIPermission> *array;
+  const nsTArray<nsCString> *types;
+};
+
+PR_STATIC_CALLBACK(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);
+
+    data->array->AppendObject(perm);
+  }
+
   return PL_DHASH_NEXT;
 }
 
 NS_IMETHODIMP nsPermissionManager::GetEnumerator(nsISimpleEnumerator **aEnum)
 {
-  *aEnum = nsnull;
-  // get the host list, to hand to the enumerator.
-  // the enumerator takes ownership of the list.
-
-  // create a new host list. enumerator takes ownership of it.
-  const char* *hostList = new const char*[mHostCount];
-  if (!hostList) return NS_ERROR_OUT_OF_MEMORY;
+  // roll an nsCOMArray of all our permissions, then hand out an enumerator
+  nsCOMArray<nsIPermission> array;
+  nsGetEnumeratorData data(&array, &mTypeArray);
 
-  // Make a copy of the pointer, so we can increase it without loosing
-  // the original pointer
-  const char** hostListCopy = hostList;
-  mHostTable.EnumerateEntries(AddHostToList, &hostListCopy);
+  mHostTable.EnumerateEntries(AddPermissionsToList, &data);
 
-  nsPermissionEnumerator* permissionEnum = new nsPermissionEnumerator(&mHostTable, hostList, mHostCount, const_cast<const char**>(mTypeArray));
-  if (!permissionEnum) {
-    delete[] hostList;
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-  NS_ADDREF(permissionEnum);
-  *aEnum = permissionEnum;
-  return NS_OK;
+  return NS_NewArrayEnumerator(aEnum, array);
 }
 
 NS_IMETHODIMP nsPermissionManager::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData)
 {
-  nsresult rv = NS_OK;
-
   if (!nsCRT::strcmp(aTopic, "profile-before-change")) {
-    // The profile is about to change.
-
-    if (mWriteTimer) {
-      mWriteTimer->Cancel();
-      mWriteTimer = 0;
+    // 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();
     }
-    
-    // Dump current permission.  This will be done by calling 
-    // RemoveAllFromMemory which clears the memory-resident
-    // permission table.  The reason the permission file does not
-    // need to be updated is because the file was updated every time
-    // the memory-resident table changed (i.e., whenever a new permission
-    // was accepted).  If this condition ever changes, the permission
-    // file would need to be updated here.
-
-    if (!nsCRT::strcmp(someData, NS_LITERAL_STRING("shutdown-cleanse").get())) {
-      if (mPermissionsFile) {
-        mPermissionsFile->Remove(PR_FALSE);
-      }
-    } else {
-      Write();
-    }
-    RemoveTypeStrings();
-    RemoveAllFromMemory();
   }  
   else if (!nsCRT::strcmp(aTopic, "profile-do-change")) {
-    // The profile has aleady changed.    
-    // Now just read them from the new profile location.
-
-    // Re-get the permissions file
-    rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(mPermissionsFile));
-    if (NS_SUCCEEDED(rv)) {
-      mPermissionsFile->AppendNative(NS_LITERAL_CSTRING(kPermissionsFileName));
-      Read();
-    }
+    // the profile has already changed; init the db from the new location
+    InitDB();
   }
 
-  return rv;
+  return NS_OK;
 }
 
 //*****************************************************************************
 //*** nsPermissionManager private methods
 //*****************************************************************************
 
 nsresult
 nsPermissionManager::RemoveAllFromMemory()
 {
+  mLargestID = 0;
+  mTypeArray.Clear();
   mHostTable.Clear();
-  mHostCount = 0;
   if (gHostArena) {
     PL_FinishArenaPool(gHostArena);
     delete gHostArena;
   }
   gHostArena = nsnull;
-  mChangedList = PR_TRUE;
   return NS_OK;
 }
 
-void
-nsPermissionManager::RemoveTypeStrings()
-{
-  for (PRUint32 i = NUMBER_OF_TYPES; i--; ) {
-    if (mTypeArray[i]) {
-      PL_strfree(mTypeArray[i]);
-      mTypeArray[i] = nsnull;
-    }
-  }
-}
-
 // Returns -1 on failure
 PRInt32
 nsPermissionManager::GetTypeIndex(const char *aType,
                                   PRBool      aAdd)
 {
-  PRInt32 firstEmpty = -1;
+  for (PRUint32 i = 0; i < mTypeArray.Length(); ++i)
+    if (mTypeArray[i].Equals(aType))
+      return i;
 
-  for (PRUint32 i = 0; i < NUMBER_OF_TYPES; ++i) {
-    if (!mTypeArray[i]) {
-      if (firstEmpty == -1)
-        // Don't break, the type might be later in the array
-        firstEmpty = i;
-    } else if (!strcmp(aType, mTypeArray[i])) {
-      return i;
-    }
+  if (!aAdd) {
+    // Not found, but that is ok - we were just looking.
+    return -1;
   }
 
-  if (!aAdd || firstEmpty == -1)
-    // Not found, but that is ok - we were just looking.
-    // Or, no free spots left - error.
+  // This type was not registered before.
+  // append it to the array, without copy-constructing the string
+  nsCString *elem = mTypeArray.AppendElement();
+  if (!elem)
     return -1;
 
-  // This type was not registered before.
-  // Can't use ToNewCString here, other strings in the array are allocated
-  // with PL_strdup too, and they all need to be freed the same way
-  mTypeArray[firstEmpty] = PL_strdup(aType);
-  if (!mTypeArray[firstEmpty])
-    return -1;
-
-  return firstEmpty;
+  elem->Assign(aType);
+  return mTypeArray.Length() - 1;
 }
 
 // wrapper function for mangling (host,type,perm) triplet into an nsIPermission.
 void
 nsPermissionManager::NotifyObserversWithPermission(const nsACString &aHost,
-                                                   const char       *aType,
+                                                   const nsCString  &aType,
                                                    PRUint32          aPermission,
                                                    const PRUnichar  *aData)
 {
   nsCOMPtr<nsIPermission> permission =
-    new nsPermission(aHost, nsDependentCString(aType), aPermission);
+    new nsPermission(aHost, aType, aPermission);
   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.
@@ -616,408 +653,206 @@ nsPermissionManager::NotifyObservers(nsI
                                      const PRUnichar *aData)
 {
   if (mObserverService)
     mObserverService->NotifyObservers(aPermission,
                                       kPermissionChangeNotification,
                                       aData);
 }
 
-static const char kTab = '\t';
-static const char kNew = '\n';
-static const char kTrue = 'T';
-static const char kFalse = 'F';
-static const char kFirstLetter = 'a';
-static const char kTypeSign = '%';
-
-static const char kMatchTypeHost[] = "host";
-
 nsresult
 nsPermissionManager::Read()
 {
   nsresult rv;
-  
-  PRBool readingOldFile = PR_FALSE;
-  nsCOMPtr<nsIInputStream> fileInputStream;
 
-  PRBool fileExists = PR_FALSE;
-  rv = mPermissionsFile->Exists(&fileExists);
+  nsCOMPtr<mozIStorageStatement> stmt;
+  rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+    "SELECT id, host, type, permission "
+    "FROM moz_hosts"), getter_AddRefs(stmt));
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (fileExists) {
-    rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
-                                    mPermissionsFile);
-  } else {
-    // Try finding the old-style file
+  PRInt64 id;
+  nsCAutoString host, type;
+  PRUint32 permission;
+  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;
 
-    nsCOMPtr<nsIFile> oldPermissionsFile;
-    rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
-                                getter_AddRefs(oldPermissionsFile));
+    rv = stmt->GetUTF8String(1, host);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = stmt->GetUTF8String(2, type);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = oldPermissionsFile->AppendNative(NS_LITERAL_CSTRING(kOldPermissionsFileName));
+    permission = stmt->AsInt32(3);
+
+    rv = AddInternal(host, type, permission, id, eDontNotify, eNoDBOperation);
     NS_ENSURE_SUCCESS(rv, rv);
+  }
+
+  return NS_OK;
+}
+
+static const char kMatchTypeHost[] = "host";
 
-    rv = oldPermissionsFile->Exists(&fileExists);
-    if (NS_SUCCEEDED(rv) && fileExists) {
-      rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
-                                      oldPermissionsFile);
-      readingOldFile = PR_TRUE;
-    } else {
-      rv = NS_ERROR_FILE_NOT_FOUND;
-    }
-    /* old format is:
-     * host \t number permission \t number permission ... \n
-     * if this format isn't respected we move onto the next line in the file.
-     * permission is T or F for accept or deny, otherwise a lowercase letter,
-     * with a=0, b=1 etc
-     */
-  }
-  // An error path is expected when cookperm.txt is not found either, or when
-  // creating the stream failed for another reason.
-  if (NS_FAILED(rv))
-    return rv;
+nsresult
+nsPermissionManager::Import()
+{
+  nsresult rv;
 
+  nsCOMPtr<nsIFile> permissionsFile;
+  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, getter_AddRefs(permissionsFile));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = permissionsFile->AppendNative(NS_LITERAL_CSTRING(kHostpermFileName));
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  nsCOMPtr<nsIInputStream> fileInputStream;
+  rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream),
+                                  permissionsFile);
+  NS_ENSURE_SUCCESS(rv, 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, 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
    */
 
-  mHasUnknownTypes = PR_FALSE;
-
   nsCAutoString buffer;
   PRBool isMore = PR_TRUE;
   while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
     if (buffer.IsEmpty() || buffer.First() == '#') {
       continue;
     }
 
-    if (!readingOldFile) {
-      nsCStringArray lineArray;
-      
-      // Split the line at tabs
-      lineArray.ParseString(buffer.get(), "\t");
-      
-      if (lineArray[0]->EqualsLiteral(kMatchTypeHost) &&
-          lineArray.Count() == 4) {
-        
-        PRInt32 error;
-        PRUint32 permission = lineArray[2]->ToInteger(&error);
-        if (error)
-          continue;
-        PRInt32 type = GetTypeIndex(lineArray[1]->get(), PR_TRUE);
-        if (type < 0)
-          continue;
-
-        rv = AddInternal(*lineArray[3], type, permission, PR_FALSE);
-        NS_ENSURE_SUCCESS(rv, rv);
-      } else {
-        mHasUnknownTypes = PR_TRUE;
-      }
+    nsCStringArray lineArray;
 
-    } else {
-      // Begin backwards compatibility code
-      nsASingleFragmentCString::char_iterator iter;
-      if (buffer.First() == kTypeSign) {
-        // A permission string line
+    // Split the line at tabs
+    lineArray.ParseString(buffer.get(), "\t");
+    
+    if (lineArray[0]->EqualsLiteral(kMatchTypeHost) &&
+        lineArray.Count() == 4) {
+      
+      PRInt32 error;
+      PRUint32 permission = lineArray[2]->ToInteger(&error);
+      if (error)
+        continue;
 
-        PRInt32 stringIndex;
-
-        if ((stringIndex = buffer.FindChar(kTab) + 1) == 0) {
-          continue;      
-        }
-
-        PRUint32 type;
-        if (PR_sscanf(buffer.get() + 1, "%u", &type) != 1 || type >= NUMBER_OF_TYPES) {
+      // 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;
-        }
-
-        // Older versions of mozilla can't parse the permission string lines.
-        // They will put them back like '%0 0F' instead of '%0 cookie'
-        // Ignore those lines, and revert to the defaults later.
-        // XXX This means that when the user has additional types besides the
-        // default, those will be lost after using an old version with the
-        // new profile, and then going back to a new version.
-        if (!strcmp(buffer.get() + stringIndex, "0F"))
-          continue;
-
-        NS_ASSERTION(GetTypeIndex(buffer.get() + stringIndex, PR_FALSE) == -1, "Corrupt cookperm.txt file");
-        mTypeArray[type] = PL_strdup(buffer.get() + stringIndex);
-
-        continue;
       }
 
-      PRInt32 hostIndex, permissionIndex;
-      PRUint32 nextPermissionIndex = 0;
-      hostIndex = 0;
-
-      if ((permissionIndex = buffer.FindChar('\t', hostIndex) + 1) == 0)
-        continue;      
-
-      // ignore leading periods in host name
-      while (hostIndex < permissionIndex && (buffer.CharAt(hostIndex) == '.'))
-        ++hostIndex;
-
-      // nullstomp the trailing tab, to get a flat string
-      buffer.BeginWriting(iter);
-      *(iter += permissionIndex - 1) = char(0);
-      nsDependentCString host(buffer.get() + hostIndex, iter);
-
-      for (;;) {
-        if (nextPermissionIndex == buffer.Length()+1)
-          break;
-
-        if ((nextPermissionIndex = buffer.FindChar(kTab, permissionIndex) + 1) == 0)
-          nextPermissionIndex = buffer.Length()+1;
-
-        const nsASingleFragmentCString &permissionString = Substring(buffer, permissionIndex, nextPermissionIndex - permissionIndex - 1);
-        permissionIndex = nextPermissionIndex;
-
-        PRInt32 type = 0;
-        PRUint32 index = 0;
-
-        if (permissionString.IsEmpty())
-          continue; // empty permission entry -- should never happen
-
-        // Parse "2T"
-        char c = permissionString.CharAt(index);
-        while (index < permissionString.Length() && c >= '0' && c <= '9') {
-          type = 10*type + (c-'0');
-          c = permissionString.CharAt(++index);
-        }
-
-        if (type >= NUMBER_OF_TYPES)
-          continue; // invalid type for this permission entry
-
-        if (index >= permissionString.Length())
-          continue; // bad format for this permission entry
-
-        PRUint32 permission;
-        if (permissionString.CharAt(index) == kTrue)
-          permission = nsIPermissionManager::ALLOW_ACTION;
-        else if (permissionString.CharAt(index) == kFalse)
-          permission = nsIPermissionManager::DENY_ACTION;
-        else
-          permission = permissionString.CharAt(index) - kFirstLetter;
-
-        // |permission| is unsigned, so no need to check for < 0
-        if (permission >= NUMBER_OF_PERMISSIONS)
-          continue;
-
-        // Ignore @@@ as host. Old style checkbox status
-        if (!permissionString.IsEmpty() && !host.EqualsLiteral("@@@@")) {
-          rv = AddInternal(host, type, permission, PR_FALSE);
-          if (NS_FAILED(rv)) return rv;
-        }
-
-      }
-      // Old files, without the part that defines the types, assume 0 is cookie
-      // etc. So add those types, but make sure not to overwrite types from new
-      // style files.
-      GetTypeIndex("cookie", PR_TRUE);
-      GetTypeIndex("image", PR_TRUE);
-      GetTypeIndex("popup", PR_TRUE);
-
-      // End backwards compatibility code
+      rv = AddInternal(*lineArray[3], *lineArray[1], permission, 0, eDontNotify, eWriteToDB);
+      NS_ENSURE_SUCCESS(rv, rv);
     }
-
   }
 
-
-  mChangedList = PR_FALSE;
+  // we're done importing - delete the old file
+  permissionsFile->Remove(PR_FALSE);
 
   return NS_OK;
 }
 
-// A helper function that adds the pointer to the entry to the list.
-// This is not threadsafe, and only safe if the consumer does not 
-// modify the list. It is only used in Write() where we are sure
-// that nothing else changes the list while we are saving.
-PR_STATIC_CALLBACK(PLDHashOperator)
-AddEntryToList(nsHostEntry *entry, void *arg)
-{
-  // arg is a double-ptr to an entry in the hostList array,
-  // so we dereference it twice to assign the |nsHostEntry*|
-  // and once so we can increment the |nsHostEntry**| array location
-  // ready for the next assignment.
-  nsHostEntry*** elementPtr = static_cast<nsHostEntry***>(arg);
-  **elementPtr = entry;
-  ++(*elementPtr);
-  return PL_DHASH_NEXT;
-}
-
-void
-nsPermissionManager::LazyWrite()
-{
-  if (mWriteTimer) {
-    mWriteTimer->SetDelay(kLazyWriteTimeout);
-  } else {
-    mWriteTimer = do_CreateInstance("@mozilla.org/timer;1");
-    if (mWriteTimer) {
-      mWriteTimer->InitWithFuncCallback(DoLazyWrite, this, kLazyWriteTimeout,
-                                        nsITimer::TYPE_ONE_SHOT);
-    }
-  }
-}
-
-void
-nsPermissionManager::DoLazyWrite(nsITimer *aTimer,
-                                 void     *aClosure)
+nsresult
+nsPermissionManager::NormalizeToACE(nsCString &aHost)
 {
-  nsPermissionManager *service = reinterpret_cast<nsPermissionManager*>(aClosure);
-  service->Write();
-  service->mWriteTimer = 0;
-}
-
-nsresult
-nsPermissionManager::Write()
-{
-  nsresult rv;
-
-  if (!mChangedList) {
-    return NS_OK;
-  }
-
-  if (!mPermissionsFile) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // Start with reading the old file, and remember any data that
-  // wasn't parsed, to put it right back into the new file.
-  // But only do that if we know there is unknown stuff
-  nsCStringArray rememberList;
-  if (mHasUnknownTypes) {
-    nsCOMPtr<nsIInputStream> fileInputStream;
-    rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), mPermissionsFile);
-    if (NS_SUCCEEDED(rv)) {
-      nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv);
-      if (NS_SUCCEEDED(rv)) {
-        nsCAutoString buffer;
-        PRBool isMore = PR_TRUE;
-        while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) {
-          if (buffer.IsEmpty() || buffer.First() == '#' ||
-              StringBeginsWith(buffer, NS_LITERAL_CSTRING(kMatchTypeHost)))
-            continue;
-
-          rememberList.AppendCString(buffer);
-        }
-      }
-    }
+  // lazily init the IDN service
+  if (!mIDNService) {
+    nsresult rv;
+    mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv);
+    NS_ENSURE_SUCCESS(rv, rv);
   }
 
-  nsCOMPtr<nsIOutputStream> fileOutputStream;
-  rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(fileOutputStream),
-                                       mPermissionsFile,
-                                       -1,
-                                       0600);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // get a buffered output stream 4096 bytes big, to optimize writes
-  nsCOMPtr<nsIOutputStream> bufferedOutputStream;
-  rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOutputStream), fileOutputStream, 4096);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  static const char kHeader[] = 
-    "# Permission File\n"
-    "# This is a generated file! Do not edit.\n\n";
-
-  bufferedOutputStream->Write(kHeader, sizeof(kHeader) - 1, &rv);
-
-  /* format is:
-   * matchtype \t type \t permission \t host \t
-   */
-
-  // Write out the list of strings we remembered from the old file
-  PRUint32 i;
-  if (mHasUnknownTypes) {
-    for (i = 0 ; i < rememberList.Count() ; ++i) {
-      bufferedOutputStream->Write(rememberList[i]->get(), 
-                                  rememberList[i]->Length(), &rv);
-      bufferedOutputStream->Write(&kNew, 1, &rv);
-    }
-  }
-
-  // create a new host list
-  nsHostEntry* *hostList = new nsHostEntry*[mHostCount];
-  if (!hostList) return NS_ERROR_OUT_OF_MEMORY;
-
-  // Make a copy of the pointer, so we can increase it without losing
-  // the original pointer
-  nsHostEntry** hostListCopy = hostList;
-  mHostTable.EnumerateEntries(AddEntryToList, &hostListCopy);
-
-  for (i = 0; i < mHostCount; ++i) {
-    nsHostEntry *entry = static_cast<nsHostEntry*>(hostList[i]);
-    NS_ASSERTION(entry, "corrupt permission list");
-
-    for (PRInt32 type = 0; type < NUMBER_OF_TYPES; ++type) {
-    
-      PRUint32 permission = entry->GetPermission(type);
-      if (permission && mTypeArray[type]) {
-        // Write matchtype
-        // only "host" is possible at the moment.
-        bufferedOutputStream->Write(kMatchTypeHost, sizeof(kMatchTypeHost) - 1, &rv);
-
-        // Write type
-        bufferedOutputStream->Write(&kTab, 1, &rv);
-        bufferedOutputStream->Write(mTypeArray[type], strlen(mTypeArray[type]), &rv);
-
-        // Write permission
-        bufferedOutputStream->Write(&kTab, 1, &rv);
-        char permissionString[5];
-        PRUint32 len = PR_snprintf(permissionString, sizeof(permissionString) - 1, "%u", permission);
-        bufferedOutputStream->Write(permissionString, len, &rv);
-
-        // Write host
-        bufferedOutputStream->Write(&kTab, 1, &rv);
-        bufferedOutputStream->Write(entry->GetHost().get(), entry->GetHost().Length(), &rv);
-        
-        // Write newline
-        bufferedOutputStream->Write(&kNew, 1, &rv);
-      }
-    }
-  }
-
-  delete[] hostList;
-
-  // All went ok. Maybe except for problems in Write(), but the stream detects
-  // that for us
-  nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(bufferedOutputStream);
-  NS_ASSERTION(safeStream, "expected a safe output stream!");
-  if (safeStream) {
-    rv = safeStream->Finish();
-    if (NS_FAILED(rv)) {
-      NS_WARNING("failed to save permissions file! possible dataloss");
-      return rv;
-    }
-  }
-
-  mChangedList = PR_FALSE;
-  return NS_OK;
+  return mIDNService->ConvertUTF8toACE(aHost, aHost);
 }
 
 nsresult
 nsPermissionManager::GetHost(nsIURI *aURI, nsACString &aResult)
 {
   nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(aURI);
   if (!innerURI) return NS_ERROR_FAILURE;
 
-  innerURI->GetHost(aResult);
+  nsresult rv = innerURI->GetAsciiHost(aResult);
 
-  // If there is no host, use the scheme, and prepend "scheme:",
-  // to make sure it isn't a host or something.
-  if (aResult.IsEmpty()) {
-    innerURI->GetScheme(aResult);
-    if (aResult.IsEmpty()) {
-      // still empty. Return error.
-      return NS_ERROR_FAILURE;
-    }
-    aResult = NS_LITERAL_CSTRING("scheme:") + 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)
+{
+  nsresult rv;
+
+  // no statement is ok - just means we don't have a profile
+  if (!aStmt)
+    return;
+
+  switch (aOp) {
+  case eOperationAdding:
+    {
+      rv = aStmt->BindInt64Parameter(0, aID);
+      if (NS_FAILED(rv)) break;
+
+      rv = aStmt->BindUTF8StringParameter(1, aHost);
+      if (NS_FAILED(rv)) break;
+      
+      rv = aStmt->BindUTF8StringParameter(2, aType);
+      if (NS_FAILED(rv)) break;
+
+      rv = aStmt->BindInt32Parameter(3, aPermission);
+      break;
+    }
+
+  case eOperationRemoving:
+    {
+      rv = aStmt->BindInt64Parameter(0, aID);
+      break;
+    }
+
+  case eOperationChanging:
+    {
+      rv = aStmt->BindInt64Parameter(0, aID);
+      if (NS_FAILED(rv)) break;
+
+      rv = aStmt->BindInt32Parameter(1, aPermission);
+      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!");
+}
+
--- a/extensions/cookie/nsPermissionManager.h
+++ b/extensions/cookie/nsPermissionManager.h
@@ -1,9 +1,9 @@
-/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* ***** 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/
  *
@@ -15,16 +15,18 @@
  * 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
@@ -40,34 +42,38 @@
 
 #include "nsIPermissionManager.h"
 #include "nsIObserver.h"
 #include "nsIObserverService.h"
 #include "nsWeakReference.h"
 #include "nsCOMPtr.h"
 #include "nsIFile.h"
 #include "nsTHashtable.h"
+#include "nsTArray.h"
 #include "nsString.h"
-#include "nsITimer.h"
 
 class nsIPermission;
+class nsIIDNService;
+class mozIStorageConnection;
+class mozIStorageStatement;
 
 ////////////////////////////////////////////////////////////////////////////////
 
-// This allow us 8 types of permissions, with 256 values for each
-// permission (some types might want to prompt, block 3rd party etc).
-// Note that nsIPermissionManager.idl only allows 16 values for permission,
-// and that nsPermissionManager::Write() can only deal with 26 values of
-// permission safely. (We allow space for 256 here, since it's faster to
-// deal with bytes than with bits).
-// Note: When changing NUMBER_OF_TYPES, also update PermissionsAreEmpty()
-// and the constructors.
-// This should be a multiple of 4, to make PermissionsAreEmpty() fast
-#define NUMBER_OF_TYPES       (8)
-#define NUMBER_OF_PERMISSIONS (16)
+class nsPermissionEntry
+{
+public:
+  nsPermissionEntry(PRUint32 aType, PRUint32 aPermission, PRInt64 aID)
+   : mType(aType)
+   , mPermission(aPermission)
+   , mID(aID) {}
+
+  PRUint32 mType;
+  PRUint32 mPermission;
+  PRInt64  mID;
+};
 
 class nsHostEntry : public PLDHashEntryHdr
 {
 public:
   // Hash methods
   typedef const char* KeyType;
   typedef const char* KeyTypePointer;
 
@@ -95,51 +101,52 @@ public:
 
   static PLDHashNumber HashKey(KeyTypePointer aKey)
   {
     // PL_DHashStringKey doesn't use the table parameter, so we can safely
     // pass nsnull
     return PL_DHashStringKey(nsnull, aKey);
   }
 
-  enum { ALLOW_MEMMOVE = PR_TRUE };
+  // force the hashtable to use the copy constructor when shuffling entries
+  // around, otherwise the Auto part of our nsAutoTArray won't be happy!
+  enum { ALLOW_MEMMOVE = PR_FALSE };
 
   // Permissions methods
   inline const nsDependentCString GetHost() const
   {
     return nsDependentCString(mHost);
   }
 
-  // Callers must do boundary checks
-  void SetPermission(PRInt32 aTypeIndex, PRUint32 aPermission)
+  inline nsTArray<nsPermissionEntry> & GetPermissions()
   {
-    mPermissions[aTypeIndex] = (PRUint8)aPermission;
+    return mPermissions;
   }
 
-  PRUint32 GetPermission(PRInt32 aTypeIndex) const
+  inline PRInt32 GetPermissionIndex(PRUint32 aType) const
   {
-    return (PRUint32)mPermissions[aTypeIndex];
+    for (PRUint32 i = 0; i < mPermissions.Length(); ++i)
+      if (mPermissions[i].mType == aType)
+        return i;
+
+    return -1;
   }
 
-  PRBool PermissionsAreEmpty() const
+  inline PRUint32 GetPermission(PRUint32 aType) const
   {
-    // Cast to PRUint32, to make this faster. Only 2 checks instead of 8
-    return (*reinterpret_cast<const PRUint32*>(&mPermissions[0])==0 && 
-            *reinterpret_cast<const PRUint32*>(&mPermissions[4])==0 );
+    for (PRUint32 i = 0; i < mPermissions.Length(); ++i)
+      if (mPermissions[i].mType == aType)
+        return mPermissions[i].mPermission;
+
+    return nsIPermissionManager::UNKNOWN_ACTION;
   }
 
 private:
   const char *mHost;
-
-  // This will contain the permissions for different types, in a bitmasked
-  // form. The type itself defines the position.
-  // One byte per permission. This is a small loss of memory (as 255 types
-  // of action per type is a lot, 16 would be enough), but will improve speed
-  // Bytemasking is around twice as fast (on one arch at least) than bitmasking
-  PRUint8 mPermissions[NUMBER_OF_TYPES];
+  nsAutoTArray<nsPermissionEntry, 1> mPermissions;
 };
 
 
 class nsPermissionManager : public nsIPermissionManager,
                             public nsIObserver,
                             public nsSupportsWeakReference
 {
 public:
@@ -150,57 +157,86 @@ public:
   NS_DECL_NSIOBSERVER
 
   nsPermissionManager();
   virtual ~nsPermissionManager();
   nsresult Init();
 
 private:
 
+  // enums for AddInternal()
+  enum OperationType {
+    eOperationNone,
+    eOperationAdding,
+    eOperationRemoving,
+    eOperationChanging
+  };
+
+  enum DBOperationType {
+    eNoDBOperation,
+    eWriteToDB
+  };
+
+  enum NotifyOperationType {
+    eDontNotify,
+    eNotify
+  };
+
   nsresult AddInternal(const nsAFlatCString &aHost,
-                       PRInt32  aTypeIndex,
+                       const nsAFlatCString &aType,
                        PRUint32 aPermission,
-                       PRBool   aNotify);
+                       PRInt64 aID,
+                       NotifyOperationType aNotifyOperation,
+                       DBOperationType aDBOperation);
+
   PRInt32 GetTypeIndex(const char *aTypeString,
                        PRBool      aAdd);
 
   nsHostEntry *GetHostEntry(const nsAFlatCString &aHost,
                             PRUint32              aType,
                             PRBool                aExactHostMatch);
 
   nsresult CommonTestPermission(nsIURI     *aURI,
                                 const char *aType,
                                 PRUint32   *aPermission,
                                 PRBool      aExactHostMatch);
 
-  // Use LazyWrite to save the permissions file on a timer. It will write
-  // the file only once if repeatedly hammered quickly.
-  void        LazyWrite();
-  static void DoLazyWrite(nsITimer *aTimer, void *aClosure);
-  nsresult    Write();
-
+  nsresult InitDB();
+  nsresult CreateTable();
+  nsresult Import();
   nsresult Read();
   void     NotifyObserversWithPermission(const nsACString &aHost,
-                                         const char       *aType,
+                                         const nsCString  &aType,
                                          PRUint32          aPermission,
                                          const PRUnichar  *aData);
   void     NotifyObservers(nsIPermission *aPermission, const PRUnichar *aData);
+  nsresult RemoveAllInternal();
   nsresult RemoveAllFromMemory();
+  nsresult NormalizeToACE(nsCString &aHost);
   nsresult GetHost(nsIURI *aURI, nsACString &aResult);
-  void     RemoveTypeStrings();
+  static void UpdateDB(OperationType         aOp,
+                       mozIStorageStatement* aStmt,
+                       PRInt64               aID,
+                       const nsACString     &aHost,
+                       const nsACString     &aType,
+                       PRUint32              aPermission);
 
   nsCOMPtr<nsIObserverService> mObserverService;
-  nsCOMPtr<nsIFile>            mPermissionsFile;
-  nsCOMPtr<nsITimer>           mWriteTimer;
+  nsCOMPtr<nsIIDNService>      mIDNService;
+
+  nsCOMPtr<mozIStorageConnection> mDBConn;
+  nsCOMPtr<mozIStorageStatement> mStmtInsert;
+  nsCOMPtr<mozIStorageStatement> mStmtDelete;
+  nsCOMPtr<mozIStorageStatement> mStmtUpdate;
+
   nsTHashtable<nsHostEntry>    mHostTable;
-  PRUint32                     mHostCount;
-  PRPackedBool                 mChangedList;
-  PRPackedBool                 mHasUnknownTypes;
+  // a unique, monotonically increasing id used to identify each database entry
+  PRInt64                      mLargestID;
 
   // An array to store the strings identifying the different types.
-  char                        *mTypeArray[NUMBER_OF_TYPES];
+  nsTArray<nsCString>          mTypeArray;
 };
 
 // {4F6B5E00-0C36-11d5-A535-0010A401EB10}
 #define NS_PERMISSIONMANAGER_CID \
 { 0x4f6b5e00, 0xc36, 0x11d5, { 0xa5, 0x35, 0x0, 0x10, 0xa4, 0x1, 0xeb, 0x10 } }
 
 #endif /* nsPermissionManager_h__ */
--- a/netwerk/base/public/nsIPermissionManager.idl
+++ b/netwerk/base/public/nsIPermissionManager.idl
@@ -67,48 +67,52 @@ interface nsIURI;
 interface nsIObserver;
 
 [scriptable, uuid(00708302-684c-42d6-a5a3-995d51b1d17c)]
 interface nsIPermissionManager : nsISupports
 {
   /**
    * Predefined return values for the testPermission method and for
    * the permission param of the add method
+   * NOTE: UNKNOWN_ACTION (0) is reserved to represent the
+   * default permission when no entry is found for a host, and
+   * should not be used by consumers to indicate otherwise.
    */
   const PRUint32 UNKNOWN_ACTION = 0;
   const PRUint32 ALLOW_ACTION = 1;
   const PRUint32 DENY_ACTION = 2;
 
   /**
    * Add permission information for a given URI and permission type. This
    * operation will cause the type string to be registered if it does not
-   * currently exist.
+   * currently exist. If a permission already exists for a given type, it
+   * will be modified.
    *
    * @param uri         the uri to add the permission for
    * @param type        a case-sensitive ASCII string, identifying the consumer.
    *                    Consumers should choose this string to be unique, with
-   *                    respect to other consumers. The number of unique type
-   *                    identifiers may be limited.
-   * @param permission  an integer from 1 to 15, representing the desired
-   *                    action (e.g. allow or deny). The interpretation of
-   *                    this number is up to the consumer, and may represent
-   *                    different actions for different types. Consumers may
-   *                    use one of the enumerated permission actions defined
-   *                    above. 0 is reserved for UNKNOWN_ACTION, and shouldn't
-   *                    be used.
-   * @throws            NS_ERROR_FAILURE if there is no more room for adding
-   *                    a new type
+   *                    respect to other consumers.
+   * @param permission  an integer representing the desired action (e.g. allow
+   *                    or deny). The interpretation of this number is up to the
+   *                    consumer, and may represent different actions for different
+   *                    types. Consumers may use one of the enumerated permission
+   *                    actions defined above, for convenience.
+   *                    NOTE: UNKNOWN_ACTION (0) is reserved to represent the
+   *                    default permission when no entry is found for a host, and
+   *                    should not be used by consumers to indicate otherwise.
    */
   void add(in nsIURI uri,
            in string type,
            in PRUint32 permission);
 
   /**
-   * Remove permission information for a given URI and permission type.
-   * Note that this method takes a host string, not an nsIURI.
+   * Remove permission information for a given host string and permission type.
+   * The host string represents the exact entry in the permission list (such as
+   * obtained from the enumerator), not a URI which that permission might apply
+   * to.
    *
    * @param host   the host to remove the permission for
    * @param type   a case-sensitive ASCII string, identifying the consumer. 
    *               The type must have been previously registered using the
    *               add() method.
    */
   void remove(in AUTF8String host,
               in string type);