Bug 1189070 - Don't discard localhost or IP address entries from the permissions database when migrating. r=ehsan, a=sledru
authorMichael Layzell <michael@thelayzells.com>
Fri, 31 Jul 2015 10:51:45 -0700
changeset 288762 947b74a5bef4ee5ac6f4bf61f6dfbb69825b069c
parent 288761 41bd412c39647a041ad1a9c4584ec63f4402419c
child 288763 ba6a50587731597eaa380c3274e588ffca089c89
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersehsan, sledru
bugs1189070
milestone42.0a2
Bug 1189070 - Don't discard localhost or IP address entries from the permissions database when migrating. r=ehsan, a=sledru
extensions/cookie/nsPermissionManager.cpp
extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js
extensions/cookie/test/unit/test_permmanager_migrate_4-7.js
extensions/cookie/test/unit/test_permmanager_migrate_4-7_no_history.js
extensions/cookie/test/unit/test_permmanager_migrate_5-7a.js
extensions/cookie/test/unit/test_permmanager_migrate_5-7b.js
extensions/cookie/test/unit/test_permmanager_migrate_6-7a.js
extensions/cookie/test/unit/test_permmanager_migrate_7-8.js
extensions/cookie/test/unit/xpcshell.ini
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -199,17 +199,17 @@ NS_IMPL_ISUPPORTS(AppClearDataObserver, 
 
 class MOZ_STACK_CLASS UpgradeHostToOriginHelper {
 public:
   virtual nsresult Insert(const nsACString& aOrigin, const nsAFlatCString& aType,
                           uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
                           int64_t aModificationTime) = 0;
 };
 
-class UpgradeHostToOriginDBMigration final : public UpgradeHostToOriginHelper {
+class MOZ_STACK_CLASS UpgradeHostToOriginDBMigration final : public UpgradeHostToOriginHelper {
 public:
   UpgradeHostToOriginDBMigration(mozIStorageConnection* aDBConn, int64_t* aID) : mDBConn(aDBConn)
                                                                                , mID(aID)
   {
     mDBConn->CreateStatement(NS_LITERAL_CSTRING(
       "INSERT INTO moz_hosts_new "
       "(id, origin, type, permission, expireType, expireTime, modificationTime) "
       "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"), getter_AddRefs(mStmt));
@@ -251,17 +251,17 @@ public:
   }
 
 private:
   nsCOMPtr<mozIStorageStatement> mStmt;
   nsCOMPtr<mozIStorageConnection> mDBConn;
   int64_t* mID;
 };
 
-class UpgradeHostToOriginHostfileImport final : public UpgradeHostToOriginHelper {
+class MOZ_STACK_CLASS UpgradeHostToOriginHostfileImport final : public UpgradeHostToOriginHelper {
 public:
   UpgradeHostToOriginHostfileImport(nsPermissionManager* aPm,
                                     nsPermissionManager::DBOperationType aOperation,
                                     int64_t aID) : mPm(aPm)
                                                  , mOperation(aOperation)
                                                  , mID(aID)
   {}
 
@@ -280,16 +280,103 @@ public:
   }
 
 private:
   nsRefPtr<nsPermissionManager> mPm;
   nsPermissionManager::DBOperationType mOperation;
   int64_t mID;
 };
 
+class MOZ_STACK_CLASS UpgradeIPHostToOriginDB final : public UpgradeHostToOriginHelper {
+public:
+  UpgradeIPHostToOriginDB(mozIStorageConnection* aDBConn, int64_t* aID) : mDBConn(aDBConn)
+                                                                        , mID(aID)
+  {
+    mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+      "INSERT INTO moz_perms"
+      "(id, origin, type, permission, expireType, expireTime, modificationTime) "
+      "VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)"), getter_AddRefs(mStmt));
+
+    mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+      "SELECT id FROM moz_perms WHERE origin = ?1 AND type = ?2"),
+      getter_AddRefs(mLookupStmt));
+  }
+
+  nsresult
+  Insert(const nsACString& aOrigin, const nsAFlatCString& aType,
+         uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
+         int64_t aModificationTime) final
+  {
+    // Every time the migration code wants to insert an origin into
+    // the database we need to check to see if someone has already
+    // created a permissions entry for that permission. If they have,
+    // we don't want to insert a duplicate row.
+    //
+    // We can afford to do this lookup unconditionally and not perform
+    // caching, as a origin type pair should only be attempted to be
+    // inserted once.
+
+    nsresult rv = mLookupStmt->Reset();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mLookupStmt->BindUTF8StringByIndex(0, aOrigin);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mLookupStmt->BindUTF8StringByIndex(1, aType);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Check if we already have the row in the database, if we do, then
+    // we don't want to be inserting it again.
+    bool moreStmts = false;
+    if (NS_FAILED(mLookupStmt->ExecuteStep(&moreStmts)) || moreStmts) {
+      mLookupStmt->Reset();
+      NS_WARNING("A permissions entry was going to be re-migrated, "
+                 "but was already found in the permissions database.");
+      return NS_OK;
+    }
+
+    // Actually insert the statement into the database.
+    rv = mStmt->BindInt64ByIndex(0, *mID);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mStmt->BindUTF8StringByIndex(1, aOrigin);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mStmt->BindUTF8StringByIndex(2, aType);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mStmt->BindInt32ByIndex(3, aPermission);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mStmt->BindInt32ByIndex(4, aExpireType);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mStmt->BindInt64ByIndex(5, aExpireTime);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = mStmt->BindInt64ByIndex(6, aModificationTime);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    // Increment the working identifier, as we are about to use this one
+    (*mID)++;
+
+    rv = mStmt->Execute();
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
+  }
+
+private:
+  nsCOMPtr<mozIStorageStatement> mStmt;
+  nsCOMPtr<mozIStorageStatement> mLookupStmt;
+  nsCOMPtr<mozIStorageConnection> mDBConn;
+  int64_t* mID;
+};
+
+
 nsresult
 UpgradeHostToOriginAndInsert(const nsACString& aHost, const nsAFlatCString& aType,
                              uint32_t aPermission, uint32_t aExpireType, int64_t aExpireTime,
                              int64_t aModificationTime, uint32_t aAppId, bool aIsInBrowserElement,
                              UpgradeHostToOriginHelper* aHelper)
 {
   if (aHost.EqualsLiteral("<file>")) {
     // We no longer support the magic host <file>
@@ -339,20 +426,22 @@ UpgradeHostToOriginAndInsert(const nsACS
 
     // Get the eTLD+1 of the domain
     nsAutoCString eTLD1;
     nsCOMPtr<nsIEffectiveTLDService> tldService =
       do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
     MOZ_ASSERT(tldService); // We should always have a tldService
     if (tldService) {
       rv = tldService->GetBaseDomainFromHost(aHost, 0, eTLD1);
-      NS_ENSURE_SUCCESS(rv, rv);
-    } else {
-      // We should never hit this branch, but we produce a fake eTLD1
-      // to avoid crashing in a release build in case we hit this branch
+    }
+
+    if (!tldService || NS_FAILED(rv)) {
+      // If the lookup on the tldService for the base domain for the host failed,
+      // that means that we just want to directly use the host as the host name
+      // for the lookup.
       eTLD1 = aHost;
     }
 
     // We want to only find history items for this particular eTLD+1, and subdomains
     rv = histQuery->SetDomain(eTLD1);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = histQuery->SetDomainIsHost(false);
@@ -441,42 +530,55 @@ UpgradeHostToOriginAndInsert(const nsACS
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // If we didn't find any origins for this host in the poermissions database,
   // we can insert the default http:// and https:// permissions into the database.
   // This has a relatively high liklihood of applying the permission to the correct
   // origin.
   if (!foundHistory) {
-    rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("http://") + aHost);
-    if (NS_SUCCEEDED(rv)) {
-      nsCOMPtr<nsIPrincipal> principal;
-      rv = GetPrincipal(uri, aAppId, aIsInBrowserElement, getter_AddRefs(principal));
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      nsAutoCString origin;
-      rv = principal->GetOrigin(origin);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      aHelper->Insert(origin, aType, aPermission,
-                      aExpireType, aExpireTime, aModificationTime);
+    nsAutoCString hostSegment;
+    nsCOMPtr<nsIPrincipal> principal;
+    nsAutoCString origin;
+
+    // If this is an ipv6 URI, we need to surround it in '[', ']' before trying to
+    // parse it as a URI.
+    if (aHost.FindChar(':') != -1) {
+      hostSegment.Assign("[");
+      hostSegment.Append(aHost);
+      hostSegment.Append("]");
+    } else {
+      hostSegment.Assign(aHost);
     }
-    rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://") + aHost);
-    if (NS_SUCCEEDED(rv)) {
-      nsCOMPtr<nsIPrincipal> principal;
-      rv = GetPrincipal(uri, aAppId, aIsInBrowserElement, getter_AddRefs(principal));
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      nsAutoCString origin;
-      rv = principal->GetOrigin(origin);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      aHelper->Insert(origin, aType, aPermission,
-                      aExpireType, aExpireTime, aModificationTime);
-    }
+
+    // http:// URI default
+    rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("http://") + hostSegment);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = GetPrincipal(uri, aAppId, aIsInBrowserElement, getter_AddRefs(principal));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = principal->GetOrigin(origin);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    aHelper->Insert(origin, aType, aPermission,
+                    aExpireType, aExpireTime, aModificationTime);
+
+    // https:// URI default
+    rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_CSTRING("https://") + hostSegment);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = GetPrincipal(uri, aAppId, aIsInBrowserElement, getter_AddRefs(principal));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = principal->GetOrigin(origin);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    aHelper->Insert(origin, aType, aPermission,
+                    aExpireType, aExpireTime, aModificationTime);
   }
 
   return NS_OK;
 }
 
 static bool
 IsExpandedPrincipal(nsIPrincipal* aPrincipal)
 {
@@ -609,17 +711,17 @@ nsPermissionManager::AppClearDataObserve
     mozilla::services::GetObserverService();
   observerService->AddObserver(new AppClearDataObserver(), "webapps-clear-data", /* holdsWeak= */ false);
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsPermissionManager Implementation
 
 #define PERMISSIONS_FILE_NAME "permissions.sqlite"
-#define HOSTS_SCHEMA_VERSION 7
+#define HOSTS_SCHEMA_VERSION 8
 
 #define HOSTPERM_FILE_NAME "hostperm.1"
 
 // Default permissions are read from a URL - this is the preference we read
 // to find that URL. If not set, don't use any default permissions.
 static const char kDefaultsUrlPrefName[] = "permissions.manager.defaultsUrl";
 
 static const char kPermissionChangeNotification[] = PERM_CHANGE_NOTIFICATION;
@@ -1054,18 +1156,18 @@ nsPermissionManager::InitDB(bool aRemove
           bool permsTableExists = false;
           mDBConn->TableExists(NS_LITERAL_CSTRING("moz_perms"), &permsTableExists);
           if (permsTableExists) {
             // The user already had a moz_perms table, and we are performing a
             // re-migration. We count the rows in the old table for telemetry,
             // and then back up their old database as moz_perms_v6
 
             nsCOMPtr<mozIStorageStatement> countStmt;
-            mDBConn->CreateStatement(NS_LITERAL_CSTRING("SELECT COUNT(*) FROM moz_perms"),
-                                     getter_AddRefs(countStmt));
+            rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING("SELECT COUNT(*) FROM moz_perms"),
+                                          getter_AddRefs(countStmt));
             bool hasResult = false;
             if (NS_SUCCEEDED(rv) &&
                 NS_SUCCEEDED(countStmt->ExecuteStep(&hasResult)) &&
                 hasResult) {
               int32_t permsCount = countStmt->AsInt32(0);
 
               // The id variable contains the number of rows inserted into the
               // moz_hosts_new table (as one ID was used per entry)
@@ -1134,16 +1236,117 @@ nsPermissionManager::InitDB(bool aRemove
           MOZ_ASSERT(hostsTableExists && permsTableExists);
         }
 #endif
 
         rv = mDBConn->SetSchemaVersion(7);
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
+      // fall through to the next upgrade
+
+    // The version 7-8 migration is the re-migration of localhost and ip-address
+    // entries due to errors in the previous version 7 migration which caused
+    // localhost and ip-address entries to be incorrectly discarded.
+    // The version 7 migration logic has been corrected, and thus this logic only
+    // needs to execute if the user is currently on version 7.
+    case 7:
+      {
+        // This migration will be relatively expensive as we need to perform
+        // database lookups for each origin which we want to insert. Fortunately,
+        // it shouldn't be too expensive as we only want to insert a small number
+        // of entries created for localhost or IP addresses.
+
+        // We only want to perform the re-migration if moz_hosts is a backup
+        bool hostsIsBackupExists = false;
+        mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_is_backup"),
+                             &hostsIsBackupExists);
+
+        // Only perform this migration if the original schema version was 7, and
+        // the moz_hosts table is a backup.
+        if (dbSchemaVersion == 7 && hostsIsBackupExists) {
+          nsCOMPtr<nsIEffectiveTLDService> tldService =
+            do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
+          MOZ_ASSERT(tldService); // We should always have a tldService
+
+          nsCOMPtr<mozIStorageStatement> stmt;
+          rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+            "SELECT host, type, permission, expireType, expireTime, "
+            "modificationTime, appId, isInBrowserElement FROM moz_hosts"),
+             getter_AddRefs(stmt));
+          NS_ENSURE_SUCCESS(rv, rv);
+
+          nsCOMPtr<mozIStorageStatement> idStmt;
+          rv = mDBConn->CreateStatement(NS_LITERAL_CSTRING(
+            "SELECT MAX(id) FROM moz_hosts"), getter_AddRefs(idStmt));
+          int64_t id = 0;
+          bool hasResult = false;
+          if (NS_SUCCEEDED(rv) &&
+              NS_SUCCEEDED(idStmt->ExecuteStep(&hasResult)) &&
+              hasResult) {
+            id = idStmt->AsInt32(0) + 1;
+          }
+
+          nsAutoCString host, type;
+          uint32_t permission;
+          uint32_t expireType;
+          int64_t expireTime;
+          int64_t modificationTime;
+          uint32_t appId;
+          bool isInBrowserElement;
+
+          while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
+            // Read in the old row
+            rv = stmt->GetUTF8String(0, host);
+            if (NS_WARN_IF(NS_FAILED(rv))) {
+              continue;
+            }
+
+            nsAutoCString eTLD1;
+            rv = tldService->GetBaseDomainFromHost(host, 0, eTLD1);
+            if (NS_SUCCEEDED(rv)) {
+              // We only care about entries which the tldService can't handle
+              continue;
+            }
+
+            rv = stmt->GetUTF8String(1, type);
+            if (NS_WARN_IF(NS_FAILED(rv))) {
+              continue;
+            }
+            permission = stmt->AsInt32(2);
+            expireType = stmt->AsInt32(3);
+            expireTime = stmt->AsInt64(4);
+            modificationTime = stmt->AsInt64(5);
+            if (NS_WARN_IF(stmt->AsInt64(6) < 0)) {
+              continue;
+            }
+            appId = static_cast<uint32_t>(stmt->AsInt64(6));
+            isInBrowserElement = static_cast<bool>(stmt->AsInt32(7));
+
+            // Perform the meat of the migration by deferring to the
+            // UpgradeHostToOriginAndInsert function.
+            UpgradeIPHostToOriginDB upHelper(mDBConn, &id);
+            rv = UpgradeHostToOriginAndInsert(host, type, permission,
+                                              expireType, expireTime,
+                                              modificationTime, appId,
+                                              isInBrowserElement,
+                                              &upHelper);
+            if (NS_FAILED(rv)) {
+              NS_WARNING("Unexpected failure when upgrading migrating permission "
+                         "from host to origin");
+            }
+          }
+        }
+
+        // Even if we didn't perform the migration, we want to bump the schema
+        // version to 8.
+        rv = mDBConn->SetSchemaVersion(8);
+        NS_ENSURE_SUCCESS(rv, rv);
+      }
+
     // current version.
     case HOSTS_SCHEMA_VERSION:
       break;
 
     // downgrading.
     // if columns have been added to the table, we can still use the ones we
     // understand safely. if columns have been deleted or altered, just
     // blow away the table and start from scratch! if you change the way
