Bug 1192036 - Import cookies from Edge. r=MattN, a=ritu
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Wed, 19 Aug 2015 16:32:54 +0100
changeset 289003 d2d041aa5782d13569d9ba53ff16b7b8ab00fa81
parent 289002 835be6d79b43f6ddf8ac420ead13beb4810d4742
child 289004 3da0d30d73b0c04d91656e3ec27c851960e6dce2
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)
reviewersMattN, ritu
bugs1192036
milestone42.0a2
Bug 1192036 - Import cookies from Edge. r=MattN, a=ritu
browser/components/migration/EdgeProfileMigrator.js
browser/components/migration/IEProfileMigrator.js
browser/components/migration/MSMigrationUtils.jsm
browser/components/migration/tests/unit/test_Edge_availability.js
--- a/browser/components/migration/EdgeProfileMigrator.js
+++ b/browser/components/migration/EdgeProfileMigrator.js
@@ -13,16 +13,17 @@ Cu.import("resource:///modules/MSMigrati
 function EdgeProfileMigrator() {
 }
 
 EdgeProfileMigrator.prototype = Object.create(MigratorPrototype);
 
 EdgeProfileMigrator.prototype.getResources = function() {
   let resources = [
     MSMigrationUtils.getBookmarksMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
+    MSMigrationUtils.getCookiesMigrator(MSMigrationUtils.MIGRATION_TYPE_EDGE),
   ];
   return resources.filter(r => r.exists);
 };
 
 EdgeProfileMigrator.prototype.classDescription = "Edge Profile Migrator";
 EdgeProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=edge";
 EdgeProfileMigrator.prototype.classID = Components.ID("{62e8834b-2d17-49f5-96ff-56344903a2ae}");
 
--- a/browser/components/migration/IEProfileMigrator.js
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -14,131 +14,19 @@ const kMainKey = "Software\\Microsoft\\I
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 Cu.import("resource:///modules/MSMigrationUtils.jsm");
 
 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: {},
-
-  /**
-   * Must be invoked once before first use of any of the provided helpers.
-   */
-  initialize: function CH_initialize() {
-    const WORD = ctypes.uint16_t;
-    const DWORD = ctypes.uint32_t;
-    const BOOL = ctypes.int;
-
-    this._structs.SYSTEMTIME = new ctypes.StructType('SYSTEMTIME', [
-      {wYear: WORD},
-      {wMonth: WORD},
-      {wDayOfWeek: WORD},
-      {wDay: WORD},
-      {wHour: WORD},
-      {wMinute: WORD},
-      {wSecond: WORD},
-      {wMilliseconds: WORD}
-    ]);
-
-    this._structs.FILETIME = new ctypes.StructType('FILETIME', [
-      {dwLowDateTime: DWORD},
-      {dwHighDateTime: DWORD}
-    ]);
-
-    try {
-      this._libs.kernel32 = ctypes.open("Kernel32");
-      this._functions.FileTimeToSystemTime =
-        this._libs.kernel32.declare("FileTimeToSystemTime",
-                                    ctypes.default_abi,
-                                    BOOL,
-                                    this._structs.FILETIME.ptr,
-                                    this._structs.SYSTEMTIME.ptr);
-    } catch (ex) {
-      this.finalize();
-    }
-  },
-
-  /**
-   * Must be invoked once after last use of any of the provided helpers.
-   */
-  finalize: function CH_finalize() {
-    this._structs = {};
-    this._functions = {};
-    for each (let lib in this._libs) {
-      try {
-        lib.close();
-      } catch (ex) {}
-    }
-    this._libs = {};
-  },
-
-  /**
-   * 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 the number of seconds since the epoch
-   */
-  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);
-
-    // 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.
- * @return whether aHost is an IP address.
- */
-function hostIsIPAddress(aHost) {
-  try {
-    Services.eTLD.getBaseDomainFromHost(aHost);
-  } catch (e if e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
-    return true;
-  } catch (e) {}
-  return false;
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 //// Resources
 
 
 function History() {
 }
 
 History.prototype = {
@@ -222,160 +110,16 @@ History.prototype = {
       handleError: function() {},
       handleCompletion: function() {
         aCallback(this._success);
       }
     });
   }
 };
 
