mirror of
https://github.com/roytam1/palemoon27.git
synced 2026-05-26 14:30:27 +00:00
3c920dd821
- Bug 1150683 - Add XPCOM interfaces for push notifications. r=dougt (24f4ff207) - Bug 1151664 - Make claim return undefined for now. r=nsm (510b60e02)
464 lines
17 KiB
JavaScript
464 lines
17 KiB
JavaScript
/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
|
|
|
|
"use strict";
|
|
|
|
const Cc = Components.classes;
|
|
const Ci = Components.interfaces;
|
|
const Cu = Components.utils;
|
|
const Cr = Components.results;
|
|
|
|
const FILE_INPUT_STREAM_CID = "@mozilla.org/network/file-input-stream;1";
|
|
|
|
const S100NS_FROM1601TO1970 = 0x19DB1DED53E8000;
|
|
const S100NS_PER_MS = 10;
|
|
|
|
const AUTH_TYPE = {
|
|
SCHEME_HTML: 0,
|
|
SCHEME_BASIC: 1,
|
|
SCHEME_DIGEST: 2
|
|
};
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
Cu.import("resource://gre/modules/NetUtil.jsm");
|
|
Cu.import("resource://gre/modules/FileUtils.jsm");
|
|
Cu.import("resource:///modules/MigrationUtils.jsm");
|
|
|
|
XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
|
|
"resource://gre/modules/PlacesUtils.jsm");
|
|
XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
|
|
"resource://gre/modules/OSCrypto.jsm");
|
|
|
|
/**
|
|
* Convert Chrome time format to Date object
|
|
*
|
|
* @param aTime
|
|
* Chrome time
|
|
* @return converted Date object
|
|
* @note Google Chrome uses FILETIME / 10 as time.
|
|
* FILETIME is based on same structure of Windows.
|
|
*/
|
|
function chromeTimeToDate(aTime)
|
|
{
|
|
return new Date((aTime * S100NS_PER_MS - S100NS_FROM1601TO1970 ) / 10000);
|
|
}
|
|
|
|
/**
|
|
* Insert bookmark items into specific folder.
|
|
*
|
|
* @param aFolderId
|
|
* id of folder where items will be inserted
|
|
* @param aItems
|
|
* bookmark items to be inserted
|
|
*/
|
|
function insertBookmarkItems(aFolderId, aItems)
|
|
{
|
|
for (let i = 0; i < aItems.length; i++) {
|
|
let item = aItems[i];
|
|
|
|
try {
|
|
if (item.type == "url") {
|
|
PlacesUtils.bookmarks.insertBookmark(aFolderId,
|
|
NetUtil.newURI(item.url),
|
|
PlacesUtils.bookmarks.DEFAULT_INDEX,
|
|
item.name);
|
|
} else if (item.type == "folder") {
|
|
let newFolderId =
|
|
PlacesUtils.bookmarks.createFolder(aFolderId,
|
|
item.name,
|
|
PlacesUtils.bookmarks.DEFAULT_INDEX);
|
|
|
|
insertBookmarkItems(newFolderId, item.children);
|
|
}
|
|
} catch (e) {
|
|
Cu.reportError(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function ChromeProfileMigrator() {
|
|
let chromeUserDataFolder = FileUtils.getDir(
|
|
#ifdef XP_WIN
|
|
"LocalAppData", ["Google", "Chrome", "User Data"]
|
|
#elifdef XP_MACOSX
|
|
"ULibDir", ["Application Support", "Google", "Chrome"]
|
|
#else
|
|
"Home", [".config", "google-chrome"]
|
|
#endif
|
|
, false);
|
|
this._chromeUserDataFolder = chromeUserDataFolder.exists() ?
|
|
chromeUserDataFolder : null;
|
|
}
|
|
|
|
ChromeProfileMigrator.prototype = Object.create(MigratorPrototype);
|
|
|
|
ChromeProfileMigrator.prototype.getResources =
|
|
function Chrome_getResources(aProfile) {
|
|
if (this._chromeUserDataFolder) {
|
|
let profileFolder = this._chromeUserDataFolder.clone();
|
|
profileFolder.append(aProfile.id);
|
|
if (profileFolder.exists()) {
|
|
let possibleResources = [GetBookmarksResource(profileFolder),
|
|
GetHistoryResource(profileFolder),
|
|
GetCookiesResource(profileFolder),
|
|
#ifdef XP_WIN
|
|
GetWindowsPasswordsResource(profileFolder)
|
|
#endif
|
|
];
|
|
return [r for each (r in possibleResources) if (r != null)];
|
|
}
|
|
}
|
|
return [];
|
|
};
|
|
|
|
Object.defineProperty(ChromeProfileMigrator.prototype, "sourceProfiles", {
|
|
get: function Chrome_sourceProfiles() {
|
|
if ("__sourceProfiles" in this)
|
|
return this.__sourceProfiles;
|
|
|
|
if (!this._chromeUserDataFolder)
|
|
return [];
|
|
|
|
let profiles = [];
|
|
try {
|
|
// Local State is a JSON file that contains profile info.
|
|
let localState = this._chromeUserDataFolder.clone();
|
|
localState.append("Local State");
|
|
if (!localState.exists())
|
|
throw new Error("Chrome's 'Local State' file does not exist.");
|
|
if (!localState.isReadable())
|
|
throw new Error("Chrome's 'Local State' file could not be read.");
|
|
|
|
let fstream = Cc[FILE_INPUT_STREAM_CID].createInstance(Ci.nsIFileInputStream);
|
|
fstream.init(localState, -1, 0, 0);
|
|
let inputStream = NetUtil.readInputStreamToString(fstream, fstream.available(),
|
|
{ charset: "UTF-8" });
|
|
let info_cache = JSON.parse(inputStream).profile.info_cache;
|
|
for (let profileFolderName in info_cache) {
|
|
let profileFolder = this._chromeUserDataFolder.clone();
|
|
profileFolder.append(profileFolderName);
|
|
profiles.push({
|
|
id: profileFolderName,
|
|
name: info_cache[profileFolderName].name || profileFolderName,
|
|
});
|
|
}
|
|
} catch (e) {
|
|
Cu.reportError("Error detecting Chrome profiles: " + e);
|
|
// If we weren't able to detect any profiles above, fallback to the Default profile.
|
|
let defaultProfileFolder = this._chromeUserDataFolder.clone();
|
|
defaultProfileFolder.append("Default");
|
|
if (defaultProfileFolder.exists()) {
|
|
profiles = [{
|
|
id: "Default",
|
|
name: "Default",
|
|
}];
|
|
}
|
|
}
|
|
|
|
// Only list profiles from which any data can be imported
|
|
return this.__sourceProfiles = profiles.filter(function(profile) {
|
|
let resources = this.getResources(profile);
|
|
return resources && resources.length > 0;
|
|
}, this);
|
|
}
|
|
});
|
|
|
|
Object.defineProperty(ChromeProfileMigrator.prototype, "sourceHomePageURL", {
|
|
get: function Chrome_sourceHomePageURL() {
|
|
let prefsFile = this._chromeUserDataFolder.clone();
|
|
prefsFile.append("Preferences");
|
|
if (prefsFile.exists()) {
|
|
// XXX reading and parsing JSON is synchronous.
|
|
let fstream = Cc[FILE_INPUT_STREAM_CID].
|
|
createInstance(Ci.nsIFileInputStream);
|
|
fstream.init(file, -1, 0, 0);
|
|
try {
|
|
return JSON.parse(
|
|
NetUtil.readInputStreamToString(fstream, fstream.available(),
|
|
{ charset: "UTF-8" })
|
|
).homepage;
|
|
}
|
|
catch(e) {
|
|
Cu.reportError("Error parsing Chrome's preferences file: " + e);
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
});
|
|
|
|
function GetBookmarksResource(aProfileFolder) {
|
|
let bookmarksFile = aProfileFolder.clone();
|
|
bookmarksFile.append("Bookmarks");
|
|
if (!bookmarksFile.exists())
|
|
return null;
|
|
|
|
return {
|
|
type: MigrationUtils.resourceTypes.BOOKMARKS,
|
|
|
|
migrate: function(aCallback) {
|
|
NetUtil.asyncFetch(bookmarksFile, MigrationUtils.wrapMigrateFunction(
|
|
function(aInputStream, aResultCode) {
|
|
if (!Components.isSuccessCode(aResultCode))
|
|
throw new Error("Could not read Bookmarks file");
|
|
|
|
// Parse Chrome bookmark file that is JSON format
|
|
let bookmarkJSON = NetUtil.readInputStreamToString(
|
|
aInputStream, aInputStream.available(), { charset : "UTF-8" });
|
|
let roots = JSON.parse(bookmarkJSON).roots;
|
|
PlacesUtils.bookmarks.runInBatchMode({
|
|
runBatched: function() {
|
|
// Importing bookmark bar items
|
|
if (roots.bookmark_bar.children &&
|
|
roots.bookmark_bar.children.length > 0) {
|
|
// Toolbar
|
|
let parentId = PlacesUtils.toolbarFolderId;
|
|
if (!MigrationUtils.isStartupMigration) {
|
|
parentId = MigrationUtils.createImportedBookmarksFolder(
|
|
"Chrome", parentId);
|
|
}
|
|
insertBookmarkItems(parentId, roots.bookmark_bar.children);
|
|
}
|
|
|
|
// Importing bookmark menu items
|
|
if (roots.other.children &&
|
|
roots.other.children.length > 0) {
|
|
// Bookmark menu
|
|
let parentId = PlacesUtils.bookmarksMenuFolderId;
|
|
if (!MigrationUtils.isStartupMigration) {
|
|
parentId = MigrationUtils.createImportedBookmarksFolder(
|
|
"Chrome", parentId);
|
|
}
|
|
insertBookmarkItems(parentId, roots.other.children);
|
|
}
|
|
}
|
|
}, null);
|
|
}, aCallback));
|
|
}
|
|
};
|
|
}
|
|
|
|
function GetHistoryResource(aProfileFolder) {
|
|
let historyFile = aProfileFolder.clone();
|
|
historyFile.append("History");
|
|
if (!historyFile.exists())
|
|
return null;
|
|
|
|
return {
|
|
type: MigrationUtils.resourceTypes.HISTORY,
|
|
|
|
migrate: function(aCallback) {
|
|
let dbConn = Services.storage.openUnsharedDatabase(historyFile);
|
|
let stmt = dbConn.createAsyncStatement(
|
|
"SELECT url, title, last_visit_time, typed_count FROM urls WHERE hidden = 0");
|
|
|
|
stmt.executeAsync({
|
|
handleResult : function(aResults) {
|
|
let places = [];
|
|
for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
|
|
try {
|
|
// if having typed_count, we changes transition type to typed.
|
|
let transType = PlacesUtils.history.TRANSITION_LINK;
|
|
if (row.getResultByName("typed_count") > 0)
|
|
transType = PlacesUtils.history.TRANSITION_TYPED;
|
|
|
|
places.push({
|
|
uri: NetUtil.newURI(row.getResultByName("url")),
|
|
title: row.getResultByName("title"),
|
|
visits: [{
|
|
transitionType: transType,
|
|
visitDate: chromeTimeToDate(
|
|
row.getResultByName(
|
|
"last_visit_time")) * 1000,
|
|
}],
|
|
});
|
|
} catch (e) {
|
|
Cu.reportError(e);
|
|
}
|
|
}
|
|
|
|
try {
|
|
PlacesUtils.asyncHistory.updatePlaces(places);
|
|
} catch (e) {
|
|
Cu.reportError(e);
|
|
}
|
|
},
|
|
|
|
handleError : function(aError) {
|
|
Cu.reportError("Async statement execution returned with '" +
|
|
aError.result + "', '" + aError.message + "'");
|
|
},
|
|
|
|
handleCompletion : function(aReason) {
|
|
dbConn.asyncClose();
|
|
aCallback(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED);
|
|
}
|
|
});
|
|
stmt.finalize();
|
|
}
|
|
};
|
|
}
|
|
|
|
function GetCookiesResource(aProfileFolder) {
|
|
let cookiesFile = aProfileFolder.clone();
|
|
cookiesFile.append("Cookies");
|
|
if (!cookiesFile.exists())
|
|
return null;
|
|
|
|
return {
|
|
type: MigrationUtils.resourceTypes.COOKIES,
|
|
|
|
migrate: function(aCallback) {
|
|
let dbConn = Services.storage.openUnsharedDatabase(cookiesFile);
|
|
let stmt = dbConn.createAsyncStatement(
|
|
"SELECT host_key, path, name, value, secure, httponly, expires_utc FROM cookies");
|
|
|
|
stmt.executeAsync({
|
|
handleResult : function(aResults) {
|
|
for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
|
|
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);
|
|
}
|
|
|
|
try {
|
|
let expiresUtc =
|
|
chromeTimeToDate(row.getResultByName("expires_utc")) / 1000;
|
|
Services.cookies.add(host_key,
|
|
row.getResultByName("path"),
|
|
row.getResultByName("name"),
|
|
row.getResultByName("value"),
|
|
row.getResultByName("secure"),
|
|
row.getResultByName("httponly"),
|
|
false,
|
|
parseInt(expiresUtc));
|
|
} catch (e) {
|
|
Cu.reportError(e);
|
|
}
|
|
}
|
|
},
|
|
|
|
handleError : function(aError) {
|
|
Cu.reportError("Async statement execution returned with '" +
|
|
aError.result + "', '" + aError.message + "'");
|
|
},
|
|
|
|
handleCompletion : function(aReason) {
|
|
dbConn.asyncClose();
|
|
aCallback(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED);
|
|
},
|
|
});
|
|
stmt.finalize();
|
|
}
|
|
}
|
|
}
|
|
|
|
function GetWindowsPasswordsResource(aProfileFolder) {
|
|
let loginFile = aProfileFolder.clone();
|
|
loginFile.append("Login Data");
|
|
if (!loginFile.exists())
|
|
return null;
|
|
|
|
return {
|
|
type: MigrationUtils.resourceTypes.PASSWORDS,
|
|
|
|
migrate(aCallback) {
|
|
let dbConn = Services.storage.openUnsharedDatabase(loginFile);
|
|
let stmt = dbConn.createAsyncStatement(`
|
|
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`);
|
|
let crypto = new OSCrypto();
|
|
let utf8Converter = Cc["@mozilla.org/intl/utf8converterservice;1"].getService(Ci.nsIUTF8ConverterService);
|
|
|
|
stmt.executeAsync({
|
|
_rowToLoginInfo(row) {
|
|
let loginInfo = {
|
|
username: utf8Converter.convertURISpecToUTF8(row.getResultByName("username_value"), "UTF-8"),
|
|
password: utf8Converter.convertURISpecToUTF8(
|
|
crypto.decryptData(crypto.arrayToString(row.getResultByName("password_value")), null),
|
|
"UTF-8"),
|
|
hostName: NetUtil.newURI(row.getResultByName("origin_url")).prePath,
|
|
submitURL: null,
|
|
httpRealm: null,
|
|
usernameElement: row.getResultByName("username_element"),
|
|
passwordElement: row.getResultByName("password_element"),
|
|
timeCreated: chromeTimeToDate(row.getResultByName("date_created") + 0).getTime(),
|
|
timesUsed: row.getResultByName("times_used") + 0,
|
|
};
|
|
|
|
switch (row.getResultByName("scheme")) {
|
|
case AUTH_TYPE.SCHEME_HTML:
|
|
loginInfo.submitURL = NetUtil.newURI(row.getResultByName("action_url")).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.hostName.length + 1);
|
|
break;
|
|
default:
|
|
throw new Error("Login data scheme type not supported: " +
|
|
row.getResultByName("scheme"));
|
|
}
|
|
|
|
return loginInfo;
|
|
},
|
|
|
|
handleResult(aResults) {
|
|
for (let row = aResults.getNextRow(); row; row = aResults.getNextRow()) {
|
|
try {
|
|
let loginInfo = this._rowToLoginInfo(row);
|
|
let login = Cc["@mozilla.org/login-manager/loginInfo;1"].createInstance(Ci.nsILoginInfo);
|
|
|
|
login.init(loginInfo.hostName, loginInfo.submitURL, loginInfo.httpRealm,
|
|
loginInfo.username, loginInfo.password, loginInfo.usernameElement,
|
|
loginInfo.passwordElement);
|
|
login.QueryInterface(Ci.nsILoginMetaInfo);
|
|
login.timeCreated = loginInfo.timeCreated;
|
|
login.timeLastUsed = loginInfo.timeCreated;
|
|
login.timePasswordChanged = loginInfo.timeCreated;
|
|
login.timesUsed = loginInfo.timesUsed;
|
|
|
|
// Add the login only if there's not an existing entry
|
|
let logins = Services.logins.findLogins({}, login.hostname,
|
|
login.formSubmitURL,
|
|
login.httpRealm);
|
|
|
|
// Bug 1187190: Password changes should be propagated depending on timestamps.
|
|
if (!logins.some(l => login.matches(l, true))) {
|
|
Services.logins.addLogin(login);
|
|
}
|
|
} catch (e) {
|
|
Cu.reportError(e);
|
|
}
|
|
}
|
|
},
|
|
|
|
handleError(aError) {
|
|
Cu.reportError("Async statement execution returned with '" +
|
|
aError.result + "', '" + aError.message + "'");
|
|
},
|
|
|
|
handleCompletion(aReason) {
|
|
dbConn.asyncClose();
|
|
aCallback(aReason == Ci.mozIStorageStatementCallback.REASON_FINISHED);
|
|
crypto.finalize();
|
|
},
|
|
});
|
|
stmt.finalize();
|
|
}
|
|
};
|
|
}
|
|
|
|
ChromeProfileMigrator.prototype.classDescription = "Chrome Profile Migrator";
|
|
ChromeProfileMigrator.prototype.contractID = "@mozilla.org/profile/migrator;1?app=browser&type=chrome";
|
|
ChromeProfileMigrator.prototype.classID = Components.ID("{4cec1de4-1671-4fc3-a53e-6c539dc77a26}");
|
|
|
|
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ChromeProfileMigrator]);
|