summaryrefslogtreecommitdiffstats
path: root/application/palemoon/components/migration/IEProfileMigrator.js
diff options
context:
space:
mode:
Diffstat (limited to 'application/palemoon/components/migration/IEProfileMigrator.js')
-rw-r--r--application/palemoon/components/migration/IEProfileMigrator.js521
1 files changed, 521 insertions, 0 deletions
diff --git a/application/palemoon/components/migration/IEProfileMigrator.js b/application/palemoon/components/migration/IEProfileMigrator.js
new file mode 100644
index 000000000..a46deae68
--- /dev/null
+++ b/application/palemoon/components/migration/IEProfileMigrator.js
@@ -0,0 +1,521 @@
+/* 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 kMainKey = "Software\\Microsoft\\Internet Explorer\\Main";
+const kRegMultiSz = 7;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource:///modules/MigrationUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
+ "resource://gre/modules/PlacesUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ctypes",
+ "resource://gre/modules/ctypes.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
+ "resource://gre/modules/WindowsRegistry.jsm");
+
+Cu.importGlobalProperties(["File"]);
+
+////////////////////////////////////////////////////////////////////////////////
+//// Helpers.
+
+let CtypesHelpers = {
+ _structs: {},
+ _functions: {},
+ _libs: {},
+
+ /**
+ * Must be invoked once before first use of any of the provided helpers.
+ */
+ initialize: function CH_initialize() {
+ const WORD = ctypes.uint16_t;
+ const DWORD = ctypes.uint32_t;
+ const BOOL = ctypes.int;
+
+ this._structs.SYSTEMTIME = new ctypes.StructType('SYSTEMTIME', [
+ {wYear: WORD},
+ {wMonth: WORD},
+ {wDayOfWeek: WORD},
+ {wDay: WORD},
+ {wHour: WORD},
+ {wMinute: WORD},
+ {wSecond: WORD},
+ {wMilliseconds: WORD}
+ ]);
+
+ this._structs.FILETIME = new ctypes.StructType('FILETIME', [
+ {dwLowDateTime: DWORD},
+ {dwHighDateTime: DWORD}
+ ]);
+
+ try {
+ this._libs.kernel32 = ctypes.open("Kernel32");
+ this._functions.FileTimeToSystemTime =
+ this._libs.kernel32.declare("FileTimeToSystemTime",
+ ctypes.default_abi,
+ BOOL,
+ this._structs.FILETIME.ptr,
+ this._structs.SYSTEMTIME.ptr);
+ } catch (ex) {
+ this.finalize();
+ }
+ },
+
+ /**
+ * Must be invoked once after last use of any of the provided helpers.
+ */
+ finalize: function CH_finalize() {
+ this._structs = {};
+ this._functions = {};
+ for each (let lib in this._libs) {
+ try {
+ lib.close();
+ } catch (ex) {}
+ }
+ this._libs = {};
+ },
+
+ /**
+ * Converts a FILETIME struct (2 DWORDS), to a SYSTEMTIME struct.
+ *
+ * @param aTimeHi
+ * Least significant DWORD.
+ * @param aTimeLo
+ * Most significant DWORD.
+ * @return a Date object representing the converted datetime.
+ */
+ fileTimeToDate: function CH_fileTimeToDate(aTimeHi, aTimeLo) {
+ let fileTime = this._structs.FILETIME();
+ fileTime.dwLowDateTime = aTimeLo;
+ fileTime.dwHighDateTime = aTimeHi;
+ let systemTime = this._structs.SYSTEMTIME();
+ let result = this._functions.FileTimeToSystemTime(fileTime.address(),
+ systemTime.address());
+ if (result == 0)
+ throw new Error(ctypes.winLastError);
+
+ return new Date(systemTime.wYear,
+ systemTime.wMonth - 1,
+ systemTime.wDay,
+ systemTime.wHour,
+ systemTime.wMinute,
+ systemTime.wSecond,
+ systemTime.wMilliseconds);
+ }
+};
+
+/**
+ * Checks whether an host is an IP (v4 or v6) address.
+ *
+ * @param aHost
+ * The host to check.
+ * @return whether aHost is an IP address.
+ */
+function hostIsIPAddress(aHost) {
+ try {
+ Services.eTLD.getBaseDomainFromHost(aHost);
+ } catch (e if e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS) {
+ return true;
+ } catch (e) {}
+ return false;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Resources
+
+function Bookmarks() {
+}
+
+Bookmarks.prototype = {
+ type: MigrationUtils.resourceTypes.BOOKMARKS,
+
+ get exists() !!this._favoritesFolder,
+
+ __favoritesFolder: null,
+ get _favoritesFolder() {
+ if (!this.__favoritesFolder) {
+ let favoritesFolder = Services.dirsvc.get("Favs", Ci.nsIFile);
+ if (favoritesFolder.exists() && favoritesFolder.isReadable())
+ this.__favoritesFolder = favoritesFolder;
+ }
+ return this.__favoritesFolder;
+ },
+
+ __toolbarFolderName: null,
+ get _toolbarFolderName() {
+ if (!this.__toolbarFolderName) {
+ // Retrieve the name of IE's favorites subfolder that holds the bookmarks
+ // in the toolbar. This was previously stored in the registry and changed
+ // in IE7 to always be called "Links".
+ let folderName = WindowsRegistry.readRegKey(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Microsoft\\Internet Explorer\\Toolbar",
+ "LinksFolderName");
+ this.__toolbarFolderName = folderName || "Links";
+ }
+ return this.__toolbarFolderName;
+ },
+
+ migrate: function B_migrate(aCallback) {
+ PlacesUtils.bookmarks.runInBatchMode({
+ runBatched: (function migrateBatched() {
+ // Import to the bookmarks menu.
+ let destFolderId = PlacesUtils.bookmarksMenuFolderId;
+ if (!MigrationUtils.isStartupMigration) {
+ destFolderId =
+ MigrationUtils.createImportedBookmarksFolder("IE", destFolderId);
+ }
+
+ this._migrateFolder(this._favoritesFolder, destFolderId);
+
+ aCallback(true);
+ }).bind(this)
+ }, null);
+ },
+
+ _migrateFolder: function B__migrateFolder(aSourceFolder, aDestFolderId) {
+ // TODO (bug 741993): the favorites order is stored in the Registry, at
+ // HCU\Software\Microsoft\Windows\CurrentVersion\Explorer\MenuOrder\Favorites
+ // Until we support it, bookmarks are imported in alphabetical order.
+ let entries = aSourceFolder.directoryEntries;
+ while (entries.hasMoreElements()) {
+ let entry = entries.getNext().QueryInterface(Ci.nsIFile);
+
+ // Make sure that entry.path == entry.target to not follow .lnk folder
+ // shortcuts which could lead to infinite cycles.
+ if (entry.isDirectory() && entry.path == entry.target) {
+ let destFolderId;
+ if (entry.leafName == this._toolbarFolderName &&
+ entry.parent.equals(this._favoritesFolder)) {
+ // Import to the bookmarks toolbar.
+ destFolderId = PlacesUtils.toolbarFolderId;
+ if (!MigrationUtils.isStartupMigration) {
+ destFolderId =
+ MigrationUtils.createImportedBookmarksFolder("IE", destFolderId);
+ }
+ }
+ else {
+ // Import to a new folder.
+ destFolderId =
+ PlacesUtils.bookmarks.createFolder(aDestFolderId, entry.leafName,
+ PlacesUtils.bookmarks.DEFAULT_INDEX);
+ }
+
+ if (entry.isReadable()) {
+ // Recursively import the folder.
+ this._migrateFolder(entry, destFolderId);
+ }
+ }
+ else {
+ // Strip the .url extension, to both check this is a valid link file,
+ // and get the associated title.
+ let matches = entry.leafName.match(/(.+)\.url$/i);
+ if (matches) {
+ let fileHandler = Cc["@mozilla.org/network/protocol;1?name=file"].
+ getService(Ci.nsIFileProtocolHandler);
+ let uri = fileHandler.readURLFile(entry);
+ let title = matches[1];
+
+ PlacesUtils.bookmarks.insertBookmark(aDestFolderId,
+ uri,
+ PlacesUtils.bookmarks.DEFAULT_INDEX,
+ title);
+ }
+ }
+ }
+ }
+};
+
+function History() {
+}
+
+History.prototype = {
+ type: MigrationUtils.resourceTypes.HISTORY,
+
+ get exists() true,
+
+ __typedURLs: null,
+ get _typedURLs() {
+ if (!this.__typedURLs) {
+ // The list of typed URLs is a sort of annotation stored in the registry.
+ // Currently, IE stores 25 entries and this value is not configurable,
+ // but we just keep reading up to the first non-existing entry to support
+ // possible future bumps of this limit.
+ this.__typedURLs = {};
+ let registry = Cc["@mozilla.org/windows-registry-key;1"].
+ createInstance(Ci.nsIWindowsRegKey);
+ try {
+ registry.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+ "Software\\Microsoft\\Internet Explorer\\TypedURLs",
+ Ci.nsIWindowsRegKey.ACCESS_READ);
+ for (let entry = 1; registry.hasValue("url" + entry); entry++) {
+ let url = registry.readStringValue("url" + entry);
+ this.__typedURLs[url] = true;
+ }
+ } catch (ex) {
+ } finally {
+ registry.close();
+ }
+ }
+ return this.__typedURLs;
+ },
+
+ migrate: function H_migrate(aCallback) {
+ let places = [];
+ 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 = this._typedURLs[uri.spec] ?
+ Ci.nsINavHistoryService.TRANSITION_TYPED :
+ Ci.nsINavHistoryService.TRANSITION_LINK;
+ let lastVisitTime = entry.get("time");
+
+ 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;
+ }
+
+ PlacesUtils.asyncHistory.updatePlaces(places, {
+ _success: false,
+ handleResult: function() {
+ // Importing any entry is considered a successful import.
+ this._success = true;
+ },
+ handleError: function() {},
+ handleCompletion: function() {
+ aCallback(this._success);
+ }
+ });
+ }
+};
+
+function Cookies() {
+}
+
+Cookies.prototype = {
+ type: MigrationUtils.resourceTypes.COOKIES,
+
+ get exists() !!this._cookiesFolder,
+
+ __cookiesFolder: null,
+ get _cookiesFolder() {
+ // Cookies are stored in txt files, in a Cookies folder whose path varies
+ // across the different OS versions. CookD takes care of most of these
+ // cases, though, in Windows Vista/7, UAC makes a difference.
+ // If UAC is enabled, the most common destination is CookD/Low. Though,
+ // if the user runs the application in administrator mode or disables UAC,
+ // cookies are stored in the original CookD destination. Cause running the
+ // browser in administrator mode is unsafe and discouraged, we just care
+ // about the UAC state.
+ if (!this.__cookiesFolder) {
+ let cookiesFolder = Services.dirsvc.get("CookD", Ci.nsIFile);
+ if (cookiesFolder.exists() && cookiesFolder.isReadable()) {
+ // Check if UAC is enabled.
+ if (Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).userCanElevate) {
+ cookiesFolder.append("Low");
+ }
+ this.__cookiesFolder = cookiesFolder;
+ }
+ }
+ return this.__cookiesFolder;
+ },
+
+ migrate: function C_migrate(aCallback) {
+ CtypesHelpers.initialize();
+
+ let cookiesGenerator = (function genCookie() {
+ let success = false;
+ let entries = this._cookiesFolder.directoryEntries;
+ while (entries.hasMoreElements()) {
+ let entry = entries.getNext().QueryInterface(Ci.nsIFile);
+ // Skip eventual bogus entries.
+ if (!entry.isFile() || !/\.txt$/.test(entry.leafName))
+ continue;
+
+ this._readCookieFile(entry, function(aSuccess) {
+ // Importing even a single cookie file is considered a success.
+ if (aSuccess)
+ success = true;
+ try {
+ cookiesGenerator.next();
+ } catch (ex) {}
+ });
+
+ yield;
+ }
+
+ CtypesHelpers.finalize();
+
+ aCallback(success);
+ }).apply(this);
+ cookiesGenerator.next();
+ },
+
+ _readCookieFile: function C__readCookieFile(aFile, aCallback) {
+ let fileReader = Cc["@mozilla.org/files/filereader;1"].
+ createInstance(Ci.nsIDOMFileReader);
+ fileReader.addEventListener("loadend", (function onLoadEnd() {
+ fileReader.removeEventListener("loadend", onLoadEnd, false);
+
+ if (fileReader.readyState != fileReader.DONE) {
+ Cu.reportError("Could not read cookie contents: " + fileReader.error);
+ aCallback(false);
+ return;
+ }
+
+ let success = true;
+ try {
+ this._parseCookieBuffer(fileReader.result);
+ } catch (ex) {
+ Components.utils.reportError("Unable to migrate cookie: " + ex);
+ success = false;
+ } finally {
+ aCallback(success);
+ }
+ }).bind(this), false);
+ fileReader.readAsText(new File(aFile));
+ },
+
+ /**
+ * Parses a cookie file buffer and returns an array of the contained cookies.
+ *
+ * The cookie file format is a newline-separated-values with a "*" used as
+ * delimeter between multiple records.
+ * Each cookie has the following fields:
+ * - name
+ * - value
+ * - host/path
+ * - flags
+ * - Expiration time most significant integer
+ * - Expiration time least significant integer
+ * - Creation time most significant integer
+ * - Creation time least significant integer
+ * - Record delimiter "*"
+ *
+ * @note All the times are in FILETIME format.
+ */
+ _parseCookieBuffer: function C__parseCookieBuffer(aTextBuffer) {
+ // Note the last record is an empty string.
+ let records = [r for each (r in aTextBuffer.split("*\n")) if (r)];
+ for (let record of records) {
+ let [name, value, hostpath, flags,
+ expireTimeLo, expireTimeHi] = record.split("\n");
+
+ // IE stores deleted cookies with a zero-length value, skip them.
+ if (value.length == 0)
+ continue;
+
+ let hostLen = hostpath.indexOf("/");
+ let host = hostpath.substr(0, hostLen);
+ let path = hostpath.substr(hostLen);
+
+ // For a non-null domain, assume it's what Mozilla considers
+ // a domain cookie. See bug 222343.
+ if (host.length > 0) {
+ // Fist delete any possible extant matching host cookie.
+ Services.cookies.remove(host, name, path, false);
+ // Now make it a domain cookie.
+ if (host[0] != "." && !hostIsIPAddress(host))
+ host = "." + host;
+ }
+
+ let expireTime = CtypesHelpers.fileTimeToDate(Number(expireTimeHi),
+ Number(expireTimeLo));
+ Services.cookies.add(host,
+ path,
+ name,
+ value,
+ Number(flags) & 0x1, // secure
+ false, // httpOnly
+ false, // session
+ expireTime);
+ }
+ }
+};
+
+
+////////////////////////////////////////////////////////////////////////////////
+//// Migrator
+
+function IEProfileMigrator()
+{
+}
+
+IEProfileMigrator.prototype = Object.create(MigratorPrototype);
+
+IEProfileMigrator.prototype.getResources = function IE_getResources() {
+ let resources = [
+ new Bookmarks()
+ , new History()
+ , new Cookies()
+ ];
+ return [r for each (r in resources) if (r.exists)];
+};
+
+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]);