Bug 519263 - Session and time-based expiration for permissions in permissions manager. r=dwitte,
authorSid Stamm <sstamm@mozilla.com>
Fri, 16 Oct 2009 14:01:04 -0700
changeset 33959 c3f243110dc2
parent 33958 012ea897d108
child 33960 fbe123a48e7b
push id9776
push userdwitte@mozilla.com
push date2009-10-16 21:02 +0000
Treeherderresults
reviewersdwitte
bugs519263
milestone1.9.3a1pre
Bug 519263 - Session and time-based expiration for permissions in permissions manager. r=dwitte,
sr=mrbkap. First patch! Yay!!
browser/components/migration/src/nsOperaProfileMigrator.cpp
extensions/cookie/nsCookiePermission.cpp
extensions/cookie/nsPermission.cpp
extensions/cookie/nsPermission.h
extensions/cookie/nsPermissionManager.cpp
extensions/cookie/nsPermissionManager.h
extensions/cookie/test/unit/test_permmanager_expiration.js
extensions/cookie/test/unit/test_permmanager_notifications.js
netwerk/base/public/nsIPermission.idl
netwerk/base/public/nsIPermissionManager.idl
xpinstall/src/nsInstallTrigger.cpp
--- a/browser/components/migration/src/nsOperaProfileMigrator.cpp
+++ b/browser/components/migration/src/nsOperaProfileMigrator.cpp
@@ -879,17 +879,19 @@ nsOperaCookieMigrator::AddCookieOverride
   nsCOMPtr<nsIURI> uri(do_CreateInstance("@mozilla.org/network/standard-url;1"));
   if (!uri)
     return NS_ERROR_OUT_OF_MEMORY;
   uri->SetHost(domain);
 
   rv = aManager->Add(uri, "cookie",
                      (mCurrHandlingInfo == 1 || mCurrHandlingInfo == 3)
                      ? (PRUint32) nsIPermissionManager::ALLOW_ACTION
-                     : (PRUint32) nsIPermissionManager::DENY_ACTION);
+                     : (PRUint32) nsIPermissionManager::DENY_ACTION,
+                     nsIPermissionManager::EXPIRE_NEVER,
+                     0);
 
   mCurrHandlingInfo = 0;
 
   return rv;
 }
 
 
 nsresult
