diff options
Diffstat (limited to 'application/palemoon/components/migration/IEProfileMigrator.js')
-rw-r--r-- | application/palemoon/components/migration/IEProfileMigrator.js | 521 |
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]); |