Files
tubestation/browser/components/migration/ChromeProfileMigrator.sys.mjs
Sandor Molnar c46a05c875 Backed out 9 changesets (bug 1800917, bug 718280) for causing xpc failures in browser/components/migration/tests/unit/test_Edge_db_migration.js CLOSED TREE
Backed out changeset d55b95d9d14f (bug 1800917)
Backed out changeset 8427f1e65df4 (bug 1800917)
Backed out changeset 2d0f77d324f2 (bug 718280)
Backed out changeset eba58fd1ed1c (bug 718280)
Backed out changeset 33027a0f9ce9 (bug 718280)
Backed out changeset 237be1f62d01 (bug 718280)
Backed out changeset 007afe158ae5 (bug 718280)
Backed out changeset f1dea7abdfb7 (bug 718280)
Backed out changeset 47e2cccd33c7 (bug 718280)
2022-12-13 00:50:16 +02:00

865 lines
25 KiB
JavaScript

/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
* vim: sw=2 ts=2 sts=2 et */
/* 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/. */
const AUTH_TYPE = {
SCHEME_HTML: 0,
SCHEME_BASIC: 1,
SCHEME_DIGEST: 2,
};
import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { MigrationUtils } from "resource:///modules/MigrationUtils.sys.mjs";
import { MigratorBase } from "resource:///modules/MigratorBase.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
ChromeMigrationUtils: "resource:///modules/ChromeMigrationUtils.sys.mjs",
PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
Qihoo360seMigrationUtils: "resource:///modules/360seMigrationUtils.sys.mjs",
});
XPCOMUtils.defineLazyModuleGetters(lazy, {
NetUtil: "resource://gre/modules/NetUtil.jsm",
});
/**
* Converts an array of chrome bookmark objects into one our own places code
* understands.
*
* @param {object[]} items Chrome Bookmark items to be inserted on this parent
* @param {Function} errorAccumulator function that gets called with any errors
* thrown so we don't drop them on the floor.
* @returns {object[]}
*/
function convertBookmarks(items, errorAccumulator) {
let itemsToInsert = [];
for (let item of items) {
try {
if (item.type == "url") {
if (item.url.trim().startsWith("chrome:")) {
// Skip invalid internal URIs. Creating an actual URI always reports
// messages to the console because Gecko has its own concept of how
// chrome:// URIs should be formed, so we avoid doing that.
continue;
}
if (item.url.trim().startsWith("edge:")) {
// Don't import internal Microsoft Edge URIs as they won't resolve within Firefox.
continue;
}
itemsToInsert.push({ url: item.url, title: item.name });
} else if (item.type == "folder") {
let folderItem = {
type: lazy.PlacesUtils.bookmarks.TYPE_FOLDER,
title: item.name,
};
folderItem.children = convertBookmarks(item.children, errorAccumulator);
itemsToInsert.push(folderItem);
}
} catch (ex) {
Cu.reportError(ex);
errorAccumulator(ex);
}
}
return itemsToInsert;
}
/**
* Chrome profile migrator. This can also be used as a parent class for
* migrators for browsers that are variants of Chrome.
*/
export class ChromeProfileMigrator extends MigratorBase {
get classDescription() {
return "Chrome Profile Migrator";
}
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=chrome";
}
get classID() {
return Components.ID("{4cec1de4-1671-4fc3-a53e-6c539dc77a26}");
}
get _chromeUserDataPathSuffix() {
return "Chrome";
}
_keychainServiceName = "Chrome Safe Storage";
_keychainAccountName = "Chrome";
async _getChromeUserDataPathIfExists() {
if (this._chromeUserDataPath) {
return this._chromeUserDataPath;
}
let path = lazy.ChromeMigrationUtils.getDataPath(
this._chromeUserDataPathSuffix
);
let exists = await IOUtils.exists(path);
if (exists) {
this._chromeUserDataPath = path;
} else {
this._chromeUserDataPath = null;
}
return this._chromeUserDataPath;
}
async getResources(aProfile) {
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
if (chromeUserDataPath) {
let profileFolder = chromeUserDataPath;
if (aProfile) {
profileFolder = PathUtils.join(chromeUserDataPath, aProfile.id);
}
if (await IOUtils.exists(profileFolder)) {
let possibleResourcePromises = [
GetBookmarksResource(profileFolder, this.getBrowserKey()),
GetHistoryResource(profileFolder),
GetCookiesResource(profileFolder),
];
if (lazy.ChromeMigrationUtils.supportsLoginsForPlatform) {
possibleResourcePromises.push(
this._GetPasswordsResource(profileFolder)
);
}
let possibleResources = await Promise.all(possibleResourcePromises);
return possibleResources.filter(r => r != null);
}
}
return [];
}
async getLastUsedDate() {
let sourceProfiles = await this.getSourceProfiles();
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
if (!chromeUserDataPath) {
return new Date(0);
}
let datePromises = sourceProfiles.map(async profile => {
let basePath = PathUtils.join(chromeUserDataPath, profile.id);
let fileDatePromises = ["Bookmarks", "History", "Cookies"].map(
async leafName => {
let path = PathUtils.join(basePath, leafName);
let info = await IOUtils.stat(path).catch(() => null);
return info ? info.lastModificationDate : 0;
}
);
let dates = await Promise.all(fileDatePromises);
return Math.max(...dates);
});
let datesOuter = await Promise.all(datePromises);
datesOuter.push(0);
return new Date(Math.max(...datesOuter));
}
async getSourceProfiles() {
if ("__sourceProfiles" in this) {
return this.__sourceProfiles;
}
let chromeUserDataPath = await this._getChromeUserDataPathIfExists();
if (!chromeUserDataPath) {
return [];
}
let localState;
let profiles = [];
try {
localState = await lazy.ChromeMigrationUtils.getLocalState(
this._chromeUserDataPathSuffix
);
let info_cache = localState.profile.info_cache;
for (let profileFolderName in info_cache) {
profiles.push({
id: profileFolderName,
name: info_cache[profileFolderName].name || profileFolderName,
});
}
} catch (e) {
// Avoid reporting NotFoundErrors from trying to get local state.
if (localState || e.name != "NotFoundError") {
Cu.reportError("Error detecting Chrome profiles: " + e);
}
// If we weren't able to detect any profiles above, fallback to the Default profile.
let defaultProfilePath = PathUtils.join(chromeUserDataPath, "Default");
if (await IOUtils.exists(defaultProfilePath)) {
profiles = [
{
id: "Default",
name: "Default",
},
];
}
}
let profileResources = await Promise.all(
profiles.map(async profile => ({
profile,
resources: await this.getResources(profile),
}))
);
// Only list profiles from which any data can be imported
this.__sourceProfiles = profileResources
.filter(({ resources }) => {
return resources && !!resources.length;
}, this)
.map(({ profile }) => profile);
return this.__sourceProfiles;
}
async _GetPasswordsResource(aProfileFolder) {
let loginPath = PathUtils.join(aProfileFolder, "Login Data");
if (!(await IOUtils.exists(loginPath))) {
return null;
}
let {
_chromeUserDataPathSuffix,
_keychainServiceName,
_keychainAccountName,
_keychainMockPassphrase = null,
} = this;
return {
type: MigrationUtils.resourceTypes.PASSWORDS,
async migrate(aCallback) {
let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
loginPath,
"Chrome passwords",
`SELECT origin_url, action_url, username_element, username_value,
password_element, password_value, signon_realm, scheme, date_created,
times_used FROM logins WHERE blacklisted_by_user = 0`
).catch(ex => {
Cu.reportError(ex);
aCallback(false);
});
// If the promise was rejected we will have already called aCallback,
// so we can just return here.
if (!rows) {
return;
}
// If there are no relevant rows, return before initializing crypto and
// thus prompting for Keychain access on macOS.
if (!rows.length) {
aCallback(true);
return;
}
let crypto;
try {
if (AppConstants.platform == "win") {
let { ChromeWindowsLoginCrypto } = ChromeUtils.importESModule(
"resource:///modules/ChromeWindowsLoginCrypto.sys.mjs"
);
crypto = new ChromeWindowsLoginCrypto(_chromeUserDataPathSuffix);
} else if (AppConstants.platform == "macosx") {
let { ChromeMacOSLoginCrypto } = ChromeUtils.importESModule(
"resource:///modules/ChromeMacOSLoginCrypto.sys.mjs"
);
crypto = new ChromeMacOSLoginCrypto(
_keychainServiceName,
_keychainAccountName,
_keychainMockPassphrase
);
} else {
aCallback(false);
return;
}
} catch (ex) {
// Handle the user canceling Keychain access or other OSCrypto errors.
Cu.reportError(ex);
aCallback(false);
return;
}
let logins = [];
let fallbackCreationDate = new Date();
for (let row of rows) {
try {
let origin_url = lazy.NetUtil.newURI(
row.getResultByName("origin_url")
);
// Ignore entries for non-http(s)/ftp URLs because we likely can't
// use them anyway.
const kValidSchemes = new Set(["https", "http", "ftp"]);
if (!kValidSchemes.has(origin_url.scheme)) {
continue;
}
let loginInfo = {
username: row.getResultByName("username_value"),
password: await crypto.decryptData(
row.getResultByName("password_value"),
null
),
origin: origin_url.prePath,
formActionOrigin: null,
httpRealm: null,
usernameElement: row.getResultByName("username_element"),
passwordElement: row.getResultByName("password_element"),
timeCreated: lazy.ChromeMigrationUtils.chromeTimeToDate(
row.getResultByName("date_created") + 0,
fallbackCreationDate
).getTime(),
timesUsed: row.getResultByName("times_used") + 0,
};
switch (row.getResultByName("scheme")) {
case AUTH_TYPE.SCHEME_HTML:
let action_url = row.getResultByName("action_url");
if (!action_url) {
// If there is no action_url, store the wildcard "" value.
// See the `formActionOrigin` IDL comments.
loginInfo.formActionOrigin = "";
break;
}
let action_uri = lazy.NetUtil.newURI(action_url);
if (!kValidSchemes.has(action_uri.scheme)) {
continue; // This continues the outer for loop.
}
loginInfo.formActionOrigin = action_uri.prePath;
break;
case AUTH_TYPE.SCHEME_BASIC:
case AUTH_TYPE.SCHEME_DIGEST:
// signon_realm format is URIrealm, so we need remove URI
loginInfo.httpRealm = row
.getResultByName("signon_realm")
.substring(loginInfo.origin.length + 1);
break;
default:
throw new Error(
"Login data scheme type not supported: " +
row.getResultByName("scheme")
);
}
logins.push(loginInfo);
} catch (e) {
Cu.reportError(e);
}
}
try {
if (logins.length) {
await MigrationUtils.insertLoginsWrapper(logins);
}
} catch (e) {
Cu.reportError(e);
}
if (crypto.finalize) {
crypto.finalize();
}
aCallback(true);
},
};
}
}
async function GetBookmarksResource(aProfileFolder, aBrowserKey) {
let bookmarksPath = PathUtils.join(aProfileFolder, "Bookmarks");
if (aBrowserKey === "chromium-360se") {
let localState = {};
try {
localState = await lazy.ChromeMigrationUtils.getLocalState("360 SE");
} catch (ex) {
Cu.reportError(ex);
}
let alternativeBookmarks = await lazy.Qihoo360seMigrationUtils.getAlternativeBookmarks(
{ bookmarksPath, localState }
);
if (alternativeBookmarks.resource) {
return alternativeBookmarks.resource;
}
bookmarksPath = alternativeBookmarks.path;
}
if (!(await IOUtils.exists(bookmarksPath))) {
return null;
}
return {
type: MigrationUtils.resourceTypes.BOOKMARKS,
migrate(aCallback) {
return (async function() {
let gotErrors = false;
let errorGatherer = function() {
gotErrors = true;
};
// Parse Chrome bookmark file that is JSON format
let bookmarkJSON = await IOUtils.readJSON(bookmarksPath);
let roots = bookmarkJSON.roots;
// Importing bookmark bar items
if (roots.bookmark_bar.children && roots.bookmark_bar.children.length) {
// Toolbar
let parentGuid = lazy.PlacesUtils.bookmarks.toolbarGuid;
let bookmarks = convertBookmarks(
roots.bookmark_bar.children,
errorGatherer
);
await MigrationUtils.insertManyBookmarksWrapper(
bookmarks,
parentGuid
);
}
// Importing Other Bookmarks items
if (roots.other.children && roots.other.children.length) {
// Other Bookmarks
let parentGuid = lazy.PlacesUtils.bookmarks.unfiledGuid;
let bookmarks = convertBookmarks(roots.other.children, errorGatherer);
await MigrationUtils.insertManyBookmarksWrapper(
bookmarks,
parentGuid
);
}
if (gotErrors) {
throw new Error("The migration included errors.");
}
})().then(
() => aCallback(true),
() => aCallback(false)
);
},
};
}
async function GetHistoryResource(aProfileFolder) {
let historyPath = PathUtils.join(aProfileFolder, "History");
if (!(await IOUtils.exists(historyPath))) {
return null;
}
return {
type: MigrationUtils.resourceTypes.HISTORY,
migrate(aCallback) {
(async function() {
const MAX_AGE_IN_DAYS = Services.prefs.getIntPref(
"browser.migrate.chrome.history.maxAgeInDays"
);
const LIMIT = Services.prefs.getIntPref(
"browser.migrate.chrome.history.limit"
);
let query =
"SELECT url, title, last_visit_time, typed_count FROM urls WHERE hidden = 0";
if (MAX_AGE_IN_DAYS) {
let maxAge = lazy.ChromeMigrationUtils.dateToChromeTime(
Date.now() - MAX_AGE_IN_DAYS * 24 * 60 * 60 * 1000
);
query += " AND last_visit_time > " + maxAge;
}
if (LIMIT) {
query += " ORDER BY last_visit_time DESC LIMIT " + LIMIT;
}
let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
historyPath,
"Chrome history",
query
);
let pageInfos = [];
let fallbackVisitDate = new Date();
for (let row of rows) {
try {
// if having typed_count, we changes transition type to typed.
let transition = lazy.PlacesUtils.history.TRANSITIONS.LINK;
if (row.getResultByName("typed_count") > 0) {
transition = lazy.PlacesUtils.history.TRANSITIONS.TYPED;
}
pageInfos.push({
title: row.getResultByName("title"),
url: new URL(row.getResultByName("url")),
visits: [
{
transition,
date: lazy.ChromeMigrationUtils.chromeTimeToDate(
row.getResultByName("last_visit_time"),
fallbackVisitDate
),
},
],
});
} catch (e) {
Cu.reportError(e);
}
}
if (pageInfos.length) {
await MigrationUtils.insertVisitsWrapper(pageInfos);
}
})().then(
() => {
aCallback(true);
},
ex => {
Cu.reportError(ex);
aCallback(false);
}
);
},
};
}
async function GetCookiesResource(aProfileFolder) {
let cookiesPath = PathUtils.join(aProfileFolder, "Cookies");
if (!(await IOUtils.exists(cookiesPath))) {
return null;
}
return {
type: MigrationUtils.resourceTypes.COOKIES,
async migrate(aCallback) {
// Get columns names and set is_sceure, is_httponly fields accordingly.
let columns = await MigrationUtils.getRowsFromDBWithoutLocks(
cookiesPath,
"Chrome cookies",
`PRAGMA table_info(cookies)`
).catch(ex => {
Cu.reportError(ex);
aCallback(false);
});
// If the promise was rejected we will have already called aCallback,
// so we can just return here.
if (!columns) {
return;
}
columns = columns.map(c => c.getResultByName("name"));
let isHttponly = columns.includes("is_httponly")
? "is_httponly"
: "httponly";
let isSecure = columns.includes("is_secure") ? "is_secure" : "secure";
let source_scheme = columns.includes("source_scheme")
? "source_scheme"
: `"${Ci.nsICookie.SCHEME_UNSET}" as source_scheme`;
// We don't support decrypting cookies yet so only import plaintext ones.
let rows = await MigrationUtils.getRowsFromDBWithoutLocks(
cookiesPath,
"Chrome cookies",
`SELECT host_key, name, value, path, expires_utc, ${isSecure}, ${isHttponly}, encrypted_value, ${source_scheme}
FROM cookies
WHERE length(encrypted_value) = 0`
).catch(ex => {
Cu.reportError(ex);
aCallback(false);
});
// If the promise was rejected we will have already called aCallback,
// so we can just return here.
if (!rows) {
return;
}
let fallbackExpiryDate = 0;
for (let row of rows) {
let host_key = row.getResultByName("host_key");
if (host_key.match(/^\./)) {
// 1st character of host_key may be ".", so we have to remove it
host_key = host_key.substr(1);
}
let schemeType = Ci.nsICookie.SCHEME_UNSET;
switch (row.getResultByName("source_scheme")) {
case 1:
schemeType = Ci.nsICookie.SCHEME_HTTP;
break;
case 2:
schemeType = Ci.nsICookie.SCHEME_HTTPS;
break;
}
try {
let expiresUtc =
lazy.ChromeMigrationUtils.chromeTimeToDate(
row.getResultByName("expires_utc"),
fallbackExpiryDate
) / 1000;
// No point adding cookies that don't have a valid expiry.
if (!expiresUtc) {
continue;
}
Services.cookies.add(
host_key,
row.getResultByName("path"),
row.getResultByName("name"),
row.getResultByName("value"),
row.getResultByName(isSecure),
row.getResultByName(isHttponly),
false,
parseInt(expiresUtc),
{},
Ci.nsICookie.SAMESITE_NONE,
schemeType
);
} catch (e) {
Cu.reportError(e);
}
}
aCallback(true);
},
};
}
/**
* Chromium migrator
*/
export class ChromiumProfileMigrator extends ChromeProfileMigrator {
get classDescription() {
return "Chromium Profile Migrator";
}
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=chromium";
}
get classID() {
return Components.ID("{8cece922-9720-42de-b7db-7cef88cb07ca}");
}
_chromeUserDataPathSuffix = "Chromium";
_keychainServiceName = "Chromium Safe Storage";
_keychainAccountName = "Chromium";
}
/**
* Chrome Canary
* Not available on Linux
*/
export class CanaryProfileMigrator extends ChromeProfileMigrator {
get classDescription() {
return "Chrome Canary Profile Migrator";
}
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=canary";
}
get classID() {
return Components.ID("{4bf85aa5-4e21-46ca-825f-f9c51a5e8c76}");
}
get _chromeUserDataPathSuffix() {
return "Canary";
}
get _keychainServiceName() {
return "Chromium Safe Storage";
}
get _keychainAccountName() {
return "Chromium";
}
}
/**
* Chrome Dev - Linux only (not available in Mac and Windows)
*/
export class ChromeDevMigrator extends ChromeProfileMigrator {
get classDescription() {
return "Chrome Dev Profile Migrator";
}
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=chrome-dev";
}
get classID() {
return Components.ID("{7370a02a-4886-42c3-a4ec-d48c726ec30a}");
}
_chromeUserDataPathSuffix = "Chrome Dev";
_keychainServiceName = "Chromium Safe Storage";
_keychainAccountName = "Chromium";
}
/**
* Chrome Beta migrator
*/
export class ChromeBetaMigrator extends ChromeProfileMigrator {
get classDescription() {
return "Chrome Beta Profile Migrator";
}
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=chrome-beta";
}
get classID() {
return Components.ID("{47f75963-840b-4950-a1f0-d9c1864f8b8e}");
}
_chromeUserDataPathSuffix = "Chrome Beta";
_keychainServiceName = "Chromium Safe Storage";
_keychainAccountName = "Chromium";
}
/**
* Brave migrator
*/
export class BraveProfileMigrator extends ChromeProfileMigrator {
get classDescription() {
return "Brave Browser Migrator";
}
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=brave";
}
get classID() {
return Components.ID("{4071880a-69e4-4c83-88b4-6c589a62801d}");
}
_chromeUserDataPathSuffix = "Brave";
_keychainServiceName = "Brave Browser Safe Storage";
_keychainAccountName = "Brave Browser";
}
/**
* Edge (Chromium-based) migrator
*/
export class ChromiumEdgeMigrator extends ChromeProfileMigrator {
get classDescription() {
return "Chromium Edge Profile Migrator";
}
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=chromium-edge";
}
get classID() {
return Components.ID("{3c7f6b7c-baa9-4338-acfa-04bf79f1dcf1}");
}
_chromeUserDataPathSuffix = "Edge";
_keychainServiceName = "Microsoft Edge Safe Storage";
_keychainAccountName = "Microsoft Edge";
}
/**
* Edge Beta (Chromium-based) migrator
*/
export class ChromiumEdgeBetaMigrator extends ChromeProfileMigrator {
get classDescription() {
return "Chromium Edge Beta Profile Migrator";
}
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=chromium-edge-beta";
}
get classID() {
return Components.ID("{0fc3d48a-c1c3-4871-b58f-a8b47d1555fb}");
}
_chromeUserDataPathSuffix = "Edge Beta";
_keychainServiceName = "Microsoft Edge Safe Storage";
_keychainAccountName = "Microsoft Edge";
}
/**
* Chromium 360 migrator
*/
export class Chromium360seMigrator extends ChromeProfileMigrator {
get classDescription() {
return "Chromium 360 Secure Browser Profile Migrator";
}
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=chromium-360se";
}
get classID() {
return Components.ID("{2e1a182e-ce4f-4dc9-a22c-d4125b931552}");
}
_chromeUserDataPathSuffix = "360 SE";
_keychainServiceName = "Microsoft Edge Safe Storage";
_keychainAccountName = "Microsoft Edge";
}
/**
* Opera migrator
*/
export class OperaProfileMigrator extends ChromeProfileMigrator {
get classDescription() {
return "Opera Browser Migrator";
}
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=opera";
}
get classID() {
return Components.ID("{16c5d501-e411-41eb-93f2-af6c9ba64dee}");
}
_chromeUserDataPathSuffix = "Opera";
_keychainServiceName = "Opera Browser Safe Storage";
_keychainAccountName = "Opera Browser";
getSourceProfiles() {
return null;
}
}
/**
* Opera GX migrator
*/
export class OperaGXProfileMigrator extends ChromeProfileMigrator {
get classDescription() {
return "Opera GX Browser Migrator";
}
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=opera-gx";
}
get classID() {
return Components.ID("{26F4E0A0-B533-4FDA-B344-6FF5DA45D6DC}");
}
_chromeUserDataPathSuffix = "Opera GX";
_keychainServiceName = "Opera Browser Safe Storage";
_keychainAccountName = "Opera Browser";
getSourceProfiles() {
return null;
}
}
/**
* Vivaldi migrator
*/
export class VivaldiProfileMigrator extends ChromeProfileMigrator {
get classDescription() {
return "Vivaldi Migrator";
}
get contractID() {
return "@mozilla.org/profile/migrator;1?app=browser&type=vivaldi";
}
get classID() {
return Components.ID("{54a6a025-e70d-49dd-ba95-0f7e45d728d3}");
}
_chromeUserDataPathSuffix = "Vivaldi";
_keychainServiceName = "Vivaldi Safe Storage";
_keychainAccountName = "Vivaldi";
}