fune/browser/components/migration/MSMigrationUtils.sys.mjs
Stanca Serban a5a1a182a7 Backed out 5 changesets (bug 1552815) for causing bp-nu bustages in nsFaviconService.cpp. CLOSED TREE
Backed out changeset 4be83e948d32 (bug 1552815)
Backed out changeset 0b2d40719586 (bug 1552815)
Backed out changeset c17e4da85514 (bug 1552815)
Backed out changeset c66812747d09 (bug 1552815)
Backed out changeset 62b3d85f4ca3 (bug 1552815)
2024-05-01 09:58:57 +03:00

749 lines
24 KiB
JavaScript

/* 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 { ctypes } from "resource://gre/modules/ctypes.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 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;
},
};
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) {
console.error(
"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 => {
console.error(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) {
console.error(
"Unable to import ",
this.importedAppLabel,
" favorite (",
entry.leafName,
"): ",
ex
);
}
}
return { bookmarks: rv, favicons };
},
};
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) {
console.error("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) {
console.error("Error reading typed URL history: ", ex);
} finally {
if (typedURLKey) {
typedURLKey.close();
}
if (typedURLTimeKey) {
typedURLTimeKey.close();
}
cTypes.finalize();
}
return typedURLs;
}
// Migrator for form passwords
function WindowsVaultFormPasswords() {}
WindowsVaultFormPasswords.prototype = {
type: MigrationUtils.resourceTypes.PASSWORDS,
get exists() {
// check if there are passwords available for migration.
return this.migrate(() => {}, true);
},
/**
* If aOnlyCheckExists is false, import the form passwords 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;
console.error(e);
} finally {
// move to next item in the table returned by VaultEnumerateItems
item = item.increment();
}
}
if (logins.length) {
await MigrationUtils.insertLoginsWrapper(logins);
}
} catch (e) {
console.error(e);
migrationSucceeded = false;
} finally {
if (successfulVaultOpen) {
// close current vault
error = ctypesVaultHelpers._functions.VaultCloseVault(vault);
if (error == FREE_CLOSE_FAILED) {
console.error("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);
},
getWindowsVaultFormPasswordsMigrator() {
return new WindowsVaultFormPasswords();
},
getTypedURLs,
getEdgeLocalDataFolder,
};