Bug 1153900 - Fix IE cookies migration. a=sylvestre
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Fri, 17 Apr 2015 05:28:00 -0400
changeset 258519 55837b9aa111
parent 258518 4411b07ee6bd
child 258520 69e54b268783
push id4686
push userryanvm@gmail.com
push date2015-04-17 16:50 +0000
treeherdermozilla-beta@6a5c3aa5b912 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssylvestre
bugs1153900, 1154472, 1154294
milestone38.0
Bug 1153900 - Fix IE cookies migration. a=sylvestre * * * Bug 1153900 - Fix TDZ issue in IEProfileMigrator.js, r=gijs * * * Bug 1153900 - fix use of 'File' constructor in IEProfileMigrator.js, r=mak * * * Bug 1154472 - fix expiry dates in IE cookie imports, r=mak * * * Bug 1154294 - write a test for IE cookies migration. r=Gijs
browser/components/migration/IEProfileMigrator.js
browser/components/migration/tests/unit/head_migration.js
browser/components/migration/tests/unit/test_IE_bookmarks.js
browser/components/migration/tests/unit/test_IE_cookies.js
browser/components/migration/tests/unit/xpcshell.ini
testing/modules/AppInfo.jsm
--- a/browser/components/migration/IEProfileMigrator.js
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -18,16 +18,18 @@ Cu.import("resource:///modules/Migration
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
                                   "resource://gre/modules/ctypes.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
                                   "resource://gre/modules/WindowsRegistry.jsm");
 
+Cu.importGlobalProperties(["File"]);
+
 ////////////////////////////////////////////////////////////////////////////////
 //// Helpers.
 
 let CtypesHelpers = {
   _structs: {},
   _functions: {},
   _libs: {},
 
@@ -78,41 +80,45 @@ let CtypesHelpers = {
       try {
         lib.close();
       } catch (ex) {}
     }
     this._libs = {};
   },
 
   /**
-   * Converts a FILETIME struct (2 DWORDS), to a SYSTEMTIME struct.
+   * Converts a FILETIME struct (2 DWORDS), to a SYSTEMTIME struct,
+   * and then deduces the number of seconds since the epoch (which
+   * is the data we want for the cookie expiry date).
    *
    * @param aTimeHi
    *        Least significant DWORD.
    * @param aTimeLo
    *        Most significant DWORD.
-   * @return a Date object representing the converted datetime.
+   * @return the number of seconds since the epoch
    */