--- a/extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js
+++ b/extensions/cookie/test/unit/test_permmanager_load_invalid_entries.js
@@ -116,17 +116,17 @@ function run_test() {
   let earliestNow = Number(Date.now());
   // Initialize the permission manager service
   var pm = Cc["@mozilla.org/permissionmanager;1"]
              .getService(Ci.nsIPermissionManager);
   let latestNow = Number(Date.now());
 
   // The schema should be upgraded to 6, and a 'modificationTime' column should
   // exist with all records having a value of 0.
-  do_check_eq(connection.schemaVersion, 7);
+  do_check_eq(connection.schemaVersion, 8);
 
   let select = connection.createStatement("SELECT modificationTime FROM moz_perms")
   let numMigrated = 0;
   while (select.executeStep()) {
     let thisModTime = select.getInt64(0);
     do_check_true(thisModTime == 0, "new modifiedTime field is correct");
     numMigrated += 1;
   }
--- a/extensions/cookie/test/unit/test_permmanager_migrate_4-7.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_4-7.js
@@ -80,16 +80,19 @@ add_task(function test() {
     insertHost("foo.com", "C", 1, 0, 0, 0, 0, false),
     insertHost("foo.com", "A", 1, 0, 0, 0, 1000, false),
     insertHost("foo.com", "A", 1, 0, 0, 0, 2000, true),
     insertHost("sub.foo.com", "B", 1, 0, 0, 0, 0, false),
     insertHost("subber.sub.foo.com", "B", 1, 0, 0, 0, 0, false),
     insertHost("bar.ca", "B", 1, 0, 0, 0, 0, false),
     insertHost("bar.ca", "B", 1, 0, 0, 0, 1000, false),
     insertHost("bar.ca", "A", 1, 0, 0, 0, 1000, true),
+    insertHost("localhost", "A", 1, 0, 0, 0, 0, false),
+    insertHost("127.0.0.1", "A", 1, 0, 0, 0, 0, false),
+    insertHost("192.0.2.235", "A", 1, 0, 0, 0, 0, false),
     insertHost("file:///some/path/to/file.html", "A", 1, 0, 0, 0, 0, false),
     insertHost("file:///another/file.html", "A", 1, 0, 0, 0, 0, false),
     insertHost("moz-nullprincipal:{8695105a-adbe-4e4e-8083-851faa5ca2d7}", "A", 1, 0, 0, 0, 0, false),
     insertHost("moz-nullprincipal:{12ahjksd-akjs-asd3-8393-asdu2189asdu}", "B", 1, 0, 0, 0, 0, false),
     insertHost("<file>", "A", 1, 0, 0, 0, 0, false),
     insertHost("<file>", "B", 1, 0, 0, 0, 0, false),
   ];
 
@@ -134,16 +137,24 @@ add_task(function test() {
     ["ftp://foo.com:8000", "C", 1, 0, 0],
     ["ftp://foo.com:8000^appId=1000", "A", 1, 0, 0],
     ["ftp://foo.com:8000^appId=2000&inBrowser=1", "A", 1, 0, 0],
 
     // In addition, because we search for port/scheme combinations under eTLD+1, we should have the
     // following entries
     ["ftp://sub.foo.com:8000", "B", 1, 0, 0],
     ["ftp://subber.sub.foo.com:8000", "B", 1, 0, 0],
+
+    // Make sure that we also support localhost, and IP addresses
+    ["http://localhost", "A", 1, 0, 0],
+    ["https://localhost", "A", 1, 0, 0],
+    ["http://127.0.0.1", "A", 1, 0, 0],
+    ["https://127.0.0.1", "A", 1, 0, 0],
+    ["http://192.0.2.235", "A", 1, 0, 0],
+    ["https://192.0.2.235", "A", 1, 0, 0],
   ];
 
   let found = expected.map((it) => 0);
 
   // Add some places to the places database
   yield PlacesTestUtils.addVisits(Services.io.newURI("https://foo.com/some/other/subdirectory", null, null));
   yield PlacesTestUtils.addVisits(Services.io.newURI("ftp://some.subdomain.of.foo.com:8000/some/subdirectory", null, null));
 
--- a/extensions/cookie/test/unit/test_permmanager_migrate_4-7_no_history.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_4-7_no_history.js
@@ -117,16 +117,19 @@ add_task(function test() {
     insertHost("foo.com", "C", 1, 0, 0, 0, 0, false),
     insertHost("foo.com", "A", 1, 0, 0, 0, 1000, false),
     insertHost("foo.com", "A", 1, 0, 0, 0, 2000, true),
     insertHost("sub.foo.com", "B", 1, 0, 0, 0, 0, false),
     insertHost("subber.sub.foo.com", "B", 1, 0, 0, 0, 0, false),
     insertHost("bar.ca", "B", 1, 0, 0, 0, 0, false),
     insertHost("bar.ca", "B", 1, 0, 0, 0, 1000, false),
     insertHost("bar.ca", "A", 1, 0, 0, 0, 1000, true),
+    insertHost("localhost", "A", 1, 0, 0, 0, 0, false),
+    insertHost("127.0.0.1", "A", 1, 0, 0, 0, 0, false),
+    insertHost("263.123.555.676", "A", 1, 0, 0, 0, 0, false),
     insertHost("file:///some/path/to/file.html", "A", 1, 0, 0, 0, 0, false),
     insertHost("file:///another/file.html", "A", 1, 0, 0, 0, 0, false),
     insertHost("moz-nullprincipal:{8695105a-adbe-4e4e-8083-851faa5ca2d7}", "A", 1, 0, 0, 0, 0, false),
     insertHost("moz-nullprincipal:{12ahjksd-akjs-asd3-8393-asdu2189asdu}", "B", 1, 0, 0, 0, 0, false),
     insertHost("<file>", "A", 1, 0, 0, 0, 0, false),
     insertHost("<file>", "B", 1, 0, 0, 0, 0, false),
   ];
 
@@ -155,16 +158,24 @@ add_task(function test() {
     ["http://bar.ca", "B", 1, 0, 0],
     ["https://bar.ca", "B", 1, 0, 0],
     ["http://bar.ca^appId=1000", "B", 1, 0, 0],
     ["https://bar.ca^appId=1000", "B", 1, 0, 0],
     ["http://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
     ["https://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
     ["file:///some/path/to/file.html", "A", 1, 0, 0],
     ["file:///another/file.html", "A", 1, 0, 0],
+
+    // Make sure that we also support localhost, and IP addresses
+    ["http://localhost", "A", 1, 0, 0],
+    ["https://localhost", "A", 1, 0, 0],
+    ["http://127.0.0.1", "A", 1, 0, 0],
+    ["https://127.0.0.1", "A", 1, 0, 0],
+    ["http://263.123.555.676", "A", 1, 0, 0],
+    ["https://263.123.555.676", "A", 1, 0, 0],
   ];
 
   let found = expected.map((it) => 0);
 
   // Force initialization of the nsPermissionManager
   let enumerator = Services.perms.enumerator;
   while (enumerator.hasMoreElements()) {
     let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
--- a/extensions/cookie/test/unit/test_permmanager_migrate_5-7a.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_5-7a.js
@@ -134,16 +134,19 @@ add_task(function test() {
     insertHost("foo.com", "C", 1, 0, 0, 0, 0, false),
     insertHost("foo.com", "A", 1, 0, 0, 0, 1000, false),
     insertHost("foo.com", "A", 1, 0, 0, 0, 2000, true),
     insertHost("sub.foo.com", "B", 1, 0, 0, 0, 0, false),
     insertHost("subber.sub.foo.com", "B", 1, 0, 0, 0, 0, false),
     insertHost("bar.ca", "B", 1, 0, 0, 0, 0, false),
     insertHost("bar.ca", "B", 1, 0, 0, 0, 1000, false),
     insertHost("bar.ca", "A", 1, 0, 0, 0, 1000, true),
+    insertHost("localhost", "A", 1, 0, 0, 0, 0, false),
+    insertHost("127.0.0.1", "A", 1, 0, 0, 0, 0, false),
+    insertHost("192.0.2.235", "A", 1, 0, 0, 0, 0, false),
     insertHost("file:///some/path/to/file.html", "A", 1, 0, 0, 0, 0, false),
     insertHost("file:///another/file.html", "A", 1, 0, 0, 0, 0, false),
     insertHost("moz-nullprincipal:{8695105a-adbe-4e4e-8083-851faa5ca2d7}", "A", 1, 0, 0, 0, 0, false),
     insertHost("moz-nullprincipal:{12ahjksd-akjs-asd3-8393-asdu2189asdu}", "B", 1, 0, 0, 0, 0, false),
     insertHost("<file>", "A", 1, 0, 0, 0, 0, false),
     insertHost("<file>", "B", 1, 0, 0, 0, 0, false),
   ];
 
@@ -188,16 +191,24 @@ add_task(function test() {
     ["ftp://foo.com:8000", "C", 1, 0, 0],
     ["ftp://foo.com:8000^appId=1000", "A", 1, 0, 0],
     ["ftp://foo.com:8000^appId=2000&inBrowser=1", "A", 1, 0, 0],
 
     // In addition, because we search for port/scheme combinations under eTLD+1, we should have the
     // following entries
     ["ftp://sub.foo.com:8000", "B", 1, 0, 0],
     ["ftp://subber.sub.foo.com:8000", "B", 1, 0, 0],
+
+    // Make sure that we also support localhost, and IP addresses
+    ["http://localhost", "A", 1, 0, 0],
+    ["https://localhost", "A", 1, 0, 0],
+    ["http://127.0.0.1", "A", 1, 0, 0],
+    ["https://127.0.0.1", "A", 1, 0, 0],
+    ["http://192.0.2.235", "A", 1, 0, 0],
+    ["https://192.0.2.235", "A", 1, 0, 0],
   ];
 
   let found = expected.map((it) => 0);
 
   // Add some places to the places database
   yield PlacesTestUtils.addVisits(Services.io.newURI("https://foo.com/some/other/subdirectory", null, null));
   yield PlacesTestUtils.addVisits(Services.io.newURI("ftp://some.subdomain.of.foo.com:8000/some/subdirectory", null, null));
 
--- a/extensions/cookie/test/unit/test_permmanager_migrate_5-7b.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_5-7b.js
@@ -70,30 +70,36 @@ add_task(function test() {
       modificationTime: modificationTime
     };
   }
 
   let created5 = [
     insertOrigin("https://foo.com", "A", 2, 0, 0, 0),
     insertOrigin("http://foo.com", "A", 2, 0, 0, 0),
     insertOrigin("http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0),
+
+    insertOrigin("http://127.0.0.1", "B", 2, 0, 0, 0),
+    insertOrigin("http://localhost", "B", 2, 0, 0, 0),
   ];
 
   let created4 = []; // Didn't create any v4 entries, so the DB should be empty
 
   // CLose the db connection
   stmt5Insert.finalize();
   db.close();
   stmt5Insert = null;
   db = null;
 
   let expected = [
     ["https://foo.com", "A", 2, 0, 0, 0],
     ["http://foo.com", "A", 2, 0, 0, 0],
-    ["http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0]
+    ["http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0],
+
+    ["http://127.0.0.1", "B", 2, 0, 0, 0],
+    ["http://localhost", "B", 2, 0, 0, 0],
   ];
 
   let found = expected.map((it) => 0);
 
   // Force initialization of the nsPermissionManager
   let enumerator = Services.perms.enumerator;
   while (enumerator.hasMoreElements()) {
     let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
--- a/extensions/cookie/test/unit/test_permmanager_migrate_6-7a.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_6-7a.js
@@ -134,16 +134,19 @@ add_task(function test() {
     insertHost("foo.com", "C", 1, 0, 0, 0, 0, false),
     insertHost("foo.com", "A", 1, 0, 0, 0, 1000, false),
     insertHost("foo.com", "A", 1, 0, 0, 0, 2000, true),
     insertHost("sub.foo.com", "B", 1, 0, 0, 0, 0, false),
     insertHost("subber.sub.foo.com", "B", 1, 0, 0, 0, 0, false),
     insertHost("bar.ca", "B", 1, 0, 0, 0, 0, false),
     insertHost("bar.ca", "B", 1, 0, 0, 0, 1000, false),
     insertHost("bar.ca", "A", 1, 0, 0, 0, 1000, true),
+    insertHost("localhost", "A", 1, 0, 0, 0, 0, false),
+    insertHost("127.0.0.1", "A", 1, 0, 0, 0, 0, false),
+    insertHost("192.0.2.235", "A", 1, 0, 0, 0, 0, false),
     insertHost("file:///some/path/to/file.html", "A", 1, 0, 0, 0, 0, false),
     insertHost("file:///another/file.html", "A", 1, 0, 0, 0, 0, false),
     insertHost("moz-nullprincipal:{8695105a-adbe-4e4e-8083-851faa5ca2d7}", "A", 1, 0, 0, 0, 0, false),
     insertHost("moz-nullprincipal:{12ahjksd-akjs-asd3-8393-asdu2189asdu}", "B", 1, 0, 0, 0, 0, false),
     insertHost("<file>", "A", 1, 0, 0, 0, 0, false),
     insertHost("<file>", "B", 1, 0, 0, 0, 0, false),
   ];
 
@@ -188,16 +191,24 @@ add_task(function test() {
     ["ftp://foo.com:8000", "C", 1, 0, 0],
     ["ftp://foo.com:8000^appId=1000", "A", 1, 0, 0],
     ["ftp://foo.com:8000^appId=2000&inBrowser=1", "A", 1, 0, 0],
 
     // In addition, because we search for port/scheme combinations under eTLD+1, we should have the
     // following entries
     ["ftp://sub.foo.com:8000", "B", 1, 0, 0],
     ["ftp://subber.sub.foo.com:8000", "B", 1, 0, 0],
+
+    // Make sure that we also support localhost, and IP addresses
+    ["http://localhost", "A", 1, 0, 0],
+    ["https://localhost", "A", 1, 0, 0],
+    ["http://127.0.0.1", "A", 1, 0, 0],
+    ["https://127.0.0.1", "A", 1, 0, 0],
+    ["http://192.0.2.235", "A", 1, 0, 0],
+    ["https://192.0.2.235", "A", 1, 0, 0],
   ];
 
   let found = expected.map((it) => 0);
 
   // Add some places to the places database
   yield PlacesTestUtils.addVisits(Services.io.newURI("https://foo.com/some/other/subdirectory", null, null));
   yield PlacesTestUtils.addVisits(Services.io.newURI("ftp://some.subdomain.of.foo.com:8000/some/subdirectory", null, null));
 
new file mode 100644
--- /dev/null
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_7-8.js
@@ -0,0 +1,266 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
+                                  "resource://testing-common/PlacesTestUtils.jsm");
+
+let PERMISSIONS_FILE_NAME = "permissions.sqlite";
+
+function GetPermissionsFile(profile)
+{
+  let file = profile.clone();
+  file.append(PERMISSIONS_FILE_NAME);
+  return file;
+}
+
+function run_test() {
+  run_next_test();
+}
+
+add_task(function test() {
+  /* Create and set up the permissions database */
+  let profile = do_get_profile();
+
+  let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+  db.schemaVersion = 7;
+
+  /*
+   * V5 table
+   */
+  db.executeSimpleSQL(
+    "CREATE TABLE moz_perms (" +
+      " id INTEGER PRIMARY KEY" +
+      ",origin TEXT" +
+      ",type TEXT" +
+      ",permission INTEGER" +
+      ",expireType INTEGER" +
+      ",expireTime INTEGER" +
+      ",modificationTime INTEGER" +
+    ")");
+
+  let stmt6Insert = db.createStatement(
+    "INSERT INTO moz_perms (" +
+      "id, origin, type, permission, expireType, expireTime, modificationTime" +
+    ") VALUES (" +
+      ":id, :origin, :type, :permission, :expireType, :expireTime, :modificationTime" +
+    ")");
+
+  /*
+   * V4 table
+   */
+  db.executeSimpleSQL(
+    "CREATE TABLE moz_hosts (" +
+      " id INTEGER PRIMARY KEY" +
+      ",host TEXT" +
+      ",type TEXT" +
+      ",permission INTEGER" +
+      ",expireType INTEGER" +
+      ",expireTime INTEGER" +
+      ",modificationTime INTEGER" +
+      ",appId INTEGER" +
+      ",isInBrowserElement INTEGER" +
+    ")");
+
+  let stmtInsert = db.createStatement(
+    "INSERT INTO moz_hosts (" +
+      "id, host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement" +
+    ") VALUES (" +
+      ":id, :host, :type, :permission, :expireType, :expireTime, :modificationTime, :appId, :isInBrowserElement" +
+    ")");
+
+  /*
+   * The v4 table is a backup
+   */
+  db.executeSimpleSQL("CREATE TABLE moz_hosts_is_backup (dummy INTEGER PRIMARY KEY)");
+
+  let id = 0;
+
+  function insertOrigin(origin, type, permission, expireType, expireTime, modificationTime) {
+    let thisId = id++;
+
+    stmt6Insert.bindByName("id", thisId);
+    stmt6Insert.bindByName("origin", origin);
+    stmt6Insert.bindByName("type", type);
+    stmt6Insert.bindByName("permission", permission);
+    stmt6Insert.bindByName("expireType", expireType);
+    stmt6Insert.bindByName("expireTime", expireTime);
+    stmt6Insert.bindByName("modificationTime", modificationTime);
+
+    stmt6Insert.execute();
+
+    return {
+      id: thisId,
+      origin: origin,
+      type: type,
+      permission: permission,
+      expireType: expireType,
+      expireTime: expireTime,
+      modificationTime: modificationTime
+    };
+  }
+
+  function insertHost(host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement) {
+    let thisId = id++;
+
+    stmtInsert.bindByName("id", thisId);
+    stmtInsert.bindByName("host", host);
+    stmtInsert.bindByName("type", type);
+    stmtInsert.bindByName("permission", permission);
+    stmtInsert.bindByName("expireType", expireType);
+    stmtInsert.bindByName("expireTime", expireTime);
+    stmtInsert.bindByName("modificationTime", modificationTime);
+    stmtInsert.bindByName("appId", appId);
+    stmtInsert.bindByName("isInBrowserElement", isInBrowserElement);
+
+    stmtInsert.execute();
+
+    return {
+      id: thisId,
+      host: host,
+      type: type,
+      permission: permission,
+      expireType: expireType,
+      expireTime: expireTime,
+      modificationTime: modificationTime,
+      appId: appId,
+      isInBrowserElement: isInBrowserElement
+    };
+  }
+
+  let created7 = [
+    insertOrigin("https://foo.com", "A", 2, 0, 0, 0),
+    insertOrigin("http://foo.com", "A", 2, 0, 0, 0),
+    insertOrigin("http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0),
+    insertOrigin("https://192.0.2.235", "A", 2, 0, 0),
+  ];
+
+  // Add some rows to the database
+  let created = [
+    insertHost("foo.com", "A", 1, 0, 0, 0, 0, false),
+    insertHost("foo.com", "C", 1, 0, 0, 0, 0, false),
+    insertHost("foo.com", "A", 1, 0, 0, 0, 1000, false),
+    insertHost("foo.com", "A", 1, 0, 0, 0, 2000, true),
+    insertHost("sub.foo.com", "B", 1, 0, 0, 0, 0, false),
+    insertHost("subber.sub.foo.com", "B", 1, 0, 0, 0, 0, false),
+    insertHost("bar.ca", "B", 1, 0, 0, 0, 0, false),
+    insertHost("bar.ca", "B", 1, 0, 0, 0, 1000, false),
+    insertHost("bar.ca", "A", 1, 0, 0, 0, 1000, true),
+    insertHost("localhost", "A", 1, 0, 0, 0, 0, false),
+    insertHost("127.0.0.1", "A", 1, 0, 0, 0, 0, false),
+    insertHost("192.0.2.235", "A", 1, 0, 0, 0, 0, false),
+    // Although ipv6 addresses are written with [] around the IP address,
+    // the .host property doesn't contain these []s, which means that we
+    // write it like this
+    insertHost("2001:db8::ff00:42:8329", "C", 1, 0, 0, 0, 0, false),
+    insertHost("file:///some/path/to/file.html", "A", 1, 0, 0, 0, 0, false),
+    insertHost("file:///another/file.html", "A", 1, 0, 0, 0, 0, false),
+    insertHost("moz-nullprincipal:{8695105a-adbe-4e4e-8083-851faa5ca2d7}", "A", 1, 0, 0, 0, 0, false),
+    insertHost("moz-nullprincipal:{12ahjksd-akjs-asd3-8393-asdu2189asdu}", "B", 1, 0, 0, 0, 0, false),
+    insertHost("<file>", "A", 1, 0, 0, 0, 0, false),
+    insertHost("<file>", "B", 1, 0, 0, 0, 0, false),
+  ];
+
+  // CLose the db connection
+  stmtInsert.finalize();
+  db.close();
+  stmtInsert = null;
+  db = null;
+
+  let expected = [
+    // We should have kept the previously migrated entries
+    ["https://foo.com", "A", 2, 0, 0, 0],
+    ["http://foo.com", "A", 2, 0, 0, 0],
+    ["http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0],
+
+    // Make sure that we also support localhost, and IP addresses
+    ["https://localhost:8080", "A", 1, 0, 0],
+    ["ftp://127.0.0.1:8080", "A", 1, 0, 0],
+
+    ["http://[2001:db8::ff00:42:8329]", "C", 1, 0, 0],
+    ["https://[2001:db8::ff00:42:8329]", "C", 1, 0, 0],
+    ["http://192.0.2.235", "A", 1, 0, 0],
+
+    // There should only be one entry of this type in the database
+    ["https://192.0.2.235", "A", 2, 0, 0],
+  ];
+
+  let found = expected.map((it) => 0);
+
+  // Add some places to the places database
+  yield PlacesTestUtils.addVisits(Services.io.newURI("https://foo.com/some/other/subdirectory", null, null));
+  yield PlacesTestUtils.addVisits(Services.io.newURI("ftp://some.subdomain.of.foo.com:8000/some/subdirectory", null, null));
+  yield PlacesTestUtils.addVisits(Services.io.newURI("ftp://127.0.0.1:8080", null, null));
+  yield PlacesTestUtils.addVisits(Services.io.newURI("https://localhost:8080", null, null));
+
+  // Force initialization of the nsPermissionManager
+  let enumerator = Services.perms.enumerator;
+  while (enumerator.hasMoreElements()) {
+    let permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
+    let isExpected = false;
+
+    expected.forEach((it, i) => {
+      if (permission.principal.origin == it[0] &&
+          permission.type == it[1] &&
+          permission.capability == it[2] &&
+          permission.expireType == it[3] &&
+          permission.expireTime == it[4]) {
+        isExpected = true;
+        found[i]++;
+      }
+    });
+
+    do_check_true(isExpected,
+                  "Permission " + (isExpected ? "should" : "shouldn't") +
+                  " be in permission database: " +
+                  permission.principal.origin + ", " +
+                  permission.type + ", " +
+                  permission.capability + ", " +
+                  permission.expireType + ", " +
+                  permission.expireTime);
+  }
+
+  found.forEach((count, i) => {
+    do_check_true(count == 1, "Expected count = 1, got count = " + count + " for permission " + expected[i]);
+  });
+
+  // Check to make sure that all of the tables which we care about are present
+  {
+    let db = Services.storage.openDatabase(GetPermissionsFile(profile));
+    do_check_true(db.tableExists("moz_perms"));
+    do_check_true(db.tableExists("moz_hosts"));
+    do_check_true(db.tableExists("moz_hosts_is_backup"));
+    do_check_false(db.tableExists("moz_perms_v6"));
+
+    let mozHostsStmt = db.createStatement("SELECT " +
+                                          "host, type, permission, expireType, expireTime, " +
+                                          "modificationTime, appId, isInBrowserElement " +
+                                          "FROM moz_hosts WHERE id = :id");
+
+    // Check that the moz_hosts table still contains the correct values.
+    created.forEach((it) => {
+      mozHostsStmt.reset();
+      mozHostsStmt.bindByName("id", it.id);
+      mozHostsStmt.executeStep();
+      do_check_eq(mozHostsStmt.getUTF8String(0), it.host);
+      do_check_eq(mozHostsStmt.getUTF8String(1), it.type);
+      do_check_eq(mozHostsStmt.getInt64(2), it.permission);
+      do_check_eq(mozHostsStmt.getInt64(3), it.expireType);
+      do_check_eq(mozHostsStmt.getInt64(4), it.expireTime);
+      do_check_eq(mozHostsStmt.getInt64(5), it.modificationTime);
+      do_check_eq(mozHostsStmt.getInt64(6), it.appId);
+      do_check_eq(mozHostsStmt.getInt64(7), it.isInBrowserElement);
+    });
+
+    // Check that there are the right number of values
+    let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
+    mozHostsCount.executeStep();
+    do_check_eq(mozHostsCount.getInt64(0), created.length);
+
+    // Check that there are the right number of values in the permissions database
+    let mozPermsCount = db.createStatement("SELECT count(*) FROM moz_perms");
+    mozPermsCount.executeStep();
+    do_check_eq(mozPermsCount.getInt64(0), expected.length);
+
+    db.close();
+  }
+});
--- a/extensions/cookie/test/unit/xpcshell.ini
+++ b/extensions/cookie/test/unit/xpcshell.ini
@@ -38,8 +38,9 @@ skip-if = debug == true
 [test_permmanager_matchesuri.js]
 [test_permmanager_matches.js]
 [test_permmanager_migrate_4-7.js]
 [test_permmanager_migrate_5-7a.js]
 [test_permmanager_migrate_5-7b.js]
 [test_permmanager_migrate_6-7a.js]
 [test_permmanager_migrate_6-7b.js]
 [test_permmanager_migrate_4-7_no_history.js]
+[test_permmanager_migrate_7-8.js]