Source code

Revision control

Copy as Markdown

Other Tools

/* 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 kLoginsKey =
"Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
const { AppConstants } = ChromeUtils.import(
);
const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { MigrationUtils, MigratorPrototype } = ChromeUtils.import(
);
const { MSMigrationUtils } = ChromeUtils.import(
);
const lazy = {};
ChromeUtils.defineModuleGetter(
lazy,
"ctypes",
);
ChromeUtils.defineModuleGetter(
lazy,
"PlacesUtils",
);
ChromeUtils.defineModuleGetter(
lazy,
"OSCrypto",
);
// Resources
function History() {}
History.prototype = {
type: MigrationUtils.resourceTypes.HISTORY,
get exists() {
return true;
},
migrate: function H_migrate(aCallback) {
let pageInfos = [];
let typedURLs = MSMigrationUtils.getTypedURLs(
"Software\\Microsoft\\Internet Explorer"
);
for (let entry of Cc[
"@mozilla.org/profile/migrator/iehistoryenumerator;1"
].createInstance(Ci.nsISimpleEnumerator)) {
let url = entry.get("uri").QueryInterface(Ci.nsIURI);
// MSIE stores some types of URLs in its history that we don't handle,
// like HTMLHelp and others. Since we don't properly map handling for
// all of them we just avoid importing them.
if (!["http", "https", "ftp", "file"].includes(url.scheme)) {
continue;
}
let title = entry.get("title");
// Embed visits have no title and don't need to be imported.
if (!title.length) {
continue;
}
// The typed urls are already fixed-up, so we can use them for comparison.
let transition = typedURLs.has(url.spec)
? lazy.PlacesUtils.history.TRANSITIONS.LINK
: lazy.PlacesUtils.history.TRANSITIONS.TYPED;
// use the current date if we have no visits for this entry.
let time = entry.get("time");
pageInfos.push({
url,
title,
visits: [
{
transition,
date: time
? lazy.PlacesUtils.toDate(entry.get("time"))
: new Date(),
},
],
});
}
// Check whether there is any history to import.
if (!pageInfos.length) {
aCallback(true);
return;
}
MigrationUtils.insertVisitsWrapper(pageInfos).then(
() => aCallback(true),
() => aCallback(false)
);
},
};
// IE form password migrator supporting windows from XP until 7 and IE from 7 until 11
function IE7FormPasswords() {
// used to distinguish between this migrator and other passwords migrators in tests.
this.name = "IE7FormPasswords";
}
IE7FormPasswords.prototype = {
type: MigrationUtils.resourceTypes.PASSWORDS,
get exists() {
// work only on windows until 7
if (AppConstants.isPlatformAndVersionAtLeast("win", "6.2")) {
return false;
}
try {
let nsIWindowsRegKey = Ci.nsIWindowsRegKey;
let key = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
nsIWindowsRegKey
);
key.open(
nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
kLoginsKey,
nsIWindowsRegKey.ACCESS_READ
);
let count = key.valueCount;
key.close();
return count > 0;
} catch (e) {
return false;
}
},
async migrate(aCallback) {
let uris = []; // the uris of the websites that are going to be migrated
for (let entry of Cc[
"@mozilla.org/profile/migrator/iehistoryenumerator;1"
].createInstance(Ci.nsISimpleEnumerator)) {
let uri = entry.get("uri").QueryInterface(Ci.nsIURI);
// MSIE stores some types of URLs in its history that we don't handle, like HTMLHelp
// and others. Since we are not going to import the logins that are performed in these URLs
// we can just skip them.
if (!["http", "https", "ftp"].includes(uri.scheme)) {
continue;
}
uris.push(uri);
}
await this._migrateURIs(uris);
aCallback(true);
},
/**
* Migrate the logins that were saved for the uris arguments.
* @param {nsIURI[]} uris - the uris that are going to be migrated.
*/
async _migrateURIs(uris) {
this.ctypesKernelHelpers = new MSMigrationUtils.CtypesKernelHelpers();
this._crypto = new lazy.OSCrypto();
let nsIWindowsRegKey = Ci.nsIWindowsRegKey;
let key = Cc["@mozilla.org/windows-registry-key;1"].createInstance(
nsIWindowsRegKey
);
key.open(
nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
kLoginsKey,
nsIWindowsRegKey.ACCESS_READ
);
let urlsSet = new Set(); // set of the already processed urls.
// number of the successfully decrypted registry values
let successfullyDecryptedValues = 0;
/* The logins are stored in the registry, where the key is a hashed URL and its
* value contains the encrypted details for all logins for that URL.
*
* First iterate through IE history, hashing each URL and looking for a match. If
* found, decrypt the value, using the URL as a salt. Finally add any found logins
* to the Firefox password manager.
*/
let logins = [];
for (let uri of uris) {
try {
// remove the query and the ref parts of the URL
let urlObject = new URL(uri.spec);
let url = urlObject.origin + urlObject.pathname;
// if the current url is already processed, it should be skipped
if (urlsSet.has(url)) {
continue;
}
urlsSet.add(url);
// hash value of the current uri
let hashStr = this._crypto.getIELoginHash(url);
if (!key.hasValue(hashStr)) {
continue;
}
let value = key.readBinaryValue(hashStr);
// if no value was found, the uri is skipped
if (value == null) {
continue;
}
let data;
try {
// the url is used as salt to decrypt the registry value
data = this._crypto.decryptData(value, url);
} catch (e) {
continue;
}
// extract the login details from the decrypted data
let ieLogins = this._extractDetails(data, uri);
// if at least a credential was found in the current data, successfullyDecryptedValues should
// be incremented by one
if (ieLogins.length) {
successfullyDecryptedValues++;
}
for (let ieLogin of ieLogins) {
logins.push({
username: ieLogin.username,
password: ieLogin.password,
origin: ieLogin.url,
timeCreated: ieLogin.creation,
});
}
} catch (e) {
Cu.reportError(
"Error while importing logins for " + uri.spec + ": " + e
);
}
}
if (logins.length) {
await MigrationUtils.insertLoginsWrapper(logins);
}
// if the number of the imported values is less than the number of values in the key, it means
// that not all the values were imported and an error should be reported
if (successfullyDecryptedValues < key.valueCount) {
Cu.reportError(
"We failed to decrypt and import some logins. " +
"This is likely because we didn't find the URLs where these " +
"passwords were submitted in the IE history and which are needed to be used " +
"as keys in the decryption."
);
}
key.close();
this._crypto.finalize();
this.ctypesKernelHelpers.finalize();
},
_crypto: null,
/**
* Extract the details of one or more logins from the raw decrypted data.
* @param {string} data - the decrypted data containing raw information.
* @param {nsURI} uri - the nsURI of page where the login has occur.
* @returns {Object[]} array of objects where each of them contains the username, password, URL,
* and creation time representing all the logins found in the data arguments.
*/
_extractDetails(data, uri) {
// the structure of the header of the IE7 decrypted data for all the logins sharing the same URL
let loginData = new lazy.ctypes.StructType("loginData", [
// Bytes 0-3 are not needed and not documented
{ unknown1: lazy.ctypes.uint32_t },
// Bytes 4-7 are the header size
{ headerSize: lazy.ctypes.uint32_t },
// Bytes 8-11 are the data size
{ dataSize: lazy.ctypes.uint32_t },
// Bytes 12-19 are not needed and not documented
{ unknown2: lazy.ctypes.uint32_t },
{ unknown3: lazy.ctypes.uint32_t },
// Bytes 20-23 are the data count: each username and password is considered as a data
{ dataMax: lazy.ctypes.uint32_t },
// Bytes 24-35 are not needed and not documented
{ unknown4: lazy.ctypes.uint32_t },
{ unknown5: lazy.ctypes.uint32_t },
{ unknown6: lazy.ctypes.uint32_t },
]);
// the structure of a IE7 decrypted login item
let loginItem = new lazy.ctypes.StructType("loginItem", [
// Bytes 0-3 are the offset of the username
{ usernameOffset: lazy.ctypes.uint32_t },
// Bytes 4-11 are the date
{ loDateTime: lazy.ctypes.uint32_t },
{ hiDateTime: lazy.ctypes.uint32_t },
// Bytes 12-15 are not needed and not documented
{ foo: lazy.ctypes.uint32_t },
// Bytes 16-19 are the offset of the password
{ passwordOffset: lazy.ctypes.uint32_t },
// Bytes 20-31 are not needed and not documented
{ unknown1: lazy.ctypes.uint32_t },
{ unknown2: lazy.ctypes.uint32_t },
{ unknown3: lazy.ctypes.uint32_t },
]);
let url = uri.prePath;
let results = [];
let arr = this._crypto.stringToArray(data);
// convert data to ctypes.unsigned_char.array(arr.length)
let cdata = lazy.ctypes.unsigned_char.array(arr.length)(arr);
// Bytes 0-35 contain the loginData data structure for all the logins sharing the same URL
let currentLoginData = lazy.ctypes.cast(cdata, loginData);
let headerSize = currentLoginData.headerSize;
let currentInfoIndex = loginData.size;
// pointer to the current login item
let currentLoginItemPointer = lazy.ctypes.cast(
cdata.addressOfElement(currentInfoIndex),
loginItem.ptr
);
// currentLoginData.dataMax is the data count: each username and password is considered as
// a data. So, the number of logins is the number of data dived by 2
let numLogins = currentLoginData.dataMax / 2;
for (let n = 0; n < numLogins; n++) {
// Bytes 0-31 starting from currentInfoIndex contain the loginItem data structure for the
// current login
let currentLoginItem = currentLoginItemPointer.contents;
let creation =
this.ctypesKernelHelpers.fileTimeToSecondsSinceEpoch(
currentLoginItem.hiDateTime,
currentLoginItem.loDateTime
) * 1000;
let currentResult = {
creation,
url,
};
// The username is UTF-16 and null-terminated.
currentResult.username = lazy.ctypes
.cast(
cdata.addressOfElement(
headerSize + 12 + currentLoginItem.usernameOffset
),
lazy.ctypes.char16_t.ptr
)
.readString();
// The password is UTF-16 and null-terminated.
currentResult.password = lazy.ctypes
.cast(
cdata.addressOfElement(
headerSize + 12 + currentLoginItem.passwordOffset
),
lazy.ctypes.char16_t.ptr
)
.readString();
results.push(currentResult);
// move to the next login item
currentLoginItemPointer = currentLoginItemPointer.increment();
}
return results;
},
};
function IEProfileMigrator() {
this.wrappedJSObject = this; // export this to be able to use it in the unittest.
}
IEProfileMigrator.prototype = Object.create(MigratorPrototype);
IEProfileMigrator.prototype.getResources = function IE_getResources() {
let resources = [
MSMigrationUtils.getBookmarksMigrator(),
new History(),
MSMigrationUtils.getCookiesMigrator(),
];
// Only support the form password migrator for Windows XP to 7.
if (AppConstants.isPlatformAndVersionAtMost("win", "6.1")) {
resources.push(new IE7FormPasswords());
}
let windowsVaultFormPasswordsMigrator = MSMigrationUtils.getWindowsVaultFormPasswordsMigrator();
windowsVaultFormPasswordsMigrator.name = "IEVaultFormPasswords";
resources.push(windowsVaultFormPasswordsMigrator);
return resources.filter(r => r.exists);
};
IEProfileMigrator.prototype.getLastUsedDate = function IE_getLastUsedDate() {
let datePromises = ["Favs", "CookD"].map(dirId => {
let { path } = Services.dirsvc.get(dirId, Ci.nsIFile);
return OS.File.stat(path)
.catch(() => null)
.then(info => {
return info ? info.lastModificationDate : 0;
});
});
datePromises.push(
new Promise(resolve => {
let typedURLs = new Map();
try {
typedURLs = MSMigrationUtils.getTypedURLs(
"Software\\Microsoft\\Internet Explorer"
);
} catch (ex) {}
let dates = [0, ...typedURLs.values()];
// dates is an array of PRTimes, which are in microseconds - convert to milliseconds
resolve(Math.max.apply(Math, dates) / 1000);
})
);
return Promise.all(datePromises).then(dates => {
return new Date(Math.max.apply(Math, dates));
});
};
IEProfileMigrator.prototype.classDescription = "IE Profile Migrator";
IEProfileMigrator.prototype.contractID =
"@mozilla.org/profile/migrator;1?app=browser&type=ie";
IEProfileMigrator.prototype.classID = Components.ID(
"{3d2532e3-4932-4774-b7ba-968f5899d3a4}"
);
var EXPORTED_SYMBOLS = ["IEProfileMigrator"];