summaryrefslogtreecommitdiffstats
path: root/browser/components/migration/IEProfileMigrator.js
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/migration/IEProfileMigrator.js')
-rw-r--r--browser/components/migration/IEProfileMigrator.js542
1 files changed, 542 insertions, 0 deletions
diff --git a/browser/components/migration/IEProfileMigrator.js b/browser/components/migration/IEProfileMigrator.js
new file mode 100644
index 000000000..ac055686c
--- /dev/null
+++ b/browser/components/migration/IEProfileMigrator.js
@@ -0,0 +1,542 @@
+/* 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 kLoginsKey = "Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2";
+const kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
+
+Cu.import("resource://gre/modules/AppConstants.jsm");
+Cu.import("resource://gre/modules/osfile.jsm"); /* globals OS */
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/Task.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/MigrationUtils.jsm"); /* globals MigratorPrototype */
+Cu.import("resource:///modules/MSMigrationUtils.jsm");
+
+
+XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
+ "resource://gre/modules/ctypes.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OSCrypto",
+ "resource://gre/modules/OSCrypto.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
+ "resource://gre/modules/WindowsRegistry.jsm");
+
+Cu.importGlobalProperties(["URL"]);
+
+// Resources
+
+function History() {
+}
+
+History.prototype = {
+ type: MigrationUtils.resourceTypes.HISTORY,
+
+ get exists() {
+ return true;
+ },
+
+ migrate: function H_migrate(aCallback) {
+ let places = [];
+ let typedURLs = MSMigrationUtils.getTypedURLs("Software\\Microsoft\\Internet Explorer");
+ let historyEnumerator = Cc["@mozilla.org/profile/migrator/iehistoryenumerator;1"].
+ createInstance(Ci.nsISimpleEnumerator);
+ while (historyEnumerator.hasMoreElements()) {
+ let entry = historyEnumerator.getNext().QueryInterface(Ci.nsIPropertyBag2);
+ 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 don't properly map handling for
+ // all of them we just avoid importing them.
+ if (["http", "https", "ftp", "file"].indexOf(uri.scheme) == -1) {
+ continue;
+ }
+
+ let title = entry.get("title");
+ // Embed visits have no title and don't need to be imported.
+ if (title.length == 0) {
+ continue;
+ }
+
+ // The typed urls are already fixed-up, so we can use them for comparison.
+ let transitionType = typedURLs.has(uri.spec) ?
+ Ci.nsINavHistoryService.TRANSITION_TYPED :
+ Ci.nsINavHistoryService.TRANSITION_LINK;
+ // use the current date if we have no visits for this entry.
+ // Note that the entry will have a time in microseconds (PRTime),
+ // and Date.now() returns milliseconds. Places expects PRTime,
+ // so we multiply the Date.now return value to make up the difference.
+ let lastVisitTime = entry.get("time") || (Date.now() * 1000);
+
+ places.push(
+ { uri: uri,
+ title: title,
+ visits: [{ transitionType: transitionType,
+ visitDate: lastVisitTime }]
+ }
+ );
+ }
+
+ // Check whether there is any history to import.
+ if (places.length == 0) {
+ aCallback(true);
+ return;
+ }
+
+ MigrationUtils.insertVisitsWrapper(places, {
+ _success: false,
+ handleResult: function() {
+ // Importing any entry is considered a successful import.
+ this._success = true;
+ },
+ handleError: function() {},
+ handleCompletion: function() {
+ aCallback(this._success);
+ }
+ });
+ }
+};
+
+// 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;
+ }
+ },
+
+ migrate(aCallback) {
+ let historyEnumerator = Cc["@mozilla.org/profile/migrator/iehistoryenumerator;1"].
+ createInstance(Ci.nsISimpleEnumerator);
+ let uris = []; // the uris of the websites that are going to be migrated
+ while (historyEnumerator.hasMoreElements()) {
+ let entry = historyEnumerator.getNext().QueryInterface(Ci.nsIPropertyBag2);
+ 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"].indexOf(uri.scheme) == -1) {
+ continue;
+ }
+
+ uris.push(uri);
+ }
+ 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.
+ */
+ _migrateURIs(uris) {
+ this.ctypesKernelHelpers = new MSMigrationUtils.CtypesKernelHelpers();
+ this._crypto = new 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.
+ */
+
+ 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, true);
+ } 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++;
+ }
+ this._addLogins(ieLogins);
+ } catch (e) {
+ Cu.reportError("Error while importing logins for " + uri.spec + ": " + e);
+ }
+ }
+ // 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,
+
+ /**
+ * Add the logins to the password manager.
+ * @param {Object[]} logins - array of the login details.
+ */
+ _addLogins(ieLogins) {
+ for (let ieLogin of ieLogins) {
+ try {
+ // create a new login
+ let login = {
+ username: ieLogin.username,
+ password: ieLogin.password,
+ hostname: ieLogin.url,
+ timeCreated: ieLogin.creation,
+ };
+ MigrationUtils.insertLoginWrapper(login);
+ } catch (e) {
+ Cu.reportError(e);
+ }
+ }
+ },
+
+ /**
+ * 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 ctypes.StructType("loginData", [
+ // Bytes 0-3 are not needed and not documented
+ {"unknown1": ctypes.uint32_t},
+ // Bytes 4-7 are the header size
+ {"headerSize": ctypes.uint32_t},
+ // Bytes 8-11 are the data size
+ {"dataSize": ctypes.uint32_t},
+ // Bytes 12-19 are not needed and not documented
+ {"unknown2": ctypes.uint32_t},
+ {"unknown3": ctypes.uint32_t},
+ // Bytes 20-23 are the data count: each username and password is considered as a data
+ {"dataMax": ctypes.uint32_t},
+ // Bytes 24-35 are not needed and not documented
+ {"unknown4": ctypes.uint32_t},
+ {"unknown5": ctypes.uint32_t},
+ {"unknown6": ctypes.uint32_t}
+ ]);
+
+ // the structure of a IE7 decrypted login item
+ let loginItem = new ctypes.StructType("loginItem", [
+ // Bytes 0-3 are the offset of the username
+ {"usernameOffset": ctypes.uint32_t},
+ // Bytes 4-11 are the date
+ {"loDateTime": ctypes.uint32_t},
+ {"hiDateTime": ctypes.uint32_t},
+ // Bytes 12-15 are not needed and not documented
+ {"foo": ctypes.uint32_t},
+ // Bytes 16-19 are the offset of the password
+ {"passwordOffset": ctypes.uint32_t},
+ // Bytes 20-31 are not needed and not documented
+ {"unknown1": ctypes.uint32_t},
+ {"unknown2": ctypes.uint32_t},
+ {"unknown3": 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 = 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 = ctypes.cast(cdata, loginData);
+ let headerSize = currentLoginData.headerSize;
+ let currentInfoIndex = loginData.size;
+ // pointer to the current login item
+ let currentLoginItemPointer = 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: creation,
+ url: url,
+ };
+ // The username is UTF-16 and null-terminated.
+ currentResult.username =
+ ctypes.cast(cdata.addressOfElement(headerSize + 12 + currentLoginItem.usernameOffset),
+ ctypes.char16_t.ptr).readString();
+ // The password is UTF-16 and null-terminated.
+ currentResult.password =
+ ctypes.cast(cdata.addressOfElement(headerSize + 12 + currentLoginItem.passwordOffset),
+ ctypes.char16_t.ptr).readString();
+ results.push(currentResult);
+ // move to the next login item
+ currentLoginItemPointer = currentLoginItemPointer.increment();
+ }
+ return results;
+ },
+};
+
+function Settings() {
+}
+
+Settings.prototype = {
+ type: MigrationUtils.resourceTypes.SETTINGS,
+
+ get exists() {
+ return true;
+ },
+
+ migrate: function S_migrate(aCallback) {
+ // Converts from yes/no to a boolean.
+ let yesNoToBoolean = v => v == "yes";
+
+ // Converts source format like "en-us,ar-kw;q=0.7,ar-om;q=0.3" into
+ // destination format like "en-us, ar-kw, ar-om".
+ // Final string is sorted by quality (q=) param.
+ function parseAcceptLanguageList(v) {
+ return v.match(/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/gi)
+ .sort(function(a, b) {
+ let qA = parseFloat(a.split(";q=")[1]) || 1.0;
+ let qB = parseFloat(b.split(";q=")[1]) || 1.0;
+ return qB - qA;
+ })
+ .map(a => a.split(";")[0]);
+ }
+
+ // For reference on some of the available IE Registry settings:
+ // * http://msdn.microsoft.com/en-us/library/cc980058%28v=prot.13%29.aspx
+ // * http://msdn.microsoft.com/en-us/library/cc980059%28v=prot.13%29.aspx
+
+ // Note that only settings exposed in our UI should be migrated.
+
+ this._set("Software\\Microsoft\\Internet Explorer\\International",
+ "AcceptLanguage",
+ "intl.accept_languages",
+ parseAcceptLanguageList);
+ // TODO (bug 745853): For now, only x-western font is translated.
+ this._set("Software\\Microsoft\\Internet Explorer\\International\\Scripts\\3",
+ "IEFixedFontName",
+ "font.name.monospace.x-western");
+ this._set(kMainKey,
+ "Use FormSuggest",
+ "browser.formfill.enable",
+ yesNoToBoolean);
+ this._set(kMainKey,
+ "FormSuggest Passwords",
+ "signon.rememberSignons",
+ yesNoToBoolean);
+ this._set(kMainKey,
+ "Anchor Underline",
+ "browser.underline_anchors",
+ yesNoToBoolean);
+ this._set(kMainKey,
+ "Display Inline Images",
+ "permissions.default.image",
+ v => yesNoToBoolean(v) ? 1 : 2);
+ this._set(kMainKey,
+ "Move System Caret",
+ "accessibility.browsewithcaret",
+ yesNoToBoolean);
+ this._set("Software\\Microsoft\\Internet Explorer\\Settings",
+ "Always Use My Colors",
+ "browser.display.document_color_use",
+ v => (!v ? 0 : 2));
+ this._set("Software\\Microsoft\\Internet Explorer\\Settings",
+ "Always Use My Font Face",
+ "browser.display.use_document_fonts",
+ v => !v);
+ this._set(kMainKey,
+ "SmoothScroll",
+ "general.smoothScroll",
+ Boolean);
+ this._set("Software\\Microsoft\\Internet Explorer\\TabbedBrowsing\\",
+ "WarnOnClose",
+ "browser.tabs.warnOnClose",
+ Boolean);
+ this._set("Software\\Microsoft\\Internet Explorer\\TabbedBrowsing\\",
+ "OpenInForeground",
+ "browser.tabs.loadInBackground",
+ v => !v);
+
+ aCallback(true);
+ },
+
+ /**
+ * Reads a setting from the Registry and stores the converted result into
+ * the appropriate Firefox preference.
+ *
+ * @param aPath
+ * Registry path under HKCU.
+ * @param aKey
+ * Name of the key.
+ * @param aPref
+ * Firefox preference.
+ * @param [optional] aTransformFn
+ * Conversion function from the Registry format to the pref format.
+ */
+ _set: function S__set(aPath, aKey, aPref, aTransformFn) {
+ let value = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ aPath, aKey);
+ // Don't import settings that have never been flipped.
+ if (value === undefined)
+ return;
+
+ if (aTransformFn)
+ value = aTransformFn(value);
+
+ switch (typeof value) {
+ case "string":
+ Services.prefs.setCharPref(aPref, value);
+ break;
+ case "number":
+ Services.prefs.setIntPref(aPref, value);
+ break;
+ case "boolean":
+ Services.prefs.setBoolPref(aPref, value);
+ break;
+ default:
+ throw new Error("Unexpected value type: " + (typeof value));
+ }
+ }
+};
+
+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(),
+ new Settings(),
+ ];
+ // 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()];
+ resolve(Math.max.apply(Math, dates));
+ }));
+ return Promise.all(datePromises).then(dates => {
+ return new Date(Math.max.apply(Math, dates));
+ });
+};
+
+Object.defineProperty(IEProfileMigrator.prototype, "sourceHomePageURL", {
+ get: function IE_get_sourceHomePageURL() {
+ let defaultStartPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_LOCAL_MACHINE,
+ kMainKey, "Default_Page_URL");
+ let startPage = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ kMainKey, "Start Page");
+ // If the user didn't customize the Start Page, he is still on the default
+ // page, that may be considered the equivalent of our about:home. There's
+ // no reason to retain it, since it is heavily targeted to IE.
+ let homepage = startPage != defaultStartPage ? startPage : "";
+
+ // IE7+ supports secondary home pages located in a REG_MULTI_SZ key. These
+ // are in addition to the Start Page, and no empty entries are possible,
+ // thus a Start Page is always defined if any of these exists, though it
+ // may be the default one.
+ let secondaryPages = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ kMainKey, "Secondary Start Pages");
+ if (secondaryPages) {
+ if (homepage)
+ secondaryPages.unshift(homepage);
+ homepage = secondaryPages.join("|");
+ }
+
+ return homepage;
+ }
+});
+
+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}");
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([IEProfileMigrator]);