summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/migration
diff options
context:
space:
mode:
Diffstat (limited to 'comm/mail/components/migration')
-rw-r--r--comm/mail/components/migration/content/migration.js464
-rw-r--r--comm/mail/components/migration/content/migration.xhtml89
-rw-r--r--comm/mail/components/migration/jar.mn7
-rw-r--r--comm/mail/components/migration/moz.build11
-rw-r--r--comm/mail/components/migration/public/moz.build12
-rw-r--r--comm/mail/components/migration/public/nsIMailProfileMigrator.idl70
-rw-r--r--comm/mail/components/migration/src/ThunderbirdProfileMigrator.jsm869
-rw-r--r--comm/mail/components/migration/src/components.conf38
-rw-r--r--comm/mail/components/migration/src/moz.build32
-rw-r--r--comm/mail/components/migration/src/nsMailProfileMigratorUtils.cpp86
-rw-r--r--comm/mail/components/migration/src/nsMailProfileMigratorUtils.h54
-rw-r--r--comm/mail/components/migration/src/nsNetscapeProfileMigratorBase.cpp371
-rw-r--r--comm/mail/components/migration/src/nsNetscapeProfileMigratorBase.h121
-rw-r--r--comm/mail/components/migration/src/nsOutlookProfileMigrator.cpp135
-rw-r--r--comm/mail/components/migration/src/nsOutlookProfileMigrator.h30
-rw-r--r--comm/mail/components/migration/src/nsProfileMigrator.cpp121
-rw-r--r--comm/mail/components/migration/src/nsProfileMigrator.h36
-rw-r--r--comm/mail/components/migration/src/nsProfileMigratorBase.cpp173
-rw-r--r--comm/mail/components/migration/src/nsProfileMigratorBase.h40
-rw-r--r--comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.cpp1175
-rw-r--r--comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.h84
21 files changed, 4018 insertions, 0 deletions
diff --git a/comm/mail/components/migration/content/migration.js b/comm/mail/components/migration/content/migration.js
new file mode 100644
index 0000000000..10e43600d1
--- /dev/null
+++ b/comm/mail/components/migration/content/migration.js
@@ -0,0 +1,464 @@
+/* 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/. */
+
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+
+var kIMig = Ci.nsIMailProfileMigrator;
+var kIPStartup = Ci.nsIProfileStartup;
+var kProfileMigratorContractIDPrefix =
+ "@mozilla.org/profile/migrator;1?app=mail&type=";
+
+var MigrationWizard = {
+ _source: "", // Source Profile Migrator ContractID suffix
+ _itemsFlags: kIMig.ALL, // Selected Import Data Sources (16-bit bitfield)
+ _selectedProfile: null, // Selected Profile name to import from
+ _wiz: null,
+ _migrator: null,
+ _autoMigrate: null,
+
+ init() {
+ document
+ .querySelector("wizard")
+ .addEventListener("wizardback", this.onBack.bind(this));
+ document
+ .querySelector("wizard")
+ .addEventListener("wizardcancel", this.onCancel.bind(this));
+
+ let importSourcePage = document.getElementById("importSource");
+ importSourcePage.addEventListener(
+ "pageadvanced",
+ this.onImportSourcePageAdvanced.bind(this)
+ );
+
+ let selectProfilePage = document.getElementById("selectProfile");
+ selectProfilePage.addEventListener(
+ "pageshow",
+ this.onSelectProfilePageShow.bind(this)
+ );
+ selectProfilePage.addEventListener(
+ "pagerewound",
+ this.onSelectProfilePageRewound.bind(this)
+ );
+ selectProfilePage.addEventListener(
+ "pageadvanced",
+ this.onSelectProfilePageAdvanced.bind(this)
+ );
+
+ let importItemsPage = document.getElementById("importItems");
+ importItemsPage.addEventListener(
+ "pageshow",
+ this.onImportItemsPageShow.bind(this)
+ );
+ importItemsPage.addEventListener(
+ "pagerewound",
+ this.onImportItemsPageAdvanced.bind(this)
+ );
+ importItemsPage.addEventListener(
+ "pageadvanced",
+ this.onImportItemsPageAdvanced.bind(this)
+ );
+
+ let migratingPage = document.getElementById("migrating");
+ migratingPage.addEventListener(
+ "pageshow",
+ this.onMigratingPageShow.bind(this)
+ );
+
+ let donePage = document.getElementById("done");
+ donePage.addEventListener("pageshow", this.onDonePageShow.bind(this));
+
+ let failedPage = document.getElementById("failed");
+ failedPage.addEventListener("pageshow", () => (this._failed = true));
+ failedPage.addEventListener("pagerewound", () => (this._failed = false));
+
+ Services.obs.addObserver(this, "Migration:Started");
+ Services.obs.addObserver(this, "Migration:ItemBeforeMigrate");
+ Services.obs.addObserver(this, "Migration:ItemAfterMigrate");
+ Services.obs.addObserver(this, "Migration:Ended");
+ Services.obs.addObserver(this, "Migration:Progress");
+
+ this._wiz = document.querySelector("wizard");
+
+ if ("arguments" in window && !window.arguments[3]) {
+ this._source = window.arguments[0];
+ this._migrator = window.arguments[1]
+ ? window.arguments[1].QueryInterface(kIMig)
+ : null;
+ this._autoMigrate = window.arguments[2].QueryInterface(kIPStartup);
+
+ // Show the "nothing" option in the automigrate case to provide an
+ // easily identifiable way to avoid migration and create a new profile.
+ var nothing = document.getElementById("nothing");
+ nothing.hidden = false;
+ }
+
+ this.onImportSourcePageShow();
+
+ // Behavior alert! If we were given a migrator already, then we are going to perform migration
+ // with that migrator, skip the wizard screen where we show all of the migration sources and
+ // jump right into migration.
+ if (this._migrator) {
+ if (this._migrator.sourceHasMultipleProfiles) {
+ this._wiz.goTo("selectProfile");
+ } else {
+ var sourceProfiles = this._migrator.sourceProfiles;
+ this._selectedProfile = sourceProfiles[0];
+ this._wiz.goTo("migrating");
+ }
+ }
+ },
+
+ uninit() {
+ Services.obs.removeObserver(this, "Migration:Started");
+ Services.obs.removeObserver(this, "Migration:ItemBeforeMigrate");
+ Services.obs.removeObserver(this, "Migration:ItemAfterMigrate");
+ Services.obs.removeObserver(this, "Migration:Ended");
+ Services.obs.removeObserver(this, "Migration:Progress");
+
+ // Imported accounts don't show up without restarting.
+ if (this._wiz.onLastPage && !this._failed) {
+ MailUtils.restartApplication();
+ }
+ },
+
+ // 1 - Import Source
+ onImportSourcePageShow() {
+ this._wiz.canRewind = false;
+ this._wiz.canAdvance = false;
+
+ // Figure out what source apps are are available to import from:
+ var group = document.getElementById("importSourceGroup");
+ for (let childNode of group.children) {
+ let suffix = childNode.id;
+ if (suffix != "nothing") {
+ var contractID =
+ kProfileMigratorContractIDPrefix + suffix.split("-")[0];
+ var migrator = Cc[contractID].createInstance(kIMig);
+ if (!migrator.sourceExists) {
+ childNode.hidden = true;
+ if (this._source == suffix) {
+ this._source = null;
+ }
+ }
+ }
+ }
+
+ var firstNonDisabled = null;
+ for (let childNode of group.children) {
+ if (!childNode.hidden && !childNode.disabled) {
+ firstNonDisabled = childNode;
+ break;
+ }
+ }
+ group.selectedItem =
+ this._source == ""
+ ? firstNonDisabled
+ : document.getElementById(this._source);
+
+ if (firstNonDisabled) {
+ this._wiz.canAdvance = true;
+ document.getElementById("importSourceFound").hidden = false;
+ return;
+ }
+ // If no usable import module was found, inform user and enable back button.
+ document.getElementById("importSourceNotFound").hidden = false;
+ this._wiz.canRewind = true;
+ this._wiz.getButton("back").setAttribute("hidden", "false");
+ },
+
+ onImportSourcePageAdvanced() {
+ var newSource =
+ document.getElementById("importSourceGroup").selectedItem.id;
+
+ if (newSource == "nothing") {
+ document.querySelector("wizard").cancel();
+ return;
+ }
+
+ if (!this._migrator || newSource != this._source) {
+ // Create the migrator for the selected source.
+ var contractID =
+ kProfileMigratorContractIDPrefix + newSource.split("-")[0];
+ this._migrator = Cc[contractID].createInstance(kIMig);
+
+ this._itemsFlags = kIMig.ALL;
+ this._selectedProfile = null;
+ }
+
+ this._source = newSource;
+
+ // check for more than one source profile
+ if (this._migrator.sourceHasMultipleProfiles) {
+ this._wiz.currentPage.next = "selectProfile";
+ } else {
+ this._wiz.currentPage.next = "migrating";
+ var sourceProfiles = this._migrator.sourceProfiles;
+ if (sourceProfiles && sourceProfiles.length == 1) {
+ this._selectedProfile = sourceProfiles[0];
+ } else {
+ this._selectedProfile = "";
+ }
+ }
+ },
+
+ // 2 - [Profile Selection]
+ onSelectProfilePageShow() {
+ // Disabling this for now, since we ask about import sources in automigration
+ // too and don't want to disable the back button
+ // if (this._autoMigrate)
+ // document.querySelector("wizard").getButton("back").disabled = true;
+
+ var profiles = document.getElementById("profiles");
+ while (profiles.hasChildNodes()) {
+ profiles.lastChild.remove();
+ }
+
+ if (!this._migrator) {
+ return;
+ }
+ var sourceProfiles = this._migrator.sourceProfiles;
+ var count = sourceProfiles.length;
+ for (var i = 0; i < count; ++i) {
+ var item = document.createXULElement("radio");
+ item.id = sourceProfiles[i];
+ item.setAttribute("label", item.id);
+ profiles.appendChild(item);
+ }
+
+ profiles.selectedItem = this._selectedProfile
+ ? document.getElementById(this._selectedProfile)
+ : profiles.firstElementChild;
+ },
+
+ onSelectProfilePageRewound() {
+ var profiles = document.getElementById("profiles");
+ this._selectedProfile = profiles.selectedItem.id;
+ },
+
+ onSelectProfilePageAdvanced() {
+ var profiles = document.getElementById("profiles");
+ this._selectedProfile = profiles.selectedItem.id;
+
+ // If we're automigrating, don't show the item selection page, just grab everything.
+ if (this._autoMigrate) {
+ this._wiz.currentPage.next = "migrating";
+ }
+ },
+
+ // 3 - ImportItems
+ onImportItemsPageShow() {
+ var dataSources = document.getElementById("dataSources");
+ while (dataSources.hasChildNodes()) {
+ dataSources.lastChild.remove();
+ }
+
+ var bundle = document.getElementById("bundle");
+
+ var items = this._migrator.getMigrateData(
+ this._selectedProfile,
+ this._autoMigrate
+ );
+ for (var i = 0; i < 16; ++i) {
+ var itemID = (items >> i) & 0x1 ? Math.pow(2, i) : 0;
+ if (itemID > 0) {
+ var checkbox = document.createXULElement("checkbox");
+ checkbox.id = itemID;
+ checkbox.setAttribute(
+ "label",
+ bundle.getString(itemID + "_" + this._source.split("-")[0])
+ );
+ dataSources.appendChild(checkbox);
+ if (!this._itemsFlags || this._itemsFlags & itemID) {
+ checkbox.checked = true;
+ }
+ }
+ }
+ },
+
+ onImportItemsPageAdvanced() {
+ var dataSources = document.getElementById("dataSources");
+ this._itemsFlags = 0;
+ for (var i = 0; i < dataSources.children.length; ++i) {
+ var checkbox = dataSources.children[i];
+ if (checkbox.localName == "checkbox" && checkbox.checked) {
+ this._itemsFlags |= parseInt(checkbox.id);
+ }
+ }
+ },
+
+ onImportItemCommand(aEvent) {
+ var items = document.getElementById("dataSources");
+ var checkboxes = items.getElementsByTagName("checkbox");
+
+ var oneChecked = false;
+ for (var i = 0; i < checkboxes.length; ++i) {
+ if (checkboxes[i].checked) {
+ oneChecked = true;
+ break;
+ }
+ }
+
+ this._wiz.canAdvance = oneChecked;
+ },
+
+ // 4 - Migrating
+ async onMigratingPageShow() {
+ this._wiz.getButton("cancel").disabled = true;
+ this._wiz.canRewind = false;
+ this._wiz.canAdvance = false;
+
+ // When automigrating or migrating all, show all of the data that can
+ // be received from this source.
+ if (this._autoMigrate || this._itemsFlags == kIMig.ALL) {
+ this._itemsFlags = this._migrator.getMigrateData(
+ this._selectedProfile,
+ this._autoMigrate
+ );
+ }
+
+ this._listItems("migratingItems");
+ try {
+ await this.onMigratingMigrate();
+ } catch (e) {
+ switch (e.message) {
+ case "file-picker-cancelled":
+ this._wiz.canRewind = true;
+ this._wiz.rewind();
+ this._wiz.canAdvance = true;
+ return;
+ case "zip-file-too-big":
+ this._wiz.canRewind = true;
+ this._wiz.rewind();
+ this._wiz.canAdvance = true;
+ let [zipFileTooBigTitle, zipFileTooBigMessage] =
+ await document.l10n.formatValues([
+ "zip-file-too-big-title",
+ "zip-file-too-big-message",
+ ]);
+ Services.prompt.alert(
+ window,
+ zipFileTooBigTitle,
+ zipFileTooBigMessage
+ );
+ document.getElementById("importSourceGroup").selectedItem =
+ document.getElementById("thunderbird-dir");
+ return;
+ default:
+ document.getElementById("failed-message-default").hidden = e.message;
+ document.getElementById("failed-message").hidden = !e.message;
+ document.getElementById("failed-message").textContent =
+ e.message || "";
+ this._wiz.canAdvance = true;
+ this._wiz.advance("failed");
+ throw e;
+ }
+ }
+ },
+
+ async onMigratingMigrate(aOuter) {
+ let [source, type] = this._source.split("-");
+ if (source == "thunderbird") {
+ // Ask user for the profile directory location.
+ await this._migrator.wrappedJSObject.getProfileDir(window, type);
+ await this._migrator.wrappedJSObject.asyncMigrate();
+ return;
+ }
+ this._migrator.migrate(
+ this._itemsFlags,
+ this._autoMigrate,
+ this._selectedProfile
+ );
+ },
+
+ _listItems(aID) {
+ var items = document.getElementById(aID);
+ while (items.hasChildNodes()) {
+ items.lastChild.remove();
+ }
+
+ var bundle = document.getElementById("bundle");
+ for (var i = 0; i < 16; ++i) {
+ var itemID = (this._itemsFlags >> i) & 0x1 ? Math.pow(2, i) : 0;
+ if (itemID > 0) {
+ var label = document.createXULElement("label");
+ label.id = itemID + "_migrated";
+ try {
+ label.setAttribute(
+ "value",
+ "- " + bundle.getString(itemID + "_" + this._source.split("-")[0])
+ );
+ items.appendChild(label);
+ } catch (e) {
+ // if the block above throws, we've enumerated all the import data types we
+ // currently support and are now just wasting time, break.
+ break;
+ }
+ }
+ }
+ },
+
+ observe(aSubject, aTopic, aData) {
+ switch (aTopic) {
+ case "Migration:Started":
+ dump("*** started\n");
+ break;
+ case "Migration:ItemBeforeMigrate": {
+ dump("*** before " + aData + "\n");
+ let label = document.getElementById(aData + "_migrated");
+ if (label) {
+ label.setAttribute("style", "font-weight: bold");
+ }
+ break;
+ }
+ case "Migration:ItemAfterMigrate": {
+ dump("*** after " + aData + "\n");
+ let label = document.getElementById(aData + "_migrated");
+ if (label) {
+ label.removeAttribute("style");
+ }
+ break;
+ }
+ case "Migration:Ended":
+ dump("*** done\n");
+ if (this._autoMigrate) {
+ // We're done now.
+ this._wiz.canAdvance = true;
+ this._wiz.advance();
+ setTimeout(window.close, 5000);
+ } else {
+ this._wiz.canAdvance = true;
+ var nextButton = this._wiz.getButton("next");
+ nextButton.click();
+ }
+ break;
+ case "Migration:Progress":
+ document.getElementById("progressBar").value = aData;
+ break;
+ }
+ },
+
+ onDonePageShow() {
+ this._wiz.getButton("cancel").disabled = true;
+ this._wiz.canRewind = false;
+ this._listItems("doneItems");
+ },
+
+ onBack(event) {
+ this._wiz.goTo("importSource");
+ this._wiz.canRewind = false;
+ event.preventDefault();
+ },
+
+ onCancel() {
+ // If .closeMigration is false, the user clicked Back button,
+ // then do not change its value.
+ if (
+ window.arguments[3] &&
+ "closeMigration" in window.arguments[3] &&
+ window.arguments[3].closeMigration !== false
+ ) {
+ window.arguments[3].closeMigration = true;
+ }
+ },
+};
diff --git a/comm/mail/components/migration/content/migration.xhtml b/comm/mail/components/migration/content/migration.xhtml
new file mode 100644
index 0000000000..4171d02f63
--- /dev/null
+++ b/comm/mail/components/migration/content/migration.xhtml
@@ -0,0 +1,89 @@
+<?xml version="1.0"?>
+# 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/.
+
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+
+<!DOCTYPE window SYSTEM "chrome://messenger/locale/migration/migration.dtd" >
+
+<window id="migrationWizard"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="&migrationWizard.title;"
+ onload="MigrationWizard.init()"
+ onunload="MigrationWizard.uninit()"
+ style="width: 40em;"
+ branded="true"
+ buttons="accept,cancel">
+ <linkset>
+ <html:link rel="localization" href="toolkit/global/wizard.ftl"/>
+ <html:link rel="localization" href="messenger/importDialog.ftl"/>
+ </linkset>
+
+ <script src="chrome://messenger/content/migration/migration.js"/>
+
+ <stringbundle id="bundle" src="chrome://messenger/locale/migration/migration.properties"/>
+
+ <wizard>
+ <wizardpage id="importSource" pageid="importSource" next="selectProfile"
+ label="&importSource.title;">
+ <vbox id="importSourceFound" hidden="true">
+#ifdef XP_WIN
+ <label control="importSourceGroup">&importFromWin.label;</label>
+#else
+ <label control="importSourceGroup">&importFromNonWin.label;</label>
+#endif
+ <radiogroup id="importSourceGroup">
+ <radio id="thunderbird-zip" data-l10n-id="import-from-thunderbird-zip"/>
+ <radio id="thunderbird-dir" data-l10n-id="import-from-thunderbird-dir"/>
+ <radio id="seamonkey" label="&importFromSeamonkey3.label;"
+ accesskey="&importFromSeamonkey3.accesskey;"/>
+#ifdef XP_WIN
+ <radio id="outlook" label="&importFromOutlook.label;"
+ accesskey="&importFromOutlook.accesskey;"/>
+#endif
+ <radio id="nothing" label="&importFromNothing.label;"
+ accesskey="&importFromNothing.accesskey;" hidden="true"/>
+ </radiogroup>
+ </vbox>
+ <label id="importSourceNotFound" hidden="true">&importSourceNotFound.label;</label>
+ </wizardpage>
+
+ <wizardpage id="selectProfile" pageid="selectProfile" label="&selectProfile.title;"
+ next="importItems">
+ <label control="profiles">&selectProfile.label;</label>
+ <radiogroup id="profiles" align="start"/>
+ </wizardpage>
+
+ <wizardpage id="importItems" pageid="importItems" label="&importItems.title;"
+ next="migrating"
+ oncommand="MigrationWizard.onImportItemCommand();">
+ <description>&importItems.label;</description>
+ <vbox id="dataSources"
+ style="overflow: auto; appearance: auto; -moz-default-appearance: listbox"
+ align="start" flex="1"/>
+ </wizardpage>
+
+ <wizardpage id="migrating" pageid="migrating" label="&migrating.title;"
+ next="done">
+ <description>&migrating.label;</description>
+ <separator class="thin"/>
+ <vbox id="migratingItems" class="indent" style="overflow: auto;" flex="1" align="start"/>
+ <separator class="thin"/>
+ <html:progress class="progressmeter-statusbar" id="progressBar" flex="1" value="0" max="100"/>
+ </wizardpage>
+
+ <wizardpage id="done" pageid="done" label="&done.title;">
+ <description>&done.label;</description>
+ <separator class="thin"/>
+ <vbox id="doneItems" class="indent" style="overflow: auto;" align="start"/>
+ </wizardpage>
+
+ <wizardpage id="failed" pageid="failed" data-l10n-id="wizardpage-failed">
+ <description id="failed-message-default"
+ data-l10n-id="wizardpage-failed-message"></description>
+ <description id="failed-message"></description>
+ </wizardpage>
+ </wizard>
+</window>
diff --git a/comm/mail/components/migration/jar.mn b/comm/mail/components/migration/jar.mn
new file mode 100644
index 0000000000..db93c8847e
--- /dev/null
+++ b/comm/mail/components/migration/jar.mn
@@ -0,0 +1,7 @@
+# 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/.
+
+messenger.jar:
+* content/messenger/migration/migration.xhtml (content/migration.xhtml)
+ content/messenger/migration/migration.js (content/migration.js)
diff --git a/comm/mail/components/migration/moz.build b/comm/mail/components/migration/moz.build
new file mode 100644
index 0000000000..110c0645dd
--- /dev/null
+++ b/comm/mail/components/migration/moz.build
@@ -0,0 +1,11 @@
+# vim: set filetype=python:
+# 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/.
+
+DIRS += [
+ "public",
+ "src",
+]
+
+JAR_MANIFESTS += ["jar.mn"]
diff --git a/comm/mail/components/migration/public/moz.build b/comm/mail/components/migration/public/moz.build
new file mode 100644
index 0000000000..20c4c0e5c5
--- /dev/null
+++ b/comm/mail/components/migration/public/moz.build
@@ -0,0 +1,12 @@
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_SOURCES += [
+ "nsIMailProfileMigrator.idl",
+]
+
+XPIDL_MODULE = "mailprofilemigration"
+
+EXPORTS += []
diff --git a/comm/mail/components/migration/public/nsIMailProfileMigrator.idl b/comm/mail/components/migration/public/nsIMailProfileMigrator.idl
new file mode 100644
index 0000000000..398857b459
--- /dev/null
+++ b/comm/mail/components/migration/public/nsIMailProfileMigrator.idl
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIProfileStartup;
+
+[scriptable, uuid(fca38a7a-c43f-4b28-adbd-61e5cc942508)]
+interface nsIMailProfileMigrator : nsISupports
+{
+ /**
+ * profile items to migrate. use with migrate().
+ */
+ const unsigned short ALL = 0x0000;
+ const unsigned short SETTINGS = 0x0001;
+ const unsigned short ACCOUNT_SETTINGS = 0x0002;
+ const unsigned short ADDRESSBOOK_DATA = 0x0004;
+ const unsigned short JUNKTRAINING = 0x0008;
+ const unsigned short PASSWORDS = 0x0010;
+ const unsigned short OTHERDATA = 0x0020;
+ const unsigned short NEWSDATA = 0x0040;
+ const unsigned short MAILDATA = 0x0080;
+ const unsigned short FILTERS = 0x0100;
+
+ /**
+ * Copy user profile information to the current active profile.
+ * @param aItems list of data items to migrate. see above for values.
+ * @param aReplace replace or append current data where applicable.
+ * @param aProfile profile to migrate from, if there is more than one.
+ */
+ void migrate(in unsigned short aItems, in nsIProfileStartup aStartup, in wstring aProfile);
+
+ /**
+ * A bit field containing profile items that this migrator
+ * offers for import.
+ * @param aProfile the profile that we are looking for available data
+ * to import
+ * @param aStarting "true" if the profile is not currently being used.
+ * @returns bit field containing profile items (see above)
+ */
+ unsigned short getMigrateData(in wstring aProfile, in boolean aDoingStartup);
+
+ /**
+ * Whether or not there is any data that can be imported from this
+ * mailer (i.e. whether or not it is installed, and there exists
+ * a user profile)
+ */
+ readonly attribute boolean sourceExists;
+
+ /**
+ * Whether or not the import source implementing this interface
+ * has multiple user profiles configured.
+ */
+ readonly attribute boolean sourceHasMultipleProfiles;
+
+ /**
+ * An array of available profile names. If the import source does not support
+ * profiles, this attribute is empty.
+ */
+ readonly attribute Array<AString> sourceProfiles;
+
+ /**
+ * An array of available profile locations. If the import source does not
+ * support profiles, this attribute is empty.
+ */
+ readonly attribute Array<nsIFile> sourceProfileLocations;
+};
diff --git a/comm/mail/components/migration/src/ThunderbirdProfileMigrator.jsm b/comm/mail/components/migration/src/ThunderbirdProfileMigrator.jsm
new file mode 100644
index 0000000000..6827ea5523
--- /dev/null
+++ b/comm/mail/components/migration/src/ThunderbirdProfileMigrator.jsm
@@ -0,0 +1,869 @@
+/* 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/. */
+
+const EXPORTED_SYMBOLS = ["ThunderbirdProfileMigrator"];
+
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+const lazy = {};
+XPCOMUtils.defineLazyGetter(
+ lazy,
+ "l10n",
+ () => new Localization(["messenger/importDialog.ftl"])
+);
+
+// Pref branches that need special handling.
+const MAIL_IDENTITY = "mail.identity.";
+const MAIL_SERVER = "mail.server.";
+const MAIL_ACCOUNT = "mail.account.";
+const IM_ACCOUNT = "messenger.account.";
+const SMTP_SERVER = "mail.smtpserver.";
+const ADDRESS_BOOK = "ldap_2.servers.";
+const LDAP_AUTO_COMPLETE = "ldap_2.autoComplete.";
+const CALENDAR = "calendar.registry.";
+
+// Prefs (branches) that we do not want to copy directly.
+const IGNORE_PREFS = [
+ "app.update.",
+ "browser.",
+ "calendar.list.sortOrder",
+ "calendar.timezone",
+ "devtools.",
+ "extensions.",
+ "mail.accountmanager.",
+ "mail.cloud_files.accounts.",
+ "mail.newsrc_root",
+ "mail.root.",
+ "mail.smtpservers",
+ "messenger.accounts",
+ "print.",
+ "services.",
+ "toolkit.telemetry.",
+];
+
+// When importing from a zip file, ignoring these folders.
+const IGNORE_DIRS = [
+ "chrome_debugger_profile",
+ "crashes",
+ "datareporting",
+ "extensions",
+ "extension-store",
+ "logs",
+ "minidumps",
+ "saved-telemetry-pings",
+ "security_state",
+ "storage",
+ "xulstore",
+];
+
+/**
+ * A pref is represented as [type, name, value].
+ *
+ * @typedef {["Bool"|"Char"|"Int", string, number|string|boolean]} PrefItem
+ *
+ * A map from source smtp server key to target smtp server key.
+ * @typedef {Map<string, string>} SmtpServerKeyMap
+ *
+ * A map from source identity key to target identity key.
+ * @typedef {Map<string, string>} IdentityKeyMap
+ *
+ * A map from source IM account key to target IM account key.
+ * @typedef {Map<string, string>} IMAccountKeyMap
+ *
+ * A map from source incoming server key to target incoming server key.
+ * @typedef {Map<string, string>} IncomingServerKeyMap
+ */
+
+/**
+ * A class to support importing from a Thunderbird profile directory.
+ *
+ * @implements {nsIMailProfileMigrator}
+ */
+class ThunderbirdProfileMigrator {
+ QueryInterface = ChromeUtils.generateQI(["nsIMailProfileMigrator"]);
+
+ get wrappedJSObject() {
+ return this;
+ }
+
+ _logger = console.createInstance({
+ prefix: "mail.import",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.import.loglevel",
+ });
+
+ get sourceExists() {
+ return true;
+ }
+
+ get sourceProfiles() {
+ return this._sourceProfileDir ? [this._sourceProfileDir.path] : [];
+ }
+
+ get sourceHasMultipleProfiles() {
+ return false;
+ }
+
+ /**
+ * Other profile migrators try known install directories to get a source
+ * profile dir. But in this class, we always ask user for the profile
+ * location.
+ */
+ async getProfileDir(window, type) {
+ let filePicker = Cc["@mozilla.org/filepicker;1"].createInstance(
+ Ci.nsIFilePicker
+ );
+ let [filePickerTitleZip, filePickerTitleDir] = await lazy.l10n.formatValues(
+ ["import-select-profile-zip", "import-select-profile-dir"]
+ );
+ switch (type) {
+ case "zip":
+ filePicker.init(window, filePickerTitleZip, filePicker.modeOpen);
+ filePicker.appendFilter("", "*.zip");
+ break;
+ case "dir":
+ filePicker.init(window, filePickerTitleDir, filePicker.modeGetFolder);
+ break;
+ default:
+ throw new Error(`Unsupported type: ${type}`);
+ }
+ let selectedFile = await new Promise((resolve, reject) => {
+ filePicker.open(rv => {
+ if (rv != Ci.nsIFilePicker.returnOK || !filePicker.file) {
+ reject(new Error("file-picker-cancelled"));
+ return;
+ }
+ resolve(filePicker.file);
+ });
+ });
+ if (selectedFile.isDirectory()) {
+ this._sourceProfileDir = selectedFile;
+ } else {
+ if (selectedFile.fileSize > 2147483647) {
+ // nsIZipReader only supports zip file less than 2GB.
+ // throw new Error(zipFileTooBigMessage);
+ throw new Error("zip-file-too-big");
+ }
+ this._importingFromZip = true;
+ // Extract the zip file to a tmp dir.
+ let targetDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ targetDir.append("tmp-profile");
+ targetDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ let ZipReader = Components.Constructor(
+ "@mozilla.org/libjar/zip-reader;1",
+ "nsIZipReader",
+ "open"
+ );
+ let zip = ZipReader(filePicker.file);
+ for (let entry of zip.findEntries(null)) {
+ let parts = entry.split("/");
+ if (IGNORE_DIRS.includes(parts[1]) || entry.endsWith("/")) {
+ continue;
+ }
+ // Folders can not be unzipped recursively, have to iterate and
+ // extract all file entries one by one.
+ let target = targetDir.clone();
+ for (let part of parts.slice(1)) {
+ // Drop the root folder name in the zip file.
+ target.append(part);
+ }
+ if (!target.parent.exists()) {
+ target.parent.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ }
+ try {
+ this._logger.debug(`Extracting ${entry} to ${target.path}`);
+ zip.extract(entry, target);
+ } catch (e) {
+ this._logger.error(e);
+ }
+ }
+ // Use the tmp dir as source profile dir.
+ this._sourceProfileDir = targetDir;
+ }
+ }
+
+ getMigrateData() {
+ return (
+ Ci.nsIMailProfileMigrator.ACCOUNT_SETTINGS |
+ Ci.nsIMailProfileMigrator.MAILDATA |
+ Ci.nsIMailProfileMigrator.NEWSDATA |
+ Ci.nsIMailProfileMigrator.ADDRESSBOOK_DATA |
+ Ci.nsIMailProfileMigrator.SETTINGS
+ );
+ }
+
+ migrate(items, startup, profile) {
+ throw new Error("migrate not implemented");
+ }
+
+ async asyncMigrate() {
+ Services.obs.notifyObservers(null, "Migration:Started");
+ try {
+ await this._importPreferences();
+ } finally {
+ if (this._importingFromZip) {
+ IOUtils.remove(this._sourceProfileDir.path, { recursive: true });
+ }
+ }
+ Services.obs.notifyObservers(null, "Migration:Ended");
+ }
+
+ /**
+ * Collect interested prefs from this._sourceProfileDir, then import them one
+ * by one.
+ */
+ async _importPreferences() {
+ // A Map to collect all prefs in interested pref branches.
+ // @type {Map<string, PrefItem[]>}
+ let branchPrefsMap = new Map([
+ [MAIL_IDENTITY, []],
+ [MAIL_SERVER, []],
+ [MAIL_ACCOUNT, []],
+ [IM_ACCOUNT, []],
+ [SMTP_SERVER, []],
+ [ADDRESS_BOOK, []],
+ [CALENDAR, []],
+ ]);
+ let accounts;
+ let defaultAccount;
+ let defaultSmtpServer;
+ let ldapAutoComplete = {};
+ let otherPrefs = [];
+
+ let sourcePrefsFile = this._sourceProfileDir.clone();
+ sourcePrefsFile.append("prefs.js");
+ let sourcePrefsBuffer = await IOUtils.read(sourcePrefsFile.path);
+
+ let savePref = (type, name, value) => {
+ for (let [branchName, branchPrefs] of branchPrefsMap) {
+ if (name.startsWith(branchName)) {
+ branchPrefs.push([type, name.slice(branchName.length), value]);
+ return;
+ }
+ }
+ if (name == "mail.accountmanager.accounts") {
+ accounts = value;
+ return;
+ }
+ if (name == "mail.accountmanager.defaultaccount") {
+ defaultAccount = value;
+ return;
+ }
+ if (name == "mail.smtp.defaultserver") {
+ defaultSmtpServer = value;
+ return;
+ }
+ if (name.startsWith(LDAP_AUTO_COMPLETE)) {
+ ldapAutoComplete[name.slice(LDAP_AUTO_COMPLETE.length)] = value;
+ return;
+ }
+ if (IGNORE_PREFS.some(ignore => name.startsWith(ignore))) {
+ return;
+ }
+ // Collect all the other prefs.
+ otherPrefs.push([type, name, value]);
+ };
+
+ Services.prefs.parsePrefsFromBuffer(sourcePrefsBuffer, {
+ onStringPref: (kind, name, value) => savePref("Char", name, value),
+ onIntPref: (kind, name, value) => savePref("Int", name, value),
+ onBoolPref: (kind, name, value) => savePref("Bool", name, value),
+ onError: msg => {
+ throw new Error(msg);
+ },
+ });
+
+ Services.obs.notifyObservers(
+ null,
+ "Migration:ItemBeforeMigrate",
+ Ci.nsIMailProfileMigrator.ACCOUNT_SETTINGS
+ );
+ await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
+
+ // Import SMTP servers first, the importing order is important.
+ let smtpServerKeyMap = this._importSmtpServers(
+ branchPrefsMap.get(SMTP_SERVER),
+ defaultSmtpServer
+ );
+ // mail.identity.idN.smtpServer depends on transformed smtp server key.
+ let identityKeyMap = this._importIdentities(
+ branchPrefsMap.get(MAIL_IDENTITY),
+ smtpServerKeyMap
+ );
+ let imAccountKeyMap = await this._importIMAccounts(
+ branchPrefsMap.get(IM_ACCOUNT)
+ );
+ // mail.server.serverN.imAccount depends on transformed im account key.
+ let incomingServerKeyMap = await this._importIncomingServers(
+ branchPrefsMap.get(MAIL_SERVER),
+ imAccountKeyMap
+ );
+ // mail.account.accountN.{identities, server} depends on previous steps.
+ this._importAccounts(
+ branchPrefsMap.get(MAIL_ACCOUNT),
+ accounts,
+ defaultAccount,
+ identityKeyMap,
+ incomingServerKeyMap
+ );
+ Services.obs.notifyObservers(
+ null,
+ "Migration:ItemAfterMigrate",
+ Ci.nsIMailProfileMigrator.ACCOUNT_SETTINGS
+ );
+ Services.obs.notifyObservers(null, "Migration:Progress", "25");
+ Services.obs.notifyObservers(
+ null,
+ "Migration:ItemBeforeMigrate",
+ Ci.nsIMailProfileMigrator.MAILDATA
+ );
+ await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
+
+ await this._copyMailFolders(incomingServerKeyMap);
+ Services.obs.notifyObservers(
+ null,
+ "Migration:ItemAfterMigrate",
+ Ci.nsIMailProfileMigrator.MAILDATA
+ );
+ Services.obs.notifyObservers(null, "Migration:Progress", "50");
+ Services.obs.notifyObservers(
+ null,
+ "Migration:ItemBeforeMigrate",
+ Ci.nsIMailProfileMigrator.ADDRESSBOOK_DATA
+ );
+ await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
+
+ this._importAddressBooks(
+ branchPrefsMap.get(ADDRESS_BOOK),
+ ldapAutoComplete
+ );
+ Services.obs.notifyObservers(
+ null,
+ "Migration:ItemAfterMigrate",
+ Ci.nsIMailProfileMigrator.ADDRESSBOOK_DATA
+ );
+ Services.obs.notifyObservers(null, "Migration:Progress", "75");
+ Services.obs.notifyObservers(
+ null,
+ "Migration:ItemBeforeMigrate",
+ Ci.nsIMailProfileMigrator.SETTINGS
+ );
+ await new Promise(resolve => Services.tm.dispatchToMainThread(resolve));
+
+ this._importPasswords();
+ this._importOtherPrefs(otherPrefs);
+ this._importCalendars(branchPrefsMap.get(CALENDAR));
+ Services.obs.notifyObservers(
+ null,
+ "Migration:ItemAfterMigrate",
+ Ci.nsIMailProfileMigrator.SETTINGS
+ );
+ Services.obs.notifyObservers(null, "Migration:Progress", "100");
+ }
+
+ /**
+ * Import SMTP servers.
+ *
+ * @param {PrefItem[]} prefs - All source prefs in the SMTP_SERVER branch.
+ * @param {string} sourceDefaultServer - The value of mail.smtp.defaultserver
+ * in the source profile.
+ * @returns {smtpServerKeyMap} A map from source server key to new server key.
+ */
+ _importSmtpServers(prefs, sourceDefaultServer) {
+ let smtpServerKeyMap = new Map();
+ let branch = Services.prefs.getBranch(SMTP_SERVER);
+ for (let [type, name, value] of prefs) {
+ let key = name.split(".")[0];
+ let newServerKey = smtpServerKeyMap.get(key);
+ if (!newServerKey) {
+ // For every smtp server, create a new one to avoid conflicts.
+ let server = MailServices.smtp.createServer();
+ newServerKey = server.key;
+ smtpServerKeyMap.set(key, newServerKey);
+ this._logger.debug(
+ `Mapping SMTP server from ${key} to ${newServerKey}`
+ );
+ }
+
+ let newName = `${newServerKey}${name.slice(key.length)}`;
+ branch[`set${type}Pref`](newName, value);
+ }
+
+ // Set defaultserver if it doesn't already exist.
+ let defaultServer = Services.prefs.getCharPref(
+ "mail.smtp.defaultserver",
+ ""
+ );
+ if (sourceDefaultServer && !defaultServer) {
+ Services.prefs.setCharPref(
+ "mail.smtp.defaultserver",
+ smtpServerKeyMap.get(sourceDefaultServer)
+ );
+ }
+ return smtpServerKeyMap;
+ }
+
+ /**
+ * Import mail identites.
+ *
+ * @param {PrefItem[]} prefs - All source prefs in the MAIL_IDENTITY branch.
+ * @param {SmtpServerKeyMap} smtpServerKeyMap - A map from the source SMTP
+ * server key to new SMTP server key.
+ * @returns {IdentityKeyMap} A map from the source identity key to new identity
+ * key.
+ */
+ _importIdentities(prefs, smtpServerKeyMap) {
+ let identityKeyMap = new Map();
+ let branch = Services.prefs.getBranch(MAIL_IDENTITY);
+ for (let [type, name, value] of prefs) {
+ let key = name.split(".")[0];
+ let newIdentityKey = identityKeyMap.get(key);
+ if (!newIdentityKey) {
+ // For every identity, create a new one to avoid conflicts.
+ let identity = MailServices.accounts.createIdentity();
+ newIdentityKey = identity.key;
+ identityKeyMap.set(key, newIdentityKey);
+ this._logger.debug(`Mapping identity from ${key} to ${newIdentityKey}`);
+ }
+
+ let newName = `${newIdentityKey}${name.slice(key.length)}`;
+ let newValue = value;
+ if (name.endsWith(".smtpServer")) {
+ newValue = smtpServerKeyMap.get(value) || newValue;
+ }
+ branch[`set${type}Pref`](newName, newValue);
+ }
+ return identityKeyMap;
+ }
+
+ /**
+ * Import IM accounts.
+ *
+ * @param {Array<[string, string, number|string|boolean]>} prefs - All source
+ * prefs in the IM_ACCOUNT branch.
+ * @returns {IMAccountKeyMap} A map from the source account key to new account
+ * key.
+ */
+ async _importIMAccounts(prefs) {
+ let imAccountKeyMap = new Map();
+ let branch = Services.prefs.getBranch(IM_ACCOUNT);
+
+ let lastKey = 1;
+ function _getUniqueAccountKey() {
+ let key = `account${lastKey++}`;
+ if (Services.prefs.getCharPref(`messenger.account.${key}.name`, "")) {
+ return _getUniqueAccountKey();
+ }
+ return key;
+ }
+
+ for (let [type, name, value] of prefs) {
+ let key = name.split(".")[0];
+ let newAccountKey = imAccountKeyMap.get(key);
+ if (!newAccountKey) {
+ // For every account, create a new one to avoid conflicts.
+ newAccountKey = _getUniqueAccountKey();
+ imAccountKeyMap.set(key, newAccountKey);
+ this._logger.debug(
+ `Mapping IM account from ${key} to ${newAccountKey}`
+ );
+ }
+
+ let newName = `${newAccountKey}${name.slice(key.length)}`;
+ branch[`set${type}Pref`](newName, value);
+ }
+
+ return imAccountKeyMap;
+ }
+
+ /**
+ * Import incoming servers.
+ *
+ * @param {PrefItem[]} prefs - All source prefs in the MAIL_SERVER branch.
+ * @param {IMAccountKeyMap} imAccountKeyMap - A map from the source account
+ * key to new account key.
+ * @returns {IncomingServerKeyMap} A map from the source server key to new
+ * server key.
+ */
+ async _importIncomingServers(prefs, imAccountKeyMap) {
+ let incomingServerKeyMap = new Map();
+ let branch = Services.prefs.getBranch(MAIL_SERVER);
+
+ let lastKey = 1;
+ function _getUniqueIncomingServerKey() {
+ let key = `server${lastKey++}`;
+ if (branch.getCharPref(`${key}.type`, "")) {
+ return _getUniqueIncomingServerKey();
+ }
+ return key;
+ }
+
+ for (let [type, name, value] of prefs) {
+ let key = name.split(".")[0];
+ let newServerKey = incomingServerKeyMap.get(key);
+ if (!newServerKey) {
+ // For every incoming server, create a new one to avoid conflicts.
+ newServerKey = _getUniqueIncomingServerKey();
+ incomingServerKeyMap.set(key, newServerKey);
+ this._logger.debug(`Mapping server from ${key} to ${newServerKey}`);
+ }
+
+ let newName = `${newServerKey}${name.slice(key.length)}`;
+ let newValue = value;
+ if (newName.endsWith(".imAccount")) {
+ newValue = imAccountKeyMap.get(value);
+ }
+ branch[`set${type}Pref`](newName, newValue || value);
+ }
+ return incomingServerKeyMap;
+ }
+
+ /**
+ * Copy mail folders from this._sourceProfileDir to the current profile dir.
+ *
+ * @param {PrefKeyMap} incomingServerKeyMap - A map from the source server key
+ * to new server key.
+ */
+ async _copyMailFolders(incomingServerKeyMap) {
+ for (let key of incomingServerKeyMap.values()) {
+ let branch = Services.prefs.getBranch(`${MAIL_SERVER}${key}.`);
+ if (!branch) {
+ continue;
+ }
+ let type = branch.getCharPref("type", "");
+ let hostname = branch.getCharPref("hostname", "");
+ if (!type || !hostname) {
+ continue;
+ }
+
+ // Use .directory-rel instead of .directory because .directory is an
+ // absolute path which may not exists.
+ let directoryRel = branch.getCharPref("directory-rel", "");
+ if (!directoryRel.startsWith("[ProfD]")) {
+ continue;
+ }
+ directoryRel = directoryRel.slice("[ProfD]".length);
+
+ let targetDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ if (type == "imap") {
+ targetDir.append("ImapMail");
+ } else if (type == "nntp") {
+ targetDir.append("News");
+ } else if (["none", "pop3", "rss"].includes(type)) {
+ targetDir.append("Mail");
+ } else {
+ continue;
+ }
+
+ // Use the hostname as mail folder name and ensure it's unique.
+ targetDir.append(hostname);
+ targetDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ // Remove the folder so that nsIFile.copyTo doesn't copy into targetDir.
+ targetDir.remove(false);
+
+ let sourceDir = this._sourceProfileDir.clone();
+ for (let part of directoryRel.split("/")) {
+ sourceDir.append(part);
+ }
+ if (
+ sourceDir.exists() &&
+ (type != "imap" || Services.appinfo.OS != "WINNT")
+ ) {
+ // For some reasons, if mail folders are copied on Windows,
+ // `errorGettingDB` is thrown after imported and restarted. IMAP folders
+ // will be downloaded automatically, better than a broken account.
+ this._logger.debug(`Copying ${sourceDir.path} to ${targetDir.path}`);
+ sourceDir.copyTo(targetDir.parent, targetDir.leafName);
+ }
+ branch.setCharPref("directory", targetDir.path);
+ // .directory-rel may be outdated, it will be created when first needed.
+ branch.clearUserPref("directory-rel");
+
+ if (type == "nntp") {
+ // Use .file-rel instead of .file because .file is an absolute path
+ // which may not exists.
+ let fileRel = branch.getCharPref("newsrc.file-rel", "");
+ if (!fileRel.startsWith("[ProfD]")) {
+ continue;
+ }
+ fileRel = fileRel.slice("[ProfD]".length);
+ let sourceNewsrc = this._sourceProfileDir.clone();
+ for (let part of fileRel.split("/")) {
+ sourceNewsrc.append(part);
+ }
+ let targetNewsrc = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ targetNewsrc.append("News");
+ targetNewsrc.append(`newsrc-${hostname}`);
+ targetNewsrc.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+ this._logger.debug(
+ `Copying ${sourceNewsrc.path} to ${targetNewsrc.path}`
+ );
+ sourceNewsrc.copyTo(targetNewsrc.parent, targetNewsrc.leafName);
+ branch.setCharPref("newsrc.file", targetNewsrc.path);
+ // .file-rel may be outdated, it will be created when first needed.
+ branch.clearUserPref("newsrc.file-rel");
+ }
+ }
+ }
+
+ /**
+ * Import mail accounts.
+ *
+ * @param {PrefItem[]} prefs - All source prefs in the MAIL_ACCOUNT branch.
+ * @param {string} sourceAccounts - The value of mail.accountmanager.accounts
+ * in the source profile.
+ * @param {string} sourceDefaultAccount - The value of
+ * mail.accountmanager.defaultaccount in the source profile.
+ * @param {IdentityKeyMap} identityKeyMap - A map from the source identity key
+ * to new identity key.
+ * @param {IncomingServerKeyMap} incomingServerKeyMap - A map from the source
+ * server key to new server key.
+ */
+ _importAccounts(
+ prefs,
+ sourceAccounts,
+ sourceDefaultAccount,
+ identityKeyMap,
+ incomingServerKeyMap
+ ) {
+ let accountKeyMap = new Map();
+ let branch = Services.prefs.getBranch(MAIL_ACCOUNT);
+ for (let [type, name, value] of prefs) {
+ let key = name.split(".")[0];
+ if (key == "lastKey") {
+ continue;
+ }
+ let newAccountKey = accountKeyMap.get(key);
+ if (!newAccountKey) {
+ // For every account, create a new one to avoid conflicts.
+ newAccountKey = MailServices.accounts.getUniqueAccountKey();
+ accountKeyMap.set(key, newAccountKey);
+ }
+
+ let newName = `${newAccountKey}${name.slice(key.length)}`;
+ let newValue = value;
+ if (name.endsWith(".identities")) {
+ newValue = identityKeyMap.get(value);
+ } else if (name.endsWith(".server")) {
+ newValue = incomingServerKeyMap.get(value);
+ }
+ branch[`set${type}Pref`](newName, newValue || value);
+ }
+
+ // Append newly create accounts to mail.accountmanager.accounts.
+ let accounts = Services.prefs
+ .getCharPref("mail.accountmanager.accounts", "")
+ .split(",");
+ if (accounts.length == 1 && accounts[0] == "") {
+ accounts.length = 0;
+ }
+ if (sourceAccounts) {
+ for (let sourceAccountKey of sourceAccounts.split(",")) {
+ accounts.push(accountKeyMap.get(sourceAccountKey));
+ }
+ Services.prefs.setCharPref(
+ "mail.accountmanager.accounts",
+ accounts.join(",")
+ );
+ }
+
+ // Set defaultaccount if it doesn't already exist.
+ let defaultAccount = Services.prefs.getCharPref(
+ "mail.accountmanager.defaultaccount",
+ ""
+ );
+ if (sourceDefaultAccount && !defaultAccount) {
+ Services.prefs.setCharPref(
+ "mail.accountmanager.defaultaccount",
+ accountKeyMap.get(sourceDefaultAccount)
+ );
+ }
+ }
+
+ /**
+ * Import address books.
+ *
+ * @param {PrefItem[]} prefs - All source prefs in the ADDRESS_BOOK branch.
+ * @param {object} ldapAutoComplete - Pref values of LDAP_AUTO_COMPLETE branch.
+ * @param {boolean} ldapAutoComplete.useDirectory
+ * @param {string} ldapAutoComplete.directoryServer
+ */
+ _importAddressBooks(prefs, ldapAutoComplete) {
+ let keyMap = new Map();
+ let branch = Services.prefs.getBranch(ADDRESS_BOOK);
+ for (let [type, name, value] of prefs) {
+ let key = name.split(".")[0];
+ if (["pab", "history"].includes(key)) {
+ continue;
+ }
+ let newKey = keyMap.get(key);
+ if (!newKey) {
+ // For every address book, create a new one to avoid conflicts.
+ let uniqueCount = 0;
+ newKey = key;
+ while (true) {
+ if (!branch.getCharPref(`${newKey}.filename`, "")) {
+ break;
+ }
+ newKey = `${key}${++uniqueCount}`;
+ }
+ keyMap.set(key, newKey);
+ }
+
+ let newName = `${newKey}${name.slice(key.length)}`;
+ branch[`set${type}Pref`](newName, value);
+ }
+
+ // Transform the value of ldap_2.autoComplete.directoryServer if needed.
+ if (
+ ldapAutoComplete.useDirectory &&
+ ldapAutoComplete.directoryServer &&
+ !Services.prefs.getBoolPref(`${LDAP_AUTO_COMPLETE}useDirectory`, false)
+ ) {
+ let key = ldapAutoComplete.directoryServer.split("/").slice(-1)[0];
+ let newKey = keyMap.get(key);
+ if (newKey) {
+ Services.prefs.setBoolPref(`${LDAP_AUTO_COMPLETE}useDirectory`, true);
+ Services.prefs.setCharPref(
+ `${LDAP_AUTO_COMPLETE}directoryServer`,
+ `ldap_2.servers.${newKey}`
+ );
+ }
+ }
+
+ this._copyAddressBookDatabases(keyMap);
+ }
+
+ /**
+ * Copy sqlite files from this._sourceProfileDir to the current profile dir.
+ *
+ * @param {Map<string, string>} keyMap - A map from the source address
+ * book key to new address book key.
+ */
+ _copyAddressBookDatabases(keyMap) {
+ // Copy user created address books.
+ for (let key of keyMap.values()) {
+ let branch = Services.prefs.getBranch(`${ADDRESS_BOOK}${key}.`);
+ let filename = branch.getCharPref("filename", "");
+ if (!filename) {
+ continue;
+ }
+ let sourceFile = this._sourceProfileDir.clone();
+ sourceFile.append(filename);
+ if (!sourceFile.exists()) {
+ this._logger.debug(
+ `Ignoring non-existing address boook file ${sourceFile.path}`
+ );
+ continue;
+ }
+
+ let targetFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ targetFile.append(sourceFile.leafName);
+ targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+ this._logger.debug(`Copying ${sourceFile.path} to ${targetFile.path}`);
+ sourceFile.copyTo(targetFile.parent, targetFile.leafName);
+
+ branch.setCharPref("filename", targetFile.leafName);
+ }
+
+ // Copy or import Personal Address Book.
+ this._importAddressBookDatabase("abook.sqlite");
+ // Copy or import Collected Addresses.
+ this._importAddressBookDatabase("history.sqlite");
+ }
+
+ /**
+ * Copy a sqlite file from this._sourceProfileDir to the current profile dir.
+ *
+ * @param {string} filename - The name of the sqlite file.
+ */
+ _importAddressBookDatabase(filename) {
+ let sourceFile = this._sourceProfileDir.clone();
+ sourceFile.append(filename);
+ let targetFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ targetFile.append(filename);
+
+ if (!sourceFile.exists()) {
+ return;
+ }
+
+ if (!targetFile.exists()) {
+ sourceFile.copyTo(targetFile.parent, "");
+ return;
+ }
+
+ let dirId = MailServices.ab.newAddressBook(
+ "tmp",
+ "",
+ Ci.nsIAbManager.JS_DIRECTORY_TYPE
+ );
+ let tmpDirectory = MailServices.ab.getDirectoryFromId(dirId);
+ sourceFile.copyTo(targetFile.parent, tmpDirectory.fileName);
+
+ let targetDirectory = MailServices.ab.getDirectory(
+ `jsaddrbook://${filename}`
+ );
+ for (let card of tmpDirectory.childCards) {
+ targetDirectory.addCard(card);
+ }
+
+ MailServices.ab.deleteAddressBook(tmpDirectory.URI);
+ }
+
+ /**
+ * Import logins.json and key4.db.
+ */
+ _importPasswords() {
+ let sourceLoginsJson = this._sourceProfileDir.clone();
+ sourceLoginsJson.append("logins.json");
+ let sourceKeyDb = this._sourceProfileDir.clone();
+ sourceKeyDb.append("key4.db");
+ let targetLoginsJson = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ targetLoginsJson.append("logins.json");
+
+ if (
+ sourceLoginsJson.exists() &&
+ sourceKeyDb.exists() &&
+ !targetLoginsJson.exists()
+ ) {
+ // Only copy if logins.json doesn't exist in the current profile.
+ sourceLoginsJson.copyTo(targetLoginsJson.parent, "");
+ sourceKeyDb.copyTo(targetLoginsJson.parent, "");
+ }
+ }
+
+ /**
+ * Import a pref from source only when this pref has no user value in the
+ * current profile.
+ *
+ * @param {PrefItem[]} prefs - All source prefs to try to import.
+ */
+ _importOtherPrefs(prefs) {
+ for (let [type, name, value] of prefs) {
+ if (!Services.prefs.prefHasUserValue(name)) {
+ Services.prefs[`set${type}Pref`](name, value);
+ }
+ }
+ }
+
+ /**
+ * Import calendars.
+ *
+ * For storage calendars, we need to import everything from the source
+ * local.sqlite to the target local.sqlite, which is not implemented yet, see
+ * bug 1719582.
+ *
+ * @param {PrefItem[]} prefs - All source prefs in the CALENDAR branch.
+ */
+ _importCalendars(prefs) {
+ let branch = Services.prefs.getBranch(CALENDAR);
+ for (let [type, name, value] of prefs) {
+ branch[`set${type}Pref`](name, value);
+ }
+ }
+}
diff --git a/comm/mail/components/migration/src/components.conf b/comm/mail/components/migration/src/components.conf
new file mode 100644
index 0000000000..85b754c645
--- /dev/null
+++ b/comm/mail/components/migration/src/components.conf
@@ -0,0 +1,38 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = [
+ {
+ "cid": "{adb2e3a7-8df4-484a-b787-6c2184eb9756}",
+ "contract_ids": ["@mozilla.org/profile/migrator;1?app=mail&type=thunderbird"],
+ "jsm": "resource:///modules/ThunderbirdProfileMigrator.jsm",
+ "constructor": "ThunderbirdProfileMigrator",
+ },
+ {
+ "cid": "{b3c78baf-3a52-41d2-9718-c319bef9affc}",
+ "contract_ids": ["@mozilla.org/toolkit/profile-migrator;1"],
+ "type": "nsProfileMigrator",
+ "headers": ["/comm/mail/components/migration/src/nsProfileMigrator.h"],
+ },
+ {
+ "cid": "{62c6e1f9-3dc3-4b68-9c39-ad2f6d471ac0}",
+ "contract_ids": ["@mozilla.org/profile/migrator;1?app=mail&type=seamonkey"],
+ "type": "nsSeamonkeyProfileMigrator",
+ "headers": ["/comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.h"],
+ },
+]
+
+if buildconfig.substs["OS_ARCH"] == "WINNT":
+ Classes += [
+ {
+ "cid": "{910b6453-0719-41e8-a4c9-0319bb34c8ff}",
+ "contract_ids": ["@mozilla.org/profile/migrator;1?app=mail&type=outlook"],
+ "type": "nsOutlookProfileMigrator",
+ "headers": [
+ "/comm/mail/components/migration/src/nsOutlookProfileMigrator.h"
+ ],
+ },
+ ]
diff --git a/comm/mail/components/migration/src/moz.build b/comm/mail/components/migration/src/moz.build
new file mode 100644
index 0000000000..cfcc8d8239
--- /dev/null
+++ b/comm/mail/components/migration/src/moz.build
@@ -0,0 +1,32 @@
+# vim: set filetype=python:
+# 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/.
+
+SOURCES += [
+ "nsMailProfileMigratorUtils.cpp",
+ "nsNetscapeProfileMigratorBase.cpp",
+ "nsProfileMigrator.cpp",
+ "nsSeamonkeyProfileMigrator.cpp",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ SOURCES += [
+ "nsOutlookProfileMigrator.cpp",
+ "nsProfileMigratorBase.cpp",
+ ]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ SOURCES += [
+ "nsProfileMigratorBase.cpp",
+ ]
+
+FINAL_LIBRARY = "mailcomps"
+
+EXTRA_JS_MODULES += [
+ "ThunderbirdProfileMigrator.jsm",
+]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
diff --git a/comm/mail/components/migration/src/nsMailProfileMigratorUtils.cpp b/comm/mail/components/migration/src/nsMailProfileMigratorUtils.cpp
new file mode 100644
index 0000000000..795cd514f4
--- /dev/null
+++ b/comm/mail/components/migration/src/nsMailProfileMigratorUtils.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsMailProfileMigratorUtils.h"
+#include "nsIFile.h"
+#include "nsIProperties.h"
+#include "nsIProfileMigrator.h"
+
+#include "nsServiceManagerUtils.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsXPCOMCID.h"
+
+void SetProxyPref(const nsACString& aHostPort, const char* aPref,
+ const char* aPortPref, nsIPrefBranch* aPrefs) {
+ nsAutoCString hostPort(aHostPort);
+ int32_t portDelimOffset = hostPort.RFindChar(':');
+ if (portDelimOffset > 0) {
+ nsAutoCString host(Substring(hostPort, 0, portDelimOffset));
+ nsAutoCString port(Substring(hostPort, portDelimOffset + 1,
+ hostPort.Length() - (portDelimOffset + 1)));
+
+ aPrefs->SetCharPref(aPref, host);
+ nsresult stringErr;
+ int32_t portValue = port.ToInteger(&stringErr);
+ aPrefs->SetIntPref(aPortPref, portValue);
+ } else
+ aPrefs->SetCharPref(aPref, hostPort);
+}
+
+void ParseOverrideServers(const char* aServers, nsIPrefBranch* aBranch) {
+ // Windows (and Opera) formats its proxy override list in the form:
+ // server;server;server where server is a server name or ip address,
+ // or "<local>". Mozilla's format is server,server,server, and <local>
+ // must be translated to "localhost,127.0.0.1"
+ nsAutoCString override(aServers);
+ int32_t left = 0, right = 0;
+ for (;;) {
+ right = override.FindChar(';', right);
+ const nsACString& host = Substring(
+ override, left, (right < 0 ? override.Length() : right) - left);
+ if (host.Equals("<local>"))
+ override.Replace(left, 7, "localhost,127.0.0.1"_ns);
+ if (right < 0) break;
+ left = right + 1;
+ override.Replace(right, 1, ","_ns);
+ }
+ aBranch->SetCharPref("network.proxy.no_proxies_on", override);
+}
+
+void GetMigrateDataFromArray(MigrationData* aDataArray,
+ int32_t aDataArrayLength, bool aReplace,
+ nsIFile* aSourceProfile, uint16_t* aResult) {
+ nsCOMPtr<nsIFile> sourceFile;
+ bool exists;
+ MigrationData* cursor;
+ MigrationData* end = aDataArray + aDataArrayLength;
+ for (cursor = aDataArray; cursor < end && cursor->fileName; ++cursor) {
+ // When in replace mode, all items can be imported.
+ // When in non-replace mode, only items that do not require file replacement
+ // can be imported.
+ if (aReplace || !cursor->replaceOnly) {
+ aSourceProfile->Clone(getter_AddRefs(sourceFile));
+ sourceFile->Append(nsDependentString(cursor->fileName));
+ sourceFile->Exists(&exists);
+ if (exists) *aResult |= cursor->sourceFlag;
+ }
+ free(cursor->fileName);
+ cursor->fileName = nullptr;
+ }
+}
+
+void GetProfilePath(nsIProfileStartup* aStartup,
+ nsCOMPtr<nsIFile>& aProfileDir) {
+ if (aStartup) {
+ aStartup->GetDirectory(getter_AddRefs(aProfileDir));
+ } else {
+ nsCOMPtr<nsIProperties> dirSvc(
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID));
+ if (dirSvc) {
+ dirSvc->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(aProfileDir));
+ }
+ }
+}
diff --git a/comm/mail/components/migration/src/nsMailProfileMigratorUtils.h b/comm/mail/components/migration/src/nsMailProfileMigratorUtils.h
new file mode 100644
index 0000000000..01f21d0d72
--- /dev/null
+++ b/comm/mail/components/migration/src/nsMailProfileMigratorUtils.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef mailprofilemigratorutils___h___
+#define mailprofilemigratorutils___h___
+
+#define MIGRATION_ITEMBEFOREMIGRATE "Migration:ItemBeforeMigrate"
+#define MIGRATION_ITEMAFTERMIGRATE "Migration:ItemAfterMigrate"
+#define MIGRATION_STARTED "Migration:Started"
+#define MIGRATION_ENDED "Migration:Ended"
+#define MIGRATION_PROGRESS "Migration:Progress"
+
+#define NOTIFY_OBSERVERS(message, item) \
+ mObserverService->NotifyObservers(nullptr, message, item)
+
+#define COPY_DATA(func, replace, itemIndex) \
+ if (NS_SUCCEEDED(rv) && (aItems & itemIndex || !aItems)) { \
+ nsAutoString index; \
+ index.AppendInt(itemIndex); \
+ NOTIFY_OBSERVERS(MIGRATION_ITEMBEFOREMIGRATE, index.get()); \
+ rv = func(replace); \
+ NOTIFY_OBSERVERS(MIGRATION_ITEMAFTERMIGRATE, index.get()); \
+ }
+
+#include "nsIPrefBranch.h"
+#include "nsIFile.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+class nsIProfileStartup;
+
+// Proxy utilities shared by the Opera and IE migrators
+void ParseOverrideServers(const char* aServers, nsIPrefBranch* aBranch);
+void SetProxyPref(const nsACString& aHostPort, const char* aPref,
+ const char* aPortPref, nsIPrefBranch* aPrefs);
+
+struct MigrationData {
+ char16_t* fileName;
+ uint32_t sourceFlag;
+ bool replaceOnly;
+};
+
+class nsIFile;
+void GetMigrateDataFromArray(MigrationData* aDataArray,
+ int32_t aDataArrayLength, bool aReplace,
+ nsIFile* aSourceProfile, uint16_t* aResult);
+
+// get the base directory of the *target* profile
+// this is already cloned, modify it to your heart's content
+void GetProfilePath(nsIProfileStartup* aStartup,
+ nsCOMPtr<nsIFile>& aProfileDir);
+
+#endif
diff --git a/comm/mail/components/migration/src/nsNetscapeProfileMigratorBase.cpp b/comm/mail/components/migration/src/nsNetscapeProfileMigratorBase.cpp
new file mode 100644
index 0000000000..b4a7affe03
--- /dev/null
+++ b/comm/mail/components/migration/src/nsNetscapeProfileMigratorBase.cpp
@@ -0,0 +1,371 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsILineInputStream.h"
+#include "nsIPrefBranch.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsIPrefService.h"
+#include "nsIServiceManager.h"
+#include "nsIURL.h"
+#include "nsNetscapeProfileMigratorBase.h"
+#include "nsNetUtil.h"
+#include "prtime.h"
+#include "prprf.h"
+#include "nsITimer.h"
+#include "nsINIParser.h"
+#include "nsMailProfileMigratorUtils.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsServiceManagerUtils.h"
+
+#define MIGRATION_BUNDLE \
+ "chrome://messenger/locale/migration/migration.properties"
+
+#define FILE_NAME_PREFS_5X u"prefs.js"_ns
+
+///////////////////////////////////////////////////////////////////////////////
+// nsNetscapeProfileMigratorBase
+nsNetscapeProfileMigratorBase::nsNetscapeProfileMigratorBase() {
+ mObserverService = do_GetService("@mozilla.org/observer-service;1");
+ mMaxProgress = 0;
+ mCurrentProgress = 0;
+ mFileCopyTransactionIndex = 0;
+}
+
+NS_IMPL_ISUPPORTS(nsNetscapeProfileMigratorBase, nsIMailProfileMigrator,
+ nsITimerCallback)
+
+nsresult nsNetscapeProfileMigratorBase::GetProfileDataFromProfilesIni(
+ nsIFile* aDataDir, nsTArray<nsString>& aProfileNames,
+ nsTArray<RefPtr<nsIFile>>& aProfileLocations) {
+ nsCOMPtr<nsIFile> profileIni;
+ nsresult rv = aDataDir->Clone(getter_AddRefs(profileIni));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ profileIni->Append(u"profiles.ini"_ns);
+
+ // Does it exist?
+ bool profileFileExists = false;
+ rv = profileIni->Exists(&profileFileExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!profileFileExists) return NS_ERROR_FILE_NOT_FOUND;
+
+ nsINIParser parser;
+ rv = parser.Init(profileIni);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString buffer, filePath;
+ bool isRelative;
+
+ // This is an infinite loop that is broken when we no longer find profiles
+ // for profileID with IsRelative option.
+ for (unsigned int c = 0; true; ++c) {
+ nsAutoCString profileID("Profile");
+ profileID.AppendInt(c);
+
+ if (NS_FAILED(parser.GetString(profileID.get(), "IsRelative", buffer)))
+ break;
+
+ isRelative = buffer.EqualsLiteral("1");
+
+ rv = parser.GetString(profileID.get(), "Path", filePath);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Malformed profiles.ini: Path= not found");
+ continue;
+ }
+
+ rv = parser.GetString(profileID.get(), "Name", buffer);
+ if (NS_FAILED(rv)) {
+ NS_ERROR("Malformed profiles.ini: Name= not found");
+ continue;
+ }
+
+ nsCOMPtr<nsIFile> rootDir;
+ rv = NS_NewNativeLocalFile(EmptyCString(), true, getter_AddRefs(rootDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = isRelative ? rootDir->SetRelativeDescriptor(aDataDir, filePath)
+ : rootDir->SetPersistentDescriptor(filePath);
+ if (NS_FAILED(rv)) continue;
+
+ bool exists = false;
+ rootDir->Exists(&exists);
+
+ if (exists) {
+ aProfileLocations.AppendElement(rootDir);
+ aProfileNames.AppendElement(NS_ConvertUTF8toUTF16(buffer));
+ }
+ }
+ return NS_OK;
+}
+
+#define GETPREF(xform, method, value) \
+ nsresult rv = aBranch->method(xform->sourcePrefName, value); \
+ if (NS_SUCCEEDED(rv)) xform->prefHasValue = true; \
+ return rv;
+
+#define SETPREF(xform, method, value) \
+ if (xform->prefHasValue) { \
+ return aBranch->method( \
+ xform->targetPrefName ? xform->targetPrefName : xform->sourcePrefName, \
+ value); \
+ } \
+ return NS_OK;
+
+nsresult nsNetscapeProfileMigratorBase::GetString(PrefTransform* aTransform,
+ nsIPrefBranch* aBranch) {
+ PrefTransform* xform = (PrefTransform*)aTransform;
+ nsCString str;
+ nsresult rv = aBranch->GetCharPref(xform->sourcePrefName, str);
+ if (NS_SUCCEEDED(rv)) {
+ xform->prefHasValue = true;
+ xform->stringValue = moz_xstrdup(str.get());
+ }
+ return rv;
+}
+
+nsresult nsNetscapeProfileMigratorBase::SetString(PrefTransform* aTransform,
+ nsIPrefBranch* aBranch) {
+ PrefTransform* xform = (PrefTransform*)aTransform;
+ SETPREF(xform, SetCharPref, nsDependentCString(xform->stringValue));
+}
+
+nsresult nsNetscapeProfileMigratorBase::GetBool(PrefTransform* aTransform,
+ nsIPrefBranch* aBranch) {
+ PrefTransform* xform = (PrefTransform*)aTransform;
+ GETPREF(xform, GetBoolPref, &xform->boolValue);
+}
+
+nsresult nsNetscapeProfileMigratorBase::SetBool(PrefTransform* aTransform,
+ nsIPrefBranch* aBranch) {
+ PrefTransform* xform = (PrefTransform*)aTransform;
+ SETPREF(xform, SetBoolPref, xform->boolValue);
+}
+
+nsresult nsNetscapeProfileMigratorBase::GetInt(PrefTransform* aTransform,
+ nsIPrefBranch* aBranch) {
+ PrefTransform* xform = (PrefTransform*)aTransform;
+ GETPREF(xform, GetIntPref, &xform->intValue);
+}
+
+nsresult nsNetscapeProfileMigratorBase::SetInt(PrefTransform* aTransform,
+ nsIPrefBranch* aBranch) {
+ PrefTransform* xform = (PrefTransform*)aTransform;
+ SETPREF(xform, SetIntPref, xform->intValue);
+}
+
+nsresult nsNetscapeProfileMigratorBase::CopyFile(
+ const nsAString& aSourceFileName, const nsAString& aTargetFileName) {
+ nsCOMPtr<nsIFile> sourceFile;
+ mSourceProfile->Clone(getter_AddRefs(sourceFile));
+
+ sourceFile->Append(aSourceFileName);
+ bool exists = false;
+ sourceFile->Exists(&exists);
+ if (!exists) return NS_OK;
+
+ nsCOMPtr<nsIFile> targetFile;
+ mTargetProfile->Clone(getter_AddRefs(targetFile));
+
+ targetFile->Append(aTargetFileName);
+ targetFile->Exists(&exists);
+ if (exists) targetFile->Remove(false);
+
+ return sourceFile->CopyTo(mTargetProfile, aTargetFileName);
+}
+
+nsresult nsNetscapeProfileMigratorBase::GetSignonFileName(
+ bool aReplace, nsACString& aFileName) {
+ nsresult rv;
+ if (aReplace) {
+ // Find out what the signons file was called, this is stored in a pref
+ // in Seamonkey.
+ nsCOMPtr<nsIPrefService> psvc(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ psvc->ResetPrefs();
+
+ nsCOMPtr<nsIFile> sourcePrefsName;
+ mSourceProfile->Clone(getter_AddRefs(sourcePrefsName));
+ sourcePrefsName->Append(FILE_NAME_PREFS_5X);
+ psvc->ReadUserPrefsFromFile(sourcePrefsName);
+
+ nsCOMPtr<nsIPrefBranch> branch(do_QueryInterface(psvc));
+ rv = branch->GetCharPref("signon.SignonFileName", aFileName);
+ } else
+ rv = LocateSignonsFile(aFileName);
+ return rv;
+}
+
+nsresult nsNetscapeProfileMigratorBase::LocateSignonsFile(nsACString& aResult) {
+ nsCOMPtr<nsIDirectoryEnumerator> entries;
+ nsresult rv = mSourceProfile->GetDirectoryEntries(getter_AddRefs(entries));
+ if (NS_FAILED(rv)) return rv;
+
+ nsAutoCString fileName;
+ bool hasMore = false;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsIFile> currFile;
+ rv = entries->GetNextFile(getter_AddRefs(currFile));
+ if (NS_FAILED(rv)) break;
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewFileURI(getter_AddRefs(uri), currFile);
+ if (NS_FAILED(rv)) break;
+ nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
+
+ nsAutoCString extn;
+ url->GetFileExtension(extn);
+
+ if (extn.EqualsIgnoreCase("s")) {
+ url->GetFileName(fileName);
+ break;
+ }
+ }
+
+ aResult = fileName;
+
+ return NS_OK;
+}
+
+// helper function, copies the contents of srcDir into destDir.
+// destDir will be created if it doesn't exist.
+
+nsresult nsNetscapeProfileMigratorBase::RecursiveCopy(nsIFile* srcDir,
+ nsIFile* destDir) {
+ nsresult rv;
+ bool isDir;
+
+ rv = srcDir->IsDirectory(&isDir);
+ if (NS_FAILED(rv)) return rv;
+ if (!isDir) return NS_ERROR_INVALID_ARG;
+
+ bool exists;
+ rv = destDir->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists)
+ rv = destDir->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ if (NS_FAILED(rv)) return rv;
+
+ nsCOMPtr<nsIDirectoryEnumerator> dirIterator;
+ rv = srcDir->GetDirectoryEntries(getter_AddRefs(dirIterator));
+ if (NS_FAILED(rv)) return rv;
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(dirIterator->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsIFile> dirEntry;
+ rv = dirIterator->GetNextFile(getter_AddRefs(dirEntry));
+ if (NS_SUCCEEDED(rv) && dirEntry) {
+ rv = dirEntry->IsDirectory(&isDir);
+ if (NS_SUCCEEDED(rv)) {
+ if (isDir) {
+ nsCOMPtr<nsIFile> newChild;
+ rv = destDir->Clone(getter_AddRefs(newChild));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString leafName;
+ dirEntry->GetLeafName(leafName);
+ newChild->AppendRelativePath(leafName);
+ rv = newChild->Exists(&exists);
+ if (NS_SUCCEEDED(rv) && !exists) {
+ rv = newChild->Create(nsIFile::DIRECTORY_TYPE, 0775);
+ if (NS_FAILED(rv)) return rv;
+ }
+ rv = RecursiveCopy(dirEntry, newChild);
+ }
+ } else {
+ // we aren't going to do any actual file copying here. Instead, add
+ // this to our file transaction list so we can copy files
+ // asynchronously...
+ fileTransactionEntry fileEntry;
+ fileEntry.srcFile = dirEntry;
+ fileEntry.destFile = destDir;
+
+ mFileCopyTransactions.AppendElement(fileEntry);
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsITimerCallback
+
+NS_IMETHODIMP
+nsNetscapeProfileMigratorBase::Notify(nsITimer* timer) {
+ CopyNextFolder();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetscapeProfileMigratorBase::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsNetscapeProfileMigratorBase");
+ return NS_OK;
+}
+
+void nsNetscapeProfileMigratorBase::CopyNextFolder() {
+ if (mFileCopyTransactionIndex < mFileCopyTransactions.Length()) {
+ fileTransactionEntry fileTransaction =
+ mFileCopyTransactions.ElementAt(mFileCopyTransactionIndex++);
+
+ // copy the file
+ fileTransaction.srcFile->CopyTo(fileTransaction.destFile,
+ fileTransaction.newName);
+
+ // add to our current progress
+ int64_t fileSize;
+ fileTransaction.srcFile->GetFileSize(&fileSize);
+ mCurrentProgress += fileSize;
+
+ uint32_t percentage = (uint32_t)(mCurrentProgress * 100 / mMaxProgress);
+
+ nsAutoString index;
+ index.AppendInt(percentage);
+
+ NOTIFY_OBSERVERS(MIGRATION_PROGRESS, index.get());
+
+ // fire a timer to handle the next one.
+ nsresult rv = NS_NewTimerWithCallback(
+ getter_AddRefs(mFileIOTimer), static_cast<nsITimerCallback*>(this),
+ percentage == 100 ? 500 : 0, nsITimer::TYPE_ONE_SHOT, nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not start mFileIOTimer timer");
+ }
+ } else
+ EndCopyFolders();
+
+ return;
+}
+
+void nsNetscapeProfileMigratorBase::EndCopyFolders() {
+ mFileCopyTransactions.Clear();
+ mFileCopyTransactionIndex = 0;
+
+ // notify the UI that we are done with the migration process
+ nsAutoString index;
+ index.AppendInt(nsIMailProfileMigrator::MAILDATA);
+ NOTIFY_OBSERVERS(MIGRATION_ITEMAFTERMIGRATE, index.get());
+
+ NOTIFY_OBSERVERS(MIGRATION_ENDED, nullptr);
+}
+
+NS_IMETHODIMP
+nsNetscapeProfileMigratorBase::GetSourceHasMultipleProfiles(bool* aResult) {
+ nsTArray<nsString> profiles;
+ GetSourceProfiles(profiles);
+
+ *aResult = profiles.Length() > 1;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNetscapeProfileMigratorBase::GetSourceExists(bool* aResult) {
+ nsTArray<nsString> profiles;
+ GetSourceProfiles(profiles);
+
+ *aResult = profiles.Length() > 0;
+ return NS_OK;
+}
diff --git a/comm/mail/components/migration/src/nsNetscapeProfileMigratorBase.h b/comm/mail/components/migration/src/nsNetscapeProfileMigratorBase.h
new file mode 100644
index 0000000000..5227673532
--- /dev/null
+++ b/comm/mail/components/migration/src/nsNetscapeProfileMigratorBase.h
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef netscapeprofilemigratorbase___h___
+#define netscapeprofilemigratorbase___h___
+
+#include "nsAttrValue.h"
+#include "nsIFile.h"
+#include "nsIStringBundle.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsIObserverService.h"
+#include "nsITimer.h"
+#include "nsIMailProfileMigrator.h"
+
+class nsIPrefBranch;
+
+struct fileTransactionEntry {
+ nsCOMPtr<nsIFile> srcFile; // the src path including leaf name
+ nsCOMPtr<nsIFile> destFile; // the destination path
+ nsString
+ newName; // only valid if the file should be renamed after getting copied
+};
+
+#define F(a) nsNetscapeProfileMigratorBase::a
+
+#define MAKEPREFTRANSFORM(pref, newpref, getmethod, setmethod) \
+ { \
+ pref, newpref, F(Get##getmethod), F(Set##setmethod), false, { -1 } \
+ }
+
+#define MAKESAMETYPEPREFTRANSFORM(pref, method) \
+ { \
+ pref, 0, F(Get##method), F(Set##method), false, { -1 } \
+ }
+
+class nsNetscapeProfileMigratorBase : public nsIMailProfileMigrator,
+ public nsITimerCallback,
+ public nsINamed
+
+{
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ nsNetscapeProfileMigratorBase();
+
+ NS_IMETHOD GetSourceHasMultipleProfiles(bool* aResult) override;
+ NS_IMETHOD GetSourceExists(bool* aResult) override;
+
+ struct PrefTransform;
+ typedef nsresult (*prefConverter)(PrefTransform*, nsIPrefBranch*);
+
+ struct PrefTransform {
+ const char* sourcePrefName;
+ const char* targetPrefName;
+ prefConverter prefGetterFunc;
+ prefConverter prefSetterFunc;
+ bool prefHasValue;
+ union {
+ int32_t intValue;
+ bool boolValue;
+ char* stringValue;
+ };
+ };
+
+ struct PrefBranchStruct {
+ char* prefName;
+ int32_t type;
+ union {
+ char* stringValue;
+ int32_t intValue;
+ bool boolValue;
+ };
+ };
+
+ typedef nsTArray<PrefBranchStruct*> PBStructArray;
+
+ static nsresult GetString(PrefTransform* aTransform, nsIPrefBranch* aBranch);
+ static nsresult SetString(PrefTransform* aTransform, nsIPrefBranch* aBranch);
+ static nsresult GetBool(PrefTransform* aTransform, nsIPrefBranch* aBranch);
+ static nsresult SetBool(PrefTransform* aTransform, nsIPrefBranch* aBranch);
+ static nsresult GetInt(PrefTransform* aTransform, nsIPrefBranch* aBranch);
+ static nsresult SetInt(PrefTransform* aTransform, nsIPrefBranch* aBranch);
+
+ nsresult RecursiveCopy(nsIFile* srcDir, nsIFile* destDir); // helper routine
+
+ protected:
+ virtual ~nsNetscapeProfileMigratorBase() {}
+ void CopyNextFolder();
+ void EndCopyFolders();
+
+ nsresult GetProfileDataFromProfilesIni(
+ nsIFile* aDataDir, nsTArray<nsString>& aProfileNames,
+ nsTArray<RefPtr<nsIFile>>& aProfileLocations);
+
+ nsresult CopyFile(const nsAString& aSourceFileName,
+ const nsAString& aTargetFileName);
+
+ nsresult GetSignonFileName(bool aReplace, nsACString& aFileName);
+ nsresult LocateSignonsFile(nsACString& aResult);
+
+ nsCOMPtr<nsIFile> mSourceProfile;
+ nsCOMPtr<nsIFile> mTargetProfile;
+
+ // List of src/destination files we still have to copy into the new profile
+ // directory.
+ nsTArray<fileTransactionEntry> mFileCopyTransactions;
+ uint32_t mFileCopyTransactionIndex;
+
+ int64_t mMaxProgress;
+ int64_t mCurrentProgress;
+
+ nsCOMPtr<nsIObserverService> mObserverService;
+ nsCOMPtr<nsITimer> mFileIOTimer;
+};
+
+#endif
diff --git a/comm/mail/components/migration/src/nsOutlookProfileMigrator.cpp b/comm/mail/components/migration/src/nsOutlookProfileMigrator.cpp
new file mode 100644
index 0000000000..dd7535e257
--- /dev/null
+++ b/comm/mail/components/migration/src/nsOutlookProfileMigrator.cpp
@@ -0,0 +1,135 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsMailProfileMigratorUtils.h"
+#include "nsIServiceManager.h"
+#include "nsOutlookProfileMigrator.h"
+#include "nsIProfileMigrator.h"
+#include "nsIImportSettings.h"
+#include "nsIFile.h"
+#include "nsITimer.h"
+#include "nsComponentManagerUtils.h"
+
+NS_IMPL_ISUPPORTS(nsOutlookProfileMigrator, nsIMailProfileMigrator,
+ nsITimerCallback)
+
+nsOutlookProfileMigrator::nsOutlookProfileMigrator() {
+ mProcessingMailFolders = false;
+ // get the import service
+ mImportModule = do_CreateInstance("@mozilla.org/import/import-outlook;1");
+}
+
+nsOutlookProfileMigrator::~nsOutlookProfileMigrator() {}
+
+nsresult nsOutlookProfileMigrator::ContinueImport() { return Notify(nullptr); }
+
+///////////////////////////////////////////////////////////////////////////////
+// nsITimerCallback
+
+NS_IMETHODIMP
+nsOutlookProfileMigrator::Notify(nsITimer* timer) {
+ int32_t progress;
+ mGenericImporter->GetProgress(&progress);
+
+ nsAutoString index;
+ index.AppendInt(progress);
+ NOTIFY_OBSERVERS(MIGRATION_PROGRESS, index.get());
+
+ if (progress == 100) // are we done yet?
+ {
+ if (mProcessingMailFolders)
+ return FinishCopyingMailFolders();
+ else
+ return FinishCopyingAddressBookData();
+ } else {
+ // fire a timer to handle the next one.
+ nsresult rv = NS_NewTimerWithCallback(
+ getter_AddRefs(mFileIOTimer), static_cast<nsITimerCallback*>(this), 100,
+ nsITimer::TYPE_ONE_SHOT, nullptr);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Could not start mFileIOTimer timer");
+ }
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOutlookProfileMigrator::GetName(nsACString& aName) {
+ aName.AssignLiteral("nsOutlookProfileMigrator");
+ return NS_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIMailProfileMigrator
+
+NS_IMETHODIMP
+nsOutlookProfileMigrator::Migrate(uint16_t aItems, nsIProfileStartup* aStartup,
+ const char16_t* aProfile) {
+ nsresult rv = NS_OK;
+
+ if (aStartup) {
+ rv = aStartup->DoStartup();
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ NOTIFY_OBSERVERS(MIGRATION_STARTED, nullptr);
+
+ rv = ImportSettings(mImportModule);
+
+ // now import address books
+ // this routine will asynchronously import address book data and it will then
+ // kick off the final migration step, copying the mail folders over.
+ rv = ImportAddressBook(mImportModule);
+
+ // don't broadcast an on end migration here. We aren't done until our asynch
+ // import process says we are done.
+ return rv;
+}
+
+NS_IMETHODIMP
+nsOutlookProfileMigrator::GetMigrateData(const char16_t* aProfile,
+ bool aReplace, uint16_t* aResult) {
+ // There's no harm in assuming everything is available.
+ *aResult = nsIMailProfileMigrator::ACCOUNT_SETTINGS |
+ nsIMailProfileMigrator::ADDRESSBOOK_DATA |
+ nsIMailProfileMigrator::MAILDATA;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOutlookProfileMigrator::GetSourceExists(bool* aResult) {
+ *aResult = false;
+
+ nsCOMPtr<nsISupports> supports;
+ mImportModule->GetImportInterface(NS_IMPORT_SETTINGS_STR,
+ getter_AddRefs(supports));
+ nsCOMPtr<nsIImportSettings> importSettings = do_QueryInterface(supports);
+
+ if (importSettings) {
+ nsString description;
+ nsCOMPtr<nsIFile> location;
+ importSettings->AutoLocate(getter_Copies(description),
+ getter_AddRefs(location), aResult);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOutlookProfileMigrator::GetSourceHasMultipleProfiles(bool* aResult) {
+ *aResult = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOutlookProfileMigrator::GetSourceProfiles(nsTArray<nsString>& aResult) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsOutlookProfileMigrator::GetSourceProfileLocations(
+ nsTArray<RefPtr<nsIFile>>& aResult) {
+ return NS_OK;
+}
diff --git a/comm/mail/components/migration/src/nsOutlookProfileMigrator.h b/comm/mail/components/migration/src/nsOutlookProfileMigrator.h
new file mode 100644
index 0000000000..6de79f98dd
--- /dev/null
+++ b/comm/mail/components/migration/src/nsOutlookProfileMigrator.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef outlookprofilemigrator___h___
+#define outlookprofilemigrator___h___
+
+#include "nsIMailProfileMigrator.h"
+#include "nsITimer.h"
+#include "nsProfileMigratorBase.h"
+
+class nsOutlookProfileMigrator : public nsIMailProfileMigrator,
+ public nsITimerCallback,
+ public nsProfileMigratorBase,
+ public nsINamed {
+ public:
+ NS_DECL_NSIMAILPROFILEMIGRATOR
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSITIMERCALLBACK
+ NS_DECL_NSINAMED
+
+ nsOutlookProfileMigrator();
+ virtual nsresult ContinueImport();
+
+ private:
+ virtual ~nsOutlookProfileMigrator();
+};
+
+#endif
diff --git a/comm/mail/components/migration/src/nsProfileMigrator.cpp b/comm/mail/components/migration/src/nsProfileMigrator.cpp
new file mode 100644
index 0000000000..c6fa2bc867
--- /dev/null
+++ b/comm/mail/components/migration/src/nsProfileMigrator.cpp
@@ -0,0 +1,121 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsIFile.h"
+#include "mozIDOMWindow.h"
+#include "nsIProfileMigrator.h"
+#include "nsIPrefService.h"
+#include "nsIServiceManager.h"
+#include "nsIToolkitProfile.h"
+#include "nsIToolkitProfileService.h"
+#include "nsIWindowWatcher.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIMutableArray.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIProperties.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsProfileMigrator.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+#else
+# include <limits.h>
+#endif
+
+NS_IMPL_ISUPPORTS(nsProfileMigrator, nsIProfileMigrator)
+
+#define MIGRATION_WIZARD_FE_URL \
+ "chrome://messenger/content/migration/migration.xhtml"_ns
+#define MIGRATION_WIZARD_FE_FEATURES "chrome,dialog,modal,centerscreen"_ns
+
+NS_IMETHODIMP
+nsProfileMigrator::Migrate(nsIProfileStartup* aStartup, const nsACString& aKey,
+ const nsACString& aProfileName) {
+ nsAutoCString key;
+ nsCOMPtr<nsIMailProfileMigrator> mailMigrator;
+ nsresult rv = GetDefaultMailMigratorKey(key, mailMigrator);
+ NS_ENSURE_SUCCESS(rv, rv); // abort migration if we failed to get a
+ // mailMigrator (if we were supposed to)
+
+ nsCOMPtr<nsISupportsCString> cstr(
+ do_CreateInstance("@mozilla.org/supports-cstring;1"));
+ NS_ENSURE_TRUE(cstr, NS_ERROR_OUT_OF_MEMORY);
+ cstr->SetData(key);
+
+ // By opening the Migration FE with a supplied mailMigrator, it will
+ // automatically migrate from it.
+ nsCOMPtr<nsIWindowWatcher> ww(do_GetService(NS_WINDOWWATCHER_CONTRACTID));
+ nsCOMPtr<nsIMutableArray> params(do_CreateInstance(NS_ARRAY_CONTRACTID));
+ if (!ww || !params) return NS_ERROR_FAILURE;
+
+ params->AppendElement(cstr);
+ params->AppendElement(mailMigrator);
+ params->AppendElement(aStartup);
+
+ nsCOMPtr<mozIDOMWindowProxy> migrateWizard;
+ return ww->OpenWindow(nullptr, MIGRATION_WIZARD_FE_URL, "_blank"_ns,
+ MIGRATION_WIZARD_FE_FEATURES, params,
+ getter_AddRefs(migrateWizard));
+}
+
+#ifdef XP_WIN
+typedef struct {
+ WORD wLanguage;
+ WORD wCodePage;
+} LANGANDCODEPAGE;
+
+# define INTERNAL_NAME_THUNDERBIRD "Thunderbird"
+# define INTERNAL_NAME_SEAMONKEY "Mozilla"
+#endif
+
+nsresult nsProfileMigrator::GetDefaultMailMigratorKey(
+ nsACString& aKey, nsCOMPtr<nsIMailProfileMigrator>& mailMigrator) {
+ // look up the value of profile.force.migration in case we are supposed to
+ // force migration using a particular migrator....
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCString forceMigrationType;
+ prefs->GetCharPref("profile.force.migration", forceMigrationType);
+
+ // if we are being forced to migrate to a particular migration type, then
+ // create an instance of that migrator and return it.
+ nsAutoCString migratorID;
+ if (!forceMigrationType.IsEmpty()) {
+ bool exists = false;
+ migratorID.AppendLiteral("@mozilla.org/messenger/server;1?type=");
+ migratorID.Append(forceMigrationType);
+ mailMigrator = do_CreateInstance(migratorID.get());
+ if (!mailMigrator) return NS_ERROR_NOT_AVAILABLE;
+
+ mailMigrator->GetSourceExists(&exists);
+ /* trying to force migration on a source which doesn't
+ * have any profiles.
+ */
+ if (!exists) return NS_ERROR_NOT_AVAILABLE;
+ aKey = forceMigrationType;
+ return NS_OK;
+ }
+
+#define MAX_SOURCE_LENGTH 10
+ const char sources[][MAX_SOURCE_LENGTH] = {"seamonkey", "outlook", ""};
+ for (uint32_t i = 0; sources[i][0]; ++i) {
+ migratorID.AssignLiteral("@mozilla.org/messenger/server;1?type=");
+ migratorID.Append(sources[i]);
+ mailMigrator = do_CreateInstance(migratorID.get());
+ if (!mailMigrator) continue;
+
+ bool exists = false;
+ mailMigrator->GetSourceExists(&exists);
+ if (exists) {
+ mailMigrator = nullptr;
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
diff --git a/comm/mail/components/migration/src/nsProfileMigrator.h b/comm/mail/components/migration/src/nsProfileMigrator.h
new file mode 100644
index 0000000000..d25a9989e9
--- /dev/null
+++ b/comm/mail/components/migration/src/nsProfileMigrator.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsIFile.h"
+#include "nsIProfileMigrator.h"
+#include "nsIMailProfileMigrator.h"
+#include "nsIServiceManager.h"
+#include "nsIToolkitProfile.h"
+#include "nsIToolkitProfileService.h"
+#include "nsCOMPtr.h"
+#include "nsDirectoryServiceDefs.h"
+
+#include "nsString.h"
+
+#define NS_THUNDERBIRD_PROFILEIMPORT_CID \
+ { \
+ 0xb3c78baf, 0x3a52, 0x41d2, { \
+ 0x97, 0x18, 0xc3, 0x19, 0xbe, 0xf9, 0xaf, 0xfc \
+ } \
+ }
+
+class nsProfileMigrator final : public nsIProfileMigrator {
+ public:
+ NS_DECL_NSIPROFILEMIGRATOR
+ NS_DECL_ISUPPORTS
+
+ nsProfileMigrator(){};
+
+ protected:
+ ~nsProfileMigrator(){};
+
+ nsresult GetDefaultMailMigratorKey(
+ nsACString& key, nsCOMPtr<nsIMailProfileMigrator>& mailMigrator);
+};
diff --git a/comm/mail/components/migration/src/nsProfileMigratorBase.cpp b/comm/mail/components/migration/src/nsProfileMigratorBase.cpp
new file mode 100644
index 0000000000..5ce067308c
--- /dev/null
+++ b/comm/mail/components/migration/src/nsProfileMigratorBase.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsMailProfileMigratorUtils.h"
+#include "nsISupportsPrimitives.h"
+#include "nsProfileMigratorBase.h"
+#include "nsIMailProfileMigrator.h"
+
+#include "nsIImportSettings.h"
+#include "nsIImportFilters.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+
+#define kPersonalAddressbookUri "jsaddrbook://abook.sqlite"
+
+nsProfileMigratorBase::nsProfileMigratorBase() {
+ mObserverService = do_GetService("@mozilla.org/observer-service;1");
+ mProcessingMailFolders = false;
+}
+
+nsProfileMigratorBase::~nsProfileMigratorBase() {
+ if (mFileIOTimer) mFileIOTimer->Cancel();
+}
+
+nsresult nsProfileMigratorBase::ImportSettings(nsIImportModule* aImportModule) {
+ nsresult rv;
+
+ nsAutoString index;
+ index.AppendInt(nsIMailProfileMigrator::ACCOUNT_SETTINGS);
+ NOTIFY_OBSERVERS(MIGRATION_ITEMBEFOREMIGRATE, index.get());
+
+ nsCOMPtr<nsISupports> supports;
+ rv = aImportModule->GetImportInterface(NS_IMPORT_SETTINGS_STR,
+ getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImportSettings> importSettings = do_QueryInterface(supports);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool importedSettings = false;
+
+ rv = importSettings->Import(getter_AddRefs(mLocalFolderAccount),
+ &importedSettings);
+
+ NOTIFY_OBSERVERS(MIGRATION_ITEMAFTERMIGRATE, index.get());
+
+ return rv;
+}
+
+nsresult nsProfileMigratorBase::ImportAddressBook(
+ nsIImportModule* aImportModule) {
+ nsresult rv;
+
+ nsAutoString index;
+ index.AppendInt(nsIMailProfileMigrator::ADDRESSBOOK_DATA);
+ NOTIFY_OBSERVERS(MIGRATION_ITEMBEFOREMIGRATE, index.get());
+
+ nsCOMPtr<nsISupports> supports;
+ rv = aImportModule->GetImportInterface(NS_IMPORT_ADDRESS_STR,
+ getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mGenericImporter = do_QueryInterface(supports);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsCString> pabString =
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We want to migrate the Outlook addressbook into our personal address book.
+ pabString->SetData(nsDependentCString(kPersonalAddressbookUri));
+ mGenericImporter->SetData("addressDestination", pabString);
+
+ bool importResult;
+ bool wantsProgress;
+ mGenericImporter->WantsProgress(&wantsProgress);
+ rv = mGenericImporter->BeginImport(nullptr, nullptr, &importResult);
+
+ if (wantsProgress)
+ ContinueImport();
+ else
+ FinishCopyingAddressBookData();
+
+ return rv;
+}
+
+nsresult nsProfileMigratorBase::FinishCopyingAddressBookData() {
+ nsAutoString index;
+ index.AppendInt(nsIMailProfileMigrator::ADDRESSBOOK_DATA);
+ NOTIFY_OBSERVERS(MIGRATION_ITEMAFTERMIGRATE, index.get());
+
+ // now kick off the mail migration code
+ ImportMailData(mImportModule);
+
+ return NS_OK;
+}
+
+nsresult nsProfileMigratorBase::ImportMailData(nsIImportModule* aImportModule) {
+ nsresult rv;
+
+ nsAutoString index;
+ index.AppendInt(nsIMailProfileMigrator::MAILDATA);
+ NOTIFY_OBSERVERS(MIGRATION_ITEMBEFOREMIGRATE, index.get());
+
+ nsCOMPtr<nsISupports> supports;
+ rv = aImportModule->GetImportInterface(NS_IMPORT_MAIL_STR,
+ getter_AddRefs(supports));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mGenericImporter = do_QueryInterface(supports);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsPRBool> migrating =
+ do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // by setting the migration flag, we force the import utility to install local
+ // folders from OE directly into Local Folders and not as a subfolder
+ migrating->SetData(true);
+ mGenericImporter->SetData("migration", migrating);
+
+ bool importResult;
+ bool wantsProgress;
+ mGenericImporter->WantsProgress(&wantsProgress);
+ rv = mGenericImporter->BeginImport(nullptr, nullptr, &importResult);
+
+ mProcessingMailFolders = true;
+
+ if (wantsProgress)
+ ContinueImport();
+ else
+ FinishCopyingMailFolders();
+
+ return rv;
+}
+
+nsresult nsProfileMigratorBase::FinishCopyingMailFolders() {
+ nsAutoString index;
+ index.AppendInt(nsIMailProfileMigrator::MAILDATA);
+ NOTIFY_OBSERVERS(MIGRATION_ITEMAFTERMIGRATE, index.get());
+
+ // now kick off the filters migration code
+ return ImportFilters(mImportModule);
+}
+
+nsresult nsProfileMigratorBase::ImportFilters(nsIImportModule* aImportModule) {
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsISupports> supports;
+ nsresult rv2 = aImportModule->GetImportInterface(NS_IMPORT_FILTERS_STR,
+ getter_AddRefs(supports));
+ nsCOMPtr<nsIImportFilters> importFilters = do_QueryInterface(supports);
+
+ if (NS_SUCCEEDED(rv2) && importFilters) {
+ nsAutoString index;
+ index.AppendInt(nsIMailProfileMigrator::FILTERS);
+ NOTIFY_OBSERVERS(MIGRATION_ITEMBEFOREMIGRATE, index.get());
+
+ bool importedFilters = false;
+ char16_t* error;
+
+ rv = importFilters->Import(&error, &importedFilters);
+
+ NOTIFY_OBSERVERS(MIGRATION_ITEMAFTERMIGRATE, index.get());
+ }
+
+ // migration is now done...notify the UI.
+ NOTIFY_OBSERVERS(MIGRATION_ENDED, nullptr);
+
+ return rv;
+}
diff --git a/comm/mail/components/migration/src/nsProfileMigratorBase.h b/comm/mail/components/migration/src/nsProfileMigratorBase.h
new file mode 100644
index 0000000000..6ca0d7fcb4
--- /dev/null
+++ b/comm/mail/components/migration/src/nsProfileMigratorBase.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef profilemigratorbase___h___
+#define profilemigratorbase___h___
+
+#include "nsIFile.h"
+#include "nsIObserverService.h"
+#include "nsITimer.h"
+#include "nsIImportGeneric.h"
+#include "nsIImportModule.h"
+#include "nsIMsgAccount.h"
+
+class nsProfileMigratorBase {
+ public:
+ nsProfileMigratorBase();
+ virtual ~nsProfileMigratorBase();
+ virtual nsresult ContinueImport() = 0;
+
+ protected:
+ nsresult ImportSettings(nsIImportModule* aImportModule);
+ nsresult ImportAddressBook(nsIImportModule* aImportModule);
+ nsresult ImportMailData(nsIImportModule* aImportModule);
+ nsresult ImportFilters(nsIImportModule* aImportModule);
+ nsresult FinishCopyingAddressBookData();
+ nsresult FinishCopyingMailFolders();
+
+ nsCOMPtr<nsIObserverService> mObserverService;
+ nsCOMPtr<nsITimer> mFileIOTimer;
+ nsCOMPtr<nsIImportGeneric> mGenericImporter;
+ nsCOMPtr<nsIImportModule> mImportModule;
+ nsCOMPtr<nsIMsgAccount>
+ mLocalFolderAccount; // needed for nsIImportSettings::Import
+ bool mProcessingMailFolders; // we are either asynchronously parsing address
+ // books or mail folders
+};
+
+#endif
diff --git a/comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.cpp b/comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.cpp
new file mode 100644
index 0000000000..27251462c9
--- /dev/null
+++ b/comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.cpp
@@ -0,0 +1,1175 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsMailProfileMigratorUtils.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIMsgAccountManager.h"
+#include "nsISmtpServer.h"
+#include "nsISmtpService.h"
+#include "nsIPrefLocalizedString.h"
+#include "nsIPrefService.h"
+#include "nsISupportsPrimitives.h"
+#include "nsNetCID.h"
+#include "nsNetUtil.h"
+#include "nsSeamonkeyProfileMigrator.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsComponentManagerUtils.h" // for do_CreateInstance
+#include "mozilla/ArrayUtils.h"
+#include "nsIFile.h"
+
+#include "nsIAbManager.h"
+#include "nsIAbDirectory.h"
+#include "../../../../mailnews/import/src/MorkImport.h"
+
+// Mail specific folder paths
+#define MAIL_DIR_50_NAME u"Mail"_ns
+#define IMAP_MAIL_DIR_50_NAME u"ImapMail"_ns
+#define NEWS_DIR_50_NAME u"News"_ns
+
+///////////////////////////////////////////////////////////////////////////////
+// nsSeamonkeyProfileMigrator
+#define FILE_NAME_JUNKTRAINING u"training.dat"_ns
+#define FILE_NAME_PERSONALDICTIONARY u"persdict.dat"_ns
+#define FILE_NAME_PERSONAL_ADDRESSBOOK u"abook.mab"_ns
+#define FILE_NAME_MAILVIEWS u"mailviews.dat"_ns
+#define FILE_NAME_CERT9DB u"cert9.db"_ns
+#define FILE_NAME_KEY4DB u"key4.db"_ns
+#define FILE_NAME_SECMODDB u"secmod.db"_ns
+#define FILE_NAME_PREFS u"prefs.js"_ns
+#define FILE_NAME_USER_PREFS u"user.js"_ns
+
+struct PrefBranchStruct {
+ char* prefName;
+ int32_t type;
+ union {
+ char* stringValue;
+ int32_t intValue;
+ bool boolValue;
+ char16_t* wstringValue;
+ };
+};
+
+NS_IMPL_ISUPPORTS(nsSeamonkeyProfileMigrator, nsIMailProfileMigrator,
+ nsITimerCallback)
+
+nsSeamonkeyProfileMigrator::nsSeamonkeyProfileMigrator() {}
+
+nsSeamonkeyProfileMigrator::~nsSeamonkeyProfileMigrator() {}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsIMailProfileMigrator
+
+NS_IMETHODIMP
+nsSeamonkeyProfileMigrator::Migrate(uint16_t aItems,
+ nsIProfileStartup* aStartup,
+ const char16_t* aProfile) {
+ nsresult rv = NS_OK;
+ bool aReplace = aStartup ? true : false;
+
+ if (!mTargetProfile) {
+ GetProfilePath(aStartup, mTargetProfile);
+ if (!mTargetProfile) return NS_ERROR_FAILURE;
+ }
+ if (!mSourceProfile) {
+ GetSourceProfile(aProfile);
+ if (!mSourceProfile) return NS_ERROR_FAILURE;
+ }
+
+ NOTIFY_OBSERVERS(MIGRATION_STARTED, nullptr);
+
+ if (aReplace) {
+ CopyPreferences(aReplace);
+ } else {
+ ImportPreferences(aItems);
+ }
+
+ // fake notifications for things we've already imported as part of
+ // CopyPreferences
+ COPY_DATA(DummyCopyRoutine, aReplace,
+ nsIMailProfileMigrator::ACCOUNT_SETTINGS);
+ COPY_DATA(DummyCopyRoutine, aReplace, nsIMailProfileMigrator::NEWSDATA);
+
+ // copy junk mail training file
+ COPY_DATA(CopyJunkTraining, aReplace, nsIMailProfileMigrator::JUNKTRAINING);
+ COPY_DATA(CopyPasswords, aReplace, nsIMailProfileMigrator::PASSWORDS);
+
+ // the last thing to do is to actually copy over any mail folders we have
+ // marked for copying we want to do this last and it will be asynchronous so
+ // the UI doesn't freeze up while we perform this potentially very long
+ // operation.
+
+ nsAutoString index;
+ index.AppendInt(nsIMailProfileMigrator::MAILDATA);
+ NOTIFY_OBSERVERS(MIGRATION_ITEMBEFOREMIGRATE, index.get());
+
+ // Generate the max progress value now that we know all of the files we need
+ // to copy
+ uint32_t count = mFileCopyTransactions.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ fileTransactionEntry fileTransaction = mFileCopyTransactions.ElementAt(i);
+ int64_t fileSize;
+ fileTransaction.srcFile->GetFileSize(&fileSize);
+ mMaxProgress += fileSize;
+ }
+
+ CopyNextFolder();
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsSeamonkeyProfileMigrator::GetMigrateData(const char16_t* aProfile,
+ bool aReplace, uint16_t* aResult) {
+ *aResult = 0;
+
+ if (!mSourceProfile) {
+ GetSourceProfile(aProfile);
+ if (!mSourceProfile) return NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ MigrationData data[] = {
+ {ToNewUnicode(FILE_NAME_PREFS), nsIMailProfileMigrator::SETTINGS, false},
+ {ToNewUnicode(FILE_NAME_JUNKTRAINING),
+ nsIMailProfileMigrator::JUNKTRAINING, true},
+ };
+
+ // Frees file name strings allocated above.
+ GetMigrateDataFromArray(data, sizeof(data) / sizeof(MigrationData), aReplace,
+ mSourceProfile, aResult);
+
+ // Now locate passwords
+ nsCString signonsFileName;
+ GetSignonFileName(aReplace, signonsFileName);
+
+ if (!signonsFileName.IsEmpty()) {
+ nsAutoString fileName;
+ CopyASCIItoUTF16(signonsFileName, fileName);
+ nsCOMPtr<nsIFile> sourcePasswordsFile;
+ mSourceProfile->Clone(getter_AddRefs(sourcePasswordsFile));
+ sourcePasswordsFile->Append(fileName);
+
+ bool exists;
+ sourcePasswordsFile->Exists(&exists);
+ if (exists) *aResult |= nsIMailProfileMigrator::PASSWORDS;
+ }
+
+ // add some extra migration fields for things we also migrate
+ *aResult |= nsIMailProfileMigrator::ACCOUNT_SETTINGS |
+ nsIMailProfileMigrator::MAILDATA |
+ nsIMailProfileMigrator::NEWSDATA |
+ nsIMailProfileMigrator::ADDRESSBOOK_DATA;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSeamonkeyProfileMigrator::GetSourceProfiles(nsTArray<nsString>& aResult) {
+ if (mProfileNames.IsEmpty() && mProfileLocations.IsEmpty()) {
+ // Fills mProfileNames and mProfileLocations
+ FillProfileDataFromSeamonkeyRegistry();
+ }
+
+ aResult = mProfileNames.Clone();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsSeamonkeyProfileMigrator::GetSourceProfileLocations(
+ nsTArray<RefPtr<nsIFile>>& aResult) {
+ if (mProfileNames.IsEmpty() && mProfileLocations.IsEmpty()) {
+ // Fills mProfileNames and mProfileLocations
+ FillProfileDataFromSeamonkeyRegistry();
+ }
+
+ aResult = mProfileLocations.Clone();
+ return NS_OK;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// nsSeamonkeyProfileMigrator
+
+nsresult nsSeamonkeyProfileMigrator::GetSourceProfile(
+ const char16_t* aProfile) {
+ uint32_t count = mProfileNames.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ nsString profileName = mProfileNames[i];
+ if (profileName.Equals(aProfile)) {
+ mSourceProfile = mProfileLocations[i];
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSeamonkeyProfileMigrator::FillProfileDataFromSeamonkeyRegistry() {
+ // Find the Seamonkey Registry
+ nsCOMPtr<nsIProperties> fileLocator(
+ do_GetService("@mozilla.org/file/directory_service;1"));
+ nsCOMPtr<nsIFile> seamonkeyData;
+#undef EXTRA_PREPEND
+
+#ifdef XP_WIN
+# define NEW_FOLDER "SeaMonkey"
+# define EXTRA_PREPEND "Mozilla"
+
+ fileLocator->Get(NS_WIN_APPDATA_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(seamonkeyData));
+ NS_ENSURE_TRUE(seamonkeyData, NS_ERROR_FAILURE);
+
+#elif defined(XP_MACOSX)
+# define NEW_FOLDER "SeaMonkey"
+# define EXTRA_PREPEND "Application Support"
+ fileLocator->Get(NS_MAC_USER_LIB_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(seamonkeyData));
+ NS_ENSURE_TRUE(seamonkeyData, NS_ERROR_FAILURE);
+
+#elif defined(XP_UNIX)
+# define NEW_FOLDER "seamonkey"
+# define EXTRA_PREPEND ".mozilla"
+ fileLocator->Get(NS_UNIX_HOME_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(seamonkeyData));
+ NS_ENSURE_TRUE(seamonkeyData, NS_ERROR_FAILURE);
+
+#else
+ // On other OS just abort.
+ return NS_ERROR_FAILURE;
+#endif
+
+ nsCOMPtr<nsIFile> newSeamonkeyData;
+ seamonkeyData->Clone(getter_AddRefs(newSeamonkeyData));
+ NS_ENSURE_TRUE(newSeamonkeyData, NS_ERROR_FAILURE);
+
+#ifdef EXTRA_PREPEND
+ newSeamonkeyData->Append(NS_LITERAL_STRING_FROM_CSTRING(EXTRA_PREPEND));
+#endif
+ newSeamonkeyData->Append(NS_LITERAL_STRING_FROM_CSTRING(NEW_FOLDER));
+
+ nsresult rv = GetProfileDataFromProfilesIni(newSeamonkeyData, mProfileNames,
+ mProfileLocations);
+
+ return rv;
+}
+
+static nsSeamonkeyProfileMigrator::PrefTransform gTransforms[] = {
+
+ MAKESAMETYPEPREFTRANSFORM("signon.SignonFileName", String),
+ MAKESAMETYPEPREFTRANSFORM("mailnews.headers.showUserAgent", Bool),
+ MAKESAMETYPEPREFTRANSFORM("mailnews.headers.showOrganization", Bool),
+ MAKESAMETYPEPREFTRANSFORM("mail.collect_addressbook", String),
+ MAKESAMETYPEPREFTRANSFORM("mail.collect_email_address_outgoing", Bool),
+ MAKESAMETYPEPREFTRANSFORM("mail.wrap_long_lines", Bool),
+ MAKESAMETYPEPREFTRANSFORM("mailnews.customHeaders", String),
+ MAKESAMETYPEPREFTRANSFORM("mail.default_html_action", Int),
+ MAKESAMETYPEPREFTRANSFORM("mail.forward_message_mode", Int),
+ MAKESAMETYPEPREFTRANSFORM("mail.SpellCheckBeforeSend", Bool),
+ MAKESAMETYPEPREFTRANSFORM("mail.warn_on_send_accel_key", Bool),
+ MAKESAMETYPEPREFTRANSFORM("mailnews.headers.showUserAgent", Bool),
+ MAKESAMETYPEPREFTRANSFORM("mailnews.headers.showOrganization", Bool),
+ MAKESAMETYPEPREFTRANSFORM("mail.biff.play_sound", Bool),
+ MAKESAMETYPEPREFTRANSFORM("mail.biff.play_sound.type", Int),
+ MAKESAMETYPEPREFTRANSFORM("mail.biff.play_sound.url", String),
+ MAKESAMETYPEPREFTRANSFORM("mail.biff.show_alert", Bool),
+ MAKESAMETYPEPREFTRANSFORM("network.proxy.type", Int),
+ MAKESAMETYPEPREFTRANSFORM("network.proxy.http", String),
+ MAKESAMETYPEPREFTRANSFORM("network.proxy.http_port", Int),
+ MAKESAMETYPEPREFTRANSFORM("network.proxy.ftp", String),
+ MAKESAMETYPEPREFTRANSFORM("network.proxy.ftp_port", Int),
+ MAKESAMETYPEPREFTRANSFORM("network.proxy.ssl", String),
+ MAKESAMETYPEPREFTRANSFORM("network.proxy.ssl_port", Int),
+ MAKESAMETYPEPREFTRANSFORM("network.proxy.socks", String),
+ MAKESAMETYPEPREFTRANSFORM("network.proxy.socks_port", Int),
+ MAKESAMETYPEPREFTRANSFORM("network.proxy.no_proxies_on", String),
+ MAKESAMETYPEPREFTRANSFORM("network.proxy.autoconfig_url", String),
+
+ MAKESAMETYPEPREFTRANSFORM("mail.accountmanager.accounts", String),
+ MAKESAMETYPEPREFTRANSFORM("mail.accountmanager.defaultaccount", String),
+ MAKESAMETYPEPREFTRANSFORM("mail.accountmanager.localfoldersserver", String),
+ MAKESAMETYPEPREFTRANSFORM("mail.smtp.defaultserver", String),
+ MAKESAMETYPEPREFTRANSFORM("mail.smtpservers", String),
+
+ MAKESAMETYPEPREFTRANSFORM("msgcompose.font_face", String),
+ MAKESAMETYPEPREFTRANSFORM("msgcompose.font_size", String),
+ MAKESAMETYPEPREFTRANSFORM("msgcompose.text_color", String),
+ MAKESAMETYPEPREFTRANSFORM("msgcompose.background_color", String),
+
+ MAKEPREFTRANSFORM("mail.pane_config", "mail.pane_config.dynamic", Int,
+ Int)};
+
+/**
+ * Use the current Seamonkey's prefs.js as base, and transform some branches.
+ * Thunderbird's prefs.js is thrown away.
+ */
+nsresult nsSeamonkeyProfileMigrator::TransformPreferences(
+ const nsAString& aSourcePrefFileName,
+ const nsAString& aTargetPrefFileName) {
+ PrefTransform* transform;
+ PrefTransform* end =
+ gTransforms + sizeof(gTransforms) / sizeof(PrefTransform);
+
+ // Load the source pref file
+ nsCOMPtr<nsIPrefService> psvc(do_GetService(NS_PREFSERVICE_CONTRACTID));
+ psvc->ResetPrefs();
+
+ nsCOMPtr<nsIFile> sourcePrefsFile;
+ mSourceProfile->Clone(getter_AddRefs(sourcePrefsFile));
+ sourcePrefsFile->Append(aSourcePrefFileName);
+ psvc->ReadUserPrefsFromFile(sourcePrefsFile);
+
+ nsCOMPtr<nsIPrefBranch> branch(do_QueryInterface(psvc));
+ for (transform = gTransforms; transform < end; ++transform)
+ transform->prefGetterFunc(transform, branch);
+
+ static const char* branchNames[] = {
+ // Keep the three below first, or change the indexes below
+ "mail.identity.", "mail.server.", "ldap_2.servers.",
+ "mail.account.", "mail.smtpserver.", "mailnews.labels.",
+ "mailnews.tags."};
+
+ // read in the various pref branch trees for accounts, identities, servers,
+ // etc.
+ PBStructArray branches[MOZ_ARRAY_LENGTH(branchNames)];
+ uint32_t i;
+ for (i = 0; i < MOZ_ARRAY_LENGTH(branchNames); ++i)
+ ReadBranch(branchNames[i], psvc, branches[i]);
+
+ // The signature file prefs may be paths to files in the seamonkey profile
+ // path so we need to copy them over and fix these paths up before we write
+ // them out to the new prefs.js.
+ CopySignatureFiles(branches[0], psvc);
+
+ // Certain mail prefs may actually be absolute paths instead of profile
+ // relative paths we need to fix these paths up before we write them out to
+ // the new prefs.js
+ CopyMailFolders(branches[1], psvc);
+
+ TransformAddressbooksForImport(psvc, branches[2], true);
+
+ // Now that we have all the pref data in memory, load the target pref file,
+ // and write it back out.
+ psvc->ResetPrefs();
+
+ // XXX Re-order this?
+
+ for (transform = gTransforms; transform < end; ++transform)
+ transform->prefSetterFunc(transform, branch);
+
+ for (i = 0; i < MOZ_ARRAY_LENGTH(branchNames); i++)
+ WriteBranch(branchNames[i], psvc, branches[i]);
+
+ nsCOMPtr<nsIFile> targetPrefsFile;
+ mTargetProfile->Clone(getter_AddRefs(targetPrefsFile));
+ targetPrefsFile->Append(aTargetPrefFileName);
+ psvc->SavePrefFile(targetPrefsFile);
+
+ return NS_OK;
+}
+
+nsresult nsSeamonkeyProfileMigrator::CopySignatureFiles(
+ PBStructArray& aIdentities, nsIPrefService* aPrefService) {
+ nsresult rv = NS_OK;
+
+ uint32_t count = aIdentities.Length();
+ for (uint32_t i = 0; i < count; ++i) {
+ PrefBranchStruct* pref = aIdentities.ElementAt(i);
+ nsDependentCString prefName(pref->prefName);
+
+ // a partial fix for bug #255043
+ // if the user's signature file from seamonkey lives in the
+ // seamonkey profile root, we'll copy it over to the new
+ // thunderbird profile root and then set the pref to the new value
+ // note, this doesn't work for multiple signatures that live
+ // below the seamonkey profile root
+ if (StringEndsWith(prefName, ".sig_file"_ns)) {
+ // turn the pref into a nsIFile
+ nsCOMPtr<nsIFile> srcSigFile =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+ rv = srcSigFile->SetPersistentDescriptor(
+ nsDependentCString(pref->stringValue));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> targetSigFile;
+ rv = mTargetProfile->Clone(getter_AddRefs(targetSigFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // now make the copy
+ bool exists;
+ srcSigFile->Exists(&exists);
+ if (exists) {
+ nsAutoString leafName;
+ srcSigFile->GetLeafName(leafName);
+ srcSigFile->CopyTo(
+ targetSigFile,
+ leafName); // will fail if we've already copied a sig file here
+ targetSigFile->Append(leafName);
+
+ // now write out the new descriptor
+ nsAutoCString descriptorString;
+ rv = targetSigFile->GetPersistentDescriptor(descriptorString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ free(pref->stringValue);
+ pref->stringValue = ToNewCString(descriptorString);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsSeamonkeyProfileMigrator::CopyMailFolders(
+ PBStructArray& aMailServers, nsIPrefService* aPrefService) {
+ // Each server has a .directory pref which points to the location of the mail
+ // data for that server. We need to do two things for that case...
+ // (1) Fix up the directory path for the new profile
+ // (2) copy the mail folder data from the source directory pref to the
+ // destination directory pref
+
+ nsresult rv;
+ uint32_t count = aMailServers.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ PrefBranchStruct* pref = aMailServers.ElementAt(i);
+ nsDependentCString prefName(pref->prefName);
+
+ if (StringEndsWith(prefName, ".directory-rel"_ns)) {
+ // When the directories are modified below, we may change the .directory
+ // pref. As we don't have a pref branch to modify at this stage and set
+ // up the relative folders properly, we'll just remove all the
+ // *.directory-rel prefs. Mailnews will cope with this, creating them
+ // when it first needs them.
+ if (pref->type == nsIPrefBranch::PREF_STRING) free(pref->stringValue);
+
+ aMailServers.RemoveElementAt(i);
+ // Now decrease i and count to match the removed element
+ --i;
+ --count;
+ } else if (StringEndsWith(prefName, ".directory"_ns)) {
+ // let's try to get a branch for this particular server to simplify things
+ prefName.Cut(prefName.Length() - strlen("directory"),
+ strlen("directory"));
+ prefName.Insert("mail.server.", 0);
+
+ nsCOMPtr<nsIPrefBranch> serverBranch;
+ aPrefService->GetBranch(prefName.get(), getter_AddRefs(serverBranch));
+
+ if (!serverBranch)
+ break; // should we clear out this server pref from aMailServers?
+
+ nsCString serverType;
+ serverBranch->GetCharPref("type", serverType);
+
+ nsCOMPtr<nsIFile> sourceMailFolder;
+ serverBranch->GetComplexValue("directory", NS_GET_IID(nsIFile),
+ getter_AddRefs(sourceMailFolder));
+
+ // now based on type, we need to build a new destination path for the mail
+ // folders for this server
+ nsCOMPtr<nsIFile> targetMailFolder;
+ if (serverType.Equals("imap")) {
+ mTargetProfile->Clone(getter_AddRefs(targetMailFolder));
+ targetMailFolder->Append(IMAP_MAIL_DIR_50_NAME);
+ } else if (serverType.Equals("none") || serverType.Equals("pop3") ||
+ serverType.Equals("rss")) {
+ // local folders and POP3 servers go under <profile>\Mail
+ mTargetProfile->Clone(getter_AddRefs(targetMailFolder));
+ targetMailFolder->Append(MAIL_DIR_50_NAME);
+ } else if (serverType.Equals("nntp")) {
+ mTargetProfile->Clone(getter_AddRefs(targetMailFolder));
+ targetMailFolder->Append(NEWS_DIR_50_NAME);
+ }
+
+ if (targetMailFolder) {
+ // for all of our server types, append the host name to the directory as
+ // part of the new location
+ nsCString hostName;
+ serverBranch->GetCharPref("hostname", hostName);
+ targetMailFolder->Append(NS_ConvertASCIItoUTF16(hostName));
+
+ // we should make sure the host name based directory we are going to
+ // migrate the accounts into is unique. This protects against the case
+ // where the user has multiple servers with the same host name.
+ rv = targetMailFolder->CreateUnique(nsIFile::DIRECTORY_TYPE, 0777);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ (void)RecursiveCopy(sourceMailFolder, targetMailFolder);
+ // now we want to make sure the actual directory pref that gets
+ // transformed into the new profile's pref.js has the right file
+ // location.
+ nsAutoCString descriptorString;
+ rv = targetMailFolder->GetPersistentDescriptor(descriptorString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ free(pref->stringValue);
+ pref->stringValue = ToNewCString(descriptorString);
+ }
+ } else if (StringEndsWith(prefName, ".newsrc.file"_ns)) {
+ // copy the news RC file into \News. this won't work if the user has
+ // different newsrc files for each account I don't know what to do in that
+ // situation.
+
+ nsCOMPtr<nsIFile> targetNewsRCFile;
+ mTargetProfile->Clone(getter_AddRefs(targetNewsRCFile));
+ targetNewsRCFile->Append(NEWS_DIR_50_NAME);
+
+ // turn the pref into a nsIFile
+ nsCOMPtr<nsIFile> srcNewsRCFile =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+ rv = srcNewsRCFile->SetPersistentDescriptor(
+ nsDependentCString(pref->stringValue));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // now make the copy
+ bool exists;
+ srcNewsRCFile->Exists(&exists);
+ if (exists) {
+ nsAutoString leafName;
+ srcNewsRCFile->GetLeafName(leafName);
+ srcNewsRCFile->CopyTo(
+ targetNewsRCFile,
+ leafName); // will fail if we've already copied a newsrc file here
+ targetNewsRCFile->Append(leafName);
+
+ // now write out the new descriptor
+ nsAutoCString descriptorString;
+ rv = targetNewsRCFile->GetPersistentDescriptor(descriptorString);
+ NS_ENSURE_SUCCESS(rv, rv);
+ free(pref->stringValue);
+ pref->stringValue = ToNewCString(descriptorString);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSeamonkeyProfileMigrator::CopyPreferences(bool aReplace) {
+ nsresult rv = NS_OK;
+ nsresult tmp;
+
+ tmp = TransformPreferences(FILE_NAME_PREFS, FILE_NAME_PREFS);
+
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = CopyFile(FILE_NAME_USER_PREFS, FILE_NAME_USER_PREFS);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ // Security Stuff
+ tmp = CopyFile(FILE_NAME_CERT9DB, FILE_NAME_CERT9DB);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = CopyFile(FILE_NAME_KEY4DB, FILE_NAME_KEY4DB);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = CopyFile(FILE_NAME_SECMODDB, FILE_NAME_SECMODDB);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+
+ tmp = CopyFile(FILE_NAME_PERSONALDICTIONARY, FILE_NAME_PERSONALDICTIONARY);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ tmp = CopyFile(FILE_NAME_MAILVIEWS, FILE_NAME_MAILVIEWS);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ return rv;
+}
+
+/**
+ * Use the current Thunderbird's prefs.js as base, transform branches of
+ * Seamonkey's prefs.js so that those branches can be imported without conflicts
+ * or overwriting.
+ */
+nsresult nsSeamonkeyProfileMigrator::ImportPreferences(uint16_t aItems) {
+ nsresult rv;
+ nsCOMPtr<nsIPrefService> psvc(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Because all operations on nsIPrefService or nsIPrefBranch will update
+ // prefs.js directly, we need to backup the current pref file to be used as a
+ // base later.
+ nsCOMPtr<nsIFile> targetPrefsFile;
+ mTargetProfile->Clone(getter_AddRefs(targetPrefsFile));
+ targetPrefsFile->Append(FILE_NAME_PREFS + u".orig"_ns);
+ rv = psvc->SavePrefFile(targetPrefsFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Load the source pref file.
+ rv = psvc->ResetPrefs();
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIFile> sourcePrefsFile;
+ mSourceProfile->Clone(getter_AddRefs(sourcePrefsFile));
+ sourcePrefsFile->Append(FILE_NAME_PREFS);
+ rv = psvc->ReadUserPrefsFromFile(sourcePrefsFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Read in the various pref branch trees for accounts, identities, servers,
+ // etc.
+ static const char* branchNames[] = {"mail.identity.", "mail.server.",
+ "mail.account.", "mail.smtpserver.",
+ "mailnews.labels.", "mailnews.tags.",
+ "ldap_2.servers."};
+ PBStructArray sourceBranches[MOZ_ARRAY_LENGTH(branchNames)];
+ for (uint32_t i = 0; i < MOZ_ARRAY_LENGTH(branchNames); i++) {
+ if ((!(aItems & nsIMailProfileMigrator::SETTINGS) && i <= 5) ||
+ (!(aItems & nsIMailProfileMigrator::ADDRESSBOOK_DATA) && i == 6)) {
+ continue;
+ }
+ ReadBranch(branchNames[i], psvc, sourceBranches[i]);
+ }
+
+ // Read back the original prefs.
+ rv = psvc->ResetPrefs();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = psvc->ReadUserPrefsFromFile(targetPrefsFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager(
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ PrefKeyHashTable smtpServerKeyHashTable;
+ PrefKeyHashTable identityKeyHashTable;
+ PrefKeyHashTable serverKeyHashTable;
+
+ // Transforming order is important here.
+ TransformSmtpServersForImport(sourceBranches[3], smtpServerKeyHashTable);
+
+ // mail.identity.idN.smtpServer depends on previous step.
+ TransformIdentitiesForImport(sourceBranches[0], accountManager,
+ smtpServerKeyHashTable, identityKeyHashTable);
+
+ TransformMailServersForImport(branchNames[1], psvc, sourceBranches[1],
+ accountManager, serverKeyHashTable);
+
+ // mail.accountN.{identities,server} depends on previous steps.
+ TransformMailAccountsForImport(psvc, sourceBranches[2], accountManager,
+ identityKeyHashTable, serverKeyHashTable);
+
+ // CopyMailFolders requires mail.server.serverN branch exists.
+ WriteBranch(branchNames[1], psvc, sourceBranches[1], false);
+ CopyMailFolders(sourceBranches[1], psvc);
+
+ // TransformAddressbooksForImport writes the branch and migrates the files.
+ TransformAddressbooksForImport(psvc, sourceBranches[6], false);
+
+ for (uint32_t i = 0; i < MOZ_ARRAY_LENGTH(branchNames); i++)
+ WriteBranch(branchNames[i], psvc, sourceBranches[i]);
+
+ targetPrefsFile->Remove(false);
+ return rv;
+}
+
+/**
+ * Transform mail.identity branch.
+ */
+nsresult nsSeamonkeyProfileMigrator::TransformIdentitiesForImport(
+ PBStructArray& aIdentities, nsIMsgAccountManager* accountManager,
+ PrefKeyHashTable& smtpServerKeyHashTable, PrefKeyHashTable& keyHashTable) {
+ nsresult rv;
+ nsTArray<nsCString> newKeys;
+
+ for (auto pref : aIdentities) {
+ nsDependentCString prefName(pref->prefName);
+ nsTArray<nsCString> keys;
+ ParseString(prefName, '.', keys);
+ auto key = keys[0];
+ if (key == "default") {
+ continue;
+ } else if (StringEndsWith(prefName, ".smtpServer"_ns)) {
+ nsDependentCString serverKey(pref->stringValue);
+ nsCString newServerKey;
+ if (smtpServerKeyHashTable.Get(serverKey, &newServerKey)) {
+ pref->stringValue = moz_xstrdup(newServerKey.get());
+ }
+ }
+
+ // For every seamonkey identity, create a new one to avoid conflicts.
+ nsCString newKey;
+ if (!keyHashTable.Get(key, &newKey)) {
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv = accountManager->CreateIdentity(getter_AddRefs(identity));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ identity->GetKey(newKey);
+ keyHashTable.InsertOrUpdate(key, newKey);
+ }
+
+ // Replace the prefName with the new key.
+ prefName.Assign(moz_xstrdup(newKey.get()));
+ for (uint32_t j = 1; j < keys.Length(); j++) {
+ prefName.Append('.');
+ prefName.Append(keys[j]);
+ }
+ pref->prefName = moz_xstrdup(prefName.get());
+ }
+ return NS_OK;
+}
+
+/**
+ * Transform mail.account branch. Also update mail.accountmanager.accounts at
+ * the end.
+ */
+nsresult nsSeamonkeyProfileMigrator::TransformMailAccountsForImport(
+ nsIPrefService* aPrefService, PBStructArray& aAccounts,
+ nsIMsgAccountManager* accountManager,
+ PrefKeyHashTable& identityKeyHashTable,
+ PrefKeyHashTable& serverKeyHashTable) {
+ nsTHashMap<nsCStringHashKey, nsCString> keyHashTable;
+ nsTArray<nsCString> newKeys;
+
+ for (auto pref : aAccounts) {
+ nsDependentCString prefName(pref->prefName);
+ nsTArray<nsCString> keys;
+ ParseString(prefName, '.', keys);
+ auto key = keys[0];
+ if (key == "default") {
+ continue;
+ } else if (StringEndsWith(prefName, ".identities"_ns)) {
+ nsDependentCString identityKey(pref->stringValue);
+ nsCString newIdentityKey;
+ if (identityKeyHashTable.Get(identityKey, &newIdentityKey)) {
+ pref->stringValue = moz_xstrdup(newIdentityKey.get());
+ }
+ } else if (StringEndsWith(prefName, ".server"_ns)) {
+ nsDependentCString serverKey(pref->stringValue);
+ nsCString newServerKey;
+ if (serverKeyHashTable.Get(serverKey, &newServerKey)) {
+ pref->stringValue = moz_xstrdup(newServerKey.get());
+ }
+ }
+
+ // For every seamonkey account, create a new one to avoid conflicts.
+ nsCString newKey;
+ if (!keyHashTable.Get(key, &newKey)) {
+ accountManager->GetUniqueAccountKey(newKey);
+ newKeys.AppendElement(newKey);
+ keyHashTable.InsertOrUpdate(key, newKey);
+ }
+
+ // Replace the prefName with the new key.
+ prefName.Assign(moz_xstrdup(newKey.get()));
+ for (uint32_t j = 1; j < keys.Length(); j++) {
+ prefName.Append('.');
+ prefName.Append(keys[j]);
+ }
+ pref->prefName = moz_xstrdup(prefName.get());
+ }
+
+ // Append newly create accounts to mail.accountmanager.accounts.
+ nsCOMPtr<nsIPrefBranch> branch;
+ nsCString newAccounts;
+ uint32_t count = newKeys.Length();
+ if (count) {
+ nsresult rv =
+ aPrefService->GetBranch("mail.accountmanager.", getter_AddRefs(branch));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = branch->GetCharPref("accounts", newAccounts);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ for (uint32_t i = 0; i < count; i++) {
+ newAccounts.Append(',');
+ newAccounts.Append(newKeys[i]);
+ }
+ if (count) {
+ (void)branch->SetCharPref("accounts", newAccounts);
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Transform mail.server branch.
+ */
+nsresult nsSeamonkeyProfileMigrator::TransformMailServersForImport(
+ const char* branchName, nsIPrefService* aPrefService,
+ PBStructArray& aMailServers, nsIMsgAccountManager* accountManager,
+ PrefKeyHashTable& keyHashTable) {
+ nsTArray<nsCString> newKeys;
+
+ for (auto pref : aMailServers) {
+ nsDependentCString prefName(pref->prefName);
+ nsTArray<nsCString> keys;
+ ParseString(prefName, '.', keys);
+ auto key = keys[0];
+ if (key == "default") {
+ continue;
+ }
+ nsCString newKey;
+ bool exists = keyHashTable.Get(key, &newKey);
+ if (!exists) {
+ do {
+ // Since updating prefs.js is batched, GetUniqueServerKey may return the
+ // previous key. Sleep 500ms and check if the returned key already
+ // exists to workaround it.
+ PR_Sleep(PR_MillisecondsToInterval(500));
+ accountManager->GetUniqueServerKey(newKey);
+ } while (newKeys.Contains(newKey));
+ newKeys.AppendElement(newKey);
+ keyHashTable.InsertOrUpdate(key, newKey);
+ }
+
+ prefName.Assign(moz_xstrdup(newKey.get()));
+ for (uint32_t j = 1; j < keys.Length(); j++) {
+ prefName.Append('.');
+ prefName.Append(keys[j]);
+ }
+
+ pref->prefName = moz_xstrdup(prefName.get());
+
+ // Set `mail.server.serverN.type` so that GetUniqueServerKey next time will
+ // get a new key.
+ if (!exists) {
+ nsCOMPtr<nsIPrefBranch> branch;
+ nsAutoCString serverTypeKey;
+ serverTypeKey.Assign(newKey.get());
+ serverTypeKey.AppendLiteral(".type");
+ nsresult rv = aPrefService->GetBranch(branchName, getter_AddRefs(branch));
+ NS_ENSURE_SUCCESS(rv, rv);
+ (void)branch->SetCharPref(serverTypeKey.get(), "placeholder"_ns);
+ }
+ }
+ return NS_OK;
+}
+
+/**
+ * Transform mail.smtpserver branch.
+ * CreateServer will update mail.smtpservers for us.
+ */
+nsresult nsSeamonkeyProfileMigrator::TransformSmtpServersForImport(
+ PBStructArray& aServers, PrefKeyHashTable& keyHashTable) {
+ nsresult rv;
+ nsCOMPtr<nsISmtpService> smtpService(
+ do_GetService("@mozilla.org/messengercompose/smtp;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<nsCString> newKeys;
+
+ for (auto pref : aServers) {
+ nsDependentCString prefName(pref->prefName);
+ nsTArray<nsCString> keys;
+ ParseString(prefName, '.', keys);
+ auto key = keys[0];
+ if (key == "default") {
+ continue;
+ }
+
+ // For every seamonkey smtp server, create a new one to avoid conflicts.
+ nsCString newKey;
+ if (!keyHashTable.Get(key, &newKey)) {
+ nsCOMPtr<nsISmtpServer> server;
+ rv = smtpService->CreateServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char* str;
+ server->GetKey(&str);
+ newKey.Assign(str);
+ newKeys.AppendElement(newKey);
+ keyHashTable.InsertOrUpdate(key, newKey);
+ }
+
+ // Replace the prefName with the new key.
+ prefName.Assign(moz_xstrdup(newKey.get()));
+ for (uint32_t j = 1; j < keys.Length(); j++) {
+ prefName.Append('.');
+ prefName.Append(keys[j]);
+ }
+ pref->prefName = moz_xstrdup(prefName.get());
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Transform ldap_2.servers branch.
+ */
+nsresult nsSeamonkeyProfileMigrator::TransformAddressbooksForImport(
+ nsIPrefService* aPrefService, PBStructArray& aAddressbooks, bool aReplace) {
+ nsTHashMap<nsCStringHashKey, nsCString> keyHashTable;
+ nsTHashMap<nsCStringHashKey, nsCString> pendingMigrations;
+ nsTArray<nsCString> newKeys;
+ nsresult rv;
+
+ nsCOMPtr<nsIPrefBranch> branch;
+ rv = aPrefService->GetBranch("ldap_2.servers.", getter_AddRefs(branch));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (auto pref : aAddressbooks) {
+ nsDependentCString prefName(pref->prefName);
+ nsTArray<nsCString> keys;
+ ParseString(prefName, '.', keys);
+ auto key = keys[0];
+ if (key == "default") {
+ continue;
+ }
+
+ nsCString newKey;
+ if (aReplace) {
+ newKey.Assign(key);
+ } else {
+ // For every addressbook, create a new one to avoid conflicts.
+ if (!keyHashTable.Get(key, &newKey)) {
+ uint32_t uniqueCount = 0;
+
+ while (true) {
+ nsAutoCString filenameKey;
+ nsAutoCString filename;
+ filenameKey.Assign(key);
+ filenameKey.AppendInt(++uniqueCount);
+ filenameKey.AppendLiteral(".filename");
+ nsresult rv = branch->GetCharPref(filenameKey.get(), filename);
+ if (NS_FAILED(rv)) {
+ newKey.Assign(key);
+ newKey.AppendInt(uniqueCount);
+ (void)branch->SetCharPref(filenameKey.get(), "placeholder"_ns);
+ break;
+ }
+ }
+ keyHashTable.InsertOrUpdate(key, newKey);
+ }
+ }
+
+ // Replace the prefName with the new key.
+ prefName.Assign(moz_xstrdup(newKey.get()));
+ for (uint32_t j = 1; j < keys.Length(); j++) {
+ prefName.Append('.');
+ prefName.Append(keys[j]);
+
+ if (j == 1) {
+ if (keys[j].Equals("dirType")) {
+ // Make sure we have the right type of directory.
+ pref->intValue = 101;
+ } else if (!aReplace && keys[j].Equals("description") &&
+ !strcmp(pref->stringValue,
+ "chrome://messenger/locale/addressbook/"
+ "addressBook.properties")) {
+ // We're importing the default directories, which have localized
+ // names. The names are tied to the pref's name, which we are
+ // changing, so the localization will fail. Instead, do the
+ // localization here and assign it to the directory being copied.
+ nsCOMPtr<nsIPrefLocalizedString> localizedString;
+ rv = branch->GetComplexValue(pref->prefName,
+ NS_GET_IID(nsIPrefLocalizedString),
+ getter_AddRefs(localizedString));
+ if (NS_SUCCEEDED(rv)) {
+ nsString localizedValue;
+ localizedString->GetData(localizedValue);
+ pref->stringValue =
+ moz_xstrdup(NS_ConvertUTF16toUTF8(localizedValue).get());
+ }
+ } else if (keys[j].Equals("filename")) {
+ // Update the prefs for the new filename of the directory.
+ nsCString oldFileName(pref->stringValue);
+ nsCString newFileName(pref->stringValue);
+
+ if (StringEndsWith(newFileName, nsCString("mab"))) {
+ newFileName.Cut(newFileName.Length() - strlen("mab"),
+ strlen("mab"));
+ newFileName.Append("sqlite");
+ pref->stringValue = moz_xstrdup(newFileName.get());
+ }
+
+ if (!aReplace) {
+ // Find an unused filename in the destination directory.
+ nsCOMPtr<nsIFile> targetAddrbook;
+ mTargetProfile->Clone(getter_AddRefs(targetAddrbook));
+ targetAddrbook->Append(NS_ConvertUTF8toUTF16(newFileName));
+ nsresult rv =
+ targetAddrbook->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsString leafName;
+ targetAddrbook->GetLeafName(leafName);
+
+ pref->stringValue =
+ moz_xstrdup(NS_ConvertUTF16toUTF8(leafName).get());
+ }
+
+ if (StringEndsWith(oldFileName, nsCString("sqlite"))) {
+ nsCOMPtr<nsIFile> oldFile;
+ mSourceProfile->Clone(getter_AddRefs(oldFile));
+ oldFile->Append(NS_ConvertUTF8toUTF16(oldFileName));
+ bool exists = false;
+ oldFile->Exists(&exists);
+ if (exists) {
+ // The source directory already has SQLite directories.
+ // Just copy them.
+ CopyFile(NS_ConvertUTF8toUTF16(oldFileName),
+ NS_ConvertUTF8toUTF16(newFileName));
+ continue;
+ }
+
+ oldFileName.Cut(oldFileName.Length() - strlen("sqlite"),
+ strlen("sqlite"));
+ oldFileName.Append("mab");
+ }
+
+ // Store the directories to be migrated for later.
+ pendingMigrations.InsertOrUpdate(newKey, oldFileName);
+ }
+ }
+ }
+ pref->prefName = moz_xstrdup(prefName.get());
+ }
+
+ // Write out the preferences and ask the address book manager to reload.
+ // This initializes the directories using the new prefs we've just set up.
+ WriteBranch("ldap_2.servers.", aPrefService, aAddressbooks, false);
+ NOTIFY_OBSERVERS("addrbook-reload", nullptr);
+
+ // Do the migration.
+ for (auto iter = pendingMigrations.Iter(); !iter.Done(); iter.Next()) {
+ nsCString dirPrefId = "ldap_2.servers."_ns;
+ dirPrefId.Append(iter.Key());
+ MigrateMABFile(dirPrefId, iter.UserData());
+ }
+
+ return NS_OK;
+}
+
+nsresult nsSeamonkeyProfileMigrator::MigrateMABFile(
+ const nsCString& aDirPrefId, const nsCString& aSourceFileName) {
+ nsCOMPtr<nsIFile> sourceFile;
+ mSourceProfile->Clone(getter_AddRefs(sourceFile));
+
+ sourceFile->Append(NS_ConvertUTF8toUTF16(aSourceFileName));
+ bool exists = false;
+ sourceFile->Exists(&exists);
+ if (!exists) return NS_OK;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIAbManager> abManager(
+ do_GetService("@mozilla.org/abmanager;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ rv = abManager->GetDirectoryFromId(aDirPrefId, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, NS_OK);
+
+ rv = ReadMABToDirectory(sourceFile, directory);
+
+ return NS_OK;
+}
+
+void nsSeamonkeyProfileMigrator::ReadBranch(const char* branchName,
+ nsIPrefService* aPrefService,
+ PBStructArray& aPrefs) {
+ // Enumerate the branch
+ nsCOMPtr<nsIPrefBranch> branch;
+ aPrefService->GetBranch(branchName, getter_AddRefs(branch));
+
+ nsTArray<nsCString> prefs;
+ nsresult rv = branch->GetChildList("", prefs);
+ if (NS_FAILED(rv)) return;
+
+ for (auto& pref : prefs) {
+ // Save each pref's value into an array
+ char* currPref = moz_xstrdup(pref.get());
+ int32_t type;
+ branch->GetPrefType(currPref, &type);
+ PrefBranchStruct* prefBranch = new PrefBranchStruct;
+ prefBranch->prefName = currPref;
+ prefBranch->type = type;
+ switch (type) {
+ case nsIPrefBranch::PREF_STRING: {
+ nsCString str;
+ rv = branch->GetCharPref(currPref, str);
+ prefBranch->stringValue = moz_xstrdup(str.get());
+ break;
+ }
+ case nsIPrefBranch::PREF_BOOL:
+ rv = branch->GetBoolPref(currPref, &prefBranch->boolValue);
+ break;
+ case nsIPrefBranch::PREF_INT:
+ rv = branch->GetIntPref(currPref, &prefBranch->intValue);
+ break;
+ default:
+ NS_WARNING(
+ "Invalid Pref Type in "
+ "nsNetscapeProfileMigratorBase::ReadBranch");
+ break;
+ }
+ if (NS_SUCCEEDED(rv))
+ aPrefs.AppendElement(prefBranch);
+ else
+ delete prefBranch;
+ }
+}
+
+void nsSeamonkeyProfileMigrator::WriteBranch(const char* branchName,
+ nsIPrefService* aPrefService,
+ PBStructArray& aPrefs,
+ bool deallocate) {
+ // Enumerate the branch
+ nsCOMPtr<nsIPrefBranch> branch;
+ aPrefService->GetBranch(branchName, getter_AddRefs(branch));
+
+ uint32_t count = aPrefs.Length();
+ for (uint32_t i = 0; i < count; i++) {
+ PrefBranchStruct* pref = aPrefs.ElementAt(i);
+ switch (pref->type) {
+ case nsIPrefBranch::PREF_STRING:
+ (void)branch->SetCharPref(pref->prefName,
+ nsDependentCString(pref->stringValue));
+ if (deallocate) {
+ free(pref->stringValue);
+ pref->stringValue = nullptr;
+ }
+ break;
+ case nsIPrefBranch::PREF_BOOL:
+ (void)branch->SetBoolPref(pref->prefName, pref->boolValue);
+ break;
+ case nsIPrefBranch::PREF_INT:
+ (void)branch->SetIntPref(pref->prefName, pref->intValue);
+ break;
+ default:
+ NS_WARNING(
+ "Invalid Pref Type in "
+ "nsNetscapeProfileMigratorBase::WriteBranch");
+ break;
+ }
+ if (deallocate) {
+ free(pref->prefName);
+ pref->prefName = nullptr;
+ delete pref;
+ }
+ pref = nullptr;
+ }
+ if (deallocate) {
+ aPrefs.Clear();
+ }
+}
+
+nsresult nsSeamonkeyProfileMigrator::DummyCopyRoutine(bool aReplace) {
+ // place holder function only to fake the UI out into showing some migration
+ // process.
+ return NS_OK;
+}
+
+nsresult nsSeamonkeyProfileMigrator::CopyJunkTraining(bool aReplace) {
+ return aReplace ? CopyFile(FILE_NAME_JUNKTRAINING, FILE_NAME_JUNKTRAINING)
+ : NS_OK;
+}
+
+nsresult nsSeamonkeyProfileMigrator::CopyPasswords(bool aReplace) {
+ nsresult rv = NS_OK;
+
+ nsCString signonsFileName;
+ GetSignonFileName(aReplace, signonsFileName);
+
+ if (signonsFileName.IsEmpty()) return NS_ERROR_FILE_NOT_FOUND;
+
+ nsAutoString fileName;
+ CopyASCIItoUTF16(signonsFileName, fileName);
+ if (aReplace)
+ rv = CopyFile(fileName, fileName);
+ else {
+ // don't do anything right now
+ }
+ return rv;
+}
diff --git a/comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.h b/comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.h
new file mode 100644
index 0000000000..e085e1b2cd
--- /dev/null
+++ b/comm/mail/components/migration/src/nsSeamonkeyProfileMigrator.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#ifndef seamonkeyprofilemigrator___h___
+#define seamonkeyprofilemigrator___h___
+
+#include "nsTHashMap.h"
+#include "nsIMailProfileMigrator.h"
+#include "nsIMsgAccountManager.h"
+#include "nsNetscapeProfileMigratorBase.h"
+
+class nsIPrefBranch;
+class nsIPrefService;
+
+class nsSeamonkeyProfileMigrator : public nsNetscapeProfileMigratorBase {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ nsSeamonkeyProfileMigrator();
+
+ // nsIMailProfileMigrator methods
+ NS_IMETHOD Migrate(uint16_t aItems, nsIProfileStartup* aStartup,
+ const char16_t* aProfile) override;
+ NS_IMETHOD GetMigrateData(const char16_t* aProfile, bool aReplace,
+ uint16_t* aResult) override;
+ NS_IMETHOD GetSourceProfiles(nsTArray<nsString>& aResult) override;
+ NS_IMETHOD GetSourceProfileLocations(
+ nsTArray<RefPtr<nsIFile>>& aResult) override;
+
+ protected:
+ virtual ~nsSeamonkeyProfileMigrator();
+ nsresult FillProfileDataFromSeamonkeyRegistry();
+ nsresult GetSourceProfile(const char16_t* aProfile);
+
+ nsresult MigrateMABFile(const nsCString& aDirPrefId,
+ const nsCString& aSourceFileName);
+
+ nsresult CopyPreferences(bool aReplace);
+ nsresult ImportPreferences(uint16_t aItems);
+ nsresult TransformPreferences(const nsAString& aSourcePrefFileName,
+ const nsAString& aTargetPrefFileName);
+
+ nsresult DummyCopyRoutine(bool aReplace);
+ nsresult CopyJunkTraining(bool aReplace);
+ nsresult CopyPasswords(bool aReplace);
+ nsresult CopyMailFolders(PBStructArray& aMailServers,
+ nsIPrefService* aPrefBranch);
+ nsresult CopySignatureFiles(PBStructArray& aIdentities,
+ nsIPrefService* aPrefBranch);
+
+ typedef nsTHashMap<nsCStringHashKey, nsCString> PrefKeyHashTable;
+
+ nsresult TransformIdentitiesForImport(
+ PBStructArray& aIdentities, nsIMsgAccountManager* accountManager,
+ PrefKeyHashTable& smtpServerKeyHashTable, PrefKeyHashTable& keyHashTable);
+ nsresult TransformMailAccountsForImport(
+ nsIPrefService* aPrefService, PBStructArray& aAccounts,
+ nsIMsgAccountManager* accountManager,
+ PrefKeyHashTable& identityKeyHashTable,
+ PrefKeyHashTable& serverKeyHashTable);
+ nsresult TransformMailServersForImport(const char* branchName,
+ nsIPrefService* aPrefService,
+ PBStructArray& aMailServers,
+ nsIMsgAccountManager* accountManager,
+ PrefKeyHashTable& keyHashTable);
+ nsresult TransformSmtpServersForImport(PBStructArray& aServers,
+ PrefKeyHashTable& keyHashTable);
+ nsresult TransformAddressbooksForImport(nsIPrefService* aPrefService,
+ PBStructArray& aAddressbooks,
+ bool aReplace);
+
+ void ReadBranch(const char* branchName, nsIPrefService* aPrefService,
+ PBStructArray& aPrefs);
+ void WriteBranch(const char* branchName, nsIPrefService* aPrefService,
+ PBStructArray& aPrefs, bool deallocate = true);
+
+ private:
+ nsTArray<nsString> mProfileNames;
+ nsTArray<RefPtr<nsIFile>> mProfileLocations;
+};
+
+#endif