/* 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} 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, };