--- a/extensions/cookie/nsCookiePermission.cpp
+++ b/extensions/cookie/nsCookiePermission.cpp
@@ -184,17 +184,18 @@ NS_IMETHODIMP
 nsCookiePermission::SetAccess(nsIURI         *aURI,
                               nsCookieAccess  aAccess)
 {
   //
   // NOTE: nsCookieAccess values conveniently match up with
   //       the permission codes used by nsIPermissionManager.
   //       this is nice because it avoids conversion code.
   //
-  return mPermMgr->Add(aURI, kPermissionType, aAccess);
+  return mPermMgr->Add(aURI, kPermissionType, aAccess,
+                       nsIPermissionManager::EXPIRE_NEVER, 0);
 }
 
 NS_IMETHODIMP
 nsCookiePermission::CanAccess(nsIURI         *aURI,
                               nsIChannel     *aChannel,
                               nsCookieAccess *aResult)
 {
 #ifdef MOZ_MAIL_NEWS
@@ -356,23 +357,26 @@ nsCookiePermission::CanSetCookie(nsIURI 
       if (NS_FAILED(rv)) return rv;
       
       if (*aResult == nsICookiePromptService::ACCEPT_SESSION_COOKIE)
         *aIsSession = PR_TRUE;
 
       if (rememberDecision) {
         switch (*aResult) {
           case nsICookiePromptService::DENY_COOKIE:
-            mPermMgr->Add(aURI, kPermissionType, (PRUint32) nsIPermissionManager::DENY_ACTION);
+            mPermMgr->Add(aURI, kPermissionType, (PRUint32) nsIPermissionManager::DENY_ACTION,
+                          nsIPermissionManager::EXPIRE_NEVER, 0);
             break;
           case nsICookiePromptService::ACCEPT_COOKIE:
-            mPermMgr->Add(aURI, kPermissionType, (PRUint32) nsIPermissionManager::ALLOW_ACTION);
+            mPermMgr->Add(aURI, kPermissionType, (PRUint32) nsIPermissionManager::ALLOW_ACTION,
+                          nsIPermissionManager::EXPIRE_NEVER, 0);
             break;
           case nsICookiePromptService::ACCEPT_SESSION_COOKIE:
-            mPermMgr->Add(aURI, kPermissionType, nsICookiePermission::ACCESS_SESSION);
+            mPermMgr->Add(aURI, kPermissionType, nsICookiePermission::ACCESS_SESSION,
+                          nsIPermissionManager::EXPIRE_NEVER, 0);
             break;
           default:
             break;
         }
       }
     } else {
       // we're not prompting, so we must be limiting the lifetime somehow
       // if it's a session cookie, we do nothing
--- a/extensions/cookie/nsPermission.cpp
+++ b/extensions/cookie/nsPermission.cpp
@@ -38,20 +38,24 @@
 #include "nsPermission.h"
 
 // nsPermission Implementation
 
 NS_IMPL_ISUPPORTS1(nsPermission, nsIPermission)
 
 nsPermission::nsPermission(const nsACString &aHost,
                            const nsACString &aType,
-                           PRUint32         aCapability)
+                           PRUint32         aCapability,
+                           PRUint32         aExpireType,
+                           PRInt64          aExpireTime)
  : mHost(aHost)
  , mType(aType)
  , mCapability(aCapability)
+ , mExpireType(aExpireType)
+ , mExpireTime(aExpireTime)
 {
 }
 
 nsPermission::~nsPermission()
 {
 }
 
 NS_IMETHODIMP
@@ -69,8 +73,22 @@ nsPermission::GetType(nsACString &aType)
 }
 
 NS_IMETHODIMP
 nsPermission::GetCapability(PRUint32 *aCapability)
 {
   *aCapability = mCapability;
   return NS_OK;
 }
