summaryrefslogtreecommitdiffstats
path: root/browser/components/migration/MSMigrationUtils.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/components/migration/MSMigrationUtils.sys.mjs1017
1 files changed, 1017 insertions, 0 deletions
diff --git a/browser/components/migration/MSMigrationUtils.sys.mjs b/browser/components/migration/MSMigrationUtils.sys.mjs
new file mode 100644
index 0000000000..ce27cfb04e
--- /dev/null
+++ b/browser/components/migration/MSMigrationUtils.sys.mjs
@@ -0,0 +1,1017 @@
+/* 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/. */
+
+import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+
+import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ WindowsRegistry: "resource://gre/modules/WindowsRegistry.sys.mjs",
+});
+const { ctypes } = ChromeUtils.import("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";
+const FREE_CLOSE_FAILED = 0;
+const INTERNET_EXPLORER_EDGE_GUID = [
+ 0x3ccd5499,
+ 0x4b1087a8,
+ 0x886015a2,
+ 0x553bdd88,
+];
+const RESULT_SUCCESS = 0;
+const VAULT_ENUMERATE_ALL_ITEMS = 512;
+const WEB_CREDENTIALS_VAULT_ID = [
+ 0x4bf4c442,
+ 0x41a09b8a,
+ 0x4add80b3,
+ 0x28db4d70,
+];
+
+const wintypes = {
+ BOOL: ctypes.int,
+ DWORD: ctypes.uint32_t,
+ DWORDLONG: ctypes.uint64_t,
+ CHAR: ctypes.char,
+ PCHAR: ctypes.char.ptr,
+ LPCWSTR: ctypes.char16_t.ptr,
+ PDWORD: ctypes.uint32_t.ptr,
+ VOIDP: ctypes.voidptr_t,
+ WORD: ctypes.uint16_t,
+};
+
+// TODO: Bug 1202978 - Refactor MSMigrationUtils ctypes helpers
+function CtypesKernelHelpers() {
+ this._structs = {};
+ this._functions = {};
+ this._libs = {};
+
+ this._structs.SYSTEMTIME = new ctypes.StructType("SYSTEMTIME", [
+ { wYear: wintypes.WORD },
+ { wMonth: wintypes.WORD },
+ { wDayOfWeek: wintypes.WORD },
+ { wDay: wintypes.WORD },
+ { wHour: wintypes.WORD },
+ { wMinute: wintypes.WORD },
+ { wSecond: wintypes.WORD },
+ { wMilliseconds: wintypes.WORD },
+ ]);
+
+ this._structs.FILETIME = new ctypes.StructType("FILETIME", [
+ { dwLowDateTime: wintypes.DWORD },
+ { dwHighDateTime: wintypes.DWORD },
+ ]);
+
+ try {
+ this._libs.kernel32 = ctypes.open("Kernel32");
+
+ this._functions.FileTimeToSystemTime = this._libs.kernel32.declare(
+ "FileTimeToSystemTime",
+ ctypes.winapi_abi,
+ wintypes.BOOL,
+ this._structs.FILETIME.ptr,
+ this._structs.SYSTEMTIME.ptr
+ );
+ } catch (ex) {
+ this.finalize();
+ }
+}
+
+CtypesKernelHelpers.prototype = {
+ /**
+ * Must be invoked once after last use of any of the provided helpers.
+ */
+ finalize() {
+ this._structs = {};
+ this._functions = {};
+ for (let key in this._libs) {
+ let lib = this._libs[key];
+ 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 {number} aTimeHi
+ * Least significant DWORD.
+ * @param {number} aTimeLo
+ * Most significant DWORD.
+ * @returns {number} 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
+ );
+ },
+};
+
+function CtypesVaultHelpers() {
+ this._structs = {};
+ this._functions = {};
+
+ this._structs.GUID = new ctypes.StructType("GUID", [
+ { id: wintypes.DWORD.array(4) },
+ ]);
+
+ this._structs.VAULT_ITEM_ELEMENT = new ctypes.StructType(
+ "VAULT_ITEM_ELEMENT",
+ [
+ // not documented
+ { schemaElementId: wintypes.DWORD },
+ // not documented
+ { unknown1: wintypes.DWORD },
+ // vault type
+ { type: wintypes.DWORD },
+ // not documented
+ { unknown2: wintypes.DWORD },
+ // value of the item
+ { itemValue: wintypes.LPCWSTR },
+ // not documented
+ { unknown3: wintypes.CHAR.array(12) },
+ ]
+ );
+
+ this._structs.VAULT_ELEMENT = new ctypes.StructType("VAULT_ELEMENT", [
+ // vault item schemaId
+ { schemaId: this._structs.GUID },
+ // a pointer to the name of the browser VAULT_ITEM_ELEMENT
+ { pszCredentialFriendlyName: wintypes.LPCWSTR },
+ // a pointer to the url VAULT_ITEM_ELEMENT
+ { pResourceElement: this._structs.VAULT_ITEM_ELEMENT.ptr },
+ // a pointer to the username VAULT_ITEM_ELEMENT
+ { pIdentityElement: this._structs.VAULT_ITEM_ELEMENT.ptr },
+ // not documented
+ { pAuthenticatorElement: this._structs.VAULT_ITEM_ELEMENT.ptr },
+ // not documented
+ { pPackageSid: this._structs.VAULT_ITEM_ELEMENT.ptr },
+ // time stamp in local format
+ { lowLastModified: wintypes.DWORD },
+ { highLastModified: wintypes.DWORD },
+ // not documented
+ { flags: wintypes.DWORD },
+ // not documented
+ { dwPropertiesCount: wintypes.DWORD },
+ // not documented
+ { pPropertyElements: this._structs.VAULT_ITEM_ELEMENT.ptr },
+ ]);
+
+ try {
+ this._vaultcliLib = ctypes.open("vaultcli.dll");
+
+ this._functions.VaultOpenVault = this._vaultcliLib.declare(
+ "VaultOpenVault",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ // GUID
+ this._structs.GUID.ptr,
+ // Flags
+ wintypes.DWORD,
+ // Vault Handle
+ wintypes.VOIDP.ptr
+ );
+ this._functions.VaultEnumerateItems = this._vaultcliLib.declare(
+ "VaultEnumerateItems",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ // Vault Handle
+ wintypes.VOIDP,
+ // Flags
+ wintypes.DWORD,
+ // Items Count
+ wintypes.PDWORD,
+ // Items
+ ctypes.voidptr_t
+ );
+ this._functions.VaultCloseVault = this._vaultcliLib.declare(
+ "VaultCloseVault",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ // Vault Handle
+ wintypes.VOIDP
+ );
+ this._functions.VaultGetItem = this._vaultcliLib.declare(
+ "VaultGetItem",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ // Vault Handle
+ wintypes.VOIDP,
+ // Schema Id
+ this._structs.GUID.ptr,
+ // Resource
+ this._structs.VAULT_ITEM_ELEMENT.ptr,
+ // Identity
+ this._structs.VAULT_ITEM_ELEMENT.ptr,
+ // Package Sid
+ this._structs.VAULT_ITEM_ELEMENT.ptr,
+ // HWND Owner
+ wintypes.DWORD,
+ // Flags
+ wintypes.DWORD,
+ // Items
+ this._structs.VAULT_ELEMENT.ptr.ptr
+ );
+ this._functions.VaultFree = this._vaultcliLib.declare(
+ "VaultFree",
+ ctypes.winapi_abi,
+ wintypes.DWORD,
+ // Memory
+ this._structs.VAULT_ELEMENT.ptr
+ );
+ } catch (ex) {
+ this.finalize();
+ }
+}
+
+CtypesVaultHelpers.prototype = {
+ /**
+ * Must be invoked once after last use of any of the provided helpers.
+ */
+ finalize() {
+ this._structs = {};
+ this._functions = {};
+ try {
+ this._vaultcliLib.close();
+ } catch (ex) {}
+ this._vaultcliLib = null;
+ },
+};
+
+/**
+ * Checks whether an host is an IP (v4 or v6) address.
+ *
+ * @param {string} aHost
+ * The host to check.
+ * @returns {boolean} whether aHost is an IP address.
+ */
+function hostIsIPAddress(aHost) {
+ try {
+ Services.eTLD.getBaseDomainFromHost(aHost);
+ } catch (e) {
+ return e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS;
+ }
+ return false;
+}
+
+var 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.nextFile;
+ 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() {
+ return !!this._favoritesFolder;
+ },
+
+ get importedAppLabel() {
+ return this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE
+ ? "IE"
+ : "Edge";
+ },
+
+ __favoritesFolder: null,
+ 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()) {
+ this.__favoritesFolder = favoritesFolder;
+ }
+ } else if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE) {
+ let edgeDir = getEdgeLocalDataFolder();
+ if (edgeDir) {
+ edgeDir.appendRelativePath(EDGE_FAVORITES);
+ if (
+ edgeDir.exists() &&
+ edgeDir.isReadable() &&
+ edgeDir.isDirectory()
+ ) {
+ this.__favoritesFolder = edgeDir;
+ }
+ }
+ }
+ }
+ return this.__favoritesFolder;
+ },
+
+ __toolbarFolderName: null,
+ get _toolbarFolderName() {
+ if (!this.__toolbarFolderName) {
+ if (this._migrationType == MSMigrationUtils.MIGRATION_TYPE_IE) {
+ // Retrieve the name of IE's favorites subfolder that holds the bookmarks
+ // in the toolbar. This was previously stored in the registry and changed
+ // in IE7 to always be called "Links".
+ let folderName = lazy.WindowsRegistry.readRegKey(
+ Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Microsoft\\Internet Explorer\\Toolbar",
+ "LinksFolderName"
+ );
+ this.__toolbarFolderName = folderName || "Links";
+ } else {
+ this.__toolbarFolderName = "Links";
+ }
+ }
+ return this.__toolbarFolderName;
+ },
+
+ migrate: function B_migrate(aCallback) {
+ return (async () => {
+ // Import to the bookmarks menu.
+ let folderGuid = lazy.PlacesUtils.bookmarks.menuGuid;
+ await this._migrateFolder(this._favoritesFolder, folderGuid);
+ })().then(
+ () => aCallback(true),
+ e => {
+ Cu.reportError(e);
+ aCallback(false);
+ }
+ );
+ },
+
+ async _migrateFolder(aSourceFolder, aDestFolderGuid) {
+ let { bookmarks, favicons } = await this._getBookmarksInFolder(
+ aSourceFolder
+ );
+ if (!bookmarks.length) {
+ return;
+ }
+
+ await MigrationUtils.insertManyBookmarksWrapper(bookmarks, aDestFolderGuid);
+ MigrationUtils.insertManyFavicons(favicons);
+ },
+
+ /**
+ * Iterates through a bookmark folder to obtain whatever information from each bookmark is needed elsewhere. This function also recurses into child folders.
+ *
+ * @param {nsIFile} aSourceFolder the folder to search for bookmarks and subfolders.
+ * @returns {Promise<object>} An object with the following properties:
+ * {Object[]} bookmarks:
+ * An array of Objects with these properties:
+ * {number} type: A type mapping to one of the types in nsINavBookmarksService
+ * {string} title: The title of the bookmark
+ * {Object[]} children: An array of objects with the same structure as this one.
+ *
+ * {Object[]} favicons
+ * An array of Objects with these properties:
+ * {Uint8Array} faviconData: The binary data of a favicon
+ * {nsIURI} uri: The URI of the associated bookmark
+ */
+ async _getBookmarksInFolder(aSourceFolder) {
+ // TODO (bug 741993): the favorites order is stored in the Registry, at
+ // HCU\Software\Microsoft\Windows\CurrentVersion\Explorer\MenuOrder\Favorites
+ // for IE, and in a similar location for Edge.
+ // Until we support it, bookmarks are imported in alphabetical order.
+ let entries = aSourceFolder.directoryEntries;
+ let rv = [];
+ let favicons = [];
+ while (entries.hasMoreElements()) {
+ let entry = entries.nextFile;
+ try {
+ // Make sure that entry.path == entry.target to not follow .lnk folder
+ // shortcuts which could lead to infinite cycles.
+ // Don't use isSymlink(), since it would throw for invalid
+ // lnk files pointing to URLs or to unresolvable paths.
+ if (entry.path == entry.target && entry.isDirectory()) {
+ let isBookmarksFolder =
+ entry.leafName == this._toolbarFolderName &&
+ entry.parent.equals(this._favoritesFolder);
+ if (isBookmarksFolder && entry.isReadable()) {
+ // Import to the bookmarks toolbar.
+ let folderGuid = lazy.PlacesUtils.bookmarks.toolbarGuid;
+ await this._migrateFolder(entry, folderGuid);
+ } else if (entry.isReadable()) {
+ let {
+ bookmarks: childBookmarks,
+ favicons: childFavicons,
+ } = await this._getBookmarksInFolder(entry);
+ favicons = favicons.concat(childFavicons);
+ rv.push({
+ type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
+ title: entry.leafName,
+ children: childBookmarks,
+ });
+ }
+ } else {
+ // Strip the .url extension, to both check this is a valid link file,
+ // and get the associated title.
+ let matches = entry.leafName.match(/(.+)\.url$/i);
+ if (matches) {
+ let fileHandler = Cc[
+ "@mozilla.org/network/protocol;1?name=file"
+ ].getService(Ci.nsIFileProtocolHandler);
+ let uri = fileHandler.readURLFile(entry);
+ // Silently failing in the event that the alternative data stream for the favicon doesn't exist
+ try {
+ let faviconData = await IOUtils.read(entry.path + ":favicon");
+ favicons.push({ faviconData, uri });
+ } catch {}
+
+ rv.push({ url: uri, title: matches[1] });
+ }
+ }
+ } catch (ex) {
+ Cu.reportError(
+ "Unable to import " +
+ this.importedAppLabel +
+ " favorite (" +
+ entry.leafName +
+ "): " +
+ ex
+ );
+ }
+ }
+ return { bookmarks: rv, favicons };
+ },
+};
+
+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()) {
+ // In versions up to Windows 7, check if UAC is enabled.
+ if (
+ AppConstants.isPlatformAndVersionAtMost("win", "6.1") &&
+ 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);
+ }
+ }
+ }
+ this.__cookiesFolders = folders.length ? folders : null;
+ return this.__cookiesFolders;
+ },
+
+ migrate(aCallback) {
+ this.ctypesKernelHelpers = new CtypesKernelHelpers();
+
+ 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.nextFile;
+ // Skip eventual bogus entries.
+ if (!entry.isFile() || !/\.(cookie|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;
+ }
+ }
+
+ this.ctypesKernelHelpers.finalize();
+
+ aCallback(success);
+ }.apply(this);
+ cookiesGenerator.next();
+ },
+
+ _readCookieFile(aFile, aCallback) {
+ File.createFromNsIFile(aFile).then(
+ file => {
+ let fileReader = new FileReader();
+ let onLoadEnd = () => {
+ fileReader.removeEventListener("loadend", onLoadEnd);
+
+ 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) {
+ Cu.reportError("Unable to migrate cookie: " + ex);
+ success = false;
+ } finally {
+ aCallback(success);
+ }
+ };
+ fileReader.addEventListener("loadend", onLoadEnd);
+ fileReader.readAsText(file);
+ },
+ () => {
+ aCallback(false);
+ }
+ );
+ },
+
+ /**
+ * 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 "*"
+ *
+ * Unfortunately, "*" can also occur inside the value of the cookie, so we
+ * can't rely exclusively on it as a record separator.
+ *
+ * All the times are in FILETIME format.
+ *
+ * @param {string} aTextBuffer The text buffer to be parsed.
+ */
+ _parseCookieBuffer(aTextBuffer) {
+ // Note the last record is an empty string...
+ let records = [];
+ let lines = aTextBuffer.split("\n");
+ while (lines.length) {
+ let record = lines.splice(0, 9);
+ // ... which means this is going to be a 1-element array for that record
+ if (record.length > 1) {
+ records.push(record);
+ }
+ }
+ for (let record of records) {
+ let [name, value, hostpath, flags, expireTimeLo, expireTimeHi] = record;
+
+ // IE stores deleted cookies with a zero-length value, skip them.
+ if (!value.length) {
+ continue;
+ }
+
+ // IE sometimes has cookies created by apps that use "~~local~~/local/file/path"
+ // as the hostpath, ignore those:
+ if (hostpath.startsWith("~~local~~")) {
+ 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) {
+ // Fist delete any possible extant matching host cookie.
+ Services.cookies.remove(host, name, path, {});
+ // Now make it a domain cookie.
+ if (host[0] != "." && !hostIsIPAddress(host)) {
+ host = "." + host;
+ }
+ }
+
+ // Fallback: expire in 1h (NB: time is in seconds since epoch, so we have
+ // to divide the result of Date.now() (which is in milliseconds) by 1000).
+ let expireTime = Math.floor(Date.now() / 1000) + 3600;
+ try {
+ expireTime = this.ctypesKernelHelpers.fileTimeToSecondsSinceEpoch(
+ Number(expireTimeHi),
+ Number(expireTimeLo)
+ );
+ } catch (ex) {
+ Cu.reportError("Failed to get expiry time for cookie for " + host);
+ }
+
+ Services.cookies.add(
+ host,
+ path,
+ name,
+ value,
+ Number(flags) & 0x1, // secure
+ false, // httpOnly
+ false, // session
+ expireTime,
+ {},
+ Ci.nsICookie.SAMESITE_NONE,
+ Ci.nsICookie.SCHEME_UNSET
+ );
+ }
+ },
+};
+
+function getTypedURLs(registryKeyPath) {
+ // The list of typed URLs is a sort of annotation stored in the registry.
+ // The number of entries stored is not UI-configurable, but has changed
+ // between different Windows versions. We just keep reading up to the first
+ // non-existing entry to support different limits / states of the registry.
+ let typedURLs = new Map();
+ let typedURLKey = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
+ Ci.nsIWindowsRegKey
+ );
+ let typedURLTimeKey = Cc[
+ "@mozilla.org/windows-registry-key;1"
+ ].createInstance(Ci.nsIWindowsRegKey);
+ let cTypes = new CtypesKernelHelpers();
+ try {
+ try {
+ typedURLKey.open(
+ Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ registryKeyPath + "\\TypedURLs",
+ Ci.nsIWindowsRegKey.ACCESS_READ
+ );
+ } catch (ex) {
+ // Ignore errors opening this registry key - if it doesn't work, there's
+ // no way we can get useful info here.
+ return typedURLs;
+ }
+ try {
+ typedURLTimeKey.open(
+ Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ registryKeyPath + "\\TypedURLsTime",
+ Ci.nsIWindowsRegKey.ACCESS_READ
+ );
+ } catch (ex) {
+ typedURLTimeKey = null;
+ }
+ let entryName;
+ for (
+ let entry = 1;
+ typedURLKey.hasValue((entryName = "url" + entry));
+ entry++
+ ) {
+ let url = typedURLKey.readStringValue(entryName);
+ // If we can't get a date for whatever reason, default to 6 months ago
+ let timeTyped = Date.now() - 31536000 / 2;
+ if (typedURLTimeKey && typedURLTimeKey.hasValue(entryName)) {
+ let urlTime = "";
+ try {
+ urlTime = typedURLTimeKey.readBinaryValue(entryName);
+ } catch (ex) {
+ Cu.reportError("Couldn't read url time for " + entryName);
+ }
+ if (urlTime.length == 8) {
+ let urlTimeHex = [];
+ for (let i = 0; i < 8; i++) {
+ let c = urlTime.charCodeAt(i).toString(16);
+ if (c.length == 1) {
+ c = "0" + c;
+ }
+ urlTimeHex.unshift(c);
+ }
+ try {
+ let hi = parseInt(urlTimeHex.slice(0, 4).join(""), 16);
+ let lo = parseInt(urlTimeHex.slice(4, 8).join(""), 16);
+ // Convert to seconds since epoch:
+ let secondsSinceEpoch = cTypes.fileTimeToSecondsSinceEpoch(hi, lo);
+
+ // If the date is very far in the past, just use the default
+ if (secondsSinceEpoch > Date.now() / 1000000) {
+ // Callers expect PRTime, which is microseconds since epoch:
+ timeTyped = secondsSinceEpoch * 1000;
+ }
+ } catch (ex) {
+ // Ignore conversion exceptions. Callers will have to deal
+ // with the fallback value.
+ }
+ }
+ }
+ typedURLs.set(url, timeTyped * 1000);
+ }
+ } catch (ex) {
+ Cu.reportError("Error reading typed URL history: " + ex);
+ } finally {
+ if (typedURLKey) {
+ typedURLKey.close();
+ }
+ if (typedURLTimeKey) {
+ typedURLTimeKey.close();
+ }
+ cTypes.finalize();
+ }
+ return typedURLs;
+}
+
+// Migrator for form passwords on Windows 8 and higher.
+function WindowsVaultFormPasswords() {}
+
+WindowsVaultFormPasswords.prototype = {
+ type: MigrationUtils.resourceTypes.PASSWORDS,
+
+ get exists() {
+ // work only on windows 8+
+ if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
+ // check if there are passwords available for migration.
+ return this.migrate(() => {}, true);
+ }
+ return false;
+ },
+
+ /**
+ * If aOnlyCheckExists is false, import the form passwords on Windows 8 and higher from the vault
+ * and then call the aCallback.
+ * Otherwise, check if there are passwords in the vault.
+ *
+ * @param {Function} aCallback - a callback called when the migration is done.
+ * @param {boolean} [aOnlyCheckExists=false] - if aOnlyCheckExists is true, just check if there are some
+ * passwords to migrate. Import the passwords from the vault and call aCallback otherwise.
+ * @returns {boolean} true if there are passwords in the vault and aOnlyCheckExists is set to true,
+ * false if there is no password in the vault and aOnlyCheckExists is set to true, undefined if
+ * aOnlyCheckExists is set to false.
+ */
+ async migrate(aCallback, aOnlyCheckExists = false) {
+ // check if the vault item is an IE/Edge one
+ function _isIEOrEdgePassword(id) {
+ return (
+ id[0] == INTERNET_EXPLORER_EDGE_GUID[0] &&
+ id[1] == INTERNET_EXPLORER_EDGE_GUID[1] &&
+ id[2] == INTERNET_EXPLORER_EDGE_GUID[2] &&
+ id[3] == INTERNET_EXPLORER_EDGE_GUID[3]
+ );
+ }
+
+ let ctypesVaultHelpers = new CtypesVaultHelpers();
+ let ctypesKernelHelpers = new CtypesKernelHelpers();
+ let migrationSucceeded = true;
+ let successfulVaultOpen = false;
+ let error, vault;
+ try {
+ // web credentials vault id
+ let vaultGuid = new ctypesVaultHelpers._structs.GUID(
+ WEB_CREDENTIALS_VAULT_ID
+ );
+ error = new wintypes.DWORD();
+ // web credentials vault
+ vault = new wintypes.VOIDP();
+ // open the current vault using the vaultGuid
+ error = ctypesVaultHelpers._functions.VaultOpenVault(
+ vaultGuid.address(),
+ 0,
+ vault.address()
+ );
+ if (error != RESULT_SUCCESS) {
+ throw new Error("Unable to open Vault: " + error);
+ }
+ successfulVaultOpen = true;
+
+ let item = new ctypesVaultHelpers._structs.VAULT_ELEMENT.ptr();
+ let itemCount = new wintypes.DWORD();
+ // enumerate all the available items. This api is going to return a table of all the
+ // available items and item is going to point to the first element of this table.
+ error = ctypesVaultHelpers._functions.VaultEnumerateItems(
+ vault,
+ VAULT_ENUMERATE_ALL_ITEMS,
+ itemCount.address(),
+ item.address()
+ );
+ if (error != RESULT_SUCCESS) {
+ throw new Error("Unable to enumerate Vault items: " + error);
+ }
+
+ let logins = [];
+ for (let j = 0; j < itemCount.value; j++) {
+ try {
+ // if it's not an ie/edge password, skip it
+ if (!_isIEOrEdgePassword(item.contents.schemaId.id)) {
+ continue;
+ }
+ let url = item.contents.pResourceElement.contents.itemValue.readString();
+ let realURL;
+ try {
+ realURL = Services.io.newURI(url);
+ } catch (ex) {
+ /* leave realURL as null */
+ }
+ if (!realURL || !["http", "https", "ftp"].includes(realURL.scheme)) {
+ // Ignore items for non-URLs or URLs that aren't HTTP(S)/FTP
+ continue;
+ }
+
+ // if aOnlyCheckExists is set to true, the purpose of the call is to return true if there is at
+ // least a password which is true in this case because a password was by now already found
+ if (aOnlyCheckExists) {
+ return true;
+ }
+ let username = item.contents.pIdentityElement.contents.itemValue.readString();
+ // the current login credential object
+ let credential = new ctypesVaultHelpers._structs.VAULT_ELEMENT.ptr();
+ error = ctypesVaultHelpers._functions.VaultGetItem(
+ vault,
+ item.contents.schemaId.address(),
+ item.contents.pResourceElement,
+ item.contents.pIdentityElement,
+ null,
+ 0,
+ 0,
+ credential.address()
+ );
+ if (error != RESULT_SUCCESS) {
+ throw new Error("Unable to get item: " + error);
+ }
+
+ let password = credential.contents.pAuthenticatorElement.contents.itemValue.readString();
+ let creation = Date.now();
+ try {
+ // login manager wants time in milliseconds since epoch, so convert
+ // to seconds since epoch and multiply to get milliseconds:
+ creation =
+ ctypesKernelHelpers.fileTimeToSecondsSinceEpoch(
+ item.contents.highLastModified,
+ item.contents.lowLastModified
+ ) * 1000;
+ } catch (ex) {
+ // Ignore exceptions in the dates and just create the login for right now.
+ }
+ // create a new login
+ logins.push({
+ username,
+ password,
+ origin: realURL.prePath,
+ timeCreated: creation,
+ });
+
+ // close current item
+ error = ctypesVaultHelpers._functions.VaultFree(credential);
+ if (error == FREE_CLOSE_FAILED) {
+ throw new Error("Unable to free item: " + error);
+ }
+ } catch (e) {
+ migrationSucceeded = false;
+ Cu.reportError(e);
+ } finally {
+ // move to next item in the table returned by VaultEnumerateItems
+ item = item.increment();
+ }
+ }
+
+ if (logins.length) {
+ await MigrationUtils.insertLoginsWrapper(logins);
+ }
+ } catch (e) {
+ Cu.reportError(e);
+ migrationSucceeded = false;
+ } finally {
+ if (successfulVaultOpen) {
+ // close current vault
+ error = ctypesVaultHelpers._functions.VaultCloseVault(vault);
+ if (error == FREE_CLOSE_FAILED) {
+ Cu.reportError("Unable to close vault: " + error);
+ }
+ }
+ ctypesKernelHelpers.finalize();
+ ctypesVaultHelpers.finalize();
+ aCallback(migrationSucceeded);
+ }
+ if (aOnlyCheckExists) {
+ return false;
+ }
+ return undefined;
+ },
+};
+
+export var MSMigrationUtils = {
+ MIGRATION_TYPE_IE: 1,
+ MIGRATION_TYPE_EDGE: 2,
+ CtypesKernelHelpers,
+ getBookmarksMigrator(migrationType = this.MIGRATION_TYPE_IE) {
+ return new Bookmarks(migrationType);
+ },
+ getCookiesMigrator(migrationType = this.MIGRATION_TYPE_IE) {
+ return new Cookies(migrationType);
+ },
+ getWindowsVaultFormPasswordsMigrator() {
+ return new WindowsVaultFormPasswords();
+ },
+ getTypedURLs,
+ getEdgeLocalDataFolder,
+};