fune/browser/components/migration/360seProfileMigrator.jsm
Jared Wein 21ab31bac4 Bug 1670810 - Don't create the 'From BrowserX' wrapper folder after import if the 2h2020 pref is enabled. r=Gijs
The value of this wrapper folder diminishes quickly over time and its presence is just a reminder of a users former browser.

Differential Revision: https://phabricator.services.mozilla.com/D93278
2020-10-13 10:00:38 +00:00

388 lines
11 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/. */
"use strict";
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
const { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
const { FileUtils } = ChromeUtils.import(
"resource://gre/modules/FileUtils.jsm"
);
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { MigrationUtils, MigratorPrototype } = ChromeUtils.import(
"resource:///modules/MigrationUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"PlacesUtils",
"resource://gre/modules/PlacesUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"PlacesUIUtils",
"resource:///modules/PlacesUIUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"Sqlite",
"resource://gre/modules/Sqlite.jsm"
);
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
const kBookmarksFileName = "360sefav.db";
function copyToTempUTF8File(file, charset) {
let inputStream = Cc[
"@mozilla.org/network/file-input-stream;1"
].createInstance(Ci.nsIFileInputStream);
inputStream.init(file, -1, -1, 0);
let inputStr = NetUtil.readInputStreamToString(
inputStream,
inputStream.available(),
{ charset }
);
// Use random to reduce the likelihood of a name collision in createUnique.
let rand = Math.floor(Math.random() * Math.pow(2, 15));
let leafName = "mozilla-temp-" + rand;
let tempUTF8File = FileUtils.getFile(
"TmpD",
["mozilla-temp-files", leafName],
true
);
tempUTF8File.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o600);
let out = FileUtils.openAtomicFileOutputStream(tempUTF8File);
try {
let bufferedOut = Cc[
"@mozilla.org/network/buffered-output-stream;1"
].createInstance(Ci.nsIBufferedOutputStream);
bufferedOut.init(out, 4096);
try {
let converterOut = Cc[
"@mozilla.org/intl/converter-output-stream;1"
].createInstance(Ci.nsIConverterOutputStream);
converterOut.init(bufferedOut, "utf-8");
try {
converterOut.writeString(inputStr || "");
bufferedOut.QueryInterface(Ci.nsISafeOutputStream).finish();
} finally {
converterOut.close();
}
} finally {
bufferedOut.close();
}
} finally {
out.close();
}
return tempUTF8File;
}
function parseINIStrings(file) {
let factory = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(
Ci.nsIINIParserFactory
);
let parser = factory.createINIParser(file);
let obj = {};
for (let section of parser.getSections()) {
obj[section] = {};
for (let key of parser.getKeys(section)) {
obj[section][key] = parser.getString(section, key);
}
}
return obj;
}
function getHash(aStr) {
// return the two-digit hexadecimal code for a byte
let toHexString = charCode => ("0" + charCode.toString(16)).slice(-2);
let hasher = Cc["@mozilla.org/security/hash;1"].createInstance(
Ci.nsICryptoHash
);
hasher.init(Ci.nsICryptoHash.MD5);
let stringStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(
Ci.nsIStringInputStream
);
stringStream.data = aStr;
hasher.updateFromStream(stringStream, -1);
// convert the binary hash data to a hex string.
let binary = hasher.finish(false);
return Array.from(binary, (c, i) => toHexString(binary.charCodeAt(i)))
.join("")
.toLowerCase();
}
function Bookmarks(aProfileFolder) {
let file = aProfileFolder.clone();
file.append(kBookmarksFileName);
this._file = file;
}
Bookmarks.prototype = {
type: MigrationUtils.resourceTypes.BOOKMARKS,
get exists() {
return this._file.exists() && this._file.isReadable();
},
migrate(aCallback) {
return (async () => {
let folderMap = new Map();
let toolbarBMs = [];
let connection = await Sqlite.openConnection({
path: this._file.path,
});
let histogramBookmarkRoots = 0;
try {
let rows = await connection.execute(
`WITH RECURSIVE
bookmark(id, parent_id, is_folder, title, url, pos) AS (
VALUES(0, -1, 1, '', '', 0)
UNION
SELECT f.id, f.parent_id, f.is_folder, f.title, f.url, f.pos
FROM tb_fav AS f
JOIN bookmark AS b ON f.parent_id = b.id
ORDER BY f.pos ASC
)
SELECT id, parent_id, is_folder, title, url FROM bookmark WHERE id`
);
for (let row of rows) {
let id = parseInt(row.getResultByName("id"), 10);
let parent_id = parseInt(row.getResultByName("parent_id"), 10);
let is_folder = parseInt(row.getResultByName("is_folder"), 10);
let title = row.getResultByName("title");
let url = row.getResultByName("url");
let bmToInsert;
if (is_folder) {
bmToInsert = {
children: [],
title,
type: PlacesUtils.bookmarks.TYPE_FOLDER,
};
folderMap.set(id, bmToInsert);
} else {
try {
new URL(url);
} catch (ex) {
Cu.reportError(
`Ignoring ${url} when importing from 360se because of exception: ${ex}`
);
continue;
}
bmToInsert = {
title,
url,
};
}
if (folderMap.has(parent_id)) {
folderMap.get(parent_id).children.push(bmToInsert);
} else if (parent_id === 0) {
toolbarBMs.push(bmToInsert);
}
}
} finally {
await connection.close();
}
if (toolbarBMs.length) {
histogramBookmarkRoots |=
MigrationUtils.SOURCE_BOOKMARK_ROOTS_BOOKMARKS_TOOLBAR;
let parentGuid = PlacesUtils.bookmarks.toolbarGuid;
if (
!Services.prefs.getBoolPref("browser.toolbars.bookmarks.2h2020") &&
!MigrationUtils.isStartupMigration &&
PlacesUtils.getChildCountForFolder(
PlacesUtils.bookmarks.toolbarGuid
) > PlacesUIUtils.NUM_TOOLBAR_BOOKMARKS_TO_UNHIDE
) {
parentGuid = await MigrationUtils.createImportedBookmarksFolder(
"360se",
parentGuid
);
}
await MigrationUtils.insertManyBookmarksWrapper(toolbarBMs, parentGuid);
PlacesUIUtils.maybeToggleBookmarkToolbarVisibilityAfterMigration();
}
Services.telemetry
.getKeyedHistogramById("FX_MIGRATION_BOOKMARKS_ROOTS")
.add("360se", histogramBookmarkRoots);
})().then(
() => aCallback(true),
e => {
Cu.reportError(e);
aCallback(false);
}
);
},
};
function Qihoo360seProfileMigrator() {
let paths = [
// for v6 and above
{
users: ["360se6", "apps", "data", "users"],
defaultUser: "default",
},
// for earlier versions
{
users: ["360se"],
defaultUser: "data",
},
];
this._usersDir = null;
this._defaultUserPath = null;
for (let path of paths) {
let usersDir = FileUtils.getDir("AppData", path.users, false);
if (usersDir.exists()) {
this._usersDir = usersDir;
this._defaultUserPath = path.defaultUser;
break;
}
}
}
Qihoo360seProfileMigrator.prototype = Object.create(MigratorPrototype);
Qihoo360seProfileMigrator.prototype.getSourceProfiles = function() {
if ("__sourceProfiles" in this) {
return this.__sourceProfiles;
}
if (!this._usersDir) {
this.__sourceProfiles = [];
return this.__sourceProfiles;
}
let profiles = [];
let noLoggedInUser = true;
try {
let loginIni = this._usersDir.clone();
loginIni.append("login.ini");
if (!loginIni.exists()) {
throw new Error("360 Secure Browser's 'login.ini' does not exist.");
}
if (!loginIni.isReadable()) {
throw new Error(
"360 Secure Browser's 'login.ini' file could not be read."
);
}
let loginIniInUtf8 = copyToTempUTF8File(loginIni, "GBK");
let loginIniObj = parseINIStrings(loginIniInUtf8);
try {
loginIniInUtf8.remove(false);
} catch (ex) {}
let nowLoginEmail = loginIniObj.NowLogin && loginIniObj.NowLogin.email;
/*
* NowLogin section may:
* 1. be missing or without email, before any user logs in.
* 2. represents the current logged in user
* 3. represents the most recent logged in user
*
* In the second case, user represented by NowLogin should be the first
* profile; otherwise the default user should be selected by default.
*/
if (nowLoginEmail) {
if (loginIniObj.NowLogin.IsLogined === "1") {
noLoggedInUser = false;
}
profiles.push({
id: this._getIdFromConfig(loginIniObj.NowLogin),
name: nowLoginEmail,
});
}
for (let section in loginIniObj) {
if (
!loginIniObj[section].email ||
(nowLoginEmail && loginIniObj[section].email == nowLoginEmail)
) {
continue;
}
profiles.push({
id: this._getIdFromConfig(loginIniObj[section]),
name: loginIniObj[section].email,
});
}
} catch (e) {
Cu.reportError("Error detecting 360 Secure Browser profiles: " + e);
} finally {
profiles[noLoggedInUser ? "unshift" : "push"]({
id: this._defaultUserPath,
name: "Default",
});
}
this.__sourceProfiles = profiles.filter(profile => {
let resources = this.getResources(profile);
return resources && !!resources.length;
});
return this.__sourceProfiles;
};
Qihoo360seProfileMigrator.prototype._getIdFromConfig = function(aConfig) {
return aConfig.UserMd5 || getHash(aConfig.email);
};
Qihoo360seProfileMigrator.prototype.getResources = function(aProfile) {
let profileFolder = this._usersDir.clone();
profileFolder.append(aProfile.id);
if (!profileFolder.exists()) {
return [];
}
let resources = [new Bookmarks(profileFolder)];
return resources.filter(r => r.exists);
};
Qihoo360seProfileMigrator.prototype.getLastUsedDate = async function() {
let sourceProfiles = await this.getSourceProfiles();
let bookmarksPaths = sourceProfiles.map(({ id }) => {
return OS.Path.join(this._usersDir.path, id, kBookmarksFileName);
});
if (!bookmarksPaths.length) {
return new Date(0);
}
let datePromises = bookmarksPaths.map(path => {
return OS.File.stat(path)
.catch(() => null)
.then(info => {
return info ? info.lastModificationDate : 0;
});
});
return Promise.all(datePromises).then(dates => {
return new Date(Math.max.apply(Math, dates));
});
};
Qihoo360seProfileMigrator.prototype.classDescription =
"360 Secure Browser Profile Migrator";
Qihoo360seProfileMigrator.prototype.contractID =
"@mozilla.org/profile/migrator;1?app=browser&type=360se";
Qihoo360seProfileMigrator.prototype.classID = Components.ID(
"{d0037b95-296a-4a4e-94b2-c3d075d20ab1}"
);
var EXPORTED_SYMBOLS = ["Qihoo360seProfileMigrator"];