+
+NS_IMETHODIMP
+nsPermission::GetExpireType(PRUint32 *aExpireType)
+{
+  *aExpireType = mExpireType;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPermission::GetExpireTime(PRInt64 *aExpireTime)
+{
+  *aExpireTime = mExpireTime;
+  return NS_OK;
+}
--- a/extensions/cookie/nsPermission.h
+++ b/extensions/cookie/nsPermission.h
@@ -45,18 +45,25 @@
 
 class nsPermission : public nsIPermission
 {
 public:
   // nsISupports
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPERMISSION
 
-  nsPermission(const nsACString &aHost, const nsACString &aType, PRUint32 aCapability);
+  nsPermission(const nsACString &aHost,
+               const nsACString &aType, 
+               PRUint32 aCapability,
+               PRUint32 aExpireType,
+               PRInt64 aExpireTime);
+
   virtual ~nsPermission();
   
 protected:
   nsCString mHost;
   nsCString mType;
   PRUint32  mCapability;
+  PRUint32  mExpireType;
+  PRInt64   mExpireTime;
 };
 
 #endif // nsPermission_h__
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -90,17 +90,17 @@ nsHostEntry::nsHostEntry(const nsHostEnt
  , mPermissions(toCopy.mPermissions)
 {
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsPermissionManager Implementation
 
 static const char kPermissionsFileName[] = "permissions.sqlite";
-#define HOSTS_SCHEMA_VERSION 1
+#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()
@@ -193,16 +193,34 @@ nsPermissionManager::InitDB(PRBool aRemo
     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!");
           
@@ -222,17 +240,18 @@ nsPermissionManager::InitDB(PRBool aRemo
     // 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));
+          "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();
@@ -243,28 +262,29 @@ nsPermissionManager::InitDB(PRBool aRemo
   }
 
   // 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));
+    "(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 WHERE id = ?1"), getter_AddRefs(mStmtUpdate));
+    "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();
 }
@@ -279,41 +299,57 @@ nsPermissionManager::CreateTable()
 
   // create the table
   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    aPermission,
+                         PRUint32    aExpireType,
+                         PRInt64     aExpireTime)
 {
   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, eNotify, eWriteToDB);
+  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 (!gHostArena) {
     gHostArena = new PLArenaPool;
     if (!gHostArena)
       return NS_ERROR_OUT_OF_MEMORY;    
     PL_INIT_ARENA_POOL(gHostArena, "PermissionHostArena", HOST_ARENA_SIZE);
@@ -330,27 +366,34 @@ nsPermissionManager::AddInternal(const n
   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);
-  PRUint32 oldPermission;
   if (index == -1) {
     if (aPermission == nsIPermissionManager::UNKNOWN_ACTION)
       op = eOperationNone;
     else
       op = eOperationAdding;
 
   } else {
-    oldPermission = entry->GetPermissions()[index].mPermission;
+    nsPermissionEntry oldPermissionEntry = entry->GetPermissions()[index];
 
-    if (aPermission == oldPermission)
+    // 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:
@@ -368,65 +411,73 @@ nsPermissionManager::AddInternal(const n
       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));
+      entry->GetPermissions().AppendElement(nsPermissionEntry(typeIndex, aPermission, id, aExpireType, aExpireTime));
 
-      if (aDBOperation == eWriteToDB)
-        UpdateDB(op, mStmtInsert, id, aHost, aType, aPermission);
+      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:
     {
-      id = entry->GetPermissions()[index].mID;
+      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);
+        UpdateDB(op, mStmtDelete, id, EmptyCString(), EmptyCString(), 0, 
+                 nsIPermissionManager::EXPIRE_NEVER, 0);
 
       if (aNotifyOperation == eNotify) {
         NotifyObserversWithPermission(aHost,
                                       mTypeArray[typeIndex],
-                                      oldPermission,
+                                      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)
-        UpdateDB(op, mStmtUpdate, id, EmptyCString(), EmptyCString(), 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;
@@ -438,16 +489,18 @@ nsPermissionManager::Remove(const nsACSt
 {
   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()
 {
   nsresult rv = RemoveAllInternal();
@@ -511,35 +564,43 @@ nsPermissionManager::CommonTestPermissio
   
   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);
+    *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.
 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) {
-      if (entry->GetPermission(aType) != nsIPermissionManager::UNKNOWN_ACTION)
+      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
 
@@ -567,17 +628,19 @@ AddPermissionsToList(nsHostEntry *entry,
 {
   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.mPermission,
+                                          permEntry.mExpireType,
+                                          permEntry.mExpireTime);
 
     data->array->AppendObject(perm);
   }
 
   return PL_DHASH_NEXT;
 }
 
 NS_IMETHODIMP nsPermissionManager::GetEnumerator(nsISimpleEnumerator **aEnum)
@@ -648,25 +711,28 @@ nsPermissionManager::GetTypeIndex(const 
   nsCString *elem = mTypeArray.AppendElement();
   if (!elem)
     return -1;
 
   elem->Assign(aType);
   return mTypeArray.Length() - 1;
 }
 
-// wrapper function for mangling (host,type,perm) triplet into an nsIPermission.
+// 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);
+    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.
@@ -682,42 +748,69 @@ nsPermissionManager::NotifyObservers(nsI
                                       aData);
 }
 
 nsresult
 nsPermissionManager::Read()
 {
   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->BindInt32Parameter(0, nsIPermissionManager::EXPIRE_TIME);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = stmtDeleteExpired->BindInt64Parameter(1, PR_Now() / 1000);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    PRBool hasResult;
+    rv = stmtDeleteExpired->ExecuteStep(&hasResult);
+    NS_ENSURE_SUCCESS(rv, rv);
+  }
+
   nsCOMPtr<mozIStorageStatement> stmt;
   rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
-    "SELECT id, host, type, permission "
+    "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);
 
-    rv = AddInternal(host, type, permission, id, eDontNotify, eNoDBOperation);
+    // 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";
 
@@ -774,17 +867,18 @@ nsPermissionManager::Import()
 
       // 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, eDontNotify, eWriteToDB);
+      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;
@@ -818,17 +912,19 @@ nsPermissionManager::GetHost(nsIURI *aUR
 }
 
 void
 nsPermissionManager::UpdateDB(OperationType         aOp,
                               mozIStorageStatement* aStmt,
                               PRInt64               aID,
                               const nsACString     &aHost,
                               const nsACString     &aType,
-                              PRUint32              aPermission)
+                              PRUint32              aPermission,
+                              PRUint32              aExpireType,
+                              PRInt64               aExpireTime)
 {
   nsresult rv;
 
   // no statement is ok - just means we don't have a profile
   if (!aStmt)
     return;
 
   switch (aOp) {
@@ -839,31 +935,43 @@ nsPermissionManager::UpdateDB(OperationT
 
       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);
+      if (NS_FAILED(rv)) break;
+
+      rv = aStmt->BindInt32Parameter(4, aExpireType);
+      if (NS_FAILED(rv)) break;
+
+      rv = aStmt->BindInt64Parameter(5, aExpireTime);
       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);
+      if (NS_FAILED(rv)) break;
+
+      rv = aStmt->BindInt32Parameter(2, aExpireType);
+      if (NS_FAILED(rv)) break;
+
+      rv = aStmt->BindInt64Parameter(3, aExpireTime);
       break;
     }
 
   default:
     {
       NS_NOTREACHED("need a valid operation in UpdateDB()!");
       rv = NS_ERROR_UNEXPECTED;
       break;
--- a/extensions/cookie/nsPermissionManager.h
+++ b/extensions/cookie/nsPermissionManager.h
@@ -55,24 +55,29 @@ class nsIIDNService;
 class mozIStorageConnection;
 class mozIStorageStatement;
 
 ////////////////////////////////////////////////////////////////////////////////
 
 class nsPermissionEntry
 {
 public:
-  nsPermissionEntry(PRUint32 aType, PRUint32 aPermission, PRInt64 aID)
+  nsPermissionEntry(PRUint32 aType, PRUint32 aPermission, PRInt64 aID, 
+                    PRUint32 aExpireType, PRInt64 aExpireTime)
    : mType(aType)
    , mPermission(aPermission)
-   , mID(aID) {}
+   , mID(aID)
+   , mExpireType(aExpireType)
+   , mExpireTime(aExpireTime) {}
 
   PRUint32 mType;
   PRUint32 mPermission;
   PRInt64  mID;
+  PRUint32 mExpireType;
+  PRInt64  mExpireTime;
 };
 
 class nsHostEntry : public PLDHashEntryHdr
 {
 public:
   // Hash methods
   typedef const char* KeyType;
   typedef const char* KeyTypePointer;
@@ -125,23 +130,26 @@ public:
   {
     for (PRUint32 i = 0; i < mPermissions.Length(); ++i)
       if (mPermissions[i].mType == aType)
         return i;
 
     return -1;
   }
 
-  inline PRUint32 GetPermission(PRUint32 aType) const
+  inline nsPermissionEntry GetPermission(PRUint32 aType) const
   {
     for (PRUint32 i = 0; i < mPermissions.Length(); ++i)
       if (mPermissions[i].mType == aType)
-        return mPermissions[i].mPermission;
+        return mPermissions[i];
 
-    return nsIPermissionManager::UNKNOWN_ACTION;
+    // unknown permission... return relevant data 
+    nsPermissionEntry unk = nsPermissionEntry(aType, nsIPermissionManager::UNKNOWN_ACTION,
+                                              -1, nsIPermissionManager::EXPIRE_NEVER, 0);
+    return unk;
   }
 
 private:
   const char *mHost;
   nsAutoTArray<nsPermissionEntry, 1> mPermissions;
 };
 
 
@@ -179,16 +187,18 @@ private:
     eDontNotify,
     eNotify
   };
 
   nsresult AddInternal(const nsAFlatCString &aHost,
                        const nsAFlatCString &aType,
                        PRUint32 aPermission,
                        PRInt64 aID,
+                       PRUint32 aExpireType,
+                       PRInt64  aExpireTime,
                        NotifyOperationType aNotifyOperation,
                        DBOperationType aDBOperation);
 
   PRInt32 GetTypeIndex(const char *aTypeString,
                        PRBool      aAdd);
 
   nsHostEntry *GetHostEntry(const nsAFlatCString &aHost,
                             PRUint32              aType,
@@ -201,28 +211,32 @@ private:
 
   nsresult InitDB(PRBool aRemoveFile);
   nsresult CreateTable();
   nsresult Import();
   nsresult Read();
   void     NotifyObserversWithPermission(const nsACString &aHost,
                                          const nsCString  &aType,
                                          PRUint32          aPermission,
+                                         PRUint32          aExpireType,
+                                         PRInt64           aExpireTime,
                                          const PRUnichar  *aData);
   void     NotifyObservers(nsIPermission *aPermission, const PRUnichar *aData);
   nsresult RemoveAllInternal();
   nsresult RemoveAllFromMemory();
   nsresult NormalizeToACE(nsCString &aHost);
   nsresult GetHost(nsIURI *aURI, nsACString &aResult);
   static void UpdateDB(OperationType         aOp,
                        mozIStorageStatement* aStmt,
                        PRInt64               aID,
                        const nsACString     &aHost,
                        const nsACString     &aType,
-                       PRUint32              aPermission);
+                       PRUint32              aPermission,
+                       PRUint32              aExpireType,
+                       PRInt64               aExpireTime);
 
   nsCOMPtr<nsIObserverService> mObserverService;
   nsCOMPtr<nsIIDNService>      mIDNService;
 
   nsCOMPtr<mozIStorageConnection> mDBConn;
   nsCOMPtr<mozIStorageStatement> mStmtInsert;
   nsCOMPtr<mozIStorageStatement> mStmtDelete;
   nsCOMPtr<mozIStorageStatement> mStmtUpdate;
new file mode 100644
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_expiration.js
@@ -0,0 +1,54 @@
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+  // setup a profile directory
+  var dir = do_get_profile();
+
+  // initialize the permission manager service
+  var pm = Cc["@mozilla.org/permissionmanager;1"]
+           .getService(Ci.nsIPermissionManager);
+
+  var ios = Cc["@mozilla.org/network/io-service;1"]
+            .getService(Ci.nsIIOService);
+  var permURI = ios.newURI("http://example.com", null, null);
+
+function run_test() {
+
+  // add a permission with *now* expiration
+  pm.add(permURI, "test/expiration-perm-exp", 1, pm.EXPIRE_TIME, (new Date()).getTime());
+
+  // add a permission with future expiration (100 milliseconds)
+  pm.add(permURI, "test/expiration-perm-exp2", 1, pm.EXPIRE_TIME, (new Date()).getTime() + 100);
+
+  // add a permission with future expiration (10000 milliseconds)
+  pm.add(permURI, "test/expiration-perm-exp3", 1, pm.EXPIRE_TIME, (new Date()).getTime() + 10000);
+
+  // add a permission without expiration
+  pm.add(permURI, "test/expiration-perm-nexp", 1, pm.EXPIRE_NEVER, 0);
+
+  // check that the permission expired
+  do_check_eq(0, pm.testPermission(permURI, "test/expiration-perm-exp"));
+
+  // ... and that the others didn't
+  do_check_eq(1, pm.testPermission(permURI, "test/expiration-perm-exp3"));
+  do_check_eq(1, pm.testPermission(permURI, "test/expiration-perm-nexp"));
+
+  // ... and that the short-term one will
+  do_test_pending();
+  do_timeout(200, "verifyExpiration();");
+
+  // clean up
+  do_test_pending();
+  do_timeout(300, "end_test();");
+}
+
+function verifyExpiration() { 
+  do_check_eq(0, pm.testPermission(permURI, "test/expiration-perm-exp2")); 
+  do_test_finished();
+}
+
+function end_test() {
+  // clean up
+  pm.removeAll();
+  do_test_finished();
+}
new file mode 100644
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_notifications.js
@@ -0,0 +1,122 @@
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+// setup a profile directory
+var dir = do_get_profile();
+
+// initialize the permission manager service
+var pm = Cc["@mozilla.org/permissionmanager;1"]
+          .getService(Ci.nsIPermissionManager);
+
+var ios = Cc["@mozilla.org/network/io-service;1"]
+          .getService(Ci.nsIIOService);
+var permURI = ios.newURI("http://example.com", null, null);
+
+var theTime = (new Date()).getTime();
+
+var numadds = 0;
+var numchanges = 0;
+var numdeletes = 0;
+var needsToClear = true;
+
+// will listen for stuff.
+var observer = {
+  QueryInterface: 
+  function(iid) {
+    if (iid.equals(Ci.nsISupports) || 
+        iid.equals(Ci.nsIObserver))
+      return this;
+    throw Components.results.NS_ERROR_NO_INTERFACE; 
+  },
+
+  observe:
+  function(subject, topic, data) {
+    if (topic !== "perm-changed")
+      return;
+
+    // "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.
+    if (data == "added") {
+      var perm = subject.QueryInterface(Ci.nsIPermission);
+      numadds++;
+      switch (numadds) {
+        case 1: /* first add */
+          do_check_eq(pm.EXPIRE_TIME, perm.expireType);
+          do_check_eq(theTime + 10000, perm.expireTime);
+          break;
+        case 2: /* second add (permission-notify) */
+          do_check_eq(pm.EXPIRE_NEVER, perm.expireType);
+          do_check_eq(pm.DENY_ACTION, perm.capability);
+          break;
+        default:
+          do_throw("too many add notifications posted.");
+      }
+      do_test_finished();
+
+    } else if (data == "changed") {
+      var perm = subject.QueryInterface(Ci.nsIPermission);
+      numchanges++;
+      switch (numchanges) {
+        case 1:
+          do_check_eq(pm.EXPIRE_TIME, perm.expireType);
+          do_check_eq(theTime + 20000, perm.expireTime);
+          break;
+        default:
+          do_throw("too many change notifications posted.");
+      }
+      do_test_finished();
+
+    } else if (data == "deleted") {
+      var perm = subject.QueryInterface(Ci.nsIPermission);
+      numdeletes++;
+      switch (numdeletes) {
+        case 1:
+          do_check_eq("test/permission-notify", perm.type);
+          break;
+        default:
+          do_throw("too many delete notifications posted.");
+      }
+      do_test_finished();
+
+    } else if (data == "cleared") {
+      // only clear once: at the end
+      do_check_true(needsToClear);
+      needsToClear = false;
+      do_test_finished();
+    } else {
+      dump("subject: " + subject + "  data: " + data + "\n");
+    }
+  },
+};
+
+function run_test() {
+
+  var obs = Cc["@mozilla.org/observer-service;1"].getService()
+            .QueryInterface(Ci.nsIObserverService);
+
+  obs.addObserver(observer, "perm-changed", false);
+
+  // add a permission
+  do_test_pending(); // for 'add' notification
+  pm.add(permURI, "test/expiration-perm", pm.ALLOW_ACTION, pm.EXPIRE_TIME, theTime + 10000);
+
+  do_test_pending(); // for 'change' notification
+  pm.add(permURI, "test/expiration-perm", pm.ALLOW_ACTION, pm.EXPIRE_TIME, theTime + 20000);
+
+  do_test_pending(); // for 'add' notification
+  pm.add(permURI, "test/permission-notify", pm.DENY_ACTION);
+
+  do_test_pending(); // for 'deleted' notification
+  pm.remove(permURI.asciiHost, "test/permission-notify");
+
+  do_test_pending(); // for 'cleared' notification
+  pm.removeAll();
+
+  do_timeout(100, "cleanup();");
+}
+
+function cleanup() {
+  obs.removeObserver(observer, "perm-changed");
+}
--- a/netwerk/base/public/nsIPermission.idl
+++ b/netwerk/base/public/nsIPermission.idl
@@ -33,18 +33,17 @@
  * 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 "nsISupports.idl"
 
-[scriptable, uuid(28F16D80-157B-11d5-A542-0010A401EB10)]
-
+[scriptable, uuid(5036f0f6-f77b-4168-9d57-a1c0dd66cf02)]
 /**
  * This interface defines a "permission" object,
  * used to specify allowed/blocked objects from
  * user-specified sites (cookies, images etc).
  */
 
 interface nsIPermission : nsISupports
 {
@@ -61,9 +60,22 @@ interface nsIPermission : nsISupports
      * @see nsIPermissionManager
      */
     readonly attribute ACString type;
 
     /**
      * The permission (see nsIPermissionManager.idl for allowed values)
      */
     readonly attribute PRUint32 capability;
+
+    /**
+     * The expiration type of the permission (session, time-based or none).
+     * Constants are EXPIRE_*, defined in nsIPermissionManager.
+     * @see nsIPermissionManager
+     */
+    readonly attribute PRUint32 expireType;
+
+    /**
+     * The expiration time of the permission (milliseconds since Jan 1 1970
+     * 0:00:00).
+     */
+    readonly attribute PRInt64 expireTime;
 };
--- a/netwerk/base/public/nsIPermissionManager.idl
+++ b/netwerk/base/public/nsIPermissionManager.idl
@@ -61,31 +61,40 @@
  */
 
 #include "nsISupports.idl"
 #include "nsISimpleEnumerator.idl"
 
 interface nsIURI;
 interface nsIObserver;
 
-[scriptable, uuid(00708302-684c-42d6-a5a3-995d51b1d17c)]
+[scriptable, uuid(0b83f9d5-3f96-41b6-91aa-ff3a7e4880d7)]
 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;
 
   /**
+   * Predefined expiration types for permissions.  Permissions can be permanent
+   * (never expire), expire at the end of the session, or expire at a specified
+   * time.
+   */
+  const PRUint32 EXPIRE_NEVER = 0;
+  const PRUint32 EXPIRE_SESSION = 1;
+  const PRUint32 EXPIRE_TIME = 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. 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
@@ -93,20 +102,28 @@ interface nsIPermissionManager : nsISupp
    * @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.
+   * @param expiretype  a constant defining whether this permission should
+   *                    never expire (EXPIRE_NEVER), expire at the end of the
+   *                    session (EXPIRE_SESSION), or expire at a specified time
+   *                    (EXPIRE_TIME).
+   * @param expiretime  an integer representation of when this permission
+   *                    should be forgotten (milliseconds since Jan 1 1970 0:00:00). 
    */
   void add(in nsIURI uri,
            in string type,
-           in PRUint32 permission);
+           in PRUint32 permission,
+           [optional] in PRUint32 expireType,
+           [optional] in PRInt64 expireTime);
 
   /**
    * 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
--- a/xpinstall/src/nsInstallTrigger.cpp
+++ b/xpinstall/src/nsInstallTrigger.cpp
@@ -295,17 +295,18 @@ static void updatePermissions( const cha
 
             host = Substring(hostlist, start, match-start);
             host.CompressWhitespace();
             host.Insert("http://", 0);
 
             rv = NS_NewURI(getter_AddRefs(uri), host);
             if (NS_SUCCEEDED(rv))
             {
-                aPermissionManager->Add( uri, XPI_PERMISSION, aPermission );
+                aPermissionManager->Add( uri, XPI_PERMISSION, aPermission, 
+                                         nsIPermissionManager::EXPIRE_NEVER, 0 );
             }
             start = match+1;
         } while ( match > 0 );
 
         // save empty list, we don't need to do this again
         aPrefBranch->SetCharPref( aPref, "");
     }
 }