diff options
Diffstat (limited to 'browser/components/migration/MSMigrationUtils.jsm')
-rw-r--r-- | browser/components/migration/MSMigrationUtils.jsm | 889 |
1 files changed, 889 insertions, 0 deletions
diff --git a/browser/components/migration/MSMigrationUtils.jsm b/browser/components/migration/MSMigrationUtils.jsm new file mode 100644 index 000000000..1e0250b06 --- /dev/null +++ b/browser/components/migration/MSMigrationUtils.jsm @@ -0,0 +1,889 @@ +/* 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/. */ + +"use strict"; + +this.EXPORTED_SYMBOLS = ["MSMigrationUtils"]; + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; + +Cu.import("resource://gre/modules/AppConstants.jsm"); +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.importGlobalProperties(["FileReader"]); + +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"; +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]; + +Cu.importGlobalProperties(["File"]); + +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.default_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 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); + } +}; + +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 aHost + * The host to check. + * @return 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.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() { + 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 = 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 Task.spawn(function* () { + // Import to the bookmarks menu. + let folderGuid = PlacesUtils.bookmarks.menuGuid; + if (!MigrationUtils.isStartupMigration) { + folderGuid = + yield MigrationUtils.createImportedBookmarksFolder(this.importedAppLabel, folderGuid); + } + yield this._migrateFolder(this._favoritesFolder, folderGuid); + }.bind(this)).then(() => aCallback(true), + e => { Cu.reportError(e); aCallback(false) }); + }, + + _migrateFolder: Task.async(function* (aSourceFolder, aDestFolderGuid) { + // 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 succeeded = true; + while (entries.hasMoreElements()) { + let entry = entries.getNext().QueryInterface(Ci.nsIFile); + 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 folderGuid; + if (entry.leafName == this._toolbarFolderName && + entry.parent.equals(this._favoritesFolder)) { + // Import to the bookmarks toolbar. + folderGuid = PlacesUtils.bookmarks.toolbarGuid; + if (!MigrationUtils.isStartupMigration) { + folderGuid = + yield MigrationUtils.createImportedBookmarksFolder(this.importedAppLabel, folderGuid); + } + } + else { + // Import to a new folder. + folderGuid = (yield MigrationUtils.insertBookmarkWrapper({ + type: PlacesUtils.bookmarks.TYPE_FOLDER, + parentGuid: aDestFolderGuid, + title: entry.leafName + })).guid; + } + + if (entry.isReadable()) { + // Recursively import the folder. + yield this._migrateFolder(entry, folderGuid); + } + } + 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); + let title = matches[1]; + + yield MigrationUtils.insertBookmarkWrapper({ + parentGuid: aDestFolderGuid, url: uri, title + }); + } + } + } catch (ex) { + Components.utils.reportError("Unable to import " + this.importedAppLabel + " favorite (" + entry.leafName + "): " + ex); + succeeded = false; + } + } + if (!succeeded) { + throw new Error("Failed to import all bookmarks correctly."); + } + }), + +}; + +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); + } + } + } + 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.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; + } + } + + this.ctypesKernelHelpers.finalize(); + + aCallback(success); + }).apply(this); + cookiesGenerator.next(); + }, + + _readCookieFile(aFile, aCallback) { + let fileReader = new FileReader(); + 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(File.createFromNsIFile(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 "*" + * + * Unfortunately, "*" can also occur inside the value of the cookie, so we + * can't rely exclusively on it as a record separator. + * + * @note All the times are in FILETIME format. + */ + _parseCookieBuffer(aTextBuffer) { + // Note the last record is an empty string... + let records = []; + let lines = aTextBuffer.split("\n"); + while (lines.length > 0) { + 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 == 0) + 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 > 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; + } + + // 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, + {}); + } + } +}; + +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 { + typedURLKey.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER, + registryKeyPath + "\\TypedURLs", + Ci.nsIWindowsRegKey.ACCESS_READ); + 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); + let timeTyped = 0; + 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: + timeTyped = cTypes.fileTimeToSecondsSinceEpoch(hi, lo); + // Callers expect PRTime, which is microseconds since epoch: + timeTyped *= 1000 * 1000; + } catch (ex) { + // Ignore conversion exceptions. Callers will have to deal + // with the fallback value (0). + } + } + } + typedURLs.set(url, timeTyped); + } + } 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. + * @return 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. + */ + 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); + } + 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, null, null); + } catch (ex) { /* leave realURL as null */ } + if (!realURL || ["http", "https", "ftp"].indexOf(realURL.scheme) == -1) { + // 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 + let login = { + username, password, + hostname: realURL.prePath, + timeCreated: creation, + }; + MigrationUtils.insertLoginWrapper(login); + + // 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(); + } + } + } 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; + } +}; + +var MSMigrationUtils = { + MIGRATION_TYPE_IE: 1, + MIGRATION_TYPE_EDGE: 2, + CtypesKernelHelpers: 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, +}; |