-  fileTimeToDate: function CH_fileTimeToDate(aTimeHi, aTimeLo) {
+  fileTimeToSecondsSinceEpoch(aTimeHi, aTimeLo) {
     let fileTime = this._structs.FILETIME();
     fileTime.dwLowDateTime = aTimeLo;
     fileTime.dwHighDateTime = aTimeHi;
     let systemTime = this._structs.SYSTEMTIME();
     let result = this._functions.FileTimeToSystemTime(fileTime.address(),
                                                       systemTime.address());
     if (result == 0)
       throw new Error(ctypes.winLastError);
 
-    return new Date(systemTime.wYear,
-                    systemTime.wMonth - 1,
-                    systemTime.wDay,
-                    systemTime.wHour,
-                    systemTime.wMinute,
-                    systemTime.wSecond,
-                    systemTime.wMilliseconds);
+    // System time is in UTC, so we use Date.UTC to get milliseconds from epoch,
+    // then divide by 1000 to get seconds, and round down:
+    return Math.floor(Date.UTC(systemTime.wYear,
+                               systemTime.wMonth - 1,
+                               systemTime.wDay,
+                               systemTime.wHour,
+                               systemTime.wMinute,
+                               systemTime.wSecond,
+                               systemTime.wMilliseconds) / 1000);
   }
 };
 
 /**
  * Checks whether an host is an IP (v4 or v6) address.
  *
  * @param aHost
  *        The host to check.
@@ -405,17 +411,17 @@ Cookies.prototype = {
         this._parseCookieBuffer(fileReader.result);
       } catch (ex) {
         Components.utils.reportError("Unable to migrate cookie: " + ex);
         success = false;
       } finally {
         aCallback(success);
       }
     }).bind(this), false);
-    fileReader.readAsText(File(aFile));
+    fileReader.readAsText(new File(aFile));
   },
 
   /**
    * Parses a cookie file buffer and returns an array of the contained cookies.
    *
    * The cookie file format is a newline-separated-values with a "*" used as
    * delimeter between multiple records.
    * Each cookie has the following fields:
@@ -439,30 +445,30 @@ Cookies.prototype = {
            expireTimeLo, expireTimeHi] = record.split("\n");
 
       // IE stores deleted cookies with a zero-length value, skip them.
       if (value.length == 0)
         continue;
 
       let hostLen = hostpath.indexOf("/");
       let host = hostpath.substr(0, hostLen);
+      let path = hostpath.substr(hostLen);
 
       // For a non-null domain, assume it's what Mozilla considers
       // a domain cookie.  See bug 222343.
       if (host.length > 0) {
         // Fist delete any possible extant matching host cookie.
         Services.cookies.remove(host, name, path, false);
         // Now make it a domain cookie.
         if (host[0] != "." && !hostIsIPAddress(host))
           host = "." + host;
       }
 
-      let path = hostpath.substr(hostLen);
-      let expireTime = CtypesHelpers.fileTimeToDate(Number(expireTimeHi),
-                                                    Number(expireTimeLo));
+      let expireTime = CtypesHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
+                                                                 Number(expireTimeLo));
       Services.cookies.add(host,
                            path,
                            name,
                            value,
                            Number(flags) & 0x1, // secure
                            false, // httpOnly
                            false, // session
                            expireTime);
--- a/browser/components/migration/tests/unit/head_migration.js
+++ b/browser/components/migration/tests/unit/head_migration.js
@@ -1,64 +1,37 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const Cu = Components.utils;
-const Cr = Components.results;
+Cu.importGlobalProperties([ "URL" ]);
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MigrationUtils",
                                   "resource:///modules/MigrationUtils.jsm");
+
 // Initialize profile.
 let gProfD = do_get_profile();
 
-// Create a fake XULAppInfo to satisfy the eventual needs of the migrators.
-let XULAppInfo = {
-  // nsIXUlAppInfo
-  get vendor() "Mozilla",
-  get name() "XPCShell",
-  get ID() "xpcshell@tests.mozilla.org",
-  get version() "1",
-  get appBuildID() "2007010101",
-  get platformVersion() "1.0",
-  get platformBuildID() "2007010101",
-
-  // nsIXUlRuntime (partial)
-  get inSafeMode() false,
-  logConsoleErrors: true,
-  get OS() "XPCShell",
-  get XPCOMABI() "noarch-spidermonkey",
-  invalidateCachesOnRestart: function () {},
-
-  // nsIWinAppHelper
-  get userCanElevate() false,
+Cu.import("resource://testing-common/AppInfo.jsm");
+updateAppInfo();
 
-  QueryInterface: function (aIID) {
-    let interfaces = [Ci.nsIXULAppInfo, Ci.nsIXULRuntime];
-    if ("nsIWinAppHelper" in Ci)
-      interfaces.push(Ci.nsIWinAppHelper);
-    if (!interfaces.some(function (v) aIID.equals(v)))
-      throw Cr.NS_ERROR_NO_INTERFACE;
-    return this;
-  }
-};
+/**
+ * Migrates the requested resource and waits for the migration to be complete.
+ */
+function promiseMigration(migrator, resourceType) {
+  // Ensure resource migration is available.
+  let availableSources = migrator.getMigrateData(null, false);
+  Assert.ok((availableSources & resourceType) > 0);
 
-const CONTRACT_ID = "@mozilla.org/xre/app-info;1";
-const CID = Components.ID("7685dac8-3637-4660-a544-928c5ec0e714}");
+  return new Promise (resolve => {
+    Services.obs.addObserver(function onMigrationEnded() {
+      Services.obs.removeObserver(onMigrationEnded, "Migration:Ended");
+      resolve();
+    }, "Migration:Ended", false);
 
-let registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
-registrar.registerFactory(CID, "XULAppInfo", CONTRACT_ID, {
-  createInstance: function (aOuter, aIID) {
-    if (aOuter != null)
-      throw Cr.NS_ERROR_NO_AGGREGATION;
-    return XULAppInfo.QueryInterface(aIID);
-  },
-  QueryInterface: XPCOMUtils.generateQI(Ci.nsIFactory)
-});
+    migrator.migrate(resourceType, null, null);
+  });
+}
--- a/browser/components/migration/tests/unit/test_IE_bookmarks.js
+++ b/browser/components/migration/tests/unit/test_IE_bookmarks.js
@@ -1,56 +1,36 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-function run_test() {
-  do_test_pending();
-
+add_task(function* () {
   let migrator = MigrationUtils.getMigrator("ie");
-
   // Sanity check for the source.
-  do_check_true(migrator.sourceExists);
-
-  // Ensure bookmarks migration is available.
-  let availableSources = migrator.getMigrateData(null, false);
-  do_check_true((availableSources & MigrationUtils.resourceTypes.BOOKMARKS) > 0);
+  Assert.ok(migrator.sourceExists);
 
   // Wait for the imported bookmarks.  Check that "From Internet Explorer"
   // folders are created in the menu and on the toolbar.
   let source = MigrationUtils.getLocalizedString("sourceNameIE");
   let label = MigrationUtils.getLocalizedString("importedBookmarksFolder", [source]);
 
   let expectedParents = [ PlacesUtils.bookmarksMenuFolderId,
                           PlacesUtils.toolbarFolderId ];
 
   PlacesUtils.bookmarks.addObserver({
-    onItemAdded: function onItemAdded(aItemId, aParentId, aIndex, aItemType,
-                                      aURI, aTitle) {
+    onItemAdded(aItemId, aParentId, aIndex, aItemType, aURI, aTitle) {
       if (aTitle == label) {
         let index = expectedParents.indexOf(aParentId);
-        do_check_neq(index, -1);
+        Assert.notEqual(index, -1);
         expectedParents.splice(index, 1);
         if (expectedParents.length == 0)
           PlacesUtils.bookmarks.removeObserver(this);
       }
     },
-    onBeginUpdateBatch: function () {},
-    onEndUpdateBatch: function () {},
-    onItemRemoved: function () {},
-    onItemChanged: function () {},
-    onItemVisited: function () {},
-    onItemMoved: function () {},
+    onBeginUpdateBatch() {},
+    onEndUpdateBatch() {},
+    onItemRemoved() {},
+    onItemChanged() {},
+    onItemVisited() {},
+    onItemMoved() {},
   }, false);
 
-  // Wait for migration.
-  Services.obs.addObserver(function onMigrationEnded() {
-    Services.obs.removeObserver(onMigrationEnded, "Migration:Ended");
-
-    // Check the bookmarks have been imported to all the expected parents.
-    do_check_eq(expectedParents.length, 0);
+  yield promiseMigration(migrator, MigrationUtils.resourceTypes.BOOKMARKS);
 
-    do_test_finished();
-  }, "Migration:Ended", false);
-
-  migrator.migrate(MigrationUtils.resourceTypes.BOOKMARKS, null,
-                   null);
-}
+  // Check the bookmarks have been imported to all the expected parents.
+  Assert.equal(expectedParents.length, 0);
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/migration/tests/unit/test_IE_cookies.js
@@ -0,0 +1,67 @@
+XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
+                                  "resource://gre/modules/ctypes.jsm");
+
+add_task(function* () {
+  let migrator = MigrationUtils.getMigrator("ie");
+  // Sanity check for the source.
+  Assert.ok(migrator.sourceExists);
+
+  const BOOL = ctypes.bool;
+  const LPCTSTR = ctypes.char16_t.ptr;
+
+  let wininet = ctypes.open("Wininet");
+  do_register_cleanup(() => {
+    try {
+      wininet.close();
+    } catch (ex) {}
+  });
+
+  /*
+  BOOL InternetSetCookie(
+    _In_  LPCTSTR lpszUrl,
+    _In_  LPCTSTR lpszCookieName,
+    _In_  LPCTSTR lpszCookieData
+  );
+  */
+  let setIECookie = wininet.declare("InternetSetCookieW",
+                                    ctypes.default_abi,
+                                    BOOL,
+                                    LPCTSTR,
+                                    LPCTSTR,
+                                    LPCTSTR);
+
+  let expiry = new Date();
+  expiry.setDate(expiry.getDate() + 7);
+  const COOKIE = {
+    host: "mycookietest.com",
+    name: "testcookie",
+    value: "testvalue",
+    expiry
+  };
+
+  // Sanity check.
+  Assert.equal(Services.cookies.countCookiesFromHost(COOKIE.host), 0,
+               "There are no cookies initially");
+
+  // Create the persistent cookie in IE.
+  let value = COOKIE.name + " = " + COOKIE.value +"; expires = " +
+              COOKIE.expiry.toUTCString();
+  let rv = setIECookie(new URL("http://" + COOKIE.host).href, null, value);
+  Assert.ok(rv, "Added a persistent IE cookie");
+
+  // Migrate cookies.
+  yield promiseMigration(migrator, MigrationUtils.resourceTypes.COOKIES);
+
+  Assert.equal(Services.cookies.countCookiesFromHost(COOKIE.host), 1,
+               "Migrated the expected number of cookies");
+
+  // Now check the cookie details.
+  let enumerator = Services.cookies.getCookiesFromHost(COOKIE.host);
+  Assert.ok(enumerator.hasMoreElements());
+  let foundCookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+
+  Assert.equal(foundCookie.name, COOKIE.name);
+  Assert.equal(foundCookie.value, COOKIE.value);
+  Assert.equal(foundCookie.host, "." + COOKIE.host);
+  Assert.equal(foundCookie.expiry, Math.floor(COOKIE.expiry / 1000));
+});
--- a/browser/components/migration/tests/unit/xpcshell.ini
+++ b/browser/components/migration/tests/unit/xpcshell.ini
@@ -1,10 +1,11 @@
 [DEFAULT]
 head = head_migration.js
 tail =
 firefox-appdir = browser
 skip-if = toolkit == 'android' || toolkit == 'gonk'
 
+[test_fx_fhr.js]
 [test_IE_bookmarks.js]
 skip-if = os != "win"
-
-[test_fx_fhr.js]
+[test_IE_cookies.js]
+skip-if = os != "win"
--- a/testing/modules/AppInfo.jsm
+++ b/testing/modules/AppInfo.jsm
@@ -21,18 +21,30 @@ let APP_INFO = {
   version: "1",
   appBuildID: "20121107",
   platformVersion: "p-ver",
   platformBuildID: "20121106",
   inSafeMode: false,
   logConsoleErrors: true,
   OS: "XPCShell",
   XPCOMABI: "noarch-spidermonkey",
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIXULAppInfo, Ci.nsIXULRuntime]),
-  invalidateCachesOnRestart: function() {},
+
+  invalidateCachesOnRestart() {},
+
+  // nsIWinAppHelper
+  get userCanElevate() false,
+
+  QueryInterface(iid) {
+    let interfaces = [ Ci.nsIXULAppInfo, Ci.nsIXULRuntime ];
+    if ("nsIWinAppHelper" in Ci)
+      interfaces.push(Ci.nsIWinAppHelper);
+    if (!interfaces.some(v => iid.equals(v)))
+      throw Cr.NS_ERROR_NO_INTERFACE;
+    return this;
+  }
 };
 
 
 /**
  * Obtain a reference to the current object used to define XULAppInfo.
  */
 this.getAppInfo = function () { return APP_INFO; }