-function Cookies() {
-}
-
-Cookies.prototype = {
-  type: MigrationUtils.resourceTypes.COOKIES,
-
-  get exists() !!this._cookiesFolder,
-
-  __cookiesFolder: null,
-  get _cookiesFolder() {
-    // Cookies are stored in txt files, in a Cookies folder whose path varies
-    // across the different OS versions.  CookD takes care of most of these
-    // cases, though, in Windows Vista/7, UAC makes a difference.
-    // If UAC is enabled, the most common destination is CookD/Low.  Though,
-    // if the user runs the application in administrator mode or disables UAC,
-    // cookies are stored in the original CookD destination.  Cause running the
-    // browser in administrator mode is unsafe and discouraged, we just care
-    // about the UAC state.
-    if (!this.__cookiesFolder) {
-      let cookiesFolder = Services.dirsvc.get("CookD", Ci.nsIFile);
-      if (cookiesFolder.exists() && cookiesFolder.isReadable()) {
-        // Check if UAC is enabled.
-        if (Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).userCanElevate) {
-          cookiesFolder.append("Low");
-        }
-        this.__cookiesFolder = cookiesFolder;
-      }
-    }
-    return this.__cookiesFolder;
-  },
-
-  migrate: function C_migrate(aCallback) {
-    CtypesHelpers.initialize();
-
-    let cookiesGenerator = (function genCookie() {
-      let success = false;
-      let entries = this._cookiesFolder.directoryEntries;
-      while (entries.hasMoreElements()) {
-        let entry = entries.getNext().QueryInterface(Ci.nsIFile);
-        // Skip eventual bogus entries.
-        if (!entry.isFile() || !/\.txt$/.test(entry.leafName))
-          continue;
-
-        this._readCookieFile(entry, function(aSuccess) {
-          // Importing even a single cookie file is considered a success.
-          if (aSuccess)
-            success = true;
-          try {
-            cookiesGenerator.next();
-          } catch (ex) {}
-        });
-
-        yield undefined;
-      }
-
-      CtypesHelpers.finalize();
-
-      aCallback(success);
-    }).apply(this);
-    cookiesGenerator.next();
-  },
-
-  _readCookieFile: function C__readCookieFile(aFile, aCallback) {
-    let fileReader = Cc["@mozilla.org/files/filereader;1"].
-                     createInstance(Ci.nsIDOMFileReader);
-    fileReader.addEventListener("loadend", (function onLoadEnd() {
-      fileReader.removeEventListener("loadend", onLoadEnd, false);
-
-      if (fileReader.readyState != fileReader.DONE) {
-        Cu.reportError("Could not read cookie contents: " + fileReader.error);
-        aCallback(false);
-        return;
-      }
-
-      let success = true;
-      try {
-        this._parseCookieBuffer(fileReader.result);
-      } catch (ex) {
-        Components.utils.reportError("Unable to migrate cookie: " + ex);
-        success = false;
-      } finally {
-        aCallback(success);
-      }
-    }).bind(this), false);
-    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:
-   *  - name
-   *  - value
-   *  - host/path
-   *  - flags
-   *  - Expiration time most significant integer
-   *  - Expiration time least significant integer
-   *  - Creation time most significant integer
-   *  - Creation time least significant integer
-   *  - Record delimiter "*"
-   *
-   * @note All the times are in FILETIME format.
-   */
-  _parseCookieBuffer: function C__parseCookieBuffer(aTextBuffer) {
-    // Note the last record is an empty string.
-    let records = [r for each (r in aTextBuffer.split("*\n")) if (r)];
-    for (let record of records) {
-      let [name, value, hostpath, flags,
-           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 expireTime = CtypesHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
-                                                                 Number(expireTimeLo));
-      Services.cookies.add(host,
-                           path,
-                           name,
-                           value,
-                           Number(flags) & 0x1, // secure
-                           false, // httpOnly
-                           false, // session
-                           expireTime);
-    }
-  }
-};
-
 function Settings() {
 }
 
 Settings.prototype = {
   type: MigrationUtils.resourceTypes.SETTINGS,
 
   get exists() true,
 
@@ -501,17 +245,17 @@ function IEProfileMigrator()
 }
 
 IEProfileMigrator.prototype = Object.create(MigratorPrototype);
 
 IEProfileMigrator.prototype.getResources = function IE_getResources() {
   let resources = [
     MSMigrationUtils.getBookmarksMigrator()
   , new History()
-  , new Cookies()
+  , MSMigrationUtils.getCookiesMigrator()
   , new Settings()
   ];
   return [r for each (r in resources) if (r.exists)];
 };
 
 Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
   get: function IE_get_sourceHomePageURL() {
     let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
--- a/browser/components/migration/MSMigrationUtils.jsm
+++ b/browser/components/migration/MSMigrationUtils.jsm
@@ -12,19 +12,166 @@ Cu.import("resource://gre/modules/XPCOMU
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource:///modules/MigrationUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
                                   "resource://gre/modules/WindowsRegistry.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
+                                  "resource://gre/modules/ctypes.jsm");
 
+const EDGE_COOKIE_PATH_OPTIONS = ["", "#!001\\", "#!002\\"];
+const EDGE_COOKIES_SUFFIX = "MicrosoftEdge\\Cookies";
 const EDGE_FAVORITES = "AC\\MicrosoftEdge\\User\\Default\\Favorites";
 
+Cu.importGlobalProperties(["File"]);
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helpers.
+
+let CtypesHelpers = {
+  _structs: {},
+  _functions: {},
+  _libs: {},
+
+  /**
+   * Must be invoked once before first use of any of the provided helpers.
+   */
+  initialize() {
+    const WORD = ctypes.uint16_t;
+    const DWORD = ctypes.uint32_t;
+    const BOOL = ctypes.int;
+
+    this._structs.SYSTEMTIME = new ctypes.StructType('SYSTEMTIME', [
+      {wYear: WORD},
+      {wMonth: WORD},
+      {wDayOfWeek: WORD},
+      {wDay: WORD},
+      {wHour: WORD},
+      {wMinute: WORD},
+      {wSecond: WORD},
+      {wMilliseconds: WORD}
+    ]);
+
+    this._structs.FILETIME = new ctypes.StructType('FILETIME', [
+      {dwLowDateTime: DWORD},
+      {dwHighDateTime: DWORD}
+    ]);
+
+    try {
+      this._libs.kernel32 = ctypes.open("Kernel32");
+      this._functions.FileTimeToSystemTime =
+        this._libs.kernel32.declare("FileTimeToSystemTime",
+                                    ctypes.default_abi,
+                                    BOOL,
+                                    this._structs.FILETIME.ptr,
+                                    this._structs.SYSTEMTIME.ptr);
+    } catch (ex) {
+      this.finalize();
+    }
+  },
+
+  /**
+   * Must be invoked once after last use of any of the provided helpers.
+   */
+  finalize() {
+    this._structs = {};
+    this._functions = {};
+    for each (let lib in this._libs) {
+      try {
+        lib.close();
+      } catch (ex) {}
+    }
+    this._libs = {};
+  },
+
+  /**
+   * 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 the number of seconds since the epoch
+   */
+  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);
+
+    // 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.
+ * @return whether aHost is an IP address.
+ */
+function hostIsIPAddress(aHost) {
+  try {
+    Services.eTLD.getBaseDomainFromHost(aHost);
+  } catch (e if e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
+    return true;
+  } catch (e) {}
+  return false;
+}
+
+let gEdgeDir;
+function getEdgeLocalDataFolder() {
+  if (gEdgeDir) {
+    return gEdgeDir.clone();
+  }
+  let packages = Services.dirsvc.get("LocalAppData", Ci.nsIFile);
+  packages.append("Packages");
+  let edgeDir = packages.clone();
+  edgeDir.append("Microsoft.MicrosoftEdge_8wekyb3d8bbwe");
+  try {
+    if (edgeDir.exists() && edgeDir.isReadable() && edgeDir.isDirectory()) {
+      gEdgeDir = edgeDir;
+      return edgeDir.clone();
+    }
+
+    // Let's try the long way:
+    let dirEntries = packages.directoryEntries;
+    while (dirEntries.hasMoreElements()) {
+      let subDir = dirEntries.getNext();
+      subDir.QueryInterface(Ci.nsIFile);
+      if (subDir.leafName.startsWith("Microsoft.MicrosoftEdge") && subDir.isReadable() &&
+          subDir.isDirectory()) {
+        gEdgeDir = subDir;
+        return subDir.clone();
+      }
+    }
+  } catch (ex) {
+    Cu.reportError("Exception trying to find the Edge favorites directory: " + ex);
+  }
+  return null;
+}
+
+
 function Bookmarks(migrationType) {
   this._migrationType = migrationType;
 }
 
 Bookmarks.prototype = {
   type: MigrationUtils.resourceTypes.BOOKMARKS,
 
   get exists() !!this._favoritesFolder,
@@ -35,42 +182,22 @@ Bookmarks.prototype = {
   get _favoritesFolder() {
     if (!this.__favoritesFolder) {
       if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE) {
         let favoritesFolder = Services.dirsvc.get("Favs", Ci.nsIFile);
         if (favoritesFolder.exists() && favoritesFolder.isReadable())
           return this.__favoritesFolder = favoritesFolder;
       }
       if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE) {
-        let appData = Services.dirsvc.get("LocalAppData", Ci.nsIFile);
-        appData.append("Packages");
-        try {
-          let edgeDir = appData.clone();
-          edgeDir.append("Microsoft.MicrosoftEdge_8wekyb3d8bbwe");
+        let edgeDir = getEdgeLocalDataFolder();
+        if (edgeDir) {
           edgeDir.appendRelativePath(EDGE_FAVORITES);
-          if (edgeDir.exists() && edgeDir.isDirectory()) {
+          if (edgeDir.exists() && edgeDir.isReadable() && edgeDir.isDirectory()) {
             return this.__favoritesFolder = edgeDir;
           }
-        } catch (ex) {} /* Ignore e.g. permissions errors here. */
-
-        // Let's try the long way:
-        try {
-          let dirEntries = appData.directoryEntries;
-          while (dirEntries.hasMoreElements()) {
-            let subDir = dirEntries.getNext();
-            subDir.QueryInterface(Ci.nsIFile);
-            if (subDir.leafName.startsWith("Microsoft.MicrosoftEdge")) {
-              subDir.appendRelativePath(EDGE_FAVORITES);
-              if (subDir.exists() && subDir.isDirectory()) {
-                return this.__favoritesFolder = subDir;
-              }
-            }
-          }
-        } catch (ex) {
-          Cu.reportError("Exception trying to find the Edge favorites directory: " + ex);
         }
       }
     }
     return this.__favoritesFolder;
   },
 
   __toolbarFolderName: null,
   get _toolbarFolderName() {
@@ -158,15 +285,201 @@ Bookmarks.prototype = {
         }
       } catch (ex) {
         Components.utils.reportError("Unable to import " + this.importedAppLabel + " favorite (" + entry.leafName + "): " + ex);
       }
     }
   })
 };
 
+function Cookies(migrationType) {
+  this._migrationType = migrationType;
+}
+
+Cookies.prototype = {
+  type: MigrationUtils.resourceTypes.COOKIES,
+
+  get exists() {
+    if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE) {
+      return !!this._cookiesFolder;
+    }
+    return !!this._cookiesFolders;
+  },
+
+  __cookiesFolder: null,
+  get _cookiesFolder() {
+    // Edge stores cookies in a number of places, and this shouldn't get called:
+    if (this._migrationType != MSMigrationUtils.MIGRATION_TYPE_IE) {
+      throw new Error("Shouldn't be looking for a single cookie folder unless we're migrating IE");
+    }
+
+    // Cookies are stored in txt files, in a Cookies folder whose path varies
+    // across the different OS versions.  CookD takes care of most of these
+    // cases, though, in Windows Vista/7, UAC makes a difference.
+    // If UAC is enabled, the most common destination is CookD/Low.  Though,
+    // if the user runs the application in administrator mode or disables UAC,
+    // cookies are stored in the original CookD destination.  Cause running the
+    // browser in administrator mode is unsafe and discouraged, we just care
+    // about the UAC state.
+    if (!this.__cookiesFolder) {
+      let cookiesFolder = Services.dirsvc.get("CookD", Ci.nsIFile);
+      if (cookiesFolder.exists() && cookiesFolder.isReadable()) {
+        // Check if UAC is enabled.
+        if (Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).userCanElevate) {
+          cookiesFolder.append("Low");
+        }
+        this.__cookiesFolder = cookiesFolder;
+      }
+    }
+    return this.__cookiesFolder;
+  },
+
+  __cookiesFolders: null,
+  get _cookiesFolders() {
+    if (this._migrationType != MSMigrationUtils.MIGRATION_TYPE_EDGE) {
+      throw new Error("Shouldn't be looking for multiple cookie folders unless we're migrating Edge");
+    }
+
+    let folders = [];
+    let edgeDir = getEdgeLocalDataFolder();
+    if (edgeDir) {
+      edgeDir.append("AC");
+      for (let path of EDGE_COOKIE_PATH_OPTIONS) {
+        let folder = edgeDir.clone();
+        let fullPath = path + EDGE_COOKIES_SUFFIX;
+        folder.appendRelativePath(fullPath);
+        if (folder.exists() && folder.isReadable() && folder.isDirectory()) {
+          folders.push(folder);
+        }
+      }
+    }
+    return this.__cookiesFolders = folders.length ? folders : null;
+  },
+
+  migrate(aCallback) {
+    CtypesHelpers.initialize();
+
+    let cookiesGenerator = (function genCookie() {
+      let success = false;
+      let folders = this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE ?
+                      this.__cookiesFolders : [this.__cookiesFolder];
+      for (let folder of folders) {
+        let entries = folder.directoryEntries;
+        while (entries.hasMoreElements()) {
+          let entry = entries.getNext().QueryInterface(Ci.nsIFile);
+          // Skip eventual bogus entries.
+          if (!entry.isFile() || !/\.txt$/.test(entry.leafName))
+            continue;
+
+          this._readCookieFile(entry, function(aSuccess) {
+            // Importing even a single cookie file is considered a success.
+            if (aSuccess)
+              success = true;
+            try {
+              cookiesGenerator.next();
+            } catch (ex) {}
+          });
+
+          yield undefined;
+        }
+      }
+
+      CtypesHelpers.finalize();
+
+      aCallback(success);
+    }).apply(this);
+    cookiesGenerator.next();
+  },
+
+  _readCookieFile(aFile, aCallback) {
+    let fileReader = Cc["@mozilla.org/files/filereader;1"].
+                     createInstance(Ci.nsIDOMFileReader);
+    let onLoadEnd = () => {
+      fileReader.removeEventListener("loadend", onLoadEnd, false);
+
+      if (fileReader.readyState != fileReader.DONE) {
+        Cu.reportError("Could not read cookie contents: " + fileReader.error);
+        aCallback(false);
+        return;
+      }
+
+      let success = true;
+      try {
+        this._parseCookieBuffer(fileReader.result);
+      } catch (ex) {
+        Components.utils.reportError("Unable to migrate cookie: " + ex);
+        success = false;
+      } finally {
+        aCallback(success);
+      }
+    };
+    fileReader.addEventListener("loadend", onLoadEnd, false);
+    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:
+   *  - name
+   *  - value
+   *  - host/path
+   *  - flags
+   *  - Expiration time most significant integer
+   *  - Expiration time least significant integer
+   *  - Creation time most significant integer
+   *  - Creation time least significant integer
+   *  - Record delimiter "*"
+   *
+   * @note All the times are in FILETIME format.
+   */
+  _parseCookieBuffer(aTextBuffer) {
+    // Note the last record is an empty string.
+    let records = [r for each (r in aTextBuffer.split("*\n")) if (r)];
+    for (let record of records) {
+      let [name, value, hostpath, flags,
+           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 expireTime = CtypesHelpers.fileTimeToSecondsSinceEpoch(Number(expireTimeHi),
+                                                                 Number(expireTimeLo));
+      Services.cookies.add(host,
+                           path,
+                           name,
+                           value,
+                           Number(flags) & 0x1, // secure
+                           false, // httpOnly
+                           false, // session
+                           expireTime);
+    }
+  }
+};
+
+
 let MSMigrationUtils = {
   MIGRATION_TYPE_IE: 1,
   MIGRATION_TYPE_EDGE: 2,
   getBookmarksMigrator(migrationType = this.MIGRATION_TYPE_IE) {
     return new Bookmarks(migrationType);
   },
+  getCookiesMigrator(migrationType = this.MIGRATION_TYPE_IE) {
+    return new Cookies(migrationType);
+  },
 };
--- a/browser/components/migration/tests/unit/test_Edge_availability.js
+++ b/browser/components/migration/tests/unit/test_Edge_availability.js
@@ -1,7 +1,16 @@
+const EDGE_AVAILABLE_MIGRATIONS = 
+  MigrationUtils.resourceTypes.COOKIES |
+  MigrationUtils.resourceTypes.BOOKMARKS;
+
 add_task(function* () {
   let migrator = MigrationUtils.getMigrator("edge");
   Cu.import("resource://gre/modules/AppConstants.jsm");
   Assert.equal(!!(migrator && migrator.sourceExists), AppConstants.isPlatformAndVersionAtLeast("win", "10"),
                "Edge should be available for migration if and only if we're on Win 10+");
+  if (migrator) {
+    let migratableData = migrator.getMigrateData(null, false);
+    Assert.equal(migratableData, EDGE_AVAILABLE_MIGRATIONS,
+                 "All the data types we expect should be available");
+  }
 });