summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/import
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--comm/mailnews/import/build/moz.build28
-rw-r--r--comm/mailnews/import/content/aboutImport.js1511
-rw-r--r--comm/mailnews/import/content/aboutImport.xhtml477
-rw-r--r--comm/mailnews/import/content/csv-field-map.js280
-rw-r--r--comm/mailnews/import/content/fieldMapImport.js259
-rw-r--r--comm/mailnews/import/content/fieldMapImport.xhtml104
-rw-r--r--comm/mailnews/import/content/importDialog.js1184
-rw-r--r--comm/mailnews/import/content/importDialog.xhtml225
-rw-r--r--comm/mailnews/import/modules/AddrBookFileImporter.jsm356
-rw-r--r--comm/mailnews/import/modules/AppleMailProfileImporter.jsm92
-rw-r--r--comm/mailnews/import/modules/BaseProfileImporter.jsm100
-rw-r--r--comm/mailnews/import/modules/BeckyProfileImporter.jsm125
-rw-r--r--comm/mailnews/import/modules/CalendarFileImporter.jsm127
-rw-r--r--comm/mailnews/import/modules/OutlookProfileImporter.jsm143
-rw-r--r--comm/mailnews/import/modules/SeamonkeyProfileImporter.jsm75
-rw-r--r--comm/mailnews/import/modules/ThunderbirdProfileImporter.jsm1024
-rw-r--r--comm/mailnews/import/modules/moz.build22
-rw-r--r--comm/mailnews/import/public/moz.build20
-rw-r--r--comm/mailnews/import/public/nsIImportABDescriptor.idl70
-rw-r--r--comm/mailnews/import/public/nsIImportABFile.idl24
-rw-r--r--comm/mailnews/import/public/nsIImportAddressBooks.idl142
-rw-r--r--comm/mailnews/import/public/nsIImportFieldMap.idl71
-rw-r--r--comm/mailnews/import/public/nsIImportFilters.idl25
-rw-r--r--comm/mailnews/import/public/nsIImportGeneric.idl81
-rw-r--r--comm/mailnews/import/public/nsIImportMail.idl94
-rw-r--r--comm/mailnews/import/public/nsIImportMailboxDescriptor.idl44
-rw-r--r--comm/mailnews/import/public/nsIImportModule.idl30
-rw-r--r--comm/mailnews/import/public/nsIImportService.idl59
-rw-r--r--comm/mailnews/import/public/nsIImportSettings.idl33
-rw-r--r--comm/mailnews/import/src/ImportCharSet.cpp58
-rw-r--r--comm/mailnews/import/src/ImportCharSet.h201
-rw-r--r--comm/mailnews/import/src/ImportDebug.h25
-rw-r--r--comm/mailnews/import/src/ImportOutFile.cpp257
-rw-r--r--comm/mailnews/import/src/ImportOutFile.h94
-rw-r--r--comm/mailnews/import/src/ImportTranslate.cpp100
-rw-r--r--comm/mailnews/import/src/ImportTranslate.h23
-rw-r--r--comm/mailnews/import/src/MapiApi.cpp1842
-rw-r--r--comm/mailnews/import/src/MapiApi.h284
-rw-r--r--comm/mailnews/import/src/MapiDbgLog.h36
-rw-r--r--comm/mailnews/import/src/MapiMessage.cpp1383
-rw-r--r--comm/mailnews/import/src/MapiMessage.h290
-rw-r--r--comm/mailnews/import/src/MapiMimeTypes.cpp81
-rw-r--r--comm/mailnews/import/src/MapiMimeTypes.h27
-rw-r--r--comm/mailnews/import/src/MapiTagStrs.cpp1473
-rw-r--r--comm/mailnews/import/src/MorkImport.cpp343
-rw-r--r--comm/mailnews/import/src/MorkImport.h50
-rw-r--r--comm/mailnews/import/src/SeamonkeyImport.jsm253
-rw-r--r--comm/mailnews/import/src/ThunderbirdImport.jsm145
-rw-r--r--comm/mailnews/import/src/components.conf104
-rw-r--r--comm/mailnews/import/src/moz.build86
-rw-r--r--comm/mailnews/import/src/nsAddrDatabase.cpp864
-rw-r--r--comm/mailnews/import/src/nsAddrDatabase.h158
-rw-r--r--comm/mailnews/import/src/nsAppleMailImport.cpp609
-rw-r--r--comm/mailnews/import/src/nsAppleMailImport.h86
-rw-r--r--comm/mailnews/import/src/nsBeckyAddressBooks.cpp311
-rw-r--r--comm/mailnews/import/src/nsBeckyAddressBooks.h36
-rw-r--r--comm/mailnews/import/src/nsBeckyFilters.cpp711
-rw-r--r--comm/mailnews/import/src/nsBeckyFilters.h73
-rw-r--r--comm/mailnews/import/src/nsBeckyImport.cpp143
-rw-r--r--comm/mailnews/import/src/nsBeckyImport.h38
-rw-r--r--comm/mailnews/import/src/nsBeckyMail.cpp566
-rw-r--r--comm/mailnews/import/src/nsBeckyMail.h41
-rw-r--r--comm/mailnews/import/src/nsBeckySettings.cpp379
-rw-r--r--comm/mailnews/import/src/nsBeckySettings.h50
-rw-r--r--comm/mailnews/import/src/nsBeckyStringBundle.cpp55
-rw-r--r--comm/mailnews/import/src/nsBeckyStringBundle.h32
-rw-r--r--comm/mailnews/import/src/nsBeckyUtils.cpp302
-rw-r--r--comm/mailnews/import/src/nsBeckyUtils.h35
-rw-r--r--comm/mailnews/import/src/nsEmlxHelperUtils.h61
-rw-r--r--comm/mailnews/import/src/nsEmlxHelperUtils.mm230
-rw-r--r--comm/mailnews/import/src/nsImportABDescriptor.cpp19
-rw-r--r--comm/mailnews/import/src/nsImportABDescriptor.h100
-rw-r--r--comm/mailnews/import/src/nsImportAddressBooks.cpp571
-rw-r--r--comm/mailnews/import/src/nsImportAddressBooks.h81
-rw-r--r--comm/mailnews/import/src/nsImportEmbeddedImageData.cpp52
-rw-r--r--comm/mailnews/import/src/nsImportEmbeddedImageData.h31
-rw-r--r--comm/mailnews/import/src/nsImportEncodeScan.cpp334
-rw-r--r--comm/mailnews/import/src/nsImportEncodeScan.h39
-rw-r--r--comm/mailnews/import/src/nsImportFieldMap.cpp325
-rw-r--r--comm/mailnews/import/src/nsImportFieldMap.h44
-rw-r--r--comm/mailnews/import/src/nsImportMail.cpp1007
-rw-r--r--comm/mailnews/import/src/nsImportMail.h86
-rw-r--r--comm/mailnews/import/src/nsImportMailboxDescriptor.cpp25
-rw-r--r--comm/mailnews/import/src/nsImportMailboxDescriptor.h94
-rw-r--r--comm/mailnews/import/src/nsImportScanFile.cpp154
-rw-r--r--comm/mailnews/import/src/nsImportScanFile.h56
-rw-r--r--comm/mailnews/import/src/nsImportService.cpp293
-rw-r--r--comm/mailnews/import/src/nsImportService.h54
-rw-r--r--comm/mailnews/import/src/nsImportStringBundle.cpp67
-rw-r--r--comm/mailnews/import/src/nsImportStringBundle.h43
-rw-r--r--comm/mailnews/import/src/nsImportTranslator.cpp308
-rw-r--r--comm/mailnews/import/src/nsImportTranslator.h87
-rw-r--r--comm/mailnews/import/src/nsOutlookCompose.cpp669
-rw-r--r--comm/mailnews/import/src/nsOutlookCompose.h63
-rw-r--r--comm/mailnews/import/src/nsOutlookImport.cpp522
-rw-r--r--comm/mailnews/import/src/nsOutlookImport.h38
-rw-r--r--comm/mailnews/import/src/nsOutlookMail.cpp830
-rw-r--r--comm/mailnews/import/src/nsOutlookMail.h84
-rw-r--r--comm/mailnews/import/src/nsOutlookSettings.cpp500
-rw-r--r--comm/mailnews/import/src/nsOutlookSettings.h27
-rw-r--r--comm/mailnews/import/src/nsOutlookStringBundle.cpp53
-rw-r--r--comm/mailnews/import/src/nsOutlookStringBundle.h36
-rw-r--r--comm/mailnews/import/src/nsTextAddress.cpp426
-rw-r--r--comm/mailnews/import/src/nsTextAddress.h60
-rw-r--r--comm/mailnews/import/src/nsTextImport.cpp646
-rw-r--r--comm/mailnews/import/src/nsTextImport.h40
-rw-r--r--comm/mailnews/import/src/nsVCardAddress.cpp151
-rw-r--r--comm/mailnews/import/src/nsVCardAddress.h28
-rw-r--r--comm/mailnews/import/src/nsVCardImport.cpp352
-rw-r--r--comm/mailnews/import/src/nsVCardImport.h39
-rw-r--r--comm/mailnews/import/src/nsWMImport.cpp199
-rw-r--r--comm/mailnews/import/src/nsWMImport.h38
-rw-r--r--comm/mailnews/import/src/nsWMSettings.cpp679
-rw-r--r--comm/mailnews/import/src/nsWMSettings.h22
-rw-r--r--comm/mailnews/import/src/nsWMStringBundle.cpp52
-rw-r--r--comm/mailnews/import/src/nsWMStringBundle.h36
-rw-r--r--comm/mailnews/import/src/nsWMUtils.cpp153
-rw-r--r--comm/mailnews/import/src/nsWMUtils.h23
-rw-r--r--comm/mailnews/import/src/rtfDecoder.cpp561
-rw-r--r--comm/mailnews/import/src/rtfDecoder.h21
-rw-r--r--comm/mailnews/import/src/rtfMailDecoder.cpp71
-rw-r--r--comm/mailnews/import/src/rtfMailDecoder.h44
-rw-r--r--comm/mailnews/import/test/moz.build6
-rw-r--r--comm/mailnews/import/test/unit/head_import.js23
-rw-r--r--comm/mailnews/import/test/unit/resources/AB_README39
-rw-r--r--comm/mailnews/import/test/unit/resources/WindowsLiveMail/MicrosoftCommunities/account{2E23}.oeaccountbin0 -> 698 bytes
-rw-r--r--comm/mailnews/import/test/unit/resources/WindowsLiveMail/donhallimap/donhallimap{testimap}.oeaccountbin0 -> 4454 bytes
-rw-r--r--comm/mailnews/import/test/unit/resources/WindowsLiveMail/donhallnntp/donhallnntp{testnntp}.oeaccountbin0 -> 2728 bytes
-rw-r--r--comm/mailnews/import/test/unit/resources/WindowsLiveMail/news.mozilla.org/account{B3B3}.oeaccountbin0 -> 960 bytes
-rw-r--r--comm/mailnews/import/test/unit/resources/WindowsLiveMail/pop3.test.test/account{D244}.oeaccountbin0 -> 1478 bytes
-rw-r--r--comm/mailnews/import/test/unit/resources/addressbook.json170
-rw-r--r--comm/mailnews/import/test/unit/resources/basic_addressbook.csv2
-rw-r--r--comm/mailnews/import/test/unit/resources/basic_csv_addressbook.csv3
-rw-r--r--comm/mailnews/import/test/unit/resources/basic_ldif_addressbook.ldif45
-rw-r--r--comm/mailnews/import/test/unit/resources/basic_vcard_addressbook.vcf12
-rw-r--r--comm/mailnews/import/test/unit/resources/becky/addressbooks/4e4d186e.bab26
-rw-r--r--comm/mailnews/import/test/unit/resources/becky/addressbooks/4e4d186f.bab13
-rw-r--r--comm/mailnews/import/test/unit/resources/becky/addressbooks/do_not_import_this.nobab12
-rw-r--r--comm/mailnews/import/test/unit/resources/becky/filters/IFilter.def23
-rw-r--r--comm/mailnews/import/test/unit/resources/becky/filters/OFilter.def22
-rw-r--r--comm/mailnews/import/test/unit/resources/bug_263304.ldif7
-rw-r--r--comm/mailnews/import/test/unit/resources/csv_no_header.csv2
-rw-r--r--comm/mailnews/import/test/unit/resources/csv_semicolon.csv3
-rw-r--r--comm/mailnews/import/test/unit/resources/dos_vcard_addressbook.vcf6
-rw-r--r--comm/mailnews/import/test/unit/resources/emptylines_vcard_addressbook.vcf18
-rw-r--r--comm/mailnews/import/test/unit/resources/import_helper.js665
-rw-r--r--comm/mailnews/import/test/unit/resources/mock_windows_reg_factory.js84
-rw-r--r--comm/mailnews/import/test/unit/resources/quote.csv2
-rw-r--r--comm/mailnews/import/test/unit/resources/shiftjis_addressbook.csv2
-rw-r--r--comm/mailnews/import/test/unit/resources/tab_comma_mixed.csv4
-rw-r--r--comm/mailnews/import/test/unit/resources/utf16_addressbook.csvbin0 -> 1006 bytes
-rw-r--r--comm/mailnews/import/test/unit/test_AddrBookFileImporter.js130
-rw-r--r--comm/mailnews/import/test/unit/test_ThunderbirdProfileImporter.js288
-rw-r--r--comm/mailnews/import/test/unit/test_becky_addressbook.js59
-rw-r--r--comm/mailnews/import/test/unit/test_becky_filters.js41
-rw-r--r--comm/mailnews/import/test/unit/test_bug_263304.js25
-rw-r--r--comm/mailnews/import/test/unit/test_bug_437556.js23
-rw-r--r--comm/mailnews/import/test/unit/test_csv_GetSample.js15
-rw-r--r--comm/mailnews/import/test/unit/test_csv_import.js23
-rw-r--r--comm/mailnews/import/test/unit/test_csv_import_quote.js11
-rw-r--r--comm/mailnews/import/test/unit/test_ldif_import.js27
-rw-r--r--comm/mailnews/import/test/unit/test_outlook_settings.js158
-rw-r--r--comm/mailnews/import/test/unit/test_shiftjis_csv.js20
-rw-r--r--comm/mailnews/import/test/unit/test_utf16_csv.js20
-rw-r--r--comm/mailnews/import/test/unit/test_vcard_import.js34
-rw-r--r--comm/mailnews/import/test/unit/test_winmail.js178
-rw-r--r--comm/mailnews/import/test/unit/xpcshell.ini26
167 files changed, 31360 insertions, 0 deletions
diff --git a/comm/mailnews/import/build/moz.build b/comm/mailnews/import/build/moz.build
new file mode 100644
index 0000000000..b6f8aa8baa
--- /dev/null
+++ b/comm/mailnews/import/build/moz.build
@@ -0,0 +1,28 @@
+# 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/.
+
+USE_LIBS += [
+ "nspr",
+]
+
+Library("import")
+FINAL_LIBRARY = "xul"
+
+# js needs to come after xul for now, because it is an archive and its content
+# is discarded when it comes first.
+USE_LIBS += [
+ "js",
+]
+
+LOCAL_INCLUDES += [
+ "../src",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ OS_LIBS += CONFIG["TK_LIBS"]
+ OS_LIBS += ["-framework Cocoa"]
+
+if CONFIG["OS_ARCH"] != "WINNT":
+ OS_LIBS += CONFIG["MOZ_ZLIB_LIBS"]
diff --git a/comm/mailnews/import/content/aboutImport.js b/comm/mailnews/import/content/aboutImport.js
new file mode 100644
index 0000000000..1f0d06d38d
--- /dev/null
+++ b/comm/mailnews/import/content/aboutImport.js
@@ -0,0 +1,1511 @@
+/* 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/. */
+
+/* global MozElements */
+
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+XPCOMUtils.defineLazyModuleGetters(this, {
+ MailServices: "resource:///modules/MailServices.jsm",
+ MailUtils: "resource:///modules/MailUtils.jsm",
+ AddrBookFileImporter: "resource:///modules/AddrBookFileImporter.jsm",
+ CalendarFileImporter: "resource:///modules/CalendarFileImporter.jsm",
+ ProfileExporter: "resource:///modules/ProfileExporter.jsm",
+ cal: "resource:///modules/calendar/calUtils.jsm",
+});
+
+/**
+ * An object to represent a source profile to import from.
+ *
+ * @typedef {object} SourceProfile
+ * @property {string} [name] - The profile name.
+ * @property {nsIFile} dir - The profile location.
+ */
+
+/**
+ * @typedef {object} Step
+ * @property {Function} returnTo - Function that resets to this step. Should end
+ * up calling |updateSteps()| with this step again.
+ */
+
+const Steps = {
+ _pastSteps: [],
+ /**
+ * Toggle visibility of the navigation steps.
+ *
+ * @param {boolean} visible - If the navigation steps should be shown.
+ */
+ toggle(visible) {
+ document.getElementById("stepNav").hidden = !visible;
+ },
+ /**
+ * Update the currently displayed steps by adding a new step and updating the
+ * forecast of remaining steps.
+ *
+ * @param {Step} currentStep
+ * @param {number} plannedSteps - Amount of steps to follow this step,
+ * including summary.
+ */
+ updateSteps(currentStep, plannedSteps) {
+ this._pastSteps.push(currentStep);
+ let confirm = document.getElementById("navConfirm");
+ const isConfirmStep = plannedSteps === 0;
+ confirm.classList.toggle("current", isConfirmStep);
+ confirm.toggleAttribute("disabled", isConfirmStep);
+ confirm.removeAttribute("aria-current");
+ document.getElementById("stepNav").replaceChildren(
+ ...this._pastSteps.map((step, index) => {
+ const li = document.createElement("li");
+ const button = document.createElement("button");
+ if (step === currentStep) {
+ if (isConfirmStep) {
+ confirm.setAttribute("aria-current", "step");
+ return confirm;
+ }
+ li.classList.add("current");
+ li.setAttribute("aria-current", "step");
+ button.setAttribute("disabled", "disabled");
+ } else {
+ li.classList.add("completed");
+ button.addEventListener("click", () => {
+ this.backTo(index);
+ });
+ }
+ document.l10n.setAttributes(button, "step-count", {
+ number: index + 1,
+ });
+ li.append(button);
+ //TODO tooltips
+ return li;
+ }),
+ ...new Array(Math.max(plannedSteps - 1, 0))
+ .fill(null)
+ .map((item, index) => {
+ const li = document.createElement("li");
+ const button = document.createElement("button");
+ document.l10n.setAttributes(button, "step-count", {
+ number: this._pastSteps.length + index + 1,
+ });
+ button.setAttribute("disabled", "disabled");
+ li.append(button);
+ //TODO tooltips
+ return li;
+ }),
+ isConfirmStep ? "" : confirm
+ );
+ },
+ /**
+ * Return to a previous step.
+ *
+ * @param {number} [stepIndex=-1] - The absolute index of the step to return
+ * to. By default goes back one step.
+ * @returns {boolean} if a previous step was recalled.
+ */
+ backTo(stepIndex = -1) {
+ if (!this._pastSteps.length || stepIndex >= this._pastSteps.length) {
+ return false;
+ }
+ if (stepIndex < 0) {
+ // Make relative step index absolute
+ stepIndex = this._pastSteps.length + stepIndex - 1;
+ }
+ let targetStep = this._pastSteps[stepIndex];
+ this._pastSteps = this._pastSteps.slice(0, stepIndex);
+ targetStep.returnTo();
+ return true;
+ },
+ /**
+ * If any previous steps have been recorded.
+ *
+ * @returns {boolean} If there are steps preceding the current state.
+ */
+ hasStepHistory() {
+ return this._pastSteps.length > 0;
+ },
+ /**
+ * Reset step state.
+ */
+ reset() {
+ this._pastSteps = [];
+ },
+};
+
+/**
+ * The base controller for an importing process.
+ */
+class ImporterController {
+ _logger = console.createInstance({
+ prefix: "mail.import",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.import.loglevel",
+ });
+
+ /**
+ * @param {string} elementId - The root element id.
+ * @param {string} paneIdPrefix - The prefix of sub pane id.
+ */
+ constructor(elementId, paneIdPrefix) {
+ this._el = document.getElementById(elementId);
+ this._paneIdPrefix = paneIdPrefix;
+ }
+
+ /**
+ * Show a specific pane, hide all the others.
+ *
+ * @param {string} id - The pane id to show.
+ */
+ showPane(id) {
+ this._currentPane = id;
+ id = `${this._paneIdPrefix}-${id}`;
+ for (let pane of this._el.querySelectorAll(":scope > section")) {
+ pane.hidden = pane.id != id;
+ }
+ }
+
+ /**
+ * Show the previous pane.
+ */
+ back() {
+ ImporterController.notificationBox.removeAllNotifications();
+ Steps.backTo();
+ }
+
+ /**
+ * Show the next pane.
+ */
+ next() {
+ if (this._restartOnOk) {
+ window.close();
+ MailUtils.restartApplication();
+ return;
+ }
+ ImporterController.notificationBox.removeAllNotifications();
+ }
+
+ /**
+ * Show the first pane.
+ */
+ reset() {
+ this._el.classList.remove(
+ "restart-only",
+ "progress",
+ "complete",
+ "final-step"
+ );
+ this._toggleBackButton(true);
+ }
+
+ /**
+ * Show the progress bar.
+ *
+ * @param {string} progressL10nId - Fluent ID to use for the progress
+ * description. Should have a |progressPercent| variable expecting the
+ * current progress like "50%".
+ */
+ showProgress(progressL10nId) {
+ this._progressL10nId = progressL10nId;
+ this.updateProgress(0);
+ this._el.classList.add("progress");
+ this._toggleBackButton(false);
+ this._inProgress = true;
+ }
+
+ /**
+ * Update the progress bar.
+ *
+ * @param {number} value - A number between 0 and 1 to represent the progress.
+ */
+ updateProgress(value) {
+ this._el.querySelector(".progressPaneProgressBar").value = value;
+ document.l10n.setAttributes(
+ this._el.querySelector(".progressPaneDesc"),
+ this._progressL10nId,
+ {
+ progressPercent: ImporterController.percentFormatter.format(value),
+ }
+ );
+ }
+
+ /**
+ * Show the finish text.
+ *
+ * @param {boolean} [restartNeeded=false] - Whether restart is needed to
+ * finish the importing.
+ */
+ finish(restartNeeded = false) {
+ this._restartOnOk = restartNeeded;
+ this._el.classList.toggle("restart-required", restartNeeded);
+ this._el.classList.add("complete");
+ document.l10n.setAttributes(
+ this._el.querySelector(".progressPaneDesc"),
+ "progress-pane-finished-desc2"
+ );
+ this._inProgress = false;
+ }
+
+ /**
+ * Show the error pane, with an error message.
+ *
+ * @param {string} msgId - The error message fluent id.
+ */
+ showError(msgId) {
+ if (this._inProgress) {
+ this._toggleBackButton(true);
+ this._el.classList.remove("progress");
+ this._restartOnOk = false;
+ this._inProgress = false;
+ }
+ ImporterController.notificationBox.removeAllNotifications();
+ let notification = ImporterController.notificationBox.appendNotification(
+ "error",
+ {
+ label: {
+ "l10n-id": msgId,
+ },
+ priority: ImporterController.notificationBox.PRIORITY_CRITICAL_HIGH,
+ },
+ null
+ );
+ notification.removeAttribute("dismissable");
+ }
+
+ /**
+ * Disable/enable the back button.
+ *
+ * @param {boolean} enable - If the back button should be enabled
+ */
+ _toggleBackButton(enable) {
+ if (this._el.querySelector(".buttons-container")) {
+ this._el.querySelector(".back").disabled = !enable;
+ }
+ }
+}
+
+XPCOMUtils.defineLazyGetter(
+ ImporterController,
+ "percentFormatter",
+ () =>
+ new Intl.NumberFormat(undefined, {
+ style: "percent",
+ })
+);
+XPCOMUtils.defineLazyGetter(
+ ImporterController,
+ "notificationBox",
+ () =>
+ new MozElements.NotificationBox(element => {
+ element.setAttribute("notificationside", "bottom");
+ document.getElementById("errorNotifications").append(element);
+ })
+);
+
+/**
+ * Control the #tabPane-app element, to support importing from an application.
+ */
+class ProfileImporterController extends ImporterController {
+ constructor() {
+ super("tabPane-app", "app");
+ document.getElementById("appItemsList").addEventListener(
+ "input",
+ () => {
+ let state = this._getItemsChecked(true);
+ document.getElementById("profileNextButton").disabled = Object.values(
+ state
+ ).every(isChecked => !isChecked);
+ },
+ {
+ capture: true,
+ passive: true,
+ }
+ );
+ }
+
+ /**
+ * A map from radio input value to the importer module name.
+ */
+ _sourceModules = {
+ Thunderbird: "ThunderbirdProfileImporter",
+ Seamonkey: "SeamonkeyProfileImporter",
+ Outlook: "OutlookProfileImporter",
+ Becky: "BeckyProfileImporter",
+ AppleMail: "AppleMailProfileImporter",
+ };
+
+ /**
+ * Maps app radio input values to their respective representations in l10n
+ * ids.
+ */
+ _sourceL10nIds = {
+ Thunderbird: "thunderbird",
+ Seamonkey: "seamonkey",
+ Outlook: "outlook",
+ Becky: "becky",
+ AppleMail: "apple-mail",
+ };
+ _sourceAppName = "thunderbird";
+
+ next() {
+ super.next();
+ switch (this._currentPane) {
+ case "profiles":
+ this._onSelectProfile();
+ break;
+ case "items":
+ this._onSelectItems();
+ break;
+ case "summary":
+ window.close();
+ break;
+ }
+ }
+
+ /**
+ * Handler for the Continue button on the sources pane.
+ *
+ * @param {string} source - Profile source to import.
+ */
+ async _onSelectSource(source) {
+ this._sourceAppName = this._sourceL10nIds[source];
+ let sourceModule = this._sourceModules[source];
+
+ let module = ChromeUtils.import(`resource:///modules/${sourceModule}.jsm`);
+ this._importer = new module[sourceModule]();
+
+ let sourceProfiles = await this._importer.getSourceProfiles();
+ if (sourceProfiles.length > 1 || this._importer.USE_FILE_PICKER) {
+ // Let the user pick a profile if there are multiple options.
+ this._showProfiles(sourceProfiles, this._importer.USE_FILE_PICKER);
+ } else if (sourceProfiles.length == 1) {
+ // Let the user pick what to import.
+ this._showItems(sourceProfiles[0]);
+ } else {
+ this.showError("error-message-no-profile");
+ throw new Error("No profile found, do not advance to app flow.");
+ }
+ }
+
+ /**
+ * Show the profiles pane, with a list of profiles and optional file pickers.
+ *
+ * @param {SourceProfile[]} profiles - An array of profiles.
+ * @param {boolean} useFilePicker - Whether to render file pickers.
+ */
+ _showProfiles(profiles, useFilePicker) {
+ Steps.updateSteps(
+ {
+ returnTo: () => {
+ this.reset();
+ this._showProfiles(profiles, useFilePicker);
+ },
+ },
+ 2
+ );
+ this._sourceProfiles = profiles;
+ document.l10n.setAttributes(
+ document.getElementById("profilesPaneTitle"),
+ `from-app-${this._sourceAppName}`
+ );
+ document.l10n.setAttributes(
+ document.getElementById("profilesPaneSubtitle"),
+ `profiles-pane-title-${this._sourceAppName}`
+ );
+ let elProfileList = document.getElementById("profileList");
+ elProfileList.hidden = !profiles.length;
+ elProfileList.innerHTML = "";
+ document.getElementById("filePickerList").hidden = !useFilePicker;
+
+ for (let profile of profiles) {
+ let label = document.createElement("label");
+ label.className = "toggle-container-with-text";
+
+ let input = document.createElement("input");
+ input.type = "radio";
+ input.name = "appProfile";
+ input.value = profile.dir.path;
+ label.append(input);
+
+ let name = document.createElement("p");
+ if (profile.name) {
+ document.l10n.setAttributes(name, "profile-source-named", {
+ profileName: profile.name,
+ });
+ } else {
+ document.l10n.setAttributes(name, "profile-source");
+ }
+ label.append(name);
+
+ let profileDetails = document.createElement("dl");
+ profileDetails.className = "result-indent tip-caption";
+ let profilePathLabel = document.createElement("dt");
+ document.l10n.setAttributes(profilePathLabel, "items-pane-directory");
+ let profilePath = document.createElement("dd");
+ profilePath.textContent = profile.dir.path;
+ profileDetails.append(profilePathLabel, profilePath);
+ label.append(profileDetails);
+
+ elProfileList.append(label);
+ }
+ document.querySelector("input[name=appProfile]").checked = true;
+ document.getElementById("profileNextButton").disabled = false;
+
+ this.showPane("profiles");
+ }
+
+ /**
+ * Handler for the Continue button on the profiles pane.
+ */
+ _onSelectProfile() {
+ let index = [
+ ...document.querySelectorAll("input[name=appProfile]"),
+ ].findIndex(el => el.checked);
+ if (this._sourceProfiles[index]) {
+ this._showItems(this._sourceProfiles[index]);
+ } else {
+ this._openFilePicker(
+ index == this._sourceProfiles.length ? "dir" : "zip"
+ );
+ }
+ }
+
+ /**
+ * Open a file picker to select a folder or a zip file.
+ *
+ * @param {'dir' | 'zip'} type - Whether to pick a folder or a zip file.
+ */
+ async _openFilePicker(type) {
+ let filePicker = Cc["@mozilla.org/filepicker;1"].createInstance(
+ Ci.nsIFilePicker
+ );
+ let [filePickerTitleDir, filePickerTitleZip] =
+ await document.l10n.formatValues([
+ "profile-file-picker-directory",
+ "profile-file-picker-archive-title",
+ ]);
+ if (type == "zip") {
+ filePicker.init(window, filePickerTitleZip, filePicker.modeOpen);
+ filePicker.appendFilter("", "*.zip");
+ } else {
+ filePicker.init(window, filePickerTitleDir, filePicker.modeGetFolder);
+ }
+ let rv = await new Promise(resolve => filePicker.open(resolve));
+ if (rv != Ci.nsIFilePicker.returnOK) {
+ return;
+ }
+ let selectedFile = filePicker.file;
+ if (!selectedFile.isDirectory()) {
+ if (selectedFile.fileSize > 2147483647) {
+ // nsIZipReader only supports zip file less than 2GB.
+ this.showError("error-message-zip-file-too-big2");
+ return;
+ }
+ this._importingFromZip = true;
+ }
+ this._showItems({ dir: selectedFile });
+ }
+
+ /**
+ * Show the items pane, with a list of items to import.
+ *
+ * @param {SourceProfile} profile - The profile to import from.
+ */
+ _showItems(profile) {
+ Steps.updateSteps(
+ {
+ returnTo: () => {
+ this.reset();
+ this._showItems(profile);
+ },
+ },
+ 1
+ );
+ this._el.classList.remove("final-step", "progress");
+ this._sourceProfile = profile;
+ document.l10n.setAttributes(
+ this._el.querySelector("#app-items h1"),
+ `from-app-${this._sourceAppName}`
+ );
+ document.getElementById("appSourceProfilePath").textContent =
+ profile.dir.path;
+ document.getElementById("appSourceProfilePath").textContent =
+ this._sourceProfile.dir.path;
+ document.getElementById("appSourceProfileNameWrapper").hidden =
+ !this._sourceProfile.name;
+ if (this._sourceProfile.name) {
+ document.getElementById("appSourceProfileName").textContent =
+ this._sourceProfile.name;
+ }
+ this._setItemsChecked(this._importer.SUPPORTED_ITEMS);
+ document.getElementById("profileNextButton").disabled = Object.values(
+ this._importer.SUPPORTED_ITEMS
+ ).every(isChecked => !isChecked);
+
+ this.showPane("items");
+ }
+
+ /** A map from checkbox id to ImportItems field */
+ _itemCheckboxes = {
+ checkAccounts: "accounts",
+ checkAddressBooks: "addressBooks",
+ checkCalendars: "calendars",
+ checkMailMessages: "mailMessages",
+ };
+
+ /**
+ * Map of fluent IDs from ImportItems if they differ.
+ *
+ * @type {Object<string>}
+ */
+ _importItemFluentId = {
+ addressBooks: "address-books",
+ mailMessages: "mail-messages",
+ };
+
+ /**
+ * Set checkbox states according to an ImportItems object.
+ *
+ * @param {ImportItems} items.
+ */
+ _setItemsChecked(items) {
+ for (let [id, field] of Object.entries(this._itemCheckboxes)) {
+ let supported = items[field];
+ let checkbox = document.getElementById(id);
+ checkbox.checked = supported;
+ checkbox.disabled = !supported;
+ }
+ }
+
+ /**
+ * Construct an ImportItems object from the checkbox states.
+ *
+ * @param {boolean} [onlySupported=false] - Only return supported ImportItems.
+ * @returns {ImportItems}
+ */
+ _getItemsChecked(onlySupported = false) {
+ let items = {};
+ for (let id in this._itemCheckboxes) {
+ let checkbox = document.getElementById(id);
+ if (!onlySupported || !checkbox.disabled) {
+ items[this._itemCheckboxes[id]] = checkbox.checked;
+ }
+ }
+ return items;
+ }
+
+ /**
+ * Handler for the Continue button on the items pane.
+ */
+ _onSelectItems() {
+ let checkedItems = this._getItemsChecked(true);
+ if (Object.values(checkedItems).some(isChecked => isChecked)) {
+ this._showSummary();
+ }
+ }
+
+ _showSummary() {
+ Steps.updateSteps({}, 0);
+ this._el.classList.add("final-step");
+ document.l10n.setAttributes(
+ this._el.querySelector("#app-summary h1"),
+ `from-app-${this._sourceAppName}`
+ );
+ document.getElementById("appSummaryProfilePath").textContent =
+ this._sourceProfile.dir.path;
+ document.getElementById("appSummaryProfileNameWrapper").hidden =
+ !this._sourceProfile.name;
+ if (this._sourceProfile.name) {
+ document.getElementById("appSummaryProfileName").textContent =
+ this._sourceProfile.name;
+ }
+ document.getElementById("appSummaryItems").replaceChildren(
+ ...Object.entries(this._getItemsChecked(true))
+ .filter(([item, checked]) => checked)
+ .map(([item]) => {
+ let li = document.createElement("li");
+ let fluentId = this._importItemFluentId[item] ?? item;
+ document.l10n.setAttributes(li, `items-pane-checkbox-${fluentId}`);
+ return li;
+ })
+ );
+ this.showPane("summary");
+ }
+
+ /**
+ * Extract the zip file to a tmp dir, set _sourceProfile.dir to the tmp dir.
+ */
+ async _extractZipFile() {
+ // 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(this._sourceProfile.dir);
+ for (let entry of zip.findEntries(null)) {
+ let parts = entry.split("/");
+ if (
+ this._importer.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);
+ this._extractedFileCount++;
+ if (this._extractedFileCount % 10 == 0) {
+ let progress = Math.min((this._extractedFileCount / 200) * 0.2, 0.2);
+ this.updateProgress(progress);
+ await new Promise(resolve => setTimeout(resolve));
+ }
+ } catch (e) {
+ this._logger.error(e);
+ }
+ }
+ // Use the tmp dir as source profile dir.
+ this._sourceProfile = { dir: targetDir };
+ this.updateProgress(0.2);
+ }
+
+ async startImport() {
+ this.showProgress("progress-pane-importing2");
+ if (this._importingFromZip) {
+ this._extractedFileCount = 0;
+ try {
+ await this._extractZipFile();
+ } catch (e) {
+ this.showError("error-message-extract-zip-file-failed2");
+ throw e;
+ }
+ }
+ this._importer.onProgress = (current, total) => {
+ this.updateProgress(
+ this._importingFromZip ? 0.2 + (0.8 * current) / total : current / total
+ );
+ };
+ try {
+ this.finish(
+ await this._importer.startImport(
+ this._sourceProfile.dir,
+ this._getItemsChecked()
+ )
+ );
+ } catch (e) {
+ this.showError("error-message-failed");
+ throw e;
+ } finally {
+ if (this._importingFromZip) {
+ IOUtils.remove(this._sourceProfile.dir.path, { recursive: true });
+ }
+ }
+ }
+}
+
+/**
+ * Control the #tabPane-addressBook element, to support importing from an
+ * address book file.
+ */
+class AddrBookImporterController extends ImporterController {
+ constructor() {
+ super("tabPane-addressBook", "addr-book");
+ }
+
+ /**
+ * Show the next pane.
+ */
+ next() {
+ super.next();
+ switch (this._currentPane) {
+ case "sources":
+ this._onSelectSource();
+ break;
+ case "csvFieldMap":
+ this._onSubmitCsvFieldMap();
+ break;
+ case "directories":
+ this._onSelectDirectory();
+ break;
+ case "summary":
+ window.close();
+ break;
+ }
+ }
+
+ showInitialStep() {
+ this._showSources();
+ }
+
+ /**
+ * Show the sources pane.
+ */
+ _showSources() {
+ document.getElementById("addrBookBackButton").hidden =
+ !Steps.hasStepHistory();
+ Steps.updateSteps(
+ {
+ returnTo: () => {
+ this.reset();
+ this._showSources();
+ },
+ },
+ 2
+ );
+ this.showPane("sources");
+ }
+
+ /**
+ * Handler for the Continue button on the sources pane.
+ */
+ async _onSelectSource() {
+ this._fileType = document.querySelector(
+ "input[name=addrBookSource]:checked"
+ ).value;
+ this._importer = new AddrBookFileImporter(this._fileType);
+
+ let filePicker = Cc["@mozilla.org/filepicker;1"].createInstance(
+ Ci.nsIFilePicker
+ );
+ let [filePickerTitle] = await document.l10n.formatValues([
+ "addr-book-file-picker",
+ ]);
+ filePicker.init(window, filePickerTitle, filePicker.modeOpen);
+ let filter = {
+ csv: "*.csv; *.tsv; *.tab",
+ ldif: "*.ldif",
+ vcard: "*.vcf",
+ sqlite: "*.sqlite",
+ mab: "*.mab",
+ }[this._fileType];
+ if (filter) {
+ filePicker.appendFilter("", filter);
+ }
+ filePicker.appendFilters(Ci.nsIFilePicker.filterAll);
+ let rv = await new Promise(resolve => filePicker.open(resolve));
+ if (rv != Ci.nsIFilePicker.returnOK) {
+ return;
+ }
+
+ this._sourceFile = filePicker.file;
+ document.getElementById("addrBookSourcePath").textContent =
+ filePicker.file.path;
+
+ if (this._fileType == "csv") {
+ let unmatchedRows = await this._importer.parseCsvFile(filePicker.file);
+ if (unmatchedRows.length) {
+ document.getElementById("csvFieldMap").data = unmatchedRows;
+ this._showCsvFieldMap();
+ return;
+ }
+ }
+ this._showDirectories();
+ }
+
+ /**
+ * Show the csvFieldMap pane, user can map source CSV fields to address book
+ * fields.
+ */
+ _showCsvFieldMap() {
+ Steps.updateSteps(
+ {
+ returnTo: () => {
+ this.reset();
+ this._showCsvFieldMap();
+ },
+ },
+ 2
+ );
+ document.getElementById("addrBookBackButton").hidden = false;
+ this.showPane("csvFieldMap");
+ }
+
+ /**
+ * Handler for the Continue button on the csvFieldMap pane.
+ */
+ async _onSubmitCsvFieldMap() {
+ this._importer.setCsvFields(document.getElementById("csvFieldMap").value);
+ this._showDirectories();
+ }
+
+ /**
+ * Show the directories pane, with a list of existing directories and an
+ * option to create a new directory.
+ */
+ async _showDirectories() {
+ Steps.updateSteps(
+ {
+ returnTo: () => {
+ this.reset();
+ this._showDirectories();
+ },
+ },
+ 1
+ );
+ document.getElementById("addrBookBackButton").hidden = false;
+ this._el.classList.remove("final-step", "progress");
+ let sourceFileName = this._sourceFile.leafName;
+ this._fallbackABName = sourceFileName.slice(
+ 0,
+ sourceFileName.lastIndexOf(".") == -1
+ ? Infinity
+ : sourceFileName.lastIndexOf(".")
+ );
+ document.l10n.setAttributes(
+ document.getElementById("newDirectoryLabel"),
+ "addr-book-import-into-new-directory2",
+ {
+ addressBookName: this._fallbackABName,
+ }
+ );
+ let elList = document.getElementById("directoryList");
+ elList.innerHTML = "";
+ this._directories = MailServices.ab.directories.filter(
+ dir => dir.dirType == Ci.nsIAbManager.JS_DIRECTORY_TYPE
+ );
+ for (let directory of this._directories) {
+ let label = document.createElement("label");
+ label.className = "toggle-container-with-text";
+
+ let input = document.createElement("input");
+ input.type = "radio";
+ input.name = "addrBookDirectory";
+ input.value = directory.dirPrefId;
+ label.append(input);
+
+ let name = document.createElement("div");
+ name.className = "strong";
+ name.textContent = directory.dirName;
+ label.append(name);
+
+ elList.append(label);
+ }
+ document.querySelector("input[name=addrBookDirectory]").checked = true;
+
+ this.showPane("directories");
+ }
+
+ /**
+ * Handler for the Continue button on the directories pane.
+ */
+ _onSelectDirectory() {
+ let index = [
+ ...document.querySelectorAll("input[name=addrBookDirectory]"),
+ ].findIndex(el => el.checked);
+ this._selectedAddressBook = this._directories[index];
+ this._showSummary();
+ }
+
+ _showSummary() {
+ Steps.updateSteps({}, 0);
+ this._el.classList.add("final-step");
+ document.getElementById("addrBookSummaryPath").textContent =
+ this._sourceFile.path;
+ let targetAddressBook = this._selectedAddressBook?.dirName;
+ let newAddressBook = false;
+ if (!targetAddressBook) {
+ targetAddressBook = this._fallbackABName;
+ newAddressBook = true;
+ }
+ let description = this._el.querySelector("#addr-book-summary .description");
+ description.hidden = !newAddressBook;
+ if (newAddressBook) {
+ document.l10n.setAttributes(
+ description,
+ "addr-book-summary-description",
+ {
+ addressBookName: targetAddressBook,
+ }
+ );
+ }
+ document.l10n.setAttributes(
+ document.getElementById("addrBookSummarySubtitle"),
+ "addr-book-summary-title",
+ {
+ addressBookName: targetAddressBook,
+ }
+ );
+ this.showPane("summary");
+ }
+
+ async startImport() {
+ let targetDirectory = this._selectedAddressBook;
+ if (!targetDirectory) {
+ // User selected to create a new address book and import into it. Create
+ // one based on the file name.
+ let dirId = MailServices.ab.newAddressBook(
+ this._fallbackABName,
+ "",
+ Ci.nsIAbManager.JS_DIRECTORY_TYPE
+ );
+ targetDirectory = MailServices.ab.getDirectoryFromId(dirId);
+ }
+
+ this.showProgress("progress-pane-importing2");
+ this._importer.onProgress = (current, total) => {
+ this.updateProgress(current / total);
+ };
+ try {
+ this.finish(
+ await this._importer.startImport(this._sourceFile, targetDirectory)
+ );
+ } catch (e) {
+ this.showError("error-message-failed");
+ throw e;
+ }
+ }
+}
+
+/**
+ * Control the #tabPane-calendar element, to support importing from a calendar
+ * file.
+ */
+class CalendarImporterController extends ImporterController {
+ constructor() {
+ super("tabPane-calendar", "calendar");
+ }
+
+ next() {
+ super.next();
+ switch (this._currentPane) {
+ case "sources":
+ this._onSelectSource();
+ break;
+ case "items":
+ this._onSelectItems();
+ break;
+ case "calendars":
+ this._onSelectCalendar();
+ break;
+ case "summary":
+ window.close();
+ break;
+ }
+ }
+
+ showInitialStep() {
+ this._showSources();
+ }
+
+ /**
+ * When filter changes, re-render the item list.
+ *
+ * @param {HTMLInputElement} filterInput - The filter input.
+ */
+ onFilterChange(filterInput) {
+ let term = filterInput.value.toLowerCase();
+ this._filteredItems = [];
+ for (let item of this._items) {
+ let element = this._itemElements[item.id];
+ if (item.title.toLowerCase().includes(term)) {
+ element.hidden = false;
+ this._filteredItems.push(item);
+ } else {
+ element.hidden = true;
+ }
+ }
+ }
+
+ /**
+ * Select or deselect all visible items.
+ *
+ * @param {boolean} selected - Select all if true, otherwise deselect all.
+ */
+ selectAllItems(selected) {
+ for (let item of this._filteredItems) {
+ let element = this._itemElements[item.id];
+ element.querySelector("input").checked = selected;
+ if (selected) {
+ this._selectedItems.add(item);
+ } else {
+ this._selectedItems.delete(item);
+ }
+ }
+ document.getElementById("calendarNextButton").disabled =
+ this._selectedItems.size == 0;
+ }
+
+ /**
+ * Show the sources pane.
+ */
+ _showSources() {
+ document.getElementById("calendarBackButton").hidden =
+ !Steps.hasStepHistory();
+ Steps.updateSteps(
+ {
+ returnTo: () => {
+ this.reset();
+ this._showSources();
+ },
+ },
+ 3
+ );
+ this.showPane("sources");
+ }
+
+ /**
+ * Handler for the Continue button on the sources pane.
+ */
+ async _onSelectSource() {
+ let filePicker = Cc["@mozilla.org/filepicker;1"].createInstance(
+ Ci.nsIFilePicker
+ );
+ filePicker.appendFilter("", "*.ics");
+ filePicker.appendFilters(Ci.nsIFilePicker.filterAll);
+ filePicker.init(
+ window,
+ await document.l10n.formatValue("file-calendar-description"),
+ filePicker.modeOpen
+ );
+ let rv = await new Promise(resolve => filePicker.open(resolve));
+ if (rv != Ci.nsIFilePicker.returnOK) {
+ return;
+ }
+
+ this._sourceFile = filePicker.file;
+ this._importer = new CalendarFileImporter();
+
+ document.getElementById("calendarSourcePath").textContent =
+ filePicker.file.path;
+
+ this._showItems();
+ }
+
+ /**
+ * Show the sources pane.
+ */
+ async _showItems() {
+ Steps.updateSteps(
+ {
+ returnTo: () => {
+ this.reset();
+ this._showItems();
+ },
+ },
+ 2
+ );
+ document.getElementById("calendarBackButton").hidden = false;
+ let elItemList = document.getElementById("calendar-item-list");
+ document.getElementById("calendarItemsTools").hidden = true;
+ document.l10n.setAttributes(elItemList, "calendar-items-loading");
+ this.showPane("items");
+
+ // Give the UI a chance to render.
+ await new Promise(resolve => setTimeout(resolve, 100));
+
+ try {
+ this._items = await this._importer.parseIcsFile(this._sourceFile);
+ } catch (e) {
+ this.showError("error-failed-to-parse-ics-file");
+ throw e;
+ }
+
+ document.getElementById("calendarItemsTools").hidden =
+ this._items.length < 2;
+ elItemList.innerHTML = "";
+ this._filteredItems = this._items;
+ this._selectedItems = new Set(this._items);
+ this._itemElements = {};
+
+ for (let item of this._items) {
+ let wrapper = document.createElement("div");
+ wrapper.className = "calendar-item-wrapper";
+ elItemList.appendChild(wrapper);
+ this._itemElements[item.id] = wrapper;
+
+ let summary = document.createXULElement("calendar-item-summary");
+ wrapper.appendChild(summary);
+ summary.item = item;
+ summary.updateItemDetails();
+
+ let input = document.createElement("input");
+ input.type = "checkbox";
+ input.checked = true;
+ wrapper.appendChild(input);
+
+ wrapper.addEventListener("click", e => {
+ if (e.target != input) {
+ input.checked = !input.checked;
+ }
+ if (input.checked) {
+ this._selectedItems.add(item);
+ } else {
+ this._selectedItems.delete(item);
+ }
+ document.getElementById("calendarNextButton").disabled =
+ this._selectedItems.size == 0;
+ });
+ }
+ }
+
+ /**
+ * Handler for the Continue button on the items pane.
+ */
+ _onSelectItems() {
+ this._showCalendars();
+ }
+
+ /**
+ * Show the calendars pane, with a list of existing writable calendars and an
+ * option to create a new calendar.
+ */
+ _showCalendars() {
+ Steps.updateSteps(
+ {
+ returnTo: () => {
+ this.reset();
+ this._showCalendars();
+ },
+ },
+ 1
+ );
+ this._el.classList.remove("final-step", "progress");
+ document.getElementById("calendarCalPath").textContent =
+ this._sourceFile.path;
+ let elList = document.getElementById("calendarList");
+ elList.innerHTML = "";
+
+ let sourceFileName = this._sourceFile.leafName;
+ this._fallbackCalendarName = sourceFileName.slice(
+ 0,
+ sourceFileName.lastIndexOf(".") == -1
+ ? Infinity
+ : sourceFileName.lastIndexOf(".")
+ );
+
+ document.l10n.setAttributes(
+ document.getElementById("newCalendarLabel"),
+ "calendar-import-into-new-calendar2",
+ {
+ targetCalendar: this._fallbackCalendarName,
+ }
+ );
+
+ this._calendars = this._importer.getTargetCalendars();
+ for (let calendar of this._calendars) {
+ let label = document.createElement("label");
+ label.className = "toggle-container-with-text";
+
+ let input = document.createElement("input");
+ input.type = "radio";
+ input.name = "targetCalendar";
+ input.value = calendar.id;
+ label.append(input);
+
+ let name = document.createElement("div");
+ name.className = "strong";
+ name.textContent = calendar.name;
+ label.append(name);
+
+ elList.append(label);
+ }
+ document.querySelector("input[name=targetCalendar]").checked = true;
+
+ this.showPane("calendars");
+ }
+
+ _onSelectCalendar() {
+ let index = [
+ ...document.querySelectorAll("input[name=targetCalendar]"),
+ ].findIndex(el => el.checked);
+ this._selectedCalendar = this._calendars[index];
+ this._showSummary();
+ }
+
+ _showSummary() {
+ Steps.updateSteps({}, 0);
+ this._el.classList.add("final-step");
+ document.getElementById("calendarSummaryPath").textContent =
+ this._sourceFile.path;
+ let targetCalendar = this._selectedCalendar?.name;
+ let newCalendar = false;
+ if (!targetCalendar) {
+ targetCalendar = this._fallbackCalendarName;
+ newCalendar = true;
+ }
+ let description = this._el.querySelector("#calendar-summary .description");
+ description.hidden = !newCalendar;
+ if (newCalendar) {
+ document.l10n.setAttributes(description, "calendar-summary-description", {
+ targetCalendar,
+ });
+ }
+ document.l10n.setAttributes(
+ document.getElementById("calendarSummarySubtitle"),
+ "calendar-summary-title",
+ {
+ itemCount: this._selectedItems.size,
+ targetCalendar,
+ }
+ );
+ this.showPane("summary");
+ }
+
+ /**
+ * Handler for the Continue button on the calendars pane.
+ */
+ async startImport() {
+ let targetCalendar = this._selectedCalendar;
+ if (!targetCalendar) {
+ // Create a new calendar.
+ targetCalendar = cal.manager.createCalendar(
+ "storage",
+ Services.io.newURI("moz-storage-calendar://")
+ );
+ targetCalendar.name = this._fallbackCalendarName;
+ cal.manager.registerCalendar(targetCalendar);
+ }
+ this.showProgress("progress-pane-importing2");
+ this._importer.onProgress = (current, total) => {
+ this.updateProgress(current / total);
+ };
+ try {
+ await this._importer.startImport(
+ [...this._selectedItems],
+ targetCalendar
+ );
+ this.finish();
+ } catch (e) {
+ this.showError("error-message-failed");
+ throw e;
+ }
+ }
+}
+
+/**
+ * Control the #tabPane-export element, to support exporting the current profile
+ * to a zip file.
+ */
+class ExportController extends ImporterController {
+ constructor() {
+ super("tabPane-export", "");
+ }
+
+ back() {
+ window.close();
+ }
+
+ async next() {
+ super.next();
+ let [filePickerTitle, brandName] = await document.l10n.formatValues([
+ "export-file-picker2",
+ "export-brand-name",
+ ]);
+ let filePicker = Cc["@mozilla.org/filepicker;1"].createInstance(
+ Ci.nsIFilePicker
+ );
+ filePicker.init(window, filePickerTitle, Ci.nsIFilePicker.modeSave);
+ filePicker.defaultString = `${brandName}_profile_backup.zip`;
+ filePicker.defaultExtension = "zip";
+ filePicker.appendFilter("", "*.zip");
+ let rv = await new Promise(resolve => filePicker.open(resolve));
+ if (
+ ![Ci.nsIFilePicker.returnOK, Ci.nsIFilePicker.returnReplace].includes(rv)
+ ) {
+ return;
+ }
+
+ let exporter = new ProfileExporter();
+ this.showProgress("progress-pane-exporting2");
+ exporter.onProgress = (current, total) => {
+ this.updateProgress(current / total);
+ };
+ try {
+ await exporter.startExport(filePicker.file);
+ this.finish();
+ } catch (e) {
+ this.showError("error-export-failed");
+ throw e;
+ }
+ }
+
+ openProfileFolder() {
+ Services.dirsvc.get("ProfD", Ci.nsIFile).reveal();
+ }
+}
+
+class StartController extends ImporterController {
+ constructor() {
+ super("tabPane-start", "start");
+ }
+
+ next() {
+ super.next();
+ switch (this._currentPane) {
+ case "sources":
+ this._onSelectSource();
+ break;
+ case "file":
+ this._onSelectFile();
+ break;
+ }
+ }
+
+ showInitialStep() {
+ this._showSources();
+ }
+
+ /**
+ * Show the sources pane.
+ */
+ _showSources() {
+ Steps.updateSteps(
+ {
+ returnTo: () => {
+ this.reset();
+ showTab("tab-start");
+ //showTab will always call showInitialStep
+ },
+ },
+ 3
+ );
+ document.getElementById("startBackButton").hidden = true;
+ this.showPane("sources");
+ }
+
+ /**
+ * Handler for the Continue button on the sources pane.
+ */
+ async _onSelectSource() {
+ let checkedInput = document.querySelector("input[name=appSource]:checked");
+
+ switch (checkedInput.value) {
+ case "file":
+ this._showFile();
+ break;
+ default:
+ await profileController._onSelectSource(checkedInput.value);
+ showTab("tab-app");
+ // Don't change back button state, since we switch to app flow.
+ return;
+ }
+
+ document.getElementById("startBackButton").hidden = false;
+ }
+
+ _showFile() {
+ Steps.updateSteps(
+ {
+ returnTo: () => {
+ this.reset();
+ showTab("tab-start");
+ this._showFile();
+ },
+ },
+ 3
+ );
+ this.showPane("file");
+ }
+
+ async _onSelectFile() {
+ let checkedInput = document.querySelector("input[name=startFile]:checked");
+ switch (checkedInput.value) {
+ case "profile":
+ // Go to the import profile from zip file step in profile flow for TB.
+ profileController.reset();
+ await profileController._onSelectSource("Thunderbird");
+ document.getElementById("appFilePickerZip").checked = true;
+ await profileController._onSelectProfile();
+ showTab("tab-app");
+ break;
+ case "calendar":
+ calendarController.reset();
+ showTab("tab-calendar");
+ calendarController.showInitialStep();
+ await calendarController._onSelectSource();
+ break;
+ case "addressbook":
+ addrBookController.reset();
+ showTab("tab-addressBook");
+ addrBookController.showInitialStep();
+ break;
+ }
+ }
+}
+
+/**
+ * Show a specific importing tab.
+ *
+ * @param {"tab-app"|"tab-addressBook"|"tab-calendar"|"tab-export"|"tab-start"} tabId -
+ * Tab to show.
+ * @param {boolean} [reset=false] - If the state should be reset as if this was
+ * the initial tab shown.
+ */
+function showTab(tabId, reset = false) {
+ if (reset) {
+ Steps.reset();
+ restart();
+ }
+ let selectedPaneId = `tabPane-${tabId.split("-")[1]}`;
+ let isExport = tabId === "tab-export";
+ document.getElementById("importDocs").hidden = isExport;
+ document.getElementById("exportDocs").hidden = !isExport;
+ Steps.toggle(!isExport);
+ document.l10n.setAttributes(
+ document.querySelector("title"),
+ isExport ? "export-page-title" : "import-page-title"
+ );
+ document.querySelector("link[rel=icon]").href = isExport
+ ? "chrome://messenger/skin/icons/new/compact/export.svg"
+ : "chrome://messenger/skin/icons/new/compact/import.svg";
+ location.hash = isExport ? "export" : "";
+ for (let tabPane of document.querySelectorAll("[id^=tabPane-]")) {
+ tabPane.hidden = tabPane.id != selectedPaneId;
+ }
+ for (let el of document.querySelectorAll("[id^=tab-]")) {
+ el.classList.toggle("is-selected", el.id == tabId);
+ }
+ if (!Steps.hasStepHistory()) {
+ switch (tabId) {
+ case "tab-start":
+ startController.showInitialStep();
+ break;
+ case "tab-addressBook":
+ addrBookController.showInitialStep();
+ break;
+ case "tab-calendar":
+ calendarController.showInitialStep();
+ break;
+ default:
+ }
+ }
+}
+
+/**
+ * Restart the import wizard. Resets all previous choices.
+ */
+function restart() {
+ startController.reset();
+ profileController.reset();
+ addrBookController.reset();
+ calendarController.reset();
+ Steps.backTo(0);
+}
+
+let profileController;
+let addrBookController;
+let calendarController;
+let exportController;
+let startController;
+
+document.addEventListener("DOMContentLoaded", () => {
+ profileController = new ProfileImporterController();
+ addrBookController = new AddrBookImporterController();
+ calendarController = new CalendarImporterController();
+ exportController = new ExportController();
+ startController = new StartController();
+ showTab(location.hash === "#export" ? "tab-export" : "tab-start", true);
+});
diff --git a/comm/mailnews/import/content/aboutImport.xhtml b/comm/mailnews/import/content/aboutImport.xhtml
new file mode 100644
index 0000000000..e4a259d1f5
--- /dev/null
+++ b/comm/mailnews/import/content/aboutImport.xhtml
@@ -0,0 +1,477 @@
+<?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/. -->
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+ <title data-l10n-id="import-page-title"></title>
+
+ <link rel="stylesheet" href="chrome://messenger/skin/messenger.css"/>
+ <link rel="stylesheet" href="chrome://global/skin/global.css"/>
+ <link rel="stylesheet" href="chrome://calendar/skin/shared/calendar-attendees.css"/>
+ <link rel="stylesheet" href="chrome://calendar/skin/shared/calendar-item-summary.css"/>
+ <link rel="stylesheet" href="chrome://messenger/skin/accountSetup.css"/>
+ <link rel="stylesheet" href="chrome://messenger/skin/aboutImport.css"/>
+
+ <link rel="localization" href="branding/brand.ftl" />
+ <link rel="localization" href="messenger/aboutImport.ftl"/>
+
+ <link rel="icon" href="chrome://messenger/skin/icons/new/compact/import.svg" sizes="any"/>
+
+ <script defer="" src="chrome://messenger/content/aboutImport.js"></script>
+ <script defer="" src="chrome://messenger/content/csv-field-map.js"></script>
+ <script defer="" src="chrome://calendar/content/widgets/calendar-item-summary.js"></script>
+</head>
+<body>
+ <main id="main">
+ <nav>
+ <ol id="stepNav" data-l10n-id="step-list">
+ <li id="navConfirm">
+ <button data-l10n-id="step-confirm" disabled="disabled"></button>
+ </li>
+ </ol>
+ </nav>
+ <section id="errorNotifications"></section>
+ <div id="tabPane-start" class="tabPane">
+ <section id="start-sources">
+ <h1 id="startSource" data-l10n-id="import-start"></h1>
+ <h2 data-l10n-id="import-start-title"></h2>
+ <p data-l10n-id="import-start-description" class="description"></p>
+ <div class="source-list indent">
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="Thunderbird"
+ name="appSource"
+ checked="checked"/>
+ <p data-l10n-id="source-thunderbird"></p>
+ <p class="tip-caption" data-l10n-id="source-thunderbird-description"></p>
+ </label>
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="Seamonkey"
+ name="appSource"/>
+ <p data-l10n-id="source-seamonkey"></p>
+ <p class="tip-caption" data-l10n-id="source-seamonkey-description"></p>
+ </label>
+#ifdef XP_WIN
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="Outlook"
+ name="appSource"/>
+ <p data-l10n-id="source-outlook"></p>
+ <p class="tip-caption" data-l10n-id="source-outlook-description"></p>
+ </label>
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="Becky"
+ name="appSource"/>
+ <p data-l10n-id="source-becky"></p>
+ <p class="tip-caption" data-l10n-id="source-becky-description"></p>
+ </label>
+#endif
+#ifdef XP_MACOSX
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="AppleMail"
+ name="appSource"/>
+ <p data-l10n-id="source-apple-mail"></p>
+ <p class="tip-caption" data-l10n-id="source-apple-mail-description"></p>
+ </label>
+#endif
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="file"
+ name="appSource"/>
+ <p data-l10n-id="source-file2"></p>
+ <p class="tip-caption" data-l10n-id="source-file-description"></p>
+ </label>
+ </div>
+ </section>
+ <section id="start-file">
+ <h1 id="startFile" data-l10n-id="import-file"></h1>
+ <h2 data-l10n-id="import-file-title"></h2>
+ <p data-l10n-id="import-file-description" class="description"></p>
+ <div class="option-list indent">
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="profile"
+ name="startFile"
+ checked="checked"/>
+ <p data-l10n-id="file-profile2"></p>
+ <p data-l10n-id="file-profile-description" class="tip-caption"></p>
+ </label>
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="calendar"
+ name="startFile"/>
+ <p data-l10n-id="file-calendar"></p>
+ <p data-l10n-id="file-calendar-description" class="tip-caption"></p>
+ </label>
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="addressbook"
+ name="startFile"/>
+ <p data-l10n-id="file-addressbook"></p>
+ <p data-l10n-id="file-addressbook-description" class="tip-caption"></p>
+ </label>
+ </div>
+ </section>
+ <footer class="buttons-container">
+ <button id="startBackButton"
+ class="back"
+ onclick="startController.back()"
+ data-l10n-id="button-back"></button>
+ <button class="primary continue"
+ onclick="startController.next()"
+ data-l10n-id="button-continue"></button>
+ </footer>
+ </div>
+ <div id="tabPane-app" class="tabPane restart-required">
+ <section id="app-profiles">
+ <h1 id="profilesPaneTitle" data-l10n-id="import-from-app"></h1>
+ <h2 id="profilesPaneSubtitle"></h2>
+ <div class="profile-list indent" id="profileList"></div>
+ <div class="profile-list indent" id="filePickerList">
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="file-picker-dir"
+ name="appProfile"/>
+ <p data-l10n-id="profile-file-picker-directory"></p>
+ </label>
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="file-picker-zip"
+ name="appProfile"
+ id="appFilePickerZip"/>
+ <p data-l10n-id="profile-file-picker-archive"></p>
+ <p data-l10n-id="profile-file-picker-archive-description" class="tip-caption"></p>
+ </label>
+ </div>
+ </section>
+ <section id="app-items">
+ <h1 data-l10n-id="import-from-app"></h1>
+ <dl>
+ <div id="appSourceProfileNameWrapper">
+ <dt data-l10n-id="items-pane-profile-name"></dt>
+ <dd id="appSourceProfileName"></dd>
+ </div>
+ <dt data-l10n-id="items-pane-directory"></dt>
+ <dd id="appSourceProfilePath"></dd>
+ </dl>
+ <h2 data-l10n-id="items-pane-title2" class="light-heading"></h2>
+ <p>
+ <img src="chrome://messenger/skin/icons/new/compact/info.svg"
+ class="info icon"
+ alt=""/>
+ <span data-l10n-id="items-pane-override"></span>
+ </p>
+ <div class="option-list indent" id="appItemsList">
+ <label class="toggle-container-with-text">
+ <input type="checkbox" id="checkAccounts"/>
+ <span data-l10n-id="items-pane-checkbox-accounts"></span>
+ </label>
+ <label class="toggle-container-with-text">
+ <input type="checkbox" id="checkAddressBooks"/>
+ <span data-l10n-id="items-pane-checkbox-address-books"></span>
+ </label>
+ <label class="toggle-container-with-text">
+ <input type="checkbox" id="checkCalendars"/>
+ <span data-l10n-id="items-pane-checkbox-calendars"></span>
+ </label>
+ <label class="toggle-container-with-text">
+ <input type="checkbox" id="checkMailMessages"/>
+ <span data-l10n-id="items-pane-checkbox-mail-messages"></span>
+ </label>
+ </div>
+ <p class="center">
+ <img src="chrome://messenger/skin/icons/new/compact/warning.svg"
+ class="icon warn"
+ alt=""/>
+ <span data-l10n-id="summary-pane-warning"></span>
+ </p>
+ </section>
+ <section id="app-summary">
+ <h1 data-l10n-id="import-from-app"></h1>
+ <dl>
+ <div id="appSummaryProfileNameWrapper">
+ <dt data-l10n-id="items-pane-profile-name"></dt>
+ <dd id="appSummaryProfileName"></dd>
+ </div>
+ <dt data-l10n-id="items-pane-directory"></dt>
+ <dd id="appSummaryProfilePath"></dd>
+ </dl>
+ <h2 data-l10n-id="summary-pane-title" class="light-heading"></h2>
+ <ul id="appSummaryItems" class="summary-items indent">
+ </ul>
+ <button id="appStartImport"
+ onclick="profileController.startImport()"
+ class="primary before-progress center-button"
+ data-l10n-id="summary-pane-start"></button>
+ <div class="progressPane">
+ <section class="progressPane-progress">
+ <progress class="progressPaneProgressBar"></progress>
+ <p class="progressPaneDesc tip-caption"></p>
+ </section>
+ </div>
+ <button class="progressFinish primary center-button"
+ onclick="profileController.next()"
+ data-l10n-id="button-finish"></button>
+ <button data-l10n-id="summary-pane-start-over"
+ class="progressFinish no-restart center-button btn-link"
+ onclick="restart()"></button>
+ <p class="restart-only center">
+ <img src="chrome://messenger/skin/icons/new/compact/warning.svg"
+ class="icon warn"
+ alt=""/>
+ <span data-l10n-id="summary-pane-warning"></span>
+ </p>
+ </section>
+ <footer class="buttons-container">
+ <button id="profileBackButton"
+ class="back"
+ onclick="profileController.back()"
+ data-l10n-id="button-back"></button>
+ <button id="profileNextButton"
+ class="primary next-button"
+ onclick="profileController.next()"
+ data-l10n-id="button-continue"></button>
+ </footer>
+ </div>
+
+ <div id="tabPane-addressBook" class="tabPane">
+ <section id="addr-book-sources">
+ <h1 id="importAddressBook" data-l10n-id="import-address-book-title"></h1>
+ <h2 data-l10n-id="import-file-title"></h2>
+ <p data-l10n-id="import-from-addr-book-file-description" class="description"></p>
+ <div class="source-list indent">
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="csv"
+ name="addrBookSource"
+ checked=""/>
+ <p data-l10n-id="addr-book-csv-file"></p>
+ </label>
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="ldif"
+ name="addrBookSource"/>
+ <p data-l10n-id="addr-book-ldif-file"></p>
+ </label>
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="vcard"
+ name="addrBookSource"/>
+ <p data-l10n-id="addr-book-vcard-file"></p>
+ </label>
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="sqlite"
+ name="addrBookSource"/>
+ <p data-l10n-id="addr-book-sqlite-file"></p>
+ </label>
+ <label class="toggle-container-with-text">
+ <input type="radio"
+ value="mab"
+ name="addrBookSource"/>
+ <p data-l10n-id="addr-book-mab-file"></p>
+ </label>
+ </div>
+ </section>
+ <section id="addr-book-csvFieldMap">
+ <h1 data-l10n-id="import-address-book-title"></h1>
+ <h2 data-l10n-id="addr-book-csv-field-map-title"></h2>
+ <p data-l10n-id="addr-book-csv-field-map-desc" class="description"></p>
+ <csv-field-map id="csvFieldMap"/>
+ </section>
+ <section id="addr-book-directories">
+ <h1 data-l10n-id="import-address-book-title"></h1>
+ <h2 data-l10n-id="addr-book-directories-title"></h2>
+ <dl>
+ <dt data-l10n-id="addr-book-directories-pane-source"></dt>
+ <dd id="addrBookSourcePath"></dd>
+ </dl>
+ <div class="profile-list indent" id="directoryList"></div>
+ <label class="toggle-container-with-text indent">
+ <input type="radio"
+ value=".new"
+ name="addrBookDirectory"/>
+ <p id="newDirectoryLabel"></p>
+ </label>
+ </section>
+ <section id="addr-book-summary">
+ <h1 data-l10n-id="import-address-book-title"></h1>
+ <h2 id="addrBookSummarySubtitle"></h2>
+ <p class="description"></p>
+ <dl>
+ <dt data-l10n-id="addr-book-directories-pane-source"></dt>
+ <dd id="addrBookSummaryPath"></dd>
+ </dl>
+ <h2 data-l10n-id="summary-pane-title" class="light-heading"></h2>
+ <ul class="summary-items indent">
+ <li data-l10n-id="items-pane-checkbox-address-books"></li>
+ </ul>
+ <button id="addrBookStartImport"
+ onclick="addrBookController.startImport()"
+ class="primary before-progress center-button"
+ data-l10n-id="summary-pane-start"></button>
+ <div class="progressPane">
+ <section class="progressPane-progress">
+ <progress class="progressPaneProgressBar"></progress>
+ <p class="progressPaneDesc tip-caption"></p>
+ </section>
+ </div>
+ <button class="progressFinish primary center-button"
+ onclick="addrBookController.next()"
+ data-l10n-id="button-finish"></button>
+ <button data-l10n-id="summary-pane-start-over"
+ class="progressFinish no-restart center-button btn-link"
+ onclick="restart()"></button>
+ <p class="restart-only center">
+ <img src="chrome://messenger/skin/icons/new/compact/warning.svg"
+ class="icon warn"
+ alt=""/>
+ <span data-l10n-id="summary-pane-warning"></span>
+ </p>
+ </section>
+ <footer class="buttons-container">
+ <button id="addrBookBackButton"
+ class="back"
+ onclick="addrBookController.back()"
+ data-l10n-id="button-back"></button>
+ <button class="primary next-button continue"
+ id="addrBookNextButton"
+ onclick="addrBookController.next()"
+ data-l10n-id="button-continue"></button>
+ </footer>
+ </div>
+
+ <div id="tabPane-calendar" class="tabPane">
+ <section id="calendar-sources">
+ <h1 data-l10n-id="import-calendar-title"></h1>
+ <h2 data-l10n-id="import-from-calendar-file-desc"></h2>
+ </section>
+ <section id="calendar-items">
+ <h1 data-l10n-id="import-calendar-title"></h1>
+ <dl>
+ <dt data-l10n-id="addr-book-directories-pane-source"></dt>
+ <dd id="calendarSourcePath"></dd>
+ </dl>
+ <h2 data-l10n-id="calendar-items-title"></h2>
+ <div id="calendarItemsTools">
+ <input type="search"
+ data-l10n-id="calendar-items-filter-input"
+ oninput="calendarController.onFilterChange(this)"/>
+ <button data-l10n-id="calendar-deselect-all-items"
+ onclick="calendarController.selectAllItems(false)"></button>
+ <button data-l10n-id="calendar-select-all-items"
+ onclick="calendarController.selectAllItems(true)"></button>
+ </div>
+ <div id="calendar-item-list"></div>
+ </section>
+ <section id="calendar-calendars">
+ <h1 data-l10n-id="import-calendar-title"></h1>
+ <dl>
+ <dt data-l10n-id="addr-book-directories-pane-source"></dt>
+ <dd id="calendarCalPath"></dd>
+ </dl>
+ <h2 data-l10n-id="calendar-target-title"></h2>
+ <div class="profile-list indent" id="calendarList"></div>
+ <label class="toggle-container-with-text indent">
+ <input type="radio"
+ value=".new"
+ name="targetCalendar"/>
+ <p id="newCalendarLabel"></p>
+ </label>
+ </section>
+ <section id="calendar-summary">
+ <h1 data-l10n-id="import-calendar-title"></h1>
+ <h2 id="calendarSummarySubtitle"></h2>
+ <p class="description"></p>
+ <dl>
+ <dt data-l10n-id="addr-book-directories-pane-source"></dt>
+ <dd id="calendarSummaryPath"></dd>
+ </dl>
+ <h2 data-l10n-id="summary-pane-title" class="light-heading"></h2>
+ <ul class="summary-items indent">
+ <li data-l10n-id="items-pane-checkbox-calendars"></li>
+ </ul>
+ <button id="calendarStartImport"
+ onclick="calendarController.startImport()"
+ class="primary before-progress center-button"
+ data-l10n-id="summary-pane-start"></button>
+ <div class="progressPane">
+ <section class="progressPane-progress">
+ <progress class="progressPaneProgressBar"></progress>
+ <p class="progressPaneDesc tip-caption"></p>
+ </section>
+ </div>
+ <button class="progressFinish primary center-button"
+ onclick="calendarController.next()"
+ data-l10n-id="button-finish"></button>
+ <button data-l10n-id="summary-pane-start-over"
+ class="progressFinish no-restart center-button btn-link"
+ onclick="restart()"></button>
+ <p class="restart-only center">
+ <img src="chrome://messenger/skin/icons/new/compact/warning.svg"
+ class="icon warn"
+ alt=""/>
+ <span data-l10n-id="summary-pane-warning"></span>
+ </p>
+ </section>
+ <footer class="buttons-container">
+ <button id="calendarBackButton"
+ class="back"
+ onclick="calendarController.back()"
+ data-l10n-id="button-back"></button>
+ <button class="primary next-button continue"
+ id="calendarNextButton"
+ onclick="calendarController.next()"
+ data-l10n-id="button-continue"></button>
+ </footer>
+ </div>
+
+ <div id="tabPane-export" class="tabPane">
+ <section>
+ <h1 data-l10n-id="export-profile"></h1>
+ <h2 data-l10n-id="export-profile-title"></h2>
+ <p class="description">
+ <img src="chrome://messenger/skin/icons/new/compact/info.svg"
+ class="info icon"
+ alt=""/>
+ <span data-l10n-id="export-profile-description"></span>
+ <button data-l10n-id="export-open-profile-folder"
+ onclick="exportController.openProfileFolder()"
+ class="btn-link"></button>
+ </p>
+ <button id="exportButton"
+ onclick="exportController.next()"
+ class="primary before-progress center-button"
+ data-l10n-id="button-export"></button>
+ <div class="progressPane">
+ <section class="progressPane-progress">
+ <progress class="progressPaneProgressBar"></progress>
+ <p class="progressPaneDesc tip-caption"></p>
+ </section>
+ </div>
+ <button class="progressFinish primary center-button"
+ onclick="exportController.back()"
+ data-l10n-id="button-finish"></button>
+ </section>
+ </div>
+
+ <footer id="importFooter" class="tip-caption">
+ <p data-l10n-id="footer-help"></p>
+ <a id="importDocs"
+ data-l10n-id="footer-import-documentation"
+ href="https://support.mozilla.org/kb/thunderbird-import"></a>
+ <a id="exportDocs"
+ data-l10n-id="footer-export-documentation"
+ href="https://support.mozilla.org/kb/thunderbird-export"></a>
+ -
+ <a data-l10n-id="footer-support-forum"
+ href="https://support.mozilla.org/products/thunderbird"></a>
+ </footer>
+ </main>
+</body>
+</html>
diff --git a/comm/mailnews/import/content/csv-field-map.js b/comm/mailnews/import/content/csv-field-map.js
new file mode 100644
index 0000000000..544a9cf50c
--- /dev/null
+++ b/comm/mailnews/import/content/csv-field-map.js
@@ -0,0 +1,280 @@
+/* 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 { exportAttributes } = ChromeUtils.import(
+ "resource:///modules/AddrBookUtils.jsm"
+);
+
+/**
+ * A component to config the mapping between CSV fields and address book fields.
+ * For each CSV field, there is a <select> with address book fields as options.
+ * If an address book field is selected for one CSV field, it can't be used for
+ * another CSV field.
+ */
+class CsvFieldMap extends HTMLElement {
+ /** Render the first two rows from the source CSV data. */
+ DATA_ROWS_LIMIT = 2;
+
+ /** @type {string[]} - The indexes of target address book fields. */
+ get value() {
+ return [...this._elTbody.querySelectorAll("select")].map(
+ select => select.value
+ );
+ }
+
+ /** @type {string[][]} - An array of rows, each row is an array of columns. */
+ set data(rows) {
+ this._init();
+ this._rows = rows.slice(0, this.DATA_ROWS_LIMIT);
+ this._render();
+ }
+
+ /**
+ * Init internal states.
+ */
+ _init() {
+ let bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/importMsgs.properties"
+ );
+ this._supportedFields = [];
+ for (let [, stringId] of exportAttributes) {
+ if (stringId) {
+ this._supportedFields.push(bundle.GetStringFromID(stringId));
+ }
+ }
+ // Create an index array ["0", "1", "2", ..., "<length -1>"].
+ this._allFieldIndexes = Array.from({
+ length: this._supportedFields.length,
+ }).map((_, index) => index.toString());
+ }
+
+ /**
+ * Init <option> list for all <select> elements.
+ */
+ _initSelectOptions() {
+ let fields;
+ let fieldIndexes = Services.prefs.getCharPref("mail.import.csv.fields", "");
+ if (fieldIndexes) {
+ // If the user has done CSV importing before, show the same field mapping.
+ fieldIndexes = fieldIndexes.split(",");
+ fields = fieldIndexes.map(i => (i == "" ? i : this._supportedFields[+i]));
+ } else {
+ // Show the same field orders as in an exported CSV file.
+ fields = this._supportedFields;
+ fieldIndexes = this._allFieldIndexes;
+ }
+
+ let i = 0;
+ for (let select of this.querySelectorAll("select")) {
+ if (fields[i]) {
+ let option = document.createElement("option");
+ option.value = fieldIndexes[i];
+ option.textContent = fields[i];
+ select.add(option);
+ } else {
+ select.disabled = true;
+ select
+ .closest("tr")
+ .querySelector("input[type=checkbox]").checked = false;
+ }
+ i++;
+ }
+
+ this._updateSelectOptions();
+ }
+
+ /**
+ * When a <select> is disabled, we remove all its options. This function is to
+ * add all available options back.
+ *
+ * @param {HTMLSelectElement} select - The <select> element.
+ */
+ _enableSelect(select) {
+ let selects = [...this._elTbody.querySelectorAll("select")];
+ let selectedFieldIndexes = selects.map(select => select.value);
+ let availableFieldIndexes = this._allFieldIndexes.filter(
+ index => !selectedFieldIndexes.includes(index)
+ );
+ for (let i = 0; i < availableFieldIndexes.length; i++) {
+ let option = document.createElement("option");
+ option.value = availableFieldIndexes[i];
+ option.textContent = this._supportedFields[option.value];
+ select.add(option);
+ }
+ }
+
+ /**
+ * Update the options of all <select> elements. The result is if an option is
+ * selected by a <select>, this option should no longer be shown as an option
+ * for other <select>.
+ *
+ * @param {HTMLSelectElement} [changedSelect] - This param is present only
+ * when an option is selected, we don't need to update the options of this
+ * <select> element.
+ */
+ _updateSelectOptions(changedSelect) {
+ let selects = [...this._elTbody.querySelectorAll("select")];
+ let selectedFieldIndexes = selects.map(select => select.value);
+ let availableFieldIndexes = this._allFieldIndexes.filter(
+ index => !selectedFieldIndexes.includes(index)
+ );
+
+ for (let select of selects) {
+ if (select.disabled || select == changedSelect) {
+ continue;
+ }
+ for (let i = select.options.length - 1; i >= 0; i--) {
+ // Remove unselected options first.
+ if (i != select.selectedIndex) {
+ select.remove(i);
+ }
+ }
+ for (let i = 0; i < availableFieldIndexes.length; i++) {
+ // Add all available options.
+ let option = document.createElement("option");
+ option.value = availableFieldIndexes[i];
+ option.textContent = this._supportedFields[option.value];
+ select.add(option);
+ }
+ }
+ }
+
+ /**
+ * Handle the change event of <select> and <input type="checkbox">.
+ */
+ _bindEvents() {
+ this._elTbody.addEventListener("change", e => {
+ let el = e.target;
+ if (el.tagName == "select") {
+ this._updateSelectOptions(el);
+ } else if (el.tagName == "input" && el.type == "checkbox") {
+ let select = el.closest("tr").querySelector("select");
+ select.disabled = !el.checked;
+ if (select.disabled) {
+ // Because it's disabled, remove all the options.
+ for (let i = select.options.length - 1; i >= 0; i--) {
+ select.remove(i);
+ }
+ } else {
+ this._enableSelect(select);
+ }
+ this._updateSelectOptions();
+ }
+ });
+ }
+
+ /**
+ * Render the table structure.
+ */
+ async _renderLayout() {
+ this.innerHTML = "";
+ let [
+ firstRowContainsHeaders,
+ sourceField,
+ sourceFirstRecord,
+ sourceSecondRecord,
+ targetField,
+ ] = await document.l10n.formatValues([
+ "csv-first-row-contains-headers",
+ "csv-source-field",
+ "csv-source-first-record",
+ "csv-source-second-record",
+ "csv-target-field",
+ ]);
+
+ let label = document.createElement("label");
+ label.className = "toggle-container-with-text";
+ let checkbox = document.createElement("input");
+ checkbox.type = "checkbox";
+ checkbox.checked = Services.prefs.getBoolPref(
+ "mail.import.csv.skipfirstrow",
+ true
+ );
+ let labelText = document.createElement("span");
+ labelText.textContent = firstRowContainsHeaders;
+ label.appendChild(checkbox);
+ label.appendChild(labelText);
+ this.appendChild(label);
+
+ let table = document.createElement("table");
+
+ let thead = document.createElement("thead");
+ let tr = document.createElement("tr");
+ let headers = [];
+ for (let colName of [sourceField, sourceFirstRecord, targetField, ""]) {
+ let th = document.createElement("th");
+ th.textContent = colName;
+ tr.appendChild(th);
+ headers.push(th);
+ }
+ thead.appendChild(tr);
+ table.appendChild(thead);
+
+ this._elTbody = document.createElement("tbody");
+ table.appendChild(this._elTbody);
+
+ this.appendChild(table);
+ this._bindEvents();
+
+ checkbox.addEventListener("change", () => {
+ if (checkbox.checked) {
+ headers[0].textContent = sourceField;
+ headers[1].textContent = sourceFirstRecord;
+ } else {
+ headers[0].textContent = sourceFirstRecord;
+ headers[1].textContent = sourceSecondRecord;
+ }
+ Services.prefs.setBoolPref(
+ "mail.import.csv.skipfirstrow",
+ checkbox.checked
+ );
+ });
+ }
+
+ /**
+ * Render the table content. Each row contains four columns:
+ * Source field | Source Data | Address book field | <checkbox>
+ */
+ _renderTable() {
+ let colCount = this._rows[0].length;
+ for (let i = 0; i < colCount; i++) {
+ let tr = document.createElement("tr");
+
+ // Render the source field name and source data.
+ for (let j = 0; j < this.DATA_ROWS_LIMIT; j++) {
+ let td = document.createElement("td");
+ td.textContent = this._rows[j]?.[i] || "";
+ tr.appendChild(td);
+ }
+
+ // Render a <select> for target field name.
+ let td = document.createElement("td");
+ let select = document.createElement("select");
+ td.appendChild(select);
+ tr.appendChild(td);
+
+ // Render a checkbox.
+ td = document.createElement("td");
+ let checkbox = document.createElement("input");
+ checkbox.type = "checkbox";
+ checkbox.checked = true;
+ td.appendChild(checkbox);
+ tr.appendChild(td);
+
+ this._elTbody.appendChild(tr);
+ }
+
+ this._initSelectOptions();
+ }
+
+ /**
+ * Render the table layout and content.
+ */
+ async _render() {
+ await this._renderLayout();
+ this._renderTable();
+ }
+}
+
+customElements.define("csv-field-map", CsvFieldMap);
diff --git a/comm/mailnews/import/content/fieldMapImport.js b/comm/mailnews/import/content/fieldMapImport.js
new file mode 100644
index 0000000000..3f777c0cb6
--- /dev/null
+++ b/comm/mailnews/import/content/fieldMapImport.js
@@ -0,0 +1,259 @@
+/* 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 importService;
+var fieldMap = null;
+var gRecordNum = 0;
+var addInterface = null;
+var dialogResult = null;
+var gPreviousButton;
+var gNextButton;
+var gMoveUpButton;
+var gMoveDownButton;
+var gListbox;
+var gSkipFirstRecordButton;
+
+window.addEventListener("DOMContentLoaded", event => {
+ OnLoadFieldMapImport();
+});
+window.addEventListener("load", resizeColumns, { once: true });
+window.addEventListener("resize", resizeColumns);
+
+document.addEventListener("dialogaccept", FieldImportOKButton);
+
+function resizeColumns() {
+ let list = document.getElementById("fieldList");
+ let cols = list.getElementsByTagName("treecol");
+ list.style.setProperty(
+ "--column1width",
+ cols[0].getBoundingClientRect().width + "px"
+ );
+ list.style.setProperty(
+ "--column2width",
+ cols[1].getBoundingClientRect().width + "px"
+ );
+}
+
+function OnLoadFieldMapImport() {
+ top.importService = Cc["@mozilla.org/import/import-service;1"].getService(
+ Ci.nsIImportService
+ );
+
+ // We need a field map object...
+ // assume we have one passed in? or just make one?
+ if (window.arguments && window.arguments[0]) {
+ top.fieldMap = window.arguments[0].fieldMap;
+ top.addInterface = window.arguments[0].addInterface;
+ top.dialogResult = window.arguments[0].result;
+ }
+ if (top.fieldMap == null) {
+ top.fieldMap = top.importService.CreateNewFieldMap();
+ top.fieldMap.DefaultFieldMap(top.fieldMap.numMozFields);
+ }
+
+ gMoveUpButton = document.getElementById("upButton");
+ gMoveDownButton = document.getElementById("downButton");
+ gPreviousButton = document.getElementById("previous");
+ gNextButton = document.getElementById("next");
+ gListbox = document.getElementById("fieldList");
+ gSkipFirstRecordButton = document.getElementById("skipFirstRecord");
+
+ // Set the state of the skip first record button
+ gSkipFirstRecordButton.checked = top.fieldMap.skipFirstRecord;
+
+ ListFields();
+ browseDataPreview(1);
+ gListbox.selectedItem = gListbox.getItemAtIndex(0);
+ disableMoveButtons();
+}
+
+function IndexInMap(index) {
+ var count = top.fieldMap.mapSize;
+ for (var i = 0; i < count; i++) {
+ if (top.fieldMap.GetFieldMap(i) == index) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function ListFields() {
+ if (top.fieldMap == null) {
+ return;
+ }
+
+ // Add rows for every mapped field.
+ let count = top.fieldMap.mapSize;
+ for (let i = 0; i < count; i++) {
+ let index = top.fieldMap.GetFieldMap(i);
+ if (index == -1) {
+ continue;
+ }
+ AddFieldToList(
+ top.fieldMap.GetFieldDescription(index),
+ index,
+ top.fieldMap.GetFieldActive(i)
+ );
+ }
+
+ // Add rows every possible field we don't already have a row for.
+ count = top.fieldMap.numMozFields;
+ for (let i = 0; i < count; i++) {
+ if (!IndexInMap(i)) {
+ AddFieldToList(top.fieldMap.GetFieldDescription(i), i, false);
+ }
+ }
+
+ // Add dummy rows if the data has more fields than Thunderbird does.
+ let data = top.addInterface.GetData("sampleData-0");
+ if (!(data instanceof Ci.nsISupportsString)) {
+ return;
+ }
+ count = data.data.split("\n").length;
+ for (let i = gListbox.itemCount; i < count; i++) {
+ AddFieldToList(null, -1, false);
+ }
+}
+
+function CreateField(name, index, on) {
+ var item = document.createXULElement("richlistitem");
+ item.setAttribute("align", "center");
+ item.setAttribute("field-index", index);
+ item.setAttribute("allowevents", "true");
+
+ var checkboxCell = document.createXULElement("hbox");
+ checkboxCell.setAttribute("style", "width: var(--column1width)");
+ let checkbox = document.createXULElement("checkbox");
+ if (!name) {
+ checkbox.disabled = true;
+ } else if (on) {
+ checkbox.setAttribute("checked", "true");
+ }
+ checkboxCell.appendChild(checkbox);
+
+ var firstCell = document.createXULElement("label");
+ firstCell.setAttribute("style", "width: var(--column2width)");
+ firstCell.setAttribute("value", name || "");
+
+ var secondCell = document.createXULElement("label");
+ secondCell.setAttribute("class", "importsampledata");
+ secondCell.setAttribute("flex", "1");
+
+ item.appendChild(checkboxCell);
+ item.appendChild(firstCell);
+ item.appendChild(secondCell);
+ return item;
+}
+
+function AddFieldToList(name, index, on) {
+ var item = CreateField(name, index, on);
+ gListbox.appendChild(item);
+}
+
+// The "Move Up/Move Down" buttons should move the items in the left column
+// up/down but the values in the right column should not change.
+function moveItem(up) {
+ var selectedItem = gListbox.selectedItem;
+ var swapPartner = up
+ ? gListbox.getPreviousItem(selectedItem, 1)
+ : gListbox.getNextItem(selectedItem, 1);
+
+ var tmpLabel = swapPartner.lastElementChild.getAttribute("value");
+ swapPartner.lastElementChild.setAttribute(
+ "value",
+ selectedItem.lastElementChild.getAttribute("value")
+ );
+ selectedItem.lastElementChild.setAttribute("value", tmpLabel);
+
+ var newItemPosition = up ? selectedItem.nextElementSibling : selectedItem;
+ gListbox.insertBefore(swapPartner, newItemPosition);
+ gListbox.ensureElementIsVisible(selectedItem);
+ disableMoveButtons();
+}
+
+function disableMoveButtons() {
+ var selectedIndex = gListbox.selectedIndex;
+ gMoveUpButton.disabled = selectedIndex == 0;
+ gMoveDownButton.disabled = selectedIndex == gListbox.getRowCount() - 1;
+}
+
+function ShowSampleData(data) {
+ var fields = data.split("\n");
+ for (var i = 0; i < gListbox.getRowCount(); i++) {
+ gListbox
+ .getItemAtIndex(i)
+ .lastElementChild.setAttribute(
+ "value",
+ i < fields.length ? fields[i] : ""
+ );
+ }
+}
+
+function FetchSampleData(num) {
+ if (!top.addInterface) {
+ return false;
+ }
+
+ var data = top.addInterface.GetData("sampleData-" + num);
+ if (!(data instanceof Ci.nsISupportsString)) {
+ return false;
+ }
+ ShowSampleData(data.data);
+ return true;
+}
+
+/**
+ * Handle the command event of #next and #previous buttons.
+ *
+ * @param {Event} event - The command event of #next or #previous button.
+ */
+function nextPreviousOnCommand(event) {
+ browseDataPreview(event.target.id == "next" ? 1 : -1);
+}
+
+/**
+ * Browse the import data preview by moving to the next or previous record.
+ * Also handle the disabled status of the #next and #previous buttons at the
+ * first or last record.
+ *
+ * @param {integer} step - How many records to move forwards or backwards.
+ * Used by the #next and #previous buttons with step of 1 or -1.
+ */
+function browseDataPreview(step) {
+ gRecordNum += step;
+ if (FetchSampleData(gRecordNum - 1)) {
+ document.l10n.setAttributes(
+ document.getElementById("labelRecordNumber"),
+ "import-ab-csv-preview-record-number",
+ {
+ recordNumber: gRecordNum,
+ }
+ );
+ }
+
+ gPreviousButton.disabled = gRecordNum == 1;
+ gNextButton.disabled =
+ addInterface.GetData("sampleData-" + gRecordNum) == null;
+}
+
+function FieldImportOKButton() {
+ var max = gListbox.getRowCount();
+ // Ensure field map is the right size
+ top.fieldMap.SetFieldMapSize(max);
+
+ for (let i = 0; i < max; i++) {
+ let fIndex = gListbox.getItemAtIndex(i).getAttribute("field-index");
+ let on = gListbox
+ .getItemAtIndex(i)
+ .querySelector("checkbox")
+ .getAttribute("checked");
+ top.fieldMap.SetFieldMap(i, fIndex);
+ top.fieldMap.SetFieldActive(i, on == "true");
+ }
+
+ top.fieldMap.skipFirstRecord = gSkipFirstRecordButton.checked;
+
+ top.dialogResult.ok = true;
+}
diff --git a/comm/mailnews/import/content/fieldMapImport.xhtml b/comm/mailnews/import/content/fieldMapImport.xhtml
new file mode 100644
index 0000000000..8278436321
--- /dev/null
+++ b/comm/mailnews/import/content/fieldMapImport.xhtml
@@ -0,0 +1,104 @@
+<?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"?>
+<?xml-stylesheet href="chrome://messenger/skin/shared/fieldMapImport.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/colors.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/themeableDialog.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger/locale/fieldMapImport.dtd">
+
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ lightweightthemes="true"
+ width="640"
+ height="480"
+ scrolling="false"
+>
+ <head>
+ <link rel="localization" href="messenger/addressbook/fieldMapImport.ftl" />
+ <title data-l10n-id="import-ab-csv-dialog-title"></title>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/fieldMapImport.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/dialogShadowDom.js"
+ ></script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <dialog
+ buttons="accept,cancel"
+ data-l10n-id="import-ab-csv-dialog"
+ data-l10n-attrs="buttonlabelaccept,buttonaccesskeyaccept"
+ >
+ <hbox align="center">
+ <label id="labelRecordNumber" />
+ <spacer flex="1" />
+ <button
+ id="previous"
+ oncommand="nextPreviousOnCommand(event);"
+ label="&fieldMapImport.previous.label;"
+ accesskey="&fieldMapImport.previous.accesskey;"
+ />
+ <button
+ id="next"
+ oncommand="nextPreviousOnCommand(event);"
+ label="&fieldMapImport.next.label;"
+ accesskey="&fieldMapImport.next.accesskey;"
+ />
+ </hbox>
+
+ <hbox align="center">
+ <checkbox
+ id="skipFirstRecord"
+ label="&fieldMapImport.skipFirstRecord.label;"
+ accesskey="&fieldMapImport.skipFirstRecord.accessKey;"
+ />
+ </hbox>
+
+ <separator class="thin" />
+ <label control="fieldList">&fieldMapImport.text;</label>
+ <separator class="thin" />
+
+ <!-- field list -->
+ <hbox flex="1">
+ <richlistbox id="fieldList" flex="1" onselect="disableMoveButtons();">
+ <treecols>
+ <treecol id="checkedHeader" />
+ <treecol
+ id="fieldNameHeader"
+ label="&fieldMapImport.fieldListTitle;"
+ />
+ <treecol id="sampleDataHeader" label="&fieldMapImport.dataTitle;" />
+ </treecols>
+ </richlistbox>
+
+ <vbox>
+ <spacer flex="1" />
+ <button
+ id="upButton"
+ class="up"
+ label="&fieldMapImport.up.label;"
+ accesskey="&fieldMapImport.up.accesskey;"
+ oncommand="moveItem(true);"
+ />
+ <button
+ id="downButton"
+ class="down"
+ label="&fieldMapImport.down.label;"
+ accesskey="&fieldMapImport.down.accesskey;"
+ oncommand="moveItem(false);"
+ />
+ <spacer flex="1" />
+ </vbox>
+ </hbox>
+ </dialog>
+ </html:body>
+</html>
diff --git a/comm/mailnews/import/content/importDialog.js b/comm/mailnews/import/content/importDialog.js
new file mode 100644
index 0000000000..cf029d4989
--- /dev/null
+++ b/comm/mailnews/import/content/importDialog.js
@@ -0,0 +1,1184 @@
+/* -*- Mode: Javascript; 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/. */
+
+"use strict";
+
+/* import-globals-from ../../extensions/newsblog/feed-subscriptions.js */
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { MailUtils } = ChromeUtils.import("resource:///modules/MailUtils.jsm");
+
+var gImportType = null;
+var gImportMsgsBundle;
+var gFeedsBundle;
+var gImportService = null;
+var gSuccessStr = null;
+var gErrorStr = null;
+var gInputStr = null;
+var gProgressInfo = null;
+var gSelectedModuleName = null;
+var gAddInterface = null;
+var gNewFeedAcctCreated = false;
+
+window.addEventListener("DOMContentLoaded", OnLoadImportDialog);
+window.addEventListener("unload", OnUnloadImportDialog);
+
+function OnLoadImportDialog() {
+ gImportMsgsBundle = document.getElementById("bundle_importMsgs");
+ gFeedsBundle = document.getElementById("bundle_feeds");
+ gImportService = Cc["@mozilla.org/import/import-service;1"].getService(
+ Ci.nsIImportService
+ );
+
+ gProgressInfo = {};
+ gProgressInfo.progressWindow = null;
+ gProgressInfo.importInterface = null;
+ gProgressInfo.mainWindow = window;
+ gProgressInfo.intervalState = 0;
+ gProgressInfo.importSuccess = false;
+ gProgressInfo.importType = null;
+ gProgressInfo.localFolderExists = false;
+
+ gSuccessStr = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ gErrorStr = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ gInputStr = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+
+ // look in arguments[0] for parameters
+ if (
+ "arguments" in window &&
+ window.arguments.length >= 1 &&
+ "importType" in window.arguments[0] &&
+ window.arguments[0].importType
+ ) {
+ // keep parameters in global for later
+ gImportType = window.arguments[0].importType;
+ gProgressInfo.importType = gImportType;
+ } else {
+ gImportType = "all";
+ gProgressInfo.importType = "all";
+ }
+
+ SetUpImportType();
+
+ // on startup, set the focus to the control element
+ // for accessibility reasons.
+ // if we used the wizardOverlay, we would get this for free.
+ // see bug #101874
+ document.getElementById("importFields").focus();
+}
+
+/**
+ * After importing, need to restart so that imported address books and mail
+ * accounts can show up.
+ */
+function OnUnloadImportDialog() {
+ let nextButton = document.getElementById("forward");
+ if (
+ gImportType == "settings" &&
+ !gErrorStr.data &&
+ nextButton.label == nextButton.getAttribute("finishedval")
+ ) {
+ MailUtils.restartApplication();
+ }
+}
+
+function SetUpImportType() {
+ // set dialog title
+ document.getElementById("importFields").value = gImportType;
+
+ // Mac migration not working right now, so disable it.
+ if (Services.appinfo.OS == "Darwin") {
+ document.getElementById("allRadio").setAttribute("disabled", "true");
+ if (gImportType == "all") {
+ document.getElementById("importFields").value = "addressbook";
+ }
+ }
+
+ let fileLabel = document.getElementById("fileLabel");
+ let accountLabel = document.getElementById("accountLabel");
+ if (gImportType == "feeds") {
+ accountLabel.hidden = false;
+ fileLabel.hidden = true;
+ ListFeedAccounts();
+ } else {
+ accountLabel.hidden = true;
+ fileLabel.hidden = false;
+ ListModules();
+ }
+}
+
+function SetDivText(id, text) {
+ var div = document.getElementById(id);
+
+ if (div) {
+ if (!div.hasChildNodes()) {
+ var textNode = document.createTextNode(text);
+ div.appendChild(textNode);
+ } else if (div.childNodes.length == 1) {
+ div.firstChild.nodeValue = text;
+ }
+ }
+}
+
+function CheckIfLocalFolderExists() {
+ try {
+ if (MailServices.accounts.localFoldersServer) {
+ gProgressInfo.localFolderExists = true;
+ }
+ } catch (ex) {
+ gProgressInfo.localFolderExists = false;
+ }
+}
+
+function showWizardBox(index) {
+ let stateBox = document.getElementById("stateBox");
+ for (let i = 0; i < stateBox.children.length; i++) {
+ stateBox.children[i].hidden = i != index;
+ }
+}
+
+function getWizardBoxIndex() {
+ let selectedIndex = 0;
+ for (let element of document.getElementById("stateBox").children) {
+ if (!element.hidden) {
+ return selectedIndex;
+ }
+ selectedIndex++;
+ }
+ return selectedIndex - 1;
+}
+
+async function ImportDialogOKButton() {
+ var listbox = document.getElementById("moduleList");
+ var header = document.getElementById("header");
+ var progressMeterEl = document.getElementById("progressMeter");
+ progressMeterEl.value = 0;
+ var progressStatusEl = document.getElementById("progressStatus");
+ var progressTitleEl = document.getElementById("progressTitle");
+
+ // better not mess around with navigation at this point
+ var nextButton = document.getElementById("forward");
+ nextButton.setAttribute("disabled", "true");
+ var backButton = document.getElementById("back");
+ backButton.setAttribute("disabled", "true");
+
+ if (listbox && listbox.selectedCount == 1) {
+ let module = "";
+ let name = "";
+ gImportType = document.getElementById("importFields").value;
+ let index = listbox.selectedItem.getAttribute("list-index");
+ if (index == -1) {
+ return false;
+ }
+ if (gImportType == "feeds") {
+ module = "Feeds";
+ } else {
+ module = gImportService.GetModule(gImportType, index);
+ name = gImportService.GetModuleName(gImportType, index);
+ }
+ gSelectedModuleName = name;
+ if (module) {
+ // Fix for Bug 57839 & 85219
+ // We use localFoldersServer(in nsIMsgAccountManager) to check if Local Folder exists.
+ // We need to check localFoldersServer before importing "mail", "settings", or "filters".
+ // Reason: We will create an account with an incoming server of type "none" after
+ // importing "mail", so the localFoldersServer is valid even though the Local Folder
+ // is not created.
+ if (
+ gImportType == "mail" ||
+ gImportType == "settings" ||
+ gImportType == "filters"
+ ) {
+ CheckIfLocalFolderExists();
+ }
+
+ let meterText = "";
+ let error = {};
+ switch (gImportType) {
+ case "mail":
+ if (await ImportMail(module, gSuccessStr, gErrorStr)) {
+ // We think it was a success, either, we need to
+ // wait for the import to finish
+ // or we are done!
+ if (gProgressInfo.importInterface == null) {
+ ShowImportResults(true, "Mail");
+ return true;
+ }
+
+ meterText = gImportMsgsBundle.getFormattedString(
+ "MailProgressMeterText",
+ [name]
+ );
+ header.setAttribute("description", meterText);
+
+ progressStatusEl.setAttribute("label", "");
+ progressTitleEl.setAttribute("label", meterText);
+
+ showWizardBox(2);
+ gProgressInfo.progressWindow = window;
+ gProgressInfo.intervalState = setInterval(
+ ContinueImportCallback,
+ 100
+ );
+ return true;
+ }
+
+ ShowImportResults(false, "Mail");
+ // Re-enable the next button, as we are here, because the user cancelled the picking.
+ // Enable next, so they can try again.
+ nextButton.removeAttribute("disabled");
+ // Also enable back button so that users can pick other import options.
+ backButton.removeAttribute("disabled");
+ return false;
+
+ case "feeds":
+ if (await ImportFeeds()) {
+ // Successful completion of pre processing and launch of async import.
+ meterText = document.getElementById("description").textContent;
+ header.setAttribute("description", meterText);
+
+ progressStatusEl.setAttribute("label", "");
+ progressTitleEl.setAttribute("label", meterText);
+ progressMeterEl.removeAttribute("value");
+
+ showWizardBox(2);
+ return true;
+ }
+
+ // Re-enable the next button, as we are here, because the user cancelled the picking.
+ // Enable next, so they can try again.
+ nextButton.removeAttribute("disabled");
+ // Also enable back button so that users can pick other import options.
+ backButton.removeAttribute("disabled");
+ return false;
+
+ case "addressbook":
+ if (await ImportAddress(module, gSuccessStr, gErrorStr)) {
+ // We think it was a success, either, we need to
+ // wait for the import to finish
+ // or we are done!
+ if (gProgressInfo.importInterface == null) {
+ ShowImportResults(true, "Address");
+ return true;
+ }
+
+ meterText = gImportMsgsBundle.getFormattedString(
+ "AddrProgressMeterText",
+ [name]
+ );
+ header.setAttribute("description", meterText);
+
+ progressStatusEl.setAttribute("label", "");
+ progressTitleEl.setAttribute("label", meterText);
+
+ showWizardBox(2);
+ gProgressInfo.progressWindow = window;
+ gProgressInfo.intervalState = setInterval(
+ ContinueImportCallback,
+ 100
+ );
+
+ return true;
+ }
+
+ ShowImportResults(false, "Address");
+ // Re-enable the next button, as we are here, because the user cancelled the picking.
+ // Enable next, so they can try again.
+ nextButton.removeAttribute("disabled");
+ // Also enable back button so that users can pick other import options.
+ backButton.removeAttribute("disabled");
+ return false;
+
+ case "settings":
+ error.value = null;
+ let newAccount = {};
+ if (!(await ImportSettings(module, newAccount, error))) {
+ if (error.value) {
+ ShowImportResultsRaw(
+ gImportMsgsBundle.getString("ImportSettingsFailed"),
+ null,
+ false
+ );
+ }
+ // Re-enable the next button, as we are here, because the user cancelled the picking.
+ // Enable next, so they can try again.
+ nextButton.removeAttribute("disabled");
+ // Also enable back button so that users can pick other import options.
+ backButton.removeAttribute("disabled");
+ return false;
+ }
+ ShowImportResultsRaw(
+ gImportMsgsBundle.getFormattedString("ImportSettingsSuccess", [
+ name,
+ ]),
+ null,
+ true
+ );
+ break;
+
+ case "filters":
+ error.value = null;
+ if (!ImportFilters(module, error)) {
+ if (error.value) {
+ ShowImportResultsRaw(
+ gImportMsgsBundle.getFormattedString("ImportFiltersFailed", [
+ name,
+ ]),
+ error.value,
+ false
+ );
+ }
+ // Re-enable the next button, as we are here, because the user cancelled the picking.
+ // Enable next, so they can try again.
+ nextButton.removeAttribute("disabled");
+ // Also enable back button so that users can pick other import options.
+ backButton.removeAttribute("disabled");
+ return false;
+ }
+
+ if (error.value) {
+ ShowImportResultsRaw(
+ gImportMsgsBundle.getFormattedString("ImportFiltersPartial", [
+ name,
+ ]),
+ error.value,
+ true
+ );
+ } else {
+ ShowImportResultsRaw(
+ gImportMsgsBundle.getFormattedString("ImportFiltersSuccess", [
+ name,
+ ]),
+ null,
+ true
+ );
+ }
+
+ break;
+ }
+ }
+ }
+
+ return true;
+}
+
+function SetStatusText(val) {
+ var progressStatus = document.getElementById("progressStatus");
+ progressStatus.setAttribute("label", val);
+}
+
+function SetProgress(val) {
+ var progressMeter = document.getElementById("progressMeter");
+ progressMeter.value = val;
+}
+
+function ContinueImportCallback() {
+ gProgressInfo.mainWindow.ContinueImport(gProgressInfo);
+}
+
+function ImportSelectionChanged() {
+ let listbox = document.getElementById("moduleList");
+ let acctNameBox = document.getElementById("acctName-box");
+ if (listbox && listbox.selectedCount == 1) {
+ let index = listbox.selectedItem.getAttribute("list-index");
+ if (index == -1) {
+ return;
+ }
+ acctNameBox.setAttribute("style", "visibility: hidden;");
+ if (gImportType == "feeds") {
+ if (index == 0) {
+ SetDivText(
+ "description",
+ gFeedsBundle.getString("ImportFeedsNewAccount")
+ );
+ let defaultName = gFeedsBundle.getString("feeds-accountname");
+ document.getElementById("acctName").value = defaultName;
+ acctNameBox.removeAttribute("style");
+ } else {
+ SetDivText(
+ "description",
+ gFeedsBundle.getString("ImportFeedsExistingAccount")
+ );
+ }
+ } else {
+ SetDivText(
+ "description",
+ gImportService.GetModuleDescription(gImportType, index)
+ );
+ }
+ }
+}
+
+function CompareImportModuleName(a, b) {
+ if (a.name > b.name) {
+ return 1;
+ }
+ if (a.name < b.name) {
+ return -1;
+ }
+ return 0;
+}
+
+function ListModules() {
+ if (gImportService == null) {
+ return;
+ }
+
+ var body = document.getElementById("moduleList");
+ while (body.hasChildNodes()) {
+ body.lastChild.remove();
+ }
+
+ var count = gImportService.GetModuleCount(gImportType);
+ var i;
+
+ var moduleArray = new Array(count);
+ for (i = 0; i < count; i++) {
+ moduleArray[i] = {
+ name: gImportService.GetModuleName(gImportType, i),
+ index: i,
+ };
+ }
+
+ // sort the array of modules by name, so that they'll show up in the right order
+ moduleArray.sort(CompareImportModuleName);
+
+ for (i = 0; i < count; i++) {
+ AddModuleToList(moduleArray[i].name, moduleArray[i].index);
+ }
+}
+
+function AddModuleToList(moduleName, index) {
+ var body = document.getElementById("moduleList");
+
+ let item = document.createXULElement("richlistitem");
+ let label = document.createXULElement("label");
+ label.setAttribute("value", moduleName);
+ item.appendChild(label);
+ item.setAttribute("list-index", index);
+ body.appendChild(item);
+}
+
+function ListFeedAccounts() {
+ let body = document.getElementById("moduleList");
+ while (body.hasChildNodes()) {
+ body.lastChild.remove();
+ }
+
+ // Add item to allow for new account creation.
+ let item = document.createXULElement("richlistitem");
+ let label = document.createXULElement("label");
+ label.setAttribute(
+ "value",
+ gFeedsBundle.getString("ImportFeedsCreateNewListItem")
+ );
+ item.appendChild(label);
+ item.setAttribute("list-index", 0);
+ body.appendChild(item);
+
+ let index = 0;
+ let feedRootFolders = FeedUtils.getAllRssServerRootFolders();
+
+ feedRootFolders.forEach(function (rootFolder) {
+ item = document.createXULElement("richlistitem");
+ let label = document.createXULElement("label");
+ label.setAttribute("value", rootFolder.prettyName);
+ item.appendChild(label);
+ item.setAttribute("list-index", ++index);
+ item.server = rootFolder.server;
+ body.appendChild(item);
+ }, this);
+
+ if (index) {
+ // If there is an existing feed account, select the first one.
+ body.selectedIndex = 1;
+ }
+}
+
+function ContinueImport(info) {
+ var isMail = info.importType == "mail";
+ var clear = true;
+ var pcnt;
+
+ if (info.importInterface) {
+ if (!info.importInterface.ContinueImport()) {
+ info.importSuccess = false;
+ clearInterval(info.intervalState);
+ if (info.progressWindow != null) {
+ showWizardBox(3);
+ info.progressWindow = null;
+ }
+
+ ShowImportResults(false, isMail ? "Mail" : "Address");
+ } else if ((pcnt = info.importInterface.GetProgress()) < 100) {
+ clear = false;
+ if (info.progressWindow != null) {
+ if (pcnt < 5) {
+ pcnt = 5;
+ }
+ SetProgress(pcnt);
+ if (isMail) {
+ let mailName = info.importInterface.GetData("currentMailbox");
+ if (mailName) {
+ mailName = mailName.QueryInterface(Ci.nsISupportsString);
+ if (mailName) {
+ SetStatusText(mailName.data);
+ }
+ }
+ }
+ }
+ } else {
+ dump("*** WARNING! sometimes this shows results too early. \n");
+ dump(" something screwy here. this used to work fine.\n");
+ clearInterval(info.intervalState);
+ info.importSuccess = true;
+ if (info.progressWindow) {
+ showWizardBox(3);
+ info.progressWindow = null;
+ }
+
+ ShowImportResults(true, isMail ? "Mail" : "Address");
+ }
+ }
+ if (clear) {
+ info.intervalState = null;
+ info.importInterface = null;
+ }
+}
+
+function ShowResults(doesWantProgress, result) {
+ if (result) {
+ if (doesWantProgress) {
+ let header = document.getElementById("header");
+ let progressStatusEl = document.getElementById("progressStatus");
+ let progressTitleEl = document.getElementById("progressTitle");
+
+ let meterText = gImportMsgsBundle.getFormattedString(
+ "AddrProgressMeterText",
+ [name]
+ );
+ header.setAttribute("description", meterText);
+
+ progressStatusEl.setAttribute("label", "");
+ progressTitleEl.setAttribute("label", meterText);
+
+ showWizardBox(2);
+ gProgressInfo.progressWindow = window;
+ gProgressInfo.intervalState = setInterval(ContinueImportCallback, 100);
+ } else {
+ ShowImportResults(true, "Address");
+ }
+ } else {
+ ShowImportResults(false, "Address");
+ }
+
+ return true;
+}
+
+function ShowImportResults(good, module) {
+ // String keys for ImportSettingsSuccess, ImportSettingsFailed,
+ // ImportMailSuccess, ImportMailFailed, ImportAddressSuccess,
+ // ImportAddressFailed, ImportFiltersSuccess, and ImportFiltersFailed.
+ var modSuccess = "Import" + module + "Success";
+ var modFailed = "Import" + module + "Failed";
+
+ // The callers seem to set 'good' to true even if there's something
+ // in the error log. So we should only make it a success case if
+ // error log/str is empty.
+ var results, title;
+ var moduleName = gSelectedModuleName ? gSelectedModuleName : "";
+ if (good && !gErrorStr.data) {
+ title = gImportMsgsBundle.getFormattedString(modSuccess, [moduleName]);
+ results = gSuccessStr.data;
+ } else if (gErrorStr.data) {
+ title = gImportMsgsBundle.getFormattedString(modFailed, [moduleName]);
+ results = gErrorStr.data;
+ }
+
+ if (results && title) {
+ ShowImportResultsRaw(title, results, good);
+ }
+}
+
+function ShowImportResultsRaw(title, results, good) {
+ SetDivText("status", title);
+ var header = document.getElementById("header");
+ header.setAttribute("description", title);
+ dump("*** results = " + results + "\n");
+ attachStrings("results", results);
+ showWizardBox(3);
+ var nextButton = document.getElementById("forward");
+ nextButton.label = nextButton.getAttribute("finishedval");
+ nextButton.removeAttribute("disabled");
+ var cancelButton = document.getElementById("cancel");
+ cancelButton.setAttribute("disabled", "true");
+ var backButton = document.getElementById("back");
+ backButton.setAttribute("disabled", "true");
+
+ // If the Local Folder doesn't exist, create it after successfully
+ // importing "mail" and "settings"
+ var checkLocalFolder =
+ gProgressInfo.importType == "mail" ||
+ gProgressInfo.importType == "settings";
+ if (good && checkLocalFolder && !gProgressInfo.localFolderExists) {
+ MailServices.accounts.createLocalMailAccount();
+ }
+}
+
+function attachStrings(aNode, aString) {
+ var attachNode = document.getElementById(aNode);
+ if (!aString) {
+ attachNode.parentNode.setAttribute("hidden", "true");
+ return;
+ }
+ var strings = aString.split("\n");
+ for (let string of strings) {
+ if (string) {
+ let currNode = document.createTextNode(string);
+ attachNode.appendChild(currNode);
+ let br = document.createElementNS("http://www.w3.org/1999/xhtml", "br");
+ attachNode.appendChild(br);
+ }
+ }
+}
+
+/**
+ * Show the file picker.
+ *
+ * @returns {Promise} the selected file, or null
+ */
+function promptForFile(fp) {
+ return new Promise(resolve => {
+ fp.open(rv => {
+ if (rv != Ci.nsIFilePicker.returnOK || !fp.file) {
+ resolve(null);
+ return;
+ }
+ resolve(fp.file);
+ });
+ });
+}
+
+/*
+ Import Settings from a specific module, returns false if it failed
+ and true if successful. A "local mail" account is returned in newAccount.
+ This is only useful in upgrading - import the settings first, then
+ import mail into the account returned from ImportSettings, then
+ import address books.
+ An error string is returned as error.value
+*/
+async function ImportSettings(module, newAccount, error) {
+ var setIntf = module.GetImportInterface("settings");
+ if (!(setIntf instanceof Ci.nsIImportSettings)) {
+ error.value = gImportMsgsBundle.getString("ImportSettingsBadModule");
+ return false;
+ }
+
+ // determine if we can auto find the settings or if we need to ask the user
+ var location = {};
+ var description = {};
+ var result = setIntf.AutoLocate(description, location);
+ if (!result) {
+ // In this case, we couldn't find the settings
+ if (location.value != null) {
+ // Settings were not found, however, they are specified
+ // in a file, so ask the user for the settings file.
+ let filePicker = Cc["@mozilla.org/filepicker;1"].createInstance();
+ if (filePicker instanceof Ci.nsIFilePicker) {
+ let file = null;
+ try {
+ filePicker.init(
+ window,
+ gImportMsgsBundle.getString("ImportSelectSettings"),
+ filePicker.modeOpen
+ );
+ filePicker.appendFilters(filePicker.filterAll);
+
+ file = await promptForFile(filePicker);
+ } catch (ex) {
+ console.error(ex);
+ error.value = null;
+ return false;
+ }
+ if (file != null) {
+ setIntf.SetLocation(file);
+ } else {
+ error.value = null;
+ return false;
+ }
+ } else {
+ error.value = gImportMsgsBundle.getString("ImportSettingsNotFound");
+ return false;
+ }
+ } else {
+ error.value = gImportMsgsBundle.getString("ImportSettingsNotFound");
+ return false;
+ }
+ }
+
+ // interesting, we need to return the account that new
+ // mail should be imported into?
+ // that's really only useful for "Upgrade"
+ result = setIntf.Import(newAccount);
+ if (!result) {
+ error.value = gImportMsgsBundle.getString("ImportSettingsFailed");
+ }
+ return result;
+}
+
+async function ImportMail(module, success, error) {
+ if (gProgressInfo.importInterface || gProgressInfo.intervalState) {
+ error.data = gImportMsgsBundle.getString("ImportAlreadyInProgress");
+ return false;
+ }
+
+ gProgressInfo.importSuccess = false;
+
+ var mailInterface = module.GetImportInterface("mail");
+ if (!(mailInterface instanceof Ci.nsIImportGeneric)) {
+ error.data = gImportMsgsBundle.getString("ImportMailBadModule");
+ return false;
+ }
+
+ var loc = mailInterface.GetData("mailLocation");
+
+ if (loc == null) {
+ // No location found, check to see if we can ask the user.
+ if (mailInterface.GetStatus("canUserSetLocation") != 0) {
+ let filePicker = Cc["@mozilla.org/filepicker;1"].createInstance();
+ if (filePicker instanceof Ci.nsIFilePicker) {
+ try {
+ filePicker.init(
+ window,
+ gImportMsgsBundle.getString("ImportSelectMailDir"),
+ filePicker.modeGetFolder
+ );
+ filePicker.appendFilters(filePicker.filterAll);
+ let file = await promptForFile(filePicker);
+ if (!file) {
+ return false;
+ }
+ mailInterface.SetData("mailLocation", file);
+ } catch (ex) {
+ console.error(ex);
+ // don't show an error when we return!
+ return false;
+ }
+ } else {
+ error.data = gImportMsgsBundle.getString("ImportMailNotFound");
+ return false;
+ }
+ } else {
+ error.data = gImportMsgsBundle.getString("ImportMailNotFound");
+ return false;
+ }
+ }
+
+ if (mailInterface.WantsProgress()) {
+ if (mailInterface.BeginImport(success, error)) {
+ gProgressInfo.importInterface = mailInterface;
+ // intervalState = setInterval(ContinueImport, 100);
+ return true;
+ }
+ return false;
+ }
+ return mailInterface.BeginImport(success, error);
+}
+
+// The address import! A little more complicated than the mail import
+// due to field maps...
+async function ImportAddress(module, success, error) {
+ if (gProgressInfo.importInterface || gProgressInfo.intervalState) {
+ error.data = gImportMsgsBundle.getString("ImportAlreadyInProgress");
+ return false;
+ }
+
+ gProgressInfo.importSuccess = false;
+
+ gAddInterface = module.GetImportInterface("addressbook");
+ if (!(gAddInterface instanceof Ci.nsIImportGeneric)) {
+ error.data = gImportMsgsBundle.getString("ImportAddressBadModule");
+ return false;
+ }
+
+ var loc = gAddInterface.GetStatus("autoFind");
+ if (loc == 0) {
+ loc = gAddInterface.GetData("addressLocation");
+ if (loc instanceof Ci.nsIFile && !loc.exists) {
+ loc = null;
+ }
+ }
+
+ if (loc == null) {
+ // Couldn't find the address book, see if we can
+ // as the user for the location or not?
+ if (gAddInterface.GetStatus("canUserSetLocation") == 0) {
+ // an autofind address book that could not be found!
+ error.data = gImportMsgsBundle.getString("ImportAddressNotFound");
+ return false;
+ }
+
+ let filePicker = Cc["@mozilla.org/filepicker;1"].createInstance();
+ if (!(filePicker instanceof Ci.nsIFilePicker)) {
+ error.data = gImportMsgsBundle.getString("ImportAddressNotFound");
+ return false;
+ }
+
+ // The address book location was not found.
+ // Determine if we need to ask for a directory
+ // or a single file.
+ let file = null;
+ let fileIsDirectory = false;
+ if (gAddInterface.GetStatus("supportsMultiple") != 0) {
+ // ask for dir
+ try {
+ filePicker.init(
+ window,
+ gImportMsgsBundle.getString("ImportSelectAddrDir"),
+ filePicker.modeGetFolder
+ );
+ filePicker.appendFilters(filePicker.filterAll);
+ file = await promptForFile(filePicker);
+ if (file && file.path) {
+ fileIsDirectory = true;
+ }
+ } catch (ex) {
+ console.error(ex);
+ file = null;
+ }
+ } else {
+ // ask for file
+ try {
+ filePicker.init(
+ window,
+ gImportMsgsBundle.getString("ImportSelectAddrFile"),
+ filePicker.modeOpen
+ );
+ let addressbookBundle = document.getElementById("bundle_addressbook");
+ if (
+ gSelectedModuleName ==
+ document
+ .getElementById("bundle_vcardImportMsgs")
+ .getString("vCardImportName")
+ ) {
+ filePicker.appendFilter(
+ addressbookBundle.getString("VCFFiles"),
+ "*.vcf"
+ );
+ } else if (
+ gSelectedModuleName ==
+ document
+ .getElementById("bundle_morkImportMsgs")
+ .getString("morkImportName")
+ ) {
+ filePicker.appendFilter(
+ document
+ .getElementById("bundle_morkImportMsgs")
+ .getString("MABFiles"),
+ "*.mab"
+ );
+ } else {
+ filePicker.appendFilter(
+ addressbookBundle.getString("LDIFFiles"),
+ "*.ldi; *.ldif"
+ );
+ filePicker.appendFilter(
+ addressbookBundle.getString("CSVFiles"),
+ "*.csv"
+ );
+ filePicker.appendFilter(
+ addressbookBundle.getString("TABFiles"),
+ "*.tab; *.txt"
+ );
+ filePicker.appendFilter(
+ addressbookBundle.getString("SupportedABFiles"),
+ "*.csv; *.ldi; *.ldif; *.tab; *.txt"
+ );
+ filePicker.appendFilters(filePicker.filterAll);
+ // Use "Supported Address Book Files" as default file filter.
+ filePicker.filterIndex = 3;
+ }
+
+ file = await promptForFile(filePicker);
+ } catch (ex) {
+ console.error(ex);
+ file = null;
+ }
+ }
+
+ if (!file) {
+ return false;
+ }
+
+ if (!fileIsDirectory && file.fileSize == 0) {
+ let errorText = gImportMsgsBundle.getFormattedString(
+ "ImportEmptyAddressBook",
+ [file.leafName]
+ );
+
+ Services.prompt.alert(window, document.title, errorText);
+ return false;
+ }
+ gAddInterface.SetData("addressLocation", file);
+ }
+
+ var map = gAddInterface.GetData("fieldMap");
+ if (map instanceof Ci.nsIImportFieldMap) {
+ let result = {};
+ result.ok = false;
+ window.openDialog(
+ "chrome://messenger/content/fieldMapImport.xhtml",
+ "",
+ "chrome,modal,titlebar",
+ {
+ fieldMap: map,
+ addInterface: gAddInterface,
+ result,
+ }
+ );
+
+ if (!result.ok) {
+ return false;
+ }
+ }
+
+ if (gAddInterface.WantsProgress()) {
+ if (gAddInterface.BeginImport(success, error)) {
+ gProgressInfo.importInterface = gAddInterface;
+ // intervalState = setInterval(ContinueImport, 100);
+ return true;
+ }
+ return false;
+ }
+
+ return gAddInterface.BeginImport(success, error);
+}
+
+/*
+ Import filters from a specific module.
+ Returns false if it failed and true if it succeeded.
+ An error string is returned as error.value.
+*/
+function ImportFilters(module, error) {
+ if (gProgressInfo.importInterface || gProgressInfo.intervalState) {
+ error.data = gImportMsgsBundle.getString("ImportAlreadyInProgress");
+ return false;
+ }
+
+ gProgressInfo.importSuccess = false;
+
+ var filtersInterface = module.GetImportInterface("filters");
+ if (!(filtersInterface instanceof Ci.nsIImportFilters)) {
+ error.data = gImportMsgsBundle.getString("ImportFiltersBadModule");
+ return false;
+ }
+
+ return filtersInterface.Import(error);
+}
+
+/*
+ Import feeds.
+*/
+async function ImportFeeds() {
+ // Get file and file url to open from filepicker.
+ let [openFile, openFileUrl] = await FeedSubscriptions.opmlPickOpenFile();
+
+ let acctName;
+ let acctNewExist = gFeedsBundle.getString("ImportFeedsExisting");
+ let fileName = openFile.path;
+ let server = document.getElementById("moduleList").selectedItem.server;
+ gNewFeedAcctCreated = false;
+
+ if (!server) {
+ // Create a new Feeds account.
+ acctName = document.getElementById("acctName").value;
+ server = FeedUtils.createRssAccount(acctName).incomingServer;
+ acctNewExist = gFeedsBundle.getString("ImportFeedsNew");
+ gNewFeedAcctCreated = true;
+ }
+
+ acctName = server.rootFolder.prettyName;
+
+ let callback = function (aStatusReport, aLastFolder, aFeedWin) {
+ let message = gFeedsBundle.getFormattedString("ImportFeedsDone", [
+ fileName,
+ acctNewExist,
+ acctName,
+ ]);
+ ShowImportResultsRaw(message + " " + aStatusReport, null, true);
+ document.getElementById("back").removeAttribute("disabled");
+
+ let subscriptionsWindow = Services.wm.getMostRecentWindow(
+ "Mail:News-BlogSubscriptions"
+ );
+ if (subscriptionsWindow) {
+ let feedWin = subscriptionsWindow.FeedSubscriptions;
+ if (aLastFolder) {
+ feedWin.FolderListener.folderAdded(aLastFolder);
+ }
+
+ feedWin.mActionMode = null;
+ feedWin.updateButtons(feedWin.mView.currentItem);
+ feedWin.clearStatusInfo();
+ feedWin.updateStatusItem("statusText", aStatusReport);
+ }
+ };
+
+ if (
+ !(await FeedSubscriptions.importOPMLFile(
+ openFile,
+ openFileUrl,
+ server,
+ callback
+ ))
+ ) {
+ return false;
+ }
+
+ let subscriptionsWindow = Services.wm.getMostRecentWindow(
+ "Mail:News-BlogSubscriptions"
+ );
+ if (subscriptionsWindow) {
+ let feedWin = subscriptionsWindow.FeedSubscriptions;
+ feedWin.mActionMode = feedWin.kImportingOPML;
+ feedWin.updateButtons(null);
+ let statusReport = gFeedsBundle.getString("subscribe-loading");
+ feedWin.updateStatusItem("statusText", statusReport);
+ feedWin.updateStatusItem("progressMeter", "?");
+ }
+
+ return true;
+}
+
+function SwitchType(newType) {
+ if (gImportType == newType) {
+ return;
+ }
+
+ gImportType = newType;
+ gProgressInfo.importType = newType;
+
+ SetUpImportType();
+
+ SetDivText("description", "");
+}
+
+function next() {
+ switch (getWizardBoxIndex()) {
+ case 0:
+ let backButton = document.getElementById("back");
+ backButton.removeAttribute("disabled");
+ let radioGroup = document.getElementById("importFields");
+
+ if (radioGroup.value == "all") {
+ let args = { closeMigration: true };
+ let SEAMONKEY_ID = "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}";
+ if (Services.appinfo.ID == SEAMONKEY_ID) {
+ window.openDialog(
+ "chrome://communicator/content/migration/migration.xhtml",
+ "",
+ "chrome,dialog,modal,centerscreen"
+ );
+ } else {
+ // Running as Thunderbird or its clone.
+ window.openDialog(
+ "chrome://messenger/content/migration/migration.xhtml",
+ "",
+ "chrome,dialog,modal,centerscreen",
+ null,
+ null,
+ null,
+ args
+ );
+ }
+ if (args.closeMigration) {
+ close();
+ }
+ } else {
+ SwitchType(radioGroup.value);
+ showWizardBox(1);
+ let moduleBox = document.getElementById("moduleBox");
+ let noModuleLabel = document.getElementById("noModuleLabel");
+ if (document.getElementById("moduleList").itemCount > 0) {
+ moduleBox.hidden = false;
+ noModuleLabel.hidden = true;
+ } else {
+ moduleBox.hidden = true;
+ noModuleLabel.hidden = false;
+ }
+ SelectFirstItem();
+ enableAdvance();
+ }
+ break;
+ case 1:
+ ImportDialogOKButton();
+ break;
+ case 3:
+ close();
+ break;
+ }
+}
+
+function SelectFirstItem() {
+ var listbox = document.getElementById("moduleList");
+ if (listbox.selectedIndex == -1 && listbox.itemCount > 0) {
+ listbox.selectedIndex = 0;
+ }
+ ImportSelectionChanged();
+}
+
+function enableAdvance() {
+ var listbox = document.getElementById("moduleList");
+ var nextButton = document.getElementById("forward");
+ if (listbox.selectedCount > 0) {
+ nextButton.removeAttribute("disabled");
+ } else {
+ nextButton.setAttribute("disabled", "true");
+ }
+}
+
+function back() {
+ var backButton = document.getElementById("back");
+ var nextButton = document.getElementById("forward");
+ switch (getWizardBoxIndex()) {
+ case 1:
+ backButton.setAttribute("disabled", "true");
+ nextButton.label = nextButton.getAttribute("nextval");
+ nextButton.removeAttribute("disabled");
+ showWizardBox(0);
+ break;
+ case 3:
+ // Clear out the results box.
+ let results = document.getElementById("results");
+ while (results.hasChildNodes()) {
+ results.lastChild.remove();
+ }
+
+ // Reset the next button.
+ nextButton.label = nextButton.getAttribute("nextval");
+ nextButton.removeAttribute("disabled");
+
+ // Enable the cancel button again.
+ document.getElementById("cancel").removeAttribute("disabled");
+
+ // If a new Feed account has been created, rebuild the list.
+ if (gNewFeedAcctCreated) {
+ ListFeedAccounts();
+ }
+
+ // Now go back to the second page.
+ showWizardBox(1);
+ break;
+ }
+}
diff --git a/comm/mailnews/import/content/importDialog.xhtml b/comm/mailnews/import/content/importDialog.xhtml
new file mode 100644
index 0000000000..c3147d8c99
--- /dev/null
+++ b/comm/mailnews/import/content/importDialog.xhtml
@@ -0,0 +1,225 @@
+<?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"?>
+<?xml-stylesheet href="chrome://messenger/skin/input-fields.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/colors.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/themeableDialog.css" type="text/css"?>
+
+<!DOCTYPE html [ <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % importDTD SYSTEM "chrome://messenger/locale/importDialog.dtd" >
+%importDTD; ]>
+
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ lightweightthemes="true"
+ width="720"
+ height="520"
+ scrolling="false"
+>
+ <head>
+ <title>&importDialog.windowTitle;</title>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/globalOverlay.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://global/content/editMenuOverlay.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://messenger-newsblog/content/feed-subscriptions.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/importDialog.js"
+ ></script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <stringbundle
+ id="bundle_importMsgs"
+ src="chrome://messenger/locale/importMsgs.properties"
+ />
+ <stringbundle
+ id="bundle_addressbook"
+ src="chrome://messenger/locale/addressbook/addressBook.properties"
+ />
+ <stringbundle
+ id="bundle_morkImportMsgs"
+ src="chrome://messenger/locale/morkImportMsgs.properties"
+ />
+ <stringbundle
+ id="bundle_vcardImportMsgs"
+ src="chrome://messenger/locale/vCardImportMsgs.properties"
+ />
+ <stringbundle
+ id="bundle_feeds"
+ src="chrome://messenger-newsblog/locale/newsblog.properties"
+ />
+
+ <hbox
+ class="box-header"
+ id="header"
+ title="&importTitle.label;"
+ description="&importShortDesc.label;"
+ />
+
+ <vbox id="stateBox" flex="1" style="min-height: 30em">
+ <vbox class="wizard-box">
+ <description>&importDescription1.label;</description>
+ <description>&importDescription2.label;</description>
+ <separator />
+ <radiogroup id="importFields">
+ <radio
+ id="allRadio"
+ value="all"
+ label="&importAll.label;"
+ accesskey="&importAll.accesskey;"
+ />
+ <separator />
+ <label control="importFields">&select.label;</label>
+ <separator class="thin" />
+ <vbox class="indent">
+ <radio
+ id="addressbookRadio"
+ value="addressbook"
+ label="&importAddressbook.label;"
+ accesskey="&importAddressbook.accesskey;"
+ />
+ <radio
+ id="mailRadio"
+ value="mail"
+ label="&importMail.label;"
+ accesskey="&importMail.accesskey;"
+ />
+ <radio
+ id="feedsRadio"
+ value="feeds"
+ label="&importFeeds.label;"
+ accesskey="&importFeeds.accesskey;"
+ />
+ <radio
+ id="settingsRadio"
+ value="settings"
+ label="&importSettings.label;"
+ accesskey="&importSettings.accesskey;"
+ />
+ <radio
+ id="filtersRadio"
+ value="filters"
+ label="&importFilters.label;"
+ accesskey="&importFilters.accesskey;"
+ />
+ </vbox>
+ </radiogroup>
+ </vbox>
+ <vbox class="wizard-box" hidden="true">
+ <vbox>
+ <vbox id="moduleBox">
+ <vbox>
+ <label
+ id="fileLabel"
+ control="moduleList"
+ value="&selectDescription.label;"
+ accesskey="&selectDescription.accesskey;"
+ />
+ <label
+ id="accountLabel"
+ control="moduleList"
+ value="&selectDescriptionB.label;"
+ accesskey="&selectDescription.accesskey;"
+ hidden="true"
+ />
+ </vbox>
+ <richlistbox
+ id="moduleList"
+ height="200px"
+ onselect="ImportSelectionChanged(); enableAdvance();"
+ />
+ </vbox>
+ <label id="noModuleLabel" hidden="true">&noModulesFound.label;</label>
+ </vbox>
+ <vbox>
+ <hbox flex="1">
+ <description
+ flex="1"
+ control="moduleList"
+ id="description"
+ class="box-padded"
+ />
+ </hbox>
+ <hbox
+ id="acctName-box"
+ flex="1"
+ class="input-container"
+ style="visibility: hidden"
+ >
+ <label
+ control="acctName"
+ class="box-padded"
+ accesskey="&acctName.accesskey;"
+ value="&acctName.label;"
+ />
+ <html:input
+ id="acctName"
+ type="text"
+ class="input-inline"
+ aria-labelledby="acctName"
+ />
+ </hbox>
+ </vbox>
+ </vbox>
+ <vbox class="wizard-box" hidden="true">
+ <spacer flex="1" />
+ <html:fieldset>
+ <label id="progressTitle" class="header">&title.label;</label>
+ <label
+ class="indent"
+ id="progressStatus"
+ value="&processing.label;"
+ />
+ <vbox class="box-padded">
+ <html:progress id="progressMeter" value="5" max="100" />
+ </vbox>
+ </html:fieldset>
+ </vbox>
+ <vbox class="wizard-box" flex="1" hidden="true">
+ <description id="status" />
+ <hbox style="overflow: auto" class="inset" flex="1">
+ <description id="results" flex="1" />
+ </hbox>
+ </vbox>
+ </vbox>
+
+ <separator />
+
+ <separator class="groove" />
+
+ <hbox class="box-padded">
+ <spacer flex="1" />
+ <button
+ id="back"
+ label="&back.label;"
+ disabled="true"
+ oncommand="back();"
+ />
+ <button
+ id="forward"
+ label="&forward.label;"
+ nextval="&forward.label;"
+ finishedval="&finish.label;"
+ oncommand="next();"
+ />
+ <separator orient="vertical" />
+ <button id="cancel" label="&cancel.label;" oncommand="window.close();" />
+ </hbox>
+ </html:body>
+</html>
diff --git a/comm/mailnews/import/modules/AddrBookFileImporter.jsm b/comm/mailnews/import/modules/AddrBookFileImporter.jsm
new file mode 100644
index 0000000000..c9cea94893
--- /dev/null
+++ b/comm/mailnews/import/modules/AddrBookFileImporter.jsm
@@ -0,0 +1,356 @@
+/* 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 = ["AddrBookFileImporter"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+const { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ setTimeout: "resource://gre/modules/Timer.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ MailStringUtils: "resource:///modules/MailStringUtils.jsm",
+ exportAttributes: "resource:///modules/AddrBookUtils.jsm",
+});
+
+XPCOMUtils.defineLazyGetter(lazy, "d3", () => {
+ let d3Scope = Cu.Sandbox(null);
+ Services.scriptloader.loadSubScript(
+ "chrome://global/content/third_party/d3/d3.js",
+ d3Scope
+ );
+ return Cu.waiveXrays(d3Scope.d3);
+});
+
+/**
+ * A module to import address book files.
+ */
+class AddrBookFileImporter {
+ /**
+ * @param {string} type - Source file type, currently supporting "csv",
+ * "ldif", "vcard" and "mab".
+ */
+ constructor(type) {
+ this._type = type;
+ }
+
+ /**
+ * Callback for progress updates.
+ *
+ * @param {number} current - Current imported items count.
+ * @param {number} total - Total items count.
+ */
+ onProgress = () => {};
+
+ _logger = console.createInstance({
+ prefix: "mail.import",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.import.loglevel",
+ });
+
+ /**
+ * Actually start importing records into a directory.
+ *
+ * @param {nsIFile} sourceFile - The source file to import from.
+ * @param {nsIAbDirectory} targetDirectory - The directory to import into.
+ */
+ async startImport(sourceFile, targetDirectory) {
+ this._logger.debug(
+ `Importing ${this._type} file from ${sourceFile.path} into ${targetDirectory.dirName}`
+ );
+ this._sourceFile = sourceFile;
+ this._targetDirectory = targetDirectory;
+
+ switch (this._type) {
+ case "csv":
+ await this._importCsvFile();
+ break;
+ case "ldif":
+ await this._importLdifFile();
+ break;
+ case "vcard":
+ await this._importVCardFile();
+ break;
+ case "sqlite":
+ await this._importSqliteFile();
+ break;
+ case "mab":
+ await this._importMabFile();
+ break;
+ default:
+ throw Components.Exception(
+ `Importing ${this._type} file is not supported`,
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+ }
+
+ /**
+ * Parse a CSV/TSV file to an array of rows, each row is an array of columns.
+ * The first row is expected to contain field names. If we recognize all the
+ * field names, return an empty array, which means everything is parsed fine.
+ * Otherwise, return all the rows.
+ *
+ * @param {nsIFile} sourceFile - The source file to import from.
+ * @returns {string[][]}
+ */
+ async parseCsvFile(sourceFile) {
+ let content = await lazy.MailStringUtils.readEncoded(sourceFile.path);
+
+ let csvRows = lazy.d3.csv.parseRows(content);
+ let tsvRows = lazy.d3.tsv.parseRows(content);
+ let dsvRows = lazy.d3.dsv(";").parseRows(content);
+ if (!csvRows.length && !tsvRows.length && !dsvRows.length) {
+ this._csvRows = [];
+ return [];
+ }
+ // If we have more CSV columns, then it's a CSV file, otherwise a TSV file.
+ this._csvRows = csvRows[0]?.length > tsvRows[0]?.length ? csvRows : tsvRows;
+ // See if it's semicolon separated.
+ if (this._csvRows[0]?.length < dsvRows[0]?.length) {
+ this._csvRows = dsvRows;
+ }
+
+ let bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/importMsgs.properties"
+ );
+ let supportedFieldNames = [];
+ this._supportedCsvProperties = [];
+ // Collect field names in an exported CSV file, and their corresponding
+ // nsIAbCard property names.
+ for (let [property, stringId] of lazy.exportAttributes) {
+ if (stringId) {
+ this._supportedCsvProperties.push(property);
+ supportedFieldNames.push(
+ bundle.GetStringFromID(stringId).toLowerCase()
+ );
+ }
+ }
+ this._csvSkipFirstRow = true;
+ this._csvProperties = [];
+ // Get the nsIAbCard properties corresponding to the user supplied file.
+ for (let field of this._csvRows[0]) {
+ if (
+ !field &&
+ this._csvRows[0].length > 1 &&
+ field == this._csvRows[0].at(-1)
+ ) {
+ // This is the last field and empty, caused by a trailing comma, which
+ // is OK.
+ return [];
+ }
+ let index = supportedFieldNames.indexOf(field.toLowerCase());
+ if (index == -1) {
+ return this._csvRows;
+ }
+ this._csvProperties.push(this._supportedCsvProperties[index]);
+ }
+ return [];
+ }
+
+ /**
+ * Set the address book properties to use when importing.
+ *
+ * @param {number[]} fieldIndexes - An array of indexes representing the
+ * mapping between the source fields and nsIAbCard fields. For example, [2,
+ * 4] means the first field maps to the 2nd property, the second field maps
+ * to the 4th property.
+ */
+ setCsvFields(fieldIndexes) {
+ Services.prefs.setCharPref(
+ "mail.import.csv.fields",
+ fieldIndexes.join(",")
+ );
+ this._csvProperties = fieldIndexes.map(
+ i => this._supportedCsvProperties[i]
+ );
+ this._csvSkipFirstRow = Services.prefs.getBoolPref(
+ "mail.import.csv.skipfirstrow",
+ true
+ );
+ }
+
+ /**
+ * Import the .csv/.tsv source file into the target directory.
+ */
+ async _importCsvFile() {
+ let totalLines = this._csvRows.length - 1;
+ let currentLine = 0;
+
+ let startRow = this._csvSkipFirstRow ? 1 : 0;
+ for (let row of this._csvRows.slice(startRow)) {
+ currentLine++;
+ let card = Cc["@mozilla.org/addressbook/cardproperty;1"].createInstance(
+ Ci.nsIAbCard
+ );
+ for (let i = 0; i < row.length; i++) {
+ let property = this._csvProperties[i];
+ if (!property) {
+ continue;
+ }
+ // Set the field value to the property.
+ card.setProperty(property, row[i]);
+ }
+ this._targetDirectory.addCard(card);
+ if (currentLine % 10 == 0) {
+ this.onProgress(currentLine, totalLines);
+ // Give UI a chance to update the progress bar.
+ await new Promise(resolve => lazy.setTimeout(resolve));
+ }
+ }
+ this.onProgress(totalLines, totalLines);
+ }
+
+ /**
+ * Import the .ldif source file into the target directory.
+ */
+ async _importLdifFile() {
+ this.onProgress(2, 10);
+ let ldifService = Cc["@mozilla.org/addressbook/abldifservice;1"].getService(
+ Ci.nsIAbLDIFService
+ );
+ let progress = {};
+ ldifService.importLDIFFile(
+ this._targetDirectory,
+ this._sourceFile,
+ false,
+ progress
+ );
+ this.onProgress(10, 10);
+ }
+
+ /**
+ * Import the .vcf source file into the target directory.
+ */
+ async _importVCardFile() {
+ let vcardService = Cc[
+ "@mozilla.org/addressbook/msgvcardservice;1"
+ ].getService(Ci.nsIMsgVCardService);
+
+ let content = await IOUtils.readUTF8(this._sourceFile.path);
+ // According to rfc6350, \r\n should be used as line break.
+ let sep = content.includes("\r\n") ? "\r\n" : "\n";
+ let lines = content.trim().split(sep);
+
+ let totalLines = lines.length;
+ let currentLine = 0;
+ let record = [];
+
+ for (let line of lines) {
+ currentLine++;
+ if (!line) {
+ continue;
+ }
+
+ if (line.toLowerCase().trimEnd() == "begin:vcard") {
+ if (record.length) {
+ throw Components.Exception(
+ "Expecting END:VCARD but got BEGIN:VCARD",
+ Cr.NS_ERROR_ILLEGAL_VALUE
+ );
+ }
+ record.push(line);
+ continue;
+ } else if (!record.length) {
+ throw Components.Exception(
+ `Expecting BEGIN:VCARD but got ${line}`,
+ Cr.NS_ERROR_ILLEGAL_VALUE
+ );
+ }
+
+ record.push(line);
+
+ if (line.toLowerCase().trimEnd() == "end:vcard") {
+ this._targetDirectory.addCard(
+ vcardService.vCardToAbCard(record.join("\n") + "\n")
+ );
+ record = [];
+ this.onProgress(currentLine, totalLines);
+ // Give UI a chance to update the progress bar.
+ await new Promise(resolve => lazy.setTimeout(resolve));
+ }
+ }
+ this.onProgress(totalLines, totalLines);
+ }
+
+ /**
+ * Import the .sqlite source file into the target directory.
+ */
+ async _importSqliteFile() {
+ this.onProgress(2, 10);
+ // Create a temporary address book.
+ let dirId = MailServices.ab.newAddressBook(
+ "tmp",
+ "",
+ Ci.nsIAbManager.JS_DIRECTORY_TYPE
+ );
+ let tmpDirectory = MailServices.ab.getDirectoryFromId(dirId);
+
+ try {
+ // Close the connection to release the file handler.
+ await tmpDirectory.cleanUp();
+ // Overwrite the sqlite database file.
+ this._sourceFile.copyTo(
+ Services.dirsvc.get("ProfD", Ci.nsIFile),
+ tmpDirectory.fileName
+ );
+ // Write-Ahead Logging file contains changes not written to .sqlite file
+ // yet.
+ let sourceWalFile = this._sourceFile.parent.clone();
+ sourceWalFile.append(this._sourceFile.leafName + "-wal");
+ if (sourceWalFile.exists()) {
+ sourceWalFile.copyTo(
+ Services.dirsvc.get("ProfD", Ci.nsIFile),
+ tmpDirectory.fileName + "-wal"
+ );
+ }
+ // Open a new connection to use the new database file.
+ let uri = tmpDirectory.URI;
+ tmpDirectory = Cc[
+ "@mozilla.org/addressbook/directory;1?type=jsaddrbook"
+ ].createInstance(Ci.nsIAbDirectory);
+ tmpDirectory.init(uri);
+
+ for (let card of tmpDirectory.childCards) {
+ this._targetDirectory.addCard(card);
+ }
+ this.onProgress(8, 10);
+
+ for (let sourceList of tmpDirectory.childNodes) {
+ let targetList = this._targetDirectory.getMailListFromName(
+ sourceList.dirName
+ );
+ if (!targetList) {
+ targetList = this._targetDirectory.addMailList(sourceList);
+ }
+ for (let card of sourceList.childCards) {
+ targetList.addCard(card);
+ }
+ }
+ this.onProgress(10, 10);
+ } finally {
+ MailServices.ab.deleteAddressBook(tmpDirectory.URI);
+ }
+ }
+
+ /**
+ * Import the .mab source file into the target directory.
+ */
+ async _importMabFile() {
+ this.onProgress(2, 10);
+ let importMab = Cc[
+ "@mozilla.org/import/import-ab-file;1?type=mab"
+ ].createInstance(Ci.nsIImportABFile);
+ importMab.readFileToDirectory(this._sourceFile, this._targetDirectory);
+ this.onProgress(10, 10);
+ }
+}
diff --git a/comm/mailnews/import/modules/AppleMailProfileImporter.jsm b/comm/mailnews/import/modules/AppleMailProfileImporter.jsm
new file mode 100644
index 0000000000..c6c6e5b4ba
--- /dev/null
+++ b/comm/mailnews/import/modules/AppleMailProfileImporter.jsm
@@ -0,0 +1,92 @@
+/* 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 = ["AppleMailProfileImporter"];
+
+var { BaseProfileImporter } = ChromeUtils.import(
+ "resource:///modules/BaseProfileImporter.jsm"
+);
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+/**
+ * A module to import things from an apple mail profile dir into the current
+ * profile.
+ */
+class AppleMailProfileImporter extends BaseProfileImporter {
+ USE_FILE_PICKER = true;
+
+ SUPPORTED_ITEMS = {
+ accounts: false,
+ addressBooks: false,
+ calendars: false,
+ mailMessages: true,
+ };
+
+ async getSourceProfiles() {
+ this._importModule = Cc[
+ "@mozilla.org/import/import-applemail;1"
+ ].createInstance(Ci.nsIImportModule);
+ this._importMailGeneric = this._importModule
+ .GetImportInterface("mail")
+ .QueryInterface(Ci.nsIImportGeneric);
+ let importMail = this._importMailGeneric
+ .GetData("mailInterface")
+ .QueryInterface(Ci.nsIImportMail);
+ let outLocation = {};
+ let outFound = {};
+ let outUserVerify = {};
+ importMail.GetDefaultLocation(outLocation, outFound, outUserVerify);
+ if (outLocation.value) {
+ return [{ dir: outLocation.value }];
+ }
+ return [];
+ }
+
+ async startImport(sourceProfileDir, items) {
+ this._logger.debug(
+ `Start importing from ${sourceProfileDir.path}, items=${JSON.stringify(
+ items
+ )}`
+ );
+ this._itemsTotalCount = Object.values(items).filter(Boolean).length;
+ this._itemsImportedCount = 0;
+
+ let successStr = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ let errorStr = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+
+ if (items.mailMessages) {
+ // @see nsIImportGeneric.
+ this._importMailGeneric.SetData("mailLocation", sourceProfileDir);
+ let wantsProgress = this._importMailGeneric.WantsProgress();
+ this._importMailGeneric.BeginImport(successStr, errorStr);
+ if (wantsProgress) {
+ while (this._importMailGeneric.GetProgress() < 100) {
+ this._logger.debug(
+ "Import mail messages progress:",
+ this._importMailGeneric.GetProgress()
+ );
+ await new Promise(resolve => setTimeout(resolve, 50));
+ this._importMailGeneric.ContinueImport();
+ }
+ }
+ if (successStr.data) {
+ this._logger.debug(
+ "Finished importing mail messages:",
+ successStr.data
+ );
+ }
+ if (errorStr.data) {
+ this._logger.error("Failed to import mail messages:", errorStr.data);
+ throw new Error(errorStr.data);
+ }
+ await this._updateProgress();
+ }
+ }
+}
diff --git a/comm/mailnews/import/modules/BaseProfileImporter.jsm b/comm/mailnews/import/modules/BaseProfileImporter.jsm
new file mode 100644
index 0000000000..18c5dce16b
--- /dev/null
+++ b/comm/mailnews/import/modules/BaseProfileImporter.jsm
@@ -0,0 +1,100 @@
+/* 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 = ["BaseProfileImporter"];
+
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+/**
+ * An object to represent a source profile to import from.
+ *
+ * @typedef {object} SourceProfile
+ * @property {string} name - The profile name.
+ * @property {nsIFile} dir - The profile location.
+ *
+ * An object to represent items to import.
+ * @typedef {object} ImportItems
+ * @property {boolean} accounts - Whether to import accounts and settings.
+ * @property {boolean} addressBooks - Whether to import address books.
+ * @property {boolean} calendars - Whether to import calendars.
+ * @property {boolean} mailMessages - Whether to import mail messages.
+ */
+
+/**
+ * Common interfaces shared by profile importers.
+ *
+ * @abstract
+ */
+class BaseProfileImporter {
+ /** @type boolean - Whether to allow importing from a user picked dir. */
+ USE_FILE_PICKER = true;
+
+ /** @type ImportItems */
+ SUPPORTED_ITEMS = {
+ accounts: true,
+ addressBooks: true,
+ calendars: true,
+ mailMessages: true,
+ };
+
+ /** When importing from a zip file, ignoring these folders. */
+ IGNORE_DIRS = [];
+
+ /**
+ * Callback for progress updates.
+ *
+ * @param {number} current - Current imported items count.
+ * @param {number} total - Total items count.
+ */
+ onProgress = () => {};
+
+ /**
+ * @returns {SourceProfile[]} Profiles found on this machine.
+ */
+ async getSourceProfiles() {
+ throw Components.Exception(
+ `getSourceProfiles not implemented in ${this.constructor.name}`,
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ /**
+ * Actually start importing things to the current profile.
+ *
+ * @param {nsIFile} sourceProfileDir - The source location to import from.
+ * @param {ImportItems} items - The items to import.
+ * @returns {boolean} Returns true when accounts have been imported, which
+ * means a restart is needed. Otherwise, no restart is needed.
+ */
+ async startImport(sourceProfileDir, items) {
+ throw Components.Exception(
+ `startImport not implemented in ${this.constructor.name}`,
+ Cr.NS_ERROR_NOT_IMPLEMENTED
+ );
+ }
+
+ /**
+ * Reset use_without_mail_account, so that imported accounts are correctly
+ * rendered in the folderPane.
+ */
+ _onImportAccounts() {
+ Services.prefs.setBoolPref("app.use_without_mail_account", false);
+ }
+
+ /**
+ * Increase _itemsImportedCount by one, and call onProgress.
+ */
+ async _updateProgress() {
+ this.onProgress(++this._itemsImportedCount, this._itemsTotalCount);
+ return new Promise(resolve => setTimeout(resolve));
+ }
+
+ _logger = console.createInstance({
+ prefix: "mail.import",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.import.loglevel",
+ });
+}
diff --git a/comm/mailnews/import/modules/BeckyProfileImporter.jsm b/comm/mailnews/import/modules/BeckyProfileImporter.jsm
new file mode 100644
index 0000000000..7c81ca7b66
--- /dev/null
+++ b/comm/mailnews/import/modules/BeckyProfileImporter.jsm
@@ -0,0 +1,125 @@
+/* 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 = ["BeckyProfileImporter"];
+
+var { BaseProfileImporter } = ChromeUtils.import(
+ "resource:///modules/BaseProfileImporter.jsm"
+);
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+/**
+ * A module to import things from an becky profile dir into the current
+ * profile.
+ */
+class BeckyProfileImporter extends BaseProfileImporter {
+ SUPPORTED_ITEMS = {
+ accounts: false,
+ addressBooks: true,
+ calendars: false,
+ mailMessages: true,
+ };
+
+ async getSourceProfiles() {
+ this._importModule = Cc[
+ "@mozilla.org/import/import-becky;1"
+ ].createInstance(Ci.nsIImportModule);
+ this._importMailGeneric = this._importModule
+ .GetImportInterface("mail")
+ .QueryInterface(Ci.nsIImportGeneric);
+ let importMail = this._importMailGeneric
+ .GetData("mailInterface")
+ .QueryInterface(Ci.nsIImportMail);
+ let outLocation = {};
+ let outFound = {};
+ let outUserVerify = {};
+ importMail.GetDefaultLocation(outLocation, outFound, outUserVerify);
+ if (outLocation.value) {
+ return [{ dir: outLocation.value }];
+ }
+ return [];
+ }
+
+ async startImport(sourceProfileDir, items) {
+ this._logger.debug(
+ `Start importing from ${sourceProfileDir.path}, items=${JSON.stringify(
+ items
+ )}`
+ );
+ this._itemsTotalCount = Object.values(items).filter(Boolean).length;
+ this._itemsImportedCount = 0;
+
+ let successStr = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ let errorStr = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+
+ if (items.mailMessages) {
+ // @see nsIImportGeneric.
+ this._importMailGeneric.SetData("mailLocation", sourceProfileDir);
+ let wantsProgress = this._importMailGeneric.WantsProgress();
+ this._importMailGeneric.BeginImport(successStr, errorStr);
+ if (wantsProgress) {
+ while (this._importMailGeneric.GetProgress() < 100) {
+ this._logger.debug(
+ "Import mail messages progress:",
+ this._importMailGeneric.GetProgress()
+ );
+ await new Promise(resolve => setTimeout(resolve, 50));
+ this._importMailGeneric.ContinueImport();
+ }
+ }
+ if (successStr.data) {
+ this._logger.debug(
+ "Finished importing mail messages:",
+ successStr.data
+ );
+ }
+ if (errorStr.data) {
+ this._logger.error("Failed to import mail messages:", errorStr.data);
+ throw new Error(errorStr.data);
+ }
+ await this._updateProgress();
+ }
+
+ if (items.addressBooks) {
+ successStr.data = "";
+ errorStr.data = "";
+
+ let importABGeneric = this._importModule
+ .GetImportInterface("addressbook")
+ .QueryInterface(Ci.nsIImportGeneric);
+ importABGeneric.SetData("addressLocation", sourceProfileDir);
+
+ // @see nsIImportGeneric.
+ let wantsProgress = importABGeneric.WantsProgress();
+ importABGeneric.BeginImport(successStr, errorStr);
+ if (wantsProgress) {
+ while (importABGeneric.GetProgress() < 100) {
+ this._logger.debug(
+ "Import address books progress:",
+ importABGeneric.GetProgress()
+ );
+ await new Promise(resolve => setTimeout(resolve, 50));
+ importABGeneric.ContinueImport();
+ }
+ }
+ if (successStr.data) {
+ this._logger.debug(
+ "Finished importing address books:",
+ successStr.data
+ );
+ }
+ if (errorStr.data) {
+ this._logger.error("Failed to import address books:", errorStr.data);
+ throw new Error(errorStr.data);
+ }
+ await this._updateProgress();
+ }
+ }
+}
diff --git a/comm/mailnews/import/modules/CalendarFileImporter.jsm b/comm/mailnews/import/modules/CalendarFileImporter.jsm
new file mode 100644
index 0000000000..ffa69feafb
--- /dev/null
+++ b/comm/mailnews/import/modules/CalendarFileImporter.jsm
@@ -0,0 +1,127 @@
+/* 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 = ["CalendarFileImporter"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+
+ChromeUtils.defineESModuleGetters(lazy, {
+ setTimeout: "resource://gre/modules/Timer.sys.mjs",
+});
+
+XPCOMUtils.defineLazyModuleGetters(lazy, {
+ cal: "resource:///modules/calendar/calUtils.jsm",
+});
+
+/**
+ * A module to import iCalendar (.ics) file.
+ */
+class CalendarFileImporter {
+ /**
+ * Callback for progress updates.
+ *
+ * @param {number} current - Current imported items count.
+ * @param {number} total - Total items count.
+ */
+ onProgress = () => {};
+
+ _logger = console.createInstance({
+ prefix: "mail.import",
+ maxLogLevel: "Warn",
+ maxLogLevelPref: "mail.import.loglevel",
+ });
+
+ /**
+ * Parse an ics file to an array of items.
+ *
+ * @param {string} file - The file path of an ics file.
+ * @returns {calIItemBase[]}
+ */
+ async parseIcsFile(file) {
+ this._logger.debug(`Getting items from ${file.path}`);
+ let importer = Cc["@mozilla.org/calendar/import;1?type=ics"].getService(
+ Ci.calIImporter
+ );
+
+ let inputStream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+ let items = [];
+
+ try {
+ // 0x01 means MODE_RDONLY.
+ inputStream.init(file, 0x01, 0o444, {});
+ items = importer.importFromStream(inputStream);
+ if (!items.length) {
+ throw new Error("noItemsFound");
+ }
+ } catch (e) {
+ this._logger.error(e);
+ throw e;
+ } finally {
+ inputStream.close();
+ }
+
+ return items;
+ }
+
+ /**
+ * Get all calendars that the current user can import items to.
+ *
+ * @returns {calICalendar[]}
+ */
+ getTargetCalendars() {
+ let calendars = lazy.cal.manager
+ .getCalendars()
+ .filter(
+ calendar =>
+ !calendar.getProperty("disabled") &&
+ !calendar.readOnly &&
+ lazy.cal.acl.userCanAddItemsToCalendar(calendar)
+ );
+ let sortOrderPref = Services.prefs.getCharPref(
+ "calendar.list.sortOrder",
+ ""
+ );
+ let sortOrder = sortOrderPref ? sortOrderPref.split(" ") : [];
+ return calendars.sort(
+ (x, y) => sortOrder.indexOf(x.id) - sortOrder.indexOf(y.id)
+ );
+ }
+
+ /**
+ * Actually start importing items into a calendar.
+ *
+ * @param {nsIFile} sourceFile - The source file to import from.
+ * @param {calICalendar} targetCalendar - The calendar to import into.
+ */
+ async startImport(items, targetCalendar) {
+ let count = 0;
+ let total = items.length;
+
+ this._logger.debug(`Importing ${total} items into ${targetCalendar.name}`);
+
+ for (let item of items) {
+ try {
+ await targetCalendar.addItem(item);
+ } catch (e) {
+ this._logger.error(e);
+ throw e;
+ }
+
+ count++;
+
+ if (count % 10 == 0) {
+ this.onProgress(count, total);
+ // Give the UI a chance to update the progress bar.
+ await new Promise(resolve => lazy.setTimeout(resolve));
+ }
+ }
+ this.onProgress(total, total);
+ }
+}
diff --git a/comm/mailnews/import/modules/OutlookProfileImporter.jsm b/comm/mailnews/import/modules/OutlookProfileImporter.jsm
new file mode 100644
index 0000000000..6c2b5932f2
--- /dev/null
+++ b/comm/mailnews/import/modules/OutlookProfileImporter.jsm
@@ -0,0 +1,143 @@
+/* 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 = ["OutlookProfileImporter"];
+
+var { BaseProfileImporter } = ChromeUtils.import(
+ "resource:///modules/BaseProfileImporter.jsm"
+);
+var { setTimeout } = ChromeUtils.importESModule(
+ "resource://gre/modules/Timer.sys.mjs"
+);
+
+/**
+ * A module to import things from an outlook profile dir into the current
+ * profile.
+ */
+class OutlookProfileImporter extends BaseProfileImporter {
+ USE_FILE_PICKER = false;
+
+ SUPPORTED_ITEMS = {
+ accounts: true,
+ addressBooks: true,
+ calendars: false,
+ mailMessages: true,
+ };
+
+ async getSourceProfiles() {
+ this._importModule = Cc[
+ "@mozilla.org/import/import-outlook;1"
+ ].createInstance(Ci.nsIImportModule);
+ this._importMailGeneric = this._importModule
+ .GetImportInterface("mail")
+ .QueryInterface(Ci.nsIImportGeneric);
+ let importMail = this._importMailGeneric
+ .GetData("mailInterface")
+ .QueryInterface(Ci.nsIImportMail);
+ let outLocation = {};
+ let outFound = {};
+ let outUserVerify = {};
+ importMail.GetDefaultLocation(outLocation, outFound, outUserVerify);
+ if (outLocation.value) {
+ return [{ dir: outLocation.value }];
+ }
+ return [];
+ }
+
+ async startImport(sourceProfileDir, items) {
+ this._logger.debug(
+ `Start importing from ${sourceProfileDir.path}, items=${JSON.stringify(
+ items
+ )}`
+ );
+ this._itemsTotalCount = Object.values(items).filter(Boolean).length;
+ this._itemsImportedCount = 0;
+
+ if (items.accounts) {
+ let importSettings = this._importModule
+ .GetImportInterface("settings")
+ .QueryInterface(Ci.nsIImportSettings);
+ let outLocalAccount = {};
+ importSettings.Import(outLocalAccount);
+ await this._updateProgress();
+ this._onImportAccounts();
+ }
+
+ let successStr = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ let errorStr = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+
+ if (items.mailMessages) {
+ // @see nsIImportGeneric.
+ let wantsProgress = this._importMailGeneric.WantsProgress();
+ this._importMailGeneric.BeginImport(successStr, errorStr);
+ if (wantsProgress) {
+ while (this._importMailGeneric.GetProgress() < 100) {
+ this._logger.debug(
+ "Import mail messages progress:",
+ this._importMailGeneric.GetProgress()
+ );
+ await new Promise(resolve => setTimeout(resolve, 50));
+ this._importMailGeneric.ContinueImport();
+ }
+ }
+ if (successStr.data) {
+ this._logger.debug(
+ "Finished importing mail messages:",
+ successStr.data
+ );
+ }
+ if (errorStr.data) {
+ this._logger.error("Failed to import mail messages:", errorStr.data);
+ throw new Error(errorStr.data);
+ }
+ await this._updateProgress();
+ }
+
+ if (items.addressBooks) {
+ successStr.data = "";
+ errorStr.data = "";
+
+ let importABGeneric = this._importModule
+ .GetImportInterface("addressbook")
+ .QueryInterface(Ci.nsIImportGeneric);
+ // Set import destination to the personal address book.
+ let addressDestination = Cc[
+ "@mozilla.org/supports-string;1"
+ ].createInstance(Ci.nsISupportsString);
+ addressDestination.data = "jsaddrbook://abooks.sqlite";
+ importABGeneric.SetData("addressDestination", addressDestination);
+
+ // @see nsIImportGeneric.
+ let wantsProgress = importABGeneric.WantsProgress();
+ importABGeneric.BeginImport(successStr, errorStr);
+ if (wantsProgress) {
+ while (importABGeneric.GetProgress() < 100) {
+ this._logger.debug(
+ "Import address books progress:",
+ importABGeneric.GetProgress()
+ );
+ await new Promise(resolve => setTimeout(resolve, 50));
+ importABGeneric.ContinueImport();
+ }
+ }
+ if (successStr.data) {
+ this._logger.debug(
+ "Finished importing address books:",
+ successStr.data
+ );
+ }
+ if (errorStr.data) {
+ this._logger.error("Failed to import address books:", errorStr.data);
+ throw new Error(errorStr.data);
+ }
+ await this._updateProgress();
+ }
+
+ return items.accounts;
+ }
+}
diff --git a/comm/mailnews/import/modules/SeamonkeyProfileImporter.jsm b/comm/mailnews/import/modules/SeamonkeyProfileImporter.jsm
new file mode 100644
index 0000000000..e96cc01a2d
--- /dev/null
+++ b/comm/mailnews/import/modules/SeamonkeyProfileImporter.jsm
@@ -0,0 +1,75 @@
+/* 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 = ["SeamonkeyProfileImporter"];
+
+var { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+var { ThunderbirdProfileImporter } = ChromeUtils.import(
+ "resource:///modules/ThunderbirdProfileImporter.jsm"
+);
+
+/**
+ * A module to import things from a seamonkey profile dir into the current
+ * profile.
+ */
+class SeamonkeyProfileImporter extends ThunderbirdProfileImporter {
+ NAME = "SeaMonkey";
+
+ /** @see BaseProfileImporter */
+ async getSourceProfiles() {
+ let slugs = {
+ win: ["AppData", "Mozilla", "SeaMonkey"],
+ macosx: ["ULibDir", "Application Support", "SeaMonkey"],
+ linux: ["Home", ".mozilla", "seamonkey"],
+ }[AppConstants.platform];
+ if (!slugs) {
+ // We don't recognize this OS.
+ return [];
+ }
+
+ let seamonkeyRoot = Services.dirsvc.get(slugs[0], Ci.nsIFile);
+ slugs.slice(1).forEach(seamonkeyRoot.append);
+ let profilesIni = seamonkeyRoot.clone();
+ profilesIni.append("profiles.ini");
+ if (!profilesIni.exists()) {
+ // No Seamonkey profile found in the well known location.
+ return [];
+ }
+
+ let profiles = [];
+ let ini = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]
+ .getService(Ci.nsIINIParserFactory)
+ .createINIParser(profilesIni);
+ for (let section of ini.getSections()) {
+ let keys = [...ini.getKeys(section)];
+ if (!keys.includes("Path")) {
+ // Not a profile section.
+ continue;
+ }
+
+ let name = keys.includes("Name") ? ini.getString(section, "Name") : null;
+ let path = ini.getString(section, "Path");
+ let isRelative = keys.includes("IsRelative")
+ ? ini.getString(section, "IsRelative") == "1"
+ : false;
+
+ let dir;
+ if (isRelative) {
+ dir = seamonkeyRoot.clone();
+ dir.append(path);
+ } else {
+ dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ dir.initWithPath(path);
+ }
+ if (!dir.exists()) {
+ // Not a valid profile.
+ continue;
+ }
+ profiles.push({ name, dir });
+ }
+ return profiles;
+ }
+}
diff --git a/comm/mailnews/import/modules/ThunderbirdProfileImporter.jsm b/comm/mailnews/import/modules/ThunderbirdProfileImporter.jsm
new file mode 100644
index 0000000000..082813309e
--- /dev/null
+++ b/comm/mailnews/import/modules/ThunderbirdProfileImporter.jsm
@@ -0,0 +1,1024 @@
+/* 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 = ["ThunderbirdProfileImporter"];
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { BaseProfileImporter } = ChromeUtils.import(
+ "resource:///modules/BaseProfileImporter.jsm"
+);
+var { AddrBookFileImporter } = ChromeUtils.import(
+ "resource:///modules/AddrBookFileImporter.jsm"
+);
+
+/**
+ * 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
+ */
+
+// Pref branches that need special handling.
+const ACCOUNT_MANAGER = "mail.accountmanager.";
+const MAIL_IDENTITY = "mail.identity.";
+const MAIL_SERVER = "mail.server.";
+const MAIL_ACCOUNT = "mail.account.";
+const IM_ACCOUNT = "messenger.account.";
+const MAIL_SMTP = "mail.smtp.";
+const SMTP_SERVER = "mail.smtpserver.";
+const ADDRESS_BOOK = "ldap_2.servers.";
+const LDAP_AUTO_COMPLETE = "ldap_2.autoComplete.";
+const CALENDAR = "calendar.registry.";
+const CALENDAR_LIST = "calendar.list.";
+
+// Prefs (branches) that we do not want to copy directly.
+const IGNORE_PREFS = [
+ "app.update.",
+ "browser.",
+ "calendar.timezone",
+ "devtools.",
+ "extensions.",
+ "mail.cloud_files.accounts.",
+ "mail.newsrc_root",
+ "mail.root.",
+ "mail.smtpservers",
+ "messenger.accounts",
+ "print.",
+ "services.",
+ "toolkit.telemetry.",
+];
+
+/**
+ * A module to import things from another thunderbird profile dir into the
+ * current profile.
+ */
+class ThunderbirdProfileImporter extends BaseProfileImporter {
+ NAME = "Thunderbird";
+
+ IGNORE_DIRS = [
+ "chrome_debugger_profile",
+ "crashes",
+ "datareporting",
+ "extensions",
+ "extension-store",
+ "logs",
+ "minidumps",
+ "saved-telemetry-pings",
+ "security_state",
+ "storage",
+ "xulstore",
+ ];
+
+ async getSourceProfiles() {
+ let profileService = Cc[
+ "@mozilla.org/toolkit/profile-service;1"
+ ].getService(Ci.nsIToolkitProfileService);
+ let sourceProfiles = [];
+ for (let profile of profileService.profiles) {
+ if (profile == profileService.currentProfile) {
+ continue;
+ }
+ sourceProfiles.push({
+ name: profile.name,
+ dir: profile.rootDir,
+ });
+ }
+ return sourceProfiles;
+ }
+
+ async startImport(sourceProfileDir, items) {
+ this._logger.debug(
+ `Start importing from ${sourceProfileDir.path}, items=${JSON.stringify(
+ items
+ )}`
+ );
+
+ this._sourceProfileDir = sourceProfileDir;
+ this._items = items;
+ this._itemsTotalCount = Object.values(items).filter(Boolean).length;
+ this._itemsImportedCount = 0;
+
+ try {
+ this._localServer = MailServices.accounts.localFoldersServer;
+ } catch (e) {}
+
+ if (items.accounts || items.addressBooks || items.calendars) {
+ await this._loadPreferences();
+ }
+
+ if (this._items.accounts) {
+ await this._importServersAndAccounts();
+ this._importOtherPrefs(this._otherPrefs);
+ await this._updateProgress();
+ }
+
+ if (this._items.addressBooks) {
+ await this._importAddressBooks(
+ this._branchPrefsMap.get(ADDRESS_BOOK),
+ this._collectPrefsToObject(this._branchPrefsMap.get(LDAP_AUTO_COMPLETE))
+ );
+ await this._updateProgress();
+ }
+
+ if (this._items.calendars) {
+ this._importCalendars(
+ this._branchPrefsMap.get(CALENDAR),
+ this._collectPrefsToObject(this._branchPrefsMap.get(CALENDAR_LIST))
+ );
+ await this._updateProgress();
+ }
+
+ if (!this._items.accounts && this._items.mailMessages) {
+ this._importMailMessagesToLocal();
+ }
+
+ await this._updateProgress();
+
+ return true;
+ }
+
+ /**
+ * Collect interested prefs from this._sourceProfileDir.
+ */
+ async _loadPreferences() {
+ // A Map to collect all prefs in interested pref branches.
+ // @type {Map<string, PrefItem[]>}
+ this._branchPrefsMap = new Map([
+ [ACCOUNT_MANAGER, []],
+ [MAIL_IDENTITY, []],
+ [MAIL_SERVER, []],
+ [MAIL_ACCOUNT, []],
+ [IM_ACCOUNT, []],
+ [MAIL_SMTP, []],
+ [SMTP_SERVER, []],
+ [ADDRESS_BOOK, []],
+ [LDAP_AUTO_COMPLETE, []],
+ [CALENDAR, []],
+ [CALENDAR_LIST, []],
+ ]);
+ this._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 this._branchPrefsMap) {
+ if (name.startsWith(branchName)) {
+ branchPrefs.push([type, name.slice(branchName.length), value]);
+ return;
+ }
+ }
+ if (IGNORE_PREFS.some(ignore => name.startsWith(ignore))) {
+ return;
+ }
+ // Collect all the other prefs.
+ this._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);
+ },
+ });
+ }
+
+ /**
+ * Import all the servers and accounts.
+ */
+ async _importServersAndAccounts() {
+ // Import SMTP servers first, the importing order is important.
+ let smtpServerKeyMap = this._importSmtpServers(
+ this._branchPrefsMap.get(SMTP_SERVER),
+ this._collectPrefsToObject(this._branchPrefsMap.get(MAIL_SMTP))
+ .defaultserver
+ );
+
+ // mail.identity.idN.smtpServer depends on transformed smtp server key.
+ let identityKeyMap = this._importIdentities(
+ this._branchPrefsMap.get(MAIL_IDENTITY),
+ smtpServerKeyMap
+ );
+ let imAccountKeyMap = await this._importIMAccounts(
+ this._branchPrefsMap.get(IM_ACCOUNT)
+ );
+
+ let accountManager = this._collectPrefsToObject(
+ this._branchPrefsMap.get(ACCOUNT_MANAGER)
+ );
+ // Officially we only support one Local Folders account, if we already have
+ // one, do not import a new one.
+ this._sourceLocalServerKeyToSkip = this._localServer
+ ? accountManager.localfoldersserver
+ : null;
+ this._sourceLocalServerAttrs = {};
+
+ // mail.server.serverN.imAccount depends on transformed im account key.
+ let incomingServerKeyMap = await this._importIncomingServers(
+ this._branchPrefsMap.get(MAIL_SERVER),
+ imAccountKeyMap
+ );
+
+ // mail.account.accountN.{identities, server} depends on previous steps.
+ this._importAccounts(
+ this._branchPrefsMap.get(MAIL_ACCOUNT),
+ accountManager.accounts,
+ accountManager.defaultaccount,
+ identityKeyMap,
+ incomingServerKeyMap
+ );
+
+ await this._importMailMessages(incomingServerKeyMap);
+ if (this._sourceLocalServerKeyToSkip) {
+ this._mergeLocalFolders();
+ }
+
+ if (accountManager.accounts) {
+ this._onImportAccounts();
+ }
+ }
+
+ /**
+ * Collect an array of prefs to an object.
+ *
+ * @param {PrefItem[]} prefs - An array of prefs.
+ * @returns {object} An object mapping pref name to pref value.
+ */
+ _collectPrefsToObject(prefs) {
+ let obj = {};
+ for (let [, name, value] of prefs) {
+ obj[name] = value;
+ }
+ return obj;
+ }
+
+ /**
+ * 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, attr] = name.split(".");
+ if (key == this._sourceLocalServerKeyToSkip) {
+ if (["directory", "directory-rel"].includes(attr)) {
+ this._sourceLocalServerAttrs[attr] = value;
+ }
+ // We already have a Local Folders account.
+ continue;
+ }
+ if (attr == "deferred_to_account") {
+ // Handling deferred account is a bit complicated, to prevent potential
+ // problems, just skip this pref so it becomes a normal account.
+ continue;
+ }
+ 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;
+ }
+
+ /**
+ * 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" || value == this._sourceLocalServerKeyToSkip) {
+ 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")) {
+ // An account can have multiple identities.
+ newValue = value
+ .split(",")
+ .map(v => identityKeyMap.get(v))
+ .filter(Boolean)
+ .join(",");
+ } 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 (sourceAccounts) {
+ for (let sourceAccountKey of sourceAccounts.split(",")) {
+ accounts.push(accountKeyMap.get(sourceAccountKey));
+ }
+ Services.prefs.setCharPref(
+ "mail.accountmanager.accounts",
+ accounts.filter(Boolean).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)
+ );
+ }
+ }
+
+ /**
+ * Try to locate a file specified by the relative path, if not possible, use
+ * the absolute path.
+ *
+ * @param {string} relValue - The pref value for the relative file path.
+ * @param {string} absValue - The pref value for the absolute file path.
+ * @returns {nsIFile}
+ */
+ _getSourceFileFromPaths(relValue, absValue) {
+ let relPath = relValue.slice("[ProfD]".length);
+ let parts = relPath.split("/");
+ if (!relValue.startsWith("[ProfD]") || parts.includes("..")) {
+ // If we don't recognize this path or if it's a path outside the ProfD,
+ // use absValue instead.
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ try {
+ file.initWithPath(absValue);
+ } catch (e) {
+ this._logger.warn("nsIFile.initWithPath failed for path=", absValue);
+ return null;
+ }
+ return file;
+ }
+
+ let sourceFile = this._sourceProfileDir.clone();
+ for (let part of parts) {
+ sourceFile.append(part);
+ }
+ return sourceFile;
+ }
+
+ /**
+ * 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 _importMailMessages(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;
+ }
+
+ 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;
+ }
+
+ this._logger.debug("Importing mail messages for", key);
+
+ let sourceDir = this._getSourceFileFromPaths(
+ branch.getCharPref("directory-rel", ""),
+ branch.getCharPref("directory", "")
+ );
+ if (sourceDir?.exists()) {
+ // Use the hostname as mail folder name and ensure it's unique.
+ targetDir.append(hostname);
+ targetDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+
+ this._recursivelyCopyMsgFolder(sourceDir, targetDir);
+ branch.setCharPref("directory", targetDir.path);
+ // .directory-rel may be outdated, it will be created when first needed.
+ branch.clearUserPref("directory-rel");
+ }
+
+ if (type == "nntp") {
+ let targetNewsrc = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ targetNewsrc.append("News");
+ targetNewsrc.append(`newsrc-${hostname}`);
+ targetNewsrc.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+
+ let sourceNewsrc = this._getSourceFileFromPaths(
+ branch.getCharPref("newsrc.file-rel", ""),
+ branch.getCharPref("newsrc.file", "")
+ );
+ if (sourceNewsrc?.exists()) {
+ 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");
+ }
+ }
+ }
+
+ /**
+ * Merge Local Folders from the source profile into the current profile.
+ * Source Local Folders become a subfoler of the current Local Folders.
+ */
+ _mergeLocalFolders() {
+ let sourceDir = this._getSourceFileFromPaths(
+ this._sourceLocalServerAttrs["directory-rel"],
+ this._sourceLocalServerAttrs.directory
+ );
+ if (!sourceDir?.exists()) {
+ return;
+ }
+ let rootMsgFolder = this._localServer.rootMsgFolder;
+ let folderName = rootMsgFolder.generateUniqueSubfolderName(
+ "Local Folders",
+ null
+ );
+ rootMsgFolder.createSubfolder(folderName, null);
+ let targetDir = rootMsgFolder.filePath;
+ targetDir.append(folderName + ".sbd");
+ this._logger.debug(
+ `Copying ${sourceDir.path} to ${targetDir.path} in Local Folders`
+ );
+ this._recursivelyCopyMsgFolder(sourceDir, targetDir, true);
+ }
+
+ /**
+ * Copy a source msg folder to a destination.
+ *
+ * @param {nsIFile} sourceDir - The source msg folder location.
+ * @param {nsIFile} targetDir - The target msg folder location.
+ * @param {boolean} isTargetLocal - Whether the targetDir is a subfolder in
+ * the Local Folders.
+ */
+ _recursivelyCopyMsgFolder(sourceDir, targetDir, isTargetLocal) {
+ this._logger.debug(`Copying ${sourceDir.path} to ${targetDir.path}`);
+
+ // Copy the whole sourceDir.
+ if (!isTargetLocal && this._items.accounts && this._items.mailMessages) {
+ // Remove the folder so that nsIFile.copyTo doesn't copy into targetDir.
+ targetDir.remove(false);
+ sourceDir.copyTo(targetDir.parent, targetDir.leafName);
+ return;
+ }
+
+ for (let entry of sourceDir.directoryEntries) {
+ if (entry.isDirectory()) {
+ let newFolder = targetDir.clone();
+ newFolder.append(entry.leafName);
+ newFolder.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ this._recursivelyCopyMsgFolder(entry, newFolder);
+ } else {
+ let leafName = entry.leafName;
+ let extName = leafName.slice(leafName.lastIndexOf(".") + 1);
+ if (isTargetLocal) {
+ // When copying to Local Folders, drop database files so that special
+ // folders (Inbox, Trash) become normal folders. Otherwise, imported
+ // special folders can't be deleted.
+ if (extName != "msf") {
+ entry.copyTo(targetDir, leafName);
+ }
+ } else if (
+ this._items.accounts &&
+ extName != leafName &&
+ ["msf", "dat"].includes(extName)
+ ) {
+ // Copy only the folder structure, databases and filter rules.
+ // Ignore the messages themselves.
+ entry.copyTo(targetDir, leafName);
+ }
+ }
+ }
+ }
+
+ /**
+ * Import msg folders from this._sourceProfileDir into the Local Folders of
+ * the current profile.
+ */
+ _importMailMessagesToLocal() {
+ // Make sure Local Folders exist first.
+ if (!this._localServer) {
+ MailServices.accounts.createLocalMailAccount();
+ this._localServer = MailServices.accounts.localFoldersServer;
+ }
+ let localMsgFolder = this._localServer.rootMsgFolder;
+ let localRootDir = this._localServer.rootMsgFolder.filePath;
+ let bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/importMsgs.properties"
+ );
+
+ // Create a "Thunderbird Import" folder, and import into it.
+ let wrapFolderName = localMsgFolder.generateUniqueSubfolderName(
+ bundle.formatStringFromName("ImportModuleFolderName", [this.NAME]),
+ null
+ );
+ localMsgFolder.createSubfolder(wrapFolderName, null);
+ let targetRootMsgFolder = localMsgFolder.getChildNamed(wrapFolderName);
+
+ // Import mail folders.
+ for (let name of ["ImapMail", "News", "Mail"]) {
+ let sourceDir = this._sourceProfileDir.clone();
+ sourceDir.append(name);
+ if (!sourceDir.exists()) {
+ continue;
+ }
+
+ for (let entry of sourceDir.directoryEntries) {
+ if (entry.isDirectory()) {
+ if (name == "Mail" && entry.leafName == "Feeds") {
+ continue;
+ }
+ let targetDir = localRootDir.clone();
+ let folderName = targetRootMsgFolder.generateUniqueSubfolderName(
+ entry.leafName,
+ null
+ );
+ targetRootMsgFolder.createSubfolder(folderName, null);
+ targetDir.append(wrapFolderName + ".sbd");
+ targetDir.append(folderName + ".sbd");
+ targetDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ this._recursivelyCopyMsgFolder(entry, targetDir, true);
+ }
+ }
+ }
+ }
+
+ /**
+ * 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) {
+ let tags = {};
+ for (let [type, name, value] of prefs) {
+ if (name.startsWith("mailnews.tags.")) {
+ let [, , key, attr] = name.split(".");
+ if (!tags[key]) {
+ tags[key] = {};
+ }
+ tags[key][attr] = value;
+ continue;
+ }
+ if (!Services.prefs.prefHasUserValue(name)) {
+ Services.prefs[`set${type}Pref`](name, value);
+ }
+ }
+
+ // Import tags, but do not overwrite existing customized tags.
+ let bundle = Services.strings.createBundle(
+ "chrome://messenger/locale/messenger.properties"
+ );
+ for (let [key, { color, tag }] of Object.entries(tags)) {
+ if (!color || !tag) {
+ continue;
+ }
+ let currentTagColor, currentTagTag;
+ try {
+ currentTagColor = MailServices.tags.getColorForKey(key);
+ currentTagTag = MailServices.tags.getTagForKey(key);
+ } catch (e) {
+ // No tag exists for this key in the current profile, safe to write.
+ Services.prefs.setCharPref(`mailnews.tags.${key}.color`, color);
+ Services.prefs.setCharPref(`mailnews.tags.${key}.tag`, tag);
+ }
+ if (currentTagColor == color && currentTagTag == tag) {
+ continue;
+ }
+ if (
+ ["$label1", "$label2", "$label3", "$label4", "$label5"].includes(key)
+ ) {
+ let seq = key.at(-1);
+ let defaultColor = Services.prefs.getCharPref(
+ `mailnews.labels.color.${seq}`
+ );
+ let defaultTag = bundle.GetStringFromName(
+ `mailnews.labels.description.${seq}`
+ );
+ if (currentTagColor == defaultColor && currentTagTag == defaultTag) {
+ // The existing tag is in default state, safe to write.
+ Services.prefs.setCharPref(`mailnews.tags.${key}.color`, color);
+ Services.prefs.setCharPref(`mailnews.tags.${key}.tag`, tag);
+ }
+ }
+ }
+ }
+
+ /**
+ * 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
+ */
+ async _importAddressBooks(prefs, ldapAutoComplete) {
+ let keyMap = new Map();
+ let branch = Services.prefs.getBranch(ADDRESS_BOOK);
+ for (let [type, name, value] of prefs) {
+ let [key, attr] = name.split(".");
+ if (["pab", "history"].includes(key)) {
+ continue;
+ }
+ if (attr == "uid") {
+ // Prevent duplicated uids when importing back, uid will be created when
+ // first used.
+ 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)}`;
+ if (newName.endsWith(".dirType") && value == 2) {
+ // dirType=2 is a Mab file, we will migrate it in _copyAddressBookDatabases.
+ value = Ci.nsIAbManager.JS_DIRECTORY_TYPE;
+ }
+ 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}`
+ );
+ }
+ }
+
+ await 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.
+ */
+ async _copyAddressBookDatabases(keyMap) {
+ let hasMabFile = false;
+
+ // 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 book file ${sourceFile.path}`
+ );
+ continue;
+ }
+
+ let leafName = sourceFile.leafName;
+ let isMabFile = leafName.endsWith(".mab");
+ if (isMabFile) {
+ leafName = leafName.slice(0, -4) + ".sqlite";
+ hasMabFile = true;
+ }
+ let targetFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ targetFile.append(leafName);
+ targetFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+ branch.setCharPref("filename", targetFile.leafName);
+ this._logger.debug(`Copying ${sourceFile.path} to ${targetFile.path}`);
+ if (isMabFile) {
+ await this._migrateMabToSqlite(sourceFile, targetFile);
+ } else {
+ sourceFile.copyTo(targetFile.parent, targetFile.leafName);
+ // Write-Ahead Logging file contains changes not written to .sqlite file
+ // yet.
+ let sourceWalFile = this._sourceProfileDir.clone();
+ sourceWalFile.append(filename + "-wal");
+ if (sourceWalFile.exists()) {
+ sourceWalFile.copyTo(targetFile.parent, targetFile.leafName + "-wal");
+ }
+ }
+ }
+
+ if (hasMabFile) {
+ await this._importMorkDatabase("abook");
+ await this._importMorkDatabase("history");
+ } else {
+ // Copy or import Personal Address Book.
+ await this._importAddressBookDatabase("abook.sqlite");
+ // Copy or import Collected Addresses.
+ await 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.
+ */
+ async _importAddressBookDatabase(filename) {
+ let sourceFile = this._sourceProfileDir.clone();
+ sourceFile.append(filename);
+ if (!sourceFile.exists()) {
+ return;
+ }
+
+ let targetDirectory = MailServices.ab.getDirectory(
+ `jsaddrbook://${filename}`
+ );
+ if (!targetDirectory) {
+ sourceFile.copyTo(Services.dirsvc.get("ProfD", Ci.nsIFile), "");
+ return;
+ }
+
+ let importer = new AddrBookFileImporter("sqlite");
+ await importer.startImport(sourceFile, targetDirectory);
+ }
+
+ /**
+ * Migrate an address book .mab file to a .sqlite file.
+ *
+ * @param {nsIFile} sourceMabFile - The source .mab file.
+ * @param {nsIFile} targetSqliteFile - The target .sqlite file, should already
+ * exists in the profile dir.
+ */
+ async _migrateMabToSqlite(sourceMabFile, targetSqliteFile) {
+ // It's better to use MailServices.ab.getDirectory, but we need to refresh
+ // AddrBookManager first.
+ let targetDirectory = Cc[
+ "@mozilla.org/addressbook/directory;1?type=jsaddrbook"
+ ].createInstance(Ci.nsIAbDirectory);
+ targetDirectory.init(`jsaddrbook://${targetSqliteFile.leafName}`);
+
+ let importer = new AddrBookFileImporter("mab");
+ await importer.startImport(sourceMabFile, targetDirectory);
+ }
+
+ /**
+ * Import pab/history address book from mab file into the corresponding sqlite
+ * file.
+ *
+ * @param {string} basename - The filename without extension, e.g. "abook".
+ */
+ async _importMorkDatabase(basename) {
+ this._logger.debug(`Importing ${basename}.mab into ${basename}.sqlite`);
+
+ let sourceMabFile = this._sourceProfileDir.clone();
+ sourceMabFile.append(`${basename}.mab`);
+ if (!sourceMabFile.exists()) {
+ return;
+ }
+
+ let targetDirectory;
+ try {
+ targetDirectory = MailServices.ab.getDirectory(
+ `jsaddrbook://${basename}.sqlite`
+ );
+ } catch (e) {
+ this._logger.warn(`Failed to open ${basename}.sqlite`, e);
+ return;
+ }
+
+ let importer = new AddrBookFileImporter("mab");
+ await importer.startImport(sourceMabFile, targetDirectory);
+ }
+
+ /**
+ * 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.
+ * @param {object} calendarList - Pref values of CALENDAR_LIST branch.
+ */
+ _importCalendars(prefs, calendarList) {
+ let branch = Services.prefs.getBranch(CALENDAR);
+ for (let [type, name, value] of prefs) {
+ branch[`set${type}Pref`](name, value);
+ }
+
+ if (calendarList.sortOrder) {
+ let prefName = `${CALENDAR_LIST}sortOrder`;
+ let prefValue =
+ Services.prefs.getCharPref(prefName, "") + " " + calendarList.sortOrder;
+ Services.prefs.setCharPref(prefName, prefValue.trim());
+ }
+ }
+}
diff --git a/comm/mailnews/import/modules/moz.build b/comm/mailnews/import/modules/moz.build
new file mode 100644
index 0000000000..5173f10d46
--- /dev/null
+++ b/comm/mailnews/import/modules/moz.build
@@ -0,0 +1,22 @@
+# 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/.
+
+EXTRA_JS_MODULES += [
+ "AddrBookFileImporter.jsm",
+ "BaseProfileImporter.jsm",
+ "CalendarFileImporter.jsm",
+ "SeamonkeyProfileImporter.jsm",
+ "ThunderbirdProfileImporter.jsm",
+]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ EXTRA_JS_MODULES += [
+ "BeckyProfileImporter.jsm",
+ "OutlookProfileImporter.jsm",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ EXTRA_JS_MODULES += [
+ "AppleMailProfileImporter.jsm",
+ ]
diff --git a/comm/mailnews/import/public/moz.build b/comm/mailnews/import/public/moz.build
new file mode 100644
index 0000000000..1ece3d15f5
--- /dev/null
+++ b/comm/mailnews/import/public/moz.build
@@ -0,0 +1,20 @@
+# 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 += [
+ "nsIImportABDescriptor.idl",
+ "nsIImportABFile.idl",
+ "nsIImportAddressBooks.idl",
+ "nsIImportFieldMap.idl",
+ "nsIImportFilters.idl",
+ "nsIImportGeneric.idl",
+ "nsIImportMail.idl",
+ "nsIImportMailboxDescriptor.idl",
+ "nsIImportModule.idl",
+ "nsIImportService.idl",
+ "nsIImportSettings.idl",
+]
+
+XPIDL_MODULE = "import"
diff --git a/comm/mailnews/import/public/nsIImportABDescriptor.idl b/comm/mailnews/import/public/nsIImportABDescriptor.idl
new file mode 100644
index 0000000000..fe0ca1b313
--- /dev/null
+++ b/comm/mailnews/import/public/nsIImportABDescriptor.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/. */
+
+/*
+
+ Interface for importing mail - ui provided by the import module. If
+ you wish to provide your own UI then implement the nsIImportGeneric
+ interface.
+
+ Can I get an attribute set method to take a const value???
+
+ */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+/**
+ * Implementation Note:
+ *
+ * The default implementation can be obtained from
+ * nsIImportService::CreateNewABDescriptor();
+ *
+ * You should only be interested in using this class if you implement
+ * the nsIImportAddressBooks interface in which case, just using the service to
+ * create new ones should work fine for you. If not, implement your
+ * own.
+ */
+[scriptable, uuid(2d8983b2-cea6-4ae2-9145-eb772481fa18)]
+interface nsIImportABDescriptor : nsISupports
+{
+ /**
+ * use the following 2 attributes however you'd like to
+ * refer to a specific address book
+ */
+ attribute unsigned long identifier;
+ attribute unsigned long ref;
+
+ /**
+ * Doesn't have to be accurate, this is merely used to report progress.
+ * If you're importing a file, using file size and reporting progress
+ * as the number of bytes processed so far makes sense. For other formats
+ * returning the number of records may make more sense.
+ */
+ attribute unsigned long size;
+
+ /**
+ * The preferred name for this address book. Depending upon how the
+ * user selected import, the caller of the nsIImportAddressBooks interface
+ * may use this name to create the destination address book or it may
+ * ignore it. However, this must be provided in all cases as it is
+ * also displayed in the UI to the user.
+ */
+ attribute AString preferredName;
+
+ /**
+ * For address books that want a file descriptor to locate the address book.
+ * For formats that do not, use identifier & ref to refer to the address book
+ * OR implement your own nsIImportABDescriptor that contains additional data
+ * necessary to identify specific address books,
+ */
+ attribute nsIFile abFile;
+
+ /**
+ * Set by the UI to indicate whether or not this address book should be imported.
+ */
+ attribute boolean import;
+};
diff --git a/comm/mailnews/import/public/nsIImportABFile.idl b/comm/mailnews/import/public/nsIImportABFile.idl
new file mode 100644
index 0000000000..b6e5334f0a
--- /dev/null
+++ b/comm/mailnews/import/public/nsIImportABFile.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/**
+ * General interface for importing from a file to an address book.
+ */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIAbDirectory;
+
+[scriptable, uuid(265209a6-9e1c-4910-97d4-d5ee3f13479b)]
+interface nsIImportABFile : nsISupports {
+ /**
+ * Import entries from a file into an address book directory.
+ * @param sourceFile - The source file.
+ * @param targetDirectory - The target address book directory.
+ */
+ void readFileToDirectory(in nsIFile sourceFile,
+ in nsIAbDirectory targetDirectory);
+};
diff --git a/comm/mailnews/import/public/nsIImportAddressBooks.idl b/comm/mailnews/import/public/nsIImportAddressBooks.idl
new file mode 100644
index 0000000000..1b5e1b8e38
--- /dev/null
+++ b/comm/mailnews/import/public/nsIImportAddressBooks.idl
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/**
+ * Interface for importing address books using the standard UI. Address book
+ * import occurs in several forms (yuck!).
+ * The destination can be 1..n new address books corresponding to the source
+ * format. For instance a text file would import into a new address book with
+ * the same name as the text file.
+ * The destination can be 1 pre-defined address book, all entries will be added
+ * to the supplied address book - this allows the address book UI so provide an
+ * import command specific for an individual address book.
+ *
+ * The source can import 1 or multiple address books.
+ * The address books can be auto-discoverable or user specified.
+ * The address books can require field mapping or not.
+ *
+ * All of this is rather complicated but it should work out OK.
+ * 1) The first UI panel will allow selection of the address book and will
+ * indicate to the user if the address book will be imported into an
+ * existing address book or new address books. (This could be 2 separate xul
+ * UI's?).
+ * 2) The second panel will show field mapping if it is required - if it is
+ * required then there will be one panel per address book for formats that
+ * support multiple address books. If it is not required then there will be
+ * no second panel.
+ * 3) Show the progress dialog for the import - this could be per address book
+ * if mapping is required? what to do, what to doooooo.....
+ * 4) All done, maybe a what was done panel??
+ */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIImportABDescriptor;
+interface nsIAbDirectory;
+interface nsIImportFieldMap;
+
+[scriptable, uuid(6bba48be-331c-41e3-bc9f-c2ea3754d977)]
+interface nsIImportAddressBooks : nsISupports {
+ /**
+ * Does this interface supports 1 or 1..n address books. You only get to
+ * choose 1 location so for formats where 1..n address books are imported
+ * from a directory, then return true. For a 1 to 1 relationship between
+ * location and address books return false.
+ */
+ boolean GetSupportsMultiple();
+
+ /**
+ * If the address book is not found via a file location, then return true
+ * along with a description string of how or where the address book is
+ * located. If it is a file location then return false.
+ * If true, return a string like: "Outlook address book".
+ * If false, GetDefaultLocation will be called.
+ */
+ boolean GetAutoFind(out wstring description);
+
+ /**
+ * Returns true if the address book needs the user to specify a field map for
+ * address books imported from this format.
+ */
+ boolean GetNeedsFieldMap(in nsIFile location);
+
+ /**
+ * If found and userVerify BOTH return false, then it is assumed that this
+ * means an error - address book cannot be found on this machine.
+ * If userVerify is true, the user will have an opportunity to specify a
+ * different location to import address book from.
+ */
+ void GetDefaultLocation(out nsIFile location,
+ out boolean found,
+ out boolean userVerify);
+ /**
+ * Returns an array containing an nsIImportABDescriptor for each
+ * address book. The array is not sorted before display to the user.
+ * location is null if GetAutoFind returned true.
+ */
+ Array<nsIImportABDescriptor> findAddressBooks(in nsIFile location);
+
+ /**
+ * Fill in defaults (if any) for a field map for importing address books from
+ * this location.
+ */
+ void InitFieldMap(in nsIImportFieldMap fieldMap);
+
+ /**
+ * Import a specific address book into the destination file supplied.
+ * If an error occurs that is non-fatal, the destination will be deleted and
+ * other address book will be imported. If a fatal error occurs, the
+ * destination will be deleted and the import operation will abort.
+ *
+ * @param aSource The source data for the import.
+ * @param aDestination The proxy database for the destination of the
+ * import.
+ * @param aFieldMap The field map containing the mapping of fields to
+ * be used in cvs and tab type imports.
+ * @param aSupportService An optional proxy support service (nullptr is
+ * acceptable if it is not required), may be required
+ * for certain import types (e.g. nsIAbLDIFService for
+ * LDIF import).
+ * @param aErrorLog The error log from the import.
+ * @param aSuccessLog The success log from the import.
+ * @param aFatalError True if there was a fatal error doing the import.
+ */
+ void ImportAddressBook(in nsIImportABDescriptor aSource,
+ in nsIAbDirectory aDestination,
+ in nsIImportFieldMap aFieldMap,
+ in nsISupports aSupportService,
+ out wstring aErrorLog,
+ out wstring aSuccessLog,
+ out boolean aFatalError);
+
+ /**
+ * Return the amount of the address book that has been imported so far. This
+ * number is used to present progress information and must never be larger
+ * than the size specified in nsIImportABDescriptor.GetSize(); May be called
+ * from a different thread than ImportAddressBook()
+ */
+ unsigned long GetImportProgress();
+
+ /**
+ * Set the location for reading sample data, this should be the same as what
+ * is passed later to FindAddressBooks.
+ */
+ void SetSampleLocation(in nsIFile location);
+
+ /**
+ * Return a string of sample data for a record, each field is separated by a
+ * newline (which means no newlines in the fields!)
+ * This is only supported by address books which use field maps and is used
+ * by the field map UI to allow the user to properly align fields to be
+ * imported.
+ *
+ * @param recordNumber index of the recrds, starting from 0.
+ * @param recordExists true if the record exists.
+ *
+ * @returns a string of sample data for the desired record
+ */
+ wstring GetSampleData(in long recordNumber, out boolean recordExists);
+};
diff --git a/comm/mailnews/import/public/nsIImportFieldMap.idl b/comm/mailnews/import/public/nsIImportFieldMap.idl
new file mode 100644
index 0000000000..83dc0ddfb1
--- /dev/null
+++ b/comm/mailnews/import/public/nsIImportFieldMap.idl
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/*
+ Field map interface for importing address books
+
+ A field map is an arbitrary sized list of mozilla address book fields.
+ The field map is used by import to map fields from the import format
+ to mozilla fields.
+ For export, the map contains the ordered list of mozilla fields to
+ export!
+*/
+
+#include "nsISupports.idl"
+
+interface nsIAbCard;
+interface nsIAbDirectory;
+
+[scriptable, uuid(deee9264-1fe3-47b1-b745-47b22de454e2)]
+interface nsIImportFieldMap : nsISupports
+{
+ /*
+ Flag to indicate whether or not to skip the first record,
+ for instance csv files often have field names as the first
+ record
+ */
+ attribute boolean skipFirstRecord;
+
+ readonly attribute long numMozFields;
+ readonly attribute long mapSize;
+
+ wstring GetFieldDescription(in long index);
+
+ /*
+ Set the size of the field map, all unpopulated entries
+ will default to -1
+ */
+ void SetFieldMapSize(in long size);
+
+ /*
+ Initialize the field map to a given size with default values
+ */
+ void DefaultFieldMap(in long size);
+
+ /*
+ Return the field number that this index maps to, -1 for no field
+ */
+ long GetFieldMap(in long index);
+
+ /*
+ Set the field that this index maps to, -1 for no field
+ */
+ void SetFieldMap(in long index, in long fieldNum);
+
+ /*
+ Return if this field is "active" in the map.
+ */
+ boolean GetFieldActive(in long index);
+
+ /*
+ Set the active state of this field
+ */
+ void SetFieldActive(in long index, in boolean active);
+
+ /*
+ Set the value of the given field in the database row
+ */
+ void SetFieldValue(in nsIAbDirectory database, in nsIAbCard row, in long fieldNum, in AString value);
+};
diff --git a/comm/mailnews/import/public/nsIImportFilters.idl b/comm/mailnews/import/public/nsIImportFilters.idl
new file mode 100644
index 0000000000..cac242e827
--- /dev/null
+++ b/comm/mailnews/import/public/nsIImportFilters.idl
@@ -0,0 +1,25 @@
+/* -*- 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/. */
+
+/*
+ Interface for importing filters.
+*/
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+[scriptable, uuid(f2680ccf-d110-4b5b-954d-e072d4a16129)]
+interface nsIImportFilters : nsISupports
+{
+ boolean AutoLocate(out wstring aDescription, out nsIFile aLocation);
+
+ void SetLocation(in nsIFile aLocation);
+
+ /*
+ Import filters and put any problems in the error out parameter.
+ */
+ boolean Import(out wstring aError);
+};
diff --git a/comm/mailnews/import/public/nsIImportGeneric.idl b/comm/mailnews/import/public/nsIImportGeneric.idl
new file mode 100644
index 0000000000..eab152973b
--- /dev/null
+++ b/comm/mailnews/import/public/nsIImportGeneric.idl
@@ -0,0 +1,81 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/*
+ Interface for importing anything. You are responsible for opening
+ up UI and doing all of the work to make it happen.
+
+*/
+
+#include "nsISupports.idl"
+
+interface nsISupportsString;
+
+[scriptable, uuid(469d7d5f-144c-4f07-9661-e49e40156348)]
+interface nsIImportGeneric : nsISupports
+{
+ /* Use these to prepare for the import */
+ /*
+ "mailInterface" - nsIImportMail interface
+ "mailLocation" - nsIFile, source location for mail
+
+ "addressInterface" - nsIImportAddressBooks interface
+ "addressLocation" - src location of address books (if needed!)
+ "addressDestination" - uri of destination address book or null if
+ new address books will be created.
+ */
+ nsISupports GetData(in string dataId);
+
+ void SetData(in string dataId, in nsISupports pData);
+
+ /*
+ "isInstalled" - if true then mail can be automatically located.
+ "canUserSetLocation" - if true then the user can specify the location
+ to look for mail. If both are false, then there is no way
+ to import mail from this format!
+ TBD: How to specify whether or not a file or a directory
+ should be specified?
+ "autoFind" - for address books, is the address book located without
+ using the file system - i.e. addressLocation is irrelevant.
+ "supportsMultiple" - 1 or 1..n address books are imported by this format?
+
+ */
+ long GetStatus(in string statusKind);
+
+ /*
+ When you are ready to import call this. If it returns TRUE then
+ you must call BeginImport and then repeatedly call GetProgress until
+ it returns 100 % done or until ContinueImport returns FALSE.
+ If this returns FALSE then BeginImport will begin and finish the import
+ before it returns.
+ */
+ boolean WantsProgress();
+
+ /* Use these for the actual import */
+ /* Begin import is expected to start a new thread UNLESS WantsProgress returned
+ FALSE. It is REQUIRED to call WantsProgress before calling BeginImport.
+ If WantsProgress was false then this will return the success or
+ failure of the import. Failure can be reported even if WantsProgress
+ returned TRUE.
+ */
+ boolean BeginImport(in nsISupportsString successLog,
+ in nsISupportsString errorLog);
+ /*
+ If WantsProgress returned TRUE then this will indicate if the import should
+ continue. If this returns FALSE then no other methods should be called
+ and the error log should be shown to the user.
+ */
+ boolean ContinueImport();
+ /*
+ Returns the percentage done. When this returns 100 then the import is done.
+ (only valid if WantsProgress returned true)
+ */
+ long GetProgress();
+ /*
+ Cancel an import in progress. Again, this is only valid if WantsProgress
+ returned true.
+ */
+ void CancelImport();
+};
diff --git a/comm/mailnews/import/public/nsIImportMail.idl b/comm/mailnews/import/public/nsIImportMail.idl
new file mode 100644
index 0000000000..336fede8f4
--- /dev/null
+++ b/comm/mailnews/import/public/nsIImportMail.idl
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/*
+
+ Interface for importing mail - ui provided by the import module. If
+ you wish to provide your own UI then implement the nsIImportGeneric
+ interface.
+
+*/
+
+/*
+ If you support this interface then the standard mailbox import UI
+ can be used to drive your import of mailboxes, which means you don't have
+ to worry about anything other than implementing this interface
+ (and nsIImportModule) to import mailboxes.
+*/
+
+/*
+ The general process is:
+ 1) Do you know where the mail is located
+ 2) Do you want the user to "verify" this location and have
+ the option of specifying a different mail directory?
+ 3) Given a directory (either specified in 1 or 2) build a list
+ of all of the mailboxes to be imported.
+ 4) Import each mail box to the destination provided!
+ 5) Update the portion of the mailbox imported so far. This should
+ always be less than the mailbox size until you are done. This
+ is used for progress bar updating and MAY BE CALLED FROM ANOTHER
+ THREAD!
+
+*/
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+interface nsIImportMailboxDescriptor;
+interface nsIMsgFolder;
+
+[scriptable, uuid(a14a3308-0849-420b-86d3-13a2948b5504)]
+interface nsIImportMail : nsISupports
+{
+ /*
+ If found and userVerify BOTH return false, then it is assumed that this
+ means an error - mail cannot be found on this machine.
+ If userVerify is true, the user will have an opportunity to specify
+ a different location to import mail from.
+ */
+ void GetDefaultLocation(out nsIFile location,
+ out boolean found,
+ out boolean userVerify);
+ /*
+ Returns an array which contains an nsIImportMailboxDescriptor for each
+ mailbox. The array is not sorted before display to the user.
+ */
+ Array<nsIImportMailboxDescriptor> findMailboxes(in nsIFile location);
+
+ /*
+ Import a specific mailbox into the destination folder supplied. If an error
+ occurs that is non-fatal, the destination will be deleted and other mailboxes
+ will be imported. If a fatal error occurs, the destination will be deleted
+ and the import operation will abort.
+ */
+ void ImportMailbox(in nsIImportMailboxDescriptor source,
+ in nsIMsgFolder dstFolder,
+ out wstring errorLog,
+ out wstring successLog,
+ out boolean fatalError);
+
+ /*
+ Return the amount of the mailbox that has been imported so far. This number
+ is used to present progress information and must never be larger than the
+ size specified in nsIImportMailboxID.GetSize(); May be called from
+ a different thread than ImportMailbox()
+ */
+ unsigned long GetImportProgress();
+
+ /*
+ * When migrating the local folders from the import source into mozilla,
+ * we want to translate reserved folder names from the import source to
+ * equivalent values for Mozilla.
+ * Localization Impact is unknown here.
+ */
+ AString translateFolderName(in AString aFolderName);
+};
+
+%{ C++
+#define kDestTrashFolderName "Trash"
+#define kDestUnsentMessagesFolderName "Unsent Messages"
+#define kDestSentFolderName "Sent"
+#define kDestInboxFolderName "Inbox"
+%}
diff --git a/comm/mailnews/import/public/nsIImportMailboxDescriptor.idl b/comm/mailnews/import/public/nsIImportMailboxDescriptor.idl
new file mode 100644
index 0000000000..f0da7f7f79
--- /dev/null
+++ b/comm/mailnews/import/public/nsIImportMailboxDescriptor.idl
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/*
+
+ Interface for importing mail - ui provided by the import module. If
+ you wish to provide your own UI then implement the nsIImportGeneric
+ interface.
+
+ */
+
+#include "nsISupports.idl"
+
+interface nsIFile;
+
+[scriptable, uuid(69eba744-9c4f-4f79-a964-2134746b3656)]
+interface nsIImportMailboxDescriptor : nsISupports
+{
+ attribute unsigned long identifier;
+ attribute unsigned long depth;
+ attribute unsigned long size;
+
+ wstring GetDisplayName();
+ void SetDisplayName([const] in wstring name);
+
+ attribute boolean import;
+ readonly attribute nsIFile file;
+};
+
+%{ C++
+
+/*
+ The default implementation can be obtained from
+ nsIImportService::CreateNewMailboxDescriptor();
+
+ You should only be interested in using this class if you implement
+ the nsIImportMail interface in which case, just using the service to
+ create new ones should work fine for you. If not, implement your
+ own.
+*/
+
+%}
diff --git a/comm/mailnews/import/public/nsIImportModule.idl b/comm/mailnews/import/public/nsIImportModule.idl
new file mode 100644
index 0000000000..00eb10601c
--- /dev/null
+++ b/comm/mailnews/import/public/nsIImportModule.idl
@@ -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/. */
+
+/*
+
+ An import module.
+
+ */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(624f0280-173f-11d3-a206-00a0cc26da63)]
+interface nsIImportModule : nsISupports
+{
+ readonly attribute wstring name;
+ readonly attribute wstring description;
+ readonly attribute string supports;
+ readonly attribute boolean supportsUpgrade;
+
+ nsISupports GetImportInterface( in string importType);
+};
+
+%{ C++
+#define NS_IMPORT_MAIL_STR "mail"
+#define NS_IMPORT_ADDRESS_STR "addressbook"
+#define NS_IMPORT_SETTINGS_STR "settings"
+#define NS_IMPORT_FILTERS_STR "filters"
+%}
diff --git a/comm/mailnews/import/public/nsIImportService.idl b/comm/mailnews/import/public/nsIImportService.idl
new file mode 100644
index 0000000000..1cfaf7fbaa
--- /dev/null
+++ b/comm/mailnews/import/public/nsIImportService.idl
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/*
+
+ The import service.
+
+ */
+
+#include "nsISupports.idl"
+
+interface nsIImportModule;
+interface nsIImportMailboxDescriptor;
+interface nsIImportABDescriptor;
+interface nsIImportGeneric;
+interface nsIImportFieldMap;
+interface nsIMsgSendListener;
+interface nsIMsgIdentity;
+interface nsIMsgCompFields;
+interface nsIMsgAttachedFile;
+interface nsIMsgEmbeddedImageData;
+
+[scriptable, uuid(d0ed4c50-5997-49c9-8a6a-045f0680ed29)]
+interface nsIImportService : nsISupports
+{
+ void DiscoverModules();
+
+ long GetModuleCount(in string filter);
+ void GetModuleInfo(in string filter, in long index, out AString name, out AString description);
+ AString GetModuleName(in string filter, in long index);
+ AString GetModuleDescription(in string filter, in long index);
+ nsIImportModule GetModule(in string filter, in long index);
+
+ nsIImportFieldMap CreateNewFieldMap();
+ nsIImportMailboxDescriptor CreateNewMailboxDescriptor();
+ nsIImportABDescriptor CreateNewABDescriptor();
+ nsIImportGeneric CreateNewGenericMail();
+ nsIImportGeneric CreateNewGenericAddressBooks();
+ void CreateRFC822Message(in nsIMsgIdentity aIdentity,
+ in nsIMsgCompFields aMsgFields,
+ in string aBodytype,
+ in ACString aBody,
+ in boolean aCreateAsDraft,
+ in Array<nsIMsgAttachedFile> aLoadedAttachments,
+ in Array<nsIMsgEmbeddedImageData> aEmbeddedObjects,
+ in nsIMsgSendListener aListener);
+
+};
+
+%{ C++
+#define NS_IMPORTSERVICE_CID \
+{ /* 5df96d60-1726-11d3-a206-00a0cc26da63 */ \
+ 0x5df96d60, 0x1726, 0x11d3, \
+ {0xa2, 0x06, 0x0, 0xa0, 0xcc, 0x26, 0xda, 0x63}}
+
+#define NS_IMPORTSERVICE_CONTRACTID "@mozilla.org/import/import-service;1"
+%}
diff --git a/comm/mailnews/import/public/nsIImportSettings.idl b/comm/mailnews/import/public/nsIImportSettings.idl
new file mode 100644
index 0000000000..b9a123bfc6
--- /dev/null
+++ b/comm/mailnews/import/public/nsIImportSettings.idl
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+/*
+ Interface for importing settings. Settings can be auto-located or
+ specified by a specific file. Depends upon the app that the settings
+ are coming from.
+
+*/
+
+#include "nsISupports.idl"
+
+interface nsIMsgAccount;
+interface nsIFile;
+
+[scriptable, uuid(1c0e3012-bc4d-4fb2-be6a-0335c7bab9ac)]
+interface nsIImportSettings : nsISupports
+{
+ boolean AutoLocate(out wstring description, out nsIFile location);
+
+ void SetLocation(in nsIFile location);
+
+ /*
+ Create all of the accounts, identities, and servers. Return an
+ account where any local mail from this app should be imported.
+ The returned account can be null which indicates that no suitable
+ account for local mail was created and a new account specifically for
+ the imported mail should be created.
+ */
+ boolean Import(out nsIMsgAccount localMailAccount);
+};
diff --git a/comm/mailnews/import/src/ImportCharSet.cpp b/comm/mailnews/import/src/ImportCharSet.cpp
new file mode 100644
index 0000000000..ea79221210
--- /dev/null
+++ b/comm/mailnews/import/src/ImportCharSet.cpp
@@ -0,0 +1,58 @@
+/* -*- 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 "ImportCharSet.h"
+
+char ImportCharSet::m_upperCaseMap[256];
+char ImportCharSet::m_Ascii[256] = {0}; // the initialiser makes it strong
+
+class UInitMaps {
+ public:
+ UInitMaps();
+};
+
+UInitMaps gInitMaps;
+
+UInitMaps::UInitMaps() {
+ int i;
+
+ for (i = 0; i < 256; i++) ImportCharSet::m_upperCaseMap[i] = i;
+ for (i = 'a'; i <= 'z'; i++) ImportCharSet::m_upperCaseMap[i] = i - 'a' + 'A';
+
+ for (i = 0; i < 256; i++) ImportCharSet::m_Ascii[i] = 0;
+
+ for (i = ImportCharSet::cUpperAChar; i <= ImportCharSet::cUpperZChar; i++)
+ ImportCharSet::m_Ascii[i] |=
+ (ImportCharSet::cAlphaNumChar | ImportCharSet::cAlphaChar);
+ for (i = ImportCharSet::cLowerAChar; i <= ImportCharSet::cLowerZChar; i++)
+ ImportCharSet::m_Ascii[i] |=
+ (ImportCharSet::cAlphaNumChar | ImportCharSet::cAlphaChar);
+ for (i = ImportCharSet::cZeroChar; i <= ImportCharSet::cNineChar; i++)
+ ImportCharSet::m_Ascii[i] |=
+ (ImportCharSet::cAlphaNumChar | ImportCharSet::cDigitChar);
+
+ ImportCharSet::m_Ascii[ImportCharSet::cTabChar] |=
+ ImportCharSet::cWhiteSpaceChar;
+ ImportCharSet::m_Ascii[ImportCharSet::cCRChar] |=
+ ImportCharSet::cWhiteSpaceChar;
+ ImportCharSet::m_Ascii[ImportCharSet::cLinefeedChar] |=
+ ImportCharSet::cWhiteSpaceChar;
+ ImportCharSet::m_Ascii[ImportCharSet::cSpaceChar] |=
+ ImportCharSet::cWhiteSpaceChar;
+
+ ImportCharSet::m_Ascii[uint8_t('(')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t(')')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t('<')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t('>')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t('@')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t(',')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t(';')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t(':')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t('\\')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t('"')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t('.')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t('[')] |= ImportCharSet::c822SpecialChar;
+ ImportCharSet::m_Ascii[uint8_t(']')] |= ImportCharSet::c822SpecialChar;
+}
diff --git a/comm/mailnews/import/src/ImportCharSet.h b/comm/mailnews/import/src/ImportCharSet.h
new file mode 100644
index 0000000000..0feb8d2a98
--- /dev/null
+++ b/comm/mailnews/import/src/ImportCharSet.h
@@ -0,0 +1,201 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 ImportCharSet_h___
+#define ImportCharSet_h___
+
+#include "nscore.h"
+
+// Some useful ASCII values
+// 'A' = 65, 0x41
+// 'Z' = 90, 0x5a
+// '_' = 95, 0x5f
+// 'a' = 97, 0x61
+// 'z' = 122, 0x7a
+// '0' = 48, 0x30
+// '1' = 49, 0x31
+// '9' = 57, 0x39
+// ' ' = 32, 0x20
+// whitespace, 10, 13, 32, 9 (linefeed, cr, space, tab) - 0x0a, 0x0d, 0x20,
+// 0x09
+// ':' = 58, 0x3a
+
+// a typedef enum would be nicer but some compilers still have trouble with
+// treating enum's as plain numbers when needed
+
+class ImportCharSet {
+ public:
+ enum {
+ cTabChar = 9,
+ cLinefeedChar = 10,
+ cCRChar = 13,
+ cSpaceChar = 32,
+ cUpperAChar = 65,
+ cUpperZChar = 90,
+ cUnderscoreChar = 95,
+ cLowerAChar = 97,
+ cLowerZChar = 122,
+ cZeroChar = 48,
+ cNineChar = 57,
+
+ cAlphaNumChar = 1,
+ cAlphaChar = 2,
+ cWhiteSpaceChar = 4,
+ cDigitChar = 8,
+ c822SpecialChar = 16
+ };
+
+ static char m_upperCaseMap[256];
+ static char m_Ascii[256];
+
+ inline static bool IsUSAscii(uint8_t ch) {
+ return (((ch & (uint8_t)0x80) == 0));
+ }
+ inline static bool Is822CtlChar(uint8_t ch) { return (ch < 32); }
+ inline static bool Is822SpecialChar(uint8_t ch) {
+ return ((m_Ascii[ch] & c822SpecialChar) == c822SpecialChar);
+ }
+ inline static bool IsWhiteSpace(uint8_t ch) {
+ return ((m_Ascii[ch] & cWhiteSpaceChar) == cWhiteSpaceChar);
+ }
+ inline static bool IsAlphaNum(uint8_t ch) {
+ return ((m_Ascii[ch] & cAlphaNumChar) == cAlphaNumChar);
+ }
+ inline static bool IsDigit(uint8_t ch) {
+ return ((m_Ascii[ch] & cDigitChar) == cDigitChar);
+ }
+
+ inline static uint8_t ToLower(uint8_t ch) {
+ if ((m_Ascii[ch] & cAlphaChar) == cAlphaChar) {
+ return cLowerAChar + (m_upperCaseMap[ch] - cUpperAChar);
+ } else
+ return ch;
+ }
+
+ inline static long AsciiToLong(const uint8_t* pChar, uint32_t len) {
+ long num = 0;
+ while (len) {
+ if ((m_Ascii[*pChar] & cDigitChar) == 0) return num;
+ num *= 10;
+ num += (*pChar - cZeroChar);
+ len--;
+ pChar++;
+ }
+ return num;
+ }
+
+ inline static void ByteToHex(uint8_t byte, uint8_t* pHex) {
+ uint8_t val = byte;
+ val /= 16;
+ if (val < 10)
+ *pHex = '0' + val;
+ else
+ *pHex = 'A' + (val - 10);
+ pHex++;
+ val = byte;
+ val &= 0x0F;
+ if (val < 10)
+ *pHex = '0' + val;
+ else
+ *pHex = 'A' + (val - 10);
+ }
+
+ inline static void LongToHexBytes(uint32_t type, uint8_t* pStr) {
+ ByteToHex((uint8_t)(type >> 24), pStr);
+ pStr += 2;
+ ByteToHex((uint8_t)((type >> 16) & 0x0FF), pStr);
+ pStr += 2;
+ ByteToHex((uint8_t)((type >> 8) & 0x0FF), pStr);
+ pStr += 2;
+ ByteToHex((uint8_t)(type & 0x0FF), pStr);
+ }
+
+ inline static void SkipWhiteSpace(const uint8_t*& pChar, uint32_t& pos,
+ uint32_t max) {
+ while ((pos < max) && (IsWhiteSpace(*pChar))) {
+ pos++;
+ pChar++;
+ }
+ }
+
+ inline static void SkipSpaceTab(const uint8_t*& pChar, uint32_t& pos,
+ uint32_t max) {
+ while ((pos < max) &&
+ ((*pChar == (uint8_t)cSpaceChar) || (*pChar == (uint8_t)cTabChar))) {
+ pos++;
+ pChar++;
+ }
+ }
+
+ inline static void SkipTilSpaceTab(const uint8_t*& pChar, uint32_t& pos,
+ uint32_t max) {
+ while ((pos < max) && (*pChar != (uint8_t)cSpaceChar) &&
+ (*pChar != (uint8_t)cTabChar)) {
+ pos++;
+ pChar++;
+ }
+ }
+
+ inline static bool StrNICmp(const uint8_t* pChar, const uint8_t* pSrc,
+ uint32_t len) {
+ while (len && (m_upperCaseMap[*pChar] == m_upperCaseMap[*pSrc])) {
+ pChar++;
+ pSrc++;
+ len--;
+ }
+ return len == 0;
+ }
+
+ inline static bool StrNCmp(const uint8_t* pChar, const uint8_t* pSrc,
+ uint32_t len) {
+ while (len && (*pChar == *pSrc)) {
+ pChar++;
+ pSrc++;
+ len--;
+ }
+ return len == 0;
+ }
+
+ inline static int FindChar(const uint8_t* pChar, uint8_t ch, uint32_t max) {
+ uint32_t pos = 0;
+ while ((pos < max) && (*pChar != ch)) {
+ pos++;
+ pChar++;
+ }
+ if (pos < max)
+ return (int)pos;
+ else
+ return -1;
+ }
+
+ inline static bool NextChar(const uint8_t*& pChar, uint8_t ch, uint32_t& pos,
+ uint32_t max) {
+ if ((pos < max) && (*pChar == ch)) {
+ pos++;
+ pChar++;
+ return true;
+ }
+ return false;
+ }
+
+ inline static int32_t strcmp(const char* pS1, const char* pS2) {
+ while (*pS1 && *pS2 && (*pS1 == *pS2)) {
+ pS1++;
+ pS2++;
+ }
+ return *pS1 - *pS2;
+ }
+
+ inline static int32_t stricmp(const char* pS1, const char* pS2) {
+ while (*pS1 && *pS2 &&
+ (m_upperCaseMap[uint8_t(*pS1)] == m_upperCaseMap[uint8_t(*pS2)])) {
+ pS1++;
+ pS2++;
+ }
+ return m_upperCaseMap[uint8_t(*pS1)] - m_upperCaseMap[uint8_t(*pS2)];
+ }
+};
+
+#endif /* ImportCharSet_h__ */
diff --git a/comm/mailnews/import/src/ImportDebug.h b/comm/mailnews/import/src/ImportDebug.h
new file mode 100644
index 0000000000..f1698ed442
--- /dev/null
+++ b/comm/mailnews/import/src/ImportDebug.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 4; 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 ImportDebug_h___
+#define ImportDebug_h___
+
+#ifdef NS_DEBUG
+# define IMPORT_DEBUG 1
+#endif
+
+#include "mozilla/Logging.h"
+extern mozilla::LazyLogModule
+ IMPORTLOGMODULE; // defined in nsImportService.cpp
+
+#define IMPORT_LOG0(x) MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (x))
+#define IMPORT_LOG1(x, y) \
+ MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (x, y))
+#define IMPORT_LOG2(x, y, z) \
+ MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (x, y, z))
+#define IMPORT_LOG3(a, b, c, d) \
+ MOZ_LOG(IMPORTLOGMODULE, mozilla::LogLevel::Debug, (a, b, c, d))
+
+#endif
diff --git a/comm/mailnews/import/src/ImportOutFile.cpp b/comm/mailnews/import/src/ImportOutFile.cpp
new file mode 100644
index 0000000000..3622b56ad7
--- /dev/null
+++ b/comm/mailnews/import/src/ImportOutFile.cpp
@@ -0,0 +1,257 @@
+/* -*- 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 "nscore.h"
+#include "nsString.h"
+#include "prio.h"
+#include "nsNetUtil.h"
+#include "nsISeekableStream.h"
+#include "nsMsgUtils.h"
+#include "ImportOutFile.h"
+#include "ImportCharSet.h"
+
+#include "ImportDebug.h"
+
+/*
+#ifdef _MAC
+#define kMacNoCreator '????'
+#define kMacTextFile 'TEXT'
+#else
+#define kMacNoCreator 0
+#define kMacTextFile 0
+#endif
+*/
+
+ImportOutFile::ImportOutFile() {
+ m_ownsFileAndBuffer = false;
+ m_pos = 0;
+ m_pBuf = nullptr;
+ m_bufSz = 0;
+ m_pTrans = nullptr;
+ m_pTransOut = nullptr;
+ m_pTransBuf = nullptr;
+}
+
+ImportOutFile::ImportOutFile(nsIFile* pFile, uint8_t* pBuf, uint32_t sz) {
+ m_pTransBuf = nullptr;
+ m_pTransOut = nullptr;
+ m_pTrans = nullptr;
+ m_ownsFileAndBuffer = false;
+ InitOutFile(pFile, pBuf, sz);
+}
+
+ImportOutFile::~ImportOutFile() {
+ if (m_ownsFileAndBuffer) {
+ Flush();
+ delete[] m_pBuf;
+ }
+
+ delete m_pTrans;
+ delete m_pTransOut;
+ delete[] m_pTransBuf;
+}
+
+bool ImportOutFile::Set8bitTranslator(nsImportTranslator* pTrans) {
+ if (!Flush()) return false;
+
+ m_engaged = false;
+ m_pTrans = pTrans;
+ m_supports8to7 = pTrans->Supports8bitEncoding();
+
+ return true;
+}
+
+bool ImportOutFile::End8bitTranslation(bool* pEngaged, nsCString& useCharset,
+ nsCString& encoding) {
+ if (!m_pTrans) return false;
+
+ bool bResult = Flush();
+ if (m_supports8to7 && m_pTransOut) {
+ if (bResult) bResult = m_pTrans->FinishConvertToFile(m_pTransOut);
+ if (bResult) bResult = Flush();
+ }
+
+ if (m_supports8to7) {
+ m_pTrans->GetCharset(useCharset);
+ m_pTrans->GetEncoding(encoding);
+ } else
+ useCharset.Truncate();
+ *pEngaged = m_engaged;
+ delete m_pTrans;
+ m_pTrans = nullptr;
+ delete m_pTransOut;
+ m_pTransOut = nullptr;
+ delete[] m_pTransBuf;
+ m_pTransBuf = nullptr;
+
+ return bResult;
+}
+
+bool ImportOutFile::InitOutFile(nsIFile* pFile, uint32_t bufSz) {
+ if (!bufSz) bufSz = 32 * 1024;
+ if (!m_pBuf) m_pBuf = new uint8_t[bufSz];
+
+ if (!m_outputStream) {
+ nsresult rv;
+ rv = MsgNewBufferedFileOutputStream(
+ getter_AddRefs(m_outputStream), pFile,
+ PR_CREATE_FILE | PR_WRONLY | PR_TRUNCATE, 0644);
+
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("Couldn't create outfile\n");
+ delete[] m_pBuf;
+ m_pBuf = nullptr;
+ return false;
+ }
+ }
+ m_pFile = pFile;
+ m_ownsFileAndBuffer = true;
+ m_pos = 0;
+ m_bufSz = bufSz;
+ return true;
+}
+
+void ImportOutFile::InitOutFile(nsIFile* pFile, uint8_t* pBuf, uint32_t sz) {
+ m_ownsFileAndBuffer = false;
+ m_pFile = pFile;
+ m_pBuf = pBuf;
+ m_bufSz = sz;
+ m_pos = 0;
+}
+
+bool ImportOutFile::Flush(void) {
+ if (!m_pos) return true;
+
+ uint32_t transLen;
+ bool duddleyDoWrite = false;
+
+ // handle translations if appropriate
+ if (m_pTrans) {
+ if (m_engaged && m_supports8to7) {
+ // Markers can get confused by this crap!!!
+ // TLR: FIXME: Need to update the markers based on
+ // the difference between the translated len and untranslated len
+
+ if (!m_pTrans->ConvertToFile(m_pBuf, m_pos, m_pTransOut, &transLen))
+ return false;
+ if (!m_pTransOut->Flush()) return false;
+ // now update our buffer...
+ if (transLen < m_pos) {
+ memcpy(m_pBuf, m_pBuf + transLen, m_pos - transLen);
+ }
+ m_pos -= transLen;
+ } else if (m_engaged) {
+ // does not actually support translation!
+ duddleyDoWrite = true;
+ } else {
+ // should we engage?
+ uint8_t* pChar = m_pBuf;
+ uint32_t len = m_pos;
+ while (len) {
+ if (!ImportCharSet::IsUSAscii(*pChar)) break;
+ pChar++;
+ len--;
+ }
+ if (len) {
+ m_engaged = true;
+ if (m_supports8to7) {
+ // allocate our translation output buffer and file...
+ m_pTransBuf = new uint8_t[m_bufSz];
+ m_pTransOut = new ImportOutFile(m_pFile, m_pTransBuf, m_bufSz);
+ return Flush();
+ } else
+ duddleyDoWrite = true;
+ } else {
+ duddleyDoWrite = true;
+ }
+ }
+ } else
+ duddleyDoWrite = true;
+
+ if (duddleyDoWrite) {
+ uint32_t written = 0;
+ nsresult rv =
+ m_outputStream->Write((const char*)m_pBuf, (int32_t)m_pos, &written);
+ if (NS_FAILED(rv) || ((uint32_t)written != m_pos)) return false;
+ m_pos = 0;
+ }
+
+ return true;
+}
+
+bool ImportOutFile::WriteU8NullTerm(const uint8_t* pSrc, bool includeNull) {
+ while (*pSrc) {
+ if (m_pos >= m_bufSz) {
+ if (!Flush()) return false;
+ }
+ *(m_pBuf + m_pos) = *pSrc;
+ m_pos++;
+ pSrc++;
+ }
+ if (includeNull) {
+ if (m_pos >= m_bufSz) {
+ if (!Flush()) return false;
+ }
+ *(m_pBuf + m_pos) = 0;
+ m_pos++;
+ }
+
+ return true;
+}
+
+bool ImportOutFile::SetMarker(int markerID) {
+ if (!Flush()) {
+ return false;
+ }
+
+ if (markerID < kMaxMarkers) {
+ int64_t pos = 0;
+ if (m_outputStream) {
+ // do we need to flush for the seek to give us the right pos?
+ m_outputStream->Flush();
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> seekStream =
+ do_QueryInterface(m_outputStream, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = seekStream->Tell(&pos);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error, Tell failed on output stream\n");
+ return false;
+ }
+ }
+ m_markers[markerID] = (uint32_t)pos + m_pos;
+ }
+
+ return true;
+}
+
+void ImportOutFile::ClearMarker(int markerID) {
+ if (markerID < kMaxMarkers) m_markers[markerID] = 0;
+}
+
+bool ImportOutFile::WriteStrAtMarker(int markerID, const char* pStr) {
+ if (markerID >= kMaxMarkers) return false;
+
+ if (!Flush()) return false;
+ int64_t pos;
+ m_outputStream->Flush();
+ nsresult rv;
+ nsCOMPtr<nsISeekableStream> seekStream =
+ do_QueryInterface(m_outputStream, &rv);
+ NS_ENSURE_SUCCESS(rv, false);
+ rv = seekStream->Tell(&pos);
+ if (NS_FAILED(rv)) return false;
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET,
+ (int32_t)m_markers[markerID]);
+ if (NS_FAILED(rv)) return false;
+ uint32_t written;
+ rv = m_outputStream->Write(pStr, strlen(pStr), &written);
+ if (NS_FAILED(rv)) return false;
+
+ rv = seekStream->Seek(nsISeekableStream::NS_SEEK_SET, pos);
+ if (NS_FAILED(rv)) return false;
+
+ return true;
+}
diff --git a/comm/mailnews/import/src/ImportOutFile.h b/comm/mailnews/import/src/ImportOutFile.h
new file mode 100644
index 0000000000..f682c4dc4f
--- /dev/null
+++ b/comm/mailnews/import/src/ImportOutFile.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 ImportOutFile_h___
+#define ImportOutFile_h___
+
+#include "nsImportTranslator.h"
+#include "nsIOutputStream.h"
+#include "nsIFile.h"
+
+#define kMaxMarkers 10
+
+class ImportOutFile;
+
+class ImportOutFile {
+ public:
+ ImportOutFile();
+ ImportOutFile(nsIFile* pFile, uint8_t* pBuf, uint32_t sz);
+ ~ImportOutFile();
+
+ bool InitOutFile(nsIFile* pFile, uint32_t bufSz = 4096);
+ void InitOutFile(nsIFile* pFile, uint8_t* pBuf, uint32_t sz);
+ inline bool WriteData(const uint8_t* pSrc, uint32_t len);
+ inline bool WriteByte(uint8_t byte);
+ bool WriteStr(const char* pStr) {
+ return WriteU8NullTerm((const uint8_t*)pStr, false);
+ }
+ bool WriteU8NullTerm(const uint8_t* pSrc, bool includeNull);
+ bool WriteEol(void) { return WriteStr("\x0D\x0A"); }
+ bool Done(void) { return Flush(); }
+
+ // Marker support
+ bool SetMarker(int markerID);
+ void ClearMarker(int markerID);
+ bool WriteStrAtMarker(int markerID, const char* pStr);
+
+ // 8-bit to 7-bit translation
+ bool Set8bitTranslator(nsImportTranslator* pTrans);
+ bool End8bitTranslation(bool* pEngaged, nsCString& useCharset,
+ nsCString& encoding);
+
+ protected:
+ bool Flush(void);
+
+ protected:
+ nsCOMPtr<nsIFile> m_pFile;
+ nsCOMPtr<nsIOutputStream> m_outputStream;
+ uint8_t* m_pBuf;
+ uint32_t m_bufSz;
+ uint32_t m_pos;
+ bool m_ownsFileAndBuffer;
+
+ // markers
+ uint32_t m_markers[kMaxMarkers];
+
+ // 8 bit to 7 bit translations
+ nsImportTranslator* m_pTrans;
+ bool m_engaged;
+ bool m_supports8to7;
+ ImportOutFile* m_pTransOut;
+ uint8_t* m_pTransBuf;
+};
+
+inline bool ImportOutFile::WriteData(const uint8_t* pSrc, uint32_t len) {
+ while ((len + m_pos) > m_bufSz) {
+ if ((m_bufSz - m_pos)) {
+ memcpy(m_pBuf + m_pos, pSrc, m_bufSz - m_pos);
+ len -= (m_bufSz - m_pos);
+ pSrc += (m_bufSz - m_pos);
+ m_pos = m_bufSz;
+ }
+ if (!Flush()) return false;
+ }
+
+ if (len) {
+ memcpy(m_pBuf + m_pos, pSrc, len);
+ m_pos += len;
+ }
+
+ return true;
+}
+
+inline bool ImportOutFile::WriteByte(uint8_t byte) {
+ if (m_pos == m_bufSz) {
+ if (!Flush()) return false;
+ }
+ *(m_pBuf + m_pos) = byte;
+ m_pos++;
+ return true;
+}
+
+#endif /* ImportOutFile_h__ */
diff --git a/comm/mailnews/import/src/ImportTranslate.cpp b/comm/mailnews/import/src/ImportTranslate.cpp
new file mode 100644
index 0000000000..64fc09bce5
--- /dev/null
+++ b/comm/mailnews/import/src/ImportTranslate.cpp
@@ -0,0 +1,100 @@
+/* -*- 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 "ImportTranslate.h"
+
+int ImportTranslate::m_useTranslator = -1;
+
+bool ImportTranslate::ConvertString(const nsCString& inStr, nsCString& outStr,
+ bool mimeHeader) {
+ if (inStr.IsEmpty()) {
+ outStr = inStr;
+ return true;
+ }
+
+ nsImportTranslator* pTrans = GetTranslator();
+ // int maxLen = (int) pTrans->GetMaxBufferSize(inStr.Length());
+ // int hLen = 0;
+ nsCString set;
+ nsCString lang;
+
+ if (mimeHeader) {
+ // add the charset and language
+ pTrans->GetCharset(set);
+ pTrans->GetLanguage(lang);
+ }
+
+ // Unfortunately, we didn't implement ConvertBuffer for all translators,
+ // just ConvertToFile. This means that this data will not always
+ // be converted to the charset of pTrans. In that case...
+ // We don't always have the data in the same charset as the current
+ // translator...
+ // It is safer to leave the charset and language field blank
+ set.Truncate();
+ lang.Truncate();
+
+ uint8_t* pBuf;
+ /*
+ pBuf = (P_U8) outStr.GetBuffer(maxLen);
+ if (!pBuf) {
+ delete pTrans;
+ return FALSE;
+ }
+ pTrans->ConvertBuffer((PC_U8)(PC_S8)inStr, inStr.GetLength(), pBuf);
+ outStr.ReleaseBuffer();
+ */
+ outStr = inStr;
+ delete pTrans;
+
+ // Now I need to run the string through the mime-header special char
+ // encoder.
+
+ pTrans = new CMHTranslator;
+ pBuf = new uint8_t[pTrans->GetMaxBufferSize(outStr.Length())];
+ pTrans->ConvertBuffer((const uint8_t*)(outStr.get()), outStr.Length(), pBuf);
+ delete pTrans;
+ outStr.Truncate();
+ if (mimeHeader) {
+ outStr = set;
+ outStr += "'";
+ outStr += lang;
+ outStr += "'";
+ }
+ outStr += (const char*)pBuf;
+ delete[] pBuf;
+
+ return true;
+}
+
+nsImportTranslator* ImportTranslate::GetTranslator(void) {
+ if (m_useTranslator == -1) {
+ // get the translator to use...
+ // CString trans;
+ // trans.LoadString(IDS_LANGUAGE_TRANSLATION);
+ m_useTranslator = 0;
+ // if (!trans.CompareNoCase("iso-2022-jp"))
+ // gWizData.m_useTranslator = 1;
+ }
+
+ switch (m_useTranslator) {
+ case 0:
+ return new nsImportTranslator;
+ // case 1:
+ // return new CSJis2JisTranslator;
+ default:
+ return new nsImportTranslator;
+ }
+}
+
+nsImportTranslator* ImportTranslate::GetMatchingTranslator(
+ const char* pCharSet) {
+ /*
+ CString jp = "iso-2022-jp";
+ if (!jp.CompareNoCase(pCharSet))
+ return new CSJis2JisTranslator;
+ */
+
+ return nullptr;
+}
diff --git a/comm/mailnews/import/src/ImportTranslate.h b/comm/mailnews/import/src/ImportTranslate.h
new file mode 100644
index 0000000000..5e31207395
--- /dev/null
+++ b/comm/mailnews/import/src/ImportTranslate.h
@@ -0,0 +1,23 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 ImportTranslate_h___
+#define ImportTranslate_h___
+
+#include "nsString.h"
+#include "nsImportTranslator.h"
+
+class ImportTranslate {
+ public:
+ static bool ConvertString(const nsCString& inStr, nsCString& outStr,
+ bool mimeHeader);
+ static nsImportTranslator* GetTranslator(void);
+ static nsImportTranslator* GetMatchingTranslator(const char* pCharSet);
+
+ protected:
+ static int m_useTranslator;
+};
+
+#endif /* ImportTranslate_h__ */
diff --git a/comm/mailnews/import/src/MapiApi.cpp b/comm/mailnews/import/src/MapiApi.cpp
new file mode 100644
index 0000000000..5490b6bc11
--- /dev/null
+++ b/comm/mailnews/import/src/MapiApi.cpp
@@ -0,0 +1,1842 @@
+/* -*- 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 "MapiDbgLog.h"
+#include "MapiApi.h"
+
+#include <sstream>
+#include "rtfMailDecoder.h"
+
+#include "prprf.h"
+#include "nsMemory.h"
+#include "nsMsgUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsNativeCharsetUtils.h"
+
+int CMapiApi::m_clients = 0;
+BOOL CMapiApi::m_initialized = false;
+nsTArray<CMsgStore*>* CMapiApi::m_pStores = NULL;
+LPMAPISESSION CMapiApi::m_lpSession = NULL;
+LPMDB CMapiApi::m_lpMdb = NULL;
+HRESULT CMapiApi::m_lastError;
+/*
+Type: 1, name: Calendar, class: IPF.Appointment
+Type: 1, name: Contacts, class: IPF.Contact
+Type: 1, name: Journal, class: IPF.Journal
+Type: 1, name: Notes, class: IPF.StickyNote
+Type: 1, name: Tasks, class: IPF.Task
+Type: 1, name: Drafts, class: IPF.Note
+*/
+
+HINSTANCE CMapiApi::m_hMapi32 = NULL;
+
+LPMAPIUNINITIALIZE gpMapiUninitialize = NULL;
+LPMAPIINITIALIZE gpMapiInitialize = NULL;
+LPMAPIALLOCATEBUFFER gpMapiAllocateBuffer = NULL;
+LPMAPIFREEBUFFER gpMapiFreeBuffer = NULL;
+LPMAPILOGONEX gpMapiLogonEx = NULL;
+LPOPENSTREAMONFILE gpMapiOpenStreamOnFile = NULL;
+
+typedef HRESULT(STDMETHODCALLTYPE WRAPCOMPRESSEDRTFSTREAM)(
+ LPSTREAM lpCompressedRTFStream, ULONG ulFlags,
+ LPSTREAM FAR* lpUncompressedRTFStream);
+typedef WRAPCOMPRESSEDRTFSTREAM* LPWRAPCOMPRESSEDRTFSTREAM;
+LPWRAPCOMPRESSEDRTFSTREAM gpWrapCompressedRTFStream = NULL;
+
+// WrapCompressedRTFStreamEx related stuff - see
+// http://support.microsoft.com/kb/839560
+typedef struct {
+ ULONG size;
+ ULONG ulFlags;
+ ULONG ulInCodePage;
+ ULONG ulOutCodePage;
+} RTF_WCSINFO;
+typedef struct {
+ ULONG size;
+ ULONG ulStreamFlags;
+} RTF_WCSRETINFO;
+
+typedef HRESULT(STDMETHODCALLTYPE WRAPCOMPRESSEDRTFSTREAMEX)(
+ LPSTREAM lpCompressedRTFStream, CONST RTF_WCSINFO* pWCSInfo,
+ LPSTREAM* lppUncompressedRTFStream, RTF_WCSRETINFO* pRetInfo);
+typedef WRAPCOMPRESSEDRTFSTREAMEX* LPWRAPCOMPRESSEDRTFSTREAMEX;
+LPWRAPCOMPRESSEDRTFSTREAMEX gpWrapCompressedRTFStreamEx = NULL;
+
+BOOL CMapiApi::LoadMapiEntryPoints(void) {
+ if (!(gpMapiUninitialize =
+ (LPMAPIUNINITIALIZE)GetProcAddress(m_hMapi32, "MAPIUninitialize")))
+ return FALSE;
+ if (!(gpMapiInitialize =
+ (LPMAPIINITIALIZE)GetProcAddress(m_hMapi32, "MAPIInitialize")))
+ return FALSE;
+ if (!(gpMapiAllocateBuffer = (LPMAPIALLOCATEBUFFER)GetProcAddress(
+ m_hMapi32, "MAPIAllocateBuffer")))
+ return FALSE;
+ if (!(gpMapiFreeBuffer =
+ (LPMAPIFREEBUFFER)GetProcAddress(m_hMapi32, "MAPIFreeBuffer")))
+ return FALSE;
+ if (!(gpMapiLogonEx =
+ (LPMAPILOGONEX)GetProcAddress(m_hMapi32, "MAPILogonEx")))
+ return FALSE;
+ if (!(gpMapiOpenStreamOnFile =
+ (LPOPENSTREAMONFILE)GetProcAddress(m_hMapi32, "OpenStreamOnFile")))
+ return FALSE;
+
+ // Available from the Outlook 2002 post-SP3 hotfix
+ // (http://support.microsoft.com/kb/883924/) Exported by msmapi32.dll; so it's
+ // unavailable to us using mapi32.dll
+ gpWrapCompressedRTFStreamEx = (LPWRAPCOMPRESSEDRTFSTREAMEX)GetProcAddress(
+ m_hMapi32, "WrapCompressedRTFStreamEx");
+ // Available always
+ gpWrapCompressedRTFStream = (LPWRAPCOMPRESSEDRTFSTREAM)GetProcAddress(
+ m_hMapi32, "WrapCompressedRTFStream");
+
+ return TRUE;
+}
+
+// Gets the PR_RTF_COMPRESSED tag property
+// Codepage is used only if the WrapCompressedRTFStreamEx is available
+BOOL CMapiApi::GetRTFPropertyDecodedAsUTF16(LPMAPIPROP pProp, nsString& val,
+ unsigned long& nativeBodyType,
+ unsigned long codepage) {
+ if (!m_hMapi32 || !(gpWrapCompressedRTFStreamEx || gpWrapCompressedRTFStream))
+ return FALSE; // Fallback to the default processing
+
+ LPSTREAM icstream = 0; // for the compressed stream
+ LPSTREAM iunstream = 0; // for the uncompressed stream
+ HRESULT hr =
+ pProp->OpenProperty(PR_RTF_COMPRESSED, &IID_IStream,
+ STGM_READ | STGM_DIRECT, 0, (LPUNKNOWN*)&icstream);
+ if (HR_FAILED(hr)) return FALSE;
+
+ if (gpWrapCompressedRTFStreamEx) { // Impossible - we use mapi32.dll!
+ RTF_WCSINFO wcsinfo = {0};
+ RTF_WCSRETINFO retinfo = {0};
+
+ retinfo.size = sizeof(RTF_WCSRETINFO);
+
+ wcsinfo.size = sizeof(RTF_WCSINFO);
+ wcsinfo.ulFlags = MAPI_NATIVE_BODY;
+ wcsinfo.ulInCodePage = codepage;
+ wcsinfo.ulOutCodePage = CP_UTF8;
+
+ if (HR_SUCCEEDED(hr = gpWrapCompressedRTFStreamEx(icstream, &wcsinfo,
+ &iunstream, &retinfo)))
+ nativeBodyType = retinfo.ulStreamFlags;
+ } else { // mapi32.dll
+ gpWrapCompressedRTFStream(icstream, 0, &iunstream);
+ }
+ icstream->Release();
+
+ if (iunstream) { // Succeeded
+ std::string streamData;
+ // Stream.Stat doesn't work for this stream!
+ bool done = false;
+ while (!done) {
+ // I think 10K is a good guess to minimize the number of reads while
+ // keeping memory usage low
+ const int bufsize = 10240;
+ char buf[bufsize];
+ ULONG read;
+ hr = iunstream->Read(buf, bufsize, &read);
+ done = (read < bufsize) || (hr != S_OK);
+ if (read) streamData.append(buf, read);
+ }
+ iunstream->Release();
+ // if rtf -> convert to plain text.
+ if (!gpWrapCompressedRTFStreamEx ||
+ (nativeBodyType == MAPI_NATIVE_BODY_TYPE_RTF)) {
+ std::stringstream s(streamData);
+ CRTFMailDecoder decoder;
+ DecodeRTF(s, decoder);
+ if (decoder.mode() == CRTFMailDecoder::mHTML)
+ nativeBodyType = MAPI_NATIVE_BODY_TYPE_HTML;
+ else if (decoder.mode() == CRTFMailDecoder::mText)
+ nativeBodyType = MAPI_NATIVE_BODY_TYPE_PLAINTEXT;
+ else
+ nativeBodyType = MAPI_NATIVE_BODY_TYPE_RTF;
+ val.Assign(decoder.text(), decoder.textSize());
+ } else { // WrapCompressedRTFStreamEx available and original type is not
+ // rtf
+ CopyUTF8toUTF16(nsDependentCString(streamData.c_str()), val);
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+void CMapiApi::MAPIUninitialize(void) {
+ if (m_hMapi32 && gpMapiUninitialize) (*gpMapiUninitialize)();
+}
+
+HRESULT CMapiApi::MAPIInitialize(LPVOID lpInit) {
+ return (m_hMapi32 && gpMapiInitialize) ? (*gpMapiInitialize)(lpInit)
+ : MAPI_E_NOT_INITIALIZED;
+}
+
+SCODE CMapiApi::MAPIAllocateBuffer(ULONG cbSize, LPVOID FAR* lppBuffer) {
+ return (m_hMapi32 && gpMapiAllocateBuffer)
+ ? (*gpMapiAllocateBuffer)(cbSize, lppBuffer)
+ : MAPI_E_NOT_INITIALIZED;
+}
+
+ULONG CMapiApi::MAPIFreeBuffer(LPVOID lpBuff) {
+ return (m_hMapi32 && gpMapiFreeBuffer) ? (*gpMapiFreeBuffer)(lpBuff)
+ : MAPI_E_NOT_INITIALIZED;
+}
+
+HRESULT CMapiApi::MAPILogonEx(ULONG ulUIParam, LPTSTR lpszProfileName,
+ LPTSTR lpszPassword, FLAGS flFlags,
+ LPMAPISESSION FAR* lppSession) {
+ return (m_hMapi32 && gpMapiLogonEx)
+ ? (*gpMapiLogonEx)(ulUIParam, lpszProfileName, lpszPassword,
+ flFlags, lppSession)
+ : MAPI_E_NOT_INITIALIZED;
+}
+
+HRESULT CMapiApi::OpenStreamOnFile(LPALLOCATEBUFFER lpAllocateBuffer,
+ LPFREEBUFFER lpFreeBuffer, ULONG ulFlags,
+ LPCTSTR lpszFileName, LPTSTR lpszPrefix,
+ LPSTREAM FAR* lppStream) {
+ return (m_hMapi32 && gpMapiOpenStreamOnFile)
+ ? (*gpMapiOpenStreamOnFile)(lpAllocateBuffer, lpFreeBuffer,
+ ulFlags, lpszFileName, lpszPrefix,
+ lppStream)
+ : MAPI_E_NOT_INITIALIZED;
+}
+
+void CMapiApi::FreeProws(LPSRowSet prows) {
+ ULONG irow;
+ if (!prows) return;
+ for (irow = 0; irow < prows->cRows; ++irow)
+ MAPIFreeBuffer(prows->aRow[irow].lpProps);
+ MAPIFreeBuffer(prows);
+}
+
+BOOL CMapiApi::LoadMapi(void) {
+ if (m_hMapi32) return TRUE;
+
+ HINSTANCE hInst = ::LoadLibraryW(L"MAPI32.DLL");
+ if (!hInst) return FALSE;
+ FARPROC pProc = GetProcAddress(hInst, "MAPIGetNetscapeVersion");
+ if (pProc) {
+ ::FreeLibrary(hInst);
+ hInst = ::LoadLibraryW(L"MAPI32BAK.DLL");
+ if (!hInst) return FALSE;
+ }
+
+ m_hMapi32 = hInst;
+ return LoadMapiEntryPoints();
+}
+
+void CMapiApi::UnloadMapi(void) {
+ if (m_hMapi32) ::FreeLibrary(m_hMapi32);
+ m_hMapi32 = NULL;
+}
+
+CMapiApi::CMapiApi() {
+ m_clients++;
+ LoadMapi();
+ if (!m_pStores) m_pStores = new nsTArray<CMsgStore*>();
+}
+
+CMapiApi::~CMapiApi() {
+ m_clients--;
+ if (!m_clients) {
+ HRESULT hr;
+
+ ClearMessageStores();
+ delete m_pStores;
+ m_pStores = NULL;
+
+ m_lpMdb = NULL;
+
+ if (m_lpSession) {
+ hr = m_lpSession->Logoff(NULL, 0, 0);
+ if (FAILED(hr)) {
+ MAPI_TRACE2("Logoff failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ }
+ m_lpSession->Release();
+ m_lpSession = NULL;
+ }
+
+ if (m_initialized) {
+ MAPIUninitialize();
+ m_initialized = FALSE;
+ }
+
+ UnloadMapi();
+ }
+}
+
+void CMapiApi::CStrToUnicode(const char* pStr, nsString& result) {
+ NS_CopyNativeToUnicode(nsDependentCString(pStr), result);
+}
+
+BOOL CMapiApi::Initialize(void) {
+ if (m_initialized) return TRUE;
+
+ HRESULT hr;
+
+ hr = MAPIInitialize(NULL);
+
+ if (FAILED(hr)) {
+ MAPI_TRACE2("MAPI Initialize failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ m_initialized = TRUE;
+ MAPI_TRACE0("MAPI Initialized\n");
+
+ return TRUE;
+}
+
+BOOL CMapiApi::LogOn(void) {
+ if (!m_initialized) {
+ MAPI_TRACE0("Tried to LogOn before initializing MAPI\n");
+ return FALSE;
+ }
+
+ if (m_lpSession) return TRUE;
+
+ HRESULT hr;
+
+ hr = MAPILogonEx(
+ 0, // might need to be passed in HWND
+ NULL, // profile name, 64 char max (LPTSTR)
+ NULL, // profile password, 64 char max (LPTSTR)
+ // MAPI_NEW_SESSION | MAPI_NO_MAIL | MAPI_LOGON_UI |
+ // MAPI_EXPLICIT_PROFILE, MAPI_NEW_SESSION | MAPI_NO_MAIL | MAPI_LOGON_UI,
+ // MAPI_NO_MAIL | MAPI_LOGON_UI,
+ MAPI_NO_MAIL | MAPI_USE_DEFAULT | MAPI_EXTENDED | MAPI_NEW_SESSION,
+ &m_lpSession);
+
+ if (FAILED(hr)) {
+ m_lpSession = NULL;
+ MAPI_TRACE2("LogOn failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ MAPI_TRACE0("MAPI Logged on\n");
+ return TRUE;
+}
+
+class CGetStoreFoldersIter : public CMapiHierarchyIter {
+ public:
+ CGetStoreFoldersIter(CMapiApi* pApi, CMapiFolderList& folders, int depth,
+ BOOL isMail = TRUE);
+
+ virtual BOOL HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry);
+
+ protected:
+ BOOL ExcludeFolderClass(const char16_t* pName);
+
+ BOOL m_isMail;
+ CMapiApi* m_pApi;
+ CMapiFolderList* m_pList;
+ int m_depth;
+};
+
+CGetStoreFoldersIter::CGetStoreFoldersIter(CMapiApi* pApi,
+ CMapiFolderList& folders, int depth,
+ BOOL isMail) {
+ m_pApi = pApi;
+ m_pList = &folders;
+ m_depth = depth;
+ m_isMail = isMail;
+}
+
+BOOL CGetStoreFoldersIter::ExcludeFolderClass(const char16_t* pName) {
+ BOOL bResult;
+ nsDependentString pNameStr(pName);
+ if (m_isMail) {
+ bResult = FALSE;
+ if (pNameStr.EqualsLiteral("IPF.Appointment"))
+ bResult = TRUE;
+ else if (pNameStr.EqualsLiteral("IPF.Contact"))
+ bResult = TRUE;
+ else if (pNameStr.EqualsLiteral("IPF.Journal"))
+ bResult = TRUE;
+ else if (pNameStr.EqualsLiteral("IPF.StickyNote"))
+ bResult = TRUE;
+ else if (pNameStr.EqualsLiteral("IPF.Task"))
+ bResult = TRUE;
+ // Skip IMAP folders
+ else if (pNameStr.EqualsLiteral("IPF.Imap"))
+ bResult = TRUE;
+ // else if (!stricmp(pName, "IPF.Note"))
+ // bResult = TRUE;
+ } else {
+ bResult = TRUE;
+ if (pNameStr.EqualsLiteral("IPF.Contact")) bResult = FALSE;
+ }
+
+ return bResult;
+}
+
+BOOL CGetStoreFoldersIter::HandleHierarchyItem(ULONG oType, ULONG cb,
+ LPENTRYID pEntry) {
+ if (oType == MAPI_FOLDER) {
+ LPMAPIFOLDER pFolder;
+ if (m_pApi->OpenEntry(cb, pEntry, (LPUNKNOWN*)&pFolder)) {
+ LPSPropValue pVal;
+ nsString name;
+
+ pVal = m_pApi->GetMapiProperty(pFolder, PR_CONTAINER_CLASS);
+ if (pVal)
+ m_pApi->GetStringFromProp(pVal, name);
+ else
+ name.Truncate();
+
+ if ((name.IsEmpty() && m_isMail) || (!ExcludeFolderClass(name.get()))) {
+ pVal = m_pApi->GetMapiProperty(pFolder, PR_DISPLAY_NAME);
+ m_pApi->GetStringFromProp(pVal, name);
+ CMapiFolder* pNewFolder =
+ new CMapiFolder(name.get(), cb, pEntry, m_depth);
+ m_pList->AddItem(pNewFolder);
+
+ pVal = m_pApi->GetMapiProperty(pFolder, PR_FOLDER_TYPE);
+ MAPI_TRACE2("Type: %d, name: %s\n", m_pApi->GetLongFromProp(pVal),
+ name.get());
+ // m_pApi->ListProperties(pFolder);
+
+ CGetStoreFoldersIter nextIter(m_pApi, *m_pList, m_depth + 1, m_isMail);
+ m_pApi->IterateHierarchy(&nextIter, pFolder);
+ }
+ pFolder->Release();
+ } else {
+ MAPI_TRACE0(
+ "GetStoreFolders - HandleHierarchyItem: Error opening folder "
+ "entry.\n");
+ return FALSE;
+ }
+ } else
+ MAPI_TRACE1(
+ "GetStoreFolders - HandleHierarchyItem: Unhandled ObjectType: %ld\n",
+ oType);
+ return TRUE;
+}
+
+BOOL CMapiApi::GetStoreFolders(ULONG cbEid, LPENTRYID lpEid,
+ CMapiFolderList& folders, int startDepth) {
+ // Fill in the array with the folders in the given store
+ if (!m_initialized || !m_lpSession) {
+ MAPI_TRACE0("MAPI not initialized for GetStoreFolders\n");
+ return FALSE;
+ }
+
+ m_lpMdb = NULL;
+
+ CMsgStore* pStore = FindMessageStore(cbEid, lpEid);
+ BOOL bResult = FALSE;
+ LPSPropValue pVal;
+
+ if (pStore && pStore->Open(m_lpSession, &m_lpMdb)) {
+ // Successful open, do the iteration of the store
+ pVal = GetMapiProperty(m_lpMdb, PR_IPM_SUBTREE_ENTRYID);
+ if (pVal) {
+ ULONG cbEntry;
+ LPENTRYID pEntry;
+ LPMAPIFOLDER lpSubTree = NULL;
+
+ if (GetEntryIdFromProp(pVal, cbEntry, pEntry)) {
+ // Open up the folder!
+ bResult = OpenEntry(cbEntry, pEntry, (LPUNKNOWN*)&lpSubTree);
+ MAPIFreeBuffer(pEntry);
+ if (bResult && lpSubTree) {
+ // Iterate the subtree with the results going into the folder list
+ CGetStoreFoldersIter iterHandler(this, folders, startDepth);
+ bResult = IterateHierarchy(&iterHandler, lpSubTree);
+ lpSubTree->Release();
+ } else {
+ MAPI_TRACE0("GetStoreFolders: Error opening sub tree.\n");
+ }
+ } else {
+ MAPI_TRACE0(
+ "GetStoreFolders: Error getting entryID from sub tree property "
+ "val.\n");
+ }
+ } else {
+ MAPI_TRACE0("GetStoreFolders: Error getting sub tree property.\n");
+ }
+ } else {
+ MAPI_TRACE0("GetStoreFolders: Error opening message store.\n");
+ }
+
+ return bResult;
+}
+
+BOOL CMapiApi::GetStoreAddressFolders(ULONG cbEid, LPENTRYID lpEid,
+ CMapiFolderList& folders) {
+ // Fill in the array with the folders in the given store
+ if (!m_initialized || !m_lpSession) {
+ MAPI_TRACE0("MAPI not initialized for GetStoreAddressFolders\n");
+ return FALSE;
+ }
+
+ m_lpMdb = NULL;
+
+ CMsgStore* pStore = FindMessageStore(cbEid, lpEid);
+ BOOL bResult = FALSE;
+ LPSPropValue pVal;
+
+ if (pStore && pStore->Open(m_lpSession, &m_lpMdb)) {
+ // Successful open, do the iteration of the store
+ pVal = GetMapiProperty(m_lpMdb, PR_IPM_SUBTREE_ENTRYID);
+ if (pVal) {
+ ULONG cbEntry;
+ LPENTRYID pEntry;
+ LPMAPIFOLDER lpSubTree = NULL;
+
+ if (GetEntryIdFromProp(pVal, cbEntry, pEntry)) {
+ // Open up the folder!
+ bResult = OpenEntry(cbEntry, pEntry, (LPUNKNOWN*)&lpSubTree);
+ MAPIFreeBuffer(pEntry);
+ if (bResult && lpSubTree) {
+ // Iterate the subtree with the results going into the folder list
+ CGetStoreFoldersIter iterHandler(this, folders, 1, FALSE);
+ bResult = IterateHierarchy(&iterHandler, lpSubTree);
+ lpSubTree->Release();
+ } else {
+ MAPI_TRACE0("GetStoreAddressFolders: Error opening sub tree.\n");
+ }
+ } else {
+ MAPI_TRACE0(
+ "GetStoreAddressFolders: Error getting entryID from sub tree "
+ "property val.\n");
+ }
+ } else {
+ MAPI_TRACE0("GetStoreAddressFolders: Error getting sub tree property.\n");
+ }
+ } else
+ MAPI_TRACE0("GetStoreAddressFolders: Error opening message store.\n");
+
+ return bResult;
+}
+
+BOOL CMapiApi::OpenStore(ULONG cbEid, LPENTRYID lpEid, LPMDB* ppMdb) {
+ if (!m_lpSession) {
+ MAPI_TRACE0("OpenStore called before a session was opened\n");
+ return FALSE;
+ }
+
+ CMsgStore* pStore = FindMessageStore(cbEid, lpEid);
+ if (pStore && pStore->Open(m_lpSession, ppMdb)) return TRUE;
+ return FALSE;
+}
+
+BOOL CMapiApi::OpenEntry(ULONG cbEntry, LPENTRYID pEntryId, LPUNKNOWN* ppOpen) {
+ if (!m_lpMdb) {
+ MAPI_TRACE0("OpenEntry called before the message store is open\n");
+ return FALSE;
+ }
+
+ return OpenMdbEntry(m_lpMdb, cbEntry, pEntryId, ppOpen);
+}
+
+BOOL CMapiApi::OpenMdbEntry(LPMDB lpMdb, ULONG cbEntry, LPENTRYID pEntryId,
+ LPUNKNOWN* ppOpen) {
+ ULONG ulObjType;
+ HRESULT hr;
+ hr = m_lpSession->OpenEntry(cbEntry, pEntryId, NULL, 0, &ulObjType,
+ (LPUNKNOWN*)ppOpen);
+ if (FAILED(hr)) {
+ MAPI_TRACE2("OpenMdbEntry failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+enum { ieidPR_ENTRYID = 0, ieidPR_OBJECT_TYPE, ieidMax };
+
+static const SizedSPropTagArray(ieidMax, ptaEid) = {ieidMax,
+ {
+ PR_ENTRYID,
+ PR_OBJECT_TYPE,
+ }};
+
+BOOL CMapiApi::IterateContents(CMapiContentIter* pIter, LPMAPIFOLDER pFolder,
+ ULONG flags) {
+ // flags can be 0 or MAPI_ASSOCIATED
+ // MAPI_ASSOCIATED is usually used for forms and views
+
+ HRESULT hr;
+ LPMAPITABLE lpTable;
+ hr = pFolder->GetContentsTable(flags, &lpTable);
+ if (FAILED(hr)) {
+ MAPI_TRACE2("GetContentsTable failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ ULONG rowCount;
+ hr = lpTable->GetRowCount(0, &rowCount);
+ if (!rowCount) {
+ MAPI_TRACE0(" Empty Table\n");
+ }
+
+ hr = lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0);
+ if (FAILED(hr)) {
+ lpTable->Release();
+ MAPI_TRACE2("SetColumns failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
+ if (FAILED(hr)) {
+ lpTable->Release();
+ MAPI_TRACE2("SeekRow failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ int cNumRows = 0;
+ LPSRowSet lpRow;
+ BOOL keepGoing = TRUE;
+ BOOL bResult = TRUE;
+ do {
+ lpRow = NULL;
+ hr = lpTable->QueryRows(1, 0, &lpRow);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE2("QueryRows failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ bResult = FALSE;
+ break;
+ }
+
+ if (lpRow) {
+ cNumRows = lpRow->cRows;
+ if (cNumRows) {
+ LPENTRYID lpEID =
+ (LPENTRYID)lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb;
+ ULONG cbEID = lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb;
+ ULONG oType = lpRow->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.ul;
+ keepGoing = HandleContentsItem(oType, cbEID, lpEID);
+ MAPI_TRACE1(" ObjectType: %ld\n", oType);
+ }
+ FreeProws(lpRow);
+ }
+
+ } while (SUCCEEDED(hr) && cNumRows && lpRow && keepGoing);
+
+ lpTable->Release();
+ return bResult;
+}
+
+BOOL CMapiApi::HandleContentsItem(ULONG oType, ULONG cb, LPENTRYID pEntry) {
+ if (oType == MAPI_MESSAGE) {
+ LPMESSAGE pMsg;
+ if (OpenEntry(cb, pEntry, (LPUNKNOWN*)&pMsg)) {
+ LPSPropValue pVal;
+ pVal = GetMapiProperty(pMsg, PR_SUBJECT);
+ ReportStringProp("PR_SUBJECT:", pVal);
+ pVal = GetMapiProperty(pMsg, PR_DISPLAY_BCC);
+ ReportStringProp("PR_DISPLAY_BCC:", pVal);
+ pVal = GetMapiProperty(pMsg, PR_DISPLAY_CC);
+ ReportStringProp("PR_DISPLAY_CC:", pVal);
+ pVal = GetMapiProperty(pMsg, PR_DISPLAY_TO);
+ ReportStringProp("PR_DISPLAY_TO:", pVal);
+ pVal = GetMapiProperty(pMsg, PR_MESSAGE_CLASS);
+ ReportStringProp("PR_MESSAGE_CLASS:", pVal);
+ ListProperties(pMsg);
+ pMsg->Release();
+ } else {
+ MAPI_TRACE0(" Folder type - error opening\n");
+ }
+ } else
+ MAPI_TRACE1(" ObjectType: %ld\n", oType);
+
+ return TRUE;
+}
+
+void CMapiApi::ListProperties(LPMAPIPROP lpProp, BOOL getValues) {
+ LPSPropTagArray pArray;
+ HRESULT hr = lpProp->GetPropList(0, &pArray);
+ if (FAILED(hr)) {
+ MAPI_TRACE0(" Unable to retrieve property list\n");
+ return;
+ }
+ ULONG count = 0;
+ LPMAPINAMEID FAR* lppPropNames;
+ SPropTagArray tagArray;
+ LPSPropTagArray lpTagArray = &tagArray;
+ tagArray.cValues = (ULONG)1;
+ nsCString desc;
+ for (ULONG i = 0; i < pArray->cValues; i++) {
+ GetPropTagName(pArray->aulPropTag[i], desc);
+ if (getValues) {
+ tagArray.aulPropTag[0] = pArray->aulPropTag[i];
+ hr = lpProp->GetNamesFromIDs(&lpTagArray, nullptr, 0, &count,
+ &lppPropNames);
+ if (hr == S_OK) MAPIFreeBuffer(lppPropNames);
+
+ LPSPropValue pVal = GetMapiProperty(lpProp, pArray->aulPropTag[i]);
+ if (pVal) {
+ desc += ", ";
+ ListPropertyValue(pVal, desc);
+ MAPIFreeBuffer(pVal);
+ }
+ }
+ MAPI_TRACE2(" Tag #%d: %s\n", (int)i, desc.get());
+ }
+
+ MAPIFreeBuffer(pArray);
+}
+
+ULONG CMapiApi::GetEmailPropertyTag(LPMAPIPROP lpProp, LONG nameID) {
+ static GUID emailGUID = {0x00062004,
+ 0x0000,
+ 0x0000,
+ {0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46}};
+
+ MAPINAMEID mapiNameID;
+ mapiNameID.lpguid = &emailGUID;
+ mapiNameID.ulKind = MNID_ID;
+ mapiNameID.Kind.lID = nameID;
+
+ LPMAPINAMEID lpMapiNames = &mapiNameID;
+ LPSPropTagArray lpMailTagArray = nullptr;
+
+ HRESULT result =
+ lpProp->GetIDsFromNames(1L, &lpMapiNames, 0, &lpMailTagArray);
+ if (result == S_OK) {
+ ULONG lTag = lpMailTagArray->aulPropTag[0];
+ MAPIFreeBuffer(lpMailTagArray);
+ return lTag;
+ } else
+ return 0L;
+}
+
+BOOL CMapiApi::HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry) {
+ if (oType == MAPI_FOLDER) {
+ LPMAPIFOLDER pFolder;
+ if (OpenEntry(cb, pEntry, (LPUNKNOWN*)&pFolder)) {
+ LPSPropValue pVal;
+ pVal = GetMapiProperty(pFolder, PR_DISPLAY_NAME);
+ ReportStringProp("Folder name:", pVal);
+ IterateContents(NULL, pFolder);
+ IterateHierarchy(NULL, pFolder);
+ pFolder->Release();
+ } else {
+ MAPI_TRACE0(" Folder type - error opening\n");
+ }
+ } else
+ MAPI_TRACE1(" ObjectType: %ld\n", oType);
+
+ return TRUE;
+}
+
+BOOL CMapiApi::IterateHierarchy(CMapiHierarchyIter* pIter, LPMAPIFOLDER pFolder,
+ ULONG flags) {
+ // flags can be CONVENIENT_DEPTH or 0
+ // CONVENIENT_DEPTH will return all depths I believe instead
+ // of just children
+ HRESULT hr;
+ LPMAPITABLE lpTable;
+ hr = pFolder->GetHierarchyTable(flags, &lpTable);
+ if (HR_FAILED(hr)) {
+ m_lastError = hr;
+ MAPI_TRACE2("IterateHierarchy: GetContentsTable failed: 0x%lx, %d\n",
+ (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ ULONG rowCount;
+ hr = lpTable->GetRowCount(0, &rowCount);
+ if (!rowCount) {
+ lpTable->Release();
+ return TRUE;
+ }
+
+ hr = lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0);
+ if (HR_FAILED(hr)) {
+ m_lastError = hr;
+ lpTable->Release();
+ MAPI_TRACE2("IterateHierarchy: SetColumns failed: 0x%lx, %d\n", (long)hr,
+ (int)hr);
+ return FALSE;
+ }
+
+ hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
+ if (HR_FAILED(hr)) {
+ m_lastError = hr;
+ lpTable->Release();
+ MAPI_TRACE2("IterateHierarchy: SeekRow failed: 0x%lx, %d\n", (long)hr,
+ (int)hr);
+ return FALSE;
+ }
+
+ int cNumRows = 0;
+ LPSRowSet lpRow;
+ BOOL keepGoing = TRUE;
+ BOOL bResult = TRUE;
+ do {
+ lpRow = NULL;
+ hr = lpTable->QueryRows(1, 0, &lpRow);
+
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE2("QueryRows failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ m_lastError = hr;
+ bResult = FALSE;
+ break;
+ }
+
+ if (lpRow) {
+ cNumRows = lpRow->cRows;
+
+ if (cNumRows) {
+ LPENTRYID lpEntry =
+ (LPENTRYID)lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb;
+ ULONG cb = lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb;
+ ULONG oType = lpRow->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.ul;
+
+ if (pIter)
+ keepGoing = pIter->HandleHierarchyItem(oType, cb, lpEntry);
+ else
+ keepGoing = HandleHierarchyItem(oType, cb, lpEntry);
+ }
+ FreeProws(lpRow);
+ }
+ } while (SUCCEEDED(hr) && cNumRows && lpRow && keepGoing);
+
+ lpTable->Release();
+
+ if (bResult && !keepGoing) bResult = FALSE;
+
+ return bResult;
+}
+
+enum { itblPR_DISPLAY_NAME, itblPR_ENTRYID, itblMax };
+
+static const SizedSPropTagArray(itblMax, ptaTbl) = {itblMax,
+ {
+ PR_DISPLAY_NAME,
+ PR_ENTRYID,
+ }};
+
+BOOL CMapiApi::IterateStores(CMapiFolderList& stores) {
+ stores.ClearAll();
+
+ if (!m_lpSession) {
+ MAPI_TRACE0("IterateStores called before session is open\n");
+ m_lastError = E_UNEXPECTED;
+ return FALSE;
+ }
+
+ HRESULT hr;
+
+ /* -- Some Microsoft sample code just to see if things are working --- */ /*
+
+ ULONG cbEIDStore;
+ LPENTRYID lpEIDStore;
+
+ hr = HrMAPIFindDefaultMsgStore(m_lpSession, &cbEIDStore, &lpEIDStore);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE0("Default message store not found\n");
+ // MessageBoxW(NULL, L"Message Store Not Found", NULL, MB_OK);
+ }
+ else {
+ LPMDB lpStore;
+ MAPI_TRACE0("Default Message store FOUND\n");
+ hr = m_lpSession->OpenMsgStore(NULL, cbEIDStore,
+ lpEIDStore, NULL,
+ MDB_NO_MAIL | MDB_NO_DIALOG, &lpStore);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE1("Unable to open default message store: 0x%lx\n", hr);
+ }
+ else {
+ MAPI_TRACE0("Default message store OPENED\n");
+ lpStore->Release();
+ }
+ }
+ */
+
+ LPMAPITABLE lpTable;
+
+ hr = m_lpSession->GetMsgStoresTable(0, &lpTable);
+ if (FAILED(hr)) {
+ MAPI_TRACE0("GetMsgStoresTable failed\n");
+ m_lastError = hr;
+ return FALSE;
+ }
+
+ ULONG rowCount;
+ hr = lpTable->GetRowCount(0, &rowCount);
+ MAPI_TRACE1("MsgStores Table rowCount: %ld\n", rowCount);
+
+ hr = lpTable->SetColumns((LPSPropTagArray)&ptaTbl, 0);
+ if (FAILED(hr)) {
+ lpTable->Release();
+ MAPI_TRACE2("SetColumns failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ m_lastError = hr;
+ return FALSE;
+ }
+
+ hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
+ if (FAILED(hr)) {
+ lpTable->Release();
+ MAPI_TRACE2("SeekRow failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ m_lastError = hr;
+ return FALSE;
+ }
+
+ int cNumRows = 0;
+ LPSRowSet lpRow;
+ BOOL keepGoing = TRUE;
+ BOOL bResult = TRUE;
+ do {
+ lpRow = NULL;
+ hr = lpTable->QueryRows(1, 0, &lpRow);
+
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE2("QueryRows failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ bResult = FALSE;
+ m_lastError = hr;
+ break;
+ }
+
+ if (lpRow) {
+ cNumRows = lpRow->cRows;
+
+ if (cNumRows) {
+ LPCTSTR lpStr =
+ (LPCTSTR)lpRow->aRow[0].lpProps[itblPR_DISPLAY_NAME].Value.LPSZ;
+ LPENTRYID lpEID =
+ (LPENTRYID)lpRow->aRow[0].lpProps[itblPR_ENTRYID].Value.bin.lpb;
+ ULONG cbEID = lpRow->aRow[0].lpProps[itblPR_ENTRYID].Value.bin.cb;
+
+ // In the future, GetStoreInfo needs to somehow return
+ // whether or not the store is from an IMAP server.
+ // Currently, GetStoreInfo opens the store and attempts
+ // to get the hierarchy tree. If the tree is empty or
+ // does not exist, then szContents will be zero. We'll
+ // assume that any store that doesn't have anything in
+ // it's hierarchy tree is not a store we want to import -
+ // there would be nothing to import from anyway!
+ // Currently, this does exclude IMAP server accounts
+ // which is the desired behaviour.
+
+ int strLen = strlen(lpStr);
+ char16_t* pwszStr =
+ (char16_t*)moz_xmalloc((strLen + 1) * sizeof(WCHAR));
+ if (!pwszStr) {
+ // out of memory
+ FreeProws(lpRow);
+ lpTable->Release();
+ return FALSE;
+ }
+ ::MultiByteToWideChar(CP_ACP, 0, lpStr, strlen(lpStr) + 1,
+ reinterpret_cast<wchar_t*>(pwszStr),
+ (strLen + 1) * sizeof(WCHAR));
+ CMapiFolder* pFolder =
+ new CMapiFolder(pwszStr, cbEID, lpEID, 0, MAPI_STORE);
+ free(pwszStr);
+
+ long szContents = 1;
+ GetStoreInfo(pFolder, &szContents);
+
+ MAPI_TRACE1(" DisplayName: %s\n", lpStr);
+ if (szContents)
+ stores.AddItem(pFolder);
+ else {
+ delete pFolder;
+ MAPI_TRACE0(" ^^^^^ Not added to store list\n");
+ }
+
+ keepGoing = TRUE;
+ }
+ FreeProws(lpRow);
+ }
+ } while (SUCCEEDED(hr) && cNumRows && lpRow && keepGoing);
+
+ lpTable->Release();
+
+ return bResult;
+}
+
+void CMapiApi::GetStoreInfo(CMapiFolder* pFolder, long* pSzContents) {
+ HRESULT hr;
+ LPMDB lpMdb;
+
+ if (pSzContents) *pSzContents = 0;
+
+ if (!OpenStore(pFolder->GetCBEntryID(), pFolder->GetEntryID(), &lpMdb))
+ return;
+
+ LPSPropValue pVal;
+ /*
+ pVal = GetMapiProperty(lpMdb, PR_DISPLAY_NAME);
+ ReportStringProp(" Message store name:", pVal);
+ pVal = GetMapiProperty(lpMdb, PR_MDB_PROVIDER);
+ ReportUIDProp(" Message store provider:", pVal);
+ pVal = GetMapiProperty(lpMdb, PR_COMMENT);
+ ReportStringProp(" Message comment:", pVal);
+ pVal = GetMapiProperty(lpMdb, PR_ACCESS_LEVEL);
+ ReportLongProp(" Message store Access Level:", pVal);
+ pVal = GetMapiProperty(lpMdb, PR_STORE_SUPPORT_MASK);
+ ReportLongProp(" Message store support mask:", pVal);
+ pVal = GetMapiProperty(lpMdb, PR_STORE_STATE);
+ ReportLongProp(" Message store state:", pVal);
+ pVal = GetMapiProperty(lpMdb, PR_OBJECT_TYPE);
+ ReportLongProp(" Message store object type:", pVal);
+ pVal = GetMapiProperty(lpMdb, PR_VALID_FOLDER_MASK);
+ ReportLongProp(" Message store valid folder mask:", pVal);
+
+ pVal = GetMapiProperty(lpMdb, 0x8001001e);
+ ReportStringProp(" Message prop 0x8001001e:", pVal);
+
+ // This key appears to be the OMI Account Manager account that corresponds
+ // to this message store. This is important for IMAP accounts
+ // since we may not want to import messages from an IMAP store!
+ // Seems silly if you ask me!
+ // In order to test this, we'll need the registry key to look under to
+ determine
+ // if it contains the "IMAP Server" value, if it does then we are an
+ // IMAP store, if not, then we are a non-IMAP store - which may always mean
+ // a regular store that should be imported.
+
+ pVal = GetMapiProperty(lpMdb, 0x80000003);
+ ReportLongProp(" Message prop 0x80000003:", pVal);
+
+ // ListProperties(lpMdb);
+ */
+
+ pVal = GetMapiProperty(lpMdb, PR_IPM_SUBTREE_ENTRYID);
+ if (pVal) {
+ ULONG cbEntry;
+ LPENTRYID pEntry;
+ LPMAPIFOLDER lpSubTree = NULL;
+
+ if (GetEntryIdFromProp(pVal, cbEntry, pEntry)) {
+ // Open up the folder!
+ ULONG ulObjType;
+ hr = lpMdb->OpenEntry(cbEntry, pEntry, NULL, 0, &ulObjType,
+ (LPUNKNOWN*)&lpSubTree);
+ MAPIFreeBuffer(pEntry);
+ if (SUCCEEDED(hr) && lpSubTree) {
+ // Find out if there are any contents in the
+ // tree.
+ LPMAPITABLE lpTable;
+ hr = lpSubTree->GetHierarchyTable(0, &lpTable);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE2("GetStoreInfo: GetHierarchyTable failed: 0x%lx, %d\n",
+ (long)hr, (int)hr);
+ } else {
+ ULONG rowCount;
+ hr = lpTable->GetRowCount(0, &rowCount);
+ lpTable->Release();
+ if (SUCCEEDED(hr) && pSzContents) *pSzContents = (long)rowCount;
+ }
+
+ lpSubTree->Release();
+ }
+ }
+ }
+}
+
+void CMapiApi::ClearMessageStores(void) {
+ if (m_pStores) {
+ CMsgStore* pStore;
+ for (size_t i = 0; i < m_pStores->Length(); i++) {
+ pStore = m_pStores->ElementAt(i);
+ delete pStore;
+ }
+ m_pStores->Clear();
+ }
+}
+
+void CMapiApi::AddMessageStore(CMsgStore* pStore) {
+ if (m_pStores) m_pStores->AppendElement(pStore);
+}
+
+CMsgStore* CMapiApi::FindMessageStore(ULONG cbEid, LPENTRYID lpEid) {
+ if (!m_lpSession) {
+ MAPI_TRACE0("FindMessageStore called before session is open\n");
+ m_lastError = E_UNEXPECTED;
+ return NULL;
+ }
+
+ ULONG result;
+ HRESULT hr;
+ CMsgStore* pStore;
+ for (size_t i = 0; i < m_pStores->Length(); i++) {
+ pStore = m_pStores->ElementAt(i);
+ hr = m_lpSession->CompareEntryIDs(cbEid, lpEid, pStore->GetCBEntryID(),
+ pStore->GetLPEntryID(), 0, &result);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE2("CompareEntryIDs failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ m_lastError = hr;
+ return NULL;
+ }
+ if (result) {
+ return pStore;
+ }
+ }
+
+ pStore = new CMsgStore(cbEid, lpEid);
+ AddMessageStore(pStore);
+ return pStore;
+}
+
+// --------------------------------------------------------------------
+// Utility stuff
+// --------------------------------------------------------------------
+
+LPSPropValue CMapiApi::GetMapiProperty(LPMAPIPROP pProp, ULONG tag) {
+ if (!pProp) return NULL;
+
+ int sz = CbNewSPropTagArray(1);
+ SPropTagArray* pTag = (SPropTagArray*)new char[sz];
+ pTag->cValues = 1;
+ pTag->aulPropTag[0] = tag;
+ LPSPropValue lpProp = NULL;
+ ULONG cValues = 0;
+ HRESULT hr = pProp->GetProps(pTag, 0, &cValues, &lpProp);
+ delete[] pTag;
+ if (HR_FAILED(hr) || (cValues != 1)) {
+ if (lpProp) MAPIFreeBuffer(lpProp);
+ return NULL;
+ } else {
+ if (PROP_TYPE(lpProp->ulPropTag) == PT_ERROR) {
+ if (lpProp->Value.l == MAPI_E_NOT_FOUND) {
+ MAPIFreeBuffer(lpProp);
+ lpProp = NULL;
+ }
+ }
+ }
+
+ return lpProp;
+}
+
+BOOL CMapiApi::IsLargeProperty(LPSPropValue pVal) {
+ return ((PROP_TYPE(pVal->ulPropTag) == PT_ERROR) &&
+ (pVal->Value.l == E_OUTOFMEMORY));
+}
+
+// The output buffer (result) must be freed with operator delete[]
+BOOL CMapiApi::GetLargeProperty(LPMAPIPROP pProp, ULONG tag, void** result) {
+ LPSTREAM lpStream;
+ HRESULT hr =
+ pProp->OpenProperty(tag, &IID_IStream, 0, 0, (LPUNKNOWN*)&lpStream);
+ if (HR_FAILED(hr)) return FALSE;
+ STATSTG st;
+ BOOL bResult = TRUE;
+ hr = lpStream->Stat(&st, STATFLAG_NONAME);
+ if (HR_FAILED(hr))
+ bResult = FALSE;
+ else {
+ if (!st.cbSize.QuadPart) st.cbSize.QuadPart = 1;
+ char* pVal = new char[(int)st.cbSize.QuadPart + 2];
+ if (pVal) {
+ ULONG sz;
+ hr = lpStream->Read(pVal, (ULONG)st.cbSize.QuadPart, &sz);
+ if (HR_FAILED(hr)) {
+ bResult = FALSE;
+ delete[] pVal;
+ } else {
+ // Just in case it's a UTF16 string
+ pVal[(int)st.cbSize.QuadPart] = pVal[(int)st.cbSize.QuadPart + 1] = 0;
+ *result = pVal;
+ }
+ } else
+ bResult = FALSE;
+ }
+
+ lpStream->Release();
+
+ return bResult;
+}
+
+BOOL CMapiApi::GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag,
+ nsCString& val) {
+ void* result;
+ if (!GetLargeProperty(pProp, tag, &result)) return FALSE;
+ if (PROP_TYPE(tag) == PT_UNICODE) // unicode string
+ LossyCopyUTF16toASCII(nsDependentString(static_cast<wchar_t*>(result)),
+ val);
+ else // either PT_STRING8 or some other binary - use as is
+ val.Assign(static_cast<char*>(result));
+ // Despite being used as wchar_t*, result it allocated as "new char[]" in
+ // GetLargeProperty().
+ delete[] static_cast<char*>(result);
+ return TRUE;
+}
+
+BOOL CMapiApi::GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag,
+ nsString& val) {
+ void* result;
+ if (!GetLargeProperty(pProp, tag, &result)) return FALSE;
+ if (PROP_TYPE(tag) == PT_UNICODE) // We already get the unicode string
+ val.Assign(static_cast<wchar_t*>(result));
+ else // either PT_STRING8 or some other binary
+ CStrToUnicode(static_cast<char*>(result), val);
+ // Despite being used as wchar_t*, result it allocated as "new char[]" in
+ // GetLargeProperty().
+ delete[] static_cast<char*>(result);
+ return TRUE;
+}
+// If the value is a string, get it...
+BOOL CMapiApi::GetEntryIdFromProp(LPSPropValue pVal, ULONG& cbEntryId,
+ LPENTRYID& lpEntryId, BOOL delVal) {
+ if (!pVal) return FALSE;
+
+ BOOL bResult = TRUE;
+ switch (PROP_TYPE(pVal->ulPropTag)) {
+ case PT_BINARY:
+ cbEntryId = pVal->Value.bin.cb;
+ MAPIAllocateBuffer(cbEntryId, (LPVOID*)&lpEntryId);
+ memcpy(lpEntryId, pVal->Value.bin.lpb, cbEntryId);
+ break;
+
+ default:
+ MAPI_TRACE0("EntryId not in BINARY prop value\n");
+ bResult = FALSE;
+ break;
+ }
+
+ if (pVal && delVal) MAPIFreeBuffer(pVal);
+
+ return bResult;
+}
+
+BOOL CMapiApi::GetStringFromProp(LPSPropValue pVal, nsCString& val,
+ BOOL delVal) {
+ BOOL bResult = TRUE;
+ if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_STRING8))
+ val = pVal->Value.lpszA;
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_UNICODE))
+ LossyCopyUTF16toASCII(nsDependentString(pVal->Value.lpszW), val);
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL))
+ val.Truncate();
+ else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) {
+ val.Truncate();
+ bResult = FALSE;
+ } else {
+ if (pVal) {
+ MAPI_TRACE1("GetStringFromProp: invalid value, expecting string - %d\n",
+ (int)PROP_TYPE(pVal->ulPropTag));
+ } else {
+ MAPI_TRACE0(
+ "GetStringFromProp: invalid value, expecting string, got null "
+ "pointer\n");
+ }
+ val.Truncate();
+ bResult = FALSE;
+ }
+ if (pVal && delVal) MAPIFreeBuffer(pVal);
+
+ return bResult;
+}
+
+BOOL CMapiApi::GetStringFromProp(LPSPropValue pVal, nsString& val,
+ BOOL delVal) {
+ BOOL bResult = TRUE;
+ if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_STRING8)) {
+ CStrToUnicode((const char*)pVal->Value.lpszA, val);
+ } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_UNICODE)) {
+ val = (char16_t*)pVal->Value.lpszW;
+ } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) {
+ val.Truncate();
+ } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) {
+ val.Truncate();
+ bResult = FALSE;
+ } else {
+ if (pVal) {
+ MAPI_TRACE1("GetStringFromProp: invalid value, expecting string - %d\n",
+ (int)PROP_TYPE(pVal->ulPropTag));
+ } else {
+ MAPI_TRACE0(
+ "GetStringFromProp: invalid value, expecting string, got null "
+ "pointer\n");
+ }
+ val.Truncate();
+ bResult = FALSE;
+ }
+ if (pVal && delVal) MAPIFreeBuffer(pVal);
+
+ return bResult;
+}
+
+LONG CMapiApi::GetLongFromProp(LPSPropValue pVal, BOOL delVal) {
+ LONG val = 0;
+ if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_LONG)) {
+ val = pVal->Value.l;
+ } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) {
+ val = 0;
+ } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) {
+ val = 0;
+ MAPI_TRACE0("GetLongFromProp: Error retrieving property\n");
+ } else {
+ MAPI_TRACE0("GetLongFromProp: invalid value, expecting long\n");
+ }
+ if (pVal && delVal) MAPIFreeBuffer(pVal);
+
+ return val;
+}
+
+void CMapiApi::ReportUIDProp(const char* pTag, LPSPropValue pVal) {
+ if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_BINARY)) {
+ if (pVal->Value.bin.cb != 16) {
+ MAPI_TRACE1("%s - INVALID, expecting 16 bytes of binary data for UID\n",
+ pTag);
+ } else {
+ nsIID uid;
+ memcpy(&uid, pVal->Value.bin.lpb, 16);
+ const char* pStr = uid.ToString().get();
+ if (pStr) {
+ MAPI_TRACE2("%s %s\n", pTag, pStr);
+ }
+ }
+ } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) {
+ MAPI_TRACE1("%s {NULL}\n", pTag);
+ } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) {
+ MAPI_TRACE1("%s {Error retrieving property}\n", pTag);
+ } else {
+ MAPI_TRACE1("%s invalid value, expecting binary\n", pTag);
+ }
+ if (pVal) MAPIFreeBuffer(pVal);
+}
+
+void CMapiApi::ReportLongProp(const char* pTag, LPSPropValue pVal) {
+ if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_LONG)) {
+ nsCString num;
+ nsCString num2;
+
+ num.AppendInt((int32_t)pVal->Value.l);
+ num2.AppendInt((int32_t)pVal->Value.l, 16);
+ MAPI_TRACE3("%s %s, 0x%s\n", pTag, num, num2);
+ } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) {
+ MAPI_TRACE1("%s {NULL}\n", pTag);
+ } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) {
+ MAPI_TRACE1("%s {Error retrieving property}\n", pTag);
+ } else {
+ MAPI_TRACE1("%s invalid value, expecting long\n", pTag);
+ }
+ if (pVal) MAPIFreeBuffer(pVal);
+}
+
+void CMapiApi::ReportStringProp(const char* pTag, LPSPropValue pVal) {
+ if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_TSTRING)) {
+ nsCString val((LPCTSTR)(pVal->Value.LPSZ));
+ MAPI_TRACE2("%s %s\n", pTag, val.get());
+ } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_NULL)) {
+ MAPI_TRACE1("%s {NULL}\n", pTag);
+ } else if (pVal && (PROP_TYPE(pVal->ulPropTag) == PT_ERROR)) {
+ MAPI_TRACE1("%s {Error retrieving property}\n", pTag);
+ } else {
+ MAPI_TRACE1("%s invalid value, expecting string\n", pTag);
+ }
+ if (pVal) MAPIFreeBuffer(pVal);
+}
+
+void CMapiApi::GetPropTagName(ULONG tag, nsCString& s) {
+ char numStr[256];
+ PR_snprintf(numStr, 256, "0x%lx, %ld", tag, tag);
+ s = numStr;
+ switch (tag) {
+#include "MapiTagStrs.cpp"
+ }
+ s += ", data: ";
+ switch (PROP_TYPE(tag)) {
+ case PT_UNSPECIFIED:
+ s += "PT_UNSPECIFIED";
+ break;
+ case PT_NULL:
+ s += "PT_NULL";
+ break;
+ case PT_I2:
+ s += "PT_I2";
+ break;
+ case PT_LONG:
+ s += "PT_LONG";
+ break;
+ case PT_R4:
+ s += "PT_R4";
+ break;
+ case PT_DOUBLE:
+ s += "PT_DOUBLE";
+ break;
+ case PT_CURRENCY:
+ s += "PT_CURRENCY";
+ break;
+ case PT_APPTIME:
+ s += "PT_APPTIME";
+ break;
+ case PT_ERROR:
+ s += "PT_ERROR";
+ break;
+ case PT_BOOLEAN:
+ s += "PT_BOOLEAN";
+ break;
+ case PT_OBJECT:
+ s += "PT_OBJECT";
+ break;
+ case PT_I8:
+ s += "PT_I8";
+ break;
+ case PT_STRING8:
+ s += "PT_STRING8";
+ break;
+ case PT_UNICODE:
+ s += "PT_UNICODE";
+ break;
+ case PT_SYSTIME:
+ s += "PT_SYSTIME";
+ break;
+ case PT_CLSID:
+ s += "PT_CLSID";
+ break;
+ case PT_BINARY:
+ s += "PT_BINARY";
+ break;
+ case PT_MV_I2:
+ s += "PT_MV_I2";
+ break;
+ case PT_MV_LONG:
+ s += "PT_MV_LONG";
+ break;
+ case PT_MV_R4:
+ s += "PT_MV_R4";
+ break;
+ case PT_MV_DOUBLE:
+ s += "PT_MV_DOUBLE";
+ break;
+ case PT_MV_CURRENCY:
+ s += "PT_MV_CURRENCY";
+ break;
+ case PT_MV_APPTIME:
+ s += "PT_MV_APPTIME";
+ break;
+ case PT_MV_SYSTIME:
+ s += "PT_MV_SYSTIME";
+ break;
+ case PT_MV_STRING8:
+ s += "PT_MV_STRING8";
+ break;
+ case PT_MV_BINARY:
+ s += "PT_MV_BINARY";
+ break;
+ case PT_MV_UNICODE:
+ s += "PT_MV_UNICODE";
+ break;
+ case PT_MV_CLSID:
+ s += "PT_MV_CLSID";
+ break;
+ case PT_MV_I8:
+ s += "PT_MV_I8";
+ break;
+ default:
+ s += "Unknown";
+ }
+}
+
+void CMapiApi::ListPropertyValue(LPSPropValue pVal, nsCString& s) {
+ nsCString strVal;
+ char nBuff[64];
+
+ s += "value: ";
+ switch (PROP_TYPE(pVal->ulPropTag)) {
+ case PT_STRING8:
+ GetStringFromProp(pVal, strVal, FALSE);
+ if (strVal.Length() > 60) {
+ strVal.SetLength(60);
+ strVal += "...";
+ }
+ strVal.ReplaceSubstring("\r", "\\r");
+ strVal.ReplaceSubstring("\n", "\\n");
+ s += strVal;
+ break;
+ case PT_LONG:
+ s.AppendInt((int32_t)pVal->Value.l);
+ s += ", 0x";
+ s.AppendInt((int32_t)pVal->Value.l, 16);
+ s += nBuff;
+ break;
+ case PT_BOOLEAN:
+ if (pVal->Value.b)
+ s += "True";
+ else
+ s += "False";
+ break;
+ case PT_NULL:
+ s += "--NULL--";
+ break;
+ case PT_SYSTIME: {
+ /*
+ COleDateTime tm(pVal->Value.ft);
+ s += tm.Format();
+ */
+ s += "-- Figure out how to format time in mozilla, PT_SYSTIME --";
+ } break;
+ default:
+ s += "?";
+ }
+}
+
+// -------------------------------------------------------------------
+// Folder list stuff
+// -------------------------------------------------------------------
+CMapiFolderList::CMapiFolderList() {}
+
+CMapiFolderList::~CMapiFolderList() { ClearAll(); }
+
+void CMapiFolderList::AddItem(CMapiFolder* pFolder) {
+ EnsureUniqueName(pFolder);
+ GenerateFilePath(pFolder);
+ m_array.AppendElement(pFolder);
+}
+
+void CMapiFolderList::ChangeName(nsString& name) {
+ if (name.IsEmpty()) {
+ name.Assign('1');
+ return;
+ }
+ char16_t lastC = name.Last();
+ if ((lastC >= '0') && (lastC <= '9')) {
+ lastC++;
+ if (lastC > '9') {
+ lastC = '1';
+ name.SetCharAt(lastC, name.Length() - 1);
+ name.Append('0');
+ } else {
+ name.SetCharAt(lastC, name.Length() - 1);
+ }
+ } else {
+ name.AppendLiteral(" 2");
+ }
+}
+
+void CMapiFolderList::EnsureUniqueName(CMapiFolder* pFolder) {
+ // For everybody in the array before me with the SAME
+ // depth, my name must be unique
+ CMapiFolder* pCurrent;
+ int i;
+ BOOL done;
+ nsString name;
+ nsString cName;
+
+ pFolder->GetDisplayName(name);
+ do {
+ done = TRUE;
+ i = m_array.Length() - 1;
+ while (i >= 0) {
+ pCurrent = GetAt(i);
+ if (pCurrent->GetDepth() == pFolder->GetDepth()) {
+ pCurrent->GetDisplayName(cName);
+ if (cName.Equals(name, nsCaseInsensitiveStringComparator)) {
+ ChangeName(name);
+ pFolder->SetDisplayName(name.get());
+ done = FALSE;
+ break;
+ }
+ } else if (pCurrent->GetDepth() < pFolder->GetDepth())
+ break;
+ i--;
+ }
+ } while (!done);
+}
+
+void CMapiFolderList::GenerateFilePath(CMapiFolder* pFolder) {
+ // A file path, includes all of my parent's path, plus mine
+ nsString name;
+ nsString path;
+ if (!pFolder->GetDepth()) {
+ pFolder->GetDisplayName(name);
+ pFolder->SetFilePath(name.get());
+ return;
+ }
+
+ CMapiFolder* pCurrent;
+ int i = m_array.Length() - 1;
+ while (i >= 0) {
+ pCurrent = GetAt(i);
+ if (pCurrent->GetDepth() == (pFolder->GetDepth() - 1)) {
+ pCurrent->GetFilePath(path);
+ path.AppendLiteral(".sbd\\");
+ pFolder->GetDisplayName(name);
+ path += name;
+ pFolder->SetFilePath(path.get());
+ return;
+ }
+ i--;
+ }
+ pFolder->GetDisplayName(name);
+ pFolder->SetFilePath(name.get());
+}
+
+void CMapiFolderList::ClearAll(void) {
+ CMapiFolder* pFolder;
+ for (size_t i = 0; i < m_array.Length(); i++) {
+ pFolder = GetAt(i);
+ delete pFolder;
+ }
+ m_array.Clear();
+}
+
+void CMapiFolderList::DumpList(void) {
+ CMapiFolder* pFolder;
+ nsString str;
+ int depth;
+ char prefix[256];
+
+ MAPI_TRACE0("Folder List ---------------------------------\n");
+ for (size_t i = 0; i < m_array.Length(); i++) {
+ pFolder = GetAt(i);
+ depth = pFolder->GetDepth();
+ pFolder->GetDisplayName(str);
+ depth *= 2;
+ if (depth > 255) depth = 255;
+ memset(prefix, ' ', depth);
+ prefix[depth] = 0;
+#ifdef MAPI_DEBUG
+ char* ansiStr = ToNewCString(str);
+ MAPI_TRACE2("%s%s: ", prefix, ansiStr);
+ free(ansiStr);
+#endif
+ pFolder->GetFilePath(str);
+#ifdef MAPI_DEBUG
+ ansiStr = ToNewCString(str);
+ MAPI_TRACE2("depth=%d, filePath=%s\n", pFolder->GetDepth(), ansiStr);
+ free(ansiStr);
+#endif
+ }
+ MAPI_TRACE0("---------------------------------------------\n");
+}
+
+CMapiFolder::CMapiFolder() {
+ m_objectType = MAPI_FOLDER;
+ m_cbEid = 0;
+ m_lpEid = NULL;
+ m_depth = 0;
+ m_doImport = TRUE;
+}
+
+CMapiFolder::CMapiFolder(const char16_t* pDisplayName, ULONG cbEid,
+ LPENTRYID lpEid, int depth, LONG oType) {
+ m_cbEid = 0;
+ m_lpEid = NULL;
+ SetDisplayName(pDisplayName);
+ SetEntryID(cbEid, lpEid);
+ SetDepth(depth);
+ SetObjectType(oType);
+ SetDoImport(TRUE);
+}
+
+CMapiFolder::CMapiFolder(const CMapiFolder* pCopyFrom) {
+ m_lpEid = NULL;
+ m_cbEid = 0;
+ SetDoImport(pCopyFrom->GetDoImport());
+ SetDisplayName(pCopyFrom->m_displayName.get());
+ SetObjectType(pCopyFrom->GetObjectType());
+ SetEntryID(pCopyFrom->GetCBEntryID(), pCopyFrom->GetEntryID());
+ SetDepth(pCopyFrom->GetDepth());
+ SetFilePath(pCopyFrom->m_mailFilePath.get());
+}
+
+CMapiFolder::~CMapiFolder() {
+ if (m_lpEid) delete m_lpEid;
+}
+
+void CMapiFolder::SetEntryID(ULONG cbEid, LPENTRYID lpEid) {
+ if (m_lpEid) delete m_lpEid;
+ m_lpEid = NULL;
+ m_cbEid = cbEid;
+ if (cbEid) {
+ m_lpEid = new BYTE[cbEid];
+ memcpy(m_lpEid, lpEid, cbEid);
+ }
+}
+
+// ---------------------------------------------------------------------
+// Message store stuff
+// ---------------------------------------------------------------------
+
+CMsgStore::CMsgStore(ULONG cbEid, LPENTRYID lpEid) {
+ m_lpEid = NULL;
+ m_lpMdb = NULL;
+ SetEntryID(cbEid, lpEid);
+}
+
+CMsgStore::~CMsgStore() {
+ if (m_lpEid) delete m_lpEid;
+
+ if (m_lpMdb) {
+ ULONG flags = LOGOFF_NO_WAIT;
+ m_lpMdb->StoreLogoff(&flags);
+ m_lpMdb->Release();
+ m_lpMdb = NULL;
+ }
+}
+
+void CMsgStore::SetEntryID(ULONG cbEid, LPENTRYID lpEid) {
+ if (m_lpEid) delete m_lpEid;
+
+ m_lpEid = NULL;
+ if (cbEid) {
+ m_lpEid = new BYTE[cbEid];
+ memcpy(m_lpEid, lpEid, cbEid);
+ }
+ m_cbEid = cbEid;
+
+ if (m_lpMdb) {
+ ULONG flags = LOGOFF_NO_WAIT;
+ m_lpMdb->StoreLogoff(&flags);
+ m_lpMdb->Release();
+ m_lpMdb = NULL;
+ }
+}
+
+BOOL CMsgStore::Open(LPMAPISESSION pSession, LPMDB* ppMdb) {
+ if (m_lpMdb) {
+ if (ppMdb) *ppMdb = m_lpMdb;
+ return TRUE;
+ }
+
+ BOOL bResult = TRUE;
+ HRESULT hr = pSession->OpenMsgStore(NULL, m_cbEid, (LPENTRYID)m_lpEid, NULL,
+ MDB_NO_MAIL, &m_lpMdb); // MDB pointer
+ if (HR_FAILED(hr)) {
+ m_lpMdb = NULL;
+ MAPI_TRACE2("OpenMsgStore failed: 0x%lx, %d\n", (long)hr, (int)hr);
+ bResult = FALSE;
+ }
+
+ if (ppMdb) *ppMdb = m_lpMdb;
+ return bResult;
+}
+
+// ------------------------------------------------------------
+// Contents Iterator
+// -----------------------------------------------------------
+
+CMapiFolderContents::CMapiFolderContents(LPMDB lpMdb, ULONG cbEid,
+ LPENTRYID lpEid) {
+ m_lpMdb = lpMdb;
+ m_fCbEid = cbEid;
+ m_fLpEid = new BYTE[cbEid];
+ memcpy(m_fLpEid, lpEid, cbEid);
+ m_count = 0;
+ m_iterCount = 0;
+ m_failure = FALSE;
+ m_lastError = 0;
+ m_lpFolder = NULL;
+ m_lpTable = NULL;
+ m_lastLpEid = NULL;
+ m_lastCbEid = 0;
+}
+
+CMapiFolderContents::~CMapiFolderContents() {
+ if (m_lastLpEid) delete m_lastLpEid;
+ delete m_fLpEid;
+ if (m_lpTable) m_lpTable->Release();
+ if (m_lpFolder) m_lpFolder->Release();
+}
+
+BOOL CMapiFolderContents::SetUpIter(void) {
+ // First, open up the MAPIFOLDER object
+ ULONG ulObjType;
+ HRESULT hr;
+ hr = m_lpMdb->OpenEntry(m_fCbEid, (LPENTRYID)m_fLpEid, NULL, 0, &ulObjType,
+ (LPUNKNOWN*)&m_lpFolder);
+
+ if (FAILED(hr) || !m_lpFolder) {
+ m_lpFolder = NULL;
+ m_lastError = hr;
+ MAPI_TRACE2("CMapiFolderContents OpenEntry failed: 0x%lx, %d\n", (long)hr,
+ (int)hr);
+ return FALSE;
+ }
+
+ if (ulObjType != MAPI_FOLDER) {
+ m_lastError = E_UNEXPECTED;
+ MAPI_TRACE0("CMapiFolderContents - bad object type, not a folder.\n");
+ return FALSE;
+ }
+
+ hr = m_lpFolder->GetContentsTable(0, &m_lpTable);
+ if (FAILED(hr) || !m_lpTable) {
+ m_lastError = hr;
+ m_lpTable = NULL;
+ MAPI_TRACE2("CMapiFolderContents - GetContentsTable failed: 0x%lx, %d\n",
+ (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ hr = m_lpTable->GetRowCount(0, &m_count);
+ if (FAILED(hr)) {
+ m_lastError = hr;
+ MAPI_TRACE0("CMapiFolderContents - GetRowCount failed\n");
+ return FALSE;
+ }
+
+ hr = m_lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0);
+ if (FAILED(hr)) {
+ m_lastError = hr;
+ MAPI_TRACE2("CMapiFolderContents - SetColumns failed: 0x%lx, %d\n",
+ (long)hr, (int)hr);
+ return FALSE;
+ }
+
+ hr = m_lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
+ if (FAILED(hr)) {
+ m_lastError = hr;
+ MAPI_TRACE2("CMapiFolderContents - SeekRow failed: 0x%lx, %d\n", (long)hr,
+ (int)hr);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+BOOL CMapiFolderContents::GetNext(ULONG* pcbEid, LPENTRYID* ppEid,
+ ULONG* poType, BOOL* pDone) {
+ *pDone = FALSE;
+ if (m_failure) return FALSE;
+ if (!m_lpFolder) {
+ if (!SetUpIter()) {
+ m_failure = TRUE;
+ return FALSE;
+ }
+ if (!m_count) {
+ *pDone = TRUE;
+ return TRUE;
+ }
+ }
+
+ int cNumRows = 0;
+ LPSRowSet lpRow = NULL;
+ HRESULT hr = m_lpTable->QueryRows(1, 0, &lpRow);
+
+ if (HR_FAILED(hr)) {
+ m_lastError = hr;
+ m_failure = TRUE;
+ MAPI_TRACE2("CMapiFolderContents - QueryRows failed: 0x%lx, %d\n", (long)hr,
+ (int)hr);
+ return FALSE;
+ }
+
+ if (lpRow) {
+ cNumRows = lpRow->cRows;
+ if (cNumRows) {
+ LPENTRYID lpEID =
+ (LPENTRYID)lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.lpb;
+ ULONG cbEID = lpRow->aRow[0].lpProps[ieidPR_ENTRYID].Value.bin.cb;
+ ULONG oType = lpRow->aRow[0].lpProps[ieidPR_OBJECT_TYPE].Value.ul;
+
+ if (m_lastCbEid != cbEID) {
+ if (m_lastLpEid) delete m_lastLpEid;
+ m_lastLpEid = new BYTE[cbEID];
+ m_lastCbEid = cbEID;
+ }
+ memcpy(m_lastLpEid, lpEID, cbEID);
+
+ *ppEid = (LPENTRYID)m_lastLpEid;
+ *pcbEid = cbEID;
+ *poType = oType;
+ } else
+ *pDone = TRUE;
+ CMapiApi::FreeProws(lpRow);
+ } else
+ *pDone = TRUE;
+
+ return TRUE;
+}
diff --git a/comm/mailnews/import/src/MapiApi.h b/comm/mailnews/import/src/MapiApi.h
new file mode 100644
index 0000000000..4d9dc7be2a
--- /dev/null
+++ b/comm/mailnews/import/src/MapiApi.h
@@ -0,0 +1,284 @@
+/* -*- Mode: C++; tab-width: 4; 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 MapiApi_h___
+#define MapiApi_h___
+
+#include "nscore.h"
+#include "nsString.h"
+#include "nsTArray.h"
+
+#include <stdio.h>
+
+#include <windows.h>
+#include <mapi.h>
+#include <mapix.h>
+#include <mapidefs.h>
+#include <mapicode.h>
+#include <mapitags.h>
+#include <mapiutil.h>
+// wabutil.h expects mapiutil to define _MAPIUTIL_H but it actually
+// defines _MAPIUTIL_H_
+#define _MAPIUTIL_H
+
+#ifndef PR_INTERNET_CPID
+# define PR_INTERNET_CPID (PROP_TAG(PT_LONG, 0x3FDE))
+#endif
+#ifndef MAPI_NATIVE_BODY
+# define MAPI_NATIVE_BODY (0x00010000)
+#endif
+#ifndef MAPI_NATIVE_BODY_TYPE_RTF
+# define MAPI_NATIVE_BODY_TYPE_RTF (0x00000001)
+#endif
+#ifndef MAPI_NATIVE_BODY_TYPE_HTML
+# define MAPI_NATIVE_BODY_TYPE_HTML (0x00000002)
+#endif
+#ifndef MAPI_NATIVE_BODY_TYPE_PLAINTEXT
+# define MAPI_NATIVE_BODY_TYPE_PLAINTEXT (0x00000004)
+#endif
+#ifndef PR_BODY_HTML_A
+# define PR_BODY_HTML_A (PROP_TAG(PT_STRING8, 0x1013))
+#endif
+#ifndef PR_BODY_HTML_W
+# define PR_BODY_HTML_W (PROP_TAG(PT_UNICODE, 0x1013))
+#endif
+#ifndef PR_BODY_HTML
+# define PR_BODY_HTML (PROP_TAG(PT_TSTRING, 0x1013))
+#endif
+
+class CMapiFolderList;
+class CMsgStore;
+class CMapiFolder;
+
+class CMapiContentIter {
+ public:
+ virtual BOOL HandleContentItem(ULONG oType, ULONG cb, LPENTRYID pEntry) = 0;
+};
+
+class CMapiHierarchyIter {
+ public:
+ virtual BOOL HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry) = 0;
+};
+
+class CMapiApi {
+ public:
+ CMapiApi();
+ ~CMapiApi();
+
+ static BOOL LoadMapi(void);
+ static BOOL LoadMapiEntryPoints(void);
+ static void UnloadMapi(void);
+
+ static HINSTANCE m_hMapi32;
+
+ static void MAPIUninitialize(void);
+ static HRESULT MAPIInitialize(LPVOID lpInit);
+ static SCODE MAPIAllocateBuffer(ULONG cbSize, LPVOID FAR* lppBuffer);
+ static ULONG MAPIFreeBuffer(LPVOID lpBuff);
+ static HRESULT MAPILogonEx(ULONG ulUIParam, LPTSTR lpszProfileName,
+ LPTSTR lpszPassword, FLAGS flFlags,
+ LPMAPISESSION FAR* lppSession);
+ static HRESULT OpenStreamOnFile(LPALLOCATEBUFFER lpAllocateBuffer,
+ LPFREEBUFFER lpFreeBuffer, ULONG ulFlags,
+ LPCTSTR lpszFileName, LPTSTR lpszPrefix,
+ LPSTREAM FAR* lppStream);
+ static void FreeProws(LPSRowSet prows);
+
+ BOOL Initialize(void);
+ BOOL LogOn(void);
+
+ void AddMessageStore(CMsgStore* pStore);
+ void SetCurrentMsgStore(LPMDB lpMdb) { m_lpMdb = lpMdb; }
+
+ // Open any given entry from the current Message Store
+ BOOL OpenEntry(ULONG cbEntry, LPENTRYID pEntryId, LPUNKNOWN* ppOpen);
+ static BOOL OpenMdbEntry(LPMDB lpMdb, ULONG cbEntry, LPENTRYID pEntryId,
+ LPUNKNOWN* ppOpen);
+
+ // Fill in the folders list with the hierarchy from the given
+ // message store.
+ BOOL GetStoreFolders(ULONG cbEid, LPENTRYID lpEid, CMapiFolderList& folders,
+ int startDepth);
+ BOOL GetStoreAddressFolders(ULONG cbEid, LPENTRYID lpEid,
+ CMapiFolderList& folders);
+ BOOL OpenStore(ULONG cbEid, LPENTRYID lpEid, LPMDB* ppMdb);
+
+ // Iteration
+ BOOL IterateStores(CMapiFolderList& list);
+ BOOL IterateContents(CMapiContentIter* pIter, LPMAPIFOLDER pFolder,
+ ULONG flags = 0);
+ BOOL IterateHierarchy(CMapiHierarchyIter* pIter, LPMAPIFOLDER pFolder,
+ ULONG flags = 0);
+
+ // Properties
+ static LPSPropValue GetMapiProperty(LPMAPIPROP pProp, ULONG tag);
+ // If delVal is true, functions will call CMapiApi::MAPIFreeBuffer on pVal.
+ static BOOL GetEntryIdFromProp(LPSPropValue pVal, ULONG& cbEntryId,
+ LPENTRYID& lpEntryId, BOOL delVal = TRUE);
+ static BOOL GetStringFromProp(LPSPropValue pVal, nsCString& val,
+ BOOL delVal = TRUE);
+ static BOOL GetStringFromProp(LPSPropValue pVal, nsString& val,
+ BOOL delVal = TRUE);
+ static LONG GetLongFromProp(LPSPropValue pVal, BOOL delVal = TRUE);
+ static BOOL GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag,
+ nsCString& val);
+ static BOOL GetLargeStringProperty(LPMAPIPROP pProp, ULONG tag,
+ nsString& val);
+ static BOOL IsLargeProperty(LPSPropValue pVal);
+ static ULONG GetEmailPropertyTag(LPMAPIPROP lpProp, LONG nameID);
+
+ static BOOL GetRTFPropertyDecodedAsUTF16(LPMAPIPROP pProp, nsString& val,
+ unsigned long& nativeBodyType,
+ unsigned long codepage = 0);
+
+ // Debugging & reporting stuff
+ static void ListProperties(LPMAPIPROP lpProp, BOOL getValues = TRUE);
+ static void ListPropertyValue(LPSPropValue pVal, nsCString& s);
+
+ protected:
+ BOOL HandleHierarchyItem(ULONG oType, ULONG cb, LPENTRYID pEntry);
+ BOOL HandleContentsItem(ULONG oType, ULONG cb, LPENTRYID pEntry);
+ void GetStoreInfo(CMapiFolder* pFolder, long* pSzContents);
+
+ // array of available message stores, cached so that
+ // message stores are only opened once, preventing multiple
+ // logon's by the user if the store requires a logon.
+ CMsgStore* FindMessageStore(ULONG cbEid, LPENTRYID lpEid);
+ void ClearMessageStores(void);
+
+ static void CStrToUnicode(const char* pStr, nsString& result);
+
+ // Debugging & reporting stuff
+ static void GetPropTagName(ULONG tag, nsCString& s);
+ static void ReportStringProp(const char* pTag, LPSPropValue pVal);
+ static void ReportUIDProp(const char* pTag, LPSPropValue pVal);
+ static void ReportLongProp(const char* pTag, LPSPropValue pVal);
+
+ private:
+ static int m_clients;
+ static BOOL m_initialized;
+ static nsTArray<CMsgStore*>* m_pStores;
+ static LPMAPISESSION m_lpSession;
+ static LPMDB m_lpMdb;
+ static HRESULT m_lastError;
+ static char16_t* m_pUniBuff;
+ static int m_uniBuffLen;
+
+ static BOOL GetLargeProperty(LPMAPIPROP pProp, ULONG tag, void** result);
+};
+
+class CMapiFolder {
+ public:
+ CMapiFolder();
+ explicit CMapiFolder(const CMapiFolder* pCopyFrom);
+ CMapiFolder(const char16_t* pDisplayName, ULONG cbEid, LPENTRYID lpEid,
+ int depth, LONG oType = MAPI_FOLDER);
+ ~CMapiFolder();
+
+ void SetDoImport(BOOL doIt) { m_doImport = doIt; }
+ void SetObjectType(long oType) { m_objectType = oType; }
+ void SetDisplayName(const char16_t* pDisplayName) {
+ m_displayName = pDisplayName;
+ }
+ void SetEntryID(ULONG cbEid, LPENTRYID lpEid);
+ void SetDepth(int depth) { m_depth = depth; }
+ void SetFilePath(const char16_t* pFilePath) { m_mailFilePath = pFilePath; }
+
+ BOOL GetDoImport(void) const { return m_doImport; }
+ LONG GetObjectType(void) const { return m_objectType; }
+ void GetDisplayName(nsString& name) const { name = m_displayName; }
+ void GetFilePath(nsString& path) const { path = m_mailFilePath; }
+ BOOL IsStore(void) const { return m_objectType == MAPI_STORE; }
+ BOOL IsFolder(void) const { return m_objectType == MAPI_FOLDER; }
+ int GetDepth(void) const { return m_depth; }
+
+ LPENTRYID GetEntryID(ULONG* pCb = NULL) const {
+ if (pCb) *pCb = m_cbEid;
+ return (LPENTRYID)m_lpEid;
+ }
+ ULONG GetCBEntryID(void) const { return m_cbEid; }
+
+ private:
+ LONG m_objectType;
+ ULONG m_cbEid;
+ BYTE* m_lpEid;
+ nsString m_displayName;
+ int m_depth;
+ nsString m_mailFilePath;
+ BOOL m_doImport;
+};
+
+class CMapiFolderList {
+ public:
+ CMapiFolderList();
+ ~CMapiFolderList();
+
+ void AddItem(CMapiFolder* pFolder);
+ CMapiFolder* GetItem(int index) {
+ if ((index >= 0) && (index < (int)m_array.Length()))
+ return GetAt(index);
+ else
+ return NULL;
+ }
+ void ClearAll(void);
+
+ // Debugging and reporting
+ void DumpList(void);
+
+ CMapiFolder* GetAt(int index) { return m_array.ElementAt(index); }
+ int GetSize(void) { return m_array.Length(); }
+
+ protected:
+ void EnsureUniqueName(CMapiFolder* pFolder);
+ void GenerateFilePath(CMapiFolder* pFolder);
+ void ChangeName(nsString& name);
+
+ private:
+ nsTArray<CMapiFolder*> m_array;
+};
+
+class CMsgStore {
+ public:
+ explicit CMsgStore(ULONG cbEid = 0, LPENTRYID lpEid = NULL);
+ ~CMsgStore();
+
+ void SetEntryID(ULONG cbEid, LPENTRYID lpEid);
+ BOOL Open(LPMAPISESSION pSession, LPMDB* ppMdb);
+
+ ULONG GetCBEntryID(void) { return m_cbEid; }
+ LPENTRYID GetLPEntryID(void) { return (LPENTRYID)m_lpEid; }
+
+ private:
+ ULONG m_cbEid;
+ BYTE* m_lpEid;
+ LPMDB m_lpMdb;
+};
+
+class CMapiFolderContents {
+ public:
+ CMapiFolderContents(LPMDB lpMdb, ULONG cbEID, LPENTRYID lpEid);
+ ~CMapiFolderContents();
+
+ BOOL GetNext(ULONG* pcbEid, LPENTRYID* ppEid, ULONG* poType, BOOL* pDone);
+
+ ULONG GetCount(void) { return m_count; }
+
+ protected:
+ BOOL SetUpIter(void);
+
+ private:
+ HRESULT m_lastError;
+ BOOL m_failure;
+ LPMDB m_lpMdb;
+ LPMAPIFOLDER m_lpFolder;
+ LPMAPITABLE m_lpTable;
+ ULONG m_fCbEid;
+ BYTE* m_fLpEid;
+ ULONG m_count;
+ ULONG m_iterCount;
+ BYTE* m_lastLpEid;
+ ULONG m_lastCbEid;
+};
+
+#endif /* MapiApi_h__ */
diff --git a/comm/mailnews/import/src/MapiDbgLog.h b/comm/mailnews/import/src/MapiDbgLog.h
new file mode 100644
index 0000000000..56580920ea
--- /dev/null
+++ b/comm/mailnews/import/src/MapiDbgLog.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; 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 MapiDbgLog_h___
+#define MapiDbgLog_h___
+
+/*
+#ifdef NS_DEBUG
+#define MAPI_DEBUG 1
+#endif
+*/
+
+#ifdef MAPI_DEBUG
+# include <stdio.h>
+
+# define MAPI_DUMP_STRING(x) printf("%s", (const char*)x)
+# define MAPI_TRACE0(x) printf(x)
+# define MAPI_TRACE1(x, y) printf(x, y)
+# define MAPI_TRACE2(x, y, z) printf(x, y, z)
+# define MAPI_TRACE3(x, y, z, a) printf(x, y, z, a)
+# define MAPI_TRACE4(x, y, z, a, b) printf(x, y, z, a, b)
+
+#else
+
+# define MAPI_DUMP_STRING(x)
+# define MAPI_TRACE0(x)
+# define MAPI_TRACE1(x, y)
+# define MAPI_TRACE2(x, y, z)
+# define MAPI_TRACE3(x, y, z, a)
+# define MAPI_TRACE4(x, y, z, a, b)
+
+#endif
+
+#endif /* MapiDbgLog_h___ */
diff --git a/comm/mailnews/import/src/MapiMessage.cpp b/comm/mailnews/import/src/MapiMessage.cpp
new file mode 100644
index 0000000000..af57aa1d4c
--- /dev/null
+++ b/comm/mailnews/import/src/MapiMessage.cpp
@@ -0,0 +1,1383 @@
+/* -*- 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 INITGUID
+# define INITGUID
+#endif
+
+#ifndef USES_IID_IMessage
+# define USES_IID_IMessage
+#endif
+
+#include "nscore.h"
+#include <time.h>
+#include "nsString.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsMsgUtils.h"
+#include "nsMimeTypes.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIOutputStream.h"
+
+#include "MapiDbgLog.h"
+#include "MapiApi.h"
+
+#include "MapiMimeTypes.h"
+
+#include "nsMsgI18N.h"
+#include "nsCRT.h"
+#include "nsNetUtil.h"
+#include "MapiMessage.h"
+
+#include "nsOutlookMail.h"
+
+#include "mozilla/Encoding.h"
+
+#include <stdlib.h>
+#include <tuple>
+
+// needed for the call the OpenStreamOnFile
+extern LPMAPIALLOCATEBUFFER gpMapiAllocateBuffer;
+extern LPMAPIFREEBUFFER gpMapiFreeBuffer;
+
+// Sample From line: From - 1 Jan 1965 00:00:00
+
+typedef const char* PC_S8;
+
+static const char* kWhitespace = "\b\t\r\n ";
+static const char* sFromLine = "From - ";
+static const char* sFromDate = "Mon Jan 1 00:00:00 1965";
+static const char* sDaysOfWeek[7] = {"Sun", "Mon", "Tue", "Wed",
+ "Thu", "Fri", "Sat"};
+
+static const char* sMonths[12] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun",
+ "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
+
+CMapiMessage::CMapiMessage(LPMESSAGE lpMsg)
+ : m_lpMsg(lpMsg), m_dldStateHeadersOnly(false), m_msgFlags(0) {
+ nsresult rv;
+ m_pIOService = do_GetService(NS_IOSERVICE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return;
+
+ FetchHeaders();
+ if (ValidState()) {
+ BuildFromLine();
+ FetchFlags();
+ GetDownloadState();
+ if (FullMessageDownloaded()) {
+ FetchBody();
+ ProcessAttachments();
+ }
+ }
+}
+
+CMapiMessage::~CMapiMessage() {
+ ClearAttachments();
+ if (m_lpMsg) m_lpMsg->Release();
+}
+
+void CMapiMessage::FormatDateTime(SYSTEMTIME& tm, nsCString& s,
+ bool includeTZ) {
+ long offset = _timezone;
+ s += sDaysOfWeek[tm.wDayOfWeek];
+ s += ", ";
+ s.AppendInt((int32_t)tm.wDay);
+ s += " ";
+ s += sMonths[tm.wMonth - 1];
+ s += " ";
+ s.AppendInt((int32_t)tm.wYear);
+ s += " ";
+ int val = tm.wHour;
+ if (val < 10) s += "0";
+ s.AppendInt((int32_t)val);
+ s += ":";
+ val = tm.wMinute;
+ if (val < 10) s += "0";
+ s.AppendInt((int32_t)val);
+ s += ":";
+ val = tm.wSecond;
+ if (val < 10) s += "0";
+ s.AppendInt((int32_t)val);
+ if (includeTZ) {
+ s += " ";
+ if (offset < 0) {
+ offset *= -1;
+ s += "+";
+ } else
+ s += "-";
+ offset /= 60;
+ val = (int)(offset / 60);
+ if (val < 10) s += "0";
+ s.AppendInt((int32_t)val);
+ val = (int)(offset % 60);
+ if (val < 10) s += "0";
+ s.AppendInt((int32_t)val);
+ }
+}
+
+bool CMapiMessage::EnsureHeader(CMapiMessageHeaders::SpecialHeader special,
+ ULONG mapiTag) {
+ if (m_headers.Value(special)) return true;
+
+ LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, mapiTag);
+ bool success = false;
+ if (pVal) {
+ if (PROP_TYPE(pVal->ulPropTag) == PT_STRING8) {
+ if (pVal->Value.lpszA && strlen(pVal->Value.lpszA)) {
+ m_headers.SetValue(special, pVal->Value.lpszA);
+ success = true;
+ }
+ } else if (PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) {
+ if (pVal->Value.lpszW && wcslen(pVal->Value.lpszW)) {
+ m_headers.SetValue(special,
+ NS_ConvertUTF16toUTF8(pVal->Value.lpszW).get());
+ success = true;
+ }
+ }
+ CMapiApi::MAPIFreeBuffer(pVal);
+ }
+
+ return success;
+}
+
+bool CMapiMessage::EnsureDate() {
+ if (m_headers.Value(CMapiMessageHeaders::hdrDate)) return true;
+
+ LPSPropValue pVal =
+ CMapiApi::GetMapiProperty(m_lpMsg, PR_MESSAGE_DELIVERY_TIME);
+ if (!pVal) pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_CREATION_TIME);
+ if (pVal) {
+ SYSTEMTIME st;
+ // the following call returns UTC
+ ::FileTimeToSystemTime(&(pVal->Value.ft), &st);
+ CMapiApi::MAPIFreeBuffer(pVal);
+ // FormatDateTime would append the local time zone, so don't use it.
+ // Instead, we just append +0000 for GMT/UTC here.
+ nsCString str;
+ FormatDateTime(st, str, false);
+ str += " +0000";
+ m_headers.SetValue(CMapiMessageHeaders::hdrDate, str.get());
+ return true;
+ }
+
+ return false;
+}
+
+void CMapiMessage::BuildFromLine(void) {
+ m_fromLine = sFromLine;
+ LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_CREATION_TIME);
+ if (pVal) {
+ SYSTEMTIME st;
+ ::FileTimeToSystemTime(&(pVal->Value.ft), &st);
+ CMapiApi::MAPIFreeBuffer(pVal);
+ FormatDateTime(st, m_fromLine, FALSE);
+ } else
+ m_fromLine += sFromDate;
+
+ m_fromLine += "\x0D\x0A";
+}
+
+#ifndef dispidHeaderItem
+# define dispidHeaderItem 0x8578
+#endif
+DEFINE_OLEGUID(PSETID_Common, MAKELONG(0x2000 + (8), 0x0006), 0, 0);
+
+void CMapiMessage::GetDownloadState() {
+ // See http://support.microsoft.com/kb/912239
+ ULONG ulVal = 0;
+ LPSPropValue lpPropVal = NULL;
+ LPSPropTagArray lpNamedPropTag = NULL;
+ MAPINAMEID NamedID = {0};
+ LPMAPINAMEID lpNamedID = NULL;
+
+ NamedID.lpguid = (LPGUID)&PSETID_Common;
+ NamedID.ulKind = MNID_ID;
+ NamedID.Kind.lID = dispidHeaderItem;
+ lpNamedID = &NamedID;
+
+ m_lpMsg->GetIDsFromNames(1, &lpNamedID, NULL, &lpNamedPropTag);
+
+ if (lpNamedPropTag && 1 == lpNamedPropTag->cValues) {
+ lpNamedPropTag->aulPropTag[0] =
+ CHANGE_PROP_TYPE(lpNamedPropTag->aulPropTag[0], PT_LONG);
+
+ // Get the value of the property.
+ m_lpMsg->GetProps(lpNamedPropTag, 0, &ulVal, &lpPropVal);
+ if (lpPropVal && 1 == ulVal && PT_LONG == PROP_TYPE(lpPropVal->ulPropTag) &&
+ lpPropVal->Value.ul)
+ m_dldStateHeadersOnly = true;
+ }
+
+ CMapiApi::MAPIFreeBuffer(lpPropVal);
+ CMapiApi::MAPIFreeBuffer(lpNamedPropTag);
+}
+
+// Headers - fetch will get PR_TRANSPORT_MESSAGE_HEADERS
+// or if they do not exist will build a header from
+// PR_DISPLAY_TO, _CC, _BCC
+// PR_SUBJECT
+// PR_MESSAGE_RECIPIENTS
+// and PR_CREATION_TIME if needed?
+bool CMapiMessage::FetchHeaders(void) {
+ ULONG tag = PR_TRANSPORT_MESSAGE_HEADERS_A;
+ LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, tag);
+ if (!pVal)
+ pVal = CMapiApi::GetMapiProperty(m_lpMsg,
+ tag = PR_TRANSPORT_MESSAGE_HEADERS_W);
+ if (pVal) {
+ if (CMapiApi::IsLargeProperty(pVal)) {
+ nsCString headers;
+ CMapiApi::GetLargeStringProperty(m_lpMsg, tag, headers);
+ m_headers.Assign(headers.get());
+ } else if ((PROP_TYPE(pVal->ulPropTag) == PT_STRING8) &&
+ (pVal->Value.lpszA) && (*(pVal->Value.lpszA)))
+ m_headers.Assign(pVal->Value.lpszA);
+ else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) &&
+ (pVal->Value.lpszW) && (*(pVal->Value.lpszW))) {
+ nsCString headers;
+ LossyCopyUTF16toASCII(nsDependentString(pVal->Value.lpszW), headers);
+ m_headers.Assign(headers.get());
+ }
+
+ CMapiApi::MAPIFreeBuffer(pVal);
+ }
+
+ EnsureDate();
+ if (!EnsureHeader(CMapiMessageHeaders::hdrFrom, PR_SENDER_NAME_W))
+ EnsureHeader(CMapiMessageHeaders::hdrFrom, PR_SENDER_EMAIL_ADDRESS_W);
+ EnsureHeader(CMapiMessageHeaders::hdrSubject, PR_SUBJECT_W);
+ EnsureHeader(CMapiMessageHeaders::hdrTo, PR_DISPLAY_TO_W);
+ EnsureHeader(CMapiMessageHeaders::hdrCc, PR_DISPLAY_CC_W);
+ EnsureHeader(CMapiMessageHeaders::hdrBcc, PR_DISPLAY_BCC_W);
+
+ ProcessContentType();
+
+ return !m_headers.IsEmpty();
+}
+
+// Mime-Version: 1.0
+// Content-Type: text/plain; charset="US-ASCII"
+// Content-Type: multipart/mixed; boundary="=====================_874475278==_"
+
+void CMapiMessage::ProcessContentType() {
+ m_mimeContentType.Truncate();
+ m_mimeBoundary.Truncate();
+ m_mimeCharset.Truncate();
+
+ const char* contentType =
+ m_headers.Value(CMapiMessageHeaders::hdrContentType);
+ if (!contentType) return;
+
+ const char *begin = contentType, *end;
+ nsCString tStr;
+
+ // Note: this isn't a complete parser, the content type
+ // we extract could have rfc822 comments in it
+ while (*begin && IsSpace(*begin)) begin++;
+ if (!(*begin)) return;
+ end = begin;
+ while (*end && (*end != ';')) end++;
+ m_mimeContentType.Assign(begin, end - begin);
+ if (!(*end)) return;
+ // look for "boundary="
+ begin = end + 1;
+ bool haveB;
+ bool haveC;
+ while (*begin) {
+ haveB = false;
+ haveC = false;
+ while (*begin && IsSpace(*begin)) begin++;
+ if (!(*begin)) return;
+ end = begin;
+ while (*end && (*end != '=')) end++;
+ if (end - begin) {
+ tStr.Assign(begin, end - begin);
+ if (tStr.LowerCaseEqualsLiteral("boundary"))
+ haveB = true;
+ else if (tStr.LowerCaseEqualsLiteral("charset"))
+ haveC = true;
+ }
+ if (!(*end)) return;
+ begin = end + 1;
+ while (*begin && IsSpace(*begin)) begin++;
+ if (*begin == '"') {
+ begin++;
+ bool slash = false;
+ tStr.Truncate();
+ while (*begin) {
+ if (slash) {
+ slash = false;
+ tStr.Append(*begin);
+ } else if (*begin == '"')
+ break;
+ else if (*begin != '\\')
+ tStr.Append(*begin);
+ else
+ slash = true;
+ begin++;
+ }
+ if (haveB) {
+ m_mimeBoundary = tStr;
+ haveB = false;
+ }
+ if (haveC) {
+ m_mimeCharset = tStr;
+ haveC = false;
+ }
+ if (!(*begin)) return;
+ begin++;
+ }
+ tStr.Truncate();
+ while (*begin && (*begin != ';')) {
+ tStr.Append(*(begin++));
+ }
+ if (haveB) {
+ tStr.Trim(kWhitespace);
+ m_mimeBoundary = tStr;
+ }
+ if (haveC) {
+ tStr.Trim(kWhitespace);
+ m_mimeCharset = tStr;
+ }
+ if (*begin) begin++;
+ }
+}
+
+const char* CpToCharset(unsigned int cp) {
+ struct CODEPAGE_TO_CHARSET {
+ unsigned long cp;
+ const char* charset;
+ };
+
+ // This table is based on
+ // http://msdn.microsoft.com/en-us/library/dd317756(v=VS.85).aspx#1; Please
+ // extend as appropriate. The codepage values are sorted ascending.
+ static const CODEPAGE_TO_CHARSET cptocharset[] = {
+ {37, "IBM037"}, // IBM EBCDIC US-Canada
+ {437, "IBM437"}, // OEM United States
+ {500, "IBM500"}, // IBM EBCDIC International
+ {708, "ASMO-708"}, // Arabic (ASMO 708)
+ // 709 Arabic (ASMO-449+, BCON V4)
+ // 710 Arabic - Transparent Arabic
+ {720, "DOS-720"}, // Arabic (Transparent ASMO); Arabic (DOS)
+ {737, "ibm737"}, // OEM Greek (formerly 437G); Greek (DOS)
+ {775, "ibm775"}, // OEM Baltic; Baltic (DOS)
+ {850, "ibm850"}, // OEM Multilingual Latin 1; Western European (DOS)
+ {852, "ibm852"}, // OEM Latin 2; Central European (DOS)
+ {855, "IBM855"}, // OEM Cyrillic (primarily Russian)
+ {857, "ibm857"}, // OEM Turkish; Turkish (DOS)
+ {858, "IBM00858"}, // OEM Multilingual Latin 1 + Euro symbol
+ {860, "IBM860"}, // OEM Portuguese; Portuguese (DOS)
+ {861, "ibm861"}, // OEM Icelandic; Icelandic (DOS)
+ {862, "DOS-862"}, // OEM Hebrew; Hebrew (DOS)
+ {863, "IBM863"}, // OEM French Canadian; French Canadian (DOS)
+ {864, "IBM864"}, // OEM Arabic; Arabic (864)
+ {865, "IBM865"}, // OEM Nordic; Nordic (DOS)
+ {866, "cp866"}, // OEM Russian; Cyrillic (DOS)
+ {869, "ibm869"}, // OEM Modern Greek; Greek, Modern (DOS)
+ {870, "IBM870"}, // IBM EBCDIC Multilingual/ROECE (Latin 2); IBM EBCDIC
+ // Multilingual Latin 2
+ {874, "windows-874"}, // ANSI/OEM Thai (same as 28605, ISO 8859-15); Thai
+ // (Windows)
+ {875, "cp875"}, // IBM EBCDIC Greek Modern
+ {932, "shift_jis"}, // ANSI/OEM Japanese; Japanese (Shift-JIS)
+ {936, "gb2312"}, // ANSI/OEM Simplified Chinese (PRC, Singapore); Chinese
+ // Simplified (GB2312)
+ {949, "ks_c_5601-1987"}, // ANSI/OEM Korean (Unified Hangul Code)
+ {950, "big5"}, // ANSI/OEM Traditional Chinese (Taiwan; Hong Kong SAR,
+ // PRC); Chinese Traditional (Big5)
+ {1026, "IBM1026"}, // IBM EBCDIC Turkish (Latin 5)
+ {1047, "IBM01047"}, // IBM EBCDIC Latin 1/Open System
+ {1140, "IBM01140"}, // IBM EBCDIC US-Canada (037 + Euro symbol); IBM
+ // EBCDIC (US-Canada-Euro)
+ {1141, "IBM01141"}, // IBM EBCDIC Germany (20273 + Euro symbol); IBM
+ // EBCDIC (Germany-Euro)
+ {1142, "IBM01142"}, // IBM EBCDIC Denmark-Norway (20277 + Euro symbol);
+ // IBM EBCDIC (Denmark-Norway-Euro)
+ {1143, "IBM01143"}, // IBM EBCDIC Finland-Sweden (20278 + Euro symbol);
+ // IBM EBCDIC (Finland-Sweden-Euro)
+ {1144, "IBM01144"}, // IBM EBCDIC Italy (20280 + Euro symbol); IBM EBCDIC
+ // (Italy-Euro)
+ {1145, "IBM01145"}, // IBM EBCDIC Latin America-Spain (20284 + Euro
+ // symbol); IBM EBCDIC (Spain-Euro)
+ {1146, "IBM01146"}, // IBM EBCDIC United Kingdom (20285 + Euro symbol);
+ // IBM EBCDIC (UK-Euro)
+ {1147, "IBM01147"}, // IBM EBCDIC France (20297 + Euro symbol); IBM
+ // EBCDIC (France-Euro)
+ {1148, "IBM01148"}, // IBM EBCDIC International (500 + Euro symbol); IBM
+ // EBCDIC (International-Euro)
+ {1149, "IBM01149"}, // IBM EBCDIC Icelandic (20871 + Euro symbol); IBM
+ // EBCDIC (Icelandic-Euro)
+ {1200, "utf-16"}, // Unicode UTF-16, little endian byte order (BMP of ISO
+ // 10646); available only to managed applications
+ {1201, "unicodeFFFE"}, // Unicode UTF-16, big endian byte order;
+ // available only to managed applications
+ {1250,
+ "windows-1250"}, // ANSI Central European; Central European (Windows)
+ {1251, "windows-1251"}, // ANSI Cyrillic; Cyrillic (Windows)
+ {1252, "windows-1252"}, // ANSI Latin 1; Western European (Windows)
+ {1253, "windows-1253"}, // ANSI Greek; Greek (Windows)
+ {1254, "windows-1254"}, // ANSI Turkish; Turkish (Windows)
+ {1255, "windows-1255"}, // ANSI Hebrew; Hebrew (Windows)
+ {1256, "windows-1256"}, // ANSI Arabic; Arabic (Windows)
+ {1257, "windows-1257"}, // ANSI Baltic; Baltic (Windows)
+ {1258, "windows-1258"}, // ANSI/OEM Vietnamese; Vietnamese (Windows)
+ {1361, "Johab"}, // Korean (Johab)
+ {10000, "macintosh"}, // MAC Roman; Western European (Mac)
+ {10001, "x-mac-japanese"}, // Japanese (Mac)
+ {10002, "x-mac-chinesetrad"}, // MAC Traditional Chinese (Big5); Chinese
+ // Traditional (Mac)
+ {10003, "x-mac-korean"}, // Korean (Mac)
+ {10004, "x-mac-arabic"}, // Arabic (Mac)
+ {10005, "x-mac-hebrew"}, // Hebrew (Mac)
+ {10006, "x-mac-greek"}, // Greek (Mac)
+ {10007, "x-mac-cyrillic"}, // Cyrillic (Mac)
+ {10008, "x-mac-chinesesimp"}, // MAC Simplified Chinese (GB 2312);
+ // Chinese Simplified (Mac)
+ {10010, "x-mac-romanian"}, // Romanian (Mac)
+ {10017, "x-mac-ukrainian"}, // Ukrainian (Mac)
+ {10021, "x-mac-thai"}, // Thai (Mac)
+ {10029, "x-mac-ce"}, // MAC Latin 2; Central European (Mac)
+ {10079, "x-mac-icelandic"}, // Icelandic (Mac)
+ {10081, "x-mac-turkish"}, // Turkish (Mac)
+ {10082, "x-mac-croatian"}, // Croatian (Mac)
+ // Unicode UTF-32, little endian byte order; available only to managed
+ // applications impossible in 8-bit mail
+ {12000, "utf-32"},
+ // Unicode UTF-32, big endian byte order; available only to managed
+ // applications impossible in 8-bit mail
+ {12001, "utf-32BE"},
+ {20000, "x-Chinese_CNS"}, // CNS Taiwan; Chinese Traditional (CNS)
+ {20001, "x-cp20001"}, // TCA Taiwan
+ {20002, "x_Chinese-Eten"}, // Eten Taiwan; Chinese Traditional (Eten)
+ {20003, "x-cp20003"}, // IBM5550 Taiwan
+ {20004, "x-cp20004"}, // TeleText Taiwan
+ {20005, "x-cp20005"}, // Wang Taiwan
+ {20105, "x-IA5"}, // IA5 (IRV International Alphabet No. 5, 7-bit);
+ // Western European (IA5)
+ {20106, "x-IA5-German"}, // IA5 German (7-bit)
+ {20107, "x-IA5-Swedish"}, // IA5 Swedish (7-bit)
+ {20108, "x-IA5-Norwegian"}, // IA5 Norwegian (7-bit)
+ {20127, "us-ascii"}, // US-ASCII (7-bit)
+ {20261, "x-cp20261"}, // T.61
+ {20269, "x-cp20269"}, // ISO 6937 Non-Spacing Accent
+ {20273, "IBM273"}, // IBM EBCDIC Germany
+ {20277, "IBM277"}, // IBM EBCDIC Denmark-Norway
+ {20278, "IBM278"}, // IBM EBCDIC Finland-Sweden
+ {20280, "IBM280"}, // IBM EBCDIC Italy
+ {20284, "IBM284"}, // IBM EBCDIC Latin America-Spain
+ {20285, "IBM285"}, // IBM EBCDIC United Kingdom
+ {20290, "IBM290"}, // IBM EBCDIC Japanese Katakana Extended
+ {20297, "IBM297"}, // IBM EBCDIC France
+ {20420, "IBM420"}, // IBM EBCDIC Arabic
+ {20423, "IBM423"}, // IBM EBCDIC Greek
+ {20424, "IBM424"}, // IBM EBCDIC Hebrew
+ {20833, "x-EBCDIC-KoreanExtended"}, // IBM EBCDIC Korean Extended
+ {20838, "IBM-Thai"}, // IBM EBCDIC Thai
+ {20866, "koi8-r"}, // Russian (KOI8-R); Cyrillic (KOI8-R)
+ {20871, "IBM871"}, // IBM EBCDIC Icelandic
+ {20880, "IBM880"}, // IBM EBCDIC Cyrillic Russian
+ {20905, "IBM905"}, // IBM EBCDIC Turkish
+ {20924,
+ "IBM00924"}, // IBM EBCDIC Latin 1/Open System (1047 + Euro symbol)
+ {20932, "EUC-JP"}, // Japanese (JIS 0208-1990 and 0121-1990)
+ {20936, "x-cp20936"}, // Simplified Chinese (GB2312); Chinese Simplified
+ // (GB2312-80)
+ {20949, "x-cp20949"}, // Korean Wansung
+ {21025, "cp1025"}, // IBM EBCDIC Cyrillic Serbian-Bulgarian
+ // 21027 (deprecated)
+ {21866, "koi8-u"}, // Ukrainian (KOI8-U); Cyrillic (KOI8-U)
+ {28591, "iso-8859-1"}, // ISO 8859-1 Latin 1; Western European (ISO)
+ {28592,
+ "iso-8859-2"}, // ISO 8859-2 Central European; Central European (ISO)
+ {28593, "iso-8859-3"}, // ISO 8859-3 Latin 3
+ {28594, "iso-8859-4"}, // ISO 8859-4 Baltic
+ {28595, "iso-8859-5"}, // ISO 8859-5 Cyrillic
+ {28596, "iso-8859-6"}, // ISO 8859-6 Arabic
+ {28597, "iso-8859-7"}, // ISO 8859-7 Greek
+ {28598, "iso-8859-8"}, // ISO 8859-8 Hebrew; Hebrew (ISO-Visual)
+ {28599, "iso-8859-9"}, // ISO 8859-9 Turkish
+ {28603, "iso-8859-13"}, // ISO 8859-13 Estonian
+ {28605, "iso-8859-15"}, // ISO 8859-15 Latin 9
+ {29001, "x-Europa"}, // Europa 3
+ {38598, "iso-8859-8-i"}, // ISO 8859-8 Hebrew; Hebrew (ISO-Logical)
+ {50220, "iso-2022-jp"}, // ISO 2022 Japanese with no halfwidth Katakana;
+ // Japanese (JIS)
+ {50221, "csISO2022JP"}, // ISO 2022 Japanese with halfwidth Katakana;
+ // Japanese (JIS-Allow 1 byte Kana)
+ {50222, "iso-2022-jp"}, // ISO 2022 Japanese JIS X 0201-1989; Japanese
+ // (JIS-Allow 1 byte Kana - SO/SI)
+ {50225, "iso-2022-kr"}, // ISO 2022 Korean
+ {50227, "x-cp50227"}, // ISO 2022 Simplified Chinese; Chinese Simplified
+ // (ISO 2022)
+ // 50229 ISO 2022 Traditional Chinese
+ // 50930 EBCDIC Japanese (Katakana) Extended
+ // 50931 EBCDIC US-Canada and Japanese
+ // 50933 EBCDIC Korean Extended and Korean
+ // 50935 EBCDIC Simplified Chinese Extended and Simplified Chinese
+ // 50936 EBCDIC Simplified Chinese
+ // 50937 EBCDIC US-Canada and Traditional Chinese
+ // 50939 EBCDIC Japanese (Latin) Extended and Japanese
+ {51932, "euc-jp"}, // EUC Japanese
+ {51936, "EUC-CN"}, // EUC Simplified Chinese; Chinese Simplified (EUC)
+ {51949, "euc-kr"}, // EUC Korean
+ // 51950 EUC Traditional Chinese
+ {52936,
+ "hz-gb-2312"}, // HZ-GB2312 Simplified Chinese; Chinese Simplified (HZ)
+ {54936, "GB18030"}, // Windows XP and later: GB18030 Simplified Chinese
+ // (4 byte); Chinese Simplified (GB18030)
+ {57002, "x-iscii-de"}, // ISCII Devanagari
+ {57003, "x-iscii-be"}, // ISCII Bengali
+ {57004, "x-iscii-ta"}, // ISCII Tamil
+ {57005, "x-iscii-te"}, // ISCII Telugu
+ {57006, "x-iscii-as"}, // ISCII Assamese
+ {57007, "x-iscii-or"}, // ISCII Oriya (Odia)
+ {57008, "x-iscii-ka"}, // ISCII Kannada
+ {57009, "x-iscii-ma"}, // ISCII Malayalam
+ {57010, "x-iscii-gu"}, // ISCII Gujarati
+ {57011, "x-iscii-pa"}, // ISCII Punjabi
+ {65000, "utf-7"}, // Unicode (UTF-7)
+ {65001, "utf-8"}, // Unicode (UTF-8)
+ };
+
+ // Binary search
+ int begin = 0, end = sizeof(cptocharset) / sizeof(cptocharset[0]) - 1;
+ while (begin <= end) {
+ int mid = (begin + end) / 2;
+ unsigned int mid_cp = cptocharset[mid].cp;
+ if (cp == mid_cp) return cptocharset[mid].charset;
+ if (cp < mid_cp)
+ end = mid - 1;
+ else // cp > cptocharset[mid].cp
+ begin = mid + 1;
+ }
+ return 0; // not found
+}
+
+// This function returns true only if the unicode (utf-16) text can be
+// losslessly represented in specified charset
+bool CMapiMessage::CheckBodyInCharsetRange(const char* charset) {
+ if (m_body.IsEmpty()) return true;
+ if (!_stricmp(charset, "utf-8")) return true;
+
+ auto encoding =
+ mozilla::Encoding::ForLabelNoReplacement(nsDependentCString(charset));
+ if (!encoding) return false;
+ auto encoder = encoding->NewEncoder();
+
+ uint8_t buffer[512];
+ auto src = mozilla::Span(m_body);
+ auto dst = mozilla::Span(buffer);
+ while (true) {
+ uint32_t result;
+ size_t read;
+ std::tie(result, read, std::ignore) =
+ encoder->EncodeFromUTF16WithoutReplacement(src, dst, false);
+ if (result == mozilla::kInputEmpty) {
+ // All converted successfully.
+ break;
+ } else if (result != mozilla::kOutputFull) {
+ // Didn't use all the input but the output isn't full, hence
+ // there was an unencodable character.
+ return false;
+ }
+ src = src.From(read);
+ }
+
+ return true;
+}
+
+bool CaseInsensitiveComp(wchar_t elem1, wchar_t elem2) {
+ return _wcsnicmp(&elem1, &elem2, 1) == 0;
+}
+
+void ExtractMetaCharset(const wchar_t* body, int bodySz,
+ /*out*/ nsCString& charset) {
+ charset.Truncate();
+ const wchar_t* body_end = body + bodySz;
+ const wchar_t str_eohd[] = L"/head";
+ const wchar_t* str_eohd_end =
+ str_eohd + sizeof(str_eohd) / sizeof(str_eohd[0]) - 1;
+ const wchar_t* eohd_pos =
+ std::search(body, body_end, str_eohd, str_eohd_end, CaseInsensitiveComp);
+ if (eohd_pos == body_end) // No header!
+ return;
+ const wchar_t str_chset[] = L"charset=";
+ const wchar_t* str_chset_end =
+ str_chset + sizeof(str_chset) / sizeof(str_chset[0]) - 1;
+ const wchar_t* chset_pos = std::search(body, eohd_pos, str_chset,
+ str_chset_end, CaseInsensitiveComp);
+ if (chset_pos == eohd_pos) // No charset!
+ return;
+ chset_pos += 8;
+
+ // remove everything from the string after the next ; or " or space,
+ // whichever comes first.
+ // The initial string looks something like
+ // <META content="text/html; charset=utf-8" http-equiv=Content-Type>
+ // <META content="text/html; charset=utf-8;" http-equiv=Content-Type>
+ // <META content="text/html; charset=utf-8 ;" http-equiv=Content-Type>
+ // <META content="text/html; charset=utf-8 " http-equiv=Content-Type>
+ const wchar_t term[] = L";\" ",
+ *term_end = term + sizeof(term) / sizeof(term[0]) - 1;
+ const wchar_t* chset_end =
+ std::find_first_of(chset_pos, eohd_pos, term, term_end);
+ if (chset_end != eohd_pos)
+ LossyCopyUTF16toASCII(
+ Substring(char16ptr_t(chset_pos), char16ptr_t(chset_end)), charset);
+}
+
+bool CMapiMessage::FetchBody(void) {
+ m_bodyIsHtml = false;
+ m_body.Truncate();
+
+ // Get the Outlook codepage info; if unsuccessful then it defaults to 0
+ // (CP_ACP) -> system default Maybe we can use this info later?
+ unsigned int codepage = 0;
+ LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_INTERNET_CPID);
+ if (pVal) {
+ if (PROP_TYPE(pVal->ulPropTag) == PT_LONG) codepage = pVal->Value.l;
+ CMapiApi::MAPIFreeBuffer(pVal);
+ }
+
+ unsigned long nativeBodyType = 0;
+ if (CMapiApi::GetRTFPropertyDecodedAsUTF16(m_lpMsg, m_body, nativeBodyType,
+ codepage)) {
+ m_bodyIsHtml = nativeBodyType == MAPI_NATIVE_BODY_TYPE_HTML;
+ } else { // Cannot get RTF version
+ // Is it html?
+ pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_BODY_HTML_W);
+ if (pVal) {
+ if (CMapiApi::IsLargeProperty(pVal))
+ CMapiApi::GetLargeStringProperty(m_lpMsg, PR_BODY_HTML_W, m_body);
+ else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) &&
+ (pVal->Value.lpszW) && (*(pVal->Value.lpszW)))
+ m_body.Assign(pVal->Value.lpszW);
+ CMapiApi::MAPIFreeBuffer(pVal);
+ }
+
+ // Kind-hearted Outlook will give us html even for a plain text message.
+ // But it will include a comment saying it did the conversion.
+ // We'll use this as a hack to really use the plain text part.
+ //
+ // Sadly there are cases where this string is returned despite the fact
+ // that the message is indeed HTML.
+ //
+ // To detect the "true" plain text messages, we look for our string
+ // immediately following the <BODY> tag.
+ if (!m_body.IsEmpty() &&
+ m_body.Find(u"<BODY>\r\n<!-- Converted from text/plain format -->") ==
+ kNotFound) {
+ m_bodyIsHtml = true;
+ } else {
+ pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_BODY_W);
+ if (pVal) {
+ if (CMapiApi::IsLargeProperty(pVal))
+ CMapiApi::GetLargeStringProperty(m_lpMsg, PR_BODY_W, m_body);
+ else if ((PROP_TYPE(pVal->ulPropTag) == PT_UNICODE) &&
+ (pVal->Value.lpszW) && (*(pVal->Value.lpszW)))
+ m_body.Assign(pVal->Value.lpszW);
+ CMapiApi::MAPIFreeBuffer(pVal);
+ }
+ }
+ }
+
+ // OK, now let's restore the original encoding!
+ // 1. We may have a header defining the charset (we already called the
+ // FetchHeaders(), and there ProcessHeaders();
+ // in this case, the m_mimeCharset is set. See
+ // nsOutlookMail::ImportMailbox())
+ // 2. We may have the codepage walue provided by Outlook ("codepage" at the
+ // very beginning of this function)
+ // 3. We may have an HTML charset header.
+
+ bool bFoundCharset = false;
+
+ if (!m_mimeCharset.IsEmpty()) // The top-level header data
+ bFoundCharset = CheckBodyInCharsetRange(m_mimeCharset.get());
+ // No valid charset in the message header - try the HTML header.
+ // arguably may be useless
+ if (!bFoundCharset && m_bodyIsHtml) {
+ ExtractMetaCharset(m_body.get(), m_body.Length(), m_mimeCharset);
+ if (!m_mimeCharset.IsEmpty())
+ bFoundCharset = CheckBodyInCharsetRange(m_mimeCharset.get());
+ }
+ // Get from Outlook (seems like it keeps the MIME part header encoding info)
+ if (!bFoundCharset && codepage) {
+ const char* charset = CpToCharset(codepage);
+ if (charset) {
+ bFoundCharset = CheckBodyInCharsetRange(charset);
+ if (bFoundCharset) m_mimeCharset.Assign(charset);
+ }
+ }
+ if (!bFoundCharset) // Everything else failed, let's use the lossless
+ // utf-8...
+ m_mimeCharset.AssignLiteral("utf-8");
+
+ MAPI_DUMP_STRING(m_body.get());
+ MAPI_TRACE0("\r\n");
+
+ return true;
+}
+
+void CMapiMessage::GetBody(nsCString& dest) const {
+ nsMsgI18NConvertFromUnicode(m_mimeCharset, m_body, dest);
+}
+
+void CMapiMessage::FetchFlags(void) {
+ LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_MESSAGE_FLAGS);
+ if (pVal) m_msgFlags = CMapiApi::GetLongFromProp(pVal);
+ pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_LAST_VERB_EXECUTED);
+ if (pVal) m_msgLastVerb = CMapiApi::GetLongFromProp(pVal);
+}
+
+enum { ieidPR_ATTACH_NUM = 0, ieidAttachMax };
+
+static const SizedSPropTagArray(ieidAttachMax, ptaEid) = {ieidAttachMax,
+ {PR_ATTACH_NUM}};
+
+bool CMapiMessage::IterateAttachTable(LPMAPITABLE lpTable) {
+ ULONG rowCount;
+ HRESULT hr = lpTable->GetRowCount(0, &rowCount);
+ if (!rowCount) {
+ return true;
+ }
+
+ hr = lpTable->SetColumns((LPSPropTagArray)&ptaEid, 0);
+ if (FAILED(hr)) {
+ MAPI_TRACE2("SetColumns for attachment table failed: 0x%lx, %d\r\n",
+ (long)hr, (int)hr);
+ return false;
+ }
+
+ hr = lpTable->SeekRow(BOOKMARK_BEGINNING, 0, NULL);
+ if (FAILED(hr)) {
+ MAPI_TRACE2("SeekRow for attachment table failed: 0x%lx, %d\r\n", (long)hr,
+ (int)hr);
+ return false;
+ }
+
+ int cNumRows = 0;
+ LPSRowSet lpRow;
+ bool bResult = true;
+ do {
+ lpRow = NULL;
+ hr = lpTable->QueryRows(1, 0, &lpRow);
+
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE2("QueryRows for attachment table failed: 0x%lx, %d\n",
+ (long)hr, (int)hr);
+ bResult = false;
+ break;
+ }
+
+ if (lpRow) {
+ cNumRows = lpRow->cRows;
+
+ if (cNumRows) {
+ DWORD aNum = lpRow->aRow[0].lpProps[ieidPR_ATTACH_NUM].Value.ul;
+ AddAttachment(aNum);
+ MAPI_TRACE1("\t\t****Attachment found - #%d\r\n", (int)aNum);
+ }
+ CMapiApi::FreeProws(lpRow);
+ }
+
+ } while (SUCCEEDED(hr) && cNumRows && lpRow);
+
+ return bResult;
+}
+
+bool CMapiMessage::GetTmpFile(/*out*/ nsIFile** aResult) {
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = GetSpecialDirectoryWithFileName(
+ NS_OS_TEMP_DIR, "mapiattach.tmp", getter_AddRefs(tmpFile));
+ if (NS_FAILED(rv)) return false;
+
+ rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ if (NS_FAILED(rv)) return false;
+
+ tmpFile.forget(aResult);
+ return true;
+}
+
+bool CMapiMessage::CopyMsgAttachToFile(LPATTACH lpAttach,
+ /*out*/ nsIFile** tmp_file) {
+ LPMESSAGE lpMsg;
+ HRESULT hr = lpAttach->OpenProperty(PR_ATTACH_DATA_OBJ, &IID_IMessage, 0, 0,
+ reinterpret_cast<LPUNKNOWN*>(&lpMsg));
+ if (HR_FAILED(hr)) return false;
+
+ if (!GetTmpFile(tmp_file)) return false;
+
+ nsCOMPtr<nsIOutputStream> destOutputStream;
+ nsresult rv = MsgNewBufferedFileOutputStream(getter_AddRefs(destOutputStream),
+ *tmp_file, -1, 0600);
+ if (NS_SUCCEEDED(rv))
+ rv = ImportMailboxRunnable::ImportMessage(lpMsg, destOutputStream,
+ nsIMsgSend::nsMsgSaveAsDraft);
+
+ if (NS_FAILED(rv)) {
+ (*tmp_file)->Remove(false);
+ (*tmp_file)->Release();
+ *tmp_file = 0;
+ }
+
+ return NS_SUCCEEDED(rv);
+}
+
+bool CMapiMessage::CopyBinAttachToFile(LPATTACH lpAttach, nsIFile** tmp_file) {
+ nsCOMPtr<nsIFile> _tmp_file;
+ nsresult rv = GetSpecialDirectoryWithFileName(
+ NS_OS_TEMP_DIR, "mapiattach.tmp", getter_AddRefs(_tmp_file));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ rv = _tmp_file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 00600);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsString tmpPath = _tmp_file->NativePath();
+ // We have to use native charset unless we migrate to Outlook 2013 "W" API.
+ nsCString tmpNativePath;
+ rv = NS_CopyUnicodeToNative(tmpPath, tmpNativePath);
+ NS_ENSURE_SUCCESS(rv, false);
+ LPSTREAM lpStreamFile;
+ HRESULT hr = CMapiApi::OpenStreamOnFile(
+ gpMapiAllocateBuffer, gpMapiFreeBuffer, STGM_READWRITE | STGM_CREATE,
+ tmpNativePath.get(), NULL, &lpStreamFile);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE1("~~ERROR~~ OpenStreamOnFile failed - temp path: %s\r\n",
+ tmpNativePath.get());
+ return false;
+ }
+
+ bool bResult = true;
+ LPSTREAM lpAttachStream;
+ hr = lpAttach->OpenProperty(PR_ATTACH_DATA_BIN, &IID_IStream, 0, 0,
+ (LPUNKNOWN*)&lpAttachStream);
+
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE0("~~ERROR~~ OpenProperty failed for PR_ATTACH_DATA_BIN.\r\n");
+ lpAttachStream = NULL;
+ bResult = false;
+ } else {
+ STATSTG st;
+ hr = lpAttachStream->Stat(&st, STATFLAG_NONAME);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE0("~~ERROR~~ Stat failed for attachment stream\r\n");
+ bResult = false;
+ } else {
+ hr = lpAttachStream->CopyTo(lpStreamFile, st.cbSize, NULL, NULL);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE0("~~ERROR~~ Attach Stream CopyTo temp file failed.\r\n");
+ bResult = false;
+ }
+ }
+ }
+
+ if (lpAttachStream) lpAttachStream->Release();
+ lpStreamFile->Release();
+ if (!bResult)
+ _tmp_file->Remove(false);
+ else
+ _tmp_file.forget(tmp_file);
+
+ return bResult;
+}
+
+bool CMapiMessage::GetURL(nsIFile* aFile, nsIURI** url) {
+ if (!m_pIOService) return false;
+
+ nsresult rv = m_pIOService->NewFileURI(aFile, url);
+ return NS_SUCCEEDED(rv);
+}
+
+bool CMapiMessage::AddAttachment(DWORD aNum) {
+ LPATTACH lpAttach = NULL;
+ HRESULT hr = m_lpMsg->OpenAttach(aNum, NULL, 0, &lpAttach);
+ if (HR_FAILED(hr)) {
+ MAPI_TRACE2(
+ "\t\t****Attachment error, unable to open attachment: %d, 0x%lx\r\n",
+ idx, hr);
+ return false;
+ }
+
+ bool bResult = false;
+ attach_data* data = new attach_data;
+ ULONG aMethod;
+ if (data) {
+ bResult = true;
+
+ // 1. Get the file that contains the attachment data
+ LPSPropValue pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_METHOD);
+ if (pVal) {
+ aMethod = CMapiApi::GetLongFromProp(pVal);
+ switch (aMethod) {
+ case ATTACH_BY_VALUE:
+ MAPI_TRACE1("\t\t** Attachment #%d by value.\r\n", aNum);
+ bResult =
+ CopyBinAttachToFile(lpAttach, getter_AddRefs(data->tmp_file));
+ data->delete_file = true;
+ break;
+ case ATTACH_BY_REFERENCE:
+ case ATTACH_BY_REF_RESOLVE:
+ case ATTACH_BY_REF_ONLY:
+ pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_PATHNAME_W);
+ if (pVal) {
+ nsCString path;
+ CMapiApi::GetStringFromProp(pVal, path);
+ nsresult rv;
+ data->tmp_file = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_FAILED(rv) || !data->tmp_file) {
+ MAPI_TRACE0("*** Error creating file spec for attachment\n");
+ bResult = false;
+ } else
+ data->tmp_file->InitWithNativePath(path);
+ }
+ MAPI_TRACE2("\t\t** Attachment #%d by ref: %s\r\n", aNum,
+ m_attachPath.get());
+ break;
+ case ATTACH_EMBEDDED_MSG:
+ MAPI_TRACE1("\t\t** Attachment #%d by Embedded Message??\r\n", aNum);
+ // Convert the embedded IMessage from PR_ATTACH_DATA_OBJ to rfc822
+ // attachment (see
+ // http://msdn.microsoft.com/en-us/library/cc842329.aspx) This is a
+ // recursive call.
+ bResult =
+ CopyMsgAttachToFile(lpAttach, getter_AddRefs(data->tmp_file));
+ data->delete_file = true;
+ break;
+ case ATTACH_OLE:
+ MAPI_TRACE1("\t\t** Attachment #%d by OLE - yuck!!!\r\n", aNum);
+ break;
+ default:
+ MAPI_TRACE2(
+ "\t\t** Attachment #%d unknown attachment method - 0x%lx\r\n",
+ aNum, aMethod);
+ bResult = false;
+ }
+ } else
+ bResult = false;
+
+ if (bResult) bResult = data->tmp_file;
+
+ if (bResult) {
+ bool isFile = false;
+ bool exists = false;
+ data->tmp_file->Exists(&exists);
+ data->tmp_file->IsFile(&isFile);
+
+ if (!exists || !isFile) {
+ bResult = false;
+ MAPI_TRACE0("Attachment file does not exist\n");
+ }
+ }
+
+ if (bResult)
+ bResult = GetURL(data->tmp_file, getter_AddRefs(data->orig_url));
+
+ if (bResult) {
+ // Now we have the file; proceed to the other properties
+
+ data->encoding = NS_xstrdup(ENCODING_BINARY);
+
+ nsString fname, fext;
+ pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_LONG_FILENAME_W);
+ if (!pVal)
+ pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_FILENAME_W);
+ CMapiApi::GetStringFromProp(pVal, fname);
+ pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_EXTENSION_W);
+ CMapiApi::GetStringFromProp(pVal, fext);
+ MAPI_TRACE2("\t\t\t--- File name: %s, extension: %s\r\n", fname.get(),
+ fext.get());
+
+ if (fext.IsEmpty()) {
+ int idx = fname.RFindChar(L'.');
+ if (idx != -1) fext = Substring(fname, idx);
+ } else if (fname.RFindChar(L'.') == -1) {
+ fname += L".";
+ fname += fext;
+ }
+ if (fname.IsEmpty()) {
+ // If no description use "Attachment i" format.
+ fname = L"Attachment ";
+ fname.AppendInt(static_cast<uint32_t>(aNum));
+ }
+ data->real_name = ToNewUTF8String(fname);
+
+ nsCString tmp;
+ // We have converted it to the rfc822 document
+ if (aMethod == ATTACH_EMBEDDED_MSG) {
+ data->type = NS_xstrdup(MESSAGE_RFC822);
+ } else {
+ pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_MIME_TAG_A);
+ CMapiApi::GetStringFromProp(pVal, tmp);
+ MAPI_TRACE1("\t\t\t--- Mime type: %s\r\n", tmp.get());
+ if (tmp.IsEmpty()) {
+ uint8_t* pType = NULL;
+ if (!fext.IsEmpty()) {
+ pType = CMimeTypes::GetMimeType(fext);
+ }
+ if (pType)
+ data->type = NS_xstrdup((PC_S8)pType);
+ else
+ data->type = NS_xstrdup(APPLICATION_OCTET_STREAM);
+ } else
+ data->type = ToNewCString(tmp);
+ }
+
+ pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_CONTENT_ID_A);
+ CMapiApi::GetStringFromProp(pVal, tmp);
+ if (!tmp.IsEmpty()) data->cid = ToNewCString(tmp);
+ }
+ if (bResult) {
+ // Now we need to decide if this attachment is embedded or not.
+ // At first, I tried to simply check for the presence of the Content-Id.
+ // But it turned out that this method is unreliable, since there exist
+ // cases when an attachment has a Content-Id while isn't embedded (even in
+ // a message with a plain-text body!). So next I tried to look for <img>
+ // tags that contain the found Content-Id. But this is unreliable, too,
+ // because there exist cases where other places of HTML reference the
+ // embedded messages (e.g. it may be a background of a table cell, or some
+ // CSS; further, it is possible that the reference to an embedded object
+ // is not in the main body, but in another embedded object - like body
+ // references a CSS attachment that in turn references a picture as a
+ // background of its element). From the other hand, it's unreliable to
+ // relax the search criteria to any occurrence of the Content-Id string in
+ // the body - partly because the string may be simply in a text or other
+ // non-referencing part, partly because of the abovementioned possibility
+ // that the reference is outside the body at all. There exist the
+ // PR_ATTACH_FLAGS property of the attachment. The MS documentation tells
+ // about two possible flags in it: ATT_INVISIBLE_IN_HTML and
+ // ATT_INVISIBLE_IN_RTF. There is at least one more undocumented flag:
+ // ATT_MHTML_REF. Some sources in Internet suggest simply check for the
+ // latter flag to distinguish between the embedded and ordinary
+ // attachments. But my observations indicate that even if the flags don't
+ // include ATT_MHTML_REF, the attachment is still may be embedded.
+ // However, my observations always show that the message is embedded if
+ // the flags is not 0. So now I will simply test for the non-zero flags to
+ // decide whether the attachment is embedded or not. Possible advantage is
+ // reliability (I hope). Another advantage is that it's much faster than
+ // search the body for Content-Id.
+
+ DWORD flags = 0;
+
+ pVal = CMapiApi::GetMapiProperty(lpAttach, PR_ATTACH_FLAGS);
+ if (pVal) flags = CMapiApi::GetLongFromProp(pVal);
+ if (m_bodyIsHtml && data->cid &&
+ (flags != 0)) // this is the embedded attachment
+ m_embattachments.push_back(data);
+ else // this is ordinary attachment
+ m_stdattachments.push_back(data);
+ } else {
+ delete data;
+ }
+ }
+
+ lpAttach->Release();
+ return bResult;
+}
+
+void CMapiMessage::ClearAttachment(attach_data* data) {
+ if (data->delete_file && data->tmp_file) data->tmp_file->Remove(false);
+
+ if (data->type) free(data->type);
+ if (data->encoding) free(data->encoding);
+ if (data->real_name) free(data->real_name);
+ if (data->cid) free(data->cid);
+
+ delete data;
+}
+
+void CMapiMessage::ClearAttachments() {
+ std::for_each(m_stdattachments.begin(), m_stdattachments.end(),
+ ClearAttachment);
+ m_stdattachments.clear();
+ std::for_each(m_embattachments.begin(), m_embattachments.end(),
+ ClearAttachment);
+ m_embattachments.clear();
+}
+
+// This method must be called AFTER the retrieval of the body,
+// since the decision if an attachment is embedded or not is made
+// based on the body type and contents
+void CMapiMessage::ProcessAttachments() {
+ LPSPropValue pVal = CMapiApi::GetMapiProperty(m_lpMsg, PR_HASATTACH);
+ bool hasAttach = true;
+
+ if (pVal) {
+ if (PROP_TYPE(pVal->ulPropTag) == PT_BOOLEAN)
+ hasAttach = (pVal->Value.b != 0);
+ CMapiApi::MAPIFreeBuffer(pVal);
+ }
+
+ if (!hasAttach) return;
+
+ // Get the attachment table?
+ LPMAPITABLE pTable = NULL;
+ HRESULT hr = m_lpMsg->GetAttachmentTable(0, &pTable);
+ if (FAILED(hr) || !pTable) return;
+ IterateAttachTable(pTable);
+ pTable->Release();
+}
+
+nsresult CMapiMessage::GetAttachments(
+ nsTArray<RefPtr<nsIMsgAttachedFile>>& attachments) {
+ attachments.Clear();
+ attachments.SetCapacity(m_stdattachments.size());
+
+ for (std::vector<attach_data*>::const_iterator it = m_stdattachments.begin();
+ it != m_stdattachments.end(); it++) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgAttachedFile> a(
+ do_CreateInstance("@mozilla.org/messengercompose/attachedfile;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ a->SetOrigUrl((*it)->orig_url);
+ a->SetTmpFile((*it)->tmp_file);
+ a->SetEncoding(nsDependentCString((*it)->encoding));
+ a->SetRealName(nsDependentCString((*it)->real_name));
+ a->SetType(nsDependentCString((*it)->type));
+ attachments.AppendElement(a);
+ }
+ return NS_OK;
+}
+
+bool CMapiMessage::GetEmbeddedAttachmentInfo(unsigned int i, nsIURI** uri,
+ const char** cid,
+ const char** name) const {
+ if (i >= m_embattachments.size()) return false;
+ attach_data* data = m_embattachments[i];
+ if (!data) return false;
+ *uri = data->orig_url;
+ *cid = data->cid;
+ *name = data->real_name;
+ return true;
+}
+
+//////////////////////////////////////////////////////
+
+// begin and end MUST point to the same string
+char* dup(const char* begin, const char* end) {
+ if (begin >= end) return 0;
+ char* str = new char[end - begin + 1];
+ memcpy(str, begin, (end - begin) * sizeof(begin[0]));
+ str[end - begin] = 0;
+ return str;
+}
+
+// See RFC822
+// 9 = '\t', 32 = ' '.
+inline bool IsPrintableASCII(char c) { return (c > ' ') && (c < 127); }
+inline bool IsWSP(char c) { return (c == ' ') || (c == '\t'); }
+
+CMapiMessageHeaders::CHeaderField::CHeaderField(const char* begin, int len)
+ : m_fname(0), m_fbody(0), m_fbody_utf8(false) {
+ const char *end = begin + len, *fname_end = begin;
+ while ((fname_end < end) && IsPrintableASCII(*fname_end) &&
+ (*fname_end != ':'))
+ ++fname_end;
+ if ((fname_end == end) || (*fname_end != ':')) return; // Not a valid header!
+ m_fname = dup(begin, fname_end + 1); // including colon
+ m_fbody = dup(fname_end + 1, end);
+}
+
+CMapiMessageHeaders::CHeaderField::CHeaderField(const char* name,
+ const char* body, bool utf8)
+ : m_fname(dup(name, name + strlen(name))),
+ m_fbody(dup(body, body + strlen(body))),
+ m_fbody_utf8(utf8) {}
+
+CMapiMessageHeaders::CHeaderField::~CHeaderField() {
+ delete[] m_fname;
+ delete[] m_fbody;
+}
+
+void CMapiMessageHeaders::CHeaderField::set_fbody(const char* txt) {
+ if (m_fbody == txt) return; // to avoid assigning to self
+ char* oldbody = m_fbody;
+ m_fbody = dup(txt, txt + strlen(txt));
+ delete[] oldbody;
+ m_fbody_utf8 = true;
+}
+
+void CMapiMessageHeaders::CHeaderField::GetUnfoldedString(
+ nsString& dest, const char* fallbackCharset) const {
+ dest.Truncate();
+ if (!m_fbody) return;
+
+ nsCString unfolded;
+ const char* pos = m_fbody;
+ while (*pos) {
+ if ((*pos == nsCRT::CR) && (*(pos + 1) == nsCRT::LF) && IsWSP(*(pos + 2)))
+ pos += 2; // Skip CRLF if it is followed by SPACE or TAB
+ else
+ unfolded.Append(*(pos++));
+ }
+ if (m_fbody_utf8)
+ CopyUTF8toUTF16(unfolded, dest);
+ else
+ nsMsgI18NConvertToUnicode(
+ fallbackCharset ? nsDependentCString(fallbackCharset) : EmptyCString(),
+ unfolded, dest);
+}
+
+////////////////////////////////////////
+
+const char* CMapiMessageHeaders::Specials[hdrMax] = {
+ "Date:", "From:", "Sender:", "Reply-To:",
+ "To:", "Cc:", "Bcc:", "Message-ID:",
+ "Subject:", "Mime-Version:", "Content-Type:", "Content-Transfer-Encoding:"};
+
+CMapiMessageHeaders::~CMapiMessageHeaders() { ClearHeaderFields(); }
+
+void CMapiMessageHeaders::Delete(CHeaderField* p) { delete p; }
+
+void CMapiMessageHeaders::ClearHeaderFields() {
+ std::for_each(m_headerFields.begin(), m_headerFields.end(), Delete);
+ m_headerFields.clear();
+}
+
+void CMapiMessageHeaders::Assign(const char* headers) {
+ for (int i = 0; i < hdrMax; i++) m_SpecialHeaders[i] = 0;
+ ClearHeaderFields();
+ if (!headers) return;
+
+ const char *start = headers, *end = headers;
+ while (*end) {
+ if ((*end == nsCRT::CR) && (*(end + 1) == nsCRT::LF)) {
+ if (!IsWSP(
+ *(end +
+ 2))) { // Not SPACE nor TAB (avoid FSP) -> next header or EOF
+ Add(new CHeaderField(start, end - start));
+ start = ++end + 1;
+ }
+ }
+ ++end;
+ }
+
+ if (start < end) { // Last header left
+ Add(new CHeaderField(start, end - start));
+ }
+}
+
+void CMapiMessageHeaders::Add(CHeaderField* f) {
+ if (!f) return;
+ if (!f->Valid()) {
+ delete f;
+ return;
+ }
+
+ SpecialHeader idx = CheckSpecialHeader(f->fname());
+ if (idx != hdrNone) {
+ // Now check if the special header was already inserted;
+ // if so, remove previous and add this new
+ CHeaderField* PrevSpecial = m_SpecialHeaders[idx];
+ if (PrevSpecial) {
+ std::vector<CHeaderField*>::iterator iter =
+ std::find(m_headerFields.begin(), m_headerFields.end(), PrevSpecial);
+ if (iter != m_headerFields.end()) m_headerFields.erase(iter);
+ delete PrevSpecial;
+ }
+ m_SpecialHeaders[idx] = f;
+ }
+ m_headerFields.push_back(f);
+}
+
+CMapiMessageHeaders::SpecialHeader CMapiMessageHeaders::CheckSpecialHeader(
+ const char* fname) {
+ for (int i = hdrFirst; i < hdrMax; i++)
+ if (stricmp(fname, Specials[i]) == 0) return static_cast<SpecialHeader>(i);
+
+ return hdrNone;
+}
+
+const CMapiMessageHeaders::CHeaderField* CMapiMessageHeaders::CFind(
+ const char* name) const {
+ SpecialHeader special = CheckSpecialHeader(name);
+ if ((special > hdrNone) && (special < hdrMax))
+ return m_SpecialHeaders[special]; // No need to search further, because it
+ // MUST be here
+
+ std::vector<CHeaderField*>::const_iterator iter = std::find_if(
+ m_headerFields.begin(), m_headerFields.end(), fname_equals(name));
+ if (iter == m_headerFields.end()) return 0;
+ return *iter;
+}
+
+const char* CMapiMessageHeaders::SpecialName(SpecialHeader special) {
+ if ((special <= hdrNone) || (special >= hdrMax)) return 0;
+ return Specials[special];
+}
+
+const char* CMapiMessageHeaders::Value(SpecialHeader special) const {
+ if ((special <= hdrNone) || (special >= hdrMax)) return 0;
+ return (m_SpecialHeaders[special]) ? m_SpecialHeaders[special]->fbody() : 0;
+}
+
+const char* CMapiMessageHeaders::Value(const char* name) const {
+ const CHeaderField* result = CFind(name);
+ return result ? result->fbody() : 0;
+}
+
+void CMapiMessageHeaders::UnfoldValue(const char* name, nsString& dest,
+ const char* fallbackCharset) const {
+ const CHeaderField* result = CFind(name);
+ if (result)
+ result->GetUnfoldedString(dest, fallbackCharset);
+ else
+ dest.Truncate();
+}
+
+void CMapiMessageHeaders::UnfoldValue(SpecialHeader special, nsString& dest,
+ const char* fallbackCharset) const {
+ if ((special <= hdrNone) || (special >= hdrMax) ||
+ (!m_SpecialHeaders[special]))
+ dest.Truncate();
+ else
+ m_SpecialHeaders[special]->GetUnfoldedString(dest, fallbackCharset);
+}
+
+int CMapiMessageHeaders::SetValue(const char* name, const char* value,
+ bool replace) {
+ if (!replace) {
+ CHeaderField* result = Find(name);
+ if (result) {
+ result->set_fbody(value);
+ return 0;
+ }
+ }
+ Add(new CHeaderField(name, value, true));
+ return 0; // No sensible result is returned; maybe do something senseful
+ // later
+}
+
+int CMapiMessageHeaders::SetValue(SpecialHeader special, const char* value) {
+ CHeaderField* result = m_SpecialHeaders[special];
+ if (result)
+ result->set_fbody(value);
+ else
+ Add(new CHeaderField(Specials[special], value, true));
+ return 0;
+}
+
+void CMapiMessageHeaders::write_to_stream::operator()(const CHeaderField* f) {
+ if (!f || NS_FAILED(m_rv)) return;
+
+ uint32_t written;
+ m_rv = m_pDst->Write(f->fname(), strlen(f->fname()), &written);
+ NS_ENSURE_SUCCESS_VOID(m_rv);
+ if (f->fbody()) {
+ m_rv = m_pDst->Write(f->fbody(), strlen(f->fbody()), &written);
+ NS_ENSURE_SUCCESS_VOID(m_rv);
+ }
+ m_rv = m_pDst->Write("\x0D\x0A", 2, &written);
+}
+
+nsresult CMapiMessageHeaders::ToStream(nsIOutputStream* pDst) const {
+ nsresult rv = std::for_each(m_headerFields.begin(), m_headerFields.end(),
+ write_to_stream(pDst));
+ if (NS_SUCCEEDED(rv)) {
+ uint32_t written;
+ rv = pDst->Write("\x0D\x0A", 2, &written); // Separator line
+ }
+ return rv;
+}
diff --git a/comm/mailnews/import/src/MapiMessage.h b/comm/mailnews/import/src/MapiMessage.h
new file mode 100644
index 0000000000..c06a84c57a
--- /dev/null
+++ b/comm/mailnews/import/src/MapiMessage.h
@@ -0,0 +1,290 @@
+/* -*- 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 MapiMessage_h___
+#define MapiMessage_h___
+
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsIFile.h"
+#include "nsIMsgSend.h"
+#include "MapiApi.h"
+
+#include <vector>
+
+#ifndef PR_LAST_VERB_EXECUTED
+# define PR_LAST_VERB_EXECUTED PROP_TAG(PT_LONG, 0x1081)
+#endif
+
+#define EXCHIVERB_REPLYTOSENDER (102)
+#define EXCHIVERB_REPLYTOALL (103)
+#define EXCHIVERB_FORWARD (104)
+
+#ifndef PR_ATTACH_CONTENT_ID
+# define PR_ATTACH_CONTENT_ID PROP_TAG(PT_TSTRING, 0x3712)
+#endif
+#ifndef PR_ATTACH_CONTENT_ID_W
+# define PR_ATTACH_CONTENT_ID_W PROP_TAG(PT_UNICODE, 0x3712)
+#endif
+#ifndef PR_ATTACH_CONTENT_ID_A
+# define PR_ATTACH_CONTENT_ID_A PROP_TAG(PT_STRING8, 0x3712)
+#endif
+
+#ifndef PR_ATTACH_FLAGS
+# define PR_ATTACH_FLAGS PROP_TAG(PT_LONG, 0x3714)
+#endif
+
+#ifndef ATT_INVISIBLE_IN_HTML
+# define ATT_INVISIBLE_IN_HTML (0x1)
+#endif
+#ifndef ATT_INVISIBLE_IN_RTF
+# define ATT_INVISIBLE_IN_RTF (0x2)
+#endif
+#ifndef ATT_MHTML_REF
+# define ATT_MHTML_REF (0x4)
+#endif
+
+//////////////////////////////////////////////////////////////////////////////
+
+class CMapiMessageHeaders {
+ public:
+ // Special headers that MUST appear at most once (see RFC822)
+ enum SpecialHeader {
+ hdrNone = -1,
+ hdrFirst = 0, // utility values
+ hdrDate = hdrFirst,
+ hdrFrom,
+ hdrSender,
+ hdrReplyTo,
+ hdrTo,
+ hdrCc,
+ hdrBcc,
+ hdrMessageID,
+ hdrSubject,
+ hdrMimeVersion,
+ hdrContentType,
+ hdrContentTransferEncoding,
+ hdrMax // utility value
+ };
+
+ explicit CMapiMessageHeaders(const char* headers = 0) { Assign(headers); }
+ ~CMapiMessageHeaders();
+ void Assign(const char* headers);
+
+ inline bool IsEmpty() const { return m_headerFields.empty(); }
+ // if no such header exists then 0 is returned, else the first value returned
+ const char* Value(const char* name) const;
+ // if no such header exists then 0 is returned
+ const char* Value(SpecialHeader special) const;
+
+ void UnfoldValue(const char* name, nsString& dest,
+ const char* fallbackCharset) const;
+ void UnfoldValue(SpecialHeader special, nsString& dest,
+ const char* fallbackCharset) const;
+
+ // value must be utf-8 or 7-bit; supposed that this function will be called
+ // when the charset of the value is known
+ // TODO: if replace is set, then all headers with this name will be removed
+ // and one with this value will be added, otherwise a new header is added
+ // (Unnecessary for now)
+ int SetValue(const char* name, const char* value, bool replace = true);
+ int SetValue(SpecialHeader special, const char* value);
+
+ static const char* SpecialName(SpecialHeader special);
+
+ nsresult ToStream(nsIOutputStream* pDst) const;
+
+ private:
+ class CHeaderField {
+ public:
+ CHeaderField(const char* begin, int len);
+ CHeaderField(const char* name, const char* body, bool utf8 = false);
+ ~CHeaderField();
+ inline bool Valid() const { return m_fname; }
+ inline const char* fname() const { return m_fname; }
+ inline const char* fbody() const { return m_fbody; }
+
+ // txt must be utf-8 or 7-bit; supposed that this function will be called
+ // when the charset of the txt is known
+ void set_fbody(const char* txt);
+
+ void GetUnfoldedString(nsString& dest, const char* fallbackCharset) const;
+
+ private:
+ char* m_fname;
+ char* m_fbody;
+ bool m_fbody_utf8;
+ }; // class HeaderField
+
+ class write_to_stream {
+ public:
+ explicit write_to_stream(nsIOutputStream* pDst)
+ : m_pDst(pDst), m_rv(NS_OK) {}
+ void operator()(const CHeaderField* f);
+ inline operator nsresult() const { return m_rv; }
+
+ private:
+ nsIOutputStream* m_pDst;
+ nsresult m_rv;
+ };
+
+ // Search helper
+ class fname_equals {
+ public:
+ explicit fname_equals(const char* search) : m_search(search) {}
+ inline bool operator()(const CHeaderField* f) const {
+ return stricmp(f->fname(), m_search) == 0;
+ }
+
+ private:
+ const char* m_search;
+ }; // class fname_equals
+
+ // The common array of special headers' names
+ static const char* Specials[hdrMax];
+
+ std::vector<CHeaderField*> m_headerFields;
+ CHeaderField* m_SpecialHeaders[hdrMax]; // Pointers into the m_headerFields
+
+ void ClearHeaderFields();
+ void Add(CHeaderField* f);
+ static void Delete(CHeaderField* p);
+ static SpecialHeader CheckSpecialHeader(const char* fname);
+ const CHeaderField* CFind(const char* name) const;
+ inline CHeaderField* Find(const char* name) {
+ return const_cast<CHeaderField*>(CFind(name));
+ }
+
+}; // class CMapiMessageHeaders
+
+//////////////////////////////////////////////////////
+
+class CMapiMessage {
+ public:
+ explicit CMapiMessage(LPMESSAGE lpMsg);
+ ~CMapiMessage();
+
+ // Attachments
+ // Ordinary (not embedded) attachments.
+ nsresult GetAttachments(nsTArray<RefPtr<nsIMsgAttachedFile>>& attachments);
+ // Embedded attachments
+ size_t EmbeddedAttachmentsCount() const { return m_embattachments.size(); }
+ bool GetEmbeddedAttachmentInfo(unsigned int i, nsIURI** uri, const char** cid,
+ const char** name) const;
+ // We don't check MSGFLAG_HASATTACH, since it returns true even if there are
+ // only embedded attachmentsin the message. TB only counts the ordinary
+ // attachments when shows the message status, so here we check only for the
+ // ordinary attachments.
+ inline bool HasAttach() const { return !m_stdattachments.empty(); }
+
+ // Retrieve info for message
+ inline bool BodyIsHtml(void) const { return m_bodyIsHtml; }
+ const char* GetFromLine(int& len) const {
+ if (m_fromLine.IsEmpty())
+ return NULL;
+ else {
+ len = m_fromLine.Length();
+ return m_fromLine.get();
+ }
+ }
+ inline CMapiMessageHeaders* GetHeaders() { return &m_headers; }
+ inline const wchar_t* GetBody(void) const { return m_body.get(); }
+ inline size_t GetBodyLen(void) const { return m_body.Length(); }
+ void GetBody(nsCString& dest) const;
+ inline const char* GetBodyCharset(void) const { return m_mimeCharset.get(); }
+ inline bool IsRead() const { return m_msgFlags & MSGFLAG_READ; }
+ inline bool IsReplied() const {
+ return (m_msgLastVerb == EXCHIVERB_REPLYTOSENDER) ||
+ (m_msgLastVerb == EXCHIVERB_REPLYTOALL);
+ }
+ inline bool IsForvarded() const { return m_msgLastVerb == EXCHIVERB_FORWARD; }
+
+ bool HasContentHeader(void) const { return !m_mimeContentType.IsEmpty(); }
+ bool HasMimeVersion(void) const {
+ return m_headers.Value(CMapiMessageHeaders::hdrMimeVersion);
+ }
+ const char* GetMimeContent(void) const { return m_mimeContentType.get(); }
+ int32_t GetMimeContentLen(void) const { return m_mimeContentType.Length(); }
+ const char* GetMimeBoundary(void) const { return m_mimeBoundary.get(); }
+
+ // The only required part of a message is its header
+ inline bool ValidState() const { return !m_headers.IsEmpty(); }
+ inline bool FullMessageDownloaded() const { return !m_dldStateHeadersOnly; }
+
+ private:
+ struct attach_data {
+ nsCOMPtr<nsIURI> orig_url;
+ nsCOMPtr<nsIFile> tmp_file;
+ char* type;
+ char* encoding;
+ char* real_name;
+ char* cid;
+ bool delete_file;
+ attach_data()
+ : type(0), encoding(0), real_name(0), cid(0), delete_file(false) {}
+ };
+
+ static const nsCString m_whitespace;
+
+ LPMESSAGE m_lpMsg;
+
+ bool m_dldStateHeadersOnly; // if the message has not been downloaded yet
+ CMapiMessageHeaders m_headers;
+ nsCString m_fromLine; // utf-8
+ nsCString m_mimeContentType; // utf-8
+ nsCString m_mimeBoundary; // utf-8
+ nsCString m_mimeCharset; // utf-8
+
+ std::vector<attach_data*> m_stdattachments;
+ std::vector<attach_data*> m_embattachments; // Embedded
+
+ nsString m_body; // to be converted from UTF-16 using m_mimeCharset
+ bool m_bodyIsHtml;
+
+ uint32_t m_msgFlags;
+ uint32_t m_msgLastVerb;
+
+ nsCOMPtr<nsIIOService> m_pIOService;
+
+ void GetDownloadState();
+
+ // Headers - fetch will get PR_TRANSPORT_MESSAGE_HEADERS
+ // or if they do not exist will build a header from
+ // PR_DISPLAY_TO, _CC, _BCC
+ // PR_SUBJECT
+ // PR_MESSAGE_RECIPIENTS
+ // and PR_CREATION_TIME if needed?
+ bool FetchHeaders(void);
+ bool FetchBody(void);
+ void FetchFlags(void);
+
+ static bool GetTmpFile(/*out*/ nsIFile** aResult);
+ static bool CopyMsgAttachToFile(LPATTACH lpAttach,
+ /*out*/ nsIFile** tmp_file);
+ static bool CopyBinAttachToFile(LPATTACH lpAttach, nsIFile** tmp_file);
+
+ static void ClearAttachment(attach_data* data);
+ void ClearAttachments();
+ bool AddAttachment(DWORD aNum);
+ bool IterateAttachTable(LPMAPITABLE tbl);
+ bool GetURL(nsIFile* aFile, nsIURI** url);
+ void ProcessAttachments();
+
+ bool EnsureHeader(CMapiMessageHeaders::SpecialHeader special, ULONG mapiTag);
+ bool EnsureDate();
+
+ void ProcessContentType();
+ bool CheckBodyInCharsetRange(const char* charset);
+ void FormatDateTime(SYSTEMTIME& tm, nsCString& s, bool includeTZ = true);
+ void BuildFromLine(void);
+
+ inline static bool IsSpace(char c) {
+ return c == ' ' || c == '\r' || c == '\n' || c == '\b' || c == '\t';
+ }
+ inline static bool IsSpace(wchar_t c) {
+ return ((c & 0xFF) == c) && IsSpace(static_cast<char>(c));
+ } // Avoid false detections
+};
+
+#endif /* MapiMessage_h__ */
diff --git a/comm/mailnews/import/src/MapiMimeTypes.cpp b/comm/mailnews/import/src/MapiMimeTypes.cpp
new file mode 100644
index 0000000000..554c5694d9
--- /dev/null
+++ b/comm/mailnews/import/src/MapiMimeTypes.cpp
@@ -0,0 +1,81 @@
+/* -*- 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 "nscore.h"
+#include "nsString.h"
+#include "MapiMimeTypes.h"
+
+uint8_t CMimeTypes::m_mimeBuffer[kMaxMimeTypeSize];
+
+BOOL CMimeTypes::GetKey(HKEY root, LPCWSTR pName, PHKEY pKey) {
+ LONG result = RegOpenKeyExW(root, pName, 0,
+ KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, pKey);
+ return result == ERROR_SUCCESS;
+}
+
+BOOL CMimeTypes::GetValueBytes(HKEY rootKey, LPCWSTR pValName,
+ LPBYTE* ppBytes) {
+ LONG err;
+ DWORD bufSz;
+
+ *ppBytes = NULL;
+ // Get the installed directory
+ err = RegQueryValueExW(rootKey, pValName, NULL, NULL, NULL, &bufSz);
+ if (err == ERROR_SUCCESS) {
+ *ppBytes = new BYTE[bufSz];
+ err = RegQueryValueExW(rootKey, pValName, NULL, NULL, *ppBytes, &bufSz);
+ if (err == ERROR_SUCCESS) {
+ return TRUE;
+ }
+ delete *ppBytes;
+ *ppBytes = NULL;
+ }
+ return FALSE;
+}
+
+void CMimeTypes::ReleaseValueBytes(LPBYTE pBytes) {
+ if (pBytes) delete pBytes;
+}
+
+BOOL CMimeTypes::GetMimeTypeFromReg(const nsString& ext, LPBYTE* ppBytes) {
+ HKEY extensionKey;
+ BOOL result = FALSE;
+ *ppBytes = NULL;
+ if (GetKey(HKEY_CLASSES_ROOT, ext.get(), &extensionKey)) {
+ result = GetValueBytes(extensionKey, L"Content Type", ppBytes);
+ RegCloseKey(extensionKey);
+ }
+
+ return result;
+}
+
+uint8_t* CMimeTypes::GetMimeType(const nsString& theExt) {
+ nsString ext = theExt;
+ if (ext.Length()) {
+ if (ext.First() != '.') {
+ ext = L".";
+ ext += theExt;
+ }
+ }
+
+ BOOL result = FALSE;
+ int len;
+
+ if (!ext.Length()) return NULL;
+ LPBYTE pByte;
+ if (GetMimeTypeFromReg(ext, &pByte)) {
+ len = strlen((const char*)pByte);
+ if (len && (len < kMaxMimeTypeSize)) {
+ memcpy(m_mimeBuffer, pByte, len);
+ m_mimeBuffer[len] = 0;
+ result = TRUE;
+ }
+ ReleaseValueBytes(pByte);
+ }
+
+ if (result) return m_mimeBuffer;
+
+ return NULL;
+}
diff --git a/comm/mailnews/import/src/MapiMimeTypes.h b/comm/mailnews/import/src/MapiMimeTypes.h
new file mode 100644
index 0000000000..d870893559
--- /dev/null
+++ b/comm/mailnews/import/src/MapiMimeTypes.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 4; 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 MapiMimeTypes_h___
+#define MapiMimeTypes_h___
+
+#include <windows.h>
+
+#define kMaxMimeTypeSize 256
+
+class CMimeTypes {
+ public:
+ static uint8_t* GetMimeType(const nsString& theExt);
+
+ protected:
+ // Registry stuff
+ static BOOL GetKey(HKEY root, LPCWSTR pName, PHKEY pKey);
+ static BOOL GetValueBytes(HKEY rootKey, LPCWSTR pValName, LPBYTE* ppBytes);
+ static void ReleaseValueBytes(LPBYTE pBytes);
+ static BOOL GetMimeTypeFromReg(const nsString& ext, LPBYTE* ppBytes);
+
+ static uint8_t m_mimeBuffer[kMaxMimeTypeSize];
+};
+
+#endif /* MapiMimeTypes_h__ */
diff --git a/comm/mailnews/import/src/MapiTagStrs.cpp b/comm/mailnews/import/src/MapiTagStrs.cpp
new file mode 100644
index 0000000000..43a796ae24
--- /dev/null
+++ b/comm/mailnews/import/src/MapiTagStrs.cpp
@@ -0,0 +1,1473 @@
+ /* -*- 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/. */
+
+ /*
+ * Message envelope properties
+ */
+
+case PR_ACKNOWLEDGEMENT_MODE:
+ s = "PR_ACKNOWLEDGEMENT_MODE";
+ break;
+case PR_ALTERNATE_RECIPIENT_ALLOWED:
+ s = "PR_ALTERNATE_RECIPIENT_ALLOWED";
+ break;
+case PR_AUTHORIZING_USERS:
+ s = "PR_AUTHORIZING_USERS";
+ break;
+case PR_AUTO_FORWARD_COMMENT:
+ s = "PR_AUTO_FORWARD_COMMENT";
+ break;
+case PR_AUTO_FORWARDED:
+ s = "PR_AUTO_FORWARDED";
+ break;
+case PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID:
+ s = "PR_CONTENT_CONFIDENTIALITY_ALGORITHM_ID";
+ break;
+case PR_CONTENT_CORRELATOR:
+ s = "PR_CONTENT_CORRELATOR";
+ break;
+case PR_CONTENT_IDENTIFIER:
+ s = "PR_CONTENT_IDENTIFIER";
+ break;
+case PR_CONTENT_LENGTH:
+ s = "PR_CONTENT_LENGTH";
+ break;
+case PR_CONTENT_RETURN_REQUESTED:
+ s = "PR_CONTENT_RETURN_REQUESTED";
+ break;
+
+case PR_CONVERSATION_KEY:
+ s = "PR_CONVERSATION_KEY";
+ break;
+
+case PR_CONVERSION_EITS:
+ s = "PR_CONVERSION_EITS";
+ break;
+case PR_CONVERSION_WITH_LOSS_PROHIBITED:
+ s = "PR_CONVERSION_WITH_LOSS_PROHIBITED";
+ break;
+case PR_CONVERTED_EITS:
+ s = "PR_CONVERTED_EITS";
+ break;
+case PR_DEFERRED_DELIVERY_TIME:
+ s = "PR_DEFERRED_DELIVERY_TIME";
+ break;
+case PR_DELIVER_TIME:
+ s = "PR_DELIVER_TIME";
+ break;
+case PR_DISCARD_REASON:
+ s = "PR_DISCARD_REASON";
+ break;
+case PR_DISCLOSURE_OF_RECIPIENTS:
+ s = "PR_DISCLOSURE_OF_RECIPIENTS";
+ break;
+case PR_DL_EXPANSION_HISTORY:
+ s = "PR_DL_EXPANSION_HISTORY";
+ break;
+case PR_DL_EXPANSION_PROHIBITED:
+ s = "PR_DL_EXPANSION_PROHIBITED";
+ break;
+case PR_EXPIRY_TIME:
+ s = "PR_EXPIRY_TIME";
+ break;
+case PR_IMPLICIT_CONVERSION_PROHIBITED:
+ s = "PR_IMPLICIT_CONVERSION_PROHIBITED";
+ break;
+case PR_IMPORTANCE:
+ s = "PR_IMPORTANCE";
+ break;
+case PR_IPM_ID:
+ s = "PR_IPM_ID";
+ break;
+case PR_LATEST_DELIVERY_TIME:
+ s = "PR_LATEST_DELIVERY_TIME";
+ break;
+case PR_MESSAGE_CLASS:
+ s = "PR_MESSAGE_CLASS";
+ break;
+case PR_MESSAGE_DELIVERY_ID:
+ s = "PR_MESSAGE_DELIVERY_ID";
+ break;
+
+case PR_MESSAGE_SECURITY_LABEL:
+ s = "PR_MESSAGE_SECURITY_LABEL";
+ break;
+case PR_OBSOLETED_IPMS:
+ s = "PR_OBSOLETED_IPMS";
+ break;
+case PR_ORIGINALLY_INTENDED_RECIPIENT_NAME:
+ s = "PR_ORIGINALLY_INTENDED_RECIPIENT_NAME";
+ break;
+case PR_ORIGINAL_EITS:
+ s = "PR_ORIGINAL_EITS";
+ break;
+case PR_ORIGINATOR_CERTIFICATE:
+ s = "PR_ORIGINATOR_CERTIFICATE";
+ break;
+case PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED:
+ s = "PR_ORIGINATOR_DELIVERY_REPORT_REQUESTED";
+ break;
+case PR_ORIGINATOR_RETURN_ADDRESS:
+ s = "PR_ORIGINATOR_RETURN_ADDRESS";
+ break;
+
+case PR_PARENT_KEY:
+ s = "PR_PARENT_KEY";
+ break;
+case PR_PRIORITY:
+ s = "PR_PRIORITY";
+ break;
+
+case PR_ORIGIN_CHECK:
+ s = "PR_ORIGIN_CHECK";
+ break;
+case PR_PROOF_OF_SUBMISSION_REQUESTED:
+ s = "PR_PROOF_OF_SUBMISSION_REQUESTED";
+ break;
+case PR_READ_RECEIPT_REQUESTED:
+ s = "PR_READ_RECEIPT_REQUESTED";
+ break;
+case PR_RECEIPT_TIME:
+ s = "PR_RECEIPT_TIME";
+ break;
+case PR_RECIPIENT_REASSIGNMENT_PROHIBITED:
+ s = "PR_RECIPIENT_REASSIGNMENT_PROHIBITED";
+ break;
+case PR_REDIRECTION_HISTORY:
+ s = "PR_REDIRECTION_HISTORY";
+ break;
+case PR_RELATED_IPMS:
+ s = "PR_RELATED_IPMS";
+ break;
+case PR_ORIGINAL_SENSITIVITY:
+ s = "PR_ORIGINAL_SENSITIVITY";
+ break;
+case PR_LANGUAGES:
+ s = "PR_LANGUAGES";
+ break;
+case PR_REPLY_TIME:
+ s = "PR_REPLY_TIME";
+ break;
+case PR_REPORT_TAG:
+ s = "PR_REPORT_TAG";
+ break;
+case PR_REPORT_TIME:
+ s = "PR_REPORT_TIME";
+ break;
+case PR_RETURNED_IPM:
+ s = "PR_RETURNED_IPM";
+ break;
+case PR_SECURITY:
+ s = "PR_SECURITY";
+ break;
+case PR_INCOMPLETE_COPY:
+ s = "PR_INCOMPLETE_COPY";
+ break;
+case PR_SENSITIVITY:
+ s = "PR_SENSITIVITY";
+ break;
+case PR_SUBJECT:
+ s = "PR_SUBJECT";
+ break;
+case PR_SUBJECT_IPM:
+ s = "PR_SUBJECT_IPM";
+ break;
+case PR_CLIENT_SUBMIT_TIME:
+ s = "PR_CLIENT_SUBMIT_TIME";
+ break;
+case PR_REPORT_NAME:
+ s = "PR_REPORT_NAME";
+ break;
+case PR_SENT_REPRESENTING_SEARCH_KEY:
+ s = "PR_SENT_REPRESENTING_SEARCH_KEY";
+ break;
+case PR_X400_CONTENT_TYPE:
+ s = "PR_X400_CONTENT_TYPE";
+ break;
+case PR_SUBJECT_PREFIX:
+ s = "PR_SUBJECT_PREFIX";
+ break;
+case PR_NON_RECEIPT_REASON:
+ s = "PR_NON_RECEIPT_REASON";
+ break;
+case PR_RECEIVED_BY_ENTRYID:
+ s = "PR_RECEIVED_BY_ENTRYID";
+ break;
+case PR_RECEIVED_BY_NAME:
+ s = "PR_RECEIVED_BY_NAME";
+ break;
+case PR_SENT_REPRESENTING_ENTRYID:
+ s = "PR_SENT_REPRESENTING_ENTRYID";
+ break;
+case PR_SENT_REPRESENTING_NAME:
+ s = "PR_SENT_REPRESENTING_NAME";
+ break;
+case PR_RCVD_REPRESENTING_ENTRYID:
+ s = "PR_RCVD_REPRESENTING_ENTRYID";
+ break;
+case PR_RCVD_REPRESENTING_NAME:
+ s = "PR_RCVD_REPRESENTING_NAME";
+ break;
+case PR_REPORT_ENTRYID:
+ s = "PR_REPORT_ENTRYID";
+ break;
+case PR_READ_RECEIPT_ENTRYID:
+ s = "PR_READ_RECEIPT_ENTRYID";
+ break;
+case PR_MESSAGE_SUBMISSION_ID:
+ s = "PR_MESSAGE_SUBMISSION_ID";
+ break;
+case PR_PROVIDER_SUBMIT_TIME:
+ s = "PR_PROVIDER_SUBMIT_TIME";
+ break;
+case PR_ORIGINAL_SUBJECT:
+ s = "PR_ORIGINAL_SUBJECT";
+ break;
+case PR_DISC_VAL:
+ s = "PR_DISC_VAL";
+ break;
+case PR_ORIG_MESSAGE_CLASS:
+ s = "PR_ORIG_MESSAGE_CLASS";
+ break;
+case PR_ORIGINAL_AUTHOR_ENTRYID:
+ s = "PR_ORIGINAL_AUTHOR_ENTRYID";
+ break;
+case PR_ORIGINAL_AUTHOR_NAME:
+ s = "PR_ORIGINAL_AUTHOR_NAME";
+ break;
+case PR_ORIGINAL_SUBMIT_TIME:
+ s = "PR_ORIGINAL_SUBMIT_TIME";
+ break;
+case PR_REPLY_RECIPIENT_ENTRIES:
+ s = "PR_REPLY_RECIPIENT_ENTRIES";
+ break;
+case PR_REPLY_RECIPIENT_NAMES:
+ s = "PR_REPLY_RECIPIENT_NAMES";
+ break;
+
+case PR_RECEIVED_BY_SEARCH_KEY:
+ s = "PR_RECEIVED_BY_SEARCH_KEY";
+ break;
+case PR_RCVD_REPRESENTING_SEARCH_KEY:
+ s = "PR_RCVD_REPRESENTING_SEARCH_KEY";
+ break;
+case PR_READ_RECEIPT_SEARCH_KEY:
+ s = "PR_READ_RECEIPT_SEARCH_KEY";
+ break;
+case PR_REPORT_SEARCH_KEY:
+ s = "PR_REPORT_SEARCH_KEY";
+ break;
+case PR_ORIGINAL_DELIVERY_TIME:
+ s = "PR_ORIGINAL_DELIVERY_TIME";
+ break;
+case PR_ORIGINAL_AUTHOR_SEARCH_KEY:
+ s = "PR_ORIGINAL_AUTHOR_SEARCH_KEY";
+ break;
+
+case PR_MESSAGE_TO_ME:
+ s = "PR_MESSAGE_TO_ME";
+ break;
+case PR_MESSAGE_CC_ME:
+ s = "PR_MESSAGE_CC_ME";
+ break;
+case PR_MESSAGE_RECIP_ME:
+ s = "PR_MESSAGE_RECIP_ME";
+ break;
+
+case PR_ORIGINAL_SENDER_NAME:
+ s = "PR_ORIGINAL_SENDER_NAME";
+ break;
+case PR_ORIGINAL_SENDER_ENTRYID:
+ s = "PR_ORIGINAL_SENDER_ENTRYID";
+ break;
+case PR_ORIGINAL_SENDER_SEARCH_KEY:
+ s = "PR_ORIGINAL_SENDER_SEARCH_KEY";
+ break;
+case PR_ORIGINAL_SENT_REPRESENTING_NAME:
+ s = "PR_ORIGINAL_SENT_REPRESENTING_NAME";
+ break;
+case PR_ORIGINAL_SENT_REPRESENTING_ENTRYID:
+ s = "PR_ORIGINAL_SENT_REPRESENTING_ENTRYID";
+ break;
+case PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY:
+ s = "PR_ORIGINAL_SENT_REPRESENTING_SEARCH_KEY";
+ break;
+
+case PR_START_DATE:
+ s = "PR_START_DATE";
+ break;
+case PR_END_DATE:
+ s = "PR_END_DATE";
+ break;
+case PR_OWNER_APPT_ID:
+ s = "PR_OWNER_APPT_ID";
+ break;
+case PR_RESPONSE_REQUESTED:
+ s = "PR_RESPONSE_REQUESTED";
+ break;
+
+case PR_SENT_REPRESENTING_ADDRTYPE:
+ s = "PR_SENT_REPRESENTING_ADDRTYPE";
+ break;
+case PR_SENT_REPRESENTING_EMAIL_ADDRESS:
+ s = "PR_SENT_REPRESENTING_EMAIL_ADDRESS";
+ break;
+
+case PR_ORIGINAL_SENDER_ADDRTYPE:
+ s = "PR_ORIGINAL_SENDER_ADDRTYPE";
+ break;
+case PR_ORIGINAL_SENDER_EMAIL_ADDRESS:
+ s = "PR_ORIGINAL_SENDER_EMAIL_ADDRESS";
+ break;
+
+case PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE:
+ s = "PR_ORIGINAL_SENT_REPRESENTING_ADDRTYPE";
+ break;
+case PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS:
+ s = "PR_ORIGINAL_SENT_REPRESENTING_EMAIL_ADDRESS";
+ break;
+
+case PR_CONVERSATION_TOPIC:
+ s = "PR_CONVERSATION_TOPIC";
+ break;
+case PR_CONVERSATION_INDEX:
+ s = "PR_CONVERSATION_INDEX";
+ break;
+
+case PR_ORIGINAL_DISPLAY_BCC:
+ s = "PR_ORIGINAL_DISPLAY_BCC";
+ break;
+case PR_ORIGINAL_DISPLAY_CC:
+ s = "PR_ORIGINAL_DISPLAY_CC";
+ break;
+case PR_ORIGINAL_DISPLAY_TO:
+ s = "PR_ORIGINAL_DISPLAY_TO";
+ break;
+
+case PR_RECEIVED_BY_ADDRTYPE:
+ s = "PR_RECEIVED_BY_ADDRTYPE";
+ break;
+case PR_RECEIVED_BY_EMAIL_ADDRESS:
+ s = "PR_RECEIVED_BY_EMAIL_ADDRESS";
+ break;
+
+case PR_RCVD_REPRESENTING_ADDRTYPE:
+ s = "PR_RCVD_REPRESENTING_ADDRTYPE";
+ break;
+case PR_RCVD_REPRESENTING_EMAIL_ADDRESS:
+ s = "PR_RCVD_REPRESENTING_EMAIL_ADDRESS";
+ break;
+
+case PR_ORIGINAL_AUTHOR_ADDRTYPE:
+ s = "PR_ORIGINAL_AUTHOR_ADDRTYPE";
+ break;
+case PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS:
+ s = "PR_ORIGINAL_AUTHOR_EMAIL_ADDRESS";
+ break;
+
+case PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE:
+ s = "PR_ORIGINALLY_INTENDED_RECIP_ADDRTYPE";
+ break;
+case PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS:
+ s = "PR_ORIGINALLY_INTENDED_RECIP_EMAIL_ADDRESS";
+ break;
+
+case PR_TRANSPORT_MESSAGE_HEADERS:
+ s = "PR_TRANSPORT_MESSAGE_HEADERS";
+ break;
+
+case PR_DELEGATION:
+ s = "PR_DELEGATION";
+ break;
+
+case PR_TNEF_CORRELATION_KEY:
+ s = "PR_TNEF_CORRELATION_KEY";
+ break;
+
+ /*
+ * Message content properties
+ */
+
+case PR_BODY:
+ s = "PR_BODY";
+ break;
+case PR_REPORT_TEXT:
+ s = "PR_REPORT_TEXT";
+ break;
+case PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY:
+ s = "PR_ORIGINATOR_AND_DL_EXPANSION_HISTORY";
+ break;
+case PR_REPORTING_DL_NAME:
+ s = "PR_REPORTING_DL_NAME";
+ break;
+case PR_REPORTING_MTA_CERTIFICATE:
+ s = "PR_REPORTING_MTA_CERTIFICATE";
+ break;
+
+ /* Removed PR_REPORT_ORIGIN_AUTHENTICATION_CHECK with DCR 3865, use
+ * PR_ORIGIN_CHECK */
+
+case PR_RTF_SYNC_BODY_CRC:
+ s = "PR_RTF_SYNC_BODY_CRC";
+ break;
+case PR_RTF_SYNC_BODY_COUNT:
+ s = "PR_RTF_SYNC_BODY_COUNT";
+ break;
+case PR_RTF_SYNC_BODY_TAG:
+ s = "PR_RTF_SYNC_BODY_TAG";
+ break;
+case PR_RTF_COMPRESSED:
+ s = "PR_RTF_COMPRESSED";
+ break;
+case PR_RTF_SYNC_PREFIX_COUNT:
+ s = "PR_RTF_SYNC_PREFIX_COUNT";
+ break;
+case PR_RTF_SYNC_TRAILING_COUNT:
+ s = "PR_RTF_SYNC_TRAILING_COUNT";
+ break;
+case PR_ORIGINALLY_INTENDED_RECIP_ENTRYID:
+ s = "PR_ORIGINALLY_INTENDED_RECIP_ENTRYID";
+ break;
+
+ /*
+ * Reserved 0x1100-0x1200
+ */
+
+ /*
+ * Message recipient properties
+ */
+
+case PR_CONTENT_INTEGRITY_CHECK:
+ s = "PR_CONTENT_INTEGRITY_CHECK";
+ break;
+case PR_EXPLICIT_CONVERSION:
+ s = "PR_EXPLICIT_CONVERSION";
+ break;
+case PR_IPM_RETURN_REQUESTED:
+ s = "PR_IPM_RETURN_REQUESTED";
+ break;
+case PR_MESSAGE_TOKEN:
+ s = "PR_MESSAGE_TOKEN";
+ break;
+case PR_NDR_REASON_CODE:
+ s = "PR_NDR_REASON_CODE";
+ break;
+case PR_NDR_DIAG_CODE:
+ s = "PR_NDR_DIAG_CODE";
+ break;
+case PR_NON_RECEIPT_NOTIFICATION_REQUESTED:
+ s = "PR_NON_RECEIPT_NOTIFICATION_REQUESTED";
+ break;
+case PR_DELIVERY_POINT:
+ s = "PR_DELIVERY_POINT";
+ break;
+
+case PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED:
+ s = "PR_ORIGINATOR_NON_DELIVERY_REPORT_REQUESTED";
+ break;
+case PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT:
+ s = "PR_ORIGINATOR_REQUESTED_ALTERNATE_RECIPIENT";
+ break;
+case PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY:
+ s = "PR_PHYSICAL_DELIVERY_BUREAU_FAX_DELIVERY";
+ break;
+case PR_PHYSICAL_DELIVERY_MODE:
+ s = "PR_PHYSICAL_DELIVERY_MODE";
+ break;
+case PR_PHYSICAL_DELIVERY_REPORT_REQUEST:
+ s = "PR_PHYSICAL_DELIVERY_REPORT_REQUEST";
+ break;
+case PR_PHYSICAL_FORWARDING_ADDRESS:
+ s = "PR_PHYSICAL_FORWARDING_ADDRESS";
+ break;
+case PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED:
+ s = "PR_PHYSICAL_FORWARDING_ADDRESS_REQUESTED";
+ break;
+case PR_PHYSICAL_FORWARDING_PROHIBITED:
+ s = "PR_PHYSICAL_FORWARDING_PROHIBITED";
+ break;
+case PR_PHYSICAL_RENDITION_ATTRIBUTES:
+ s = "PR_PHYSICAL_RENDITION_ATTRIBUTES";
+ break;
+case PR_PROOF_OF_DELIVERY:
+ s = "PR_PROOF_OF_DELIVERY";
+ break;
+case PR_PROOF_OF_DELIVERY_REQUESTED:
+ s = "PR_PROOF_OF_DELIVERY_REQUESTED";
+ break;
+case PR_RECIPIENT_CERTIFICATE:
+ s = "PR_RECIPIENT_CERTIFICATE";
+ break;
+case PR_RECIPIENT_NUMBER_FOR_ADVICE:
+ s = "PR_RECIPIENT_NUMBER_FOR_ADVICE";
+ break;
+case PR_RECIPIENT_TYPE:
+ s = "PR_RECIPIENT_TYPE";
+ break;
+case PR_REGISTERED_MAIL_TYPE:
+ s = "PR_REGISTERED_MAIL_TYPE";
+ break;
+case PR_REPLY_REQUESTED:
+ s = "PR_REPLY_REQUESTED";
+ break;
+case PR_REQUESTED_DELIVERY_METHOD:
+ s = "PR_REQUESTED_DELIVERY_METHOD";
+ break;
+case PR_SENDER_ENTRYID:
+ s = "PR_SENDER_ENTRYID";
+ break;
+case PR_SENDER_NAME:
+ s = "PR_SENDER_NAME";
+ break;
+case PR_SUPPLEMENTARY_INFO:
+ s = "PR_SUPPLEMENTARY_INFO";
+ break;
+case PR_TYPE_OF_MTS_USER:
+ s = "PR_TYPE_OF_MTS_USER";
+ break;
+case PR_SENDER_SEARCH_KEY:
+ s = "PR_SENDER_SEARCH_KEY";
+ break;
+case PR_SENDER_ADDRTYPE:
+ s = "PR_SENDER_ADDRTYPE";
+ break;
+case PR_SENDER_EMAIL_ADDRESS:
+ s = "PR_SENDER_EMAIL_ADDRESS";
+ break;
+
+ /*
+ * Message non-transmittable properties
+ */
+
+ /*
+ * The two tags, PR_MESSAGE_RECIPIENTS and PR_MESSAGE_ATTACHMENTS,
+ * are to be used in the exclude list passed to
+ * IMessage::CopyTo when the caller wants either the recipients or attachments
+ * of the message to not get copied. It is also used in the ProblemArray
+ * return from IMessage::CopyTo when an error is encountered copying them
+ */
+
+case PR_CURRENT_VERSION:
+ s = "PR_CURRENT_VERSION";
+ break;
+case PR_DELETE_AFTER_SUBMIT:
+ s = "PR_DELETE_AFTER_SUBMIT";
+ break;
+case PR_DISPLAY_BCC:
+ s = "PR_DISPLAY_BCC";
+ break;
+case PR_DISPLAY_CC:
+ s = "PR_DISPLAY_CC";
+ break;
+case PR_DISPLAY_TO:
+ s = "PR_DISPLAY_TO";
+ break;
+case PR_PARENT_DISPLAY:
+ s = "PR_PARENT_DISPLAY";
+ break;
+case PR_MESSAGE_DELIVERY_TIME:
+ s = "PR_MESSAGE_DELIVERY_TIME";
+ break;
+case PR_MESSAGE_FLAGS:
+ s = "PR_MESSAGE_FLAGS";
+ break;
+case PR_MESSAGE_SIZE:
+ s = "PR_MESSAGE_SIZE";
+ break;
+case PR_PARENT_ENTRYID:
+ s = "PR_PARENT_ENTRYID";
+ break;
+case PR_SENTMAIL_ENTRYID:
+ s = "PR_SENTMAIL_ENTRYID";
+ break;
+case PR_CORRELATE:
+ s = "PR_CORRELATE";
+ break;
+case PR_CORRELATE_MTSID:
+ s = "PR_CORRELATE_MTSID";
+ break;
+case PR_DISCRETE_VALUES:
+ s = "PR_DISCRETE_VALUES";
+ break;
+case PR_RESPONSIBILITY:
+ s = "PR_RESPONSIBILITY";
+ break;
+case PR_SPOOLER_STATUS:
+ s = "PR_SPOOLER_STATUS";
+ break;
+case PR_TRANSPORT_STATUS:
+ s = "PR_TRANSPORT_STATUS";
+ break;
+case PR_MESSAGE_RECIPIENTS:
+ s = "PR_MESSAGE_RECIPIENTS";
+ break;
+case PR_MESSAGE_ATTACHMENTS:
+ s = "PR_MESSAGE_ATTACHMENTS";
+ break;
+case PR_SUBMIT_FLAGS:
+ s = "PR_SUBMIT_FLAGS";
+ break;
+case PR_RECIPIENT_STATUS:
+ s = "PR_RECIPIENT_STATUS";
+ break;
+case PR_TRANSPORT_KEY:
+ s = "PR_TRANSPORT_KEY";
+ break;
+case PR_MSG_STATUS:
+ s = "PR_MSG_STATUS";
+ break;
+case PR_MESSAGE_DOWNLOAD_TIME:
+ s = "PR_MESSAGE_DOWNLOAD_TIME";
+ break;
+case PR_CREATION_VERSION:
+ s = "PR_CREATION_VERSION";
+ break;
+case PR_MODIFY_VERSION:
+ s = "PR_MODIFY_VERSION";
+ break;
+case PR_HASATTACH:
+ s = "PR_HASATTACH";
+ break;
+case PR_BODY_CRC:
+ s = "PR_BODY_CRC";
+ break;
+case PR_NORMALIZED_SUBJECT:
+ s = "PR_NORMALIZED_SUBJECT";
+ break;
+case PR_RTF_IN_SYNC:
+ s = "PR_RTF_IN_SYNC";
+ break;
+case PR_ATTACH_SIZE:
+ s = "PR_ATTACH_SIZE";
+ break;
+case PR_ATTACH_NUM:
+ s = "PR_ATTACH_NUM";
+ break;
+case PR_PREPROCESS:
+ s = "PR_PREPROCESS";
+ break;
+
+ /* PR_ORIGINAL_DISPLAY_TO, _CC, and _BCC moved to transmittible range 03/09/95
+ */
+
+case PR_ORIGINATING_MTA_CERTIFICATE:
+ s = "PR_ORIGINATING_MTA_CERTIFICATE";
+ break;
+case PR_PROOF_OF_SUBMISSION:
+ s = "PR_PROOF_OF_SUBMISSION";
+ break;
+
+ /*
+ * The range of non-message and non-recipient property IDs (0x3000 - 0x3FFF)
+ * is further broken down into ranges to make assigning new property IDs
+ * easier.
+ *
+ * From To Kind of property
+ * --------------------------------
+ * 3000 32FF MAPI_defined common property
+ * 3200 33FF MAPI_defined form property
+ * 3400 35FF MAPI_defined message store property
+ * 3600 36FF MAPI_defined Folder or AB Container property
+ * 3700 38FF MAPI_defined attachment property
+ * 3900 39FF MAPI_defined address book property
+ * 3A00 3BFF MAPI_defined mailuser property
+ * 3C00 3CFF MAPI_defined DistList property
+ * 3D00 3DFF MAPI_defined Profile Section property
+ * 3E00 3EFF MAPI_defined Status property
+ * 3F00 3FFF MAPI_defined display table property
+ */
+
+ /*
+ * Properties common to numerous MAPI objects.
+ *
+ * Those properties that can appear on messages are in the
+ * non-transmittable range for messages. They start at the high
+ * end of that range and work down.
+ *
+ * Properties that never appear on messages are defined in the common
+ * property range (see above).
+ */
+
+ /*
+ * properties that are common to multiple objects (including message objects)
+ * -- these ids are in the non-transmittable range
+ */
+
+case PR_ENTRYID:
+ s = "PR_ENTRYID";
+ break;
+case PR_OBJECT_TYPE:
+ s = "PR_OBJECT_TYPE";
+ break;
+case PR_ICON:
+ s = "PR_ICON";
+ break;
+case PR_MINI_ICON:
+ s = "PR_MINI_ICON";
+ break;
+case PR_STORE_ENTRYID:
+ s = "PR_STORE_ENTRYID";
+ break;
+case PR_STORE_RECORD_KEY:
+ s = "PR_STORE_RECORD_KEY";
+ break;
+case PR_RECORD_KEY:
+ s = "PR_RECORD_KEY";
+ break;
+case PR_MAPPING_SIGNATURE:
+ s = "PR_MAPPING_SIGNATURE";
+ break;
+case PR_ACCESS_LEVEL:
+ s = "PR_ACCESS_LEVEL";
+ break;
+case PR_INSTANCE_KEY:
+ s = "PR_INSTANCE_KEY";
+ break;
+case PR_ROW_TYPE:
+ s = "PR_ROW_TYPE";
+ break;
+case PR_ACCESS:
+ s = "PR_ACCESS";
+ break;
+
+ /*
+ * properties that are common to multiple objects (usually not including
+ * message objects)
+ * -- these ids are in the transmittable range
+ */
+
+case PR_ROWID:
+ s = "PR_ROWID";
+ break;
+case PR_DISPLAY_NAME:
+ s = "PR_DISPLAY_NAME";
+ break;
+case PR_ADDRTYPE:
+ s = "PR_ADDRTYPE";
+ break;
+case PR_EMAIL_ADDRESS:
+ s = "PR_EMAIL_ADDRESS";
+ break;
+case PR_COMMENT:
+ s = "PR_COMMENT";
+ break;
+case PR_DEPTH:
+ s = "PR_DEPTH";
+ break;
+case PR_PROVIDER_DISPLAY:
+ s = "PR_PROVIDER_DISPLAY";
+ break;
+case PR_CREATION_TIME:
+ s = "PR_CREATION_TIME";
+ break;
+case PR_LAST_MODIFICATION_TIME:
+ s = "PR_LAST_MODIFICATION_TIME";
+ break;
+case PR_RESOURCE_FLAGS:
+ s = "PR_RESOURCE_FLAGS";
+ break;
+case PR_PROVIDER_DLL_NAME:
+ s = "PR_PROVIDER_DLL_NAME";
+ break;
+case PR_SEARCH_KEY:
+ s = "PR_SEARCH_KEY";
+ break;
+case PR_PROVIDER_UID:
+ s = "PR_PROVIDER_UID";
+ break;
+case PR_PROVIDER_ORDINAL:
+ s = "PR_PROVIDER_ORDINAL";
+ break;
+
+/*
+ * MAPI Form properties
+ */
+case PR_FORM_VERSION:
+ s = "PR_FORM_VERSION";
+ break;
+case PR_FORM_CLSID:
+ s = "PR_FORM_CLSID";
+ break;
+case PR_FORM_CONTACT_NAME:
+ s = "PR_FORM_CONTACT_NAME";
+ break;
+case PR_FORM_CATEGORY:
+ s = "PR_FORM_CATEGORY";
+ break;
+case PR_FORM_CATEGORY_SUB:
+ s = "PR_FORM_CATEGORY_SUB";
+ break;
+case PR_FORM_HOST_MAP:
+ s = "PR_FORM_HOST_MAP";
+ break;
+case PR_FORM_HIDDEN:
+ s = "PR_FORM_HIDDEN";
+ break;
+case PR_FORM_DESIGNER_NAME:
+ s = "PR_FORM_DESIGNER_NAME";
+ break;
+case PR_FORM_DESIGNER_GUID:
+ s = "PR_FORM_DESIGNER_GUID";
+ break;
+case PR_FORM_MESSAGE_BEHAVIOR:
+ s = "PR_FORM_MESSAGE_BEHAVIOR";
+ break;
+
+ /*
+ * Message store properties
+ */
+
+case PR_DEFAULT_STORE:
+ s = "PR_DEFAULT_STORE";
+ break;
+case PR_STORE_SUPPORT_MASK:
+ s = "PR_STORE_SUPPORT_MASK";
+ break;
+case PR_STORE_STATE:
+ s = "PR_STORE_STATE";
+ break;
+
+case PR_IPM_SUBTREE_SEARCH_KEY:
+ s = "PR_IPM_SUBTREE_SEARCH_KEY";
+ break;
+case PR_IPM_OUTBOX_SEARCH_KEY:
+ s = "PR_IPM_OUTBOX_SEARCH_KEY";
+ break;
+case PR_IPM_WASTEBASKET_SEARCH_KEY:
+ s = "PR_IPM_WASTEBASKET_SEARCH_KEY";
+ break;
+case PR_IPM_SENTMAIL_SEARCH_KEY:
+ s = "PR_IPM_SENTMAIL_SEARCH_KEY";
+ break;
+case PR_MDB_PROVIDER:
+ s = "PR_MDB_PROVIDER";
+ break;
+case PR_RECEIVE_FOLDER_SETTINGS:
+ s = "PR_RECEIVE_FOLDER_SETTINGS";
+ break;
+
+case PR_VALID_FOLDER_MASK:
+ s = "PR_VALID_FOLDER_MASK";
+ break;
+case PR_IPM_SUBTREE_ENTRYID:
+ s = "PR_IPM_SUBTREE_ENTRYID";
+ break;
+
+case PR_IPM_OUTBOX_ENTRYID:
+ s = "PR_IPM_OUTBOX_ENTRYID";
+ break;
+case PR_IPM_WASTEBASKET_ENTRYID:
+ s = "PR_IPM_WASTEBASKET_ENTRYID";
+ break;
+case PR_IPM_SENTMAIL_ENTRYID:
+ s = "PR_IPM_SENTMAIL_ENTRYID";
+ break;
+case PR_VIEWS_ENTRYID:
+ s = "PR_VIEWS_ENTRYID";
+ break;
+case PR_COMMON_VIEWS_ENTRYID:
+ s = "PR_COMMON_VIEWS_ENTRYID";
+ break;
+case PR_FINDER_ENTRYID:
+ s = "PR_FINDER_ENTRYID";
+ break;
+
+ /* Proptags 0x35E8-0x35FF reserved for folders "guaranteed" by
+ * PR_VALID_FOLDER_MASK */
+
+ /*
+ * Folder and AB Container properties
+ */
+
+case PR_CONTAINER_FLAGS:
+ s = "PR_CONTAINER_FLAGS";
+ break;
+case PR_FOLDER_TYPE:
+ s = "PR_FOLDER_TYPE";
+ break;
+case PR_CONTENT_COUNT:
+ s = "PR_CONTENT_COUNT";
+ break;
+case PR_CONTENT_UNREAD:
+ s = "PR_CONTENT_UNREAD";
+ break;
+case PR_CREATE_TEMPLATES:
+ s = "PR_CREATE_TEMPLATES";
+ break;
+case PR_DETAILS_TABLE:
+ s = "PR_DETAILS_TABLE";
+ break;
+case PR_SEARCH:
+ s = "PR_SEARCH";
+ break;
+case PR_SELECTABLE:
+ s = "PR_SELECTABLE";
+ break;
+case PR_SUBFOLDERS:
+ s = "PR_SUBFOLDERS";
+ break;
+case PR_STATUS:
+ s = "PR_STATUS";
+ break;
+case PR_ANR:
+ s = "PR_ANR";
+ break;
+case PR_CONTENTS_SORT_ORDER:
+ s = "PR_CONTENTS_SORT_ORDER";
+ break;
+case PR_CONTAINER_HIERARCHY:
+ s = "PR_CONTAINER_HIERARCHY";
+ break;
+case PR_CONTAINER_CONTENTS:
+ s = "PR_CONTAINER_CONTENTS";
+ break;
+case PR_FOLDER_ASSOCIATED_CONTENTS:
+ s = "PR_FOLDER_ASSOCIATED_CONTENTS";
+ break;
+case PR_DEF_CREATE_DL:
+ s = "PR_DEF_CREATE_DL";
+ break;
+case PR_DEF_CREATE_MAILUSER:
+ s = "PR_DEF_CREATE_MAILUSER";
+ break;
+case PR_CONTAINER_CLASS:
+ s = "PR_CONTAINER_CLASS";
+ break;
+case PR_CONTAINER_MODIFY_VERSION:
+ s = "PR_CONTAINER_MODIFY_VERSION";
+ break;
+case PR_AB_PROVIDER_ID:
+ s = "PR_AB_PROVIDER_ID";
+ break;
+case PR_DEFAULT_VIEW_ENTRYID:
+ s = "PR_DEFAULT_VIEW_ENTRYID";
+ break;
+case PR_ASSOC_CONTENT_COUNT:
+ s = "PR_ASSOC_CONTENT_COUNT";
+ break;
+
+ /* Reserved 0x36C0-0x36FF */
+
+ /*
+ * Attachment properties
+ */
+
+case PR_ATTACHMENT_X400_PARAMETERS:
+ s = "PR_ATTACHMENT_X400_PARAMETERS";
+ break;
+case PR_ATTACH_DATA_OBJ:
+ s = "PR_ATTACH_DATA_OBJ";
+ break;
+case PR_ATTACH_DATA_BIN:
+ s = "PR_ATTACH_DATA_BIN";
+ break;
+case PR_ATTACH_ENCODING:
+ s = "PR_ATTACH_ENCODING";
+ break;
+case PR_ATTACH_EXTENSION:
+ s = "PR_ATTACH_EXTENSION";
+ break;
+case PR_ATTACH_FILENAME:
+ s = "PR_ATTACH_FILENAME";
+ break;
+case PR_ATTACH_METHOD:
+ s = "PR_ATTACH_METHOD";
+ break;
+case PR_ATTACH_LONG_FILENAME:
+ s = "PR_ATTACH_LONG_FILENAME";
+ break;
+case PR_ATTACH_PATHNAME:
+ s = "PR_ATTACH_PATHNAME";
+ break;
+case PR_ATTACH_RENDERING:
+ s = "PR_ATTACH_RENDERING";
+ break;
+case PR_ATTACH_TAG:
+ s = "PR_ATTACH_TAG";
+ break;
+case PR_RENDERING_POSITION:
+ s = "PR_RENDERING_POSITION";
+ break;
+case PR_ATTACH_TRANSPORT_NAME:
+ s = "PR_ATTACH_TRANSPORT_NAME";
+ break;
+case PR_ATTACH_LONG_PATHNAME:
+ s = "PR_ATTACH_LONG_PATHNAME";
+ break;
+case PR_ATTACH_MIME_TAG:
+ s = "PR_ATTACH_MIME_TAG";
+ break;
+case PR_ATTACH_ADDITIONAL_INFO:
+ s = "PR_ATTACH_ADDITIONAL_INFO";
+ break;
+
+ /*
+ * AB Object properties
+ */
+
+case PR_DISPLAY_TYPE:
+ s = "PR_DISPLAY_TYPE";
+ break;
+case PR_TEMPLATEID:
+ s = "PR_TEMPLATEID";
+ break;
+case PR_PRIMARY_CAPABILITY:
+ s = "PR_PRIMARY_CAPABILITY";
+ break;
+
+/*
+ * Mail user properties
+ */
+case PR_7BIT_DISPLAY_NAME:
+ s = "PR_7BIT_DISPLAY_NAME";
+ break;
+case PR_ACCOUNT:
+ s = "PR_ACCOUNT";
+ break;
+case PR_ALTERNATE_RECIPIENT:
+ s = "PR_ALTERNATE_RECIPIENT";
+ break;
+case PR_CALLBACK_TELEPHONE_NUMBER:
+ s = "PR_CALLBACK_TELEPHONE_NUMBER";
+ break;
+case PR_CONVERSION_PROHIBITED:
+ s = "PR_CONVERSION_PROHIBITED";
+ break;
+case PR_DISCLOSE_RECIPIENTS:
+ s = "PR_DISCLOSE_RECIPIENTS";
+ break;
+case PR_GENERATION:
+ s = "PR_GENERATION";
+ break;
+case PR_GIVEN_NAME:
+ s = "PR_GIVEN_NAME";
+ break;
+case PR_GOVERNMENT_ID_NUMBER:
+ s = "PR_GOVERNMENT_ID_NUMBER";
+ break;
+case PR_BUSINESS_TELEPHONE_NUMBER:
+ s = "PR_BUSINESS_TELEPHONE_NUMBER or PR_OFFICE_TELEPHONE_NUMBER";
+ break;
+case PR_HOME_TELEPHONE_NUMBER:
+ s = "PR_HOME_TELEPHONE_NUMBER";
+ break;
+case PR_INITIALS:
+ s = "PR_INITIALS";
+ break;
+case PR_KEYWORD:
+ s = "PR_KEYWORD";
+ break;
+case PR_LANGUAGE:
+ s = "PR_LANGUAGE";
+ break;
+case PR_LOCATION:
+ s = "PR_LOCATION";
+ break;
+case PR_MAIL_PERMISSION:
+ s = "PR_MAIL_PERMISSION";
+ break;
+case PR_MHS_COMMON_NAME:
+ s = "PR_MHS_COMMON_NAME";
+ break;
+case PR_ORGANIZATIONAL_ID_NUMBER:
+ s = "PR_ORGANIZATIONAL_ID_NUMBER";
+ break;
+case PR_SURNAME:
+ s = "PR_SURNAME";
+ break;
+case PR_ORIGINAL_ENTRYID:
+ s = "PR_ORIGINAL_ENTRYID";
+ break;
+case PR_ORIGINAL_DISPLAY_NAME:
+ s = "PR_ORIGINAL_DISPLAY_NAME";
+ break;
+case PR_ORIGINAL_SEARCH_KEY:
+ s = "PR_ORIGINAL_SEARCH_KEY";
+ break;
+case PR_POSTAL_ADDRESS:
+ s = "PR_POSTAL_ADDRESS";
+ break;
+case PR_COMPANY_NAME:
+ s = "PR_COMPANY_NAME";
+ break;
+case PR_TITLE:
+ s = "PR_TITLE";
+ break;
+case PR_DEPARTMENT_NAME:
+ s = "PR_DEPARTMENT_NAME";
+ break;
+case PR_OFFICE_LOCATION:
+ s = "PR_OFFICE_LOCATION";
+ break;
+case PR_PRIMARY_TELEPHONE_NUMBER:
+ s = "PR_PRIMARY_TELEPHONE_NUMBER";
+ break;
+case PR_BUSINESS2_TELEPHONE_NUMBER:
+ s = "PR_BUSINESS2_TELEPHONE_NUMBER or PR_OFFICE2_TELEPHONE_NUMBER";
+ break;
+case PR_MOBILE_TELEPHONE_NUMBER:
+ s = "PR_MOBILE_TELEPHONE_NUMBER or PR_CELLULAR_TELEPHONE_NUMBER";
+ break;
+case PR_RADIO_TELEPHONE_NUMBER:
+ s = "PR_RADIO_TELEPHONE_NUMBER";
+ break;
+case PR_CAR_TELEPHONE_NUMBER:
+ s = "PR_CAR_TELEPHONE_NUMBER";
+ break;
+case PR_OTHER_TELEPHONE_NUMBER:
+ s = "PR_OTHER_TELEPHONE_NUMBER";
+ break;
+case PR_TRANSMITABLE_DISPLAY_NAME:
+ s = "PR_TRANSMITABLE_DISPLAY_NAME";
+ break;
+case PR_PAGER_TELEPHONE_NUMBER:
+ s = "PR_PAGER_TELEPHONE_NUMBER or PR_BEEPER_TELEPHONE_NUMBER";
+ break;
+case PR_USER_CERTIFICATE:
+ s = "PR_USER_CERTIFICATE";
+ break;
+case PR_PRIMARY_FAX_NUMBER:
+ s = "PR_PRIMARY_FAX_NUMBER";
+ break;
+case PR_BUSINESS_FAX_NUMBER:
+ s = "PR_BUSINESS_FAX_NUMBER";
+ break;
+case PR_HOME_FAX_NUMBER:
+ s = "PR_HOME_FAX_NUMBER";
+ break;
+case PR_COUNTRY:
+ s = "PR_COUNTRY or PR_BUSINESS_ADDRESS_COUNTRY";
+ break;
+
+case PR_LOCALITY:
+ s = "PR_LOCALITY or PR_BUSINESS_ADDRESS_CITY";
+ break;
+
+case PR_STATE_OR_PROVINCE:
+ s = "PR_STATE_OR_PROVINCE or PR_BUSINESS_ADDRESS_STATE_OR_PROVINCE";
+ break;
+
+case PR_STREET_ADDRESS:
+ s = "PR_STREET_ADDRESS or PR_BUSINESS_ADDRESS_STREET";
+ break;
+
+case PR_POSTAL_CODE:
+ s = "PR_POSTAL_CODE or PR_BUSINESS_ADDRESS_POSTAL_CODE";
+ break;
+
+case PR_POST_OFFICE_BOX:
+ s = "PR_POST_OFFICE_BOX or PR_BUSINESS_ADDRESS_POST_OFFICE_BOX";
+ break;
+
+case PR_TELEX_NUMBER:
+ s = "PR_TELEX_NUMBER";
+ break;
+case PR_ISDN_NUMBER:
+ s = "PR_ISDN_NUMBER";
+ break;
+case PR_ASSISTANT_TELEPHONE_NUMBER:
+ s = "PR_ASSISTANT_TELEPHONE_NUMBER";
+ break;
+case PR_HOME2_TELEPHONE_NUMBER:
+ s = "PR_HOME2_TELEPHONE_NUMBER";
+ break;
+case PR_ASSISTANT:
+ s = "PR_ASSISTANT";
+ break;
+case PR_SEND_RICH_INFO:
+ s = "PR_SEND_RICH_INFO";
+ break;
+
+case PR_WEDDING_ANNIVERSARY:
+ s = "PR_WEDDING_ANNIVERSARY";
+ break;
+case PR_BIRTHDAY:
+ s = "PR_BIRTHDAY";
+ break;
+
+case PR_HOBBIES:
+ s = "PR_HOBBIES";
+ break;
+
+case PR_MIDDLE_NAME:
+ s = "PR_MIDDLE_NAME";
+ break;
+
+case PR_DISPLAY_NAME_PREFIX:
+ s = "PR_DISPLAY_NAME_PREFIX";
+ break;
+
+case PR_PROFESSION:
+ s = "PR_PROFESSION";
+ break;
+
+case PR_PREFERRED_BY_NAME:
+ s = "PR_PREFERRED_BY_NAME";
+ break;
+
+case PR_SPOUSE_NAME:
+ s = "PR_SPOUSE_NAME";
+ break;
+
+case PR_COMPUTER_NETWORK_NAME:
+ s = "PR_COMPUTER_NETWORK_NAME";
+ break;
+
+case PR_CUSTOMER_ID:
+ s = "PR_CUSTOMER_ID";
+ break;
+
+case PR_TTYTDD_PHONE_NUMBER:
+ s = "PR_TTYTDD_PHONE_NUMBER";
+ break;
+
+case PR_FTP_SITE:
+ s = "PR_FTP_SITE";
+ break;
+
+case PR_GENDER:
+ s = "PR_GENDER";
+ break;
+
+case PR_MANAGER_NAME:
+ s = "PR_MANAGER_NAME";
+ break;
+
+case PR_NICKNAME:
+ s = "PR_NICKNAME";
+ break;
+
+case PR_PERSONAL_HOME_PAGE:
+ s = "PR_PERSONAL_HOME_PAGE";
+ break;
+
+case PR_BUSINESS_HOME_PAGE:
+ s = "PR_BUSINESS_HOME_PAGE";
+ break;
+
+case PR_CONTACT_VERSION:
+ s = "PR_CONTACT_VERSION";
+ break;
+case PR_CONTACT_ENTRYIDS:
+ s = "PR_CONTACT_ENTRYIDS";
+ break;
+
+case PR_CONTACT_ADDRTYPES:
+ s = "PR_CONTACT_ADDRTYPES";
+ break;
+
+case PR_CONTACT_DEFAULT_ADDRESS_INDEX:
+ s = "PR_CONTACT_DEFAULT_ADDRESS_INDEX";
+ break;
+
+case PR_CONTACT_EMAIL_ADDRESSES:
+ s = "PR_CONTACT_EMAIL_ADDRESSES";
+ break;
+
+case PR_COMPANY_MAIN_PHONE_NUMBER:
+ s = "PR_COMPANY_MAIN_PHONE_NUMBER";
+ break;
+
+case PR_CHILDRENS_NAMES:
+ s = "PR_CHILDRENS_NAMES";
+ break;
+
+case PR_HOME_ADDRESS_CITY:
+ s = "PR_HOME_ADDRESS_CITY";
+ break;
+
+case PR_HOME_ADDRESS_COUNTRY:
+ s = "PR_HOME_ADDRESS_COUNTRY";
+ break;
+
+case PR_HOME_ADDRESS_POSTAL_CODE:
+ s = "PR_HOME_ADDRESS_POSTAL_CODE";
+ break;
+
+case PR_HOME_ADDRESS_STATE_OR_PROVINCE:
+ s = "PR_HOME_ADDRESS_STATE_OR_PROVINCE";
+ break;
+
+case PR_HOME_ADDRESS_STREET:
+ s = "PR_HOME_ADDRESS_STREET";
+ break;
+
+case PR_HOME_ADDRESS_POST_OFFICE_BOX:
+ s = "PR_HOME_ADDRESS_POST_OFFICE_BOX";
+ break;
+
+case PR_OTHER_ADDRESS_CITY:
+ s = "PR_OTHER_ADDRESS_CITY";
+ break;
+
+case PR_OTHER_ADDRESS_COUNTRY:
+ s = "PR_OTHER_ADDRESS_COUNTRY";
+ break;
+
+case PR_OTHER_ADDRESS_POSTAL_CODE:
+ s = "PR_OTHER_ADDRESS_POSTAL_CODE";
+ break;
+
+case PR_OTHER_ADDRESS_STATE_OR_PROVINCE:
+ s = "PR_OTHER_ADDRESS_STATE_OR_PROVINCE";
+ break;
+
+case PR_OTHER_ADDRESS_STREET:
+ s = "PR_OTHER_ADDRESS_STREET";
+ break;
+
+case PR_OTHER_ADDRESS_POST_OFFICE_BOX:
+ s = "PR_OTHER_ADDRESS_POST_OFFICE_BOX";
+ break;
+
+ /*
+ * Profile section properties
+ */
+
+case PR_STORE_PROVIDERS:
+ s = "PR_STORE_PROVIDERS";
+ break;
+case PR_AB_PROVIDERS:
+ s = "PR_AB_PROVIDERS";
+ break;
+case PR_TRANSPORT_PROVIDERS:
+ s = "PR_TRANSPORT_PROVIDERS";
+ break;
+
+case PR_DEFAULT_PROFILE:
+ s = "PR_DEFAULT_PROFILE";
+ break;
+case PR_AB_SEARCH_PATH:
+ s = "PR_AB_SEARCH_PATH";
+ break;
+case PR_AB_DEFAULT_DIR:
+ s = "PR_AB_DEFAULT_DIR";
+ break;
+case PR_AB_DEFAULT_PAB:
+ s = "PR_AB_DEFAULT_PAB";
+ break;
+
+case PR_FILTERING_HOOKS:
+ s = "PR_FILTERING_HOOKS";
+ break;
+case PR_SERVICE_NAME:
+ s = "PR_SERVICE_NAME";
+ break;
+case PR_SERVICE_DLL_NAME:
+ s = "PR_SERVICE_DLL_NAME";
+ break;
+case PR_SERVICE_ENTRY_NAME:
+ s = "PR_SERVICE_ENTRY_NAME";
+ break;
+case PR_SERVICE_UID:
+ s = "PR_SERVICE_UID";
+ break;
+case PR_SERVICE_EXTRA_UIDS:
+ s = "PR_SERVICE_EXTRA_UIDS";
+ break;
+case PR_SERVICES:
+ s = "PR_SERVICES";
+ break;
+case PR_SERVICE_SUPPORT_FILES:
+ s = "PR_SERVICE_SUPPORT_FILES";
+ break;
+case PR_SERVICE_DELETE_FILES:
+ s = "PR_SERVICE_DELETE_FILES";
+ break;
+case PR_AB_SEARCH_PATH_UPDATE:
+ s = "PR_AB_SEARCH_PATH_UPDATE";
+ break;
+case PR_PROFILE_NAME:
+ s = "PR_PROFILE_NAME";
+ break;
+
+ /*
+ * Status object properties
+ */
+
+case PR_IDENTITY_DISPLAY:
+ s = "PR_IDENTITY_DISPLAY";
+ break;
+case PR_IDENTITY_ENTRYID:
+ s = "PR_IDENTITY_ENTRYID";
+ break;
+case PR_RESOURCE_METHODS:
+ s = "PR_RESOURCE_METHODS";
+ break;
+case PR_RESOURCE_TYPE:
+ s = "PR_RESOURCE_TYPE";
+ break;
+case PR_STATUS_CODE:
+ s = "PR_STATUS_CODE";
+ break;
+case PR_IDENTITY_SEARCH_KEY:
+ s = "PR_IDENTITY_SEARCH_KEY";
+ break;
+case PR_OWN_STORE_ENTRYID:
+ s = "PR_OWN_STORE_ENTRYID";
+ break;
+case PR_RESOURCE_PATH:
+ s = "PR_RESOURCE_PATH";
+ break;
+case PR_STATUS_STRING:
+ s = "PR_STATUS_STRING";
+ break;
+case PR_X400_DEFERRED_DELIVERY_CANCEL:
+ s = "PR_X400_DEFERRED_DELIVERY_CANCEL";
+ break;
+case PR_HEADER_FOLDER_ENTRYID:
+ s = "PR_HEADER_FOLDER_ENTRYID";
+ break;
+case PR_REMOTE_PROGRESS:
+ s = "PR_REMOTE_PROGRESS";
+ break;
+case PR_REMOTE_PROGRESS_TEXT:
+ s = "PR_REMOTE_PROGRESS_TEXT";
+ break;
+case PR_REMOTE_VALIDATE_OK:
+ s = "PR_REMOTE_VALIDATE_OK";
+ break;
+
+ /*
+ * Display table properties
+ */
+
+case PR_CONTROL_FLAGS:
+ s = "PR_CONTROL_FLAGS";
+ break;
+case PR_CONTROL_STRUCTURE:
+ s = "PR_CONTROL_STRUCTURE";
+ break;
+case PR_CONTROL_TYPE:
+ s = "PR_CONTROL_TYPE";
+ break;
+case PR_DELTAX:
+ s = "PR_DELTAX";
+ break;
+case PR_DELTAY:
+ s = "PR_DELTAY";
+ break;
+case PR_XPOS:
+ s = "PR_XPOS";
+ break;
+case PR_YPOS:
+ s = "PR_YPOS";
+ break;
+case PR_CONTROL_ID:
+ s = "PR_CONTROL_ID";
+ break;
+case PR_INITIAL_DETAILS_PANE:
+ s = "PR_INITIAL_DETAILS_PANE";
+ break;
+/*
+ * Secure property id range
+ */
+case PROP_ID_SECURE_MIN:
+ s = "PROP_ID_SECURE_MIN";
+ break;
+case PROP_ID_SECURE_MAX:
+ s = "PROP_ID_SECURE_MAX";
+ break;
diff --git a/comm/mailnews/import/src/MorkImport.cpp b/comm/mailnews/import/src/MorkImport.cpp
new file mode 100644
index 0000000000..4cdecb8067
--- /dev/null
+++ b/comm/mailnews/import/src/MorkImport.cpp
@@ -0,0 +1,343 @@
+/* -*- 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/. */
+
+/*
+ * Mork import addressbook interfaces
+ */
+
+#include "MorkImport.h"
+
+#include "nsCOMPtr.h"
+#include "nsIImportService.h"
+#include "nsIImportGeneric.h"
+#include "nsIImportAddressBooks.h"
+#include "nsIImportABDescriptor.h"
+#include "nsIImportFieldMap.h"
+#include "nsImportStringBundle.h"
+#include "nsIComponentManager.h"
+#include "nsIAbDirectory.h"
+#include "nsAddrDatabase.h"
+#include "nsInterfaceHashtable.h"
+#include "nsHashKeys.h"
+
+static const char kRowIDProperty[] = "DbRowID";
+
+class MorkImportAddressImpl final : public nsIImportAddressBooks {
+ public:
+ explicit MorkImportAddressImpl(nsIStringBundle* aStringBundle);
+
+ static nsresult Create(nsIImportAddressBooks** aImport,
+ nsIStringBundle* aStringBundle);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMPORTADDRESSBOOKS
+
+ private:
+ ~MorkImportAddressImpl() {}
+ nsCOMPtr<nsIFile> mFileLocation;
+ nsCOMPtr<nsIStringBundle> mStringBundle;
+};
+
+MorkImport::MorkImport() {
+ nsImportStringBundle::GetStringBundle(
+ "chrome://messenger/locale/morkImportMsgs.properties",
+ getter_AddRefs(mStringBundle));
+}
+
+MorkImport::~MorkImport() {}
+
+NS_IMPL_ISUPPORTS(MorkImport, nsIImportModule)
+
+NS_IMETHODIMP MorkImport::GetName(char16_t** name) {
+ NS_ENSURE_ARG_POINTER(name);
+ *name =
+ nsImportStringBundle::GetStringByName("morkImportName", mStringBundle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP MorkImport::GetDescription(char16_t** description) {
+ NS_ENSURE_ARG_POINTER(description);
+ *description = nsImportStringBundle::GetStringByName("morkImportDescription",
+ mStringBundle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP MorkImport::GetSupports(char** supports) {
+ NS_ENSURE_ARG_POINTER(supports);
+ *supports = strdup(NS_IMPORT_ADDRESS_STR);
+ return NS_OK;
+}
+
+NS_IMETHODIMP MorkImport::GetSupportsUpgrade(bool* upgrade) {
+ NS_ENSURE_ARG_POINTER(upgrade);
+ *upgrade = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP MorkImport::GetImportInterface(const char* importType,
+ nsISupports** interface) {
+ NS_ENSURE_ARG_POINTER(importType);
+ NS_ENSURE_ARG_POINTER(interface);
+
+ *interface = nullptr;
+ nsresult rv;
+
+ if (strcmp(importType, "addressbook")) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsIImportAddressBooks> pAddress;
+ nsCOMPtr<nsIImportGeneric> pGeneric;
+ rv = MorkImportAddressImpl::Create(getter_AddRefs(pAddress), mStringBundle);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImportService> impSvc(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = impSvc->CreateNewGenericAddressBooks(getter_AddRefs(pGeneric));
+ NS_ENSURE_SUCCESS(rv, rv);
+ pGeneric->SetData("addressInterface", pAddress);
+ nsCOMPtr<nsISupports> pInterface(do_QueryInterface(pGeneric));
+ pInterface.forget(interface);
+
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+
+nsresult MorkImportAddressImpl::Create(nsIImportAddressBooks** aImport,
+ nsIStringBundle* aStringBundle) {
+ NS_ENSURE_ARG_POINTER(aImport);
+ NS_ADDREF(*aImport = new MorkImportAddressImpl(aStringBundle));
+ return NS_OK;
+}
+
+MorkImportAddressImpl::MorkImportAddressImpl(nsIStringBundle* aStringBundle)
+ : mStringBundle(aStringBundle) {}
+
+NS_IMPL_ISUPPORTS(MorkImportAddressImpl, nsIImportAddressBooks)
+
+NS_IMETHODIMP MorkImportAddressImpl::GetSupportsMultiple(bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP MorkImportAddressImpl::GetAutoFind(char16_t** addrDescription,
+ bool* _retval) {
+ NS_ENSURE_ARG_POINTER(addrDescription);
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP MorkImportAddressImpl::GetNeedsFieldMap(nsIFile* aLocation,
+ bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP MorkImportAddressImpl::GetDefaultLocation(nsIFile** ppLoc,
+ bool* found,
+ bool* userVerify) {
+ NS_ENSURE_ARG_POINTER(ppLoc);
+ NS_ENSURE_ARG_POINTER(found);
+ NS_ENSURE_ARG_POINTER(userVerify);
+
+ *ppLoc = nullptr;
+ *found = false;
+ *userVerify = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP MorkImportAddressImpl::FindAddressBooks(
+ nsIFile* pLoc, nsTArray<RefPtr<nsIImportABDescriptor>>& books) {
+ NS_ENSURE_ARG_POINTER(pLoc);
+
+ books.Clear();
+ bool exists = false;
+ nsresult rv = pLoc->Exists(&exists);
+ if (NS_FAILED(rv) || !exists) return NS_ERROR_FAILURE;
+
+ bool isFile = false;
+ rv = pLoc->IsFile(&isFile);
+ if (NS_FAILED(rv) || !isFile) return NS_ERROR_FAILURE;
+
+ mFileLocation = pLoc;
+
+ /* Build an address book descriptor based on the file passed in! */
+ nsString name;
+ rv = mFileLocation->GetLeafName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t idx = name.RFindChar('.');
+ if ((idx != -1) && (idx > 0) && ((name.Length() - idx - 1) < 5)) {
+ name.SetLength(idx);
+ }
+
+ nsCOMPtr<nsIImportABDescriptor> desc;
+
+ nsCOMPtr<nsIImportService> impSvc(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = impSvc->CreateNewABDescriptor(getter_AddRefs(desc));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t sz = 0;
+ pLoc->GetFileSize(&sz);
+ desc->SetPreferredName(name);
+ desc->SetSize((uint32_t)sz);
+ desc->SetAbFile(mFileLocation);
+ books.AppendElement(desc);
+ return NS_OK;
+}
+
+NS_IMETHODIMP MorkImportAddressImpl::InitFieldMap(nsIImportFieldMap* fieldMap) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+MorkImportAddressImpl::ImportAddressBook(
+ nsIImportABDescriptor* pSource, nsIAbDirectory* pDestination,
+ nsIImportFieldMap* fieldMap, nsISupports* aSupportService,
+ char16_t** pErrorLog, char16_t** pSuccessLog, bool* fatalError) {
+ NS_ENSURE_ARG_POINTER(pSource);
+ NS_ENSURE_ARG_POINTER(pDestination);
+ NS_ENSURE_ARG_POINTER(fatalError);
+
+ nsCOMPtr<nsIFile> oldFile;
+ pSource->GetAbFile(getter_AddRefs(oldFile));
+
+ nsresult rv = ReadMABToDirectory(oldFile, pDestination);
+
+ *pSuccessLog =
+ nsImportStringBundle::GetStringByName("morkImportSuccess", mStringBundle);
+ return rv;
+}
+
+nsresult ReadMABToDirectory(nsIFile* oldFile, nsIAbDirectory* newDirectory) {
+ nsresult rv;
+
+ nsAddrDatabase database = nsAddrDatabase();
+ database.SetDbPath(oldFile);
+ database.OpenMDB(oldFile, false);
+
+ nsInterfaceHashtable<nsUint32HashKey, nsIAbCard> cardMap;
+
+ nsCOMPtr<nsISimpleEnumerator> enumerator;
+ database.EnumerateCards(getter_AddRefs(enumerator));
+
+ nsCOMPtr<nsISupports> supports;
+ nsCOMPtr<nsIAbCard> card;
+ bool isMailList;
+ while (NS_SUCCEEDED(enumerator->GetNext(getter_AddRefs(supports))) &&
+ supports) {
+ card = do_QueryInterface(supports);
+
+ card->GetIsMailList(&isMailList);
+ if (isMailList) {
+ continue;
+ }
+
+ uint32_t rowId;
+ card->GetPropertyAsUint32(kRowIDProperty, &rowId);
+ cardMap.InsertOrUpdate(rowId, card);
+
+ nsIAbCard* outCard;
+ newDirectory->AddCard(card, &outCard);
+ }
+
+ database.EnumerateCards(getter_AddRefs(enumerator));
+
+ while (NS_SUCCEEDED(enumerator->GetNext(getter_AddRefs(supports))) &&
+ supports) {
+ card = do_QueryInterface(supports);
+ card->GetIsMailList(&isMailList);
+
+ if (!isMailList) {
+ continue;
+ }
+
+ nsCOMPtr<nsIAbDirectory> mailList =
+ do_CreateInstance("@mozilla.org/addressbook/directoryproperty;1");
+ mailList->SetIsMailList(true);
+
+ nsAutoString listName;
+ card->GetDisplayName(listName);
+ mailList->SetDirName(listName);
+
+ nsAutoString nickName;
+ rv = card->GetPropertyAsAString("NickName", nickName);
+ if (NS_SUCCEEDED(rv)) {
+ mailList->SetListNickName(nickName);
+ }
+
+ nsAutoString description;
+ rv = card->GetPropertyAsAString("Notes", description);
+ if (NS_SUCCEEDED(rv)) {
+ mailList->SetDescription(description);
+ }
+
+ nsIAbDirectory* outList;
+ rv = newDirectory->AddMailList(mailList, &outList);
+ if (NS_FAILED(rv)) {
+ continue;
+ }
+
+ uint32_t listRowId;
+ card->GetPropertyAsUint32(kRowIDProperty, &listRowId);
+
+ nsCOMPtr<nsISimpleEnumerator> listEnumerator;
+ database.EnumerateListAddresses(listRowId, getter_AddRefs(listEnumerator));
+
+ nsCOMPtr<nsISupports> listSupports;
+ nsCOMPtr<nsIAbCard> listCard;
+ while (
+ NS_SUCCEEDED(listEnumerator->GetNext(getter_AddRefs(listSupports))) &&
+ listSupports) {
+ listCard = do_QueryInterface(listSupports);
+
+ uint32_t rowId;
+ listCard->GetPropertyAsUint32(kRowIDProperty, &rowId);
+ cardMap.Get(rowId, getter_AddRefs(listCard));
+
+ nsIAbCard* outCard;
+ outList->AddCard(listCard, &outCard);
+ }
+ }
+
+ database.ForceClosed();
+ return NS_OK;
+}
+
+NS_IMETHODIMP MorkImportAddressImpl::GetImportProgress(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = 0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP MorkImportAddressImpl::SetSampleLocation(nsIFile* pLocation) {
+ NS_ENSURE_ARG_POINTER(pLocation);
+ return NS_OK;
+}
+
+NS_IMETHODIMP MorkImportAddressImpl::GetSampleData(int32_t index, bool* pFound,
+ char16_t** pStr) {
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsImportABFromMab, nsIImportABFile)
+
+nsImportABFromMab::nsImportABFromMab() {}
+
+NS_IMETHODIMP
+nsImportABFromMab::ReadFileToDirectory(nsIFile* sourceFile,
+ nsIAbDirectory* targetDirectory) {
+ return ReadMABToDirectory(sourceFile, targetDirectory);
+}
diff --git a/comm/mailnews/import/src/MorkImport.h b/comm/mailnews/import/src/MorkImport.h
new file mode 100644
index 0000000000..80a0d25bec
--- /dev/null
+++ b/comm/mailnews/import/src/MorkImport.h
@@ -0,0 +1,50 @@
+/* -*- 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 MorkImport_h___
+#define MorkImport_h___
+
+#include "nsIImportABFile.h"
+#include "nsIImportModule.h"
+#include "nsCOMPtr.h"
+#include "nsIStringBundle.h"
+
+#include "nsIFile.h"
+#include "nsIAbDirectory.h"
+
+#define MORKIMPORT_CID \
+ { /* 54d48d9f-1bac-47be-9190-c4dc74e837e2 */ \
+ 0x54d48d9f, 0x1bac, 0x47be, { \
+ 0x91, 0x90, 0xc4, 0xdc, 0x74, 0xe8, 0x37, 0xe2 \
+ } \
+ }
+
+nsresult ReadMABToDirectory(nsIFile* oldFile, nsIAbDirectory* newDirectory);
+
+class nsImportABFromMab : public nsIImportABFile {
+ public:
+ nsImportABFromMab();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIMPORTABFILE
+
+ protected:
+ virtual ~nsImportABFromMab(){};
+};
+
+class MorkImport : public nsIImportModule {
+ public:
+ MorkImport();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIMPORTMODULE
+
+ protected:
+ virtual ~MorkImport();
+ nsCOMPtr<nsIStringBundle> mStringBundle;
+};
+
+#endif /* MorkImport_h___ */
diff --git a/comm/mailnews/import/src/SeamonkeyImport.jsm b/comm/mailnews/import/src/SeamonkeyImport.jsm
new file mode 100644
index 0000000000..c88f1830d7
--- /dev/null
+++ b/comm/mailnews/import/src/SeamonkeyImport.jsm
@@ -0,0 +1,253 @@
+/* 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/. */
+
+let { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+let seamonkeyImportMsgs = Services.strings.createBundle(
+ "chrome://messenger/locale/seamonkeyImportMsgs.properties"
+);
+
+var EXPORTED_SYMBOLS = ["SeamonkeyImport"];
+
+/**
+ * Implements nsIImportGeneric instead of nsIImportAddressBook. The actual
+ * importing is delegated to nsSeamonkeyProfileMigrator.
+ */
+function SeamonkeyImportAddressbook() {
+ this.migrator = Cc[
+ "@mozilla.org/profile/migrator;1?app=mail&type=seamonkey"
+ ].createInstance(Ci.nsIMailProfileMigrator);
+ this.sourceProfileName = null;
+ this.sourceProfileLocation = null;
+}
+
+SeamonkeyImportAddressbook.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIImportGeneric"]),
+
+ /**
+ * Return the location of addressbook.
+ */
+ GetData() {
+ if (!this.sourceProfileName || !this.sourceProfileLocation) {
+ try {
+ this.sourceProfileName = this.migrator.sourceProfiles[0];
+ this.sourceProfileLocation = this.migrator.sourceProfileLocations[0];
+ } catch (e) {
+ return null;
+ }
+ }
+
+ return this.sourceProfileLocation;
+ },
+
+ SetData() {
+ return 0;
+ },
+
+ WantsProgress() {
+ return false;
+ },
+
+ GetProgress() {
+ return 0;
+ },
+
+ GetStatus() {
+ return 0;
+ },
+
+ CancelImport() {
+ return 0;
+ },
+
+ ContinueImport() {
+ return 0;
+ },
+
+ BeginImport(successLog, errorLog) {
+ this.migrator.migrate(
+ Ci.nsIMailProfileMigrator.ADDRESSBOOK_DATA,
+ null,
+ this.sourceProfileName
+ );
+ successLog.data = seamonkeyImportMsgs.GetStringFromName(
+ "SeamonkeyImportAddressSuccess"
+ );
+ return true;
+ },
+};
+
+/**
+ * Implements nsIImportMail. The importing process is managed by nsImportMail.
+ */
+function SeamonkeyImportMail() {}
+
+SeamonkeyImportMail.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIImportMail"]),
+
+ GetDefaultLocation(location, found, userVerify) {
+ let migrator = Cc[
+ "@mozilla.org/profile/migrator;1?app=mail&type=seamonkey"
+ ].createInstance(Ci.nsIMailProfileMigrator);
+
+ try {
+ let sourceProfile = migrator.sourceProfileLocations[0];
+ location.value = sourceProfile;
+ found.value = true;
+ } catch (e) {
+ found.value = false;
+ }
+ userVerify.value = false;
+ },
+
+ _createMailboxDescriptor(path, name, depth) {
+ let importService = Cc[
+ "@mozilla.org/import/import-service;1"
+ ].createInstance(Ci.nsIImportService);
+ let descriptor = importService.CreateNewMailboxDescriptor();
+ descriptor.size = 100;
+ descriptor.depth = depth;
+ descriptor.SetDisplayName(name);
+ descriptor.file.initWithPath(path);
+
+ return descriptor;
+ },
+
+ _collectMailboxesInDirectory(directory, depth) {
+ let result = [];
+ let name = directory.leafName;
+ if (depth > 0 && !name.endsWith(".msf") && !name.endsWith(".dat")) {
+ if (name.endsWith(".sbd")) {
+ name = name.slice(0, name.lastIndexOf("."));
+ }
+ let descriptor = this._createMailboxDescriptor(
+ directory.path,
+ name,
+ depth
+ );
+ result.push(descriptor);
+ }
+ if (directory.isDirectory()) {
+ for (let entry of directory.directoryEntries) {
+ if (
+ (depth == 0 &&
+ entry.leafName != "ImapMail" &&
+ entry.leafName != "Mail") ||
+ (depth == 1 && entry.leafName == "Feeds")
+ ) {
+ continue;
+ }
+ result.push(...this._collectMailboxesInDirectory(entry, depth + 1));
+ }
+ }
+ return result;
+ },
+
+ // Collect mailboxes in a Seamonkey profile.
+ findMailboxes(location) {
+ return this._collectMailboxesInDirectory(location, 0);
+ },
+
+ // Copy mailboxes a Seamonkey profile to Thunderbird profile.
+ ImportMailbox(source, dstFolder, errorLog, successLog, fatalError) {
+ if (source.file.isFile()) {
+ source.file.copyTo(
+ dstFolder.filePath.parent,
+ dstFolder.filePath.leafName
+ );
+ successLog.value = `Import ${source.file.leafName} succeeded.\n`;
+ }
+ },
+};
+
+/**
+ * Implements nsIImportSettings. The actual importing is delegated to
+ * nsSeamonkeyProfileMigrator.
+ */
+function SeamonkeyImportSettings() {
+ this.migrator = Cc[
+ "@mozilla.org/profile/migrator;1?app=mail&type=seamonkey"
+ ].createInstance(Ci.nsIMailProfileMigrator);
+ this.sourceProfileName = null;
+ this.sourceProfileLocation = null;
+}
+
+SeamonkeyImportSettings.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIImportSettings"]),
+
+ AutoLocate(desc, loc) {
+ if (!this.sourceProfileName || !this.sourceProfileLocation) {
+ try {
+ this.sourceProfileName = this.migrator.sourceProfiles[0];
+ this.sourceProfileLocation = this.migrator.sourceProfileLocations[0];
+ } catch (e) {
+ return false;
+ }
+ }
+ loc = this.sourceProfileLocation;
+ return true;
+ },
+
+ Import() {
+ this.migrator.migrate(
+ Ci.nsIMailProfileMigrator.SETTINGS,
+ null,
+ this.sourceProfileName
+ );
+
+ // Reload accounts so that `CheckIfLocalFolderExists` in importDialog works
+ MailServices.accounts.unloadAccounts();
+ MailServices.accounts.loadAccounts();
+ return true;
+ },
+};
+
+/**
+ * Implements nsIImportModule so that Seamonkey is shown as an option in the
+ * importDialog.xhtml. Currently supports importing addressbook and mail, see
+ * the GetImportInterface function.
+ */
+function SeamonkeyImport() {}
+
+SeamonkeyImport.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIImportModule"]),
+
+ get name() {
+ return seamonkeyImportMsgs.GetStringFromName("SeamonkeyImportName");
+ },
+
+ get description() {
+ return seamonkeyImportMsgs.GetStringFromName("SeamonkeyImportDescription");
+ },
+
+ get supports() {
+ return "addressbook,mail,settings";
+ },
+
+ get supportsUpgrade() {
+ return false;
+ },
+
+ GetImportInterface(type) {
+ if (type == "addressbook") {
+ return new SeamonkeyImportAddressbook();
+ } else if (type == "mail") {
+ let importService = Cc[
+ "@mozilla.org/import/import-service;1"
+ ].createInstance(Ci.nsIImportService);
+ let genericInterface = importService.CreateNewGenericMail();
+ genericInterface.SetData("mailInterface", new SeamonkeyImportMail());
+ let name = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ name.data = "SeaMonkey";
+ genericInterface.SetData("name", name);
+ return genericInterface;
+ } else if (type == "settings") {
+ return new SeamonkeyImportSettings();
+ }
+ return null;
+ },
+};
diff --git a/comm/mailnews/import/src/ThunderbirdImport.jsm b/comm/mailnews/import/src/ThunderbirdImport.jsm
new file mode 100644
index 0000000000..8263fa7098
--- /dev/null
+++ b/comm/mailnews/import/src/ThunderbirdImport.jsm
@@ -0,0 +1,145 @@
+/* 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 = ["ThunderbirdImport"];
+
+const { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+const lazy = {};
+
+XPCOMUtils.defineLazyGetter(
+ lazy,
+ "l10n",
+ () => new Localization(["messenger/importDialog.ftl"], true)
+);
+
+/**
+ * The importing process is managed by importDialog.js and nsImportMail.cpp.
+ *
+ * @implements {nsIImportMail}
+ */
+class ThunderbirdImportMail {
+ QueryInterface = ChromeUtils.generateQI(["nsIImportMail"]);
+
+ GetDefaultLocation(location, found, userVerify) {
+ userVerify.value = true;
+ }
+
+ /**
+ * Create a nsIImportMailboxDescriptor instance.
+ *
+ * @param {string} path - The mailbox path.
+ * @param {string} name - The mailbox name.
+ * @param {number} depth - The depth of the mailbox folder.
+ * @returns {nsIImportMailboxDescriptor}
+ */
+ _createMailboxDescriptor(path, name, depth) {
+ let importService = Cc[
+ "@mozilla.org/import/import-service;1"
+ ].createInstance(Ci.nsIImportService);
+ let descriptor = importService.CreateNewMailboxDescriptor();
+ descriptor.size = 100;
+ descriptor.depth = depth;
+ descriptor.SetDisplayName(name);
+ descriptor.file.initWithPath(path);
+
+ return descriptor;
+ }
+
+ /**
+ * Recursively find mailboxes in a directory.
+ *
+ * @param {nsIFile} directory - The directory to find mailboxes.
+ * @param {number} depth - The depth of the current directory.
+ * @returns {nsIImportMailboxDescriptor[]} - All mailboxes found.
+ */
+ _collectMailboxesInDirectory(directory, depth) {
+ let result = [];
+ let name = directory.leafName;
+ if (depth > 0 && !name.endsWith(".msf") && !name.endsWith(".dat")) {
+ if (name.endsWith(".sbd")) {
+ name = name.slice(0, name.lastIndexOf("."));
+ }
+ let descriptor = this._createMailboxDescriptor(
+ directory.path,
+ name,
+ depth
+ );
+ result.push(descriptor);
+ }
+ if (directory.isDirectory()) {
+ for (let entry of directory.directoryEntries) {
+ if (
+ (depth == 0 &&
+ entry.leafName != "ImapMail" &&
+ entry.leafName != "Mail") ||
+ (depth == 1 && entry.leafName == "Feeds")
+ ) {
+ continue;
+ }
+ result.push(...this._collectMailboxesInDirectory(entry, depth + 1));
+ }
+ }
+ return result;
+ }
+
+ findMailboxes(location) {
+ return this._collectMailboxesInDirectory(location, 0);
+ }
+
+ ImportMailbox(source, dstFolder, errorLog, successLog, fatalError) {
+ if (source.file.isFile()) {
+ source.file.copyTo(
+ dstFolder.filePath.parent,
+ dstFolder.filePath.leafName
+ );
+ successLog.value = `Import ${source.file.leafName} succeeded.\n`;
+ }
+ }
+}
+
+/**
+ * With this class, Thunderbird is shown as an option in the importDialog.xhtml.
+ * Currently supports importing mail, see the GetImportInterface function.
+ *
+ * @implements {nsIImportModule}
+ */
+class ThunderbirdImport {
+ QueryInterface = ChromeUtils.generateQI(["nsIImportModule"]);
+
+ get name() {
+ return lazy.l10n.formatValueSync("thunderbird-import-name");
+ }
+
+ get description() {
+ return lazy.l10n.formatValueSync("thunderbird-import-description");
+ }
+
+ get supports() {
+ return "mail";
+ }
+
+ get supportsUpgrade() {
+ return false;
+ }
+
+ GetImportInterface(type) {
+ if (type == "mail") {
+ let importService = Cc[
+ "@mozilla.org/import/import-service;1"
+ ].createInstance(Ci.nsIImportService);
+ let genericInterface = importService.CreateNewGenericMail();
+ genericInterface.SetData("mailInterface", new ThunderbirdImportMail());
+ let name = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ name.data = "Thunderbird";
+ genericInterface.SetData("name", name);
+ return genericInterface;
+ }
+ return null;
+ }
+}
diff --git a/comm/mailnews/import/src/components.conf b/comm/mailnews/import/src/components.conf
new file mode 100644
index 0000000000..e8891fca57
--- /dev/null
+++ b/comm/mailnews/import/src/components.conf
@@ -0,0 +1,104 @@
+# -*- 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": "{991f078e-a6d5-44f2-b91e-c52efcfa3360}",
+ "contract_ids": ["@mozilla.org/import/import-seamonkey;1"],
+ "jsm": "resource:///modules/SeamonkeyImport.jsm",
+ "constructor": "SeamonkeyImport",
+ "categories": {"mailnewsimport": "seamonkey"},
+ },
+ {
+ "cid": "{c6988841-d916-44a3-bb4d-f0838a98e95a}",
+ "contract_ids": ["@mozilla.org/import/import-thunderbird;1"],
+ "jsm": "resource:///modules/ThunderbirdImport.jsm",
+ "constructor": "ThunderbirdImport",
+ "categories": {"mailnewsimport": "thunderbird"},
+ },
+ {
+ "cid": "{a6629718-9a97-4073-ab48-442fcceaea5d}",
+ "contract_ids": ["@mozilla.org/import/import-ab-file;1?type=mab"],
+ "type": "nsImportABFromMab",
+ "headers": ["/comm/mailnews/import/src/MorkImport.h"],
+ },
+ {
+ "cid": "{5df96d60-1726-11d3-a206-00a0cc26da63}",
+ "contract_ids": ["@mozilla.org/import/import-service;1"],
+ "type": "nsImportService",
+ "headers": ["/comm/mailnews/import/src/nsImportService.h"],
+ "name": "Import",
+ "interfaces": ["nsIImportService"],
+ },
+ {
+ "cid": "{a5991d01-ada7-11d3-a9c2-00a0cc26da63}",
+ "contract_ids": ["@mozilla.org/import/import-text;1"],
+ "type": "nsTextImport",
+ "headers": ["/comm/mailnews/import/src/nsTextImport.h"],
+ "categories": {"mailnewsimport": "text"},
+ },
+ {
+ "cid": "{0eb034a3-964a-4e2f-92ebcc55d9ae9dd2}",
+ "contract_ids": ["@mozilla.org/import/import-vcard;1"],
+ "type": "nsVCardImport",
+ "headers": ["/comm/mailnews/import/src/nsVCardImport.h"],
+ "categories": {"mailnewsimport": "vcard"},
+ },
+ {
+ "cid": "{54d48d9f-1bac-47be-9190-c4dc74e837e2}",
+ "contract_ids": ["@mozilla.org/import/import-mork;1"],
+ "type": "MorkImport",
+ "headers": ["/comm/mailnews/import/src/MorkImport.h"],
+ "categories": {"mailnewsimport": "mork"},
+ },
+]
+
+if buildconfig.substs["OS_ARCH"] == "Darwin":
+ Classes += [
+ {
+ "cid": "{6d3f101c-70ec-4e04-b68d-9908d1aeddf3}",
+ "contract_ids": ["@mozilla.org/import/import-applemail;1"],
+ "type": "nsAppleMailImportModule",
+ "headers": ["/comm/mailnews/import/src/nsAppleMailImport.h"],
+ "categories": {"mailnewsimport": "applemail"},
+ },
+ {
+ "cid": "{9117a1ea-e012-43b5-a020-cb8a66cc09e1}",
+ "contract_ids": ["@mozilla.org/import/import-appleMailImpl;1"],
+ "type": "nsAppleMailImportMail",
+ "init_method": "Initialize",
+ "headers": ["/comm/mailnews/import/src/nsAppleMailImport.h"],
+ },
+ ]
+
+if buildconfig.substs["OS_ARCH"] == "WINNT":
+ Classes += [
+ {
+ "cid": "{42bc82bc-8e9f-4597-8b6e-e529daaf3af1}",
+ "contract_ids": ["@mozilla.org/import/import-wm;1"],
+ "type": "nsWMImport",
+ "headers": ["/comm/mailnews/import/src/nsWMImport.h"],
+ "categories": {"mailnewsimport": "winlivemail"},
+ },
+ {
+ "cid": "{7952a6cf-2442-4c04-9f02-150b15a0a841}",
+ "contract_ids": ["@mozilla.org/import/import-becky;1"],
+ "type": "nsBeckyImport",
+ "headers": ["/comm/mailnews/import/src/nsBeckyImport.h"],
+ "categories": {"mailnewsimport": "becky"},
+ },
+ ]
+
+ if buildconfig.substs["MOZ_MAPI_SUPPORT"]:
+ Classes += [
+ {
+ "cid": "{1db469a0-8b00-11d3-a206-00a0cc26da63}",
+ "contract_ids": ["@mozilla.org/import/import-outlook;1"],
+ "type": "nsOutlookImport",
+ "headers": ["/comm/mailnews/import/src/nsOutlookImport.h"],
+ "categories": {"mailnewsimport": "outlook"},
+ },
+ ]
diff --git a/comm/mailnews/import/src/moz.build b/comm/mailnews/import/src/moz.build
new file mode 100644
index 0000000000..d587d87fa5
--- /dev/null
+++ b/comm/mailnews/import/src/moz.build
@@ -0,0 +1,86 @@
+# 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 += [
+ "ImportCharSet.cpp",
+ "ImportOutFile.cpp",
+ "ImportTranslate.cpp",
+ "MorkImport.cpp",
+ "nsAddrDatabase.cpp",
+ "nsImportABDescriptor.cpp",
+ "nsImportAddressBooks.cpp",
+ "nsImportEmbeddedImageData.cpp",
+ "nsImportEncodeScan.cpp",
+ "nsImportFieldMap.cpp",
+ "nsImportMail.cpp",
+ "nsImportMailboxDescriptor.cpp",
+ "nsImportScanFile.cpp",
+ "nsImportService.cpp",
+ "nsImportStringBundle.cpp",
+ "nsImportTranslator.cpp",
+ "nsTextAddress.cpp",
+ "nsTextImport.cpp",
+ "nsVCardAddress.cpp",
+ "nsVCardImport.cpp",
+]
+
+if not CONFIG["MOZ_SUITE"]:
+ EXTRA_JS_MODULES += [
+ "SeamonkeyImport.jsm",
+ "ThunderbirdImport.jsm",
+ ]
+
+ XPCOM_MANIFESTS += [
+ "components.conf",
+ ]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ SOURCES += [
+ "nsAppleMailImport.cpp",
+ "nsEmlxHelperUtils.mm",
+ ]
+
+if CONFIG["OS_ARCH"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "nsBeckyAddressBooks.cpp",
+ "nsBeckyFilters.cpp",
+ "nsBeckyImport.cpp",
+ "nsBeckyMail.cpp",
+ "nsBeckySettings.cpp",
+ "nsBeckyStringBundle.cpp",
+ "nsBeckyUtils.cpp",
+ ]
+
+ if CONFIG["MOZ_MAPI_SUPPORT"]:
+ SOURCES += [
+ "MapiApi.cpp",
+ "MapiMessage.cpp",
+ "MapiMimeTypes.cpp",
+ "nsOutlookCompose.cpp",
+ "nsOutlookImport.cpp",
+ "nsOutlookMail.cpp",
+ "nsOutlookSettings.cpp",
+ "nsOutlookStringBundle.cpp",
+ "rtfDecoder.cpp",
+ "rtfMailDecoder.cpp",
+ ]
+
+ SOURCES["rtfDecoder.cpp"].flags += ["-Wno-switch"]
+ LOCAL_INCLUDES += ["/comm/mailnews/mapi/include"]
+
+ if CONFIG["CC_TYPE"] in ("msvc", "clang-cl"):
+ SOURCES += [
+ "nsWMImport.cpp",
+ "nsWMSettings.cpp",
+ "nsWMStringBundle.cpp",
+ "nsWMUtils.cpp",
+ ]
+
+EXPORTS += [
+ "ImportDebug.h",
+ "nsVCardAddress.h",
+]
+
+FINAL_LIBRARY = "import"
diff --git a/comm/mailnews/import/src/nsAddrDatabase.cpp b/comm/mailnews/import/src/nsAddrDatabase.cpp
new file mode 100644
index 0000000000..1bc0df1795
--- /dev/null
+++ b/comm/mailnews/import/src/nsAddrDatabase.cpp
@@ -0,0 +1,864 @@
+/* -*- 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/. */
+
+// this file implements the nsAddrDatabase interface using the MDB Interface.
+
+#include "nsAddrDatabase.h"
+#include "nsMsgUtils.h"
+#include "nsIMdbFactoryFactory.h"
+#include "nsSimpleEnumerator.h"
+
+#define kAddressCharSetColumn "AddrCharSet"
+#define kMailListName "ListName"
+#define kMailListNickName "ListNickName"
+#define kMailListDescription "ListDescription"
+#define kMailListTotalAddresses "ListTotalAddresses"
+// not shown in the UI
+#define kLowerPriEmailColumn "LowercasePrimaryEmail"
+#define kLower2ndEmailColumn "LowercaseSecondEmail"
+
+#define ID_PAB_TABLE 1
+
+static const char kPabTableKind[] = "ns:addrbk:db:table:kind:pab";
+static const char kDeletedCardsTableKind[] =
+ "ns:addrbk:db:table:kind:deleted"; // this table is used to keep the
+ // deleted cards
+
+static const char kCardRowScope[] = "ns:addrbk:db:row:scope:card:all";
+static const char kListRowScope[] = "ns:addrbk:db:row:scope:list:all";
+static const char kDataRowScope[] = "ns:addrbk:db:row:scope:data:all";
+
+#define COLUMN_STR_MAX 16
+
+static const char kRecordKeyColumn[] = "RecordKey";
+static const char kLastRecordKeyColumn[] = "LastRecordKey";
+static const char kRowIDProperty[] = "DbRowID";
+
+static const char kLowerListNameColumn[] = "LowercaseListName";
+
+struct mdbOid gAddressBookTableOID;
+
+static const char kMailListAddressFormat[] = "Address%d";
+
+nsAddrDatabase::nsAddrDatabase()
+ : m_mdbEnv(nullptr),
+ m_mdbStore(nullptr),
+ m_mdbPabTable(nullptr),
+ m_mdbTokensInitialized(false),
+ m_PabTableKind(0),
+ m_DeletedCardsTableKind(0),
+ m_CardRowScopeToken(0),
+ m_UIDColumnToken(0),
+ m_FirstNameColumnToken(0),
+ m_LastNameColumnToken(0),
+ m_PhoneticFirstNameColumnToken(0),
+ m_PhoneticLastNameColumnToken(0),
+ m_DisplayNameColumnToken(0),
+ m_NickNameColumnToken(0),
+ m_PriEmailColumnToken(0),
+ m_2ndEmailColumnToken(0),
+ m_WorkPhoneColumnToken(0),
+ m_HomePhoneColumnToken(0),
+ m_FaxColumnToken(0),
+ m_PagerColumnToken(0),
+ m_CellularColumnToken(0),
+ m_WorkPhoneTypeColumnToken(0),
+ m_HomePhoneTypeColumnToken(0),
+ m_FaxTypeColumnToken(0),
+ m_PagerTypeColumnToken(0),
+ m_CellularTypeColumnToken(0),
+ m_HomeAddressColumnToken(0),
+ m_HomeAddress2ColumnToken(0),
+ m_HomeCityColumnToken(0),
+ m_HomeStateColumnToken(0),
+ m_HomeZipCodeColumnToken(0),
+ m_HomeCountryColumnToken(0),
+ m_WorkAddressColumnToken(0),
+ m_WorkAddress2ColumnToken(0),
+ m_WorkCityColumnToken(0),
+ m_WorkStateColumnToken(0),
+ m_WorkZipCodeColumnToken(0),
+ m_WorkCountryColumnToken(0),
+ m_CompanyColumnToken(0),
+ m_AimScreenNameColumnToken(0),
+ m_AnniversaryYearColumnToken(0),
+ m_AnniversaryMonthColumnToken(0),
+ m_AnniversaryDayColumnToken(0),
+ m_SpouseNameColumnToken(0),
+ m_FamilyNameColumnToken(0),
+ m_DefaultAddressColumnToken(0),
+ m_CategoryColumnToken(0),
+ m_WebPage1ColumnToken(0),
+ m_WebPage2ColumnToken(0),
+ m_BirthYearColumnToken(0),
+ m_BirthMonthColumnToken(0),
+ m_BirthDayColumnToken(0),
+ m_Custom1ColumnToken(0),
+ m_Custom2ColumnToken(0),
+ m_Custom3ColumnToken(0),
+ m_Custom4ColumnToken(0),
+ m_NotesColumnToken(0),
+ m_LastModDateColumnToken(0),
+ m_PopularityIndexColumnToken(0),
+ m_AddressCharSetColumnToken(0) {}
+
+nsAddrDatabase::~nsAddrDatabase() {
+ Close(false); // better have already been closed.
+
+ // RemoveFromCache(this);
+ // clean up after ourself!
+ if (m_mdbPabTable) m_mdbPabTable->Release();
+ NS_IF_RELEASE(m_mdbStore);
+ NS_IF_RELEASE(m_mdbEnv);
+}
+
+nsresult nsAddrDatabase::GetMDBFactory(nsIMdbFactory** aMdbFactory) {
+ if (!mMdbFactory) {
+ nsresult rv;
+ nsCOMPtr<nsIMdbFactoryService> mdbFactoryService =
+ do_GetService("@mozilla.org/db/mork;1", &rv);
+ if (NS_SUCCEEDED(rv) && mdbFactoryService) {
+ rv = mdbFactoryService->GetMdbFactory(getter_AddRefs(mMdbFactory));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mMdbFactory) return NS_ERROR_FAILURE;
+ }
+ }
+ NS_ADDREF(*aMdbFactory = mMdbFactory);
+ return NS_OK;
+}
+
+nsresult nsAddrDatabase::SetDbPath(nsIFile* aDbPath) {
+ return aDbPath->Clone(getter_AddRefs(m_dbName));
+}
+
+// Open the MDB database synchronously. If successful, this routine
+// will set up the m_mdbStore and m_mdbEnv of the database object
+// so other database calls can work.
+nsresult nsAddrDatabase::OpenMDB(nsIFile* dbName, bool create) {
+ nsCOMPtr<nsIMdbFactory> mdbFactory;
+ nsresult ret = GetMDBFactory(getter_AddRefs(mdbFactory));
+ NS_ENSURE_SUCCESS(ret, ret);
+
+ ret = mdbFactory->MakeEnv(NULL, &m_mdbEnv);
+ if (NS_SUCCEEDED(ret)) {
+ nsIMdbThumb* thumb = nullptr;
+
+ PathString filePath = dbName->NativePath();
+
+ nsIMdbHeap* dbHeap = nullptr;
+
+ if (m_mdbEnv) m_mdbEnv->SetAutoClear(true);
+
+ bool dbNameExists = false;
+ ret = dbName->Exists(&dbNameExists);
+ NS_ENSURE_SUCCESS(ret, ret);
+
+ if (!dbNameExists)
+ ret = NS_ERROR_FILE_NOT_FOUND;
+ else {
+ mdbOpenPolicy inOpenPolicy;
+ mdb_bool canOpen;
+ mdbYarn outFormatVersion;
+ nsIMdbFile* oldFile = nullptr;
+ int64_t fileSize;
+ ret = dbName->GetFileSize(&fileSize);
+ NS_ENSURE_SUCCESS(ret, ret);
+
+ ret = mdbFactory->OpenOldFile(
+ m_mdbEnv, dbHeap, filePath.get(),
+ mdbBool_kFalse, // not readonly, we want modifiable
+ &oldFile);
+ if (oldFile) {
+ if (NS_SUCCEEDED(ret)) {
+ ret = mdbFactory->CanOpenFilePort(m_mdbEnv,
+ oldFile, // the file to investigate
+ &canOpen, &outFormatVersion);
+ if (NS_SUCCEEDED(ret) && canOpen) {
+ inOpenPolicy.mOpenPolicy_ScopePlan.mScopeStringSet_Count = 0;
+ inOpenPolicy.mOpenPolicy_MinMemory = 0;
+ inOpenPolicy.mOpenPolicy_MaxLazy = 0;
+
+ ret = mdbFactory->OpenFileStore(m_mdbEnv, dbHeap, oldFile,
+ &inOpenPolicy, &thumb);
+ } else if (fileSize != 0)
+ ret = NS_ERROR_FILE_ACCESS_DENIED;
+ }
+ NS_RELEASE(oldFile); // always release our file ref, store has own
+ }
+ if (NS_FAILED(ret)) ret = NS_ERROR_FILE_ACCESS_DENIED;
+ }
+
+ if (NS_SUCCEEDED(ret) && thumb) {
+ mdb_count outTotal; // total somethings to do in operation
+ mdb_count outCurrent; // subportion of total completed so far
+ mdb_bool outDone = false; // is operation finished?
+ mdb_bool outBroken; // is operation irreparably dead and broken?
+ do {
+ ret = thumb->DoMore(m_mdbEnv, &outTotal, &outCurrent, &outDone,
+ &outBroken);
+ if (NS_FAILED(ret)) {
+ outDone = true;
+ break;
+ }
+ } while (NS_SUCCEEDED(ret) && !outBroken && !outDone);
+ if (NS_SUCCEEDED(ret) && outDone) {
+ ret = mdbFactory->ThumbToOpenStore(m_mdbEnv, thumb, &m_mdbStore);
+ if (NS_SUCCEEDED(ret) && m_mdbStore) {
+ ret = InitExistingDB();
+ create = false;
+ }
+ }
+ } else if (create && ret != NS_ERROR_FILE_ACCESS_DENIED) {
+ ret = NS_ERROR_NOT_IMPLEMENTED;
+ }
+ NS_IF_RELEASE(thumb);
+ }
+ return ret;
+}
+
+nsresult nsAddrDatabase::CloseMDB(bool commit) {
+ if (commit) return NS_ERROR_NOT_IMPLEMENTED;
+ //??? RemoveFromCache(this); // if we've closed it, better not leave it in
+ // the cache.
+ return NS_OK;
+}
+
+// force the database to close - this'll flush out anybody holding onto
+// a database without having a listener!
+// This is evil in the com world, but there are times we need to delete the
+// file.
+nsresult nsAddrDatabase::ForceClosed() {
+ nsresult err = NS_OK;
+
+ // make sure someone has a reference so object won't get deleted out from
+ // under us.
+ // NS_ADDREF_THIS();
+ // OK, remove from cache first and close the store.
+ // RemoveFromCache(this);
+
+ err = CloseMDB(false); // since we're about to delete it, no need to commit.
+ NS_IF_RELEASE(m_mdbStore);
+ // NS_RELEASE_THIS();
+ return err;
+}
+
+nsresult nsAddrDatabase::Close(bool forceCommit /* = TRUE */) {
+ return CloseMDB(forceCommit);
+}
+
+nsresult nsAddrDatabase::InitExistingDB() {
+ nsresult err = InitMDBInfo();
+ if (NS_SUCCEEDED(err)) {
+ if (!m_mdbStore || !m_mdbEnv) return NS_ERROR_NULL_POINTER;
+
+ err = m_mdbStore->GetTable(m_mdbEnv, &gAddressBookTableOID, &m_mdbPabTable);
+ if (NS_SUCCEEDED(err) && m_mdbPabTable) {
+ // This code has always run here. Removing it fails an assertion in the
+ // Mork code which indicates a bad state. In the interest of saving
+ // effort, and since this whole file is doomed after the next release,
+ // I'm leaving it behind.
+ nsIMdbTableRowCursor* rowCursor = nullptr;
+ nsIMdbRow* findRow = nullptr;
+ mdb_pos rowPos = 0;
+
+ err = m_mdbPabTable->GetTableRowCursor(m_mdbEnv, -1, &rowCursor);
+ if (NS_SUCCEEDED(err) && rowCursor) {
+ do {
+ err = rowCursor->NextRow(m_mdbEnv, &findRow, &rowPos);
+ } while (NS_SUCCEEDED(err) && findRow);
+ rowCursor->Release();
+ }
+ }
+ }
+ return err;
+}
+
+// initialize the various tokens and tables in our db's env
+nsresult nsAddrDatabase::InitMDBInfo() {
+ nsresult err = NS_OK;
+
+ if (!m_mdbTokensInitialized && m_mdbStore && m_mdbEnv) {
+ m_mdbTokensInitialized = true;
+ err = m_mdbStore->StringToToken(m_mdbEnv, kCardRowScope,
+ &m_CardRowScopeToken);
+ err = m_mdbStore->StringToToken(m_mdbEnv, kListRowScope,
+ &m_ListRowScopeToken);
+ err = m_mdbStore->StringToToken(m_mdbEnv, kDataRowScope,
+ &m_DataRowScopeToken);
+ gAddressBookTableOID.mOid_Scope = m_CardRowScopeToken;
+ gAddressBookTableOID.mOid_Id = ID_PAB_TABLE;
+ if (NS_SUCCEEDED(err)) {
+ m_mdbStore->StringToToken(m_mdbEnv, kUIDProperty, &m_UIDColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kFirstNameProperty,
+ &m_FirstNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLastNameProperty,
+ &m_LastNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPhoneticFirstNameProperty,
+ &m_PhoneticFirstNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPhoneticLastNameProperty,
+ &m_PhoneticLastNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kDisplayNameProperty,
+ &m_DisplayNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kNicknameProperty,
+ &m_NickNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPriEmailProperty,
+ &m_PriEmailColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLowerPriEmailColumn,
+ &m_LowerPriEmailColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, k2ndEmailProperty,
+ &m_2ndEmailColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLower2ndEmailColumn,
+ &m_Lower2ndEmailColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPopularityIndexProperty,
+ &m_PopularityIndexColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkPhoneProperty,
+ &m_WorkPhoneColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomePhoneProperty,
+ &m_HomePhoneColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kFaxProperty, &m_FaxColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPagerProperty, &m_PagerColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCellularProperty,
+ &m_CellularColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkPhoneTypeProperty,
+ &m_WorkPhoneTypeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomePhoneTypeProperty,
+ &m_HomePhoneTypeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kFaxTypeProperty,
+ &m_FaxTypeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kPagerTypeProperty,
+ &m_PagerTypeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCellularTypeProperty,
+ &m_CellularTypeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeAddressProperty,
+ &m_HomeAddressColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeAddress2Property,
+ &m_HomeAddress2ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeCityProperty,
+ &m_HomeCityColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeStateProperty,
+ &m_HomeStateColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeZipCodeProperty,
+ &m_HomeZipCodeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeCountryProperty,
+ &m_HomeCountryColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkAddressProperty,
+ &m_WorkAddressColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkAddress2Property,
+ &m_WorkAddress2ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkCityProperty,
+ &m_WorkCityColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkStateProperty,
+ &m_WorkStateColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkZipCodeProperty,
+ &m_WorkZipCodeColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkCountryProperty,
+ &m_WorkCountryColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kJobTitleProperty,
+ &m_JobTitleColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kDepartmentProperty,
+ &m_DepartmentColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCompanyProperty,
+ &m_CompanyColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kScreenNameProperty,
+ &m_AimScreenNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kAnniversaryYearProperty,
+ &m_AnniversaryYearColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kAnniversaryMonthProperty,
+ &m_AnniversaryMonthColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kAnniversaryDayProperty,
+ &m_AnniversaryDayColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kSpouseNameProperty,
+ &m_SpouseNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kFamilyNameProperty,
+ &m_FamilyNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kWorkWebPageProperty,
+ &m_WebPage1ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kHomeWebPageProperty,
+ &m_WebPage2ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kBirthYearProperty,
+ &m_BirthYearColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kBirthMonthProperty,
+ &m_BirthMonthColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kBirthDayProperty,
+ &m_BirthDayColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCustom1Property,
+ &m_Custom1ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCustom2Property,
+ &m_Custom2ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCustom3Property,
+ &m_Custom3ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kCustom4Property,
+ &m_Custom4ColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kNotesProperty, &m_NotesColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLastModifiedDateProperty,
+ &m_LastModDateColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kRecordKeyColumn,
+ &m_RecordKeyColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kAddressCharSetColumn,
+ &m_AddressCharSetColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLastRecordKeyColumn,
+ &m_LastRecordKeyColumnToken);
+
+ err = m_mdbStore->StringToToken(m_mdbEnv, kPabTableKind, &m_PabTableKind);
+
+ m_mdbStore->StringToToken(m_mdbEnv, kMailListName,
+ &m_ListNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kMailListNickName,
+ &m_ListNickNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kMailListDescription,
+ &m_ListDescriptionColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kMailListTotalAddresses,
+ &m_ListTotalColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kLowerListNameColumn,
+ &m_LowerListNameColumnToken);
+ m_mdbStore->StringToToken(m_mdbEnv, kDeletedCardsTableKind,
+ &m_DeletedCardsTableKind);
+ }
+ }
+ return err;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+uint32_t nsAddrDatabase::GetListAddressTotal(nsIMdbRow* listRow) {
+ uint32_t count = 0;
+ GetIntColumn(listRow, m_ListTotalColumnToken, &count, 0);
+ return count;
+}
+
+nsresult nsAddrDatabase::GetAddressRowByPos(nsIMdbRow* listRow, uint16_t pos,
+ nsIMdbRow** cardRow) {
+ if (!m_mdbStore || !listRow || !cardRow || !m_mdbEnv)
+ return NS_ERROR_NULL_POINTER;
+
+ mdb_token listAddressColumnToken;
+
+ char columnStr[COLUMN_STR_MAX];
+ PR_snprintf(columnStr, COLUMN_STR_MAX, kMailListAddressFormat, pos);
+ m_mdbStore->StringToToken(m_mdbEnv, columnStr, &listAddressColumnToken);
+
+ nsAutoString tempString;
+ mdb_id rowID;
+ nsresult err =
+ GetIntColumn(listRow, listAddressColumnToken, (uint32_t*)&rowID, 0);
+ NS_ENSURE_SUCCESS(err, err);
+
+ return GetCardRowByRowID(rowID, cardRow);
+}
+
+nsresult nsAddrDatabase::GetStringColumn(nsIMdbRow* cardRow, mdb_token outToken,
+ nsString& str) {
+ nsresult err = NS_ERROR_NULL_POINTER;
+ nsIMdbCell* cardCell;
+
+ if (cardRow && m_mdbEnv) {
+ err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell);
+ if (NS_SUCCEEDED(err) && cardCell) {
+ struct mdbYarn yarn;
+ cardCell->AliasYarn(m_mdbEnv, &yarn);
+ NS_ConvertUTF8toUTF16 uniStr((const char*)yarn.mYarn_Buf,
+ yarn.mYarn_Fill);
+ if (!uniStr.IsEmpty())
+ str.Assign(uniStr);
+ else
+ err = NS_ERROR_FAILURE;
+ cardCell->Release(); // always release ref
+ } else
+ err = NS_ERROR_FAILURE;
+ }
+ return err;
+}
+
+void nsAddrDatabase::YarnToUInt32(struct mdbYarn* yarn, uint32_t* pResult) {
+ uint8_t numChars = std::min<mdb_fill>(8, yarn->mYarn_Fill);
+ *pResult = MsgUnhex((char*)yarn->mYarn_Buf, numChars);
+}
+
+nsresult nsAddrDatabase::GetIntColumn(nsIMdbRow* cardRow, mdb_token outToken,
+ uint32_t* pValue, uint32_t defaultValue) {
+ nsresult err = NS_ERROR_NULL_POINTER;
+ nsIMdbCell* cardCell;
+
+ if (pValue) *pValue = defaultValue;
+ if (cardRow && m_mdbEnv) {
+ err = cardRow->GetCell(m_mdbEnv, outToken, &cardCell);
+ if (NS_SUCCEEDED(err) && cardCell) {
+ struct mdbYarn yarn;
+ cardCell->AliasYarn(m_mdbEnv, &yarn);
+ YarnToUInt32(&yarn, pValue);
+ cardCell->Release();
+ } else
+ err = NS_ERROR_FAILURE;
+ }
+ return err;
+}
+
+nsresult nsAddrDatabase::InitCardFromRow(nsIAbCard* newCard,
+ nsIMdbRow* cardRow) {
+ nsresult rv = NS_OK;
+ if (!newCard || !cardRow || !m_mdbEnv) return NS_ERROR_NULL_POINTER;
+
+ nsCOMPtr<nsIMdbRowCellCursor> cursor;
+ nsCOMPtr<nsIMdbCell> cell;
+
+ rv = cardRow->GetRowCellCursor(m_mdbEnv, -1, getter_AddRefs(cursor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mdb_column columnNumber;
+ char columnName[100];
+ struct mdbYarn colYarn = {columnName, 0, sizeof(columnName), 0, 0, nullptr};
+ struct mdbYarn cellYarn;
+
+ do {
+ rv = cursor->NextCell(m_mdbEnv, getter_AddRefs(cell), &columnNumber,
+ nullptr);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!cell) break;
+
+ // Get the value of the cell
+ cell->AliasYarn(m_mdbEnv, &cellYarn);
+ NS_ConvertUTF8toUTF16 value(static_cast<const char*>(cellYarn.mYarn_Buf),
+ cellYarn.mYarn_Fill);
+
+ if (!value.IsEmpty()) {
+ // Get the column of the cell
+ // Mork makes this so hard...
+ rv = m_mdbStore->TokenToString(m_mdbEnv, columnNumber, &colYarn);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char* name =
+ PL_strndup(static_cast<char*>(colYarn.mYarn_Buf), colYarn.mYarn_Fill);
+ newCard->SetPropertyAsAString(name, value);
+ PL_strfree(name);
+ }
+ } while (true);
+
+ uint32_t key = 0;
+ rv = GetIntColumn(cardRow, m_RecordKeyColumnToken, &key, 0);
+ if (NS_SUCCEEDED(rv)) newCard->SetPropertyAsUint32(kRecordKeyColumn, key);
+
+ return NS_OK;
+}
+
+nsresult nsAddrDatabase::GetListCardFromDB(nsIAbCard* listCard,
+ nsIMdbRow* listRow) {
+ nsresult err = NS_OK;
+ if (!listCard || !listRow) return NS_ERROR_NULL_POINTER;
+
+ nsAutoString tempString;
+
+ err = GetStringColumn(listRow, m_UIDColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty()) {
+ listCard->SetPropertyAsAString(kUIDProperty, tempString);
+ }
+ err = GetStringColumn(listRow, m_ListNameColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty()) {
+ listCard->SetDisplayName(tempString);
+ listCard->SetLastName(tempString);
+ }
+ err = GetStringColumn(listRow, m_ListNickNameColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty()) {
+ listCard->SetPropertyAsAString(kNicknameProperty, tempString);
+ }
+ err = GetStringColumn(listRow, m_ListDescriptionColumnToken, tempString);
+ if (NS_SUCCEEDED(err) && !tempString.IsEmpty()) {
+ listCard->SetPropertyAsAString(kNotesProperty, tempString);
+ }
+ uint32_t key = 0;
+ err = GetIntColumn(listRow, m_RecordKeyColumnToken, &key, 0);
+ if (NS_SUCCEEDED(err)) listCard->SetPropertyAsUint32(kRecordKeyColumn, key);
+ return err;
+}
+
+class nsAddrDBEnumerator : public nsSimpleEnumerator {
+ public:
+ const nsID& DefaultInterface() override { return NS_GET_IID(nsIAbCard); }
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ // nsAddrDBEnumerator methods:
+ explicit nsAddrDBEnumerator(nsAddrDatabase* aDb);
+ void Clear();
+
+ protected:
+ nsAddrDatabase* mDb;
+ nsIMdbTable* mDbTable;
+ nsCOMPtr<nsIMdbTableRowCursor> mRowCursor;
+ nsCOMPtr<nsIMdbRow> mCurrentRow;
+ mdb_pos mRowPos;
+};
+
+nsAddrDBEnumerator::nsAddrDBEnumerator(nsAddrDatabase* aDb)
+ : mDb(aDb), mDbTable(aDb->GetPabTable()), mRowPos(-1) {}
+
+NS_IMETHODIMP
+nsAddrDBEnumerator::HasMoreElements(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+ *aResult = false;
+
+ if (!mDbTable || !mDb->GetEnv()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsCOMPtr<nsIMdbTableRowCursor> rowCursor;
+ mDbTable->GetTableRowCursor(mDb->GetEnv(), mRowPos,
+ getter_AddRefs(rowCursor));
+ NS_ENSURE_TRUE(rowCursor, NS_ERROR_FAILURE);
+
+ mdbOid rowOid;
+ rowCursor->NextRowOid(mDb->GetEnv(), &rowOid, nullptr);
+ while (rowOid.mOid_Id != (mdb_id)-1) {
+ if (mDb->IsListRowScopeToken(rowOid.mOid_Scope) ||
+ mDb->IsCardRowScopeToken(rowOid.mOid_Scope)) {
+ *aResult = true;
+
+ return NS_OK;
+ }
+
+ if (!mDb->IsDataRowScopeToken(rowOid.mOid_Scope)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ rowCursor->NextRowOid(mDb->GetEnv(), &rowOid, nullptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAddrDBEnumerator::GetNext(nsISupports** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+
+ if (!mDbTable || !mDb->GetEnv()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (!mRowCursor) {
+ mDbTable->GetTableRowCursor(mDb->GetEnv(), -1, getter_AddRefs(mRowCursor));
+ NS_ENSURE_TRUE(mRowCursor, NS_ERROR_FAILURE);
+ }
+
+ nsCOMPtr<nsIAbCard> resultCard;
+ mRowCursor->NextRow(mDb->GetEnv(), getter_AddRefs(mCurrentRow), &mRowPos);
+ while (mCurrentRow) {
+ mdbOid rowOid;
+ if (NS_SUCCEEDED(mCurrentRow->GetOid(mDb->GetEnv(), &rowOid))) {
+ nsresult rv;
+ if (mDb->IsListRowScopeToken(rowOid.mOid_Scope)) {
+ rv = mDb->CreateABListCard(mCurrentRow, getter_AddRefs(resultCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (mDb->IsCardRowScopeToken(rowOid.mOid_Scope)) {
+ rv = mDb->CreateABCard(mCurrentRow, 0, getter_AddRefs(resultCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else if (!mDb->IsDataRowScopeToken(rowOid.mOid_Scope)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (resultCard) {
+ return CallQueryInterface(resultCard, aResult);
+ }
+ }
+
+ mRowCursor->NextRow(mDb->GetEnv(), getter_AddRefs(mCurrentRow), &mRowPos);
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+class nsListAddressEnumerator final : public nsSimpleEnumerator {
+ public:
+ const nsID& DefaultInterface() override { return NS_GET_IID(nsIAbCard); }
+
+ // nsISimpleEnumerator methods:
+ NS_DECL_NSISIMPLEENUMERATOR
+
+ // nsListAddressEnumerator methods:
+ nsListAddressEnumerator(nsAddrDatabase* aDb, mdb_id aRowID);
+
+ protected:
+ ~nsListAddressEnumerator() override = default;
+ nsAddrDatabase* mDb;
+ nsIMdbTable* mDbTable;
+ nsCOMPtr<nsIMdbRow> mListRow;
+ mdb_id mListRowID;
+ uint32_t mAddressTotal;
+ uint16_t mAddressPos;
+};
+
+nsListAddressEnumerator::nsListAddressEnumerator(nsAddrDatabase* aDb,
+ mdb_id aRowID)
+ : mDb(aDb),
+ mDbTable(aDb->GetPabTable()),
+ mListRowID(aRowID),
+ mAddressPos(0) {
+ mDb->GetListRowByRowID(mListRowID, getter_AddRefs(mListRow));
+ mAddressTotal = aDb->GetListAddressTotal(mListRow);
+}
+
+NS_IMETHODIMP
+nsListAddressEnumerator::HasMoreElements(bool* aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = false;
+
+ if (!mDbTable || !mDb->GetEnv()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // In some cases it is possible that GetAddressRowByPos returns success,
+ // but currentRow is null. This is typically due to the fact that a card
+ // has been deleted from the parent and not the list. Whilst we have fixed
+ // that there are still a few dbs around there that we need to support
+ // correctly. Therefore, whilst processing lists ensure that we don't return
+ // false if the only thing stopping us is a blank row, just skip it and try
+ // the next one.
+ while (mAddressPos < mAddressTotal) {
+ nsCOMPtr<nsIMdbRow> currentRow;
+ nsresult rv = mDb->GetAddressRowByPos(mListRow, mAddressPos + 1,
+ getter_AddRefs(currentRow));
+
+ if (NS_SUCCEEDED(rv) && currentRow) {
+ *aResult = true;
+ break;
+ }
+
+ ++mAddressPos;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsListAddressEnumerator::GetNext(nsISupports** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ *aResult = nullptr;
+
+ if (!mDbTable || !mDb->GetEnv()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ while (++mAddressPos <= mAddressTotal) {
+ nsCOMPtr<nsIMdbRow> currentRow;
+ nsresult rv = mDb->GetAddressRowByPos(mListRow, mAddressPos,
+ getter_AddRefs(currentRow));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIAbCard> resultCard;
+ rv =
+ mDb->CreateABCard(currentRow, mListRowID, getter_AddRefs(resultCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(resultCard, aResult);
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+nsresult nsAddrDatabase::EnumerateCards(nsISimpleEnumerator** result) {
+ NS_ADDREF(*result = new nsAddrDBEnumerator(this));
+ return NS_OK;
+}
+
+nsresult nsAddrDatabase::EnumerateListAddresses(uint32_t listRowID,
+ nsISimpleEnumerator** result) {
+ NS_ADDREF(*result = new nsListAddressEnumerator(this, listRowID));
+ return NS_OK;
+}
+
+nsresult nsAddrDatabase::CreateCard(nsIMdbRow* cardRow, mdb_id listRowID,
+ nsIAbCard** result) {
+ if (!cardRow || !m_mdbEnv || !result) return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+
+ mdbOid outOid;
+ mdb_id rowID = 0;
+
+ if (NS_SUCCEEDED(cardRow->GetOid(m_mdbEnv, &outOid))) rowID = outOid.mOid_Id;
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIAbCard> personCard;
+ personCard =
+ do_CreateInstance("@mozilla.org/addressbook/cardproperty;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ InitCardFromRow(personCard, cardRow);
+ personCard->SetPropertyAsUint32(kRowIDProperty, rowID);
+
+ personCard.forget(result);
+ }
+
+ return rv;
+}
+
+nsresult nsAddrDatabase::CreateABCard(nsIMdbRow* cardRow, mdb_id listRowID,
+ nsIAbCard** result) {
+ return CreateCard(cardRow, listRowID, result);
+}
+
+/* create a card for mailing list in the address book */
+nsresult nsAddrDatabase::CreateABListCard(nsIMdbRow* listRow,
+ nsIAbCard** result) {
+ if (!listRow || !m_mdbEnv || !result) return NS_ERROR_NULL_POINTER;
+
+ nsresult rv = NS_OK;
+
+ mdbOid outOid;
+ mdb_id rowID = 0;
+
+ if (NS_SUCCEEDED(listRow->GetOid(m_mdbEnv, &outOid))) rowID = outOid.mOid_Id;
+
+ char* listURI = nullptr;
+
+ nsAutoString fileName;
+ rv = m_dbName->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ listURI = PR_smprintf("MailList%ld", rowID);
+
+ nsCOMPtr<nsIAbCard> personCard;
+ personCard =
+ do_CreateInstance("@mozilla.org/addressbook/cardproperty;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (personCard) {
+ GetListCardFromDB(personCard, listRow);
+
+ personCard->SetPropertyAsUint32(kRowIDProperty, rowID);
+ personCard->SetIsMailList(true);
+ personCard->SetMailListURI(listURI);
+ }
+
+ personCard.forget(result);
+ if (listURI) PR_smprintf_free(listURI);
+
+ return rv;
+}
+
+nsresult nsAddrDatabase::GetCardRowByRowID(mdb_id rowID, nsIMdbRow** dbRow) {
+ if (!m_mdbStore || !m_mdbEnv) return NS_ERROR_NULL_POINTER;
+
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_CardRowScopeToken;
+ rowOid.mOid_Id = rowID;
+
+ return m_mdbStore->GetRow(m_mdbEnv, &rowOid, dbRow);
+}
+
+nsresult nsAddrDatabase::GetListRowByRowID(mdb_id rowID, nsIMdbRow** dbRow) {
+ if (!m_mdbStore || !m_mdbEnv) return NS_ERROR_NULL_POINTER;
+
+ mdbOid rowOid;
+ rowOid.mOid_Scope = m_ListRowScopeToken;
+ rowOid.mOid_Id = rowID;
+
+ return m_mdbStore->GetRow(m_mdbEnv, &rowOid, dbRow);
+}
diff --git a/comm/mailnews/import/src/nsAddrDatabase.h b/comm/mailnews/import/src/nsAddrDatabase.h
new file mode 100644
index 0000000000..d667c92e47
--- /dev/null
+++ b/comm/mailnews/import/src/nsAddrDatabase.h
@@ -0,0 +1,158 @@
+/* -*- 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 _nsAddrDatabase_H_
+#define _nsAddrDatabase_H_
+
+#include "nsIAbCard.h"
+#include "nsIFile.h"
+#include "mdb.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+class nsAddrDatabase {
+ using PathString = mozilla::PathString;
+
+ public:
+ nsresult SetDbPath(nsIFile* aDbPath);
+ nsresult Close(bool forceCommit);
+ nsresult OpenMDB(nsIFile* dbName, bool create);
+ nsresult CloseMDB(bool commit);
+ nsresult ForceClosed(void);
+ nsresult EnumerateCards(nsISimpleEnumerator** _retval);
+ nsresult EnumerateListAddresses(uint32_t listRowID,
+ nsISimpleEnumerator** _retval);
+
+ nsAddrDatabase();
+ virtual ~nsAddrDatabase();
+
+ nsresult GetMDBFactory(nsIMdbFactory** aMdbFactory);
+ nsIMdbEnv* GetEnv() { return m_mdbEnv; }
+ uint32_t GetCurVersion();
+ nsIMdbTableRowCursor* GetTableRowCursor();
+ nsIMdbTable* GetPabTable() { return m_mdbPabTable; }
+
+ nsresult CreateABCard(nsIMdbRow* cardRow, mdb_id listRowID,
+ nsIAbCard** result);
+ nsresult CreateABListCard(nsIMdbRow* listRow, nsIAbCard** result);
+
+ bool IsListRowScopeToken(mdb_scope scope) {
+ return (scope == m_ListRowScopeToken) ? true : false;
+ }
+ bool IsCardRowScopeToken(mdb_scope scope) {
+ return (scope == m_CardRowScopeToken) ? true : false;
+ }
+ bool IsDataRowScopeToken(mdb_scope scope) {
+ return (scope == m_DataRowScopeToken) ? true : false;
+ }
+ nsresult GetCardRowByRowID(mdb_id rowID, nsIMdbRow** dbRow);
+ nsresult GetListRowByRowID(mdb_id rowID, nsIMdbRow** dbRow);
+
+ uint32_t GetListAddressTotal(nsIMdbRow* listRow);
+ nsresult GetAddressRowByPos(nsIMdbRow* listRow, uint16_t pos,
+ nsIMdbRow** cardRow);
+
+ nsresult InitCardFromRow(nsIAbCard* aNewCard, nsIMdbRow* aCardRow);
+
+ protected:
+ void YarnToUInt32(struct mdbYarn* yarn, uint32_t* pResult);
+ nsresult GetStringColumn(nsIMdbRow* cardRow, mdb_token outToken,
+ nsString& str);
+ nsresult GetIntColumn(nsIMdbRow* cardRow, mdb_token outToken,
+ uint32_t* pValue, uint32_t defaultValue);
+ nsresult GetListCardFromDB(nsIAbCard* listCard, nsIMdbRow* listRow);
+ nsresult CreateCard(nsIMdbRow* cardRow, mdb_id listRowID, nsIAbCard** result);
+
+ // mdb bookkeeping stuff
+ nsresult InitExistingDB();
+ nsresult InitMDBInfo();
+
+ nsIMdbEnv* m_mdbEnv; // to be used in all the db calls.
+ nsIMdbStore* m_mdbStore;
+ nsIMdbTable* m_mdbPabTable;
+ nsCOMPtr<nsIFile> m_dbName;
+ bool m_mdbTokensInitialized;
+
+ mdb_kind m_PabTableKind;
+ mdb_kind m_DeletedCardsTableKind;
+
+ mdb_scope m_CardRowScopeToken;
+ mdb_scope m_ListRowScopeToken;
+ mdb_scope m_DataRowScopeToken;
+
+ mdb_token m_UIDColumnToken;
+ mdb_token m_FirstNameColumnToken;
+ mdb_token m_LastNameColumnToken;
+ mdb_token m_PhoneticFirstNameColumnToken;
+ mdb_token m_PhoneticLastNameColumnToken;
+ mdb_token m_DisplayNameColumnToken;
+ mdb_token m_NickNameColumnToken;
+ mdb_token m_PriEmailColumnToken;
+ mdb_token m_2ndEmailColumnToken;
+ mdb_token m_DefaultEmailColumnToken;
+ mdb_token m_CardTypeColumnToken;
+ mdb_token m_WorkPhoneColumnToken;
+ mdb_token m_HomePhoneColumnToken;
+ mdb_token m_FaxColumnToken;
+ mdb_token m_PagerColumnToken;
+ mdb_token m_CellularColumnToken;
+ mdb_token m_WorkPhoneTypeColumnToken;
+ mdb_token m_HomePhoneTypeColumnToken;
+ mdb_token m_FaxTypeColumnToken;
+ mdb_token m_PagerTypeColumnToken;
+ mdb_token m_CellularTypeColumnToken;
+ mdb_token m_HomeAddressColumnToken;
+ mdb_token m_HomeAddress2ColumnToken;
+ mdb_token m_HomeCityColumnToken;
+ mdb_token m_HomeStateColumnToken;
+ mdb_token m_HomeZipCodeColumnToken;
+ mdb_token m_HomeCountryColumnToken;
+ mdb_token m_WorkAddressColumnToken;
+ mdb_token m_WorkAddress2ColumnToken;
+ mdb_token m_WorkCityColumnToken;
+ mdb_token m_WorkStateColumnToken;
+ mdb_token m_WorkZipCodeColumnToken;
+ mdb_token m_WorkCountryColumnToken;
+ mdb_token m_JobTitleColumnToken;
+ mdb_token m_DepartmentColumnToken;
+ mdb_token m_CompanyColumnToken;
+ mdb_token m_AimScreenNameColumnToken;
+ mdb_token m_AnniversaryYearColumnToken;
+ mdb_token m_AnniversaryMonthColumnToken;
+ mdb_token m_AnniversaryDayColumnToken;
+ mdb_token m_SpouseNameColumnToken;
+ mdb_token m_FamilyNameColumnToken;
+ mdb_token m_DefaultAddressColumnToken;
+ mdb_token m_CategoryColumnToken;
+ mdb_token m_WebPage1ColumnToken;
+ mdb_token m_WebPage2ColumnToken;
+ mdb_token m_BirthYearColumnToken;
+ mdb_token m_BirthMonthColumnToken;
+ mdb_token m_BirthDayColumnToken;
+ mdb_token m_Custom1ColumnToken;
+ mdb_token m_Custom2ColumnToken;
+ mdb_token m_Custom3ColumnToken;
+ mdb_token m_Custom4ColumnToken;
+ mdb_token m_NotesColumnToken;
+ mdb_token m_LastModDateColumnToken;
+ mdb_token m_RecordKeyColumnToken;
+ mdb_token m_LowerPriEmailColumnToken;
+ mdb_token m_Lower2ndEmailColumnToken;
+
+ mdb_token m_PopularityIndexColumnToken;
+
+ mdb_token m_AddressCharSetColumnToken;
+ mdb_token m_LastRecordKeyColumnToken;
+
+ mdb_token m_ListNameColumnToken;
+ mdb_token m_ListNickNameColumnToken;
+ mdb_token m_ListDescriptionColumnToken;
+ mdb_token m_ListTotalColumnToken;
+ mdb_token m_LowerListNameColumnToken;
+
+ nsCOMPtr<nsIMdbFactory> mMdbFactory;
+};
+
+#endif
diff --git a/comm/mailnews/import/src/nsAppleMailImport.cpp b/comm/mailnews/import/src/nsAppleMailImport.cpp
new file mode 100644
index 0000000000..0404014527
--- /dev/null
+++ b/comm/mailnews/import/src/nsAppleMailImport.cpp
@@ -0,0 +1,609 @@
+/* -*- 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 "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIImportService.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsIImportGeneric.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsIFile.h"
+#include "nsIStringBundle.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsNetUtil.h"
+#include "nsMsgUtils.h"
+#include "mozilla/Components.h"
+
+#include "nsEmlxHelperUtils.h"
+#include "nsAppleMailImport.h"
+#include "nsIOutputStream.h"
+
+// some hard-coded strings
+#define DEFAULT_MAIL_FOLDER "~/Library/Mail/"
+#define POP_MBOX_SUFFIX ".mbox"
+#define IMAP_MBOX_SUFFIX ".imapmbox"
+
+// stringbundle URI
+#define APPLEMAIL_MSGS_URL \
+ "chrome://messenger/locale/appleMailImportMsgs.properties"
+
+// magic constants
+#define kAccountMailboxID 1234
+
+nsAppleMailImportModule::nsAppleMailImportModule() {
+ IMPORT_LOG0("nsAppleMailImportModule Created");
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ if (bundleService)
+ bundleService->CreateBundle(APPLEMAIL_MSGS_URL, getter_AddRefs(mBundle));
+}
+
+nsAppleMailImportModule::~nsAppleMailImportModule() {
+ IMPORT_LOG0("nsAppleMailImportModule Deleted");
+}
+
+NS_IMPL_ISUPPORTS(nsAppleMailImportModule, nsIImportModule)
+
+NS_IMETHODIMP nsAppleMailImportModule::GetName(char16_t** aName) {
+ if (!mBundle) {
+ return NS_ERROR_FAILURE;
+ }
+ nsAutoString name;
+ nsresult rv = mBundle->GetStringFromName("ApplemailImportName", name);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aName = ToNewUnicode(name);
+ return rv;
+}
+
+NS_IMETHODIMP nsAppleMailImportModule::GetDescription(char16_t** aName) {
+ if (!mBundle) {
+ return NS_ERROR_FAILURE;
+ }
+ nsAutoString name;
+ nsresult rv = mBundle->GetStringFromName("ApplemailImportDescription", name);
+ NS_ENSURE_SUCCESS(rv, rv);
+ *aName = ToNewUnicode(name);
+ return rv;
+}
+
+NS_IMETHODIMP nsAppleMailImportModule::GetSupports(char** aSupports) {
+ NS_ENSURE_ARG_POINTER(aSupports);
+ *aSupports = strdup(NS_IMPORT_MAIL_STR);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAppleMailImportModule::GetSupportsUpgrade(bool* aUpgrade) {
+ NS_ENSURE_ARG_POINTER(aUpgrade);
+ *aUpgrade = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAppleMailImportModule::GetImportInterface(
+ const char* aImportType, nsISupports** aInterface) {
+ NS_ENSURE_ARG_POINTER(aImportType);
+ NS_ENSURE_ARG_POINTER(aInterface);
+ *aInterface = nullptr;
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+
+ if (!strcmp(aImportType, "mail")) {
+ nsCOMPtr<nsIImportMail> mail(
+ do_CreateInstance(NS_APPLEMAILIMPL_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImportService> impSvc(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImportGeneric> generic;
+ rv = impSvc->CreateNewGenericMail(getter_AddRefs(generic));
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoString name;
+ rv = mBundle->GetStringFromName("ApplemailImportName", name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISupportsString> nameString(
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nameString->SetData(name);
+
+ generic->SetData("name", nameString);
+ generic->SetData("mailInterface", mail);
+
+ generic.forget(aInterface);
+ }
+ }
+ }
+ }
+
+ return rv;
+}
+
+#pragma mark -
+
+nsAppleMailImportMail::nsAppleMailImportMail() : mProgress(0), mCurDepth(0) {
+ IMPORT_LOG0("nsAppleMailImportMail created");
+}
+
+nsresult nsAppleMailImportMail::Initialize() {
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleService, NS_ERROR_UNEXPECTED);
+
+ return bundleService->CreateBundle(APPLEMAIL_MSGS_URL,
+ getter_AddRefs(mBundle));
+}
+
+nsAppleMailImportMail::~nsAppleMailImportMail() {
+ IMPORT_LOG0("nsAppleMailImportMail destroyed");
+}
+
+NS_IMPL_ISUPPORTS(nsAppleMailImportMail, nsIImportMail)
+
+NS_IMETHODIMP nsAppleMailImportMail::GetDefaultLocation(nsIFile** aLocation,
+ bool* aFound,
+ bool* aUserVerify) {
+ NS_ENSURE_ARG_POINTER(aFound);
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(aUserVerify);
+
+ *aLocation = nullptr;
+ *aFound = false;
+ *aUserVerify = true;
+
+ // try to find current user's top-level Mail folder
+ nsCOMPtr<nsIFile> mailFolder(do_CreateInstance(NS_LOCAL_FILE_CONTRACTID));
+ if (mailFolder) {
+ nsresult rv =
+ mailFolder->InitWithNativePath(nsLiteralCString(DEFAULT_MAIL_FOLDER));
+ if (NS_SUCCEEDED(rv)) {
+ *aFound = true;
+ *aUserVerify = false;
+ mailFolder.forget(aLocation);
+ }
+ }
+
+ return NS_OK;
+}
+
+// this is the method that initiates all searching for mailboxes.
+// it will assume that it has a directory like ~/Library/Mail/
+NS_IMETHODIMP nsAppleMailImportMail::FindMailboxes(
+ nsIFile* aMailboxFile,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& boxes) {
+ NS_ENSURE_ARG_POINTER(aMailboxFile);
+
+ IMPORT_LOG0("FindMailboxes for Apple mail invoked");
+
+ boxes.Clear();
+ bool exists = false;
+ nsresult rv = aMailboxFile->Exists(&exists);
+ if (NS_FAILED(rv) || !exists) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIImportService> importService(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mCurDepth = 1;
+
+ // 1. look for accounts with mailboxes
+ FindAccountMailDirs(aMailboxFile, boxes, importService);
+ mCurDepth--;
+
+ if (NS_SUCCEEDED(rv)) {
+ // 2. look for "global" mailboxes, that don't belong to any specific
+ // account. they are inside the
+ // root's Mailboxes/ folder
+ nsCOMPtr<nsIFile> mailboxesDir(
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ mailboxesDir->InitWithFile(aMailboxFile);
+ rv = mailboxesDir->Append(u"Mailboxes"_ns);
+ if (NS_SUCCEEDED(rv)) {
+ IMPORT_LOG0("Looking for global Apple mailboxes");
+
+ mCurDepth++;
+ rv = FindMboxDirs(mailboxesDir, boxes, importService);
+ mCurDepth--;
+ }
+ }
+ }
+ return rv;
+}
+
+// operates on the Mail/ directory root, trying to find accounts (which are
+// folders named something like "POP-hwaara@gmail.com") and add their .mbox dirs
+void nsAppleMailImportMail::FindAccountMailDirs(
+ nsIFile* aRoot, nsTArray<RefPtr<nsIImportMailboxDescriptor>>& aMailboxDescs,
+ nsIImportService* aImportService) {
+ nsCOMPtr<nsIDirectoryEnumerator> directoryEnumerator;
+ nsresult rv = aRoot->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
+ if (NS_FAILED(rv)) return;
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) &&
+ hasMore) {
+ // get the next file entry
+ nsCOMPtr<nsIFile> currentEntry;
+ directoryEnumerator->GetNextFile(getter_AddRefs(currentEntry));
+ if (!currentEntry) continue;
+
+ // make sure it's a directory
+ bool isDirectory = false;
+ currentEntry->IsDirectory(&isDirectory);
+
+ if (isDirectory) {
+ // now let's see if it's an account folder. if so, we want to traverse it
+ // for .mbox children
+ nsAutoString folderName;
+ currentEntry->GetLeafName(folderName);
+ bool isAccountFolder = false;
+
+ if (StringBeginsWith(folderName, u"POP-"_ns)) {
+ // cut off "POP-" prefix so we get a nice folder name
+ folderName.Cut(0, 4);
+ isAccountFolder = true;
+ } else if (StringBeginsWith(folderName, u"IMAP-"_ns)) {
+ // cut off "IMAP-" prefix so we get a nice folder name
+ folderName.Cut(0, 5);
+ isAccountFolder = true;
+ }
+
+ if (isAccountFolder) {
+ IMPORT_LOG1("Found account: %s\n",
+ NS_ConvertUTF16toUTF8(folderName).get());
+
+ // create a mailbox for this account, so we get a parent for "Inbox",
+ // "Sent Messages", etc.
+ nsCOMPtr<nsIImportMailboxDescriptor> desc;
+ rv = aImportService->CreateNewMailboxDescriptor(getter_AddRefs(desc));
+ if (NS_FAILED(rv)) continue;
+ desc->SetSize(1);
+ desc->SetDepth(mCurDepth);
+ desc->SetDisplayName(folderName.get());
+ desc->SetIdentifier(kAccountMailboxID);
+
+ nsCOMPtr<nsIFile> mailboxDescFile;
+ rv = desc->GetFile(getter_AddRefs(mailboxDescFile));
+ if (NS_FAILED(rv) || !mailboxDescFile) continue;
+
+ mailboxDescFile->InitWithFile(currentEntry);
+
+ // add this mailbox descriptor to the list
+ aMailboxDescs.AppendElement(desc);
+
+ // now add all the children mailboxes
+ mCurDepth++;
+ FindMboxDirs(currentEntry, aMailboxDescs, aImportService);
+ mCurDepth--;
+ }
+ }
+ }
+}
+
+// adds the specified file as a mailboxdescriptor to the array
+nsresult nsAppleMailImportMail::AddMboxDir(
+ nsIFile* aFolder,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& aMailboxDescs,
+ nsIImportService* aImportService) {
+ nsAutoString folderName;
+ aFolder->GetLeafName(folderName);
+
+ // cut off the suffix, if any, or prefix if this is an account folder.
+ if (StringEndsWith(folderName,
+ NS_LITERAL_STRING_FROM_CSTRING(POP_MBOX_SUFFIX)))
+ folderName.SetLength(folderName.Length() - 5);
+ else if (StringEndsWith(folderName,
+ NS_LITERAL_STRING_FROM_CSTRING(IMAP_MBOX_SUFFIX)))
+ folderName.SetLength(folderName.Length() - 9);
+ else if (StringBeginsWith(folderName, u"POP-"_ns))
+ folderName.Cut(4, folderName.Length());
+ else if (StringBeginsWith(folderName, u"IMAP-"_ns))
+ folderName.Cut(5, folderName.Length());
+
+ nsCOMPtr<nsIImportMailboxDescriptor> desc;
+ nsresult rv =
+ aImportService->CreateNewMailboxDescriptor(getter_AddRefs(desc));
+ if (NS_SUCCEEDED(rv)) {
+ // find out number of messages in this .mbox
+ uint32_t numMessages = 0;
+ {
+ // move to the .mbox's Messages folder
+ nsCOMPtr<nsIFile> messagesFolder;
+ aFolder->Clone(getter_AddRefs(messagesFolder));
+ nsresult rv = messagesFolder->Append(u"Messages"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // count the number of messages in this folder. it sucks that we have to
+ // iterate through the folder but XPCOM doesn't give us any way to just
+ // get the file count, unfortunately. :-(
+ nsCOMPtr<nsIDirectoryEnumerator> dirEnumerator;
+ messagesFolder->GetDirectoryEntries(getter_AddRefs(dirEnumerator));
+ if (dirEnumerator) {
+ bool hasMore = false;
+ while (NS_SUCCEEDED(dirEnumerator->HasMoreElements(&hasMore)) &&
+ hasMore) {
+ nsCOMPtr<nsIFile> file;
+ dirEnumerator->GetNextFile(getter_AddRefs(file));
+ if (file) {
+ bool isFile = false;
+ file->IsFile(&isFile);
+ if (isFile) numMessages++;
+ }
+ }
+ }
+ }
+
+ desc->SetSize(numMessages);
+ desc->SetDisplayName(folderName.get());
+ desc->SetDepth(mCurDepth);
+
+ IMPORT_LOG3("Will import %s with approx %d messages, depth is %d",
+ NS_ConvertUTF16toUTF8(folderName).get(), numMessages,
+ mCurDepth);
+
+ // XXX: this is silly. there's no setter for the mailbox descriptor's file,
+ // so we need to get it, and then modify it.
+ nsCOMPtr<nsIFile> mailboxDescFile;
+ rv = desc->GetFile(getter_AddRefs(mailboxDescFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (mailboxDescFile) mailboxDescFile->InitWithFile(aFolder);
+
+ // add this mailbox descriptor to the list
+ aMailboxDescs.AppendElement(desc);
+ }
+
+ return NS_OK;
+}
+
+// Starts looking for .mbox dirs in the specified dir. The .mbox dirs contain
+// messages and can be considered leafs in a tree of nested mailboxes
+// (subfolders).
+//
+// If a mailbox has sub-mailboxes, they are contained in a sibling folder with
+// the same name without the ".mbox" part. example:
+// MyParentMailbox.mbox/
+// MyParentMailbox/
+// MyChildMailbox.mbox/
+// MyOtherChildMailbox.mbox/
+//
+nsresult nsAppleMailImportMail::FindMboxDirs(
+ nsIFile* aFolder,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& aMailboxDescs,
+ nsIImportService* aImportService) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ NS_ENSURE_ARG_POINTER(aImportService);
+
+ // make sure this is a directory.
+ bool isDir = false;
+ if (NS_FAILED(aFolder->IsDirectory(&isDir)) || !isDir)
+ return NS_ERROR_FAILURE;
+
+ // iterate through the folder contents
+ nsCOMPtr<nsIDirectoryEnumerator> directoryEnumerator;
+ nsresult rv =
+ aFolder->GetDirectoryEntries(getter_AddRefs(directoryEnumerator));
+ if (NS_FAILED(rv) || !directoryEnumerator) return rv;
+
+ bool hasMore = false;
+ while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) &&
+ hasMore) {
+ // get the next file entry
+ nsCOMPtr<nsIFile> currentEntry;
+ directoryEnumerator->GetNextFile(getter_AddRefs(currentEntry));
+ if (!currentEntry) continue;
+
+ // we only care about directories...
+ if (NS_FAILED(currentEntry->IsDirectory(&isDir)) || !isDir) continue;
+
+ // now find out if this is a .mbox dir
+ nsAutoString currentFolderName;
+ if (NS_SUCCEEDED(currentEntry->GetLeafName(currentFolderName)) &&
+ (StringEndsWith(currentFolderName,
+ NS_LITERAL_STRING_FROM_CSTRING(POP_MBOX_SUFFIX)) ||
+ StringEndsWith(currentFolderName,
+ NS_LITERAL_STRING_FROM_CSTRING(IMAP_MBOX_SUFFIX)))) {
+ IMPORT_LOG1("Adding .mbox dir: %s",
+ NS_ConvertUTF16toUTF8(currentFolderName).get());
+
+ // add this .mbox
+ rv = AddMboxDir(currentEntry, aMailboxDescs, aImportService);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1("Couldn't add .mbox for import: %s ... continuing anyway",
+ NS_ConvertUTF16toUTF8(currentFolderName).get());
+ continue;
+ }
+
+ // see if this .mbox dir has any sub-mailboxes
+ nsAutoString siblingMailboxDirPath;
+ currentEntry->GetPath(siblingMailboxDirPath);
+
+ // cut off suffix
+ if (StringEndsWith(siblingMailboxDirPath,
+ NS_LITERAL_STRING_FROM_CSTRING(IMAP_MBOX_SUFFIX)))
+ siblingMailboxDirPath.SetLength(siblingMailboxDirPath.Length() - 9);
+ else if (StringEndsWith(siblingMailboxDirPath,
+ NS_LITERAL_STRING_FROM_CSTRING(POP_MBOX_SUFFIX)))
+ siblingMailboxDirPath.SetLength(siblingMailboxDirPath.Length() - 5);
+
+ IMPORT_LOG1("trying to locate a '%s'",
+ NS_ConvertUTF16toUTF8(siblingMailboxDirPath).get());
+ nsCOMPtr<nsIFile> siblingMailboxDir(
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) continue;
+
+ rv = siblingMailboxDir->InitWithPath(siblingMailboxDirPath);
+ bool reallyExists = false;
+ siblingMailboxDir->Exists(&reallyExists);
+
+ if (NS_SUCCEEDED(rv) && reallyExists) {
+ IMPORT_LOG1("Found what looks like an .mbox container: %s",
+ NS_ConvertUTF16toUTF8(currentFolderName).get());
+
+ // traverse this folder for other .mboxes
+ mCurDepth++;
+ FindMboxDirs(siblingMailboxDir, aMailboxDescs, aImportService);
+ mCurDepth--;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsAppleMailImportMail::ImportMailbox(nsIImportMailboxDescriptor* aMailbox,
+ nsIMsgFolder* aDstFolder,
+ char16_t** aErrorLog,
+ char16_t** aSuccessLog,
+ bool* aFatalError) {
+ nsAutoString errorLog, successLog;
+
+ // reset progress
+ mProgress = 0;
+
+ nsAutoString mailboxName;
+ aMailbox->GetDisplayName(getter_Copies(mailboxName));
+
+ nsCOMPtr<nsIFile> mboxFolder;
+ nsresult rv = aMailbox->GetFile(getter_AddRefs(mboxFolder));
+ if (NS_FAILED(rv) || !mboxFolder) {
+ ReportStatus(u"ApplemailImportMailboxConverterror", mailboxName, errorLog);
+ SetLogs(successLog, errorLog, aSuccessLog, aErrorLog);
+ return NS_ERROR_FAILURE;
+ }
+
+ // if we're an account mailbox, nothing do. if we're a real mbox
+ // then we've got some messages to import!
+ uint32_t mailboxIdentifier;
+ aMailbox->GetIdentifier(&mailboxIdentifier);
+
+ if (mailboxIdentifier != kAccountMailboxID) {
+ // move to the .mbox's Messages folder
+ nsCOMPtr<nsIFile> messagesFolder;
+ mboxFolder->Clone(getter_AddRefs(messagesFolder));
+ rv = messagesFolder->Append(u"Messages"_ns);
+ if (NS_FAILED(rv)) {
+ // even if there are no messages, it might still be a valid mailbox, or
+ // even a parent for other mailboxes.
+ //
+ // just indicate that we're done, using the same number that we used to
+ // estimate number of messages earlier.
+ uint32_t finalSize;
+ aMailbox->GetSize(&finalSize);
+ mProgress = finalSize;
+
+ // report that we successfully imported this mailbox
+ ReportStatus(u"ApplemailImportMailboxSuccess", mailboxName, successLog);
+ SetLogs(successLog, errorLog, aSuccessLog, aErrorLog);
+ return NS_OK;
+ }
+
+ // let's import the messages!
+ nsCOMPtr<nsIDirectoryEnumerator> directoryEnumerator;
+ rv = messagesFolder->GetDirectoryEntries(
+ getter_AddRefs(directoryEnumerator));
+ if (NS_FAILED(rv)) {
+ ReportStatus(u"ApplemailImportMailboxConvertError", mailboxName,
+ errorLog);
+ SetLogs(successLog, errorLog, aSuccessLog, aErrorLog);
+ return NS_ERROR_FAILURE;
+ }
+
+ // prepare an outstream to the destination file
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ rv = aDstFolder->GetMsgStore(getter_AddRefs(msgStore));
+ if (!msgStore || NS_FAILED(rv)) {
+ ReportStatus(u"ApplemailImportMailboxConverterror", mailboxName,
+ errorLog);
+ SetLogs(successLog, errorLog, aSuccessLog, aErrorLog);
+ return NS_ERROR_FAILURE;
+ }
+
+ bool hasMore = false;
+ nsCOMPtr<nsIOutputStream> outStream;
+
+ while (NS_SUCCEEDED(directoryEnumerator->HasMoreElements(&hasMore)) &&
+ hasMore) {
+ // get the next file entry
+ nsCOMPtr<nsIFile> currentEntry;
+ directoryEnumerator->GetNextFile(getter_AddRefs(currentEntry));
+ if (!currentEntry) continue;
+
+ // make sure it's an .emlx file
+ bool isFile = false;
+ currentEntry->IsFile(&isFile);
+ if (!isFile) continue;
+
+ nsAutoString leafName;
+ currentEntry->GetLeafName(leafName);
+ if (!StringEndsWith(leafName, u".emlx"_ns)) continue;
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = msgStore->GetNewMsgOutputStream(aDstFolder, getter_AddRefs(msgHdr),
+ getter_AddRefs(outStream));
+ if (NS_FAILED(rv)) break;
+
+ // Add the data to the mbox stream.
+ if (NS_SUCCEEDED(nsEmlxHelperUtils::AddEmlxMessageToStream(currentEntry,
+ outStream))) {
+ mProgress++;
+ msgStore->FinishNewMessage(outStream, msgHdr);
+ outStream = nullptr;
+ } else {
+ msgStore->DiscardNewMessage(outStream, msgHdr);
+ outStream = nullptr;
+ break;
+ }
+ }
+ }
+ // just indicate that we're done, using the same number that we used to
+ // estimate number of messages earlier.
+ uint32_t finalSize;
+ aMailbox->GetSize(&finalSize);
+ mProgress = finalSize;
+
+ // report that we successfully imported this mailbox
+ ReportStatus(u"ApplemailImportMailboxSuccess", mailboxName, successLog);
+ SetLogs(successLog, errorLog, aSuccessLog, aErrorLog);
+
+ return NS_OK;
+}
+
+void nsAppleMailImportMail::ReportStatus(const char16_t* aErrorName,
+ nsString& aName, nsAString& aStream) {
+ // get (and format, if needed) the error string from the bundle
+ nsAutoString outString;
+ AutoTArray<nsString, 1> fmt = {aName};
+ nsresult rv = mBundle->FormatStringFromName(
+ NS_ConvertUTF16toUTF8(aErrorName).get(), fmt, outString);
+ // write it out the stream
+ if (NS_SUCCEEDED(rv)) {
+ aStream.Append(outString);
+ aStream.Append(char16_t('\n'));
+ }
+}
+
+void nsAppleMailImportMail::SetLogs(const nsAString& aSuccess,
+ const nsAString& aError,
+ char16_t** aOutSuccess,
+ char16_t** aOutError) {
+ if (aOutError && !*aOutError) *aOutError = ToNewUnicode(aError);
+ if (aOutSuccess && !*aOutSuccess) *aOutSuccess = ToNewUnicode(aSuccess);
+}
+
+NS_IMETHODIMP nsAppleMailImportMail::GetImportProgress(uint32_t* aDoneSoFar) {
+ NS_ENSURE_ARG_POINTER(aDoneSoFar);
+ *aDoneSoFar = mProgress;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsAppleMailImportMail::TranslateFolderName(
+ const nsAString& aFolderName, nsAString& aResult) {
+ aResult = aFolderName;
+ return NS_OK;
+}
diff --git a/comm/mailnews/import/src/nsAppleMailImport.h b/comm/mailnews/import/src/nsAppleMailImport.h
new file mode 100644
index 0000000000..dd799b06be
--- /dev/null
+++ b/comm/mailnews/import/src/nsAppleMailImport.h
@@ -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/. */
+
+#ifndef nsAppleMailImport_h___
+#define nsAppleMailImport_h___
+
+#include "mozilla/Logging.h"
+#include "nsIImportModule.h"
+#include "nsCOMPtr.h"
+#include "nsIStringBundle.h"
+#include "nsIImportMail.h"
+#include "ImportDebug.h"
+
+#define NS_APPLEMAILIMPL_CID \
+ { \
+ 0x9117a1ea, 0xe012, 0x43b5, { \
+ 0xa0, 0x20, 0xcb, 0x8a, 0x66, 0xcc, 0x09, 0xe1 \
+ } \
+ }
+
+#define NS_APPLEMAILIMPORT_CID \
+ { \
+ 0x6d3f101c, 0x70ec, 0x4e04, { \
+ 0xb6, 0x8d, 0x99, 0x08, 0xd1, 0xae, 0xdd, 0xf3 \
+ } \
+ }
+
+#define NS_APPLEMAILIMPL_CONTRACTID "@mozilla.org/import/import-appleMailImpl;1"
+
+#define kAppleMailSupportsString "mail"
+
+class nsIImportService;
+
+class nsAppleMailImportModule : public nsIImportModule {
+ public:
+ nsAppleMailImportModule();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMPORTMODULE
+
+ private:
+ virtual ~nsAppleMailImportModule();
+
+ nsCOMPtr<nsIStringBundle> mBundle;
+};
+
+class nsAppleMailImportMail : public nsIImportMail {
+ public:
+ nsAppleMailImportMail();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMPORTMAIL
+
+ nsresult Initialize();
+
+ private:
+ virtual ~nsAppleMailImportMail();
+
+ void FindAccountMailDirs(
+ nsIFile* aRoot,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& aMailboxDescs,
+ nsIImportService* aImportService);
+ nsresult FindMboxDirs(
+ nsIFile* aFolder,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& aMailboxDescs,
+ nsIImportService* aImportService);
+ nsresult AddMboxDir(
+ nsIFile* aFolder,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& aMailboxDescs,
+ nsIImportService* aImportService);
+
+ // aInfoString is the format to a "foo %s" string. It may be NULL if the error
+ // string needs no such format.
+ void ReportStatus(const char16_t* aErrorName, nsString& aName,
+ nsAString& aStream);
+ static void SetLogs(const nsAString& success, const nsAString& error,
+ char16_t** aOutErrorLog, char16_t** aSuccessLog);
+
+ nsCOMPtr<nsIStringBundle> mBundle;
+ uint32_t mProgress;
+ uint16_t mCurDepth;
+};
+
+#endif /* nsAppleMailImport_h___ */
diff --git a/comm/mailnews/import/src/nsBeckyAddressBooks.cpp b/comm/mailnews/import/src/nsBeckyAddressBooks.cpp
new file mode 100644
index 0000000000..651bbbeb94
--- /dev/null
+++ b/comm/mailnews/import/src/nsBeckyAddressBooks.cpp
@@ -0,0 +1,311 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIFile.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsString.h"
+#include "nsIImportService.h"
+#include "nsIImportABDescriptor.h"
+#include "nsMsgUtils.h"
+#include "nsVCardAddress.h"
+
+#include "nsBeckyAddressBooks.h"
+#include "nsBeckyStringBundle.h"
+#include "nsBeckyUtils.h"
+
+NS_IMPL_ISUPPORTS(nsBeckyAddressBooks, nsIImportAddressBooks)
+
+nsresult nsBeckyAddressBooks::Create(nsIImportAddressBooks** aImport) {
+ NS_ENSURE_ARG_POINTER(aImport);
+ NS_ADDREF(*aImport = new nsBeckyAddressBooks());
+ return NS_OK;
+}
+
+nsBeckyAddressBooks::nsBeckyAddressBooks() : mReadBytes(0) {}
+
+nsBeckyAddressBooks::~nsBeckyAddressBooks() {}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::GetSupportsMultiple(bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::GetAutoFind(char16_t** aDescription, bool* _retval) {
+ NS_ENSURE_ARG_POINTER(aDescription);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *aDescription =
+ nsBeckyStringBundle::GetStringByName("BeckyImportDescription");
+ *_retval = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::GetNeedsFieldMap(nsIFile* aLocation, bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = false;
+ return NS_OK;
+}
+
+nsresult nsBeckyAddressBooks::FindAddressBookDirectory(
+ nsIFile** aAddressBookDirectory) {
+ nsCOMPtr<nsIFile> userDirectory;
+ nsresult rv = nsBeckyUtils::FindUserDirectory(getter_AddRefs(userDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = userDirectory->Append(u"AddrBook"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ rv = userDirectory->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) return NS_ERROR_FILE_NOT_FOUND;
+
+ bool isDirectory = false;
+ rv = userDirectory->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isDirectory) return NS_ERROR_FILE_NOT_FOUND;
+
+ userDirectory.forget(aAddressBookDirectory);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::GetDefaultLocation(nsIFile** aLocation, bool* aFound,
+ bool* aUserVerify) {
+ NS_ENSURE_ARG_POINTER(aFound);
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(aUserVerify);
+
+ *aLocation = nullptr;
+ *aFound = false;
+ *aUserVerify = true;
+
+ if (NS_SUCCEEDED(nsBeckyAddressBooks::FindAddressBookDirectory(aLocation))) {
+ *aFound = true;
+ *aUserVerify = false;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsBeckyAddressBooks::CreateAddressBookDescriptor(
+ nsIImportABDescriptor** aDescriptor) {
+ nsresult rv;
+ nsCOMPtr<nsIImportService> importService =
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return importService->CreateNewABDescriptor(aDescriptor);
+}
+
+bool nsBeckyAddressBooks::IsAddressBookFile(nsIFile* aFile) {
+ if (!aFile) return false;
+
+ nsresult rv;
+ bool isFile = false;
+ rv = aFile->IsFile(&isFile);
+ if (NS_FAILED(rv) && !isFile) return false;
+
+ nsAutoString name;
+ rv = aFile->GetLeafName(name);
+ return StringEndsWith(name, u".bab"_ns);
+}
+
+bool nsBeckyAddressBooks::HasAddressBookFile(nsIFile* aDirectory) {
+ if (!aDirectory) return false;
+
+ nsresult rv;
+ bool isDirectory = false;
+ rv = aDirectory->IsDirectory(&isDirectory);
+ if (NS_FAILED(rv) || !isDirectory) return false;
+
+ nsCOMPtr<nsIDirectoryEnumerator> entries;
+ rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ bool more;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsIFile> file;
+ rv = entries->GetNextFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, false);
+ if (IsAddressBookFile(file)) return true;
+ }
+
+ return false;
+}
+
+uint32_t nsBeckyAddressBooks::CountAddressBookSize(nsIFile* aDirectory) {
+ if (!aDirectory) return 0;
+
+ nsresult rv;
+ bool isDirectory = false;
+ rv = aDirectory->IsDirectory(&isDirectory);
+ if (NS_FAILED(rv) || !isDirectory) return 0;
+
+ nsCOMPtr<nsIDirectoryEnumerator> entries;
+ rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ uint32_t total = 0;
+ bool more;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsIFile> file;
+ rv = entries->GetNextFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, 0);
+
+ int64_t size;
+ file->GetFileSize(&size);
+ if (total + size > std::numeric_limits<uint32_t>::max())
+ return std::numeric_limits<uint32_t>::max();
+
+ total += static_cast<uint32_t>(size);
+ }
+
+ return total;
+}
+
+nsresult nsBeckyAddressBooks::AppendAddressBookDescriptor(
+ nsIFile* aEntry, nsTArray<RefPtr<nsIImportABDescriptor>>& books) {
+ if (!HasAddressBookFile(aEntry)) return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsIImportABDescriptor> descriptor;
+ rv = CreateAddressBookDescriptor(getter_AddRefs(descriptor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t size = CountAddressBookSize(aEntry);
+ descriptor->SetSize(size);
+ descriptor->SetAbFile(aEntry);
+
+ nsAutoString name;
+ aEntry->GetLeafName(name);
+ descriptor->SetPreferredName(name);
+
+ books.AppendElement(descriptor);
+ return NS_OK;
+}
+
+// Recursively descend down the dirs, appending to the books array.
+nsresult nsBeckyAddressBooks::CollectAddressBooks(
+ nsIFile* aTarget, nsTArray<RefPtr<nsIImportABDescriptor>>& books) {
+ nsresult rv = AppendAddressBookDescriptor(aTarget, books);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDirectoryEnumerator> entries;
+ rv = aTarget->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsIFile> file;
+ rv = entries->GetNextFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isDirectory = false;
+ rv = file->IsDirectory(&isDirectory);
+ if (NS_SUCCEEDED(rv) && isDirectory) {
+ rv = CollectAddressBooks(file, books);
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::FindAddressBooks(
+ nsIFile* aLocation, nsTArray<RefPtr<nsIImportABDescriptor>>& books) {
+ NS_ENSURE_ARG_POINTER(aLocation);
+
+ books.Clear();
+ bool isDirectory = false;
+ nsresult rv = aLocation->IsDirectory(&isDirectory);
+ if (NS_FAILED(rv) || !isDirectory) return NS_ERROR_FAILURE;
+
+ rv = CollectAddressBooks(aLocation, books);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::InitFieldMap(nsIImportFieldMap* aFieldMap) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::ImportAddressBook(
+ nsIImportABDescriptor* aSource, nsIAbDirectory* aDestination,
+ nsIImportFieldMap* aFieldMap, nsISupports* aSupportService,
+ char16_t** aErrorLog, char16_t** aSuccessLog, bool* aFatalError) {
+ NS_ENSURE_ARG_POINTER(aSource);
+ NS_ENSURE_ARG_POINTER(aDestination);
+ NS_ENSURE_ARG_POINTER(aErrorLog);
+ NS_ENSURE_ARG_POINTER(aSuccessLog);
+ NS_ENSURE_ARG_POINTER(aFatalError);
+
+ mReadBytes = 0;
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = aSource->GetAbFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIDirectoryEnumerator> entries;
+ rv = file->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more;
+ nsAutoString error;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsIFile> file;
+ rv = entries->GetNextFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!IsAddressBookFile(file)) continue;
+
+ bool aborted = false;
+ nsAutoString name;
+ aSource->GetPreferredName(name);
+ nsVCardAddress vcard;
+ rv = vcard.ImportAddresses(&aborted, name.get(), file, aDestination, error,
+ &mReadBytes);
+ if (NS_FAILED(rv)) {
+ break;
+ }
+ }
+
+ if (!error.IsEmpty())
+ *aErrorLog = ToNewUnicode(error);
+ else
+ *aSuccessLog =
+ nsBeckyStringBundle::GetStringByName("BeckyImportAddressSuccess");
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::GetImportProgress(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mReadBytes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::SetSampleLocation(nsIFile* aLocation) { return NS_OK; }
+
+NS_IMETHODIMP
+nsBeckyAddressBooks::GetSampleData(int32_t aRecordNumber, bool* aRecordExists,
+ char16_t** _retval) {
+ return NS_ERROR_FAILURE;
+}
diff --git a/comm/mailnews/import/src/nsBeckyAddressBooks.h b/comm/mailnews/import/src/nsBeckyAddressBooks.h
new file mode 100644
index 0000000000..af19ea1917
--- /dev/null
+++ b/comm/mailnews/import/src/nsBeckyAddressBooks.h
@@ -0,0 +1,36 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 nsBeckyAddressBooks_h___
+#define nsBeckyAddressBooks_h___
+
+#include "nsIImportAddressBooks.h"
+#include "nsIFile.h"
+
+class nsBeckyAddressBooks final : public nsIImportAddressBooks {
+ public:
+ nsBeckyAddressBooks();
+ static nsresult Create(nsIImportAddressBooks** aImport);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIMPORTADDRESSBOOKS
+
+ private:
+ virtual ~nsBeckyAddressBooks();
+
+ uint32_t mReadBytes;
+
+ nsresult CollectAddressBooks(nsIFile* aTarget,
+ nsTArray<RefPtr<nsIImportABDescriptor>>& books);
+ nsresult FindAddressBookDirectory(nsIFile** aAddressBookDirectory);
+ nsresult AppendAddressBookDescriptor(
+ nsIFile* aEntry, nsTArray<RefPtr<nsIImportABDescriptor>>& books);
+ uint32_t CountAddressBookSize(nsIFile* aDirectory);
+ bool HasAddressBookFile(nsIFile* aDirectory);
+ bool IsAddressBookFile(nsIFile* aFile);
+ nsresult CreateAddressBookDescriptor(nsIImportABDescriptor** aDescriptor);
+};
+
+#endif /* nsBeckyAddressBooks_h___ */
diff --git a/comm/mailnews/import/src/nsBeckyFilters.cpp b/comm/mailnews/import/src/nsBeckyFilters.cpp
new file mode 100644
index 0000000000..196560311b
--- /dev/null
+++ b/comm/mailnews/import/src/nsBeckyFilters.cpp
@@ -0,0 +1,711 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "nsILineInputStream.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIMsgFilter.h"
+#include "nsIMsgFilterList.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgAccount.h"
+#include "nsIMsgSearchTerm.h"
+#include "nsIMsgFolder.h"
+#include "nsCOMPtr.h"
+#include "nsMsgSearchCore.h"
+#include "nsMsgUtils.h"
+#include "msgCore.h"
+
+#include "nsBeckyFilters.h"
+#include "nsBeckyStringBundle.h"
+#include "nsBeckyUtils.h"
+
+NS_IMPL_ISUPPORTS(nsBeckyFilters, nsIImportFilters)
+
+nsresult nsBeckyFilters::Create(nsIImportFilters** aImport) {
+ NS_ENSURE_ARG_POINTER(aImport);
+ NS_ADDREF(*aImport = new nsBeckyFilters());
+ return NS_OK;
+}
+
+nsBeckyFilters::nsBeckyFilters()
+ : mLocation(nullptr), mServer(nullptr), mConvertedFile(nullptr) {}
+
+nsBeckyFilters::~nsBeckyFilters() {}
+
+nsresult nsBeckyFilters::GetDefaultFilterLocation(nsIFile** aFile) {
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> filterDir;
+ rv = nsBeckyUtils::GetDefaultMailboxDirectory(getter_AddRefs(filterDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ filterDir.forget(aFile);
+ return NS_OK;
+}
+
+nsresult nsBeckyFilters::GetFilterFile(bool aIncoming, nsIFile* aLocation,
+ nsIFile** aFile) {
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ // We assume the caller has already checked that aLocation is a directory,
+ // otherwise it would not make sense to call us.
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> filter;
+ aLocation->Clone(getter_AddRefs(filter));
+ if (aIncoming)
+ rv = filter->Append(u"IFilter.def"_ns);
+ else
+ rv = filter->Append(u"OFilter.def"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ rv = filter->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) return NS_ERROR_FILE_NOT_FOUND;
+
+ filter.forget(aFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyFilters::AutoLocate(char16_t** aDescription, nsIFile** aLocation,
+ bool* _retval) {
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ if (aDescription) {
+ *aDescription =
+ nsBeckyStringBundle::GetStringByName("BeckyImportDescription");
+ }
+ *aLocation = nullptr;
+ *_retval = false;
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> location;
+ rv = GetDefaultFilterLocation(getter_AddRefs(location));
+ if (NS_FAILED(rv))
+ location = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ else
+ *_retval = true;
+
+ location.forget(aLocation);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyFilters::SetLocation(nsIFile* aLocation) {
+ NS_ENSURE_ARG_POINTER(aLocation);
+
+ bool exists = false;
+ nsresult rv = aLocation->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) return NS_ERROR_FILE_NOT_FOUND;
+
+ mLocation = aLocation;
+ return NS_OK;
+}
+
+static nsMsgSearchAttribValue ConvertSearchKeyToAttrib(const nsACString& aKey) {
+ if (aKey.EqualsLiteral("From") || aKey.EqualsLiteral("Sender") ||
+ aKey.EqualsLiteral("From, Sender, X-Sender")) {
+ return nsMsgSearchAttrib::Sender;
+ } else if (aKey.EqualsLiteral("Subject")) {
+ return nsMsgSearchAttrib::Subject;
+ } else if (aKey.EqualsLiteral("[body]")) {
+ return nsMsgSearchAttrib::Body;
+ } else if (aKey.EqualsLiteral("Date")) {
+ return nsMsgSearchAttrib::Date;
+ } else if (aKey.EqualsLiteral("To")) {
+ return nsMsgSearchAttrib::To;
+ } else if (aKey.EqualsLiteral("Cc")) {
+ return nsMsgSearchAttrib::CC;
+ } else if (aKey.EqualsLiteral("To, Cc, Bcc:")) {
+ return nsMsgSearchAttrib::ToOrCC;
+ }
+ return -1;
+}
+
+static nsMsgSearchOpValue ConvertSearchFlagsToOperator(
+ const nsACString& aFlags) {
+ nsCString flags(aFlags);
+ int32_t lastTabPosition = flags.RFindChar('\t');
+ if ((lastTabPosition == -1) ||
+ ((int32_t)aFlags.Length() == lastTabPosition - 1)) {
+ return -1;
+ }
+
+ switch (aFlags.CharAt(0)) {
+ case 'X':
+ return nsMsgSearchOp::DoesntContain;
+ case 'O':
+ if (aFlags.FindChar('T', lastTabPosition + 1) >= 0)
+ return nsMsgSearchOp::BeginsWith;
+ return nsMsgSearchOp::Contains;
+ default:
+ return -1;
+ }
+}
+
+nsresult nsBeckyFilters::ParseRuleLine(const nsCString& aLine,
+ nsMsgSearchAttribValue* aSearchAttribute,
+ nsMsgSearchOpValue* aSearchOperator,
+ nsString& aSearchKeyword) {
+ int32_t firstColonPosition = aLine.FindChar(':');
+ if (firstColonPosition == -1 ||
+ (int32_t)aLine.Length() == firstColonPosition - 1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t secondColonPosition = aLine.FindChar(':', firstColonPosition + 1);
+ if (secondColonPosition == -1 ||
+ (int32_t)aLine.Length() == secondColonPosition - 1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ int32_t length = secondColonPosition - firstColonPosition - 1;
+ nsMsgSearchAttribValue searchAttribute;
+ searchAttribute = ConvertSearchKeyToAttrib(
+ Substring(aLine, firstColonPosition + 1, length));
+ if (searchAttribute < 0) return NS_ERROR_FAILURE;
+
+ int32_t tabPosition = aLine.FindChar('\t');
+ if (tabPosition == -1 || (int32_t)aLine.Length() == tabPosition - 1) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsMsgSearchOpValue searchOperator;
+ searchOperator =
+ ConvertSearchFlagsToOperator(Substring(aLine, tabPosition + 1));
+ if (searchOperator < 0) return NS_ERROR_FAILURE;
+
+ *aSearchOperator = searchOperator;
+ *aSearchAttribute = searchAttribute;
+ length = tabPosition - secondColonPosition - 1;
+ CopyUTF8toUTF16(Substring(aLine, secondColonPosition + 1, length),
+ aSearchKeyword);
+ return NS_OK;
+}
+
+nsresult nsBeckyFilters::SetSearchTerm(const nsCString& aLine,
+ nsIMsgFilter* aFilter) {
+ NS_ENSURE_ARG_POINTER(aFilter);
+
+ nsresult rv;
+ nsMsgSearchAttribValue searchAttribute = -1;
+ nsMsgSearchOpValue searchOperator = -1;
+ nsAutoString searchKeyword;
+ rv = ParseRuleLine(aLine, &searchAttribute, &searchOperator, searchKeyword);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgSearchTerm> term;
+ rv = aFilter->CreateTerm(getter_AddRefs(term));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = term->SetAttrib(searchAttribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = term->SetOp(searchOperator);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgSearchValue> value;
+ rv = term->GetValue(getter_AddRefs(value));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = value->SetAttrib(searchAttribute);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = value->SetStr(searchKeyword);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = term->SetValue(value);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = term->SetBooleanAnd(false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!searchKeyword.IsEmpty())
+ rv = aFilter->SetFilterName(searchKeyword);
+ else
+ rv = aFilter->SetFilterName(u"No name"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return aFilter->AppendTerm(term);
+}
+
+nsresult nsBeckyFilters::CreateRuleAction(nsIMsgFilter* aFilter,
+ nsMsgRuleActionType actionType,
+ nsIMsgRuleAction** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgRuleAction> action;
+ rv = aFilter->CreateAction(getter_AddRefs(action));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = action->SetType(actionType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ action.forget(_retval);
+
+ return NS_OK;
+}
+
+nsresult nsBeckyFilters::GetActionTarget(const nsCString& aLine,
+ nsCString& aTarget) {
+ int32_t firstColonPosition = aLine.FindChar(':');
+ if (firstColonPosition < -1 ||
+ aLine.Length() == static_cast<uint32_t>(firstColonPosition)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ aTarget.Assign(Substring(aLine, firstColonPosition + 1));
+
+ return NS_OK;
+}
+
+nsresult nsBeckyFilters::GetResendTarget(const nsCString& aLine,
+ nsCString& aTemplate,
+ nsCString& aTargetAddress) {
+ nsresult rv;
+ nsAutoCString target;
+ rv = GetActionTarget(aLine, target);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t asteriskPosition = target.FindChar('*');
+ if (asteriskPosition < 0) {
+ aTemplate.Assign(target);
+ return NS_OK;
+ }
+
+ if (target.Length() == static_cast<uint32_t>(asteriskPosition))
+ return NS_ERROR_FAILURE;
+
+ aTemplate.Assign(StringHead(target, asteriskPosition - 1));
+ aTargetAddress.Assign(Substring(target, asteriskPosition + 1));
+
+ return NS_OK;
+}
+
+nsresult nsBeckyFilters::CreateResendAction(
+ const nsCString& aLine, nsIMsgFilter* aFilter,
+ const nsMsgRuleActionType& aActionType, nsIMsgRuleAction** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgRuleAction> action;
+ rv = CreateRuleAction(aFilter, aActionType, getter_AddRefs(action));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString templateString;
+ nsAutoCString targetAddress;
+ rv = GetResendTarget(aLine, templateString, targetAddress);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aActionType == nsMsgFilterAction::Forward)
+ rv = action->SetStrValue(targetAddress);
+ else
+ rv = action->SetStrValue(templateString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ action.forget(_retval);
+
+ return NS_OK;
+}
+
+nsresult nsBeckyFilters::GetFolderNameFromTarget(const nsCString& aTarget,
+ nsAString& aName) {
+ int32_t backslashPosition = aTarget.RFindChar('\\');
+ if (backslashPosition > 0) {
+ NS_ConvertUTF8toUTF16 utf16String(
+ Substring(aTarget, backslashPosition + 1));
+ nsBeckyUtils::TranslateFolderName(utf16String, aName);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsBeckyFilters::GetDistributeTarget(const nsCString& aLine,
+ nsCString& aTargetFolder) {
+ nsresult rv;
+ nsAutoCString target;
+ rv = GetActionTarget(aLine, target);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ target.Trim("\\", false, true);
+ nsAutoString folderName;
+ rv = GetFolderNameFromTarget(target, folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> folder;
+ rv = GetMessageFolder(folderName, getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!folder) {
+ rv = mServer->GetRootMsgFolder(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ return folder->GetURI(aTargetFolder);
+}
+
+nsresult nsBeckyFilters::CreateDistributeAction(
+ const nsCString& aLine, nsIMsgFilter* aFilter,
+ const nsMsgRuleActionType& aActionType, nsIMsgRuleAction** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgRuleAction> action;
+ rv = CreateRuleAction(aFilter, aActionType, getter_AddRefs(action));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsAutoCString targetFolder;
+ rv = GetDistributeTarget(aLine, targetFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = action->SetTargetFolderUri(targetFolder);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ action.forget(_retval);
+
+ return NS_OK;
+}
+
+nsresult nsBeckyFilters::CreateLeaveOrDeleteAction(const nsCString& aLine,
+ nsIMsgFilter* aFilter,
+ nsIMsgRuleAction** _retval) {
+ nsresult rv;
+ nsMsgRuleActionType actionType;
+ if (aLine.CharAt(3) == '0') {
+ actionType = nsMsgFilterAction::LeaveOnPop3Server;
+ } else if (aLine.CharAt(3) == '1') {
+ if (aLine.CharAt(5) == '1')
+ actionType = nsMsgFilterAction::Delete;
+ else
+ actionType = nsMsgFilterAction::DeleteFromPop3Server;
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIMsgRuleAction> action;
+ rv = CreateRuleAction(aFilter, actionType, getter_AddRefs(action));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ action.forget(_retval);
+
+ return NS_OK;
+}
+
+nsresult nsBeckyFilters::SetRuleAction(const nsCString& aLine,
+ nsIMsgFilter* aFilter) {
+ if (!aFilter || aLine.Length() < 4) return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIMsgRuleAction> action;
+ switch (aLine.CharAt(1)) {
+ case 'R': // Reply
+ rv = CreateResendAction(aLine, aFilter, nsMsgFilterAction::Reply,
+ getter_AddRefs(action));
+ break;
+ case 'F': // Forward
+ rv = CreateResendAction(aLine, aFilter, nsMsgFilterAction::Forward,
+ getter_AddRefs(action));
+ break;
+ case 'L': // Leave or delete
+ rv = CreateLeaveOrDeleteAction(aLine, aFilter, getter_AddRefs(action));
+ break;
+ case 'Y': // Copy
+ rv = CreateDistributeAction(aLine, aFilter,
+ nsMsgFilterAction::CopyToFolder,
+ getter_AddRefs(action));
+ break;
+ case 'M': // Move
+ rv = CreateDistributeAction(aLine, aFilter,
+ nsMsgFilterAction::MoveToFolder,
+ getter_AddRefs(action));
+ break;
+ case 'G': // Set flag
+ if (aLine.CharAt(3) == 'R') // Read
+ rv = CreateRuleAction(aFilter, nsMsgFilterAction::MarkRead,
+ getter_AddRefs(action));
+ break;
+ default:
+ return NS_OK;
+ }
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (action) {
+ rv = aFilter->AppendAction(action);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsBeckyFilters::CreateFilter(bool aIncoming, nsIMsgFilter** _retval) {
+ NS_ENSURE_STATE(mServer);
+
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ mServer->GetFilterList(nullptr, getter_AddRefs(filterList));
+ NS_ENSURE_STATE(filterList);
+
+ nsCOMPtr<nsIMsgFilter> filter;
+ nsresult rv = filterList->CreateFilter(EmptyString(), getter_AddRefs(filter));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aIncoming)
+ filter->SetFilterType(nsMsgFilterType::InboxRule | nsMsgFilterType::Manual);
+ else
+ filter->SetFilterType(nsMsgFilterType::PostOutgoing |
+ nsMsgFilterType::Manual);
+
+ filter->SetEnabled(true);
+ filter.forget(_retval);
+
+ return NS_OK;
+}
+
+nsresult nsBeckyFilters::AppendFilter(nsIMsgFilter* aFilter) {
+ NS_ENSURE_STATE(mServer);
+
+ nsCOMPtr<nsIMsgFilterList> filterList;
+ mServer->GetFilterList(nullptr, getter_AddRefs(filterList));
+ NS_ENSURE_STATE(filterList);
+
+ uint32_t count;
+ nsresult rv = filterList->GetFilterCount(&count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return filterList->InsertFilterAt(count, aFilter);
+}
+
+nsresult nsBeckyFilters::ParseFilterFile(nsIFile* aFile, bool aIncoming) {
+ nsresult rv;
+ nsCOMPtr<nsILineInputStream> lineStream;
+ rv = nsBeckyUtils::CreateLineInputStream(aFile, getter_AddRefs(lineStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ nsAutoCString line;
+
+ nsCOMPtr<nsIMsgFilter> filter;
+ while (NS_SUCCEEDED(rv) && more) {
+ rv = lineStream->ReadLine(line, &more);
+
+ switch (line.CharAt(0)) {
+ case ':':
+ if (line.EqualsLiteral(":Begin \"\"")) {
+ CreateFilter(aIncoming, getter_AddRefs(filter));
+ } else if (line.EqualsLiteral(":End \"\"")) {
+ if (filter) AppendFilter(filter);
+ filter = nullptr;
+ }
+ break;
+ case '!':
+ SetRuleAction(line, filter);
+ break;
+ case '@':
+ SetSearchTerm(line, filter);
+ break;
+ case '$': // $X: disabled
+ if (StringBeginsWith(line, "$X"_ns) && filter) {
+ filter->SetEnabled(false);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyFilters::Import(char16_t** aError, bool* _retval) {
+ NS_ENSURE_ARG_POINTER(aError);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ // If mLocation is null, set it to the default filter directory.
+ // If mLocation is a file, we import it as incoming folder.
+ // If mLocation is a directory, we try to import incoming and outgoing folders
+ // from it (in default files).
+
+ *_retval = false;
+ nsresult rv;
+ nsCOMPtr<nsIFile> filterFile;
+
+ bool haveFile = false;
+
+ if (!mLocation) {
+ bool retval = false;
+ rv = AutoLocate(nullptr, getter_AddRefs(mLocation), &retval);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!retval) return NS_ERROR_FILE_NOT_FOUND;
+ }
+
+ // What type of location do we have?
+ bool isDirectory = false;
+ rv = mLocation->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isDirectory) {
+ haveFile = false;
+ } else {
+ bool isFile = false;
+ rv = mLocation->IsFile(&isFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (isFile) {
+ haveFile = true;
+ mLocation->Clone(getter_AddRefs(filterFile));
+ } else {
+ // mLocation is neither file nor directory.
+ return NS_ERROR_UNEXPECTED;
+ }
+ }
+
+ bool haveIncoming = true;
+ if (haveFile) {
+ // If the passed filename equals OFilter.def, import as outgoing filters.
+ // Everything else is considered incoming.
+ nsAutoString fileName;
+ rv = mLocation->GetLeafName(fileName);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (fileName.EqualsLiteral("OFilter.def")) haveIncoming = false;
+ }
+
+ // Try importing from the passed in file or the default incoming filters file.
+ if ((haveFile && haveIncoming) ||
+ (!haveFile && NS_SUCCEEDED(GetFilterFile(true, mLocation,
+ getter_AddRefs(filterFile))))) {
+ rv = CollectServers();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsBeckyUtils::ConvertToUTF8File(filterFile,
+ getter_AddRefs(mConvertedFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ParseFilterFile(mConvertedFile, true);
+ if (NS_SUCCEEDED(rv)) *_retval = true;
+
+ (void)RemoveConvertedFile();
+ }
+
+ // If we didn't have a file passed (but a directory), try finding also
+ // outgoing filters.
+ if ((haveFile && !haveIncoming) ||
+ (!haveFile && NS_SUCCEEDED(GetFilterFile(false, mLocation,
+ getter_AddRefs(filterFile))))) {
+ rv = CollectServers();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsBeckyUtils::ConvertToUTF8File(filterFile,
+ getter_AddRefs(mConvertedFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ParseFilterFile(mConvertedFile, false);
+ if (NS_SUCCEEDED(rv)) *_retval = true;
+
+ (void)RemoveConvertedFile();
+ }
+
+ return rv;
+}
+
+nsresult nsBeckyFilters::FindMessageFolder(const nsAString& aName,
+ nsIMsgFolder* aParentFolder,
+ nsIMsgFolder** _retval) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgFolder> found;
+ rv = aParentFolder->GetChildNamed(aName, getter_AddRefs(found));
+ if (found) {
+ NS_ADDREF(*_retval = found);
+ return NS_OK;
+ }
+
+ nsTArray<RefPtr<nsIMsgFolder>> children;
+ rv = aParentFolder->GetSubFolders(children);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ for (nsIMsgFolder* child : children) {
+ rv = FindMessageFolder(aName, child, getter_AddRefs(found));
+ if (found) {
+ NS_ADDREF(*_retval = found);
+ return NS_OK;
+ }
+ }
+
+ return NS_MSG_ERROR_INVALID_FOLDER_NAME;
+}
+
+nsresult nsBeckyFilters::FindMessageFolderInServer(
+ const nsAString& aName, nsIMsgIncomingServer* aServer,
+ nsIMsgFolder** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgFolder> rootFolder;
+ rv = aServer->GetRootMsgFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return FindMessageFolder(aName, rootFolder, _retval);
+}
+
+nsresult nsBeckyFilters::GetMessageFolder(const nsAString& aName,
+ nsIMsgFolder** _retval) {
+ nsresult rv;
+
+ nsCOMPtr<nsIMsgAccountManager> accountManager;
+ accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<RefPtr<nsIMsgAccount>> accounts;
+ rv = accountManager->GetAccounts(accounts);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgFolder> found;
+ for (auto account : accounts) {
+ if (!account) continue;
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ account->GetIncomingServer(getter_AddRefs(server));
+ if (!server) continue;
+ FindMessageFolderInServer(aName, server, getter_AddRefs(found));
+ if (found) break;
+ }
+
+ if (!found) {
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = accountManager->GetLocalFoldersServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ FindMessageFolderInServer(aName, server, getter_AddRefs(found));
+ }
+
+ if (!found) return NS_MSG_ERROR_INVALID_FOLDER_NAME;
+
+ found.forget(_retval);
+
+ return NS_OK;
+}
+
+nsresult nsBeckyFilters::CollectServers() {
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager;
+ accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccount> defaultAccount;
+ rv = accountManager->GetDefaultAccount(getter_AddRefs(defaultAccount));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (defaultAccount)
+ return defaultAccount->GetIncomingServer(getter_AddRefs(mServer));
+
+ // We can also import filters into the Local Folders account.
+ rv = accountManager->GetLocalFoldersServer(getter_AddRefs(mServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!mServer) return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+nsresult nsBeckyFilters::RemoveConvertedFile() {
+ nsresult rv = NS_OK;
+ if (mConvertedFile) {
+ bool exists = false;
+ mConvertedFile->Exists(&exists);
+ if (exists) {
+ rv = mConvertedFile->Remove(false);
+ if (NS_SUCCEEDED(rv)) mConvertedFile = nullptr;
+ }
+ }
+ return rv;
+}
diff --git a/comm/mailnews/import/src/nsBeckyFilters.h b/comm/mailnews/import/src/nsBeckyFilters.h
new file mode 100644
index 0000000000..91ee2ed813
--- /dev/null
+++ b/comm/mailnews/import/src/nsBeckyFilters.h
@@ -0,0 +1,73 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 nsBeckyFilters_h___
+#define nsBeckyFilters_h___
+
+#include "nsIImportFilters.h"
+#include "nsIFile.h"
+#include "nsString.h"
+#include "nsIMsgIncomingServer.h"
+#include "nsMsgFilterCore.h"
+
+class nsIMsgFilter;
+class nsIMsgRuleAction;
+
+class nsBeckyFilters final : public nsIImportFilters {
+ public:
+ nsBeckyFilters();
+ static nsresult Create(nsIImportFilters** aImport);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIMPORTFILTERS
+
+ private:
+ virtual ~nsBeckyFilters();
+
+ nsCOMPtr<nsIFile> mLocation;
+ nsCOMPtr<nsIMsgIncomingServer> mServer;
+ nsCOMPtr<nsIFile> mConvertedFile;
+
+ nsresult GetDefaultFilterLocation(nsIFile** aFile);
+ nsresult GetFilterFile(bool aIncoming, nsIFile* aLocation, nsIFile** aFile);
+ nsresult ParseFilterFile(nsIFile* aFile, bool aIncoming);
+ nsresult ParseRuleLine(const nsCString& aLine,
+ nsMsgSearchAttribValue* aSearchAttribute,
+ nsMsgSearchOpValue* aSearchOperator,
+ nsString& aSearchKeyword);
+ nsresult CollectServers();
+ nsresult FindMessageFolder(const nsAString& aName,
+ nsIMsgFolder* aParantFolder,
+ nsIMsgFolder** _retval);
+ nsresult FindMessageFolderInServer(const nsAString& aName,
+ nsIMsgIncomingServer* aServer,
+ nsIMsgFolder** _retval);
+ nsresult GetMessageFolder(const nsAString& aName, nsIMsgFolder** _retval);
+ nsresult GetActionTarget(const nsCString& aLine, nsCString& aTarget);
+ nsresult GetFolderNameFromTarget(const nsCString& aTarget, nsAString& aName);
+ nsresult GetDistributeTarget(const nsCString& aLine,
+ nsCString& aTargetFolder);
+ nsresult GetResendTarget(const nsCString& aLine, nsCString& aTemplate,
+ nsCString& aTargetAddress);
+ nsresult CreateRuleAction(nsIMsgFilter* aFilter,
+ nsMsgRuleActionType actionType,
+ nsIMsgRuleAction** _retval);
+ nsresult CreateDistributeAction(const nsCString& aLine, nsIMsgFilter* aFilter,
+ const nsMsgRuleActionType& aActionType,
+ nsIMsgRuleAction** _retval);
+ nsresult CreateLeaveOrDeleteAction(const nsCString& aLine,
+ nsIMsgFilter* aFilter,
+ nsIMsgRuleAction** _retval);
+ nsresult CreateResendAction(const nsCString& aLine, nsIMsgFilter* aFilter,
+ const nsMsgRuleActionType& aActionType,
+ nsIMsgRuleAction** _retval);
+ nsresult CreateFilter(bool aIncoming, nsIMsgFilter** _retval);
+ nsresult AppendFilter(nsIMsgFilter* aFilter);
+ nsresult SetRuleAction(const nsCString& aLine, nsIMsgFilter* aFilter);
+ nsresult SetSearchTerm(const nsCString& aLine, nsIMsgFilter* aFilter);
+ nsresult RemoveConvertedFile();
+};
+
+#endif /* nsBeckyFilters_h___ */
diff --git a/comm/mailnews/import/src/nsBeckyImport.cpp b/comm/mailnews/import/src/nsBeckyImport.cpp
new file mode 100644
index 0000000000..8b56ba95d3
--- /dev/null
+++ b/comm/mailnews/import/src/nsBeckyImport.cpp
@@ -0,0 +1,143 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "nscore.h"
+#include "nsIImportService.h"
+#include "nsIImportMail.h"
+#include "nsIImportGeneric.h"
+#include "nsIImportAddressBooks.h"
+#include "nsIImportSettings.h"
+#include "nsIImportFilters.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsTextFormatter.h"
+#include "nsUnicharUtils.h"
+#include "nsCOMPtr.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+
+#include "nsBeckyImport.h"
+#include "nsBeckyMail.h"
+#include "nsBeckyAddressBooks.h"
+#include "nsBeckySettings.h"
+#include "nsBeckyFilters.h"
+#include "nsBeckyStringBundle.h"
+
+nsBeckyImport::nsBeckyImport() {}
+
+nsBeckyImport::~nsBeckyImport() {}
+
+NS_IMPL_ISUPPORTS(nsBeckyImport, nsIImportModule)
+
+NS_IMETHODIMP
+nsBeckyImport::GetName(char16_t** aName) {
+ NS_ENSURE_ARG_POINTER(aName);
+ *aName = nsBeckyStringBundle::GetStringByName("BeckyImportName");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyImport::GetDescription(char16_t** aDescription) {
+ NS_ENSURE_ARG_POINTER(aDescription);
+ *aDescription =
+ nsBeckyStringBundle::GetStringByName("BeckyImportDescription");
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyImport::GetSupports(char** aSupports) {
+ NS_ENSURE_ARG_POINTER(aSupports);
+ *aSupports = strdup(kBeckySupportsString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyImport::GetSupportsUpgrade(bool* aUpgrade) {
+ NS_ENSURE_ARG_POINTER(aUpgrade);
+ *aUpgrade = true;
+ return NS_OK;
+}
+
+nsresult nsBeckyImport::GetMailImportInterface(nsISupports** aInterface) {
+ nsCOMPtr<nsIImportMail> importer;
+ nsresult rv = nsBeckyMail::Create(getter_AddRefs(importer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImportService> importService(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCOMPtr<nsIImportGeneric> generic;
+ rv = importService->CreateNewGenericMail(getter_AddRefs(generic));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ generic->SetData("mailInterface", importer);
+
+ nsString name;
+ name.Adopt(nsBeckyStringBundle::GetStringByName("BeckyImportName"));
+
+ nsCOMPtr<nsISupportsString> nameString(
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nameString->SetData(name);
+ generic->SetData("name", nameString);
+
+ return CallQueryInterface(generic, aInterface);
+}
+
+nsresult nsBeckyImport::GetAddressBookImportInterface(
+ nsISupports** aInterface) {
+ nsresult rv;
+ nsCOMPtr<nsIImportAddressBooks> importer;
+ rv = nsBeckyAddressBooks::Create(getter_AddRefs(importer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImportService> importService(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIImportGeneric> generic;
+ rv = importService->CreateNewGenericAddressBooks(getter_AddRefs(generic));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ generic->SetData("addressInterface", importer);
+ return CallQueryInterface(generic, aInterface);
+}
+
+nsresult nsBeckyImport::GetSettingsImportInterface(nsISupports** aInterface) {
+ nsresult rv;
+ nsCOMPtr<nsIImportSettings> importer;
+ rv = nsBeckySettings::Create(getter_AddRefs(importer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(importer, aInterface);
+}
+
+nsresult nsBeckyImport::GetFiltersImportInterface(nsISupports** aInterface) {
+ nsresult rv;
+ nsCOMPtr<nsIImportFilters> importer;
+ rv = nsBeckyFilters::Create(getter_AddRefs(importer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(importer, aInterface);
+}
+
+NS_IMETHODIMP
+nsBeckyImport::GetImportInterface(const char* aImportType,
+ nsISupports** aInterface) {
+ NS_ENSURE_ARG_POINTER(aImportType);
+ NS_ENSURE_ARG_POINTER(aInterface);
+
+ *aInterface = nullptr;
+ if (!strcmp(aImportType, "mail")) return GetMailImportInterface(aInterface);
+ if (!strcmp(aImportType, "addressbook"))
+ return GetAddressBookImportInterface(aInterface);
+ if (!strcmp(aImportType, "settings"))
+ return GetSettingsImportInterface(aInterface);
+ if (!strcmp(aImportType, "filters"))
+ return GetFiltersImportInterface(aInterface);
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
diff --git a/comm/mailnews/import/src/nsBeckyImport.h b/comm/mailnews/import/src/nsBeckyImport.h
new file mode 100644
index 0000000000..0884d417fb
--- /dev/null
+++ b/comm/mailnews/import/src/nsBeckyImport.h
@@ -0,0 +1,38 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 nsBeckyImport_h___
+#define nsBeckyImport_h___
+
+#include "nsIImportModule.h"
+
+#define NS_BECKYIMPORT_CID \
+ { \
+ 0x7952a6cf, 0x2442, 0x4c04, { \
+ 0x9f, 0x02, 0x15, 0x0b, 0x15, 0xa0, 0xa8, 0x41 \
+ } \
+ }
+
+#define kBeckySupportsString \
+ NS_IMPORT_MAIL_STR "," NS_IMPORT_ADDRESS_STR "," NS_IMPORT_SETTINGS_STR \
+ "," NS_IMPORT_FILTERS_STR
+
+class nsBeckyImport final : public nsIImportModule {
+ public:
+ nsBeckyImport();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIMPORTMODULE
+
+ private:
+ virtual ~nsBeckyImport();
+
+ nsresult GetMailImportInterface(nsISupports** aInterface);
+ nsresult GetAddressBookImportInterface(nsISupports** aInterface);
+ nsresult GetSettingsImportInterface(nsISupports** aInterface);
+ nsresult GetFiltersImportInterface(nsISupports** aInterface);
+};
+
+#endif /* nsBeckyImport_h___ */
diff --git a/comm/mailnews/import/src/nsBeckyMail.cpp b/comm/mailnews/import/src/nsBeckyMail.cpp
new file mode 100644
index 0000000000..36ab1e1104
--- /dev/null
+++ b/comm/mailnews/import/src/nsBeckyMail.cpp
@@ -0,0 +1,566 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsILineInputStream.h"
+#include "nsNetUtil.h"
+#include "nsIImportService.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgFolder.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsMsgUtils.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "nsMsgMessageFlags.h"
+#include "nsTArray.h"
+#include "nspr.h"
+#include "nsThreadUtils.h"
+#include "nsIDirectoryEnumerator.h"
+
+#include "nsBeckyMail.h"
+#include "nsBeckyUtils.h"
+#include "nsBeckyStringBundle.h"
+
+#define FROM_LINE "From - Mon Jan 1 00:00:00 1965" MSG_LINEBREAK
+#define X_BECKY_STATUS_HEADER "X-Becky-Status"
+#define X_BECKY_INCLUDE_HEADER "X-Becky-Include"
+
+enum {
+ BECKY_STATUS_READ = 1 << 0,
+ BECKY_STATUS_FORWARDED = 1 << 1,
+ BECKY_STATUS_REPLIED = 1 << 2
+};
+
+NS_IMPL_ISUPPORTS(nsBeckyMail, nsIImportMail)
+
+nsresult nsBeckyMail::Create(nsIImportMail** aImport) {
+ NS_ENSURE_ARG_POINTER(aImport);
+ NS_ADDREF(*aImport = new nsBeckyMail());
+ return NS_OK;
+}
+
+nsBeckyMail::nsBeckyMail() : mReadBytes(0) {}
+
+nsBeckyMail::~nsBeckyMail() {}
+
+NS_IMETHODIMP
+nsBeckyMail::GetDefaultLocation(nsIFile** aLocation, bool* aFound,
+ bool* aUserVerify) {
+ NS_ENSURE_ARG_POINTER(aFound);
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(aUserVerify);
+
+ *aLocation = nullptr;
+ *aUserVerify = true;
+ *aFound = false;
+ if (NS_SUCCEEDED(nsBeckyUtils::GetDefaultMailboxDirectory(aLocation)))
+ *aFound = true;
+
+ return NS_OK;
+}
+
+nsresult nsBeckyMail::CreateMailboxDescriptor(
+ nsIImportMailboxDescriptor** aDescriptor) {
+ nsresult rv;
+ nsCOMPtr<nsIImportService> importService;
+ importService = do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return importService->CreateNewMailboxDescriptor(aDescriptor);
+}
+
+nsresult nsBeckyMail::GetMailboxName(nsIFile* aMailbox, nsAString& aName) {
+ nsCOMPtr<nsIFile> iniFile;
+ nsBeckyUtils::GetMailboxINIFile(aMailbox, getter_AddRefs(iniFile));
+ if (iniFile) {
+ nsCOMPtr<nsIFile> convertedFile;
+ nsBeckyUtils::ConvertToUTF8File(iniFile, getter_AddRefs(convertedFile));
+ if (convertedFile) {
+ nsAutoCString utf8Name;
+ nsBeckyUtils::GetMailboxNameFromINIFile(convertedFile, utf8Name);
+ convertedFile->Remove(false);
+ CopyUTF8toUTF16(utf8Name, aName);
+ }
+ }
+
+ if (aName.IsEmpty()) {
+ nsAutoString name;
+ aMailbox->GetLeafName(name);
+ name.Trim("!", true, false);
+ aName.Assign(name);
+ }
+
+ return NS_OK;
+}
+
+nsresult nsBeckyMail::AppendMailboxDescriptor(
+ nsIFile* aEntry, const nsString& aName, uint32_t aDepth,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& aCollected) {
+ nsresult rv;
+ nsCOMPtr<nsIImportMailboxDescriptor> descriptor;
+ rv = CreateMailboxDescriptor(getter_AddRefs(descriptor));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int64_t size;
+ rv = aEntry->GetFileSize(&size);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = descriptor->SetSize(size);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = descriptor->SetDisplayName(aName.get());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> mailboxFile;
+ rv = descriptor->GetFile(getter_AddRefs(mailboxFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ descriptor->SetDepth(aDepth);
+
+ mailboxFile->InitWithFile(aEntry);
+ aCollected.AppendElement(descriptor);
+
+ return NS_OK;
+}
+
+nsresult nsBeckyMail::CollectMailboxesInFolderListFile(
+ nsIFile* aListFile, uint32_t aDepth,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& aCollected) {
+ nsresult rv;
+ nsCOMPtr<nsILineInputStream> lineStream;
+ rv = nsBeckyUtils::CreateLineInputStream(aListFile,
+ getter_AddRefs(lineStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> parent;
+ rv = aListFile->GetParent(getter_AddRefs(parent));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ nsAutoCString folderName;
+ bool isEmpty = true;
+ while (more && NS_SUCCEEDED(rv)) {
+ rv = lineStream->ReadLine(folderName, &more);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (folderName.IsEmpty()) continue;
+
+ nsCOMPtr<nsIFile> folder;
+ rv = parent->Clone(getter_AddRefs(folder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = folder->AppendNative(folderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ isEmpty = false;
+ rv = CollectMailboxesInDirectory(folder, aDepth + 1, aCollected);
+ }
+
+ return isEmpty ? NS_ERROR_FILE_NOT_FOUND : NS_OK;
+}
+
+nsresult nsBeckyMail::CollectMailboxesInDirectory(
+ nsIFile* aDirectory, uint32_t aDepth,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& aCollected) {
+ nsAutoString mailboxName;
+ nsresult rv = GetMailboxName(aDirectory, mailboxName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (aDepth != 0)
+ AppendMailboxDescriptor(aDirectory, mailboxName, aDepth, aCollected);
+
+ nsCOMPtr<nsIFile> folderListFile;
+ rv = nsBeckyUtils::GetFolderListFile(aDirectory,
+ getter_AddRefs(folderListFile));
+ bool folderListExists = false;
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = CollectMailboxesInFolderListFile(folderListFile, aDepth, aCollected);
+ folderListExists = true;
+ }
+
+ nsCOMPtr<nsIDirectoryEnumerator> entries;
+ rv = aDirectory->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsIFile> file;
+ rv = entries->GetNextFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString name;
+ rv = file->GetLeafName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (StringEndsWith(name, u".bmf"_ns)) {
+ AppendMailboxDescriptor(file, mailboxName, aDepth, aCollected);
+ }
+
+ // The Folder.lst file is not created if there is only one sub folder,
+ // so we need to find the sub folder by our hands.
+ // The folder name does not begin with # or ! maybe. Yes, maybe...
+ if (!folderListExists) {
+ if (StringBeginsWith(name, u"#"_ns) || StringBeginsWith(name, u"!"_ns))
+ continue;
+
+ bool isDirectory = false;
+ rv = file->IsDirectory(&isDirectory);
+ if (isDirectory) {
+ CollectMailboxesInDirectory(file, aDepth + 1, aCollected);
+ continue;
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyMail::FindMailboxes(
+ nsIFile* aLocation, nsTArray<RefPtr<nsIImportMailboxDescriptor>>& boxes) {
+ NS_ENSURE_ARG_POINTER(aLocation);
+
+ boxes.Clear();
+ nsresult rv = CollectMailboxesInDirectory(aLocation, 0, boxes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ return NS_OK;
+}
+
+static nsresult GetBeckyStatusValue(const nsCString& aHeader,
+ nsACString& aValue) {
+ int32_t valueStartPosition;
+
+ valueStartPosition = aHeader.FindChar(':');
+ if (valueStartPosition < 0) return NS_ERROR_UNEXPECTED;
+
+ valueStartPosition++;
+
+ int32_t commaPosition = aHeader.FindChar(',', valueStartPosition);
+ if (commaPosition < 0) return NS_ERROR_UNEXPECTED;
+
+ nsAutoCString value(Substring(aHeader, valueStartPosition,
+ commaPosition - valueStartPosition));
+ value.Trim(" \t");
+
+ aValue.Assign(value);
+
+ return NS_OK;
+}
+
+static nsresult GetBeckyIncludeValue(const nsCString& aHeader,
+ nsACString& aValue) {
+ int32_t valueStartPosition;
+
+ valueStartPosition = aHeader.FindChar(':');
+ if (valueStartPosition < 0) return NS_ERROR_FAILURE;
+
+ valueStartPosition++;
+ nsAutoCString value(Substring(aHeader, valueStartPosition));
+ value.Trim(" \t");
+
+ aValue.Assign(value);
+
+ return NS_OK;
+}
+
+static bool ConvertBeckyStatusToMozillaStatus(
+ const nsCString& aHeader, nsMsgMessageFlagType* aMozillaStatusFlag) {
+ nsresult rv;
+ nsAutoCString statusString;
+ rv = GetBeckyStatusValue(aHeader, statusString);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ nsresult errorCode;
+ uint32_t beckyStatusFlag =
+ static_cast<uint32_t>(statusString.ToInteger(&errorCode, 16));
+ if (NS_FAILED(errorCode)) return false;
+
+ if (beckyStatusFlag & BECKY_STATUS_READ)
+ *aMozillaStatusFlag |= nsMsgMessageFlags::Read;
+ if (beckyStatusFlag & BECKY_STATUS_FORWARDED)
+ *aMozillaStatusFlag |= nsMsgMessageFlags::Forwarded;
+ if (beckyStatusFlag & BECKY_STATUS_REPLIED)
+ *aMozillaStatusFlag |= nsMsgMessageFlags::Replied;
+
+ return true;
+}
+
+static inline bool CheckHeaderKey(const nsCString& aHeader,
+ const char* aKeyString) {
+ nsAutoCString key(StringHead(aHeader, aHeader.FindChar(':')));
+ key.Trim(" \t");
+ return key.Equals(aKeyString);
+}
+
+static inline bool IsBeckyStatusHeader(const nsCString& aHeader) {
+ return CheckHeaderKey(aHeader, X_BECKY_STATUS_HEADER);
+}
+
+static inline bool IsBeckyIncludeLine(const nsCString& aLine) {
+ return CheckHeaderKey(aLine, X_BECKY_INCLUDE_HEADER);
+}
+
+static inline bool IsEndOfHeaders(const nsCString& aLine) {
+ return aLine.IsEmpty();
+}
+
+static inline bool IsEndOfMessage(const nsCString& aLine) {
+ return aLine.EqualsLiteral(".");
+}
+
+class ImportMessageRunnable : public mozilla::Runnable {
+ public:
+ ImportMessageRunnable(nsIFile* aMessageFile, nsIMsgFolder* aFolder);
+ NS_DECL_NSIRUNNABLE
+ nsresult mResult;
+
+ private:
+ nsresult WriteHeaders(nsCString& aHeaders, nsIOutputStream* aOutputStream);
+ nsresult HandleHeaderLine(const nsCString& aHeaderLine, nsACString& aHeaders);
+ nsresult GetAttachmentFile(nsIFile* aMailboxFile, const nsCString& aHeader,
+ nsIFile** _retval);
+ nsresult WriteAttachmentFile(nsIFile* aMailboxFile, const nsCString& aHeader,
+ nsIOutputStream* aOutputStream);
+
+ nsCOMPtr<nsIFile> mMessageFile;
+ nsCOMPtr<nsIMsgFolder> mFolder;
+};
+
+ImportMessageRunnable::ImportMessageRunnable(nsIFile* aMessageFile,
+ nsIMsgFolder* aFolder)
+ : mozilla::Runnable("ImportMessageRunnable"),
+ mMessageFile(aMessageFile),
+ mFolder(aFolder) {}
+
+nsresult ImportMessageRunnable::WriteHeaders(nsCString& aHeaders,
+ nsIOutputStream* aOutputStream) {
+ nsresult rv;
+ uint32_t writtenBytes = 0;
+
+ rv = aOutputStream->Write(FROM_LINE, strlen(FROM_LINE), &writtenBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = aOutputStream->Write(aHeaders.get(), aHeaders.Length(), &writtenBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv =
+ aOutputStream->Write(MSG_LINEBREAK, strlen(MSG_LINEBREAK), &writtenBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+ aHeaders.Truncate();
+
+ return NS_OK;
+}
+
+nsresult ImportMessageRunnable::HandleHeaderLine(const nsCString& aHeaderLine,
+ nsACString& aHeaders) {
+ aHeaders.Append(aHeaderLine);
+ aHeaders.AppendLiteral(MSG_LINEBREAK);
+
+ nsMsgMessageFlagType flag = 0;
+ if (IsBeckyStatusHeader(aHeaderLine) &&
+ ConvertBeckyStatusToMozillaStatus(aHeaderLine, &flag)) {
+ char* statusLine;
+ statusLine = PR_smprintf(X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, flag);
+ aHeaders.Append(statusLine);
+ PR_smprintf_free(statusLine);
+ aHeaders.AppendLiteral(X_MOZILLA_KEYWORDS);
+ }
+
+ return NS_OK;
+}
+
+nsresult ImportMessageRunnable::GetAttachmentFile(nsIFile* aMailboxFile,
+ const nsCString& aHeader,
+ nsIFile** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> attachmentFile;
+
+ rv = aMailboxFile->Clone(getter_AddRefs(attachmentFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = attachmentFile->Append(u"#Attach"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString nativeAttachmentPath;
+ rv = GetBeckyIncludeValue(aHeader, nativeAttachmentPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = attachmentFile->AppendRelativeNativePath(nativeAttachmentPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ attachmentFile->Exists(&exists);
+ if (!exists) return NS_ERROR_FILE_NOT_FOUND;
+
+ attachmentFile.forget(_retval);
+ return NS_OK;
+}
+
+nsresult ImportMessageRunnable::WriteAttachmentFile(
+ nsIFile* aMailboxFile, const nsCString& aHeader,
+ nsIOutputStream* aOutputStream) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> parentDirectory;
+ rv = aMailboxFile->GetParent(getter_AddRefs(parentDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> attachmentFile;
+ rv = GetAttachmentFile(parentDirectory, aHeader,
+ getter_AddRefs(attachmentFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), attachmentFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char buffer[FILE_IO_BUFFER_SIZE];
+ uint32_t readBytes = 0;
+ uint32_t writtenBytes = 0;
+ rv =
+ aOutputStream->Write(MSG_LINEBREAK, strlen(MSG_LINEBREAK), &writtenBytes);
+ while (NS_SUCCEEDED(inputStream->Read(buffer, sizeof(buffer), &readBytes)) &&
+ readBytes > 0) {
+ rv = aOutputStream->Write(buffer, readBytes, &writtenBytes);
+ if (NS_FAILED(rv)) break;
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP ImportMessageRunnable::Run() {
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ mResult = mFolder->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(mResult, NS_OK);
+
+ nsCOMPtr<nsILineInputStream> lineStream;
+ mResult = nsBeckyUtils::CreateLineInputStream(mMessageFile,
+ getter_AddRefs(lineStream));
+ NS_ENSURE_SUCCESS(mResult, NS_OK);
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ nsCOMPtr<nsIOutputStream> outputStream;
+ mResult = msgStore->GetNewMsgOutputStream(mFolder, getter_AddRefs(msgHdr),
+ getter_AddRefs(outputStream));
+ NS_ENSURE_SUCCESS(mResult, NS_OK);
+
+ bool inHeader = true;
+ bool more = true;
+ nsAutoCString headers;
+ while (NS_SUCCEEDED(mResult) && more) {
+ nsAutoCString line;
+ mResult = lineStream->ReadLine(line, &more);
+ if (NS_FAILED(mResult)) break;
+
+ if (inHeader) {
+ if (IsEndOfHeaders(line)) {
+ inHeader = false;
+ mResult = WriteHeaders(headers, outputStream);
+ } else {
+ mResult = HandleHeaderLine(line, headers);
+ }
+ } else if (IsEndOfMessage(line)) {
+ inHeader = true;
+ mResult = msgStore->FinishNewMessage(outputStream, msgHdr);
+ // outputStream is closed by FinishNewMessage().
+ outputStream = nullptr;
+ mResult = msgStore->GetNewMsgOutputStream(mFolder, getter_AddRefs(msgHdr),
+ getter_AddRefs(outputStream));
+ } else if (IsBeckyIncludeLine(line)) {
+ mResult = WriteAttachmentFile(mMessageFile, line, outputStream);
+ } else {
+ uint32_t writtenBytes = 0;
+ if (StringBeginsWith(line, ".."_ns))
+ line.Cut(0, 1);
+ else if (CheckHeaderKey(line, "From"))
+ line.Insert('>', 0);
+
+ line.AppendLiteral(MSG_LINEBREAK);
+ mResult = outputStream->Write(line.get(), line.Length(), &writtenBytes);
+ }
+ }
+
+ if (outputStream) {
+ // DiscardNewMessage() closes outputStream.
+ if (NS_FAILED(mResult))
+ msgStore->DiscardNewMessage(outputStream, msgHdr);
+ else
+ outputStream->Close(); /* No check? */
+ }
+
+ return NS_OK;
+}
+
+static nsresult ProxyImportMessage(nsIFile* aMessageFile,
+ nsIMsgFolder* aFolder) {
+ RefPtr<ImportMessageRunnable> importMessage =
+ new ImportMessageRunnable(aMessageFile, aFolder);
+ nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete(
+ "ProxyImportMessage"_ns, mozilla::GetMainThreadSerialEventTarget(),
+ do_AddRef(importMessage));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return importMessage->mResult;
+}
+
+nsresult nsBeckyMail::ImportMailFile(nsIFile* aMailFile,
+ nsIMsgFolder* aDestination) {
+ int64_t size;
+ aMailFile->GetFileSize(&size);
+ if (size == 0) return NS_OK;
+
+ return ProxyImportMessage(aMailFile, aDestination);
+}
+
+NS_IMETHODIMP
+nsBeckyMail::ImportMailbox(nsIImportMailboxDescriptor* aSource,
+ nsIMsgFolder* aDestination, char16_t** aErrorLog,
+ char16_t** aSuccessLog, bool* aFatalError) {
+ NS_ENSURE_ARG_POINTER(aSource);
+ NS_ENSURE_ARG_POINTER(aDestination);
+ NS_ENSURE_ARG_POINTER(aErrorLog);
+ NS_ENSURE_ARG_POINTER(aSuccessLog);
+ NS_ENSURE_ARG_POINTER(aFatalError);
+
+ mReadBytes = 0;
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> mailboxFolder;
+ rv = aSource->GetFile(getter_AddRefs(mailboxFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = ImportMailFile(mailboxFolder, aDestination);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t finalSize;
+ aSource->GetSize(&finalSize);
+ mReadBytes = finalSize;
+
+ nsAutoString name;
+ aSource->GetDisplayName(getter_Copies(name));
+
+ nsAutoString successMessage;
+ AutoTArray<nsString, 1> format = {name};
+ rv = nsBeckyStringBundle::FormatStringFromName("BeckyImportMailboxSuccess",
+ format, successMessage);
+ successMessage.AppendLiteral("\n");
+ *aSuccessLog = ToNewUnicode(successMessage);
+
+ return rv;
+}
+
+NS_IMETHODIMP
+nsBeckyMail::GetImportProgress(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = mReadBytes;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckyMail::TranslateFolderName(const nsAString& aFolderName,
+ nsAString& _retval) {
+ return nsBeckyUtils::TranslateFolderName(aFolderName, _retval);
+}
diff --git a/comm/mailnews/import/src/nsBeckyMail.h b/comm/mailnews/import/src/nsBeckyMail.h
new file mode 100644
index 0000000000..e0b445b1fe
--- /dev/null
+++ b/comm/mailnews/import/src/nsBeckyMail.h
@@ -0,0 +1,41 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 nsBeckyMail_h___
+#define nsBeckyMail_h___
+
+#include "nsIImportMail.h"
+
+class nsIFile;
+class nsIMsgFolder;
+
+class nsBeckyMail final : public nsIImportMail {
+ public:
+ nsBeckyMail();
+ static nsresult Create(nsIImportMail** aImport);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIMPORTMAIL
+
+ private:
+ virtual ~nsBeckyMail();
+
+ uint32_t mReadBytes;
+
+ nsresult CollectMailboxesInDirectory(
+ nsIFile* aDirectory, uint32_t aDepth,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& aCollected);
+ nsresult CollectMailboxesInFolderListFile(
+ nsIFile* aListFile, uint32_t aDepth,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& aCollected);
+ nsresult AppendMailboxDescriptor(
+ nsIFile* aEntry, const nsString& aName, uint32_t aDepth,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& aCollected);
+ nsresult ImportMailFile(nsIFile* aMailFile, nsIMsgFolder* aDestination);
+ nsresult CreateMailboxDescriptor(nsIImportMailboxDescriptor** aDescriptor);
+ nsresult GetMailboxName(nsIFile* aMailbox, nsAString& aName);
+};
+
+#endif /* nsBeckyMail_h___ */
diff --git a/comm/mailnews/import/src/nsBeckySettings.cpp b/comm/mailnews/import/src/nsBeckySettings.cpp
new file mode 100644
index 0000000000..9722a62007
--- /dev/null
+++ b/comm/mailnews/import/src/nsBeckySettings.cpp
@@ -0,0 +1,379 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "nsIMsgAccountManager.h"
+#include "nsServiceManagerUtils.h"
+#include "nsIINIParser.h"
+#include "nsISmtpService.h"
+#include "nsISmtpServer.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsNetUtil.h"
+#include "nsString.h"
+#include "msgCore.h"
+#include "nsBeckySettings.h"
+#include "nsBeckyStringBundle.h"
+#include "nsBeckyUtils.h"
+
+NS_IMPL_ISUPPORTS(nsBeckySettings, nsIImportSettings)
+
+nsresult nsBeckySettings::Create(nsIImportSettings** aImport) {
+ NS_ENSURE_ARG_POINTER(aImport);
+ NS_ADDREF(*aImport = new nsBeckySettings());
+ return NS_OK;
+}
+
+nsBeckySettings::nsBeckySettings() {}
+
+nsBeckySettings::~nsBeckySettings() {}
+
+NS_IMETHODIMP
+nsBeckySettings::AutoLocate(char16_t** aDescription, nsIFile** aLocation,
+ bool* _retval) {
+ NS_ENSURE_ARG_POINTER(aDescription);
+ NS_ENSURE_ARG_POINTER(aLocation);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *aDescription = nsBeckyStringBundle::GetStringByName("BeckyImportName");
+ *aLocation = nullptr;
+ *_retval = false;
+
+ nsCOMPtr<nsIFile> location;
+ nsresult rv =
+ nsBeckyUtils::GetDefaultMailboxINIFile(getter_AddRefs(location));
+ if (NS_FAILED(rv))
+ location = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ else
+ *_retval = true;
+
+ location.forget(aLocation);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsBeckySettings::SetLocation(nsIFile* aLocation) {
+ mLocation = aLocation;
+ return NS_OK;
+}
+
+nsresult nsBeckySettings::CreateParser() {
+ if (!mLocation) {
+ nsresult rv =
+ nsBeckyUtils::GetDefaultMailboxINIFile(getter_AddRefs(mLocation));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // nsIINIParser accepts only UTF-8 encoding, so we need to convert the file
+ // first.
+ nsresult rv;
+ rv = nsBeckyUtils::ConvertToUTF8File(mLocation,
+ getter_AddRefs(mConvertedFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return nsBeckyUtils::CreateINIParserForFile(mConvertedFile,
+ getter_AddRefs(mParser));
+}
+
+nsresult nsBeckySettings::CreateSmtpServer(const nsCString& aUserName,
+ const nsCString& aServerName,
+ nsISmtpServer** aServer,
+ bool* existing) {
+ nsresult rv;
+
+ nsCOMPtr<nsISmtpService> smtpService =
+ do_GetService("@mozilla.org/messengercompose/smtp;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISmtpServer> server;
+ rv = smtpService->FindServer(aUserName.get(), aServerName.get(),
+ getter_AddRefs(server));
+
+ if (NS_FAILED(rv) || !server) {
+ rv = smtpService->CreateServer(getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ server->SetHostname(aServerName);
+ server->SetUsername(aUserName);
+ *existing = false;
+ } else {
+ *existing = true;
+ }
+
+ server.forget(aServer);
+
+ return NS_OK;
+}
+
+nsresult nsBeckySettings::CreateIncomingServer(const nsCString& aUserName,
+ const nsCString& aServerName,
+ const nsCString& aProtocol,
+ nsIMsgIncomingServer** aServer) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ accountManager->FindServer(aUserName, aServerName, aProtocol, 0,
+ getter_AddRefs(incomingServer));
+
+ if (!incomingServer) {
+ rv = accountManager->CreateIncomingServer(aUserName, aServerName, aProtocol,
+ getter_AddRefs(incomingServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ incomingServer.forget(aServer);
+
+ return NS_OK;
+}
+
+nsresult nsBeckySettings::SetupSmtpServer(nsISmtpServer** aServer) {
+ nsresult rv;
+ nsAutoCString userName, serverName;
+
+ mParser->GetString("Account"_ns, "SMTPServer"_ns, serverName);
+ mParser->GetString("Account"_ns, "UserID"_ns, userName);
+
+ nsCOMPtr<nsISmtpServer> server;
+ bool existing = false;
+ rv =
+ CreateSmtpServer(userName, serverName, getter_AddRefs(server), &existing);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // If we already have an existing server, do not touch it's settings.
+ if (existing) {
+ server.forget(aServer);
+ return NS_OK;
+ }
+
+ nsAutoCString value;
+ rv = mParser->GetString("Account"_ns, "SMTPPort"_ns, value);
+ int32_t port = 25;
+ if (NS_SUCCEEDED(rv)) {
+ nsresult errorCode;
+ port = value.ToInteger(&errorCode, 10);
+ }
+ server->SetPort(port);
+
+ mParser->GetString("Account"_ns, "SSLSMTP"_ns, value);
+ if (value.EqualsLiteral("1")) server->SetSocketType(nsMsgSocketType::SSL);
+
+ mParser->GetString("Account"_ns, "SMTPAUTH"_ns, value);
+ if (value.EqualsLiteral("1")) {
+ mParser->GetString("Account"_ns, "SMTPAUTHMODE"_ns, value);
+ nsMsgAuthMethodValue authMethod = nsMsgAuthMethod::none;
+ if (value.EqualsLiteral("1")) {
+ authMethod = nsMsgAuthMethod::passwordEncrypted;
+ } else if (value.EqualsLiteral("2") || value.EqualsLiteral("4") ||
+ value.EqualsLiteral("6")) {
+ authMethod = nsMsgAuthMethod::passwordCleartext;
+ } else {
+ authMethod = nsMsgAuthMethod::anything;
+ }
+ server->SetAuthMethod(authMethod);
+ }
+
+ server.forget(aServer);
+
+ return NS_OK;
+}
+
+nsresult nsBeckySettings::SetPop3ServerProperties(
+ nsIMsgIncomingServer* aServer) {
+ nsCOMPtr<nsIPop3IncomingServer> pop3Server = do_QueryInterface(aServer);
+
+ nsAutoCString value;
+ mParser->GetString("Account"_ns, "POP3Auth"_ns,
+ value); // 0: plain, 1: APOP, 2: CRAM-MD5, 3: NTLM
+ nsMsgAuthMethodValue authMethod;
+ if (value.IsEmpty() || value.EqualsLiteral("0")) {
+ authMethod = nsMsgAuthMethod::passwordCleartext;
+ } else if (value.EqualsLiteral("1")) {
+ authMethod = nsMsgAuthMethod::old;
+ } else if (value.EqualsLiteral("2")) {
+ authMethod = nsMsgAuthMethod::passwordEncrypted;
+ } else if (value.EqualsLiteral("3")) {
+ authMethod = nsMsgAuthMethod::NTLM;
+ } else {
+ authMethod = nsMsgAuthMethod::none;
+ }
+ aServer->SetAuthMethod(authMethod);
+
+ mParser->GetString("Account"_ns, "LeaveServer"_ns, value);
+ if (value.EqualsLiteral("1")) {
+ pop3Server->SetLeaveMessagesOnServer(true);
+ nsresult rv = mParser->GetString("Account"_ns, "KeepDays"_ns, value);
+ if (NS_FAILED(rv)) return NS_OK;
+
+ nsresult errorCode;
+ int32_t leftDays = value.ToInteger(&errorCode, 10);
+ if (NS_SUCCEEDED(errorCode)) {
+ pop3Server->SetNumDaysToLeaveOnServer(leftDays);
+ pop3Server->SetDeleteByAgeFromServer(true);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult nsBeckySettings::SetupIncomingServer(nsIMsgIncomingServer** aServer) {
+ nsAutoCString value;
+ mParser->GetString("Account"_ns, "Protocol"_ns, value);
+ nsCString protocol;
+ if (value.EqualsLiteral("1")) {
+ protocol = "imap"_ns;
+ } else {
+ protocol = "pop3"_ns;
+ }
+
+ nsAutoCString userName, serverName;
+ mParser->GetString("Account"_ns, "MailServer"_ns, serverName);
+ mParser->GetString("Account"_ns, "UserID"_ns, userName);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = CreateIncomingServer(userName, serverName, protocol,
+ getter_AddRefs(server));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isSecure = false;
+ int32_t port = 0;
+ nsresult errorCode;
+ if (protocol.EqualsLiteral("pop3")) {
+ SetPop3ServerProperties(server);
+ rv = mParser->GetString("Account"_ns, "POP3Port"_ns, value);
+ if (NS_SUCCEEDED(rv))
+ port = value.ToInteger(&errorCode, 10);
+ else
+ port = 110;
+ mParser->GetString("Account"_ns, "SSLPOP"_ns, value);
+ if (value.EqualsLiteral("1")) isSecure = true;
+ } else if (protocol.EqualsLiteral("imap")) {
+ rv = mParser->GetString("Account"_ns, "IMAP4Port"_ns, value);
+ if (NS_SUCCEEDED(rv))
+ port = value.ToInteger(&errorCode, 10);
+ else
+ port = 143;
+ mParser->GetString("Account"_ns, "SSLIMAP"_ns, value);
+ if (value.EqualsLiteral("1")) isSecure = true;
+ }
+
+ server->SetPort(port);
+ if (isSecure) server->SetSocketType(nsMsgSocketType::SSL);
+
+ mParser->GetString("Account"_ns, "CheckInt"_ns, value);
+ if (value.EqualsLiteral("1")) server->SetDoBiff(true);
+ rv = mParser->GetString("Account"_ns, "CheckEvery"_ns, value);
+ if (NS_SUCCEEDED(rv)) {
+ int32_t minutes = value.ToInteger(&errorCode, 10);
+ if (NS_SUCCEEDED(errorCode)) server->SetBiffMinutes(minutes);
+ }
+
+ server.forget(aServer);
+
+ return NS_OK;
+}
+
+nsresult nsBeckySettings::CreateIdentity(nsIMsgIdentity** aIdentity) {
+ nsAutoCString email, fullName, identityName, bccAddress;
+
+ mParser->GetString("Account"_ns, "Name"_ns, identityName);
+ mParser->GetString("Account"_ns, "YourName"_ns, fullName);
+ mParser->GetString("Account"_ns, "MailAddress"_ns, email);
+ mParser->GetString("Account"_ns, "PermBcc"_ns, bccAddress);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv = accountManager->CreateIdentity(getter_AddRefs(identity));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ identity->SetLabel(NS_ConvertUTF8toUTF16(identityName));
+ identity->SetFullName(NS_ConvertUTF8toUTF16(fullName));
+ identity->SetEmail(email);
+ if (!bccAddress.IsEmpty()) {
+ identity->SetDoBcc(true);
+ identity->SetDoBccList(bccAddress);
+ }
+
+ identity.forget(aIdentity);
+
+ return NS_OK;
+}
+
+nsresult nsBeckySettings::CreateAccount(nsIMsgIdentity* aIdentity,
+ nsIMsgIncomingServer* aIncomingServer,
+ nsIMsgAccount** aAccount) {
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accountManager =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = accountManager->CreateAccount(getter_AddRefs(account));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = account->AddIdentity(aIdentity);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = account->SetIncomingServer(aIncomingServer);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ account.forget(aAccount);
+
+ return NS_OK;
+}
+
+nsresult nsBeckySettings::RemoveConvertedFile() {
+ if (mConvertedFile) {
+ bool exists;
+ mConvertedFile->Exists(&exists);
+ if (exists) mConvertedFile->Remove(false);
+ mConvertedFile = nullptr;
+ }
+ return NS_OK;
+}
+
+#define NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(expr, rv) \
+ if (NS_FAILED(expr)) { \
+ RemoveConvertedFile(); \
+ return rv; \
+ }
+
+NS_IMETHODIMP
+nsBeckySettings::Import(nsIMsgAccount** aLocalMailAccount, bool* _retval) {
+ NS_ENSURE_ARG_POINTER(aLocalMailAccount);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsresult rv = CreateParser();
+ NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv);
+
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ rv = SetupIncomingServer(getter_AddRefs(incomingServer));
+ NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv);
+
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ rv = SetupSmtpServer(getter_AddRefs(smtpServer));
+ NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv);
+
+ nsCOMPtr<nsIMsgIdentity> identity;
+ rv = CreateIdentity(getter_AddRefs(identity));
+ NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv);
+
+ nsAutoCString smtpKey;
+ smtpServer->GetKey(getter_Copies(smtpKey));
+ identity->SetSmtpServerKey(smtpKey);
+
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = CreateAccount(identity, incomingServer, getter_AddRefs(account));
+ NS_RETURN_IF_FAILED_WITH_REMOVE_CONVERTED_FILE(rv, rv);
+
+ RemoveConvertedFile();
+ if (aLocalMailAccount) account.forget(aLocalMailAccount);
+ *_retval = true;
+ return NS_OK;
+}
diff --git a/comm/mailnews/import/src/nsBeckySettings.h b/comm/mailnews/import/src/nsBeckySettings.h
new file mode 100644
index 0000000000..bbbe9fe268
--- /dev/null
+++ b/comm/mailnews/import/src/nsBeckySettings.h
@@ -0,0 +1,50 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 nsBeckySettings_h___
+#define nsBeckySettings_h___
+
+#include "nsIImportSettings.h"
+#include "nsIFile.h"
+#include "nsIINIParser.h"
+
+class nsIMsgIncomingServer;
+class nsIMsgIdentity;
+class nsISmtpServer;
+
+class nsBeckySettings final : public nsIImportSettings {
+ public:
+ nsBeckySettings();
+ static nsresult Create(nsIImportSettings** aImport);
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIMPORTSETTINGS
+
+ private:
+ virtual ~nsBeckySettings();
+
+ nsCOMPtr<nsIFile> mLocation;
+ nsCOMPtr<nsIFile> mConvertedFile;
+ nsCOMPtr<nsIINIParser> mParser;
+
+ nsresult CreateParser();
+ nsresult CreateIdentity(nsIMsgIdentity** aIdentity);
+ nsresult CreateAccount(nsIMsgIdentity* aIdentity,
+ nsIMsgIncomingServer* aIncomingServer,
+ nsIMsgAccount** aAccount);
+ nsresult CreateSmtpServer(const nsCString& aUserName,
+ const nsCString& aServerName,
+ nsISmtpServer** aServer, bool* existing);
+ nsresult CreateIncomingServer(const nsCString& aUserName,
+ const nsCString& aServerName,
+ const nsCString& aProtocol,
+ nsIMsgIncomingServer** aServer);
+ nsresult SetupIncomingServer(nsIMsgIncomingServer** aServer);
+ nsresult SetupSmtpServer(nsISmtpServer** aServer);
+ nsresult SetPop3ServerProperties(nsIMsgIncomingServer* aServer);
+ nsresult RemoveConvertedFile();
+};
+
+#endif /* nsBeckySettings_h___ */
diff --git a/comm/mailnews/import/src/nsBeckyStringBundle.cpp b/comm/mailnews/import/src/nsBeckyStringBundle.cpp
new file mode 100644
index 0000000000..1037fdafb2
--- /dev/null
+++ b/comm/mailnews/import/src/nsBeckyStringBundle.cpp
@@ -0,0 +1,55 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "prprf.h"
+#include "prmem.h"
+#include "nsCOMPtr.h"
+#include "nsIStringBundle.h"
+#include "nsServiceManagerUtils.h"
+#include "nsXPCOMCIDInternal.h"
+
+#include "nsBeckyStringBundle.h"
+
+#define BECKY_MESSAGES_URL \
+ "chrome://messenger/locale/beckyImportMsgs.properties"
+
+nsCOMPtr<nsIStringBundle> nsBeckyStringBundle::mBundle = nullptr;
+
+void nsBeckyStringBundle::GetStringBundle(void) {
+ if (mBundle) return;
+
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
+ if (NS_SUCCEEDED(rv) && bundleService)
+ rv = bundleService->CreateBundle(BECKY_MESSAGES_URL,
+ getter_AddRefs(mBundle));
+}
+
+void nsBeckyStringBundle::EnsureStringBundle(void) {
+ if (!mBundle) GetStringBundle();
+}
+
+char16_t* nsBeckyStringBundle::GetStringByName(const char* aName) {
+ EnsureStringBundle();
+
+ if (mBundle) {
+ nsAutoString string;
+ mBundle->GetStringFromName(aName, string);
+ return ToNewUnicode(string);
+ }
+
+ return nullptr;
+}
+
+nsresult nsBeckyStringBundle::FormatStringFromName(const char* name,
+ nsTArray<nsString>& params,
+ nsAString& _retval) {
+ EnsureStringBundle();
+
+ return mBundle->FormatStringFromName(name, params, _retval);
+}
+
+void nsBeckyStringBundle::Cleanup(void) { mBundle = nullptr; }
diff --git a/comm/mailnews/import/src/nsBeckyStringBundle.h b/comm/mailnews/import/src/nsBeckyStringBundle.h
new file mode 100644
index 0000000000..2b7045ebcc
--- /dev/null
+++ b/comm/mailnews/import/src/nsBeckyStringBundle.h
@@ -0,0 +1,32 @@
+/* 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 _nsBeckyStringBundle_H__
+#define _nsBeckyStringBundle_H__
+
+#include "nsString.h"
+
+class nsIStringBundle;
+
+class nsBeckyStringBundle final {
+ public:
+ static char16_t* GetStringByName(const char* name);
+ static nsresult FormatStringFromName(const char* name,
+ nsTArray<nsString>& params,
+ nsAString& _retval);
+ static void GetStringBundle(void);
+ static void EnsureStringBundle(void);
+ static void Cleanup(void);
+
+ private:
+ static nsCOMPtr<nsIStringBundle> mBundle;
+};
+
+#define BECKYIMPORT_NAME 2000
+#define BECKYIMPORT_DESCRIPTION 2001
+#define BECKYIMPORT_MAILBOX_SUCCESS 2002
+#define BECKYIMPORT_MAILBOX_BADPARAM 2003
+#define BECKYIMPORT_MAILBOX_CONVERTERROR 2004
+#define BECKYIMPORT_ADDRESS_SUCCESS 2005
+
+#endif /* _nsBeckyStringBundle_H__ */
diff --git a/comm/mailnews/import/src/nsBeckyUtils.cpp b/comm/mailnews/import/src/nsBeckyUtils.cpp
new file mode 100644
index 0000000000..ecb1c35a53
--- /dev/null
+++ b/comm/mailnews/import/src/nsBeckyUtils.cpp
@@ -0,0 +1,302 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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 "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsIDirectoryEnumerator.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsString.h"
+#include "nsMsgI18N.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIInputStream.h"
+#include "nsIOutputStream.h"
+#include "nsILineInputStream.h"
+#include "nsIConverterInputStream.h"
+#include "nsIConverterOutputStream.h"
+#include "nsMsgI18N.h"
+#include "nsNetUtil.h"
+#include "nsIINIParser.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsMsgUtils.h"
+#include "msgCore.h"
+#include "nsIImportMail.h"
+#include "nsThreadUtils.h"
+
+#include "nsBeckyUtils.h"
+#include "SpecialSystemDirectory.h"
+
+nsresult nsBeckyUtils::FindUserDirectoryOnWindows7(nsIFile** aLocation) {
+ NS_ENSURE_ARG_POINTER(aLocation);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> directory;
+ rv = GetSpecialSystemDirectory(Win_Documents, getter_AddRefs(directory));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = directory->AppendNative("Becky"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ rv = directory->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) return NS_ERROR_FILE_NOT_FOUND;
+
+ bool isDirectory = false;
+ rv = directory->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isDirectory) return NS_ERROR_FILE_NOT_FOUND;
+
+ directory.forget(aLocation);
+ return NS_OK;
+}
+
+nsresult nsBeckyUtils::FindUserDirectoryOnWindowsXP(nsIFile** aLocation) {
+ NS_ENSURE_ARG_POINTER(aLocation);
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> directory =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = directory->InitWithPath(u"C:\\Becky!"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists = false;
+ rv = directory->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) return NS_ERROR_FILE_NOT_FOUND;
+
+ bool isDirectory = false;
+ rv = directory->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isDirectory) return NS_ERROR_FILE_NOT_FOUND;
+
+ nsCOMPtr<nsIDirectoryEnumerator> entries;
+ rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&more)) && more) {
+ nsCOMPtr<nsIFile> file;
+ rv = entries->GetNextFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isDirectory = false;
+ rv = file->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isDirectory) {
+ file.forget(aLocation);
+ return NS_OK;
+ }
+ }
+
+ directory.forget(aLocation);
+ return NS_OK;
+}
+
+nsresult nsBeckyUtils::FindUserDirectory(nsIFile** aLocation) {
+ nsresult rv = FindUserDirectoryOnWindows7(aLocation);
+ if (rv == NS_ERROR_FILE_NOT_FOUND) {
+ rv = FindUserDirectoryOnWindowsXP(aLocation);
+ }
+ return rv;
+}
+
+nsresult nsBeckyUtils::ConvertNativeStringToUTF8(const nsACString& aOriginal,
+ nsACString& _retval) {
+ nsresult rv;
+ nsAutoString unicodeString;
+ rv = NS_CopyNativeToUnicode(aOriginal, unicodeString);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ CopyUTF16toUTF8(unicodeString, _retval);
+ return NS_OK;
+}
+
+nsresult nsBeckyUtils::CreateLineInputStream(nsIFile* aFile,
+ nsILineInputStream** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return CallQueryInterface(inputStream, _retval);
+}
+
+nsresult nsBeckyUtils::GetFolderListFile(nsIFile* aLocation,
+ nsIFile** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> folderListFile;
+ rv = aLocation->Clone(getter_AddRefs(folderListFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = folderListFile->Append(u"Folder.lst"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = folderListFile->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!exists) return NS_ERROR_FILE_NOT_FOUND;
+
+ folderListFile.forget(_retval);
+ return NS_OK;
+}
+
+nsresult nsBeckyUtils::GetDefaultFolderName(nsIFile* aFolderListFile,
+ nsACString& name) {
+ nsresult rv;
+ nsCOMPtr<nsILineInputStream> lineStream;
+ rv = CreateLineInputStream(aFolderListFile, getter_AddRefs(lineStream));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ rv = lineStream->ReadLine(name, &more);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return NS_OK;
+}
+
+nsresult nsBeckyUtils::GetDefaultMailboxDirectory(nsIFile** _retval) {
+ nsCOMPtr<nsIFile> userDirectory;
+ nsresult rv = FindUserDirectory(getter_AddRefs(userDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> folderListFile;
+ rv = GetFolderListFile(userDirectory, getter_AddRefs(folderListFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString defaultFolderName;
+ rv = GetDefaultFolderName(folderListFile, defaultFolderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = userDirectory->AppendNative(defaultFolderName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool exists;
+ rv = userDirectory->Exists(&exists);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!exists) return NS_ERROR_FILE_NOT_FOUND;
+
+ bool isDirectory = false;
+ rv = userDirectory->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!isDirectory) return NS_ERROR_FILE_NOT_FOUND;
+
+ userDirectory.forget(_retval);
+ return NS_OK;
+}
+
+nsresult nsBeckyUtils::GetDefaultMailboxINIFile(nsIFile** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> mailboxDirectory;
+ rv = GetDefaultMailboxDirectory(getter_AddRefs(mailboxDirectory));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return GetMailboxINIFile(mailboxDirectory, _retval);
+}
+
+nsresult nsBeckyUtils::GetMailboxINIFile(nsIFile* aDirectory,
+ nsIFile** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> target;
+ rv = aDirectory->Clone(getter_AddRefs(target));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = target->Append(u"Mailbox.ini"_ns);
+ NS_ENSURE_SUCCESS(rv, rv);
+ bool exists;
+ rv = target->Exists(&exists);
+ if (!exists) return NS_ERROR_FILE_NOT_FOUND;
+
+ target.forget(_retval);
+ return NS_OK;
+}
+
+nsresult nsBeckyUtils::CreateINIParserForFile(nsIFile* aFile,
+ nsIINIParser** aParser) {
+ nsresult rv;
+ nsCOMPtr<nsIINIParserFactory> factory =
+ do_GetService("@mozilla.org/xpcom/ini-processor-factory;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return factory->CreateINIParser(aFile, aParser);
+}
+
+nsresult nsBeckyUtils::GetMailboxNameFromINIFile(nsIFile* aFile,
+ nsCString& aName) {
+ nsresult rv;
+ nsCOMPtr<nsIINIParser> parser;
+ rv = CreateINIParserForFile(aFile, getter_AddRefs(parser));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return parser->GetString("Account"_ns, "Name"_ns, aName);
+}
+
+nsresult nsBeckyUtils::ConvertToUTF8File(nsIFile* aSourceFile,
+ nsIFile** _retval) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> convertedFile;
+ rv = GetSpecialDirectoryWithFileName(NS_OS_TEMP_DIR,
+ "thunderbird-becky-import",
+ getter_AddRefs(convertedFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = convertedFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIInputStream> source;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(source), aSourceFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString sourceCharset;
+ rv = MsgDetectCharsetFromFile(aSourceFile, sourceCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIOutputStream> destination;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(destination), convertedFile);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const uint32_t kBlock = 8192;
+
+ nsCOMPtr<nsIConverterInputStream> convertedInput =
+ do_CreateInstance("@mozilla.org/intl/converter-input-stream;1");
+ convertedInput->Init(source, sourceCharset.get(), kBlock, 0x0000);
+
+ nsCOMPtr<nsIConverterOutputStream> convertedOutput =
+ do_CreateInstance("@mozilla.org/intl/converter-output-stream;1");
+ convertedOutput->Init(destination, "UTF-8");
+
+ char16_t* line = (char16_t*)moz_xmalloc(kBlock);
+ uint32_t readBytes = kBlock;
+ bool writtenBytes;
+ while (readBytes == kBlock) {
+ rv = convertedInput->Read(line, kBlock, &readBytes);
+ rv = convertedOutput->Write(readBytes, line, &writtenBytes);
+ }
+ convertedOutput->Close();
+ convertedInput->Close();
+
+ convertedFile.forget(_retval);
+ return NS_OK;
+}
+
+nsresult nsBeckyUtils::TranslateFolderName(const nsAString& aFolderName,
+ nsAString& _retval) {
+ if (aFolderName.LowerCaseEqualsLiteral("!trash"))
+ _retval = NS_LITERAL_STRING_FROM_CSTRING(kDestTrashFolderName);
+ else if (aFolderName.LowerCaseEqualsLiteral("!!!!inbox"))
+ _retval = NS_LITERAL_STRING_FROM_CSTRING(kDestInboxFolderName);
+ else if (aFolderName.LowerCaseEqualsLiteral("!!!!outbox"))
+ _retval = NS_LITERAL_STRING_FROM_CSTRING(kDestSentFolderName);
+ else if (aFolderName.LowerCaseEqualsLiteral("!!!!unsent"))
+ _retval = NS_LITERAL_STRING_FROM_CSTRING(kDestUnsentMessagesFolderName);
+ else
+ _retval = aFolderName;
+
+ return NS_OK;
+}
diff --git a/comm/mailnews/import/src/nsBeckyUtils.h b/comm/mailnews/import/src/nsBeckyUtils.h
new file mode 100644
index 0000000000..9a0529b5dc
--- /dev/null
+++ b/comm/mailnews/import/src/nsBeckyUtils.h
@@ -0,0 +1,35 @@
+/* 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 _nsBeckyUtils_H__
+#define _nsBeckyUtils_H__
+
+#include "nsString.h"
+class nsIFile;
+class nsILineInputStream;
+class nsIINIParser;
+
+class nsBeckyUtils final {
+ public:
+ static nsresult FindUserDirectoryOnWindows7(nsIFile** aLocation);
+ static nsresult FindUserDirectoryOnWindowsXP(nsIFile** aLocation);
+ static nsresult FindUserDirectory(nsIFile** aFile);
+ static nsresult ConvertNativeStringToUTF8(const nsACString& aOriginal,
+ nsACString& _retval);
+ static nsresult CreateLineInputStream(nsIFile* aFile,
+ nsILineInputStream** _retval);
+ static nsresult GetDefaultMailboxDirectory(nsIFile** _retval);
+ static nsresult GetFolderListFile(nsIFile* aLocation, nsIFile** _retval);
+ static nsresult GetDefaultFolderName(nsIFile* aFolderListFile,
+ nsACString& name);
+ static nsresult GetDefaultMailboxINIFile(nsIFile** _retval);
+ static nsresult GetMailboxINIFile(nsIFile* aDirectory, nsIFile** _retval);
+ static nsresult CreateINIParserForFile(nsIFile* aFile,
+ nsIINIParser** aParser);
+ static nsresult GetMailboxNameFromINIFile(nsIFile* aFile, nsCString& aName);
+ static nsresult ConvertToUTF8File(nsIFile* aSourceFile, nsIFile** _retval);
+ static nsresult TranslateFolderName(const nsAString& aFolderName,
+ nsAString& _retval);
+};
+
+#endif /* _nsBeckyUtils_H__ */
diff --git a/comm/mailnews/import/src/nsEmlxHelperUtils.h b/comm/mailnews/import/src/nsEmlxHelperUtils.h
new file mode 100644
index 0000000000..a7e0e69f11
--- /dev/null
+++ b/comm/mailnews/import/src/nsEmlxHelperUtils.h
@@ -0,0 +1,61 @@
+/* -*- 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 nsEmlxHelperUtils_h___
+#define nsEmlxHelperUtils_h___
+
+#include "nscore.h"
+#include "nsString.h"
+
+class nsIOutputStream;
+class nsIFile;
+
+class nsEmlxHelperUtils {
+ /* All emlx messages have a "flags" number in the metadata.
+ These are the masks to decode that, found via
+ http://jwz.livejournal.com/505711.html */
+ enum EmlxMetadataMask {
+ kRead = 1 << 0, // read
+ // 1 << 1, // deleted
+ kAnswered = 1 << 2, // answered
+ // 1 << 3, // encrypted
+ kFlagged = 1 << 4, // flagged
+ // 1 << 5, // recent
+ // 1 << 6, // draft
+ // 1 << 7, // initial (no longer used)
+ kForwarded = 1 << 8, // forwarded
+ // 1 << 9, // redirected
+ // 3F << 10, // attachment count (6 bits)
+ // 7F << 16, // priority level (7 bits)
+ // 1 << 23, // signed
+ // 1 << 24, // is junk
+ // 1 << 25, // is not junk
+ // 1 << 26, // font size delta 7 (3 bits)
+ // 1 << 29, // junk mail level recorded
+ // 1 << 30, // highlight text in toc
+ // 1 << 31 // (unused)
+ };
+
+ // This method will scan the raw EMLX message buffer for "dangerous" so-called
+ // "From-lines" that we need to escape. If it needs to modify any lines, it
+ // will return a non-NULL aOutBuffer. If aOutBuffer is NULL, no modification
+ // needed to be made.
+ static nsresult ConvertToMboxRD(const char* aMessageBufferStart,
+ const char* aMessageBufferEnd,
+ nsCString& aOutBuffer);
+
+ // returns an int representing the X-Mozilla-Status flags set (e.g. "read",
+ // "flagged") converted from EMLX flags.
+ static nsresult ConvertToMozillaStatusFlags(const char* aXMLBufferStart,
+ const char* aXMLBufferEnd,
+ uint32_t* aMozillaStatusFlags);
+
+ public:
+ // add an .emlx message to the mbox output
+ static nsresult AddEmlxMessageToStream(nsIFile* aEmlxFile,
+ nsIOutputStream* aOutoutStream);
+};
+
+#endif // nsEmlxHelperUtils_h___
diff --git a/comm/mailnews/import/src/nsEmlxHelperUtils.mm b/comm/mailnews/import/src/nsEmlxHelperUtils.mm
new file mode 100644
index 0000000000..a0c5aaaee2
--- /dev/null
+++ b/comm/mailnews/import/src/nsEmlxHelperUtils.mm
@@ -0,0 +1,230 @@
+/* -*- 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 "nsEmlxHelperUtils.h"
+#include "nsIOutputStream.h"
+#include "nsNetUtil.h"
+#include "nsCOMPtr.h"
+#include "nsObjCExceptions.h"
+#include "nsMsgMessageFlags.h"
+#include "nsMsgLocalFolderHdrs.h"
+#include "msgCore.h"
+#include "nsTArray.h"
+#include "nsAppleMailImport.h"
+#include "prprf.h"
+#include "nsIFile.h"
+
+#import <Cocoa/Cocoa.h>
+
+nsresult nsEmlxHelperUtils::ConvertToMozillaStatusFlags(const char* aXMLBufferStart,
+ const char* aXMLBufferEnd,
+ uint32_t* aMozillaStatusFlags) {
+ // create a NSData wrapper around the buffer, so we can use the Cocoa call below
+ NSData* metadata = [[[NSData alloc] initWithBytesNoCopy:(void*)aXMLBufferStart
+ length:(aXMLBufferEnd - aXMLBufferStart)
+ freeWhenDone:NO] autorelease];
+
+ // get the XML data as a dictionary
+ NSPropertyListFormat format;
+ id plist = [NSPropertyListSerialization propertyListWithData:metadata
+ options:NSPropertyListImmutable
+ format:&format
+ error:NULL];
+
+ if (!plist) return NS_ERROR_FAILURE;
+
+ // find the <flags>...</flags> value and convert to int
+ const uint32_t emlxMessageFlags = [[(NSDictionary*)plist objectForKey:@"flags"] intValue];
+
+ if (emlxMessageFlags == 0) return NS_ERROR_FAILURE;
+
+ if (emlxMessageFlags & nsEmlxHelperUtils::kRead) *aMozillaStatusFlags |= nsMsgMessageFlags::Read;
+ if (emlxMessageFlags & nsEmlxHelperUtils::kForwarded)
+ *aMozillaStatusFlags |= nsMsgMessageFlags::Forwarded;
+ if (emlxMessageFlags & nsEmlxHelperUtils::kAnswered)
+ *aMozillaStatusFlags |= nsMsgMessageFlags::Replied;
+ if (emlxMessageFlags & nsEmlxHelperUtils::kFlagged)
+ *aMozillaStatusFlags |= nsMsgMessageFlags::Marked;
+
+ return NS_OK;
+}
+
+nsresult nsEmlxHelperUtils::ConvertToMboxRD(const char* aMessageBufferStart,
+ const char* aMessageBufferEnd, nsCString& aOutBuffer) {
+ nsTArray<const char*> foundFromLines;
+
+ const char* cur = aMessageBufferStart;
+ while (cur < aMessageBufferEnd) {
+ const char* foundFromStr = strnstr(cur, "From ", aMessageBufferEnd - cur);
+
+ if (foundFromStr) {
+ // skip all prepending '>' chars
+ const char* fromLineStart = foundFromStr;
+ while (fromLineStart-- >= aMessageBufferStart) {
+ if (*fromLineStart == '\n' || fromLineStart == aMessageBufferStart) {
+ if (fromLineStart > aMessageBufferStart) fromLineStart++;
+ foundFromLines.AppendElement(fromLineStart);
+ break;
+ } else if (*fromLineStart != '>')
+ break;
+ }
+
+ // advance past the last found From string.
+ cur = foundFromStr + 5;
+
+ // look for more From lines.
+ continue;
+ }
+
+ break;
+ }
+
+ // go through foundFromLines
+ if (foundFromLines.Length()) {
+ const char* chunkStart = aMessageBufferStart;
+ for (unsigned i = 0; i < foundFromLines.Length(); ++i) {
+ aOutBuffer.Append(chunkStart, (foundFromLines[i] - chunkStart));
+ aOutBuffer.Append(">"_ns);
+
+ chunkStart = foundFromLines[i];
+ }
+ aOutBuffer.Append(chunkStart, (aMessageBufferEnd - chunkStart));
+ }
+
+ return NS_OK;
+}
+
+nsresult nsEmlxHelperUtils::AddEmlxMessageToStream(nsIFile* aMessage, nsIOutputStream* aOut) {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ // needed to be sure autoreleased objects are released too, which they might not
+ // in a C++ environment where the main event loop has no autorelease pool (e.g on a XPCOM thread)
+ NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ nsAutoCString path;
+ aMessage->GetNativePath(path);
+
+ NSData* data = [NSData dataWithContentsOfFile:[NSString stringWithUTF8String:path.get()]];
+ if (!data) {
+ [pool release];
+ return NS_ERROR_FAILURE;
+ }
+
+ char* startOfMessageData = NULL;
+ uint32_t actualBytesWritten = 0;
+
+ // The anatomy of an EMLX file:
+ //
+ // -------------------------------
+ // < A number describing how many bytes ahead there is message data >
+ // < Message data >
+ // < XML metadata for this message >
+ // -------------------------------
+
+ // read the first line of the emlx file, which is a number of how many bytes ahead the actual
+ // message data is.
+ uint64_t numberOfBytesToRead = strtol((char*)[data bytes], &startOfMessageData, 10);
+ if (numberOfBytesToRead <= 0 || !startOfMessageData) {
+ [pool release];
+ return NS_ERROR_FAILURE;
+ }
+
+ // skip whitespace
+ while (*startOfMessageData == ' ' || *startOfMessageData == '\n' || *startOfMessageData == '\r' ||
+ *startOfMessageData == '\t')
+ ++startOfMessageData;
+
+ constexpr auto kBogusFromLine = "From \n"_ns;
+ constexpr auto kEndOfMessage = "\n\n"_ns;
+
+ // write the bogus "From " line which is a magic separator in the mbox format
+ rv = aOut->Write(kBogusFromLine.get(), kBogusFromLine.Length(), &actualBytesWritten);
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ // now read the XML metadata, so we can extract info like which flags (read? replied? flagged?
+ // etc) this message has.
+ const char* startOfXMLMetadata = startOfMessageData + numberOfBytesToRead;
+ const char* endOfXMLMetadata = (char*)[data bytes] + [data length];
+
+ uint32_t x_mozilla_flags = 0;
+ ConvertToMozillaStatusFlags(startOfXMLMetadata, endOfXMLMetadata, &x_mozilla_flags);
+
+ // write the X-Mozilla-Status header according to which flags we've gathered above.
+ uint32_t dummyRv;
+ nsAutoCString buf(PR_smprintf(X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, x_mozilla_flags));
+ NS_ASSERTION(!buf.IsEmpty(), "printf error with X-Mozilla-Status header");
+ if (buf.IsEmpty()) {
+ [pool release];
+ return rv;
+ }
+
+ rv = aOut->Write(buf.get(), buf.Length(), &dummyRv);
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ // write out X-Mozilla-Keywords header as well to reserve some space for it
+ // in the mbox file.
+ rv = aOut->Write(X_MOZILLA_KEYWORDS, X_MOZILLA_KEYWORDS_LEN, &dummyRv);
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ // write out empty X-Mozilla_status2 header
+ buf.Adopt(PR_smprintf(X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, 0));
+ NS_ASSERTION(!buf.IsEmpty(), "printf error with X-Mozilla-Status2 header");
+ if (buf.IsEmpty()) {
+ [pool release];
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ rv = aOut->Write(buf.get(), buf.Length(), &dummyRv);
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ // do any conversion needed for the mbox data to be valid mboxrd.
+ nsCString convertedData;
+ rv = ConvertToMboxRD(startOfMessageData, (startOfMessageData + numberOfBytesToRead),
+ convertedData);
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ // write the actual message data.
+ if (convertedData.IsEmpty())
+ rv = aOut->Write(startOfMessageData, (uint32_t)numberOfBytesToRead, &actualBytesWritten);
+ else {
+ IMPORT_LOG1("Escaped From-lines in %s!", path.get());
+ rv = aOut->Write(convertedData.get(), convertedData.Length(), &actualBytesWritten);
+ }
+
+ if (NS_FAILED(rv)) {
+ [pool release];
+ return rv;
+ }
+
+ NS_ASSERTION(actualBytesWritten ==
+ (convertedData.IsEmpty() ? numberOfBytesToRead : convertedData.Length()),
+ "Didn't write as many bytes as expected for .emlx file?");
+
+ // add newlines to denote the end of this message in the mbox
+ rv = aOut->Write(kEndOfMessage.get(), kEndOfMessage.Length(), &actualBytesWritten);
+
+ [pool release];
+
+ return rv;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
diff --git a/comm/mailnews/import/src/nsImportABDescriptor.cpp b/comm/mailnews/import/src/nsImportABDescriptor.cpp
new file mode 100644
index 0000000000..983f1c6ffb
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportABDescriptor.cpp
@@ -0,0 +1,19 @@
+/* -*- 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 "nscore.h"
+#include "nsImportABDescriptor.h"
+
+////////////////////////////////////////////////////////////////////////
+
+nsresult nsImportABDescriptor::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsImportABDescriptor> it = new nsImportABDescriptor();
+ return it->QueryInterface(aIID, aResult);
+}
+
+NS_IMPL_ISUPPORTS(nsImportABDescriptor, nsIImportABDescriptor)
+
+nsImportABDescriptor::nsImportABDescriptor()
+ : mId(0), mRef(0), mSize(0), mImport(true) {}
diff --git a/comm/mailnews/import/src/nsImportABDescriptor.h b/comm/mailnews/import/src/nsImportABDescriptor.h
new file mode 100644
index 0000000000..681ed8e83f
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportABDescriptor.h
@@ -0,0 +1,100 @@
+/* -*- 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 nsImportABDescriptor_h___
+#define nsImportABDescriptor_h___
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsString.h"
+#include "nsIImportABDescriptor.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+
+////////////////////////////////////////////////////////////////////////
+
+class nsImportABDescriptor : public nsIImportABDescriptor {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD GetIdentifier(uint32_t* pIdentifier) override {
+ *pIdentifier = mId;
+ return NS_OK;
+ }
+ NS_IMETHOD SetIdentifier(uint32_t ident) override {
+ mId = ident;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetRef(uint32_t* pRef) override {
+ *pRef = mRef;
+ return NS_OK;
+ }
+ NS_IMETHOD SetRef(uint32_t ref) override {
+ mRef = ref;
+ return NS_OK;
+ }
+
+ /* attribute unsigned long size; */
+ NS_IMETHOD GetSize(uint32_t* pSize) override {
+ *pSize = mSize;
+ return NS_OK;
+ }
+ NS_IMETHOD SetSize(uint32_t theSize) override {
+ mSize = theSize;
+ return NS_OK;
+ }
+
+ /* attribute AString displayName; */
+ NS_IMETHOD GetPreferredName(nsAString& aName) override {
+ aName = mDisplayName;
+ return NS_OK;
+ }
+ NS_IMETHOD SetPreferredName(const nsAString& aName) override {
+ mDisplayName = aName;
+ return NS_OK;
+ }
+
+ /* readonly attribute nsIFile fileSpec; */
+ NS_IMETHOD GetAbFile(nsIFile** aFile) override {
+ if (!mFile) return NS_ERROR_NULL_POINTER;
+
+ return mFile->Clone(aFile);
+ }
+
+ NS_IMETHOD SetAbFile(nsIFile* aFile) override {
+ if (!aFile) {
+ mFile = nullptr;
+ return NS_OK;
+ }
+
+ return aFile->Clone(getter_AddRefs(mFile));
+ }
+
+ /* attribute boolean import; */
+ NS_IMETHOD GetImport(bool* pImport) override {
+ *pImport = mImport;
+ return NS_OK;
+ }
+ NS_IMETHOD SetImport(bool doImport) override {
+ mImport = doImport;
+ return NS_OK;
+ }
+
+ nsImportABDescriptor();
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ private:
+ virtual ~nsImportABDescriptor() {}
+ uint32_t mId; // used by creator of the structure
+ uint32_t mRef; // depth in the hierarchy
+ nsString mDisplayName; // name of this mailbox
+ nsCOMPtr<nsIFile> mFile; // source file (if applicable)
+ uint32_t mSize; // size
+ bool mImport; // import it or not?
+};
+
+#endif
diff --git a/comm/mailnews/import/src/nsImportAddressBooks.cpp b/comm/mailnews/import/src/nsImportAddressBooks.cpp
new file mode 100644
index 0000000000..b39e7c68ef
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportAddressBooks.cpp
@@ -0,0 +1,571 @@
+/* -*- 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 "nsImportAddressBooks.h"
+
+#include "plstr.h"
+#include "nsIImportService.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIImportABDescriptor.h"
+#include "nsIAbManager.h"
+#include "nsImportStringBundle.h"
+#include "nsTextFormatter.h"
+#include "msgCore.h"
+#include "ImportDebug.h"
+
+nsresult NS_NewGenericAddressBooks(nsIImportGeneric** aImportGeneric) {
+ NS_ASSERTION(aImportGeneric != nullptr, "null ptr");
+ if (!aImportGeneric) return NS_ERROR_NULL_POINTER;
+
+ RefPtr<nsImportGenericAddressBooks> pGen = new nsImportGenericAddressBooks();
+ return pGen->QueryInterface(NS_GET_IID(nsIImportGeneric),
+ (void**)aImportGeneric);
+}
+
+nsImportGenericAddressBooks::nsImportGenericAddressBooks() {
+ m_totalSize = 0;
+ m_doImport = false;
+ m_pThreadData = nullptr;
+
+ m_autoFind = false;
+ m_description = nullptr;
+ m_gotLocation = false;
+ m_found = false;
+ m_userVerify = false;
+
+ nsImportStringBundle::GetStringBundle(IMPORT_MSGS_URL,
+ getter_AddRefs(m_stringBundle));
+}
+
+nsImportGenericAddressBooks::~nsImportGenericAddressBooks() {
+ if (m_description) free(m_description);
+}
+
+NS_IMPL_ISUPPORTS(nsImportGenericAddressBooks, nsIImportGeneric)
+
+NS_IMETHODIMP nsImportGenericAddressBooks::GetData(const char* dataId,
+ nsISupports** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ nsresult rv;
+ *_retval = nullptr;
+ if (!PL_strcasecmp(dataId, "addressInterface")) {
+ NS_IF_ADDREF(*_retval = m_pInterface);
+ }
+
+ if (!PL_strcasecmp(dataId, "addressLocation")) {
+ if (!m_pLocation) GetDefaultLocation();
+ NS_IF_ADDREF(*_retval = m_pLocation);
+ }
+
+ if (!PL_strcasecmp(dataId, "addressDestination")) {
+ if (!m_pDestinationUri.IsEmpty()) {
+ nsCOMPtr<nsISupportsCString> abString =
+ do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ abString->SetData(m_pDestinationUri);
+ abString.forget(_retval);
+ }
+ }
+
+ if (!PL_strcasecmp(dataId, "fieldMap")) {
+ if (m_pFieldMap) {
+ NS_ADDREF(*_retval = m_pFieldMap);
+ } else {
+ if (m_pInterface && m_pLocation) {
+ bool needsIt = false;
+ m_pInterface->GetNeedsFieldMap(m_pLocation, &needsIt);
+ if (needsIt) {
+ GetDefaultFieldMap();
+ if (m_pFieldMap) {
+ NS_ADDREF(*_retval = m_pFieldMap);
+ }
+ }
+ }
+ }
+ }
+
+ if (!PL_strncasecmp(dataId, "sampleData-", 11)) {
+ // extra the record number
+ const char* pNum = dataId + 11;
+ int32_t rNum = 0;
+ while (*pNum) {
+ rNum *= 10;
+ rNum += (*pNum - '0');
+ pNum++;
+ }
+ IMPORT_LOG1("Requesting sample data #: %ld\n", (long)rNum);
+ if (m_pInterface) {
+ nsCOMPtr<nsISupportsString> data =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+ char16_t* pData = nullptr;
+ bool found = false;
+ rv = m_pInterface->GetSampleData(rNum, &found, &pData);
+ if (NS_FAILED(rv)) return rv;
+ if (found) {
+ data->SetData(nsDependentString(pData));
+ data.forget(_retval);
+ }
+ free(pData);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportGenericAddressBooks::SetData(const char* dataId,
+ nsISupports* item) {
+ NS_ASSERTION(dataId != nullptr, "null ptr");
+ if (!dataId) return NS_ERROR_NULL_POINTER;
+
+ if (!PL_strcasecmp(dataId, "addressInterface")) {
+ m_pInterface = nullptr;
+ if (item) m_pInterface = do_QueryInterface(item);
+ }
+
+ if (!PL_strcasecmp(dataId, "addressLocation")) {
+ m_pLocation = nullptr;
+
+ if (item) {
+ nsresult rv;
+ m_pLocation = do_QueryInterface(item, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ if (m_pInterface) m_pInterface->SetSampleLocation(m_pLocation);
+ }
+
+ if (!PL_strcasecmp(dataId, "addressDestination")) {
+ if (item) {
+ nsCOMPtr<nsISupportsCString> abString = do_QueryInterface(item);
+ if (abString) {
+ abString->GetData(m_pDestinationUri);
+ }
+ }
+ }
+
+ if (!PL_strcasecmp(dataId, "fieldMap")) {
+ m_pFieldMap = nullptr;
+ if (item) m_pFieldMap = do_QueryInterface(item);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportGenericAddressBooks::GetStatus(const char* statusKind,
+ int32_t* _retval) {
+ NS_ASSERTION(statusKind != nullptr, "null ptr");
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!statusKind || !_retval) return NS_ERROR_NULL_POINTER;
+
+ *_retval = 0;
+
+ if (!PL_strcasecmp(statusKind, "isInstalled")) {
+ GetDefaultLocation();
+ *_retval = (int32_t)m_found;
+ }
+
+ if (!PL_strcasecmp(statusKind, "canUserSetLocation")) {
+ GetDefaultLocation();
+ *_retval = (int32_t)m_userVerify;
+ }
+
+ if (!PL_strcasecmp(statusKind, "autoFind")) {
+ GetDefaultLocation();
+ *_retval = (int32_t)m_autoFind;
+ }
+
+ if (!PL_strcasecmp(statusKind, "supportsMultiple")) {
+ bool multi = false;
+ if (m_pInterface) m_pInterface->GetSupportsMultiple(&multi);
+ *_retval = (int32_t)multi;
+ }
+
+ if (!PL_strcasecmp(statusKind, "needsFieldMap")) {
+ bool needs = false;
+ if (m_pInterface && m_pLocation)
+ m_pInterface->GetNeedsFieldMap(m_pLocation, &needs);
+ *_retval = (int32_t)needs;
+ }
+
+ return NS_OK;
+}
+
+void nsImportGenericAddressBooks::GetDefaultLocation(void) {
+ if (!m_pInterface) return;
+
+ if ((m_pLocation && m_gotLocation) || m_autoFind) return;
+
+ if (m_description) free(m_description);
+ m_description = nullptr;
+ m_pInterface->GetAutoFind(&m_description, &m_autoFind);
+ m_gotLocation = true;
+ if (m_autoFind) {
+ m_found = true;
+ m_userVerify = false;
+ return;
+ }
+
+ nsCOMPtr<nsIFile> pLoc;
+ m_pInterface->GetDefaultLocation(getter_AddRefs(pLoc), &m_found,
+ &m_userVerify);
+ if (!m_pLocation) m_pLocation = pLoc;
+}
+
+void nsImportGenericAddressBooks::GetDefaultBooks(void) {
+ if (!m_pInterface) return;
+
+ if (!m_pLocation && !m_autoFind) return;
+
+ nsresult rv = m_pInterface->FindAddressBooks(m_pLocation, m_Books);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error: FindAddressBooks failed\n");
+ }
+}
+
+void nsImportGenericAddressBooks::GetDefaultFieldMap(void) {
+ if (!m_pInterface || !m_pLocation) return;
+
+ nsresult rv;
+ nsCOMPtr<nsIImportService> impSvc(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Unable to get nsIImportService.\n");
+ return;
+ }
+
+ rv = impSvc->CreateNewFieldMap(getter_AddRefs(m_pFieldMap));
+ if (NS_FAILED(rv)) return;
+
+ int32_t sz = 0;
+ rv = m_pFieldMap->GetNumMozFields(&sz);
+ if (NS_SUCCEEDED(rv)) rv = m_pFieldMap->DefaultFieldMap(sz);
+ if (NS_SUCCEEDED(rv)) rv = m_pInterface->InitFieldMap(m_pFieldMap);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error: Unable to initialize field map\n");
+ m_pFieldMap = nullptr;
+ }
+}
+
+NS_IMETHODIMP nsImportGenericAddressBooks::WantsProgress(bool* _retval) {
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ GetDefaultLocation();
+ GetDefaultBooks();
+
+ bool result = false;
+ uint32_t totalSize = 0;
+
+ for (nsIImportABDescriptor* book : m_Books) {
+ bool doImport = false;
+ nsresult rv = book->GetImport(&doImport);
+ if (NS_SUCCEEDED(rv) && doImport) {
+ uint32_t size = 0;
+ (void)book->GetSize(&size);
+ result = true;
+ totalSize += size;
+ }
+ }
+ m_totalSize = totalSize;
+ m_doImport = result;
+
+ *_retval = result;
+
+ return NS_OK;
+}
+
+void nsImportGenericAddressBooks::SetLogs(nsString& success, nsString& error,
+ nsISupportsString* pSuccess,
+ nsISupportsString* pError) {
+ nsAutoString str;
+ if (pSuccess) {
+ pSuccess->GetData(str);
+ str.Append(success);
+ pSuccess->SetData(success);
+ }
+ if (pError) {
+ pError->GetData(str);
+ str.Append(error);
+ pError->SetData(error);
+ }
+}
+
+already_AddRefed<nsIAbDirectory> GetAddressBookFromUri(const char* pUri) {
+ if (!pUri) return nullptr;
+
+ nsCOMPtr<nsIAbManager> abManager = do_GetService("@mozilla.org/abmanager;1");
+ if (!abManager) return nullptr;
+
+ nsCOMPtr<nsIAbDirectory> directory;
+ abManager->GetDirectory(nsDependentCString(pUri), getter_AddRefs(directory));
+ if (!directory) return nullptr;
+
+ return directory.forget();
+}
+
+already_AddRefed<nsIAbDirectory> GetAddressBook(nsString name, bool makeNew) {
+ if (!makeNew) {
+ // FIXME: How do I get the list of address books and look for a
+ // specific name. Major bogosity!
+ // For now, assume we didn't find anything with that name
+ }
+
+ IMPORT_LOG0("In GetAddressBook\n");
+
+ nsresult rv;
+ nsCOMPtr<nsIAbDirectory> directory;
+ nsCOMPtr<nsIAbManager> abManager =
+ do_GetService("@mozilla.org/abmanager;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString dirPrefId;
+ rv = abManager->NewAddressBook(name, EmptyCString(),
+ nsIAbManager::JS_DIRECTORY_TYPE,
+ EmptyCString(), dirPrefId);
+ if (NS_SUCCEEDED(rv)) {
+ rv = abManager->GetDirectoryFromId(dirPrefId, getter_AddRefs(directory));
+ }
+ }
+
+ return directory.forget();
+}
+
+NS_IMETHODIMP nsImportGenericAddressBooks::BeginImport(
+ nsISupportsString* successLog, nsISupportsString* errorLog, bool* _retval) {
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!_retval) return NS_ERROR_NULL_POINTER;
+
+ nsString success;
+ nsString error;
+
+ if (!m_doImport) {
+ *_retval = true;
+ nsImportStringBundle::GetStringByID(IMPORT_NO_ADDRBOOKS, m_stringBundle,
+ success);
+ SetLogs(success, error, successLog, errorLog);
+ return NS_OK;
+ }
+
+ if (!m_pInterface) {
+ nsImportStringBundle::GetStringByID(IMPORT_ERROR_AB_NOTINITIALIZED,
+ m_stringBundle, error);
+ SetLogs(success, error, successLog, errorLog);
+ *_retval = false;
+ return NS_OK;
+ }
+
+ bool needsFieldMap = false;
+
+ if (NS_FAILED(m_pInterface->GetNeedsFieldMap(m_pLocation, &needsFieldMap)) ||
+ (needsFieldMap && !m_pFieldMap)) {
+ nsImportStringBundle::GetStringByID(IMPORT_ERROR_AB_NOTINITIALIZED,
+ m_stringBundle, error);
+ SetLogs(success, error, successLog, errorLog);
+ *_retval = false;
+ return NS_OK;
+ }
+
+ m_pSuccessLog = successLog;
+ m_pErrorLog = errorLog;
+
+ // create the info need to drive address book import. We're
+ // not going to create a new thread for this since address books
+ // don't tend to be large, and import is rare.
+ m_pThreadData = new AddressThreadData();
+ m_pThreadData->books = m_Books.Clone();
+ m_pThreadData->addressImport = m_pInterface;
+ m_pThreadData->fieldMap = m_pFieldMap;
+ m_pThreadData->errorLog = m_pErrorLog;
+ m_pThreadData->successLog = m_pSuccessLog;
+ m_pThreadData->pDestinationUri = m_pDestinationUri;
+
+ // Create/obtain any address books that we need here, so that we don't need
+ // to do so inside the import thread which would just proxy the create
+ // operations back to the main thread anyway.
+ nsCOMPtr<nsIAbDirectory> db;
+ if (!m_pDestinationUri.IsEmpty()) {
+ db = GetAddressBookFromUri(m_pDestinationUri.get());
+ }
+ for (nsIImportABDescriptor* book : m_Books) {
+ if (!db) {
+ nsString name;
+ book->GetPreferredName(name);
+ db = GetAddressBook(name, true);
+ }
+ m_DBs.AppendObject(db);
+ }
+ m_pThreadData->dBs = &m_DBs;
+
+ m_pThreadData->stringBundle = m_stringBundle;
+
+ nsresult rv;
+ m_pThreadData->ldifService =
+ do_GetService("@mozilla.org/addressbook/abldifservice;1", &rv);
+
+ ImportAddressThread(m_pThreadData);
+ delete m_pThreadData;
+ m_pThreadData = nullptr;
+ *_retval = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportGenericAddressBooks::ContinueImport(bool* _retval) {
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!_retval) return NS_ERROR_NULL_POINTER;
+
+ *_retval = true;
+ if (m_pThreadData) {
+ if (m_pThreadData->fatalError) *_retval = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportGenericAddressBooks::GetProgress(int32_t* _retval) {
+ // This returns the progress from the the currently
+ // running import mail or import address book thread.
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!_retval) return NS_ERROR_NULL_POINTER;
+
+ if (!m_pThreadData || !(m_pThreadData->threadAlive)) {
+ *_retval = 100;
+ return NS_OK;
+ }
+
+ uint32_t sz = 0;
+ if (m_pThreadData->currentSize && m_pInterface) {
+ if (NS_FAILED(m_pInterface->GetImportProgress(&sz))) sz = 0;
+ }
+
+ if (m_totalSize)
+ *_retval = ((m_pThreadData->currentTotal + sz) * 100) / m_totalSize;
+ else
+ *_retval = 0;
+
+ // never return less than 5 so it looks like we are doing something!
+ if (*_retval < 5) *_retval = 5;
+
+ // as long as the thread is alive don't return completely
+ // done.
+ if (*_retval > 99) *_retval = 99;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportGenericAddressBooks::CancelImport(void) {
+ if (m_pThreadData) {
+ m_pThreadData->abort = true;
+ m_pThreadData = nullptr;
+ }
+
+ return NS_OK;
+}
+
+AddressThreadData::AddressThreadData() {
+ fatalError = false;
+ driverAlive = true;
+ threadAlive = true;
+ abort = false;
+ currentTotal = 0;
+ currentSize = 0;
+}
+
+AddressThreadData::~AddressThreadData() {}
+
+void nsImportGenericAddressBooks::ReportError(const char16_t* pName,
+ nsString* pStream,
+ nsIStringBundle* aBundle) {
+ if (!pStream) return;
+ // load the error string
+ char16_t* pFmt =
+ nsImportStringBundle::GetStringByID(IMPORT_ERROR_GETABOOK, aBundle);
+ nsString pText;
+ nsTextFormatter::ssprintf(pText, pFmt, pName);
+ pStream->Append(pText);
+ free(pFmt);
+ pStream->AppendLiteral(MSG_LINEBREAK);
+}
+
+static void ImportAddressThread(void* stuff) {
+ IMPORT_LOG0("In Begin ImportAddressThread\n");
+
+ AddressThreadData* pData = (AddressThreadData*)stuff;
+
+ nsString success;
+ nsString error;
+
+ uint32_t count = pData->books.Length();
+ for (uint32_t i = 0; (i < count) && !(pData->abort); i++) {
+ nsIImportABDescriptor* book = pData->books[i];
+
+ uint32_t size = 0;
+ bool doImport = false;
+ nsresult rv = book->GetImport(&doImport);
+ if (NS_SUCCEEDED(rv) && doImport) rv = book->GetSize(&size);
+
+ if (NS_SUCCEEDED(rv) && size && doImport) {
+ nsString name;
+ book->GetPreferredName(name);
+
+ nsCOMPtr<nsIAbDirectory> db = pData->dBs->ObjectAt(i);
+
+ bool fatalError = false;
+ pData->currentSize = size;
+ if (db) {
+ char16_t* pSuccess = nullptr;
+ char16_t* pError = nullptr;
+
+ /*
+ if (pData->fieldMap) {
+ int32_t sz = 0;
+ int32_t mapIndex;
+ bool active;
+ pData->fieldMap->GetMapSize(&sz);
+ IMPORT_LOG1("**** Field Map Size: %d\n", (int) sz);
+ for (int32_t i = 0; i < sz; i++) {
+ pData->fieldMap->GetFieldMap(i, &mapIndex);
+ pData->fieldMap->GetFieldActive(i, &active);
+ IMPORT_LOG3("Field map #%d: index=%d, active=%d\n", (int) i, (int)
+ mapIndex, (int) active);
+ }
+ }
+ */
+
+ rv = pData->addressImport->ImportAddressBook(
+ book, db, pData->fieldMap, pData->ldifService, &pError, &pSuccess,
+ &fatalError);
+ if (NS_SUCCEEDED(rv) && pSuccess) {
+ success.Append(pSuccess);
+ free(pSuccess);
+ }
+ if (pError) {
+ error.Append(pError);
+ free(pError);
+ }
+ } else {
+ nsImportGenericAddressBooks::ReportError(name.get(), &error,
+ pData->stringBundle);
+ }
+
+ pData->currentSize = 0;
+ pData->currentTotal += size;
+
+ if (fatalError) {
+ pData->fatalError = true;
+ break;
+ }
+ }
+ }
+
+ nsImportGenericAddressBooks::SetLogs(success, error, pData->successLog,
+ pData->errorLog);
+
+ if (pData->abort || pData->fatalError) {
+ // FIXME: do what is necessary to get rid of what has been imported so far.
+ // Nothing if we went into an existing address book! Otherwise, delete
+ // the ones we created?
+ }
+}
diff --git a/comm/mailnews/import/src/nsImportAddressBooks.h b/comm/mailnews/import/src/nsImportAddressBooks.h
new file mode 100644
index 0000000000..8d0edd2c67
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportAddressBooks.h
@@ -0,0 +1,81 @@
+/* -*- 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 "nsCOMPtr.h"
+#include "nsIImportAddressBooks.h"
+#include "nsIImportGeneric.h"
+#include "nsIImportFieldMap.h"
+#include "nsString.h"
+#include "nsIFile.h"
+#include "nsIAbDirectory.h"
+#include "nsIAbLDIFService.h"
+#include "nsIStringBundle.h"
+#include "nsIArray.h"
+#include "nsCOMArray.h"
+
+static void ImportAddressThread(void* stuff);
+
+class AddressThreadData;
+
+class nsImportGenericAddressBooks : public nsIImportGeneric {
+ public:
+ nsImportGenericAddressBooks();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMPORTGENERIC
+
+ private:
+ virtual ~nsImportGenericAddressBooks();
+ void GetDefaultLocation(void);
+ void GetDefaultBooks(void);
+ void GetDefaultFieldMap(void);
+
+ public:
+ static void SetLogs(nsString& success, nsString& error,
+ nsISupportsString* pSuccess, nsISupportsString* pError);
+ static void ReportError(const char16_t* pName, nsString* pStream,
+ nsIStringBundle* aBundle);
+
+ private:
+ nsCOMPtr<nsIImportAddressBooks> m_pInterface;
+ nsTArray<RefPtr<nsIImportABDescriptor>> m_Books;
+ nsCOMArray<nsIAbDirectory> m_DBs;
+ nsCOMPtr<nsIFile> m_pLocation;
+ nsCOMPtr<nsIImportFieldMap> m_pFieldMap;
+ bool m_autoFind;
+ char16_t* m_description;
+ bool m_gotLocation;
+ bool m_found;
+ bool m_userVerify;
+ nsCOMPtr<nsISupportsString> m_pSuccessLog;
+ nsCOMPtr<nsISupportsString> m_pErrorLog;
+ uint32_t m_totalSize;
+ bool m_doImport;
+ AddressThreadData* m_pThreadData;
+ nsCString m_pDestinationUri;
+ nsCOMPtr<nsIStringBundle> m_stringBundle;
+};
+
+class AddressThreadData {
+ public:
+ bool driverAlive;
+ bool threadAlive;
+ bool abort;
+ bool fatalError;
+ uint32_t currentTotal;
+ uint32_t currentSize;
+ nsTArray<RefPtr<nsIImportABDescriptor>> books;
+ nsCOMArray<nsIAbDirectory>* dBs;
+ nsCOMPtr<nsIAbLDIFService> ldifService;
+ nsCOMPtr<nsIImportAddressBooks> addressImport;
+ nsCOMPtr<nsIImportFieldMap> fieldMap;
+ nsCOMPtr<nsISupportsString> successLog;
+ nsCOMPtr<nsISupportsString> errorLog;
+ nsCString pDestinationUri;
+ nsCOMPtr<nsIStringBundle> stringBundle;
+
+ AddressThreadData();
+ ~AddressThreadData();
+};
diff --git a/comm/mailnews/import/src/nsImportEmbeddedImageData.cpp b/comm/mailnews/import/src/nsImportEmbeddedImageData.cpp
new file mode 100644
index 0000000000..d49bccdbfc
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportEmbeddedImageData.cpp
@@ -0,0 +1,52 @@
+/* -*- 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 "nsImportEmbeddedImageData.h"
+
+NS_IMPL_ISUPPORTS(nsImportEmbeddedImageData, nsIMsgEmbeddedImageData)
+
+nsImportEmbeddedImageData::nsImportEmbeddedImageData() {}
+
+nsImportEmbeddedImageData::nsImportEmbeddedImageData(nsIURI* aUri,
+ const nsACString& aCid)
+ : m_uri(aUri), m_cid(aCid) {}
+
+nsImportEmbeddedImageData::nsImportEmbeddedImageData(nsIURI* aUri,
+ const nsACString& aCid,
+ const nsACString& aName)
+ : m_uri(aUri), m_cid(aCid), m_name(aName) {}
+
+nsImportEmbeddedImageData::~nsImportEmbeddedImageData() {}
+
+NS_IMETHODIMP nsImportEmbeddedImageData::GetUri(nsIURI** aUri) {
+ NS_ENSURE_ARG_POINTER(aUri);
+ NS_IF_ADDREF(*aUri = m_uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportEmbeddedImageData::SetUri(nsIURI* aUri) {
+ m_uri = aUri;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportEmbeddedImageData::GetCid(nsACString& aCid) {
+ aCid = m_cid;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportEmbeddedImageData::SetCid(const nsACString& aCid) {
+ m_cid = aCid;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportEmbeddedImageData::GetName(nsACString& aName) {
+ aName = m_name;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportEmbeddedImageData::SetName(const nsACString& aName) {
+ m_name = aName;
+ return NS_OK;
+}
diff --git a/comm/mailnews/import/src/nsImportEmbeddedImageData.h b/comm/mailnews/import/src/nsImportEmbeddedImageData.h
new file mode 100644
index 0000000000..0eaa08b113
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportEmbeddedImageData.h
@@ -0,0 +1,31 @@
+/* -*- 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 __IMPORTEMBEDDEDIMAGETDATA_H__
+#define __IMPORTEMBEDDEDIMAGETDATA_H__
+
+#include "nsIMsgSend.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsIURI.h"
+
+class nsImportEmbeddedImageData final : public nsIMsgEmbeddedImageData {
+ public:
+ nsImportEmbeddedImageData(nsIURI* aUri, const nsACString& aCID);
+ nsImportEmbeddedImageData(nsIURI* aUri, const nsACString& aCID,
+ const nsACString& aName);
+ nsImportEmbeddedImageData();
+ NS_DECL_NSIMSGEMBEDDEDIMAGEDATA
+ NS_DECL_ISUPPORTS
+
+ nsCOMPtr<nsIURI> m_uri;
+ nsCString m_cid;
+ nsCString m_name;
+
+ private:
+ ~nsImportEmbeddedImageData();
+};
+
+#endif
diff --git a/comm/mailnews/import/src/nsImportEncodeScan.cpp b/comm/mailnews/import/src/nsImportEncodeScan.cpp
new file mode 100644
index 0000000000..64607688ef
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportEncodeScan.cpp
@@ -0,0 +1,334 @@
+/* -*- 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 "nscore.h"
+#include "nsImportEncodeScan.h"
+#include "nsNetUtil.h"
+
+#define kBeginAppleSingle 0
+#define kBeginDataFork 1
+#define kBeginResourceFork 2
+#define kAddEntries 3
+#define kScanningDataFork 4
+#define kScanningRsrcFork 5
+#define kDoneWithFile 6
+
+uint32_t gAppleSingleHeader[6] = {0x00051600, 0x00020000, 0, 0, 0, 0};
+#define kAppleSingleHeaderSize (6 * sizeof(uint32_t))
+
+#ifdef _MAC_IMPORT_CODE
+# include "MoreDesktopMgr.h"
+
+CInfoPBRec gCatInfoPB;
+U32 g2000Secs = 0;
+long gGMTDelta = 0;
+
+long GetGmtDelta(void);
+U32 Get2000Secs(void);
+
+long GetGmtDelta(void) {
+ MachineLocation myLocation;
+ ReadLocation(&myLocation);
+ long myDelta = BitAnd(myLocation.u.gmtDelta, 0x00FFFFFF);
+ if (BitTst(&myDelta, 23)) myDelta = BitOr(myDelta, 0xFF000000);
+ return myDelta;
+}
+
+U32 Get2000Secs(void) {
+ DateTimeRec dr;
+ dr.year = 2000;
+ dr.month = 1;
+ dr.day = 1;
+ dr.hour = 0;
+ dr.minute = 0;
+ dr.second = 0;
+ dr.dayOfWeek = 0;
+ U32 result;
+ DateToSeconds(&dr, &result);
+ return result;
+}
+#endif
+
+nsImportEncodeScan::nsImportEncodeScan() {
+ m_isAppleSingle = false;
+ m_encodeScanState = 0;
+ m_resourceForkSize = 0;
+ m_dataForkSize = 0;
+}
+
+nsImportEncodeScan::~nsImportEncodeScan() {}
+
+bool nsImportEncodeScan::InitEncodeScan(bool appleSingleEncode,
+ nsIFile* fileLoc, const char* pName,
+ uint8_t* pBuf, uint32_t sz) {
+ CleanUpEncodeScan();
+ m_isAppleSingle = appleSingleEncode;
+ m_encodeScanState = kBeginAppleSingle;
+ m_pInputFile = fileLoc;
+ m_useFileName = pName;
+ m_pBuf = pBuf;
+ m_bufSz = sz;
+ if (!m_isAppleSingle) {
+ if (!m_inputStream) {
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(m_inputStream),
+ m_pInputFile);
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ InitScan(m_inputStream, pBuf, sz);
+ } else {
+#ifdef _MAC_IMPORT_CODE
+ // Fill in the file sizes
+ m_resourceForkSize = fileLoc.GetMacFileSize(UFileLocation::eResourceFork);
+ m_dataForkSize = fileLoc.GetMacFileSize(UFileLocation::eDataFork);
+#endif
+ }
+
+ return true;
+}
+
+void nsImportEncodeScan::CleanUpEncodeScan(void) {
+ m_pInputStream->Close();
+ m_pInputStream = nullptr;
+ m_pInputFile = nullptr;
+}
+
+// 26 + 12 per entry
+
+void nsImportEncodeScan::FillInEntries(int numEntries) {
+#ifdef _MAC_IMPORT_CODE
+ int len = m_useFileName.GetLength();
+ if (len < 32) len = 32;
+ long entry[3];
+ long fileOffset = 26 + (12 * numEntries);
+ entry[0] = 3;
+ entry[1] = fileOffset;
+ entry[2] = m_useFileName.GetLength();
+ fileOffset += len;
+ MemCpy(m_pBuf + m_bytesInBuf, entry, 12);
+ m_bytesInBuf += 12;
+
+ Str255 comment;
+ comment[0] = 0;
+ OSErr err = FSpDTGetComment(m_inputFileLoc, comment);
+ if (comment[0] > 200) comment[0] = 200;
+ entry[0] = 4;
+ entry[1] = fileOffset;
+ entry[2] = comment[0];
+ fileOffset += 200;
+ MemCpy(m_pBuf + m_bytesInBuf, entry, 12);
+ m_bytesInBuf += 12;
+
+ entry[0] = 8;
+ entry[1] = fileOffset;
+ entry[2] = 16;
+ fileOffset += 16;
+ MemCpy(m_pBuf + m_bytesInBuf, entry, 12);
+ m_bytesInBuf += 12;
+
+ entry[0] = 9;
+ entry[1] = fileOffset;
+ entry[2] = 32;
+ fileOffset += 32;
+ MemCpy(m_pBuf + m_bytesInBuf, entry, 12);
+ m_bytesInBuf += 12;
+
+ entry[0] = 10;
+ entry[1] = fileOffset;
+ entry[2] = 4;
+ fileOffset += 4;
+ MemCpy(m_pBuf + m_bytesInBuf, entry, 12);
+ m_bytesInBuf += 12;
+
+ if (m_resourceForkSize) {
+ entry[0] = 2;
+ entry[1] = fileOffset;
+ entry[2] = m_resourceForkSize;
+ fileOffset += m_resourceForkSize;
+ MemCpy(m_pBuf + m_bytesInBuf, entry, 12);
+ m_bytesInBuf += 12;
+ }
+
+ if (m_dataForkSize) {
+ entry[0] = 1;
+ entry[1] = fileOffset;
+ entry[2] = m_dataForkSize;
+ fileOffset += m_dataForkSize;
+ MemCpy(m_pBuf + m_bytesInBuf, entry, 12);
+ m_bytesInBuf += 12;
+ }
+
+#endif
+}
+
+bool nsImportEncodeScan::AddEntries(void) {
+#ifdef _MAC_IMPORT_CODE
+ if (!g2000Secs) {
+ g2000Secs = Get2000Secs();
+ gGMTDelta = GetGmtDelta();
+ }
+ MemCpy(m_pBuf + m_bytesInBuf, (PC_S8)m_useFileName,
+ m_useFileName.GetLength());
+ m_bytesInBuf += m_useFileName.GetLength();
+ if (m_useFileName.GetLength() < 32) {
+ int len = m_useFileName.GetLength();
+ while (len < 32) {
+ *((P_S8)m_pBuf + m_bytesInBuf) = 0;
+ m_bytesInBuf++;
+ len++;
+ }
+ }
+
+ Str255 comment;
+ comment[0] = 0;
+ OSErr err = FSpDTGetComment(m_inputFileLoc, comment);
+ comment[0] = 200;
+ MemCpy(m_pBuf + m_bytesInBuf, &(comment[1]), comment[0]);
+ m_bytesInBuf += comment[0];
+
+ long dates[4];
+ dates[0] = gCatInfoPB.hFileInfo.ioFlCrDat;
+ dates[1] = gCatInfoPB.hFileInfo.ioFlMdDat;
+ dates[2] = gCatInfoPB.hFileInfo.ioFlBkDat;
+ dates[3] = 0x80000000;
+ for (short i = 0; i < 3; i++) {
+ dates[i] -= g2000Secs;
+ dates[i] += gGMTDelta;
+ }
+ MemCpy(m_pBuf + m_bytesInBuf, dates, 16);
+ m_bytesInBuf += 16;
+
+ FInfo fInfo = gCatInfoPB.hFileInfo.ioFlFndrInfo;
+ FXInfo fxInfo = gCatInfoPB.hFileInfo.ioFlXFndrInfo;
+ fInfo.fdFlags = 0;
+ fInfo.fdLocation.h = 0;
+ fInfo.fdLocation.v = 0;
+ fInfo.fdFldr = 0;
+ MemSet(&fxInfo, 0, sizeof(fxInfo));
+ MemCpy(m_pBuf + m_bytesInBuf, &fInfo, 16);
+ m_bytesInBuf += 16;
+ MemCpy(m_pBuf + m_bytesInBuf, &fxInfo, 16);
+ m_bytesInBuf += 16;
+
+ dates[0] = 0;
+ if ((gCatInfoPB.hFileInfo.ioFlAttrib & 1) != 0) dates[0] |= 1;
+ MemCpy(m_pBuf + m_bytesInBuf, dates, 4);
+ m_bytesInBuf += 4;
+
+#endif
+ return true;
+}
+
+bool nsImportEncodeScan::Scan(bool* pDone) {
+ nsresult rv;
+
+ *pDone = false;
+ if (m_isAppleSingle) {
+ // Stuff the buffer with things needed to encode the file...
+ // then just allow UScanFile to handle each fork, but be careful
+ // when handling eof.
+ switch (m_encodeScanState) {
+ case kBeginAppleSingle: {
+#ifdef _MAC_IMPORT_CODE
+ OSErr err = GetCatInfoNoName(
+ m_inputFileLoc.GetVRefNum(), m_inputFileLoc.GetParID(),
+ m_inputFileLoc.GetFileNamePtr(), &gCatInfoPB);
+ if (err != noErr) return FALSE;
+#endif
+ m_eof = false;
+ m_pos = 0;
+ memcpy(m_pBuf, gAppleSingleHeader, kAppleSingleHeaderSize);
+ m_bytesInBuf = kAppleSingleHeaderSize;
+ int numEntries = 5;
+ if (m_dataForkSize) numEntries++;
+ if (m_resourceForkSize) numEntries++;
+ memcpy(m_pBuf + m_bytesInBuf, &numEntries, sizeof(numEntries));
+ m_bytesInBuf += sizeof(numEntries);
+ FillInEntries(numEntries);
+ m_encodeScanState = kAddEntries;
+ return ScanBuffer(pDone);
+ } break;
+
+ case kBeginDataFork: {
+ if (!m_dataForkSize) {
+ m_encodeScanState = kDoneWithFile;
+ return true;
+ }
+ // Initialize the scan of the data fork...
+ if (!m_inputStream) {
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(m_inputStream),
+ m_pInputFile);
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+ m_encodeScanState = kScanningDataFork;
+ return true;
+ } break;
+
+ case kScanningDataFork: {
+ bool result = FillBufferFromFile();
+ if (!result) return false;
+ if (m_eof) {
+ m_eof = false;
+ result = ScanBuffer(pDone);
+ if (!result) return false;
+ m_inputStream->Close();
+ m_inputStream = nullptr;
+ m_encodeScanState = kDoneWithFile;
+ return true;
+ } else
+ return ScanBuffer(pDone);
+ } break;
+
+ case kScanningRsrcFork: {
+ bool result = FillBufferFromFile();
+ if (!result) return false;
+ if (m_eof) {
+ m_eof = false;
+ result = ScanBuffer(pDone);
+ if (!result) return false;
+ m_inputStream->Close();
+ m_inputStream = nullptr;
+ m_encodeScanState = kBeginDataFork;
+ return true;
+ } else
+ return ScanBuffer(pDone);
+ } break;
+
+ case kBeginResourceFork: {
+ if (!m_resourceForkSize) {
+ m_encodeScanState = kBeginDataFork;
+ return true;
+ }
+ /*
+ // FIXME: Open the resource fork on the Mac!!!
+ m_fH = UFile::OpenRsrcFileRead(m_inputFileLoc);
+ if (m_fH == TR_FILE_ERROR)
+ return FALSE;
+ */
+ m_encodeScanState = kScanningRsrcFork;
+ return true;
+ } break;
+
+ case kAddEntries: {
+ ShiftBuffer();
+ if (!AddEntries()) return false;
+ m_encodeScanState = kBeginResourceFork;
+ return ScanBuffer(pDone);
+ } break;
+
+ case kDoneWithFile: {
+ ShiftBuffer();
+ m_eof = true;
+ if (!ScanBuffer(pDone)) return false;
+ *pDone = true;
+ return true;
+ } break;
+ }
+
+ } else
+ return nsImportScanFile::Scan(pDone);
+
+ return false;
+}
diff --git a/comm/mailnews/import/src/nsImportEncodeScan.h b/comm/mailnews/import/src/nsImportEncodeScan.h
new file mode 100644
index 0000000000..4c9b784fc6
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportEncodeScan.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 nsImportEncodeScan_h___
+#define nsImportEncodeScan_h___
+
+#include "mozilla/Attributes.h"
+#include "nsIFile.h"
+#include "nsImportScanFile.h"
+#include "nsString.h"
+
+class nsImportEncodeScan : public nsImportScanFile {
+ public:
+ nsImportEncodeScan();
+ ~nsImportEncodeScan();
+
+ bool InitEncodeScan(bool appleSingleEncode, nsIFile* pFile, const char* pName,
+ uint8_t* pBuf, uint32_t sz);
+ void CleanUpEncodeScan(void);
+
+ virtual bool Scan(bool* pDone) override;
+
+ protected:
+ void FillInEntries(int numEntries);
+ bool AddEntries(void);
+
+ protected:
+ bool m_isAppleSingle;
+ nsCOMPtr<nsIFile> m_pInputFile;
+ nsCOMPtr<nsIInputStream> m_inputStream;
+ int m_encodeScanState;
+ long m_resourceForkSize;
+ long m_dataForkSize;
+ nsCString m_useFileName;
+};
+
+#endif /* nsImportEncodeScan_h__ */
diff --git a/comm/mailnews/import/src/nsImportFieldMap.cpp b/comm/mailnews/import/src/nsImportFieldMap.cpp
new file mode 100644
index 0000000000..74e15f11fe
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportFieldMap.cpp
@@ -0,0 +1,325 @@
+/* -*- 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 "nscore.h"
+#include "nsIAbCard.h"
+#include "nsIStringBundle.h"
+#include "nsImportFieldMap.h"
+#include "nsImportStringBundle.h"
+#include "nsCRTGlue.h"
+#include "ImportDebug.h"
+#include "nsCOMPtr.h"
+
+////////////////////////////////////////////////////////////////////////
+
+nsresult nsImportFieldMap::Create(nsIStringBundle* aBundle, REFNSIID aIID,
+ void** aResult) {
+ RefPtr<nsImportFieldMap> it = new nsImportFieldMap(aBundle);
+ return it->QueryInterface(aIID, aResult);
+}
+
+NS_IMPL_ISUPPORTS(nsImportFieldMap, nsIImportFieldMap)
+
+NS_IMETHODIMP nsImportFieldMap::GetSkipFirstRecord(bool* result) {
+ NS_ENSURE_ARG_POINTER(result);
+ *result = m_skipFirstRecord;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::SetSkipFirstRecord(bool aResult) {
+ m_skipFirstRecord = aResult;
+ return NS_OK;
+}
+
+nsImportFieldMap::nsImportFieldMap(nsIStringBundle* aBundle) {
+ m_numFields = 0;
+ m_pFields = nullptr;
+ m_pActive = nullptr;
+ m_allocated = 0;
+ // need to init the description array
+ m_mozFieldCount = 0;
+ m_skipFirstRecord = false;
+ nsCOMPtr<nsIStringBundle> pBundle = aBundle;
+
+ nsString* pStr;
+ for (int32_t i = IMPORT_FIELD_DESC_START; i <= IMPORT_FIELD_DESC_END;
+ i++, m_mozFieldCount++) {
+ pStr = new nsString();
+ if (pBundle) {
+ nsImportStringBundle::GetStringByID(i, pBundle, *pStr);
+ } else
+ pStr->AppendInt(i);
+ m_descriptions.AppendElement(pStr);
+ }
+}
+
+nsImportFieldMap::~nsImportFieldMap() {
+ if (m_pFields) delete[] m_pFields;
+ if (m_pActive) delete[] m_pActive;
+
+ nsString* pStr;
+ for (int32_t i = 0; i < m_mozFieldCount; i++) {
+ pStr = m_descriptions.ElementAt(i);
+ delete pStr;
+ }
+ m_descriptions.Clear();
+}
+
+NS_IMETHODIMP nsImportFieldMap::GetNumMozFields(int32_t* aNumFields) {
+ NS_ASSERTION(aNumFields != nullptr, "null ptr");
+ if (!aNumFields) return NS_ERROR_NULL_POINTER;
+
+ *aNumFields = m_mozFieldCount;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::GetMapSize(int32_t* aNumFields) {
+ NS_ASSERTION(aNumFields != nullptr, "null ptr");
+ if (!aNumFields) return NS_ERROR_NULL_POINTER;
+
+ *aNumFields = m_numFields;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::GetFieldDescription(int32_t index,
+ char16_t** _retval) {
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!_retval) return NS_ERROR_NULL_POINTER;
+
+ *_retval = nullptr;
+ if ((index < 0) || ((size_t)index >= m_descriptions.Length()))
+ return NS_ERROR_FAILURE;
+
+ *_retval = ToNewUnicode(*(m_descriptions.ElementAt(index)));
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::SetFieldMapSize(int32_t size) {
+ nsresult rv = Allocate(size);
+ if (NS_FAILED(rv)) return rv;
+
+ m_numFields = size;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::DefaultFieldMap(int32_t size) {
+ nsresult rv = SetFieldMapSize(size);
+ if (NS_FAILED(rv)) return rv;
+ for (int32_t i = 0; i < size; i++) {
+ m_pFields[i] = i;
+ m_pActive[i] = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::GetFieldMap(int32_t index, int32_t* _retval) {
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!_retval) return NS_ERROR_NULL_POINTER;
+
+ if ((index < 0) || (index >= m_numFields)) return NS_ERROR_FAILURE;
+
+ *_retval = m_pFields[index];
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::SetFieldMap(int32_t index, int32_t fieldNum) {
+ if (index == -1) {
+ nsresult rv = Allocate(m_numFields + 1);
+ if (NS_FAILED(rv)) return rv;
+ index = m_numFields;
+ m_numFields++;
+ } else {
+ if ((index < 0) || (index >= m_numFields)) return NS_ERROR_FAILURE;
+ }
+
+ if ((fieldNum != -1) && ((fieldNum < 0) || (fieldNum >= m_mozFieldCount)))
+ return NS_ERROR_FAILURE;
+
+ m_pFields[index] = fieldNum;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::GetFieldActive(int32_t index, bool* active) {
+ NS_ASSERTION(active != nullptr, "null ptr");
+ if (!active) return NS_ERROR_NULL_POINTER;
+ if ((index < 0) || (index >= m_numFields)) return NS_ERROR_FAILURE;
+
+ *active = m_pActive[index];
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::SetFieldActive(int32_t index, bool active) {
+ if ((index < 0) || (index >= m_numFields)) return NS_ERROR_FAILURE;
+
+ m_pActive[index] = active;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportFieldMap::SetFieldValue(nsIAbDirectory* database,
+ nsIAbCard* row, int32_t fieldNum,
+ const nsAString& value) {
+ // Allow the special value for a null field
+ if (fieldNum == -1) return NS_OK;
+
+ if ((fieldNum < 0) || (fieldNum >= m_mozFieldCount)) return NS_ERROR_FAILURE;
+
+ // UGGG!!!!! lot's of typing here!
+ nsresult rv;
+
+ switch (fieldNum) {
+ case 0:
+ rv = row->SetFirstName(value);
+ break;
+ case 1:
+ rv = row->SetLastName(value);
+ break;
+ case 2:
+ rv = row->SetDisplayName(value);
+ break;
+ case 3:
+ rv = row->SetPropertyAsAString(kNicknameProperty, value);
+ break;
+ case 4:
+ rv = row->SetPrimaryEmail(value);
+ break;
+ case 5:
+ rv = row->SetPropertyAsAString(k2ndEmailProperty, value);
+ break;
+ case 6:
+ rv = row->SetPropertyAsAString(kWorkPhoneProperty, value);
+ break;
+ case 7:
+ rv = row->SetPropertyAsAString(kHomePhoneProperty, value);
+ break;
+ case 8:
+ rv = row->SetPropertyAsAString(kFaxProperty, value);
+ break;
+ case 9:
+ rv = row->SetPropertyAsAString(kPagerProperty, value);
+ break;
+ case 10:
+ rv = row->SetPropertyAsAString(kCellularProperty, value);
+ break;
+ case 11:
+ rv = row->SetPropertyAsAString(kHomeAddressProperty, value);
+ break;
+ case 12:
+ rv = row->SetPropertyAsAString(kHomeAddress2Property, value);
+ break;
+ case 13:
+ rv = row->SetPropertyAsAString(kHomeCityProperty, value);
+ break;
+ case 14:
+ rv = row->SetPropertyAsAString(kHomeStateProperty, value);
+ break;
+ case 15:
+ rv = row->SetPropertyAsAString(kHomeZipCodeProperty, value);
+ break;
+ case 16:
+ rv = row->SetPropertyAsAString(kHomeCountryProperty, value);
+ break;
+ case 17:
+ rv = row->SetPropertyAsAString(kWorkAddressProperty, value);
+ break;
+ case 18:
+ rv = row->SetPropertyAsAString(kWorkAddress2Property, value);
+ break;
+ case 19:
+ rv = row->SetPropertyAsAString(kWorkCityProperty, value);
+ break;
+ case 20:
+ rv = row->SetPropertyAsAString(kWorkStateProperty, value);
+ break;
+ case 21:
+ rv = row->SetPropertyAsAString(kWorkZipCodeProperty, value);
+ break;
+ case 22:
+ rv = row->SetPropertyAsAString(kWorkCountryProperty, value);
+ break;
+ case 23:
+ rv = row->SetPropertyAsAString(kJobTitleProperty, value);
+ break;
+ case 24:
+ rv = row->SetPropertyAsAString(kDepartmentProperty, value);
+ break;
+ case 25:
+ rv = row->SetPropertyAsAString(kCompanyProperty, value);
+ break;
+ case 26:
+ rv = row->SetPropertyAsAString(kWorkWebPageProperty, value);
+ break;
+ case 27:
+ rv = row->SetPropertyAsAString(kHomeWebPageProperty, value);
+ break;
+ case 28:
+ rv = row->SetPropertyAsAString(kBirthYearProperty, value);
+ break;
+ case 29:
+ rv = row->SetPropertyAsAString(kBirthMonthProperty, value);
+ break;
+ case 30:
+ rv = row->SetPropertyAsAString(kBirthDayProperty, value);
+ break;
+ case 31:
+ rv = row->SetPropertyAsAString(kCustom1Property, value);
+ break;
+ case 32:
+ rv = row->SetPropertyAsAString(kCustom2Property, value);
+ break;
+ case 33:
+ rv = row->SetPropertyAsAString(kCustom3Property, value);
+ break;
+ case 34:
+ rv = row->SetPropertyAsAString(kCustom4Property, value);
+ break;
+ case 35:
+ rv = row->SetPropertyAsAString(kNotesProperty, value);
+ break;
+ case 36:
+ rv = row->SetPropertyAsAString(kAIMProperty, value);
+ break;
+ default:
+ /* Get the field description, and add it as an anonymous attr? */
+ /* OR WHAT???? */
+ { rv = NS_ERROR_FAILURE; }
+ }
+
+ return rv;
+}
+
+nsresult nsImportFieldMap::Allocate(int32_t newSize) {
+ if (newSize <= m_allocated) return NS_OK;
+
+ int32_t sz = m_allocated;
+ while (sz < newSize) sz += 30;
+
+ int32_t* pData = new int32_t[sz];
+ if (!pData) return NS_ERROR_OUT_OF_MEMORY;
+ bool* pActive = new bool[sz];
+ if (!pActive) {
+ delete[] pData;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ int32_t i;
+ for (i = 0; i < sz; i++) {
+ pData[i] = -1;
+ pActive[i] = true;
+ }
+ if (m_numFields) {
+ for (i = 0; i < m_numFields; i++) {
+ pData[i] = m_pFields[i];
+ pActive[i] = m_pActive[i];
+ }
+ delete[] m_pFields;
+ delete[] m_pActive;
+ }
+ m_allocated = sz;
+ m_pFields = pData;
+ m_pActive = pActive;
+ return NS_OK;
+}
diff --git a/comm/mailnews/import/src/nsImportFieldMap.h b/comm/mailnews/import/src/nsImportFieldMap.h
new file mode 100644
index 0000000000..1d13df70a1
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportFieldMap.h
@@ -0,0 +1,44 @@
+/* -*- 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 nsImportFieldMap_h___
+#define nsImportFieldMap_h___
+
+#include "nscore.h"
+#include "nsIImportFieldMap.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsIStringBundle.h"
+
+////////////////////////////////////////////////////////////////////////
+
+class nsIStringBundle;
+
+class nsImportFieldMap : public nsIImportFieldMap {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIIMPORTFIELDMAP
+
+ explicit nsImportFieldMap(nsIStringBundle* aBundle);
+
+ static nsresult Create(nsIStringBundle* aBundle, REFNSIID aIID,
+ void** aResult);
+
+ private:
+ virtual ~nsImportFieldMap();
+ nsresult Allocate(int32_t newSize);
+
+ private:
+ int32_t m_numFields;
+ int32_t* m_pFields;
+ bool* m_pActive;
+ int32_t m_allocated;
+ nsTArray<nsString*> m_descriptions;
+ int32_t m_mozFieldCount;
+ bool m_skipFirstRecord;
+};
+
+#endif
diff --git a/comm/mailnews/import/src/nsImportMail.cpp b/comm/mailnews/import/src/nsImportMail.cpp
new file mode 100644
index 0000000000..e845f773f8
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportMail.cpp
@@ -0,0 +1,1007 @@
+/* -*- 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 "nsImportMail.h"
+
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsIMsgAccountManager.h"
+#include "nsImportStringBundle.h"
+#include "nsTextFormatter.h"
+#include "ImportDebug.h"
+#include "plstr.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Components.h"
+#include "msgCore.h"
+
+// forward decl for proxy methods
+nsresult ProxyGetSubFolders(nsIMsgFolder* aFolder);
+nsresult ProxyGetChildNamed(nsIMsgFolder* aFolder, const nsAString& aName,
+ nsIMsgFolder** aChild);
+nsresult ProxyGetParent(nsIMsgFolder* aFolder, nsIMsgFolder** aParent);
+nsresult ProxyContainsChildNamed(nsIMsgFolder* aFolder, const nsAString& aName,
+ bool* aResult);
+nsresult ProxyGenerateUniqueSubfolderName(nsIMsgFolder* aFolder,
+ const nsAString& aPrefix,
+ nsIMsgFolder* aOtherFolder,
+ nsAString& aName);
+nsresult ProxyCreateSubfolder(nsIMsgFolder* aFolder, const nsAString& aName);
+nsresult ProxyForceDBClosed(nsIMsgFolder* aFolder);
+
+nsresult NS_NewGenericMail(nsIImportGeneric** aImportGeneric) {
+ NS_ASSERTION(aImportGeneric != nullptr, "null ptr");
+ if (!aImportGeneric) return NS_ERROR_NULL_POINTER;
+
+ RefPtr<nsImportGenericMail> pGen = new nsImportGenericMail();
+ return pGen->QueryInterface(NS_GET_IID(nsIImportGeneric),
+ (void**)aImportGeneric);
+}
+
+nsImportGenericMail::nsImportGenericMail() {
+ m_found = false;
+ m_userVerify = false;
+ m_gotLocation = false;
+ m_gotDefaultMailboxes = false;
+ m_totalSize = 0;
+ m_doImport = false;
+ m_pThreadData = nullptr;
+
+ m_pDestFolder = nullptr;
+ m_deleteDestFolder = false;
+ m_createdFolder = false;
+ m_performingMigration = false;
+
+ nsresult rv = nsImportStringBundle::GetStringBundle(
+ IMPORT_MSGS_URL, getter_AddRefs(m_stringBundle));
+ if (NS_FAILED(rv))
+ IMPORT_LOG0("Failed to get string bundle for Importing Mail");
+}
+
+nsImportGenericMail::~nsImportGenericMail() {
+ if (m_pThreadData) {
+ m_pThreadData->DriverAbort();
+ m_pThreadData = nullptr;
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsImportGenericMail, nsIImportGeneric)
+
+NS_IMETHODIMP nsImportGenericMail::GetData(const char* dataId,
+ nsISupports** _retval) {
+ nsresult rv = NS_OK;
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ *_retval = nullptr;
+ if (!PL_strcasecmp(dataId, "mailInterface")) {
+ NS_IF_ADDREF(*_retval = m_pInterface);
+ }
+
+ if (!PL_strcasecmp(dataId, "mailLocation")) {
+ if (!m_pSrcLocation) GetDefaultLocation();
+ NS_IF_ADDREF(*_retval = m_pSrcLocation);
+ }
+
+ if (!PL_strcasecmp(dataId, "mailDestination")) {
+ if (!m_pDestFolder) GetDefaultDestination();
+ NS_IF_ADDREF(*_retval = m_pDestFolder);
+ }
+
+ if (!PL_strcasecmp(dataId, "migration")) {
+ nsCOMPtr<nsISupportsPRBool> migrationString =
+ do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ migrationString->SetData(m_performingMigration);
+ migrationString.forget(_retval);
+ }
+
+ if (!PL_strcasecmp(dataId, "currentMailbox")) {
+ // create an nsISupportsString, get the current mailbox
+ // name being imported and put it in the string
+ nsCOMPtr<nsISupportsString> data =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+ if (m_pThreadData) {
+ GetMailboxName(m_pThreadData->currentMailbox, data);
+ }
+ data.forget(_retval);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsImportGenericMail::SetData(const char* dataId,
+ nsISupports* item) {
+ nsresult rv = NS_OK;
+ NS_ASSERTION(dataId != nullptr, "null ptr");
+ if (!dataId) return NS_ERROR_NULL_POINTER;
+
+ if (!PL_strcasecmp(dataId, "mailInterface")) {
+ m_pInterface = nullptr;
+ if (item) m_pInterface = do_QueryInterface(item);
+ }
+
+ if (!PL_strcasecmp(dataId, "mailLocation")) {
+ m_mailboxes.Clear();
+ m_gotDefaultMailboxes = false;
+ m_pSrcLocation = nullptr;
+ if (item) {
+ nsresult rv;
+ nsCOMPtr<nsIFile> location = do_QueryInterface(item, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_pSrcLocation = location;
+ }
+ }
+
+ if (!PL_strcasecmp(dataId, "mailDestination")) {
+ m_pDestFolder = nullptr;
+ if (item) m_pDestFolder = do_QueryInterface(item);
+ m_deleteDestFolder = false;
+ }
+
+ if (!PL_strcasecmp(dataId, "name")) {
+ if (item) {
+ nsCOMPtr<nsISupportsString> nameString = do_QueryInterface(item, &rv);
+ if (NS_SUCCEEDED(rv)) rv = nameString->GetData(m_pName);
+ }
+ }
+
+ if (!PL_strcasecmp(dataId, "migration")) {
+ if (item) {
+ nsCOMPtr<nsISupportsPRBool> migrationString =
+ do_QueryInterface(item, &rv);
+ if (NS_SUCCEEDED(rv))
+ rv = migrationString->GetData(&m_performingMigration);
+ }
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsImportGenericMail::GetStatus(const char* statusKind,
+ int32_t* _retval) {
+ NS_ASSERTION(statusKind != nullptr, "null ptr");
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!statusKind || !_retval) return NS_ERROR_NULL_POINTER;
+
+ *_retval = 0;
+
+ if (!PL_strcasecmp(statusKind, "isInstalled")) {
+ GetDefaultLocation();
+ *_retval = (int32_t)m_found;
+ }
+
+ if (!PL_strcasecmp(statusKind, "canUserSetLocation")) {
+ GetDefaultLocation();
+ *_retval = (int32_t)m_userVerify;
+ }
+
+ return NS_OK;
+}
+
+void nsImportGenericMail::GetDefaultLocation(void) {
+ if (!m_pInterface) return;
+
+ if (m_pSrcLocation && m_gotLocation) return;
+
+ m_gotLocation = true;
+
+ nsCOMPtr<nsIFile> pLoc;
+ m_pInterface->GetDefaultLocation(getter_AddRefs(pLoc), &m_found,
+ &m_userVerify);
+ if (!m_pSrcLocation) m_pSrcLocation = pLoc;
+}
+
+void nsImportGenericMail::GetDefaultMailboxes(void) {
+ if (!m_pInterface || !m_pSrcLocation) return;
+ if (m_gotDefaultMailboxes) return;
+ m_pInterface->FindMailboxes(m_pSrcLocation, m_mailboxes);
+ m_gotDefaultMailboxes = true;
+}
+
+void nsImportGenericMail::GetDefaultDestination(void) {
+ if (m_pDestFolder) return;
+ if (!m_pInterface) return;
+
+ nsIMsgFolder* rootFolder;
+ m_deleteDestFolder = false;
+ m_createdFolder = false;
+ if (CreateFolder(&rootFolder)) {
+ m_pDestFolder = rootFolder;
+ m_deleteDestFolder = true;
+ m_createdFolder = true;
+ return;
+ }
+ IMPORT_LOG0(
+ "*** GetDefaultDestination: Failed to create a default import "
+ "destination folder.");
+}
+
+NS_IMETHODIMP nsImportGenericMail::WantsProgress(bool* _retval) {
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ if (m_pThreadData) {
+ m_pThreadData->DriverAbort();
+ m_pThreadData = nullptr;
+ }
+
+ GetDefaultLocation();
+ GetDefaultMailboxes();
+
+ if (!m_pDestFolder) {
+ GetDefaultDestination();
+ }
+
+ bool result = false;
+ uint32_t totalSize = 0;
+ for (nsIImportMailboxDescriptor* box : m_mailboxes) {
+ bool doImport = false;
+ uint32_t size = 0;
+ nsresult rv = box->GetImport(&doImport);
+ if (NS_SUCCEEDED(rv) && doImport) {
+ (void)box->GetSize(&size);
+ result = true;
+ }
+ totalSize += size;
+ }
+ m_totalSize = totalSize;
+ m_doImport = result;
+ *_retval = result;
+ return NS_OK;
+}
+
+void nsImportGenericMail::GetMailboxName(uint32_t index,
+ nsISupportsString* pStr) {
+ if (index >= m_mailboxes.Length()) {
+ return;
+ }
+ nsAutoString name;
+ m_mailboxes[index]->GetDisplayName(getter_Copies(name));
+ if (!name.IsEmpty()) {
+ pStr->SetData(name);
+ }
+}
+
+NS_IMETHODIMP nsImportGenericMail::BeginImport(nsISupportsString* successLog,
+ nsISupportsString* errorLog,
+ bool* _retval) {
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!_retval) return NS_ERROR_NULL_POINTER;
+
+ nsString success;
+ nsString error;
+
+ if (!m_doImport) {
+ nsImportStringBundle::GetStringByID(IMPORT_NO_MAILBOXES, m_stringBundle,
+ success);
+ SetLogs(success, error, successLog, errorLog);
+ *_retval = true;
+ return NS_OK;
+ }
+
+ if (!m_pInterface || !m_gotDefaultMailboxes) {
+ IMPORT_LOG0(
+ "*** BeginImport: Either the interface or source mailbox is not set "
+ "properly.");
+ nsImportStringBundle::GetStringByID(IMPORT_ERROR_MB_NOTINITIALIZED,
+ m_stringBundle, error);
+ SetLogs(success, error, successLog, errorLog);
+ *_retval = false;
+ return NS_OK;
+ }
+
+ if (!m_pDestFolder) {
+ IMPORT_LOG0(
+ "*** BeginImport: The destination mailbox is not set properly.");
+ nsImportStringBundle::GetStringByID(IMPORT_ERROR_MB_NODESTFOLDER,
+ m_stringBundle, error);
+ SetLogs(success, error, successLog, errorLog);
+ *_retval = false;
+ return NS_OK;
+ }
+
+ if (m_pThreadData) {
+ m_pThreadData->DriverAbort();
+ m_pThreadData = nullptr;
+ }
+
+ m_pSuccessLog = successLog;
+ m_pErrorLog = errorLog;
+
+ // kick off the thread to do the import!!!!
+ m_pThreadData = new ImportThreadData();
+ m_pThreadData->boxes = m_mailboxes.Clone();
+ m_pThreadData->mailImport = m_pInterface;
+ m_pThreadData->errorLog = m_pErrorLog;
+ m_pThreadData->successLog = m_pSuccessLog;
+
+ m_pThreadData->ownsDestRoot = m_deleteDestFolder;
+ m_pThreadData->destRoot = m_pDestFolder;
+ m_pThreadData->performingMigration = m_performingMigration;
+
+ m_pThreadData->stringBundle = m_stringBundle;
+
+ // Previously this was run in a sub-thread, after introducing
+ // SeamonkeyImport.jsm and because JS XPCOM can only run in the main thread,
+ // this has been changed to run in the main thread.
+ ImportMailThread(m_pThreadData);
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportGenericMail::ContinueImport(bool* _retval) {
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!_retval) return NS_ERROR_NULL_POINTER;
+
+ *_retval = true;
+ if (m_pThreadData) {
+ if (m_pThreadData->fatalError) *_retval = false;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportGenericMail::GetProgress(int32_t* _retval) {
+ // This returns the progress from the the currently
+ // running import mail or import address book thread.
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!_retval) return NS_ERROR_NULL_POINTER;
+
+ if (!m_pThreadData || !(m_pThreadData->threadAlive)) {
+ *_retval = 100;
+ return NS_OK;
+ }
+
+ uint32_t sz = 0;
+ if (m_pThreadData->currentSize && m_pInterface) {
+ if (NS_FAILED(m_pInterface->GetImportProgress(&sz))) sz = 0;
+ }
+
+ // *_retval = (int32_t) (((uint32_t)(m_pThreadData->currentTotal + sz) *
+ // (uint32_t)100) / m_totalSize);
+
+ if (m_totalSize) {
+ double perc;
+ perc = (double)m_pThreadData->currentTotal;
+ perc += sz;
+ perc *= 100;
+ perc /= m_totalSize;
+ *_retval = (int32_t)perc;
+ if (*_retval > 100) *_retval = 100;
+ } else
+ *_retval = 0;
+
+ // never return 100% while the thread is still alive
+ if (*_retval > 99) *_retval = 99;
+
+ return NS_OK;
+}
+
+void nsImportGenericMail::ReportError(int32_t id, const char16_t* pName,
+ nsString* pStream,
+ nsIStringBundle* aBundle) {
+ if (!pStream) return;
+
+ // load the error string
+ char16_t* pFmt = nsImportStringBundle::GetStringByID(id, aBundle);
+ nsString pText;
+ nsTextFormatter::ssprintf(pText, pFmt, pName);
+ pStream->Append(pText);
+ free(pFmt);
+ pStream->Append(NS_ConvertASCIItoUTF16(MSG_LINEBREAK));
+}
+
+void nsImportGenericMail::SetLogs(nsString& success, nsString& error,
+ nsISupportsString* pSuccess,
+ nsISupportsString* pError) {
+ nsAutoString str;
+ if (pSuccess) {
+ pSuccess->GetData(str);
+ str.Append(success);
+ pSuccess->SetData(str);
+ }
+ if (pError) {
+ pError->GetData(str);
+ str.Append(error);
+ pError->SetData(str);
+ }
+}
+
+NS_IMETHODIMP nsImportGenericMail::CancelImport(void) {
+ if (m_pThreadData) {
+ m_pThreadData->abort = true;
+ m_pThreadData->DriverAbort();
+ m_pThreadData = nullptr;
+ }
+
+ return NS_OK;
+}
+
+ImportThreadData::ImportThreadData() {
+ fatalError = false;
+ driverAlive = true;
+ threadAlive = true;
+ abort = false;
+ currentTotal = 0;
+ currentSize = 0;
+ destRoot = nullptr;
+ ownsDestRoot = false;
+}
+
+ImportThreadData::~ImportThreadData() {}
+
+void ImportThreadData::DriverDelete(void) {
+ driverAlive = false;
+ if (!driverAlive && !threadAlive) delete this;
+}
+
+void ImportThreadData::ThreadDelete() {
+ threadAlive = false;
+ if (!driverAlive && !threadAlive) delete this;
+}
+
+void ImportThreadData::DriverAbort() {
+ if (abort && !threadAlive && destRoot) {
+ if (ownsDestRoot) {
+ destRoot->RecursiveDelete(true);
+ } else {
+ // FIXME: just delete the stuff we created?
+ }
+ } else
+ abort = true;
+ DriverDelete();
+}
+
+static void ImportMailThread(void* stuff) {
+ ImportThreadData* pData = (ImportThreadData*)stuff;
+
+ IMPORT_LOG0("ImportMailThread: Starting...");
+
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIMsgFolder> destRoot(pData->destRoot);
+
+ uint32_t count = pData->boxes.Length();
+
+ uint32_t size;
+ uint32_t depth = 1;
+ uint32_t newDepth;
+ nsString lastName;
+
+ nsCOMPtr<nsIMsgFolder> curFolder(destRoot);
+
+ nsCOMPtr<nsIMsgFolder> newFolder;
+ nsCOMPtr<nsIMsgFolder> subFolder;
+
+ bool exists;
+
+ nsString success;
+ nsString error;
+
+ // GetSubFolders() will initialize folders if they are not already
+ // initialized.
+ ProxyGetSubFolders(curFolder);
+
+ IMPORT_LOG1("ImportMailThread: Total number of folders to import = %d.",
+ count);
+
+ // Note that the front-end js script only displays one import result string so
+ // we combine both good and bad import status into one string (in var
+ // 'success').
+
+ for (uint32_t i = 0; (i < count) && !(pData->abort); i++) {
+ nsIImportMailboxDescriptor* box = pData->boxes[i];
+ pData->currentMailbox = i;
+
+ bool doImport = false;
+ size = 0;
+ rv = box->GetImport(&doImport);
+ if (doImport) rv = box->GetSize(&size);
+ rv = box->GetDepth(&newDepth);
+ if (newDepth > depth) {
+ // OK, we are going to add a subfolder under the last/previous folder we
+ // processed, so find this folder (stored in 'lastName') who is going to
+ // be the new parent folder.
+ IMPORT_LOG1("ImportMailThread: Processing child folder '%s'.",
+ NS_ConvertUTF16toUTF8(lastName).get());
+ rv = ProxyGetChildNamed(curFolder, lastName, getter_AddRefs(subFolder));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1(
+ "*** ImportMailThread: Failed to get the interface for child "
+ "folder '%s'.",
+ NS_ConvertUTF16toUTF8(lastName).get());
+ nsImportGenericMail::ReportError(IMPORT_ERROR_MB_FINDCHILD,
+ lastName.get(), &error,
+ pData->stringBundle);
+ pData->fatalError = true;
+ break;
+ }
+ curFolder = subFolder;
+ // Make sure this new parent folder obj has the correct subfolder list
+ // so far.
+ rv = ProxyGetSubFolders(curFolder);
+ } else if (newDepth < depth) {
+ rv = NS_OK;
+ while ((newDepth < depth) && NS_SUCCEEDED(rv)) {
+ rv = curFolder->GetParent(getter_AddRefs(curFolder));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1(
+ "*** ImportMailThread: Failed to get the interface for parent "
+ "folder '%s'.",
+ NS_ConvertUTF16toUTF8(lastName).get());
+ nsImportGenericMail::ReportError(IMPORT_ERROR_MB_FINDCHILD,
+ lastName.get(), &error,
+ pData->stringBundle);
+ pData->fatalError = true;
+ break;
+ }
+ depth--;
+ }
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1(
+ "*** ImportMailThread: Failed to get the proxy interface for "
+ "parent folder '%s'.",
+ NS_ConvertUTF16toUTF8(lastName).get());
+ nsImportStringBundle::GetStringByID(IMPORT_ERROR_MB_NOPROXY,
+ pData->stringBundle, error);
+ pData->fatalError = true;
+ break;
+ }
+ }
+ depth = newDepth;
+ char16_t* pName = nullptr;
+ box->GetDisplayName(&pName);
+ if (pName) {
+ lastName = pName;
+ free(pName);
+ } else
+ lastName.AssignLiteral("Unknown!");
+
+ // translate the folder name if we are doing migration, but
+ // only for special folders which are at the root level
+ if (pData->performingMigration && depth == 1)
+ pData->mailImport->TranslateFolderName(lastName, lastName);
+
+ exists = false;
+ rv = ProxyContainsChildNamed(curFolder, lastName, &exists);
+
+ // If we are performing profile migration (as opposed to importing) then
+ // we are starting with empty local folders. In that case, always choose
+ // to over-write the existing local folder with this name. Don't create a
+ // unique subfolder name. Otherwise you end up with "Inbox, Inbox0" or
+ // "Unsent Folders, UnsentFolders0"
+ if (exists && !pData->performingMigration) {
+ nsString subName;
+ ProxyGenerateUniqueSubfolderName(curFolder, lastName, nullptr, subName);
+ if (!subName.IsEmpty()) lastName.Assign(subName);
+ }
+
+ IMPORT_LOG1("ImportMailThread: Creating new import folder '%s'.",
+ NS_ConvertUTF16toUTF8(lastName).get());
+ ProxyCreateSubfolder(
+ curFolder,
+ lastName); // this may fail if the folder already exists..that's ok
+
+ rv = ProxyGetChildNamed(curFolder, lastName, getter_AddRefs(newFolder));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1(
+ "*** ImportMailThread: Failed to locate subfolder '%s' after it's "
+ "been created.",
+ NS_ConvertUTF16toUTF8(lastName).get());
+ nsImportGenericMail::ReportError(IMPORT_ERROR_MB_CREATE, lastName.get(),
+ &error, pData->stringBundle);
+ }
+
+ if (size && doImport && newFolder && NS_SUCCEEDED(rv)) {
+ bool fatalError = false;
+ pData->currentSize = size;
+ char16_t* pSuccess = nullptr;
+ char16_t* pError = nullptr;
+ rv = pData->mailImport->ImportMailbox(box, newFolder, &pError, &pSuccess,
+ &fatalError);
+ if (pError) {
+ error.Append(pError);
+ free(pError);
+ }
+ if (pSuccess) {
+ success.Append(pSuccess);
+ free(pSuccess);
+ }
+
+ pData->currentSize = 0;
+ pData->currentTotal += size;
+
+ // commit to the db synchronously, but using a proxy since it doesn't
+ // like being used elsewhere than from the main thread. OK, we've copied
+ // the actual folder/file over if the folder size is not 0 (ie, the msg
+ // summary is no longer valid) so close the msg database so that when
+ // the folder is reopened the folder db can be reconstructed (which
+ // validates msg summary and forces folder to be reparsed).
+ rv = ProxyForceDBClosed(newFolder);
+ fatalError = NS_FAILED(rv);
+
+ if (fatalError) {
+ IMPORT_LOG1(
+ "*** ImportMailThread: ImportMailbox returned fatalError, "
+ "mailbox #%d\n",
+ (int)i);
+ pData->fatalError = true;
+ break;
+ }
+ }
+ }
+
+ // Now save the new acct info to pref file.
+ nsCOMPtr<nsIMsgAccountManager> accMgr =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (NS_SUCCEEDED(rv) && accMgr) {
+ rv = accMgr->SaveAccountInfo();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Can't save account info to pref file");
+ }
+
+ nsImportGenericMail::SetLogs(success, error, pData->successLog,
+ pData->errorLog);
+
+ if (pData->abort || pData->fatalError) {
+ IMPORT_LOG0("*** ImportMailThread: Abort or fatalError flag was set\n");
+ if (pData->ownsDestRoot) {
+ IMPORT_LOG0("Calling destRoot->RecursiveDelete\n");
+ destRoot->RecursiveDelete(true);
+ } else {
+ // FIXME: just delete the stuff we created?
+ }
+ }
+
+ IMPORT_LOG1("Import mailbox thread done: %d\n", (int)pData->currentTotal);
+
+ pData->ThreadDelete();
+}
+
+// Creates a folder in Local Folders with the module name + mail
+// for e.g: Outlook Mail
+bool nsImportGenericMail::CreateFolder(nsIMsgFolder** ppFolder) {
+ nsresult rv;
+ *ppFolder = nullptr;
+
+ nsCOMPtr<nsIStringBundle> bundle;
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ if (!bundleService) return false;
+ rv = bundleService->CreateBundle(IMPORT_MSGS_URL, getter_AddRefs(bundle));
+ if (NS_FAILED(rv)) return false;
+ nsString folderName;
+ if (!m_pName.IsEmpty()) {
+ AutoTArray<nsString, 1> moduleName = {m_pName};
+ rv = bundle->FormatStringFromName("ImportModuleFolderName", moduleName,
+ folderName);
+ } else {
+ rv = bundle->GetStringFromName("DefaultFolderName", folderName);
+ }
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to get Folder Name!\n");
+ return false;
+ }
+ nsCOMPtr<nsIMsgAccountManager> accMgr =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to create account manager!\n");
+ return false;
+ }
+
+ nsCOMPtr<nsIMsgIncomingServer> server;
+ rv = accMgr->GetLocalFoldersServer(getter_AddRefs(server));
+ // if Local Folders does not exist already, create it
+ if (NS_FAILED(rv) || !server) {
+ rv = accMgr->CreateLocalMailAccount();
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to create Local Folders!\n");
+ return false;
+ }
+
+ rv = accMgr->GetLocalFoldersServer(getter_AddRefs(server));
+ }
+
+ if (NS_SUCCEEDED(rv) && server) {
+ nsCOMPtr<nsIMsgFolder> localRootFolder;
+ rv = server->GetRootMsgFolder(getter_AddRefs(localRootFolder));
+ if (localRootFolder) {
+ // we need to call GetSubFolders() so that the folders get initialized
+ // if they are not initialized yet.
+ nsTArray<RefPtr<nsIMsgFolder>> dummy;
+ rv = localRootFolder->GetSubFolders(dummy);
+ if (NS_SUCCEEDED(rv)) {
+ // check if the folder name we picked already exists.
+ bool exists = false;
+ rv = localRootFolder->ContainsChildNamed(folderName, &exists);
+ if (exists) {
+ nsString name;
+ localRootFolder->GenerateUniqueSubfolderName(folderName, nullptr,
+ name);
+ if (!name.IsEmpty())
+ folderName.Assign(name);
+ else {
+ IMPORT_LOG0("*** Failed to find a unique folder name!\n");
+ return false;
+ }
+ }
+ IMPORT_LOG1("Creating folder for importing mail: '%s'\n",
+ NS_ConvertUTF16toUTF8(folderName).get());
+
+ // Bug 564162 identifies a dataloss design flaw.
+ // A working Thunderbird client can have mail in Local Folders and a
+ // subsequent import 'Everything' will trigger a migration which
+ // overwrites existing mailboxes with the imported mailboxes.
+ rv = localRootFolder->CreateSubfolder(folderName, nullptr);
+ if (NS_SUCCEEDED(rv)) {
+ rv = localRootFolder->GetChildNamed(folderName, ppFolder);
+ if (*ppFolder) {
+ IMPORT_LOG1("Folder '%s' created successfully\n",
+ NS_ConvertUTF16toUTF8(folderName).get());
+ return true;
+ }
+ }
+ }
+ } // if localRootFolder
+ } // if server
+ IMPORT_LOG0("****** FAILED TO CREATE FOLDER FOR IMPORT\n");
+ return false;
+}
+
+/**
+ * These are the proxy objects we use to proxy nsIMsgFolder methods back
+ * the the main thread. Since there are only five, we can hand roll them.
+ * A better design might be a co-routine-ish design where the ui thread
+ * hands off each folder to the import thread and when the thread finishes
+ * the folder, the main thread hands it the next folder.
+ */
+
+class GetSubFoldersRunnable : public mozilla::Runnable {
+ public:
+ explicit GetSubFoldersRunnable(nsIMsgFolder* aFolder);
+ NS_DECL_NSIRUNNABLE
+ nsresult mResult;
+
+ private:
+ nsCOMPtr<nsIMsgFolder> m_folder;
+};
+
+GetSubFoldersRunnable::GetSubFoldersRunnable(nsIMsgFolder* aFolder)
+ : mozilla::Runnable("GetSubFoldersRunnable"), m_folder(aFolder) {}
+
+NS_IMETHODIMP GetSubFoldersRunnable::Run() {
+ nsTArray<RefPtr<nsIMsgFolder>> dummy;
+ mResult = m_folder->GetSubFolders(dummy);
+ return NS_OK; // Sync runnable must return OK.
+}
+
+nsresult ProxyGetSubFolders(nsIMsgFolder* aFolder) {
+ RefPtr<GetSubFoldersRunnable> getSubFolders =
+ new GetSubFoldersRunnable(aFolder);
+ nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete(
+ "ProxyGetSubFolders"_ns, mozilla::GetMainThreadSerialEventTarget(),
+ do_AddRef(getSubFolders));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return getSubFolders->mResult;
+}
+
+class GetChildNamedRunnable : public mozilla::Runnable {
+ public:
+ GetChildNamedRunnable(nsIMsgFolder* aFolder, const nsAString& aName,
+ nsIMsgFolder** aChild);
+ NS_DECL_NSIRUNNABLE
+ nsresult mResult;
+
+ protected:
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ nsString m_name;
+ nsIMsgFolder** m_child;
+};
+
+GetChildNamedRunnable::GetChildNamedRunnable(nsIMsgFolder* aFolder,
+ const nsAString& aName,
+ nsIMsgFolder** aChild)
+ : mozilla::Runnable("GetChildNamedRunnable"),
+ mResult(NS_OK),
+ m_folder(aFolder),
+ m_name(aName),
+ m_child(aChild) {}
+
+NS_IMETHODIMP GetChildNamedRunnable::Run() {
+ mResult = m_folder->GetChildNamed(m_name, m_child);
+ return NS_OK; // Sync runnable must return OK.
+}
+
+nsresult ProxyGetChildNamed(nsIMsgFolder* aFolder, const nsAString& aName,
+ nsIMsgFolder** aChild) {
+ RefPtr<GetChildNamedRunnable> getChildNamed =
+ new GetChildNamedRunnable(aFolder, aName, aChild);
+ nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete(
+ "ProxyGetChildNamed"_ns, mozilla::GetMainThreadSerialEventTarget(),
+ do_AddRef(getChildNamed));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return getChildNamed->mResult;
+}
+
+class GetParentRunnable : public mozilla::Runnable {
+ public:
+ GetParentRunnable(nsIMsgFolder* aFolder, nsIMsgFolder** aParent);
+ NS_DECL_NSIRUNNABLE
+ nsresult mResult;
+
+ protected:
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ nsIMsgFolder** m_parent;
+};
+
+GetParentRunnable::GetParentRunnable(nsIMsgFolder* aFolder,
+ nsIMsgFolder** aParent)
+ : mozilla::Runnable("GetParentRunnable"),
+ mResult(NS_OK),
+ m_folder(aFolder),
+ m_parent(aParent) {}
+
+NS_IMETHODIMP GetParentRunnable::Run() {
+ mResult = m_folder->GetParent(m_parent);
+ return NS_OK; // Sync runnable must return OK.
+}
+
+nsresult ProxyGetParent(nsIMsgFolder* aFolder, nsIMsgFolder** aParent) {
+ RefPtr<GetParentRunnable> getParent = new GetParentRunnable(aFolder, aParent);
+ nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete(
+ "ProxyGetParent"_ns, mozilla::GetMainThreadSerialEventTarget(),
+ do_AddRef(getParent));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return getParent->mResult;
+}
+
+class ContainsChildNamedRunnable : public mozilla::Runnable {
+ public:
+ ContainsChildNamedRunnable(nsIMsgFolder* aFolder, const nsAString& aName,
+ bool* aResult);
+ NS_DECL_NSIRUNNABLE
+ nsresult mResult;
+
+ protected:
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ nsString m_name;
+ bool* m_result;
+};
+
+ContainsChildNamedRunnable::ContainsChildNamedRunnable(nsIMsgFolder* aFolder,
+ const nsAString& aName,
+ bool* aResult)
+ : mozilla::Runnable("ContainsChildNamedRunnable"),
+ mResult(NS_OK),
+ m_folder(aFolder),
+ m_name(aName),
+ m_result(aResult) {}
+
+NS_IMETHODIMP ContainsChildNamedRunnable::Run() {
+ mResult = m_folder->ContainsChildNamed(m_name, m_result);
+ return NS_OK; // Sync runnable must return OK.
+}
+
+nsresult ProxyContainsChildNamed(nsIMsgFolder* aFolder, const nsAString& aName,
+ bool* aResult) {
+ NS_ENSURE_ARG(aFolder);
+ RefPtr<ContainsChildNamedRunnable> containsChildNamed =
+ new ContainsChildNamedRunnable(aFolder, aName, aResult);
+ nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete(
+ "ProxyContainsChildNamed"_ns, mozilla::GetMainThreadSerialEventTarget(),
+ do_AddRef(containsChildNamed));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return containsChildNamed->mResult;
+}
+
+class GenerateUniqueSubfolderNameRunnable : public mozilla::Runnable {
+ public:
+ GenerateUniqueSubfolderNameRunnable(nsIMsgFolder* aFolder,
+ const nsAString& prefix,
+ nsIMsgFolder* otherFolder,
+ nsAString& name);
+ NS_DECL_NSIRUNNABLE
+ nsresult mResult;
+
+ protected:
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ nsString m_prefix;
+ nsCOMPtr<nsIMsgFolder> m_otherFolder;
+ nsString m_name;
+};
+
+GenerateUniqueSubfolderNameRunnable::GenerateUniqueSubfolderNameRunnable(
+ nsIMsgFolder* aFolder, const nsAString& aPrefix, nsIMsgFolder* aOtherFolder,
+ nsAString& aName)
+ : mozilla::Runnable("GenerateUniqueSubfolderNameRunnable"),
+ mResult(NS_OK),
+ m_folder(aFolder),
+ m_prefix(aPrefix),
+ m_otherFolder(aOtherFolder),
+ m_name(aName) {}
+
+NS_IMETHODIMP GenerateUniqueSubfolderNameRunnable::Run() {
+ mResult =
+ m_folder->GenerateUniqueSubfolderName(m_prefix, m_otherFolder, m_name);
+ return NS_OK; // Sync runnable must return OK.
+}
+
+nsresult ProxyGenerateUniqueSubfolderName(nsIMsgFolder* aFolder,
+ const nsAString& aPrefix,
+ nsIMsgFolder* aOtherFolder,
+ nsAString& aName)
+
+{
+ RefPtr<GenerateUniqueSubfolderNameRunnable> generateUniqueSubfolderName =
+ new GenerateUniqueSubfolderNameRunnable(aFolder, aPrefix, aOtherFolder,
+ aName);
+ nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete(
+ "ProxyGenerateUniqueSubfolderName"_ns,
+ mozilla::GetMainThreadSerialEventTarget(),
+ do_AddRef(generateUniqueSubfolderName));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return generateUniqueSubfolderName->mResult;
+}
+
+class CreateSubfolderRunnable : public mozilla::Runnable {
+ public:
+ CreateSubfolderRunnable(nsIMsgFolder* aFolder, const nsAString& aName);
+ NS_DECL_NSIRUNNABLE
+ nsresult mResult;
+
+ protected:
+ nsCOMPtr<nsIMsgFolder> m_folder;
+ nsString m_name;
+};
+
+CreateSubfolderRunnable::CreateSubfolderRunnable(nsIMsgFolder* aFolder,
+ const nsAString& aName)
+ : mozilla::Runnable("CreateSubfolderRunnable"),
+ mResult(NS_OK),
+ m_folder(aFolder),
+ m_name(aName) {}
+
+NS_IMETHODIMP CreateSubfolderRunnable::Run() {
+ mResult = m_folder->CreateSubfolder(m_name, nullptr);
+ return NS_OK; // Sync runnable must return OK.
+}
+
+nsresult ProxyCreateSubfolder(nsIMsgFolder* aFolder, const nsAString& aName) {
+ NS_ENSURE_ARG_POINTER(aFolder);
+ RefPtr<CreateSubfolderRunnable> createSubfolder =
+ new CreateSubfolderRunnable(aFolder, aName);
+ nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete(
+ "ProxyCreateSubfolder"_ns, mozilla::GetMainThreadSerialEventTarget(),
+ do_AddRef(createSubfolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return createSubfolder->mResult;
+}
+
+class ForceDBClosedRunnable : public mozilla::Runnable {
+ public:
+ explicit ForceDBClosedRunnable(nsIMsgFolder* aFolder);
+ NS_DECL_NSIRUNNABLE
+ nsresult mResult;
+
+ protected:
+ nsCOMPtr<nsIMsgFolder> m_folder;
+};
+
+ForceDBClosedRunnable::ForceDBClosedRunnable(nsIMsgFolder* aFolder)
+ : mozilla::Runnable("ForceDBClosedRunnable"), m_folder(aFolder) {}
+
+NS_IMETHODIMP ForceDBClosedRunnable::Run() {
+ mResult = m_folder->ForceDBClosed();
+ return NS_OK; // Sync runnable must return OK.
+}
+
+nsresult ProxyForceDBClosed(nsIMsgFolder* aFolder) {
+ RefPtr<ForceDBClosedRunnable> forceDBClosed =
+ new ForceDBClosedRunnable(aFolder);
+ nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete(
+ "ProxyForceDBClosed"_ns, mozilla::GetMainThreadSerialEventTarget(),
+ do_AddRef(forceDBClosed));
+ NS_ENSURE_SUCCESS(rv, rv);
+ return forceDBClosed->mResult;
+}
diff --git a/comm/mailnews/import/src/nsImportMail.h b/comm/mailnews/import/src/nsImportMail.h
new file mode 100644
index 0000000000..fcd2a0c40c
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportMail.h
@@ -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 "nsCOMPtr.h"
+#include "nsIImportMail.h"
+#include "nsIImportGeneric.h"
+#include "nsString.h"
+#include "nsIMsgFolder.h"
+#include "nsIStringBundle.h"
+
+#define IMPORT_MSGS_URL "chrome://messenger/locale/importMsgs.properties"
+
+////////////////////////////////////////////////////////////////////////
+
+static void ImportMailThread(void* stuff);
+
+class ImportThreadData;
+
+class nsImportGenericMail : public nsIImportGeneric {
+ public:
+ nsImportGenericMail();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMPORTGENERIC
+
+ private:
+ virtual ~nsImportGenericMail();
+ bool CreateFolder(nsIMsgFolder** ppFolder);
+ void GetDefaultMailboxes(void);
+ void GetDefaultLocation(void);
+ void GetDefaultDestination(void);
+ void GetMailboxName(uint32_t index, nsISupportsString* pStr);
+
+ public:
+ static void SetLogs(nsString& success, nsString& error,
+ nsISupportsString* pSuccess, nsISupportsString* pError);
+ static void ReportError(int32_t id, const char16_t* pName, nsString* pStream,
+ nsIStringBundle* aBundle);
+
+ private:
+ nsString m_pName; // module name that created this interface
+ nsCOMPtr<nsIMsgFolder> m_pDestFolder;
+ bool m_deleteDestFolder;
+ bool m_createdFolder;
+ nsCOMPtr<nsIFile> m_pSrcLocation;
+ bool m_gotLocation;
+ bool m_gotDefaultMailboxes;
+ bool m_found;
+ bool m_userVerify;
+ nsCOMPtr<nsIImportMail> m_pInterface;
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>> m_mailboxes;
+ nsCOMPtr<nsISupportsString> m_pSuccessLog;
+ nsCOMPtr<nsISupportsString> m_pErrorLog;
+ uint32_t m_totalSize;
+ bool m_doImport;
+ ImportThreadData* m_pThreadData;
+ bool m_performingMigration;
+ nsCOMPtr<nsIStringBundle> m_stringBundle;
+};
+
+class ImportThreadData {
+ public:
+ bool driverAlive;
+ bool threadAlive;
+ bool abort;
+ bool fatalError;
+ uint32_t currentTotal;
+ uint32_t currentSize;
+ nsCOMPtr<nsIMsgFolder> destRoot;
+ bool ownsDestRoot;
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>> boxes;
+ nsCOMPtr<nsIImportMail> mailImport;
+ nsCOMPtr<nsISupportsString> successLog;
+ nsCOMPtr<nsISupportsString> errorLog;
+ uint32_t currentMailbox;
+ bool performingMigration;
+ nsCOMPtr<nsIStringBundle> stringBundle;
+
+ ImportThreadData();
+ ~ImportThreadData();
+ void DriverDelete();
+ void ThreadDelete();
+ void DriverAbort();
+};
diff --git a/comm/mailnews/import/src/nsImportMailboxDescriptor.cpp b/comm/mailnews/import/src/nsImportMailboxDescriptor.cpp
new file mode 100644
index 0000000000..8dc17b9317
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportMailboxDescriptor.cpp
@@ -0,0 +1,25 @@
+/* -*- 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 "nscore.h"
+#include "nsImportMailboxDescriptor.h"
+#include "nsComponentManagerUtils.h"
+
+////////////////////////////////////////////////////////////////////////
+
+nsresult nsImportMailboxDescriptor::Create(REFNSIID aIID, void** aResult) {
+ RefPtr<nsImportMailboxDescriptor> it = new nsImportMailboxDescriptor();
+ return it->QueryInterface(aIID, aResult);
+}
+
+NS_IMPL_ISUPPORTS(nsImportMailboxDescriptor, nsIImportMailboxDescriptor)
+
+nsImportMailboxDescriptor::nsImportMailboxDescriptor() {
+ m_import = true;
+ m_size = 0;
+ m_depth = 0;
+ m_id = 0;
+ m_pFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+}
diff --git a/comm/mailnews/import/src/nsImportMailboxDescriptor.h b/comm/mailnews/import/src/nsImportMailboxDescriptor.h
new file mode 100644
index 0000000000..b760cbd266
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportMailboxDescriptor.h
@@ -0,0 +1,94 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 nsImportMailboxDescriptor_h___
+#define nsImportMailboxDescriptor_h___
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsString.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsIFile.h"
+#include "nsCOMPtr.h"
+
+////////////////////////////////////////////////////////////////////////
+
+class nsImportMailboxDescriptor : public nsIImportMailboxDescriptor {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_IMETHOD GetIdentifier(uint32_t* pIdentifier) override {
+ *pIdentifier = m_id;
+ return NS_OK;
+ }
+ NS_IMETHOD SetIdentifier(uint32_t ident) override {
+ m_id = ident;
+ return NS_OK;
+ }
+
+ /* attribute unsigned long depth; */
+ NS_IMETHOD GetDepth(uint32_t* pDepth) override {
+ *pDepth = m_depth;
+ return NS_OK;
+ }
+ NS_IMETHOD SetDepth(uint32_t theDepth) override {
+ m_depth = theDepth;
+ return NS_OK;
+ }
+
+ /* attribute unsigned long size; */
+ NS_IMETHOD GetSize(uint32_t* pSize) override {
+ *pSize = m_size;
+ return NS_OK;
+ }
+ NS_IMETHOD SetSize(uint32_t theSize) override {
+ m_size = theSize;
+ return NS_OK;
+ }
+
+ /* attribute wstring displayName; */
+ NS_IMETHOD GetDisplayName(char16_t** pName) override {
+ *pName = ToNewUnicode(m_displayName);
+ return NS_OK;
+ }
+ NS_IMETHOD SetDisplayName(const char16_t* pName) override {
+ m_displayName = pName;
+ return NS_OK;
+ }
+
+ /* attribute boolean import; */
+ NS_IMETHOD GetImport(bool* pImport) override {
+ *pImport = m_import;
+ return NS_OK;
+ }
+ NS_IMETHOD SetImport(bool doImport) override {
+ m_import = doImport;
+ return NS_OK;
+ }
+
+ /* readonly attribute nsIFile file; */
+ NS_IMETHOD GetFile(nsIFile** aFile) override {
+ if (m_pFile) {
+ NS_ADDREF(*aFile = m_pFile);
+ return NS_OK;
+ } else
+ return NS_ERROR_FAILURE;
+ }
+
+ nsImportMailboxDescriptor();
+
+ static nsresult Create(REFNSIID aIID, void** aResult);
+
+ private:
+ virtual ~nsImportMailboxDescriptor() {}
+ uint32_t m_id; // used by creator of the structure
+ uint32_t m_depth; // depth in the hierarchy
+ nsString m_displayName; // name of this mailbox
+ nsCOMPtr<nsIFile> m_pFile; // source file (if applicable)
+ uint32_t m_size;
+ bool m_import; // import it or not?
+};
+
+#endif
diff --git a/comm/mailnews/import/src/nsImportScanFile.cpp b/comm/mailnews/import/src/nsImportScanFile.cpp
new file mode 100644
index 0000000000..79971ae10a
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportScanFile.cpp
@@ -0,0 +1,154 @@
+/* -*- 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 "nscore.h"
+#include "nsImportScanFile.h"
+#include "ImportCharSet.h"
+
+nsImportScanFile::nsImportScanFile() {
+ m_allocated = false;
+ m_eof = false;
+ m_pBuf = nullptr;
+}
+
+nsImportScanFile::~nsImportScanFile() {
+ if (m_allocated) CleanUpScan();
+}
+
+void nsImportScanFile::InitScan(nsIInputStream* pInputStream, uint8_t* pBuf,
+ uint32_t sz) {
+ m_pInputStream = pInputStream;
+ m_pBuf = pBuf;
+ m_bufSz = sz;
+ m_bytesInBuf = 0;
+ m_pos = 0;
+}
+
+void nsImportScanFile::CleanUpScan(void) {
+ m_pInputStream = nullptr;
+ if (m_allocated) {
+ delete[] m_pBuf;
+ m_pBuf = NULL;
+ }
+}
+
+void nsImportScanFile::ShiftBuffer(void) {
+ uint8_t* pTop;
+ uint8_t* pCurrent;
+
+ if (m_pos < m_bytesInBuf) {
+ pTop = m_pBuf;
+ pCurrent = pTop + m_pos;
+ uint32_t cnt = m_bytesInBuf - m_pos;
+ while (cnt) {
+ *pTop = *pCurrent;
+ pTop++;
+ pCurrent++;
+ cnt--;
+ }
+ }
+
+ m_bytesInBuf -= m_pos;
+ m_pos = 0;
+}
+
+bool nsImportScanFile::FillBufferFromFile(void) {
+ uint64_t available;
+ nsresult rv = m_pInputStream->Available(&available);
+ if (NS_FAILED(rv)) return false;
+
+ // Fill up a buffer and scan it
+ ShiftBuffer();
+
+ // Read in some more bytes
+ uint32_t cnt = m_bufSz - m_bytesInBuf;
+ // To distinguish from disk errors
+ // Check first for end of file?
+ // Set a done flag if true...
+ uint32_t read;
+ char* pBuf = (char*)m_pBuf;
+ pBuf += m_bytesInBuf;
+ rv = m_pInputStream->Read(pBuf, (int32_t)cnt, &read);
+
+ if (NS_FAILED(rv)) return false;
+ rv = m_pInputStream->Available(&available);
+ if (NS_FAILED(rv)) m_eof = true;
+
+ m_bytesInBuf += cnt;
+ return true;
+}
+
+bool nsImportScanFile::Scan(bool* pDone) {
+ uint64_t available;
+ nsresult rv = m_pInputStream->Available(&available);
+ if (NS_FAILED(rv)) {
+ if (m_pos < m_bytesInBuf) ScanBuffer(pDone);
+ *pDone = true;
+ return true;
+ }
+
+ // Fill up a buffer and scan it
+ if (!FillBufferFromFile()) return false;
+
+ return ScanBuffer(pDone);
+}
+
+bool nsImportScanFile::ScanBuffer(bool*) { return true; }
+
+bool nsImportScanFileLines::ScanBuffer(bool* pDone) {
+ // m_pos, m_bytesInBuf, m_eof, m_pBuf are relevant
+
+ uint32_t pos = m_pos;
+ uint32_t max = m_bytesInBuf;
+ uint8_t* pChar = m_pBuf + pos;
+ uint32_t startPos;
+
+ while (pos < max) {
+ if (m_needEol) {
+ // Find the next eol...
+ while ((pos < max) && (*pChar != ImportCharSet::cCRChar) &&
+ (*pChar != ImportCharSet::cLinefeedChar)) {
+ pos++;
+ pChar++;
+ }
+ m_pos = pos;
+ if (pos < max) m_needEol = false;
+ if (pos == max) // need more buffer for an end of line
+ break;
+ }
+ // Skip past any eol characters
+ while ((pos < max) && ((*pChar == ImportCharSet::cCRChar) ||
+ (*pChar == ImportCharSet::cLinefeedChar))) {
+ pos++;
+ pChar++;
+ }
+ m_pos = pos;
+ if (pos == max) break;
+ // Make sure we can find either the eof or the
+ // next end of line
+ startPos = pos;
+ while ((pos < max) && (*pChar != ImportCharSet::cCRChar) &&
+ (*pChar != ImportCharSet::cLinefeedChar)) {
+ pos++;
+ pChar++;
+ }
+
+ // Is line too big for our buffer?
+ if ((pos == max) && !m_eof) {
+ if (!m_pos) { // line too big for our buffer
+ m_pos = pos;
+ m_needEol = true;
+ }
+ break;
+ }
+
+ if (!ProcessLine(m_pBuf + startPos, pos - startPos, pDone)) {
+ return false;
+ }
+ m_pos = pos;
+ }
+
+ return true;
+}
diff --git a/comm/mailnews/import/src/nsImportScanFile.h b/comm/mailnews/import/src/nsImportScanFile.h
new file mode 100644
index 0000000000..e5704aaf36
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportScanFile.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 nsImportScanFile_h__
+#define nsImportScanFile_h__
+
+#include "mozilla/Attributes.h"
+#include "nsCOMPtr.h"
+#include "nsIInputStream.h"
+
+class nsImportScanFile {
+ public:
+ nsImportScanFile();
+ virtual ~nsImportScanFile();
+
+ void InitScan(nsIInputStream* pInputStream, uint8_t* pBuf, uint32_t sz);
+
+ void CleanUpScan(void);
+
+ virtual bool Scan(bool* pDone);
+
+ protected:
+ void ShiftBuffer(void);
+ bool FillBufferFromFile(void);
+ virtual bool ScanBuffer(bool* pDone);
+
+ protected:
+ nsCOMPtr<nsIInputStream> m_pInputStream;
+ uint8_t* m_pBuf;
+ uint32_t m_bufSz;
+ uint32_t m_bytesInBuf;
+ uint32_t m_pos;
+ bool m_eof;
+ bool m_allocated;
+};
+
+class nsImportScanFileLines : public nsImportScanFile {
+ public:
+ nsImportScanFileLines() { m_needEol = false; }
+
+ void ResetLineScan(void) { m_needEol = false; }
+
+ virtual bool ProcessLine(uint8_t* /* pLine */, uint32_t /* len */,
+ bool* /* pDone */) {
+ return true;
+ }
+
+ protected:
+ virtual bool ScanBuffer(bool* pDone) override;
+
+ bool m_needEol;
+};
+
+#endif /* nsImportScanFile_h__ */
diff --git a/comm/mailnews/import/src/nsImportService.cpp b/comm/mailnews/import/src/nsImportService.cpp
new file mode 100644
index 0000000000..062e7d664c
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportService.cpp
@@ -0,0 +1,293 @@
+/* -*- 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 "nsString.h"
+#include "nsMemory.h"
+#include "nsIImportModule.h"
+#include "nsIImportService.h"
+#include "nsImportMailboxDescriptor.h"
+#include "nsImportABDescriptor.h"
+#include "nsIImportGeneric.h"
+#include "nsImportFieldMap.h"
+#include "nsICategoryManager.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsThreadUtils.h"
+#include "ImportDebug.h"
+#include "nsImportService.h"
+#include "nsImportStringBundle.h"
+#include "nsCRTGlue.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsIMsgSend.h"
+#include "nsMsgUtils.h"
+#include "mozilla/SimpleEnumerator.h"
+
+mozilla::LazyLogModule IMPORTLOGMODULE("Import");
+
+////////////////////////////////////////////////////////////////////////
+
+nsImportService::nsImportService() {
+ IMPORT_LOG0("* nsImport Service Created\n");
+
+ m_didDiscovery = false;
+
+ nsresult rv = nsImportStringBundle::GetStringBundle(
+ IMPORT_MSGS_URL, getter_AddRefs(m_stringBundle));
+ if (NS_FAILED(rv))
+ IMPORT_LOG0("Failed to get string bundle for Importing Mail");
+}
+
+nsImportService::~nsImportService() {
+ IMPORT_LOG0("* nsImport Service Deleted\n");
+}
+
+NS_IMPL_ISUPPORTS(nsImportService, nsIImportService)
+
+NS_IMETHODIMP nsImportService::DiscoverModules(void) {
+ m_didDiscovery = false;
+ return DoDiscover();
+}
+
+NS_IMETHODIMP nsImportService::CreateNewFieldMap(nsIImportFieldMap** _retval) {
+ return nsImportFieldMap::Create(m_stringBundle, NS_GET_IID(nsIImportFieldMap),
+ (void**)_retval);
+}
+
+NS_IMETHODIMP nsImportService::CreateNewMailboxDescriptor(
+ nsIImportMailboxDescriptor** _retval) {
+ return nsImportMailboxDescriptor::Create(
+ NS_GET_IID(nsIImportMailboxDescriptor), (void**)_retval);
+}
+
+NS_IMETHODIMP nsImportService::CreateNewABDescriptor(
+ nsIImportABDescriptor** _retval) {
+ return nsImportABDescriptor::Create(NS_GET_IID(nsIImportABDescriptor),
+ (void**)_retval);
+}
+
+extern nsresult NS_NewGenericMail(nsIImportGeneric** aImportGeneric);
+
+NS_IMETHODIMP nsImportService::CreateNewGenericMail(
+ nsIImportGeneric** _retval) {
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!_retval) return NS_ERROR_NULL_POINTER;
+
+ return NS_NewGenericMail(_retval);
+}
+
+extern nsresult NS_NewGenericAddressBooks(nsIImportGeneric** aImportGeneric);
+
+NS_IMETHODIMP nsImportService::CreateNewGenericAddressBooks(
+ nsIImportGeneric** _retval) {
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!_retval) return NS_ERROR_NULL_POINTER;
+
+ return NS_NewGenericAddressBooks(_retval);
+}
+
+NS_IMETHODIMP nsImportService::GetModuleCount(const char* filter,
+ int32_t* _retval) {
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!_retval) return NS_ERROR_NULL_POINTER;
+
+ DoDiscover();
+
+ nsCString filterStr(filter);
+ int32_t count = 0;
+ for (auto& importModule : m_importModules) {
+ if (importModule.SupportsThings(filterStr)) count++;
+ }
+ *_retval = count;
+
+ return NS_OK;
+}
+
+ImportModuleDesc* nsImportService::GetImportModule(const char* filter,
+ int32_t index) {
+ DoDiscover();
+
+ nsCString filterStr(filter);
+ int32_t count = 0;
+ for (auto& importModule : m_importModules) {
+ if (importModule.SupportsThings(filterStr)) {
+ if (count++ == index) {
+ return &importModule;
+ }
+ }
+ }
+
+ return nullptr;
+}
+
+NS_IMETHODIMP nsImportService::GetModuleInfo(const char* filter, int32_t index,
+ nsAString& name,
+ nsAString& moduleDescription) {
+ ImportModuleDesc* importModule = GetImportModule(filter, index);
+ if (!importModule) return NS_ERROR_FAILURE;
+
+ name = importModule->GetName();
+ moduleDescription = importModule->GetDescription();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportService::GetModuleName(const char* filter, int32_t index,
+ nsAString& _retval) {
+ ImportModuleDesc* importModule = GetImportModule(filter, index);
+ if (!importModule) return NS_ERROR_FAILURE;
+
+ _retval = importModule->GetName();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsImportService::GetModuleDescription(const char* filter,
+ int32_t index,
+ nsAString& _retval) {
+ ImportModuleDesc* importModule = GetImportModule(filter, index);
+ if (!importModule) return NS_ERROR_FAILURE;
+
+ _retval = importModule->GetDescription();
+ return NS_OK;
+}
+
+class nsProxySendRunnable : public mozilla::Runnable {
+ public:
+ nsProxySendRunnable(
+ nsIMsgIdentity* aIdentity, nsIMsgCompFields* aMsgFields,
+ const char* attachment1_type, const nsACString& attachment1_body,
+ bool aIsDraft,
+ nsTArray<RefPtr<nsIMsgAttachedFile>> const& aLoadedAttachments,
+ nsTArray<RefPtr<nsIMsgEmbeddedImageData>> const& aEmbeddedAttachments,
+ nsIMsgSendListener* aListener);
+ NS_DECL_NSIRUNNABLE
+ private:
+ nsCOMPtr<nsIMsgIdentity> m_identity;
+ nsCOMPtr<nsIMsgCompFields> m_compFields;
+ bool m_isDraft;
+ nsCString m_bodyType;
+ nsCString m_body;
+ nsTArray<RefPtr<nsIMsgAttachedFile>> m_loadedAttachments;
+ nsTArray<RefPtr<nsIMsgEmbeddedImageData>> m_embeddedAttachments;
+ nsCOMPtr<nsIMsgSendListener> m_listener;
+};
+
+nsProxySendRunnable::nsProxySendRunnable(
+ nsIMsgIdentity* aIdentity, nsIMsgCompFields* aMsgFields,
+ const char* aBodyType, const nsACString& aBody, bool aIsDraft,
+ nsTArray<RefPtr<nsIMsgAttachedFile>> const& aLoadedAttachments,
+ nsTArray<RefPtr<nsIMsgEmbeddedImageData>> const& aEmbeddedAttachments,
+ nsIMsgSendListener* aListener)
+ : mozilla::Runnable("nsProxySendRunnable"),
+ m_identity(aIdentity),
+ m_compFields(aMsgFields),
+ m_isDraft(aIsDraft),
+ m_bodyType(aBodyType),
+ m_body(aBody),
+ m_loadedAttachments(aLoadedAttachments.Clone()),
+ m_embeddedAttachments(aEmbeddedAttachments.Clone()),
+ m_listener(aListener) {}
+
+NS_IMETHODIMP nsProxySendRunnable::Run() {
+ nsresult rv;
+ nsCOMPtr<nsIMsgSend> msgSend =
+ do_CreateInstance("@mozilla.org/messengercompose/send;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return msgSend->CreateRFC822Message(
+ m_identity, m_compFields, m_bodyType.get(), m_body, m_isDraft,
+ m_loadedAttachments, m_embeddedAttachments, m_listener);
+}
+
+NS_IMETHODIMP
+nsImportService::CreateRFC822Message(
+ nsIMsgIdentity* aIdentity, nsIMsgCompFields* aMsgFields,
+ const char* aBodyType, const nsACString& aBody, bool aIsDraft,
+ nsTArray<RefPtr<nsIMsgAttachedFile>> const& aLoadedAttachments,
+ nsTArray<RefPtr<nsIMsgEmbeddedImageData>> const& aEmbeddedAttachments,
+ nsIMsgSendListener* aListener) {
+ RefPtr<nsProxySendRunnable> runnable = new nsProxySendRunnable(
+ aIdentity, aMsgFields, aBodyType, aBody, aIsDraft, aLoadedAttachments,
+ aEmbeddedAttachments, aListener);
+ // invoke the callback
+ return NS_DispatchToMainThread(runnable);
+}
+
+NS_IMETHODIMP nsImportService::GetModule(const char* filter, int32_t index,
+ nsIImportModule** _retval) {
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!_retval) return NS_ERROR_NULL_POINTER;
+ *_retval = nullptr;
+
+ ImportModuleDesc* importModule = GetImportModule(filter, index);
+ if (!importModule) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIImportModule> modulePtr = importModule->GetModule();
+ modulePtr.forget(_retval);
+ return NS_OK;
+}
+
+nsresult nsImportService::DoDiscover(void) {
+ if (m_didDiscovery) return NS_OK;
+
+ m_importModules.Clear();
+
+ nsresult rv;
+
+ nsCOMPtr<nsICategoryManager> catMan =
+ do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsISimpleEnumerator> e;
+ rv = catMan->EnumerateCategory("mailnewsimport", getter_AddRefs(e));
+ NS_ENSURE_SUCCESS(rv, rv);
+ for (auto& key : mozilla::SimpleEnumerator<nsISupportsCString>(e)) {
+ nsCString keyStr;
+ key->ToString(getter_Copies(keyStr));
+ nsCString contractIdStr;
+ rv = catMan->GetCategoryEntry("mailnewsimport", keyStr, contractIdStr);
+ if (NS_SUCCEEDED(rv)) LoadModuleInfo(contractIdStr);
+ }
+
+ m_didDiscovery = true;
+
+ return NS_OK;
+}
+
+nsresult nsImportService::LoadModuleInfo(const nsCString& contractId) {
+ // load the component and get all of the info we need from it....
+ nsresult rv;
+ nsCOMPtr<nsIImportModule> module = do_CreateInstance(contractId.get(), &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ m_importModules.EmplaceBack(module);
+
+ return NS_OK;
+}
+
+ImportModuleDesc::ImportModuleDesc(nsIImportModule* importModule)
+ : m_pModule(importModule) {
+ nsresult rv;
+ rv = importModule->GetName(getter_Copies(m_name));
+ if (NS_FAILED(rv)) m_name.AssignLiteral("Unknown");
+
+ rv = importModule->GetDescription(getter_Copies(m_description));
+ if (NS_FAILED(rv)) m_description.AssignLiteral("Unknown description");
+
+ importModule->GetSupports(getter_Copies(m_supports));
+
+#ifdef IMPORT_DEBUG
+ IMPORT_LOG3("* nsImportService registered import module: %s, %s, %s\n",
+ NS_LossyConvertUTF16toASCII(m_name).get(),
+ NS_LossyConvertUTF16toASCII(m_description).get(),
+ m_supports.get());
+#endif
+}
+
+bool ImportModuleDesc::SupportsThings(const nsACString& thing) {
+ for (auto& item : m_supports.Split(',')) {
+ if (item == thing) return true;
+ }
+ return false;
+}
diff --git a/comm/mailnews/import/src/nsImportService.h b/comm/mailnews/import/src/nsImportService.h
new file mode 100644
index 0000000000..37dd95b935
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportService.h
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 nsImportService_h__
+#define nsImportService_h__
+
+#include "nsString.h"
+#include "nsMemory.h"
+#include "nsIImportModule.h"
+#include "nsIImportService.h"
+#include "nsIStringBundle.h"
+#include "nsTArray.h"
+
+class ImportModuleDesc {
+ public:
+ explicit ImportModuleDesc(nsIImportModule* importModule);
+
+ const nsAString& GetName(void) { return m_name; }
+ const nsAString& GetDescription(void) { return m_description; }
+
+ nsCOMPtr<nsIImportModule>& GetModule() { return m_pModule; }
+
+ bool SupportsThings(const nsACString& pThings);
+
+ private:
+ nsString m_name;
+ nsString m_description;
+ nsCString m_supports;
+ nsCOMPtr<nsIImportModule> m_pModule;
+};
+
+class nsImportService : public nsIImportService {
+ public:
+ nsImportService();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIIMPORTSERVICE
+
+ private:
+ virtual ~nsImportService();
+ nsresult LoadModuleInfo(const nsCString& contractId);
+ nsresult DoDiscover(void);
+ ImportModuleDesc* GetImportModule(const char* filter, int32_t index);
+
+ private:
+ AutoTArray<ImportModuleDesc, 10> m_importModules;
+ bool m_didDiscovery;
+ nsCString m_sysCharset;
+ nsCOMPtr<nsIStringBundle> m_stringBundle;
+};
+
+#endif // nsImportService_h__
diff --git a/comm/mailnews/import/src/nsImportStringBundle.cpp b/comm/mailnews/import/src/nsImportStringBundle.cpp
new file mode 100644
index 0000000000..0ef79bfcfc
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportStringBundle.cpp
@@ -0,0 +1,67 @@
+/* -*- 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 "prprf.h"
+#include "prmem.h"
+#include "nsCOMPtr.h"
+#include "nsIStringBundle.h"
+#include "nsImportStringBundle.h"
+#include "nsServiceManagerUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Components.h"
+
+nsresult nsImportStringBundle::GetStringBundle(const char* aPropertyURL,
+ nsIStringBundle** aBundle) {
+ nsresult rv;
+
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(sBundleService, NS_ERROR_UNEXPECTED);
+ rv = sBundleService->CreateBundle(aPropertyURL, aBundle);
+
+ return rv;
+}
+
+void nsImportStringBundle::GetStringByID(int32_t aStringID,
+ nsIStringBundle* aBundle,
+ nsString& aResult) {
+ aResult.Adopt(GetStringByID(aStringID, aBundle));
+}
+
+char16_t* nsImportStringBundle::GetStringByID(int32_t aStringID,
+ nsIStringBundle* aBundle) {
+ if (aBundle) {
+ nsAutoString str;
+ nsresult rv = aBundle->GetStringFromID(aStringID, str);
+ if (NS_SUCCEEDED(rv)) return ToNewUnicode(str);
+ }
+
+ nsString resultString(u"[StringID "_ns);
+ resultString.AppendInt(aStringID);
+ resultString.AppendLiteral("?]");
+
+ return ToNewUnicode(resultString);
+}
+
+void nsImportStringBundle::GetStringByName(const char* aName,
+ nsIStringBundle* aBundle,
+ nsString& aResult) {
+ aResult.Adopt(GetStringByName(aName, aBundle));
+}
+
+char16_t* nsImportStringBundle::GetStringByName(const char* aName,
+ nsIStringBundle* aBundle) {
+ if (aBundle) {
+ nsAutoString str;
+ nsresult rv = aBundle->GetStringFromName(aName, str);
+ if (NS_SUCCEEDED(rv)) return ToNewUnicode(str);
+ }
+
+ nsString resultString(u"[StringName "_ns);
+ resultString.Append(NS_ConvertUTF8toUTF16(aName).get());
+ resultString.AppendLiteral("?]");
+
+ return ToNewUnicode(resultString);
+}
diff --git a/comm/mailnews/import/src/nsImportStringBundle.h b/comm/mailnews/import/src/nsImportStringBundle.h
new file mode 100644
index 0000000000..ba234888fc
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportStringBundle.h
@@ -0,0 +1,43 @@
+/* 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 _nsImportStringBundle_H__
+#define _nsImportStringBundle_H__
+
+#include "nsString.h"
+
+class nsIStringBundle;
+
+class nsImportStringBundle {
+ public:
+ static char16_t* GetStringByID(int32_t aStringID,
+ nsIStringBundle* aBundle = nullptr);
+ static void GetStringByID(int32_t aStringID, nsIStringBundle* aBundle,
+ nsString& aResult);
+ static char16_t* GetStringByName(const char* aName,
+ nsIStringBundle* aBundle = nullptr);
+ static void GetStringByName(const char* aName, nsIStringBundle* aBundle,
+ nsString& aResult);
+ static nsresult GetStringBundle(const char* aPropertyURL,
+ nsIStringBundle** aBundle);
+};
+
+#define IMPORT_MSGS_URL "chrome://messenger/locale/importMsgs.properties"
+
+#define IMPORT_NO_ADDRBOOKS 2000
+#define IMPORT_ERROR_AB_NOTINITIALIZED 2001
+#define IMPORT_ERROR_AB_NOTHREAD 2002
+#define IMPORT_ERROR_GETABOOK 2003
+#define IMPORT_NO_MAILBOXES 2004
+#define IMPORT_ERROR_MB_NOTINITIALIZED 2005
+#define IMPORT_ERROR_MB_NOTHREAD 2006
+#define IMPORT_ERROR_MB_NOPROXY 2007
+#define IMPORT_ERROR_MB_FINDCHILD 2008
+#define IMPORT_ERROR_MB_CREATE 2009
+#define IMPORT_ERROR_MB_NODESTFOLDER 2010
+
+#define IMPORT_FIELD_DESC_START 2100
+#define IMPORT_FIELD_DESC_END 2136
+
+#endif /* _nsImportStringBundle_H__ */
diff --git a/comm/mailnews/import/src/nsImportTranslator.cpp b/comm/mailnews/import/src/nsImportTranslator.cpp
new file mode 100644
index 0000000000..f988e7035d
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportTranslator.cpp
@@ -0,0 +1,308 @@
+/* -*- 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 "ImportOutFile.h"
+#include "nsImportTranslator.h"
+
+#include "ImportCharSet.h"
+
+bool nsImportTranslator::ConvertToFile(const uint8_t* pIn, uint32_t inLen,
+ ImportOutFile* pOutFile,
+ uint32_t* pProcessed) {
+ if (pProcessed) *pProcessed = inLen;
+ return (pOutFile->WriteData(pIn, inLen));
+}
+
+void CMHTranslator::ConvertBuffer(const uint8_t* pIn, uint32_t inLen,
+ uint8_t* pOut) {
+ while (inLen) {
+ if (!ImportCharSet::IsUSAscii(*pIn) ||
+ ImportCharSet::Is822SpecialChar(*pIn) ||
+ ImportCharSet::Is822CtlChar(*pIn) ||
+ (*pIn == ImportCharSet::cSpaceChar) || (*pIn == '*') ||
+ (*pIn == '\'') || (*pIn == '%')) {
+ // needs to be encode as %hex val
+ *pOut = '%';
+ pOut++;
+ ImportCharSet::ByteToHex(*pIn, pOut);
+ pOut += 2;
+ } else {
+ *pOut = *pIn;
+ pOut++;
+ }
+ pIn++;
+ inLen--;
+ }
+ *pOut = 0;
+}
+
+bool CMHTranslator::ConvertToFile(const uint8_t* pIn, uint32_t inLen,
+ ImportOutFile* pOutFile,
+ uint32_t* pProcessed) {
+ uint8_t hex[2];
+ while (inLen) {
+ if (!ImportCharSet::IsUSAscii(*pIn) ||
+ ImportCharSet::Is822SpecialChar(*pIn) ||
+ ImportCharSet::Is822CtlChar(*pIn) ||
+ (*pIn == ImportCharSet::cSpaceChar) || (*pIn == '*') ||
+ (*pIn == '\'') || (*pIn == '%')) {
+ // needs to be encode as %hex val
+ if (!pOutFile->WriteByte('%')) return false;
+ ImportCharSet::ByteToHex(*pIn, hex);
+ if (!pOutFile->WriteData(hex, 2)) return false;
+ } else {
+ if (!pOutFile->WriteByte(*pIn)) return false;
+ }
+ pIn++;
+ inLen--;
+ }
+
+ if (pProcessed) *pProcessed = inLen;
+
+ return true;
+}
+
+bool C2047Translator::ConvertToFileQ(const uint8_t* pIn, uint32_t inLen,
+ ImportOutFile* pOutFile,
+ uint32_t* pProcessed) {
+ if (!inLen) return true;
+
+ int maxLineLen = 64;
+ int curLineLen = m_startLen;
+ bool startLine = true;
+
+ uint8_t hex[2];
+ while (inLen) {
+ if (startLine) {
+ if (!pOutFile->WriteStr(" =?")) return false;
+ if (!pOutFile->WriteStr(m_charset.get())) return false;
+ if (!pOutFile->WriteStr("?q?")) return false;
+ curLineLen += (6 + m_charset.Length());
+ startLine = false;
+ }
+
+ if (!ImportCharSet::IsUSAscii(*pIn) ||
+ ImportCharSet::Is822SpecialChar(*pIn) ||
+ ImportCharSet::Is822CtlChar(*pIn) ||
+ (*pIn == ImportCharSet::cSpaceChar) || (*pIn == '?') || (*pIn == '=')) {
+ // needs to be encode as =hex val
+ if (!pOutFile->WriteByte('=')) return false;
+ ImportCharSet::ByteToHex(*pIn, hex);
+ if (!pOutFile->WriteData(hex, 2)) return false;
+ curLineLen += 3;
+ } else {
+ if (!pOutFile->WriteByte(*pIn)) return false;
+ curLineLen++;
+ }
+ pIn++;
+ inLen--;
+ if (curLineLen > maxLineLen) {
+ if (!pOutFile->WriteStr("?=")) return false;
+ if (inLen) {
+ if (!pOutFile->WriteStr("\x0D\x0A ")) return false;
+ }
+
+ startLine = true;
+ curLineLen = 0;
+ }
+ }
+
+ if (!startLine) {
+ // end the encoding!
+ if (!pOutFile->WriteStr("?=")) return false;
+ }
+
+ if (pProcessed) *pProcessed = inLen;
+
+ return true;
+}
+
+bool C2047Translator::ConvertToFile(const uint8_t* pIn, uint32_t inLen,
+ ImportOutFile* pOutFile,
+ uint32_t* pProcessed) {
+ if (m_useQuotedPrintable)
+ return ConvertToFileQ(pIn, inLen, pOutFile, pProcessed);
+
+ if (!inLen) return true;
+
+ int maxLineLen = 64;
+ int curLineLen = m_startLen;
+ bool startLine = true;
+ int encodeMax;
+ uint8_t* pEncoded = new uint8_t[maxLineLen * 2];
+
+ while (inLen) {
+ if (startLine) {
+ if (!pOutFile->WriteStr(" =?")) {
+ delete[] pEncoded;
+ return false;
+ }
+ if (!pOutFile->WriteStr(m_charset.get())) {
+ delete[] pEncoded;
+ return false;
+ }
+ if (!pOutFile->WriteStr("?b?")) {
+ delete[] pEncoded;
+ return false;
+ }
+ curLineLen += (6 + m_charset.Length());
+ startLine = false;
+ }
+ encodeMax = maxLineLen - curLineLen;
+ encodeMax *= 3;
+ encodeMax /= 4;
+ if ((uint32_t)encodeMax > inLen) encodeMax = (int)inLen;
+
+ // encode the line, end the line
+ // then continue. Update curLineLen, pIn, startLine, and inLen
+ UMimeEncode::ConvertBuffer(pIn, encodeMax, pEncoded, maxLineLen, maxLineLen,
+ "\x0D\x0A");
+
+ if (!pOutFile->WriteStr((const char*)pEncoded)) {
+ delete[] pEncoded;
+ return false;
+ }
+
+ pIn += encodeMax;
+ inLen -= encodeMax;
+ startLine = true;
+ curLineLen = 0;
+ if (!pOutFile->WriteStr("?=")) {
+ delete[] pEncoded;
+ return false;
+ }
+ if (inLen) {
+ if (!pOutFile->WriteStr("\x0D\x0A ")) {
+ delete[] pEncoded;
+ return false;
+ }
+ }
+ }
+
+ delete[] pEncoded;
+
+ if (pProcessed) *pProcessed = inLen;
+
+ return true;
+}
+
+uint32_t UMimeEncode::GetBufferSize(uint32_t inBytes) {
+ // it takes 4 base64 bytes to represent 3 regular bytes
+ inBytes += 3;
+ inBytes /= 3;
+ inBytes *= 4;
+ // This should be plenty, but just to be safe
+ inBytes += 4;
+
+ // now allow for end of line characters
+ inBytes += ((inBytes + 39) / 40) * 4;
+
+ return inBytes;
+}
+
+static uint8_t gBase64[] =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+uint32_t UMimeEncode::ConvertBuffer(const uint8_t* pIn, uint32_t inLen,
+ uint8_t* pOut, uint32_t maxLen,
+ uint32_t firstLineLen,
+ const char* pEolStr) {
+ uint32_t pos = 0;
+ uint32_t len = 0;
+ uint32_t lineLen = 0;
+ uint32_t maxLine = firstLineLen;
+ int eolLen = 0;
+ if (pEolStr) eolLen = strlen(pEolStr);
+
+ while ((pos + 2) < inLen) {
+ // Encode 3 bytes
+ *pOut = gBase64[*pIn >> 2];
+ pOut++;
+ len++;
+ lineLen++;
+ *pOut = gBase64[(((*pIn) & 0x3) << 4) | (((*(pIn + 1)) & 0xF0) >> 4)];
+ pIn++;
+ pOut++;
+ len++;
+ lineLen++;
+ *pOut = gBase64[(((*pIn) & 0xF) << 2) | (((*(pIn + 1)) & 0xC0) >> 6)];
+ pIn++;
+ pOut++;
+ len++;
+ lineLen++;
+ *pOut = gBase64[(*pIn) & 0x3F];
+ pIn++;
+ pOut++;
+ len++;
+ lineLen++;
+ pos += 3;
+ if (lineLen >= maxLine) {
+ lineLen = 0;
+ maxLine = maxLen;
+ if (pEolStr) {
+ memcpy(pOut, pEolStr, eolLen);
+ pOut += eolLen;
+ len += eolLen;
+ }
+ }
+ }
+
+ if ((pos < inLen) && ((lineLen + 3) > maxLine)) {
+ lineLen = 0;
+ maxLine = maxLen;
+ if (pEolStr) {
+ memcpy(pOut, pEolStr, eolLen);
+ pOut += eolLen;
+ len += eolLen;
+ }
+ }
+
+ if (pos < inLen) {
+ // Get the last few bytes!
+ *pOut = gBase64[*pIn >> 2];
+ pOut++;
+ len++;
+ pos++;
+ if (pos < inLen) {
+ *pOut = gBase64[(((*pIn) & 0x3) << 4) | (((*(pIn + 1)) & 0xF0) >> 4)];
+ pIn++;
+ pOut++;
+ pos++;
+ len++;
+ if (pos < inLen) {
+ // Should be dead code!! (Then why is it here doofus?)
+ *pOut = gBase64[(((*pIn) & 0xF) << 2) | (((*(pIn + 1)) & 0xC0) >> 6)];
+ pIn++;
+ pOut++;
+ len++;
+ *pOut = gBase64[(*pIn) & 0x3F];
+ pos++;
+ pOut++;
+ len++;
+ } else {
+ *pOut = gBase64[(((*pIn) & 0xF) << 2)];
+ pOut++;
+ len++;
+ *pOut = '=';
+ pOut++;
+ len++;
+ }
+ } else {
+ *pOut = gBase64[(((*pIn) & 0x3) << 4)];
+ pOut++;
+ len++;
+ *pOut = '=';
+ pOut++;
+ len++;
+ *pOut = '=';
+ pOut++;
+ len++;
+ }
+ }
+
+ *pOut = 0;
+
+ return len;
+}
diff --git a/comm/mailnews/import/src/nsImportTranslator.h b/comm/mailnews/import/src/nsImportTranslator.h
new file mode 100644
index 0000000000..c475700ad6
--- /dev/null
+++ b/comm/mailnews/import/src/nsImportTranslator.h
@@ -0,0 +1,87 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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 nsImportTranslator_h___
+#define nsImportTranslator_h___
+
+#include "mozilla/Attributes.h"
+#include "nscore.h"
+#include "nsString.h"
+#include "nsCOMPtr.h"
+
+class ImportOutFile;
+
+class UMimeEncode {
+ public:
+ static uint32_t GetBufferSize(uint32_t inByes);
+ static uint32_t ConvertBuffer(const uint8_t* pIn, uint32_t inLen,
+ uint8_t* pOut, uint32_t maxLen = 72,
+ uint32_t firstLineLen = 72,
+ const char* pEolStr = nullptr);
+};
+
+class nsImportTranslator {
+ public:
+ virtual ~nsImportTranslator() {}
+ virtual bool Supports8bitEncoding(void) { return false; }
+ virtual uint32_t GetMaxBufferSize(uint32_t inLen) { return inLen + 1; }
+ virtual void ConvertBuffer(const uint8_t* pIn, uint32_t inLen,
+ uint8_t* pOut) {
+ memcpy(pOut, pIn, inLen);
+ pOut[inLen] = 0;
+ }
+ virtual bool ConvertToFile(const uint8_t* pIn, uint32_t inLen,
+ ImportOutFile* pOutFile,
+ uint32_t* pProcessed = nullptr);
+ virtual bool FinishConvertToFile(ImportOutFile* /* pOutFile */) {
+ return true;
+ }
+
+ virtual void GetCharset(nsCString& charSet) { charSet = "us-ascii"; }
+ virtual void GetLanguage(nsCString& lang) { lang = "en"; }
+ virtual void GetEncoding(nsCString& encoding) { encoding.Truncate(); }
+};
+
+// Specialized encoder, not a valid language translator, used for Mime headers.
+// rfc2231
+class CMHTranslator : public nsImportTranslator {
+ public:
+ virtual uint32_t GetMaxBufferSize(uint32_t inLen) override {
+ return (inLen * 3) + 1;
+ }
+ virtual void ConvertBuffer(const uint8_t* pIn, uint32_t inLen,
+ uint8_t* pOut) override;
+ virtual bool ConvertToFile(const uint8_t* pIn, uint32_t inLen,
+ ImportOutFile* pOutFile,
+ uint32_t* pProcessed = nullptr) override;
+};
+
+// Specialized encoder, not a valid language translator, used for mail headers
+// rfc2047
+class C2047Translator : public nsImportTranslator {
+ public:
+ virtual ~C2047Translator() {}
+
+ C2047Translator(const char* pCharset, uint32_t headerLen) {
+ m_charset = pCharset;
+ m_startLen = headerLen;
+ m_useQuotedPrintable = false;
+ }
+
+ void SetUseQuotedPrintable(void) { m_useQuotedPrintable = true; }
+
+ virtual bool ConvertToFile(const uint8_t* pIn, uint32_t inLen,
+ ImportOutFile* pOutFile,
+ uint32_t* pProcessed = nullptr) override;
+ bool ConvertToFileQ(const uint8_t* pIn, uint32_t inLen,
+ ImportOutFile* pOutFile, uint32_t* pProcessed);
+
+ protected:
+ bool m_useQuotedPrintable;
+ nsCString m_charset;
+ uint32_t m_startLen;
+};
+
+#endif /* nsImportTranslator_h__ */
diff --git a/comm/mailnews/import/src/nsOutlookCompose.cpp b/comm/mailnews/import/src/nsOutlookCompose.cpp
new file mode 100644
index 0000000000..1098dcbab7
--- /dev/null
+++ b/comm/mailnews/import/src/nsOutlookCompose.cpp
@@ -0,0 +1,669 @@
+/* -*- 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 "nscore.h"
+#include "prthread.h"
+#include "nsString.h"
+#include "nsMsgUtils.h"
+#include "nsUnicharUtils.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsIURI.h"
+#include "nsMsgI18N.h"
+#include "nsIOutputStream.h"
+#include "nsIInputStream.h"
+#include "nsMsgAttachmentData.h"
+#include "nsIMsgCompFields.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgSend.h"
+#include "nsImportEmbeddedImageData.h"
+#include "nsNetCID.h"
+#include "nsCRT.h"
+#include "nsOutlookCompose.h"
+#include "nsTArray.h"
+
+#include "ImportDebug.h"
+
+#include "nsMimeTypes.h"
+#include "nsMsgUtils.h"
+
+#include "nsIThread.h"
+#include "nsThreadUtils.h"
+
+#include "nsMsgMessageFlags.h"
+#include "nsMsgLocalFolderHdrs.h"
+
+#define NS_MSGCOMPFIELDS_CID \
+ { /* e64b0f51-0d7b-4e2f-8c60-3862ee8c174f */ \
+ 0xe64b0f51, 0x0d7b, 0x4e2f, { \
+ 0x8c, 0x60, 0x38, 0x62, 0xee, 0x8c, 0x17, 0x4f \
+ } \
+ }
+static NS_DEFINE_CID(kMsgCompFieldsCID, NS_MSGCOMPFIELDS_CID);
+
+#ifdef IMPORT_DEBUG
+static const char* p_test_headers =
+ "Received: from netppl.invalid (IDENT:monitor@get.freebsd.because.microsoftsucks.invalid [209.3.31.115])\n\
+ by mail4.sirius.invalid (8.9.1/8.9.1) with SMTP id PAA27232;\n\
+ Mon, 17 May 1999 15:27:43 -0700 (PDT)\n\
+Message-ID: <ikGD3jRTsKklU.Ggm2HmE2A1Jsqd0p@netppl.invalid>\n\
+From: \"adsales@qualityservice.invalid\" <adsales@qualityservice.invalid>\n\
+Subject: Re: Your College Diploma (36822)\n\
+Date: Mon, 17 May 1999 15:09:29 -0400 (EDT)\n\
+MIME-Version: 1.0\n\
+Content-Type: TEXT/PLAIN; charset=\"US-ASCII\"\n\
+Content-Transfer-Encoding: 7bit\n\
+X-UIDL: 19990517.152941\n\
+Status: RO";
+
+static const char* p_test_body =
+ "Hello world?\n\
+";
+#else
+# define p_test_headers nullptr
+# define p_test_body nullptr
+#endif
+
+#define kWhitespace "\b\t\r\n "
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+// A replacement for SimpleBufferTonyRCopiedTwice round-robin buffer and
+// ReadFileState classes
+class CCompositionFile {
+ public:
+ // fifoBuffer is used for memory allocation optimization
+ // convertCRs controls if we want to convert standalone CRs to CRLFs
+ CCompositionFile(nsIFile* aFile, void* fifoBuffer, uint32_t fifoBufferSize,
+ bool convertCRs = false);
+
+ explicit operator bool() const { return m_fileSize && m_pInputStream; }
+
+ // Reads up to and including the term sequence, or entire file if term isn't
+ // found termSize may be used to include NULLs in the terminator sequences.
+ // termSize value of -1 means "zero-terminated string" -> size is calculated
+ // with strlen
+ nsresult ToString(nsCString& dest, const char* term = 0, int termSize = -1);
+ nsresult ToStream(nsIOutputStream* dest, const char* term = 0,
+ int termSize = -1);
+ char LastChar() { return m_lastChar; }
+
+ private:
+ nsCOMPtr<nsIFile> m_pFile;
+ nsCOMPtr<nsIInputStream> m_pInputStream;
+ int64_t m_fileSize;
+ int64_t m_fileReadPos;
+ char* m_fifoBuffer;
+ uint32_t m_fifoBufferSize;
+ char* m_fifoBufferReadPos; // next character to read
+ char* m_fifoBufferWrittenPos; // if we have read less than buffer size then
+ // this will show it
+ bool m_convertCRs;
+ char m_lastChar;
+
+ nsresult EnsureHasDataInBuffer();
+ template <class _OutFn>
+ nsresult ToDest(_OutFn dest, const char* term, int termSize);
+};
+
+//////////////////////////////////////////////////////////////////////////////////////////////////
+
+// First off, a listener
+class OutlookSendListener : public nsIMsgSendListener {
+ public:
+ OutlookSendListener() { m_done = false; }
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ /* void OnStartSending (in string aMsgID, in uint32_t aMsgSize); */
+ NS_IMETHOD OnStartSending(const char* aMsgID, uint32_t aMsgSize) {
+ return NS_OK;
+ }
+
+ /* void OnProgress (in string aMsgID, in uint32_t aProgress, in uint32_t
+ * aProgressMax); */
+ NS_IMETHOD OnProgress(const char* aMsgID, uint32_t aProgress,
+ uint32_t aProgressMax) {
+ return NS_OK;
+ }
+
+ /* void OnStatus (in string aMsgID, in wstring aMsg); */
+ NS_IMETHOD OnStatus(const char* aMsgID, const char16_t* aMsg) {
+ return NS_OK;
+ }
+
+ /* void OnStopSending (in string aMsgID, in nsresult aStatus, in wstring aMsg,
+ * in nsIFile returnFile); */
+ NS_IMETHOD OnStopSending(const char* aMsgID, nsresult aStatus,
+ const char16_t* aMsg, nsIFile* returnFile) {
+ m_done = true;
+ m_location = returnFile;
+ return NS_OK;
+ }
+
+ /* void OnTransportSecurityError( in string msgID, in nsresult status, in
+ * nsITransportSecurityInfo secInfo, in ACString location); */
+ NS_IMETHOD OnTransportSecurityError(const char* msgID, nsresult status,
+ nsITransportSecurityInfo* secInfo,
+ nsACString const& location) {
+ return NS_OK;
+ }
+
+ /* void OnSendNotPerformed */
+ NS_IMETHOD OnSendNotPerformed(const char* aMsgID, nsresult aStatus) {
+ return NS_OK;
+ }
+
+ /* void OnGetDraftFolderURI (); */
+ NS_IMETHOD OnGetDraftFolderURI(const char* aMsgID,
+ const nsACString& aFolderURI) {
+ return NS_OK;
+ }
+
+ static nsresult CreateSendListener(nsIMsgSendListener** ppListener);
+ void Reset() {
+ m_done = false;
+ m_location = nullptr;
+ }
+
+ public:
+ virtual ~OutlookSendListener() {}
+
+ bool m_done;
+ nsCOMPtr<nsIFile> m_location;
+};
+
+NS_IMPL_ISUPPORTS(OutlookSendListener, nsIMsgSendListener)
+
+nsresult OutlookSendListener::CreateSendListener(
+ nsIMsgSendListener** ppListener) {
+ NS_ENSURE_ARG_POINTER(ppListener);
+ NS_ADDREF(*ppListener = new OutlookSendListener());
+ return NS_OK;
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////
+
+nsOutlookCompose::nsOutlookCompose() {
+ m_optimizationBuffer = new char[FILE_IO_BUFFER_SIZE];
+}
+
+nsOutlookCompose::~nsOutlookCompose() {
+ if (m_pIdentity) {
+ nsresult rv = m_pIdentity->ClearAllValues();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "failed to clear values");
+ if (NS_FAILED(rv)) return;
+ }
+ delete[] m_optimizationBuffer;
+}
+
+nsCOMPtr<nsIMsgIdentity> nsOutlookCompose::m_pIdentity = nullptr;
+
+nsresult nsOutlookCompose::CreateIdentity(void) {
+ if (m_pIdentity) return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgAccountManager> accMgr =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = accMgr->CreateIdentity(getter_AddRefs(m_pIdentity));
+ nsString name;
+ name.AssignLiteral("Import Identity");
+ if (m_pIdentity) {
+ m_pIdentity->SetFullName(name);
+ m_pIdentity->SetEmail("import@service.invalid"_ns);
+ }
+ return rv;
+}
+
+void nsOutlookCompose::ReleaseIdentity() { m_pIdentity = nullptr; }
+
+nsresult nsOutlookCompose::CreateComponents(void) {
+ nsresult rv = NS_OK;
+
+ m_pMsgFields = nullptr;
+ if (!m_pListener)
+ rv = OutlookSendListener::CreateSendListener(getter_AddRefs(m_pListener));
+
+ if (NS_SUCCEEDED(rv)) {
+ m_pMsgFields = do_CreateInstance(kMsgCompFieldsCID, &rv);
+ if (NS_SUCCEEDED(rv) && m_pMsgFields) {
+ // IMPORT_LOG0("nsOutlookCompose - CreateComponents succeeded\n");
+ m_pMsgFields->SetForcePlainText(false);
+ return NS_OK;
+ }
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsOutlookCompose::ComposeTheMessage(nsMsgDeliverMode mode,
+ CMapiMessage& msg,
+ nsIFile** pMsg) {
+ nsresult rv = CreateComponents();
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = CreateIdentity();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // IMPORT_LOG0("Outlook Compose created necessary components\n");
+
+ CMapiMessageHeaders* headers = msg.GetHeaders();
+
+ nsString unival;
+ headers->UnfoldValue(CMapiMessageHeaders::hdrFrom, unival,
+ msg.GetBodyCharset());
+ m_pMsgFields->SetFrom(unival);
+ headers->UnfoldValue(CMapiMessageHeaders::hdrTo, unival,
+ msg.GetBodyCharset());
+ m_pMsgFields->SetTo(unival);
+ headers->UnfoldValue(CMapiMessageHeaders::hdrSubject, unival,
+ msg.GetBodyCharset());
+ m_pMsgFields->SetSubject(unival);
+ headers->UnfoldValue(CMapiMessageHeaders::hdrCc, unival,
+ msg.GetBodyCharset());
+ m_pMsgFields->SetCc(unival);
+ headers->UnfoldValue(CMapiMessageHeaders::hdrReplyTo, unival,
+ msg.GetBodyCharset());
+ m_pMsgFields->SetReplyTo(unival);
+ m_pMsgFields->SetMessageId(headers->Value(CMapiMessageHeaders::hdrMessageID));
+
+ // We only use those headers that may need to be processed by Thunderbird
+ // to create a good rfc822 document, or need to be encoded (like To and Cc).
+ // These will replace the originals on import. All the other headers
+ // will be copied to the destination unaltered in CopyComposedMessage().
+
+ nsTArray<RefPtr<nsIMsgAttachedFile>> attachments;
+ msg.GetAttachments(attachments);
+
+ nsString bodyW;
+ bodyW = msg.GetBody();
+
+ nsTArray<RefPtr<nsIMsgEmbeddedImageData>> embeddedObjects;
+
+ if (msg.BodyIsHtml()) {
+ for (unsigned int i = 0; i < msg.EmbeddedAttachmentsCount(); i++) {
+ nsIURI* uri;
+ const char* cid;
+ const char* name;
+ if (msg.GetEmbeddedAttachmentInfo(i, &uri, &cid, &name)) {
+ nsCOMPtr<nsIMsgEmbeddedImageData> imageData =
+ new nsImportEmbeddedImageData(uri, nsDependentCString(cid),
+ nsDependentCString(name));
+ embeddedObjects.AppendElement(imageData);
+ }
+ }
+ }
+
+ nsCString bodyA;
+ const char* charset = msg.GetBodyCharset();
+ nsMsgI18NConvertFromUnicode(
+ charset ? nsDependentCString(charset) : EmptyCString(), bodyW, bodyA);
+
+ nsCOMPtr<nsIImportService> impService(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // nsIImportService::CreateRFC822Message creates a runnable and dispatches to
+ // the main thread.
+ rv = impService->CreateRFC822Message(
+ m_pIdentity, // dummy identity
+ m_pMsgFields, // message fields
+ msg.BodyIsHtml() ? "text/html" : "text/plain",
+ bodyA, // body pointer
+ mode == nsIMsgSend::nsMsgSaveAsDraft,
+ attachments, // local attachments
+ embeddedObjects,
+ m_pListener); // listener
+
+ OutlookSendListener* pListen =
+ static_cast<OutlookSendListener*>(m_pListener.get());
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1("*** Error, CreateAndSendMessage FAILED: 0x%x\n", rv);
+ } else {
+ // Wait for the listener to get done.
+ nsCOMPtr<nsIThread> thread(do_GetCurrentThread());
+ while (!pListen->m_done) {
+ NS_ProcessNextEvent(thread, true);
+ }
+ }
+
+ if (pListen->m_location) {
+ pListen->m_location->Clone(pMsg);
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ IMPORT_LOG0("*** Error, Outlook compose unsuccessful\n");
+ }
+
+ pListen->Reset();
+ return rv;
+}
+
+nsresult nsOutlookCompose::CopyComposedMessage(nsIFile* pSrc,
+ nsIOutputStream* pDst,
+ CMapiMessage& origMsg) {
+ // I'm unsure if we really need the convertCRs feature here.
+ // The headers in the file are generated by TB, the body was generated by rtf
+ // reader that always used CRLF, and the attachments were processed by TB
+ // either... However, I let it stay as it was in the original code.
+ CCompositionFile f(pSrc, m_optimizationBuffer, FILE_IO_BUFFER_SIZE, true);
+ if (!f) {
+ IMPORT_LOG0("*** Error, unexpected zero file size for composed message\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ // The "From ..." separates the messages. Without it, TB cannot see the
+ // messages in the mailbox file. Thus, the lines that look like "From ..." in
+ // the message must be escaped (see EscapeFromSpaceLine())
+ int fromLineLen;
+ const char* fromLine = origMsg.GetFromLine(fromLineLen);
+ uint32_t written;
+ nsresult rv = pDst->Write(fromLine, fromLineLen, &written);
+
+ // Bug 219269
+ // Write out the x-mozilla-status headers.
+ char statusLine[50];
+ uint32_t msgFlags = 0;
+ if (origMsg.IsRead()) msgFlags |= nsMsgMessageFlags::Read;
+ if (!origMsg.FullMessageDownloaded()) msgFlags |= nsMsgMessageFlags::Partial;
+ if (origMsg.IsForvarded()) msgFlags |= nsMsgMessageFlags::Forwarded;
+ if (origMsg.IsReplied()) msgFlags |= nsMsgMessageFlags::Replied;
+ if (origMsg.HasAttach()) msgFlags |= nsMsgMessageFlags::Attachment;
+ _snprintf(statusLine, sizeof(statusLine),
+ X_MOZILLA_STATUS_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF);
+ rv = pDst->Write(statusLine, strlen(statusLine), &written);
+ _snprintf(statusLine, sizeof(statusLine),
+ X_MOZILLA_STATUS2_FORMAT MSG_LINEBREAK, msgFlags & 0xFFFF0000);
+ rv = pDst->Write(statusLine, strlen(statusLine), &written);
+ // End Bug 219269
+
+ // well, isn't this a hoot!
+ // Read the headers from the new message, get the ones we like
+ // and write out only the headers we want from the new message,
+ // along with all of the other headers from the "old" message!
+
+ nsCString newHeadersStr;
+ rv = f.ToString(newHeadersStr,
+ MSG_LINEBREAK MSG_LINEBREAK); // Read all the headers
+ NS_ENSURE_SUCCESS(rv, rv);
+ UpdateHeaders(*origMsg.GetHeaders(),
+ CMapiMessageHeaders(newHeadersStr.get()));
+ rv = origMsg.GetHeaders()->ToStream(pDst);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // I use the terminating sequence here to avoid a possible situation when a
+ // "From " line gets split over two sequential reads and thus will not be
+ // escaped. This is done by reading up to CRLF (one line every time), though
+ // it may be slower
+
+ // Here I revert the changes that were made when the multipart/related message
+ // was composed in nsMsgSend::ProcessMultipartRelated() - the Content-Ids of
+ // attachments were replaced with new ones.
+ nsCString line;
+ while (NS_SUCCEEDED(f.ToString(line, MSG_LINEBREAK))) {
+ EscapeFromSpaceLine(pDst, const_cast<char*>(line.get()),
+ line.get() + line.Length());
+ }
+
+ if (f.LastChar() != nsCRT::LF) {
+ rv = pDst->Write(MSG_LINEBREAK, 2, &written);
+ if (written != 2) rv = NS_ERROR_FAILURE;
+ }
+
+ return rv;
+}
+
+nsresult nsOutlookCompose::ProcessMessage(nsMsgDeliverMode mode,
+ CMapiMessage& msg,
+ nsIOutputStream* pDst) {
+ nsCOMPtr<nsIFile> compositionFile;
+ nsresult rv = ComposeTheMessage(mode, msg, getter_AddRefs(compositionFile));
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = CopyComposedMessage(compositionFile, pDst, msg);
+ compositionFile->Remove(false);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error copying composed message to destination mailbox\n");
+ }
+ return rv;
+}
+
+void nsOutlookCompose::UpdateHeader(CMapiMessageHeaders& oldHeaders,
+ const CMapiMessageHeaders& newHeaders,
+ CMapiMessageHeaders::SpecialHeader header,
+ bool addIfAbsent) {
+ const char* oldVal = oldHeaders.Value(header);
+ if (!addIfAbsent && !oldVal) return;
+ const char* newVal = newHeaders.Value(header);
+ if (!newVal) return;
+ // Bug 145150 - Turn "Content-Type: application/ms-tnef" into "Content-Type:
+ // text/plain"
+ // so the body text can be displayed normally (instead of in an
+ // attachment).
+ if (header == CMapiMessageHeaders::hdrContentType)
+ if (stricmp(newVal, "application/ms-tnef") == 0) newVal = "text/plain";
+ // End Bug 145150
+ if (oldVal) {
+ if (strcmp(oldVal, newVal) == 0) return;
+ // Backup the old header value
+ nsCString backupHdrName("X-MozillaBackup-");
+ backupHdrName += CMapiMessageHeaders::SpecialName(header);
+ oldHeaders.SetValue(backupHdrName.get(), oldVal, false);
+ }
+ // Now replace it with new value
+ oldHeaders.SetValue(header, newVal);
+}
+
+void nsOutlookCompose::UpdateHeaders(CMapiMessageHeaders& oldHeaders,
+ const CMapiMessageHeaders& newHeaders) {
+ // Well, ain't this a peach?
+ // This is rather disgusting but there really isn't much to be done about
+ // it....
+
+ // 1. For each "old" header, replace it with the new one if we want,
+ // then right it out.
+ // 2. Then if we haven't written the "important" new headers, write them out
+ // 3. Terminate the headers with an extra eol.
+
+ // Important headers:
+ // "Content-type",
+ // "MIME-Version",
+ // "Content-transfer-encoding"
+ // consider "X-Accept-Language"?
+
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrContentType);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrMimeVersion);
+ UpdateHeader(oldHeaders, newHeaders,
+ CMapiMessageHeaders::hdrContentTransferEncoding);
+
+ // Other replaced headers (only if they exist):
+ // "From",
+ // "To",
+ // "Subject",
+ // "Reply-to",
+ // "Cc"
+
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrFrom, false);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrTo, false);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrSubject, false);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrReplyTo, false);
+ UpdateHeader(oldHeaders, newHeaders, CMapiMessageHeaders::hdrCc, false);
+}
+
+//////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+CCompositionFile::CCompositionFile(nsIFile* aFile, void* fifoBuffer,
+ uint32_t fifoBufferSize, bool convertCRs)
+ : m_pFile(aFile),
+ m_fileSize(0),
+ m_fileReadPos(0),
+ m_fifoBuffer(static_cast<char*>(fifoBuffer)),
+ m_fifoBufferSize(fifoBufferSize),
+ m_fifoBufferReadPos(static_cast<char*>(fifoBuffer)),
+ m_fifoBufferWrittenPos(static_cast<char*>(fifoBuffer)),
+ m_convertCRs(convertCRs),
+ m_lastChar(0) {
+ m_pFile->GetFileSize(&m_fileSize);
+ if (!m_fileSize) {
+ IMPORT_LOG0("*** Error, unexpected zero file size for composed message\n");
+ return;
+ }
+
+ nsresult rv =
+ NS_NewLocalFileInputStream(getter_AddRefs(m_pInputStream), m_pFile);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error, unable to open composed message file\n");
+ return;
+ }
+}
+
+nsresult CCompositionFile::EnsureHasDataInBuffer() {
+ if (m_fifoBufferReadPos < m_fifoBufferWrittenPos) return NS_OK;
+ // Populate the buffer with new data!
+ uint32_t count = m_fifoBufferSize;
+ if ((m_fileReadPos + count) > m_fileSize) count = m_fileSize - m_fileReadPos;
+ if (!count) return NS_ERROR_FAILURE; // Isn't there a "No more data" error?
+
+ uint32_t bytesRead = 0;
+ nsresult rv = m_pInputStream->Read(m_fifoBuffer, count, &bytesRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (!bytesRead || (bytesRead > count)) return NS_ERROR_FAILURE;
+ m_fifoBufferWrittenPos = m_fifoBuffer + bytesRead;
+ m_fifoBufferReadPos = m_fifoBuffer;
+ m_fileReadPos += bytesRead;
+
+ return NS_OK;
+}
+
+class CTermGuard {
+ public:
+ CTermGuard(const char* term, int termSize)
+ : m_term(term),
+ m_termSize(term ? ((termSize != -1) ? termSize : strlen(term)) : 0),
+ m_matchPos(0) {}
+
+ // if the guard triggered
+ inline bool IsTriggered() const {
+ return m_termSize && (m_matchPos == m_termSize);
+ }
+ // indicates if the guard has something to check
+ inline bool IsChecking() const { return m_termSize; }
+
+ bool Check(char c) // returns true only if the whole sequence passed
+ {
+ if (!m_termSize) // no guard
+ return false;
+ if (m_matchPos >= m_termSize) // check past success!
+ m_matchPos = 0;
+ if (m_term[m_matchPos] != c) // Reset sequence
+ m_matchPos = 0;
+ if (m_term[m_matchPos] == c) { // Sequence continues
+ return ++m_matchPos == m_termSize; // If equal then sequence complete!
+ }
+ // Sequence broken
+ return false;
+ }
+
+ private:
+ const char* m_term;
+ int m_termSize;
+ int m_matchPos;
+};
+
+template <class _OutFn>
+nsresult CCompositionFile::ToDest(_OutFn dest, const char* term, int termSize) {
+ CTermGuard guard(term, termSize);
+
+ // We already know the required string size, so reduce future reallocations
+ if (!guard.IsChecking() && !m_convertCRs)
+ dest.SetCapacity(m_fileSize - m_fileReadPos);
+
+ bool wasCR = false;
+ char c = 0;
+ nsresult rv;
+ while (NS_SUCCEEDED(rv = EnsureHasDataInBuffer())) {
+ if (!guard.IsChecking() && !m_convertCRs) { // Use efficient algorithm
+ dest.Append(m_fifoBufferReadPos,
+ m_fifoBufferWrittenPos - m_fifoBufferReadPos);
+ } else { // Check character by character to convert CRs and find
+ // terminating sequence
+ while (m_fifoBufferReadPos < m_fifoBufferWrittenPos) {
+ c = *m_fifoBufferReadPos;
+ if (m_convertCRs && wasCR) {
+ wasCR = false;
+ if (c != nsCRT::LF) {
+ const char kTmpLF = nsCRT::LF;
+ dest.Append(&kTmpLF, 1);
+ if (guard.Check(nsCRT::LF)) {
+ c = nsCRT::LF; // save last char
+ break;
+ }
+ }
+ }
+ dest.Append(&c, 1);
+ m_fifoBufferReadPos++;
+
+ if (guard.Check(c)) break;
+
+ if (m_convertCRs && (c == nsCRT::CR)) wasCR = true;
+ }
+ if (guard.IsTriggered()) break;
+ }
+ }
+
+ // check for trailing CR (only if caller didn't specify the terminating
+ // sequence that ends with CR - in this case he knows what he does!)
+ if (m_convertCRs && !guard.IsTriggered() && (c == nsCRT::CR)) {
+ c = nsCRT::LF;
+ dest.Append(&c, 1);
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ m_lastChar = c;
+ return NS_OK;
+}
+
+class dest_nsCString {
+ public:
+ explicit dest_nsCString(nsCString& str) : m_str(str) { m_str.Truncate(); }
+ void SetCapacity(int32_t sz) { m_str.SetCapacity(sz); }
+ nsresult Append(const char* buf, uint32_t count) {
+ m_str.Append(buf, count);
+ return NS_OK;
+ }
+
+ private:
+ nsCString& m_str;
+};
+
+class dest_Stream {
+ public:
+ explicit dest_Stream(nsIOutputStream* dest) : m_stream(dest) {}
+ void SetCapacity(int32_t) { /*do nothing*/
+ }
+ // const_cast here is due to the poor design of the EscapeFromSpaceLine()
+ // that requires a non-constant pointer while doesn't modify its data
+ nsresult Append(const char* buf, uint32_t count) {
+ return EscapeFromSpaceLine(m_stream, const_cast<char*>(buf), buf + count);
+ }
+
+ private:
+ nsIOutputStream* m_stream;
+};
+
+nsresult CCompositionFile::ToString(nsCString& dest, const char* term,
+ int termSize) {
+ return ToDest(dest_nsCString(dest), term, termSize);
+}
+
+nsresult CCompositionFile::ToStream(nsIOutputStream* dest, const char* term,
+ int termSize) {
+ return ToDest(dest_Stream(dest), term, termSize);
+}
diff --git a/comm/mailnews/import/src/nsOutlookCompose.h b/comm/mailnews/import/src/nsOutlookCompose.h
new file mode 100644
index 0000000000..8d49157e68
--- /dev/null
+++ b/comm/mailnews/import/src/nsOutlookCompose.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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 nsOutlookCompose_h__
+#define nsOutlookCompose_h__
+
+#include "nscore.h"
+#include "nsString.h"
+#include "nsIFile.h"
+#include "nsIImportService.h"
+#include "nsIOutputStream.h"
+
+class nsIMsgSend;
+class nsIMsgCompFields;
+class nsIMsgIdentity;
+class nsIMsgSendListener;
+
+#include "nsIMsgSend.h"
+#include "nsNetUtil.h"
+
+#include "MapiMessage.h"
+
+#include <list>
+
+///////////////////////////////////////////////////////////////////////////////////////////////
+
+class nsOutlookCompose {
+ public:
+ nsOutlookCompose();
+ ~nsOutlookCompose();
+
+ nsresult ProcessMessage(nsMsgDeliverMode mode, CMapiMessage& msg,
+ nsIOutputStream* pDst);
+ static nsresult CreateIdentity(void);
+ static void ReleaseIdentity(void);
+
+ private:
+ nsresult CreateComponents(void);
+
+ void UpdateHeader(CMapiMessageHeaders& oldHeaders,
+ const CMapiMessageHeaders& newHeaders,
+ CMapiMessageHeaders::SpecialHeader header,
+ bool addIfAbsent = true);
+ void UpdateHeaders(CMapiMessageHeaders& oldHeaders,
+ const CMapiMessageHeaders& newHeaders);
+
+ nsresult ComposeTheMessage(nsMsgDeliverMode mode, CMapiMessage& msg,
+ nsIFile** pMsg);
+ nsresult CopyComposedMessage(nsIFile* pSrc, nsIOutputStream* pDst,
+ CMapiMessage& origMsg);
+
+ private:
+ nsCOMPtr<nsIMsgSendListener> m_pListener;
+ nsCOMPtr<nsIMsgCompFields> m_pMsgFields;
+ static nsCOMPtr<nsIMsgIdentity> m_pIdentity;
+ char* m_optimizationBuffer;
+ nsCOMPtr<nsIImportService> m_pImportService;
+};
+
+#endif /* nsOutlookCompose_h__ */
diff --git a/comm/mailnews/import/src/nsOutlookImport.cpp b/comm/mailnews/import/src/nsOutlookImport.cpp
new file mode 100644
index 0000000000..ceafa2d7a1
--- /dev/null
+++ b/comm/mailnews/import/src/nsOutlookImport.cpp
@@ -0,0 +1,522 @@
+/* -*- 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/. */
+
+/*
+ Outlook (Win32) import mail and addressbook interfaces
+*/
+#include "nscore.h"
+#include "nsString.h"
+#include "nsMsgUtils.h"
+#include "nsIImportService.h"
+#include "nsOutlookImport.h"
+#include "nsIImportService.h"
+#include "nsIImportMail.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsIImportGeneric.h"
+#include "nsIImportAddressBooks.h"
+#include "nsIImportABDescriptor.h"
+#include "nsIImportFieldMap.h"
+#include "nsXPCOM.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIAbDirectory.h"
+#include "nsOutlookSettings.h"
+#include "nsTextFormatter.h"
+#include "nsOutlookStringBundle.h"
+#include "ImportDebug.h"
+#include "nsUnicharUtils.h"
+
+#include "nsOutlookMail.h"
+
+#include "MapiApi.h"
+
+class ImportOutlookMailImpl : public nsIImportMail {
+ public:
+ ImportOutlookMailImpl();
+
+ static nsresult Create(nsIImportMail** aImport);
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIImportmail interface
+
+ /* void GetDefaultLocation (out nsIFile location, out boolean found, out
+ * boolean userVerify); */
+ NS_IMETHOD GetDefaultLocation(nsIFile** location, bool* found,
+ bool* userVerify);
+
+ NS_IMETHOD FindMailboxes(nsIFile* location,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& boxes);
+
+ NS_IMETHOD ImportMailbox(nsIImportMailboxDescriptor* source,
+ nsIMsgFolder* dstFolder, char16_t** pErrorLog,
+ char16_t** pSuccessLog, bool* fatalError);
+
+ /* unsigned long GetImportProgress (); */
+ NS_IMETHOD GetImportProgress(uint32_t* _retval);
+
+ NS_IMETHOD TranslateFolderName(const nsAString& aFolderName,
+ nsAString& _retval);
+
+ public:
+ static void ReportSuccess(nsString& name, int32_t count, nsString* pStream);
+ static void ReportError(int32_t errorNum, nsString& name, nsString* pStream);
+ static void AddLinebreak(nsString* pStream);
+ static void SetLogs(nsString& success, nsString& error, char16_t** pError,
+ char16_t** pSuccess);
+
+ private:
+ virtual ~ImportOutlookMailImpl();
+ nsOutlookMail m_mail;
+ uint32_t m_bytesDone;
+};
+
+class ImportOutlookAddressImpl : public nsIImportAddressBooks {
+ public:
+ ImportOutlookAddressImpl();
+
+ static nsresult Create(nsIImportAddressBooks** aImport);
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIImportAddressBooks interface
+
+ NS_IMETHOD GetSupportsMultiple(bool* _retval) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetAutoFind(char16_t** description, bool* _retval);
+
+ NS_IMETHOD GetNeedsFieldMap(nsIFile* location, bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetDefaultLocation(nsIFile** location, bool* found,
+ bool* userVerify) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_IMETHOD FindAddressBooks(nsIFile* location,
+ nsTArray<RefPtr<nsIImportABDescriptor>>& books);
+
+ NS_IMETHOD InitFieldMap(nsIImportFieldMap* fieldMap) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_IMETHOD ImportAddressBook(nsIImportABDescriptor* source,
+ nsIAbDirectory* destination,
+ nsIImportFieldMap* fieldMap,
+ nsISupports* aSupportService,
+ char16_t** errorLog, char16_t** successLog,
+ bool* fatalError);
+
+ NS_IMETHOD GetImportProgress(uint32_t* _retval);
+
+ NS_IMETHOD GetSampleData(int32_t index, bool* pFound, char16_t** pStr) {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_IMETHOD SetSampleLocation(nsIFile*) { return NS_OK; }
+
+ private:
+ virtual ~ImportOutlookAddressImpl();
+ void ReportSuccess(nsString& name, nsString* pStream);
+
+ private:
+ uint32_t m_msgCount;
+ uint32_t m_msgTotal;
+ nsOutlookMail m_address;
+};
+////////////////////////////////////////////////////////////////////////
+
+////////////////////////////////////////////////////////////////////////
+
+nsOutlookImport::nsOutlookImport() {
+ IMPORT_LOG0("nsOutlookImport Module Created\n");
+
+ nsOutlookStringBundle::GetStringBundle();
+}
+
+nsOutlookImport::~nsOutlookImport() {
+ IMPORT_LOG0("nsOutlookImport Module Deleted\n");
+}
+
+NS_IMPL_ISUPPORTS(nsOutlookImport, nsIImportModule)
+
+NS_IMETHODIMP nsOutlookImport::GetName(char16_t** name) {
+ NS_ASSERTION(name != nullptr, "null ptr");
+ if (!name) return NS_ERROR_NULL_POINTER;
+
+ *name = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_NAME);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOutlookImport::GetDescription(char16_t** name) {
+ NS_ASSERTION(name != nullptr, "null ptr");
+ if (!name) return NS_ERROR_NULL_POINTER;
+
+ *name = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_DESCRIPTION);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOutlookImport::GetSupports(char** supports) {
+ NS_ASSERTION(supports != nullptr, "null ptr");
+ if (!supports) return NS_ERROR_NULL_POINTER;
+
+ *supports = strdup(kOutlookSupportsString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOutlookImport::GetSupportsUpgrade(bool* pUpgrade) {
+ NS_ASSERTION(pUpgrade != nullptr, "null ptr");
+ if (!pUpgrade) return NS_ERROR_NULL_POINTER;
+
+ *pUpgrade = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOutlookImport::GetImportInterface(const char* pImportType,
+ nsISupports** ppInterface) {
+ NS_ASSERTION(pImportType != nullptr, "null ptr");
+ if (!pImportType) return NS_ERROR_NULL_POINTER;
+ NS_ASSERTION(ppInterface != nullptr, "null ptr");
+ if (!ppInterface) return NS_ERROR_NULL_POINTER;
+
+ *ppInterface = nullptr;
+ nsresult rv;
+ if (!strcmp(pImportType, "mail")) {
+ // create the nsIImportMail interface and return it!
+ nsCOMPtr<nsIImportMail> pMail;
+ nsCOMPtr<nsIImportGeneric> pGeneric;
+ rv = ImportOutlookMailImpl::Create(getter_AddRefs(pMail));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImportService> impSvc(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = impSvc->CreateNewGenericMail(getter_AddRefs(pGeneric));
+ if (NS_SUCCEEDED(rv)) {
+ pGeneric->SetData("mailInterface", pMail);
+ nsString name;
+ nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_NAME, name);
+ nsCOMPtr<nsISupportsString> nameString(
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nameString->SetData(name);
+ pGeneric->SetData("name", nameString);
+ nsCOMPtr<nsISupports> pInterface(do_QueryInterface(pGeneric));
+ pInterface.forget(ppInterface);
+ }
+ }
+ }
+ }
+ return rv;
+ }
+
+ if (!strcmp(pImportType, "addressbook")) {
+ // create the nsIImportAddressBook interface and return it!
+ nsCOMPtr<nsIImportAddressBooks> pAddress;
+ nsCOMPtr<nsIImportGeneric> pGeneric;
+ rv = ImportOutlookAddressImpl::Create(getter_AddRefs(pAddress));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImportService> impSvc(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = impSvc->CreateNewGenericAddressBooks(getter_AddRefs(pGeneric));
+ if (NS_SUCCEEDED(rv)) {
+ pGeneric->SetData("addressInterface", pAddress);
+ nsCOMPtr<nsISupports> pInterface(do_QueryInterface(pGeneric));
+ pInterface.forget(ppInterface);
+ }
+ }
+ }
+ return rv;
+ }
+
+ if (!strcmp(pImportType, "settings")) {
+ nsCOMPtr<nsIImportSettings> pSettings;
+ rv = nsOutlookSettings::Create(getter_AddRefs(pSettings));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsISupports> pInterface(do_QueryInterface(pSettings));
+ pInterface.forget(ppInterface);
+ }
+ return rv;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+nsresult ImportOutlookMailImpl::Create(nsIImportMail** aImport) {
+ NS_ENSURE_ARG_POINTER(aImport);
+ NS_ADDREF(*aImport = new ImportOutlookMailImpl());
+ return NS_OK;
+}
+
+ImportOutlookMailImpl::ImportOutlookMailImpl() {
+ nsOutlookCompose::CreateIdentity();
+}
+
+ImportOutlookMailImpl::~ImportOutlookMailImpl() {
+ nsOutlookCompose::ReleaseIdentity();
+}
+
+NS_IMPL_ISUPPORTS(ImportOutlookMailImpl, nsIImportMail)
+
+NS_IMETHODIMP ImportOutlookMailImpl::GetDefaultLocation(nsIFile** ppLoc,
+ bool* found,
+ bool* userVerify) {
+ NS_ASSERTION(ppLoc != nullptr, "null ptr");
+ NS_ASSERTION(found != nullptr, "null ptr");
+ NS_ASSERTION(userVerify != nullptr, "null ptr");
+ if (!ppLoc || !found || !userVerify) return NS_ERROR_NULL_POINTER;
+
+ *found = false;
+ *ppLoc = nullptr;
+ *userVerify = false;
+ // We need to verify here that we can get the mail, if true then
+ // return a dummy location, otherwise return no location
+ CMapiApi mapi;
+ if (!mapi.Initialize()) return NS_OK;
+ if (!mapi.LogOn()) return NS_OK;
+
+ CMapiFolderList store;
+ if (!mapi.IterateStores(store)) return NS_OK;
+
+ if (store.GetSize() == 0) return NS_OK;
+
+ nsresult rv;
+ nsCOMPtr<nsIFile> resultFile =
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ *found = true;
+ resultFile.forget(ppLoc);
+ *userVerify = false;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP ImportOutlookMailImpl::FindMailboxes(
+ nsIFile* pLoc, nsTArray<RefPtr<nsIImportMailboxDescriptor>>& boxes) {
+ NS_ASSERTION(pLoc != nullptr, "null ptr");
+ if (!pLoc) return NS_ERROR_NULL_POINTER;
+ return m_mail.GetMailFolders(boxes);
+}
+
+void ImportOutlookMailImpl::AddLinebreak(nsString* pStream) {
+ if (pStream) pStream->Append(char16_t('\n'));
+}
+
+void ImportOutlookMailImpl::ReportSuccess(nsString& name, int32_t count,
+ nsString* pStream) {
+ if (!pStream) return;
+ // load the success string
+ char16_t* pFmt =
+ nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_MAILBOX_SUCCESS);
+ nsString pText;
+ nsTextFormatter::ssprintf(pText, pFmt, name.get(), count);
+ pStream->Append(pText);
+ nsOutlookStringBundle::FreeString(pFmt);
+ AddLinebreak(pStream);
+}
+
+void ImportOutlookMailImpl::ReportError(int32_t errorNum, nsString& name,
+ nsString* pStream) {
+ if (!pStream) return;
+ // load the error string
+ char16_t* pFmt = nsOutlookStringBundle::GetStringByID(errorNum);
+ nsString pText;
+ nsTextFormatter::ssprintf(pText, pFmt, name.get());
+ pStream->Append(pText);
+ nsOutlookStringBundle::FreeString(pFmt);
+ AddLinebreak(pStream);
+}
+
+void ImportOutlookMailImpl::SetLogs(nsString& success, nsString& error,
+ char16_t** pError, char16_t** pSuccess) {
+ if (pError) *pError = ToNewUnicode(error);
+ if (pSuccess) *pSuccess = ToNewUnicode(success);
+}
+
+NS_IMETHODIMP
+ImportOutlookMailImpl::ImportMailbox(nsIImportMailboxDescriptor* pSource,
+ nsIMsgFolder* dstFolder,
+ char16_t** pErrorLog,
+ char16_t** pSuccessLog, bool* fatalError) {
+ NS_ENSURE_ARG_POINTER(pSource);
+ NS_ENSURE_ARG_POINTER(dstFolder);
+ NS_ENSURE_ARG_POINTER(fatalError);
+
+ nsString success;
+ nsString error;
+ bool abort = false;
+ nsString name;
+ char16_t* pName;
+ if (NS_SUCCEEDED(pSource->GetDisplayName(&pName))) {
+ name = pName;
+ free(pName);
+ }
+
+ uint32_t mailSize = 0;
+ pSource->GetSize(&mailSize);
+ if (mailSize == 0) {
+ ReportSuccess(name, 0, &success);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ return NS_OK;
+ }
+
+ uint32_t index = 0;
+ pSource->GetIdentifier(&index);
+ int32_t msgCount = 0;
+ nsresult rv = NS_OK;
+
+ m_bytesDone = 0;
+
+ rv = m_mail.ImportMailbox(&m_bytesDone, &abort, (int32_t)index, name.get(),
+ dstFolder, &msgCount);
+
+ if (NS_SUCCEEDED(rv))
+ ReportSuccess(name, msgCount, &success);
+ else
+ ReportError(OUTLOOKIMPORT_MAILBOX_CONVERTERROR, name, &error);
+
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+
+ return rv;
+}
+
+NS_IMETHODIMP ImportOutlookMailImpl::GetImportProgress(uint32_t* pDoneSoFar) {
+ NS_ASSERTION(pDoneSoFar != nullptr, "null ptr");
+ if (!pDoneSoFar) return NS_ERROR_NULL_POINTER;
+
+ *pDoneSoFar = m_bytesDone;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ImportOutlookMailImpl::TranslateFolderName(
+ const nsAString& aFolderName, nsAString& _retval) {
+ if (aFolderName.LowerCaseEqualsLiteral("deleted items"))
+ _retval = NS_LITERAL_STRING_FROM_CSTRING(kDestTrashFolderName);
+ else if (aFolderName.LowerCaseEqualsLiteral("sent items"))
+ _retval = NS_LITERAL_STRING_FROM_CSTRING(kDestSentFolderName);
+ else if (aFolderName.LowerCaseEqualsLiteral("outbox"))
+ _retval = NS_LITERAL_STRING_FROM_CSTRING(kDestUnsentMessagesFolderName);
+ else
+ _retval = aFolderName;
+ return NS_OK;
+}
+
+nsresult ImportOutlookAddressImpl::Create(nsIImportAddressBooks** aImport) {
+ NS_ENSURE_ARG_POINTER(aImport);
+ NS_ADDREF(*aImport = new ImportOutlookAddressImpl());
+ return NS_OK;
+}
+
+ImportOutlookAddressImpl::ImportOutlookAddressImpl() {
+ m_msgCount = 0;
+ m_msgTotal = 0;
+}
+
+ImportOutlookAddressImpl::~ImportOutlookAddressImpl() {}
+
+NS_IMPL_ISUPPORTS(ImportOutlookAddressImpl, nsIImportAddressBooks)
+
+NS_IMETHODIMP ImportOutlookAddressImpl::GetAutoFind(char16_t** description,
+ bool* _retval) {
+ NS_ASSERTION(description != nullptr, "null ptr");
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!description || !_retval) return NS_ERROR_NULL_POINTER;
+
+ *_retval = true;
+ nsString str;
+ nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_ADDRNAME, str);
+ *description = ToNewUnicode(str);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ImportOutlookAddressImpl::FindAddressBooks(
+ nsIFile* location, nsTArray<RefPtr<nsIImportABDescriptor>>& books) {
+ return m_address.GetAddressBooks(books);
+}
+
+NS_IMETHODIMP ImportOutlookAddressImpl::ImportAddressBook(
+ nsIImportABDescriptor* source, nsIAbDirectory* destination,
+ nsIImportFieldMap* fieldMap, nsISupports* aSupportService,
+ char16_t** pErrorLog, char16_t** pSuccessLog, bool* fatalError) {
+ m_msgCount = 0;
+ m_msgTotal = 0;
+ NS_ASSERTION(source != nullptr, "null ptr");
+ NS_ASSERTION(destination != nullptr, "null ptr");
+ NS_ASSERTION(fatalError != nullptr, "null ptr");
+
+ nsString success;
+ nsString error;
+ if (!source || !destination || !fatalError) {
+ IMPORT_LOG0("*** Bad param passed to outlook address import\n");
+ nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_ADDRESS_BADPARAM, error);
+ if (fatalError) *fatalError = true;
+ ImportOutlookMailImpl::SetLogs(success, error, pErrorLog, pSuccessLog);
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsString name;
+ source->GetPreferredName(name);
+
+ uint32_t id;
+ if (NS_FAILED(source->GetIdentifier(&id))) {
+ ImportOutlookMailImpl::ReportError(OUTLOOKIMPORT_ADDRESS_BADSOURCEFILE,
+ name, &error);
+ ImportOutlookMailImpl::SetLogs(success, error, pErrorLog, pSuccessLog);
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = NS_OK;
+ rv = m_address.ImportAddresses(&m_msgCount, &m_msgTotal, name.get(), id,
+ destination, error);
+ if (NS_SUCCEEDED(rv) && error.IsEmpty())
+ ReportSuccess(name, &success);
+ else
+ ImportOutlookMailImpl::ReportError(OUTLOOKIMPORT_ADDRESS_CONVERTERROR, name,
+ &error);
+
+ ImportOutlookMailImpl::SetLogs(success, error, pErrorLog, pSuccessLog);
+ IMPORT_LOG0("*** Returning from outlook address import\n");
+ return NS_OK;
+}
+
+NS_IMETHODIMP ImportOutlookAddressImpl::GetImportProgress(uint32_t* _retval) {
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!_retval) return NS_ERROR_NULL_POINTER;
+
+ uint32_t result = m_msgCount;
+ if (m_msgTotal) {
+ result *= 100;
+ result /= m_msgTotal;
+ } else
+ result = 0;
+
+ if (result > 100) result = 100;
+
+ *_retval = result;
+
+ return NS_OK;
+}
+
+void ImportOutlookAddressImpl::ReportSuccess(nsString& name,
+ nsString* pStream) {
+ if (!pStream) return;
+ // load the success string
+ char16_t* pFmt =
+ nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_ADDRESS_SUCCESS);
+ nsString pText;
+ nsTextFormatter::ssprintf(pText, pFmt, name.get());
+ pStream->Append(pText);
+ nsOutlookStringBundle::FreeString(pFmt);
+ ImportOutlookMailImpl::AddLinebreak(pStream);
+}
diff --git a/comm/mailnews/import/src/nsOutlookImport.h b/comm/mailnews/import/src/nsOutlookImport.h
new file mode 100644
index 0000000000..fb94b31502
--- /dev/null
+++ b/comm/mailnews/import/src/nsOutlookImport.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; 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 nsOutlookImport_h___
+#define nsOutlookImport_h___
+
+#include "nsIImportModule.h"
+#include "nsCOMPtr.h"
+
+#define NS_OUTLOOKIMPORT_CID \
+ { /* 1DB469A0-8B00-11d3-A206-00A0CC26DA63 */ \
+ 0x1db469a0, 0x8b00, 0x11d3, { \
+ 0xa2, 0x6, 0x0, 0xa0, 0xcc, 0x26, 0xda, 0x63 \
+ } \
+ }
+
+#define kOutlookSupportsString \
+ NS_IMPORT_MAIL_STR "," NS_IMPORT_ADDRESS_STR "," NS_IMPORT_SETTINGS_STR
+
+class nsOutlookImport : public nsIImportModule {
+ public:
+ nsOutlookImport();
+
+ NS_DECL_ISUPPORTS
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // we support the nsIImportModule interface
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ NS_DECL_NSIIMPORTMODULE
+
+ protected:
+ virtual ~nsOutlookImport();
+};
+
+#endif /* nsOutlookImport_h___ */
diff --git a/comm/mailnews/import/src/nsOutlookMail.cpp b/comm/mailnews/import/src/nsOutlookMail.cpp
new file mode 100644
index 0000000000..3569c9096d
--- /dev/null
+++ b/comm/mailnews/import/src/nsOutlookMail.cpp
@@ -0,0 +1,830 @@
+/* -*- 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/. */
+
+/*
+ Outlook mail import
+*/
+
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsMsgUtils.h"
+#include "nsIImportService.h"
+#include "nsIImportFieldMap.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsIImportABDescriptor.h"
+#include "nsOutlookStringBundle.h"
+#include "nsIAbCard.h"
+#include "mdb.h"
+#include "ImportDebug.h"
+#include "nsOutlookMail.h"
+#include "nsUnicharUtils.h"
+#include "nsIOutputStream.h"
+#include "nsIMsgPluggableStore.h"
+#include "nsIMsgHdr.h"
+#include "nsIMsgFolder.h"
+#include "nsMsgI18N.h"
+#include "nsNetUtil.h"
+
+/* ------------ Address book stuff ----------------- */
+typedef struct {
+ int32_t mozField;
+ int32_t multiLine;
+ ULONG mapiTag;
+} MAPIFields;
+
+/*
+ Fields in MAPI, not in Mozilla
+ PR_OFFICE_LOCATION
+ FIX - PR_BIRTHDAY - stored as PT_SYSTIME - FIX to extract for moz address book
+ birthday PR_DISPLAY_NAME_PREFIX - Mr., Mrs. Dr., etc. PR_SPOUSE_NAME PR_GENDER
+ - integer, not text FIX - PR_CONTACT_EMAIL_ADDRESSES - multiuline strings for
+ email addresses, needs parsing to get secondary email address for mozilla
+*/
+
+#define kIsMultiLine -2
+#define kNoMultiLine -1
+
+static MAPIFields gMapiFields[] = {
+ {35, kIsMultiLine, PR_BODY},
+ {6, kNoMultiLine, PR_BUSINESS_TELEPHONE_NUMBER},
+ {7, kNoMultiLine, PR_HOME_TELEPHONE_NUMBER},
+ {25, kNoMultiLine, PR_COMPANY_NAME},
+ {23, kNoMultiLine, PR_TITLE},
+ {10, kNoMultiLine, PR_CELLULAR_TELEPHONE_NUMBER},
+ {9, kNoMultiLine, PR_PAGER_TELEPHONE_NUMBER},
+ {8, kNoMultiLine, PR_BUSINESS_FAX_NUMBER},
+ {8, kNoMultiLine, PR_HOME_FAX_NUMBER},
+ {22, kNoMultiLine, PR_COUNTRY},
+ {19, kNoMultiLine, PR_LOCALITY},
+ {20, kNoMultiLine, PR_STATE_OR_PROVINCE},
+ {17, 18, PR_STREET_ADDRESS},
+ {21, kNoMultiLine, PR_POSTAL_CODE},
+ {27, kNoMultiLine, PR_PERSONAL_HOME_PAGE},
+ {26, kNoMultiLine, PR_BUSINESS_HOME_PAGE},
+ {13, kNoMultiLine, PR_HOME_ADDRESS_CITY},
+ {16, kNoMultiLine, PR_HOME_ADDRESS_COUNTRY},
+ {15, kNoMultiLine, PR_HOME_ADDRESS_POSTAL_CODE},
+ {14, kNoMultiLine, PR_HOME_ADDRESS_STATE_OR_PROVINCE},
+ {11, 12, PR_HOME_ADDRESS_STREET},
+ {24, kNoMultiLine, PR_DEPARTMENT_NAME}};
+/* ---------------------------------------------------- */
+
+#define kCopyBufferSize (16 * 1024)
+
+// The email address in Outlook Contacts doesn't have a named
+// property, we need to use this mapi name ID to access the email
+// The MAPINAMEID for email address has ulKind=MNID_ID
+// Outlook stores each email address in two IDs, 32899/32900 for Email1
+// 32915/32916 for Email2, 32931/32932 for Email3
+// Current we use OUTLOOK_EMAIL1_MAPI_ID1 for primary email
+// OUTLOOK_EMAIL2_MAPI_ID1 for secondary email
+#define OUTLOOK_EMAIL1_MAPI_ID1 32899
+#define OUTLOOK_EMAIL1_MAPI_ID2 32900
+#define OUTLOOK_EMAIL2_MAPI_ID1 32915
+#define OUTLOOK_EMAIL2_MAPI_ID2 32916
+#define OUTLOOK_EMAIL3_MAPI_ID1 32931
+#define OUTLOOK_EMAIL3_MAPI_ID2 32932
+
+nsOutlookMail::nsOutlookMail() {
+ m_gotAddresses = false;
+ m_gotFolders = false;
+ m_haveMapi = CMapiApi::LoadMapi();
+ m_lpMdb = NULL;
+}
+
+nsOutlookMail::~nsOutlookMail() {
+ // EmptyAttachments();
+}
+
+nsresult nsOutlookMail::GetMailFolders(
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& boxes) {
+ if (!m_haveMapi) {
+ IMPORT_LOG0("GetMailFolders called before Mapi is initialized\n");
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv;
+ boxes.Clear();
+
+ nsCOMPtr<nsIImportService> impSvc(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ m_gotFolders = true;
+
+ m_folderList.ClearAll();
+
+ m_mapi.Initialize();
+ m_mapi.LogOn();
+
+ if (m_storeList.GetSize() == 0) m_mapi.IterateStores(m_storeList);
+
+ int i = 0;
+ CMapiFolder* pFolder;
+ if (m_storeList.GetSize() > 1) {
+ while ((pFolder = m_storeList.GetItem(i))) {
+ CMapiFolder* pItem = new CMapiFolder(pFolder);
+ pItem->SetDepth(1);
+ m_folderList.AddItem(pItem);
+ if (!m_mapi.GetStoreFolders(pItem->GetCBEntryID(), pItem->GetEntryID(),
+ m_folderList, 2)) {
+ IMPORT_LOG1("GetStoreFolders for index %d failed.\n", i);
+ }
+ i++;
+ }
+ } else {
+ if ((pFolder = m_storeList.GetItem(i))) {
+ if (!m_mapi.GetStoreFolders(pFolder->GetCBEntryID(),
+ pFolder->GetEntryID(), m_folderList, 1)) {
+ IMPORT_LOG1("GetStoreFolders for index %d failed.\n", i);
+ }
+ }
+ }
+
+ // Create the mailbox descriptors for the list of folders
+ nsCOMPtr<nsIImportMailboxDescriptor> pID;
+ nsString name;
+ nsString uniName;
+
+ for (i = 0; i < m_folderList.GetSize(); i++) {
+ pFolder = m_folderList.GetItem(i);
+ rv = impSvc->CreateNewMailboxDescriptor(getter_AddRefs(pID));
+ if (NS_SUCCEEDED(rv)) {
+ pID->SetDepth(pFolder->GetDepth());
+ pID->SetIdentifier(i);
+
+ pFolder->GetDisplayName(name);
+ pID->SetDisplayName(name.get());
+
+ pID->SetSize(1000);
+ boxes.AppendElement(pID);
+ }
+ }
+ return NS_OK;
+}
+
+bool nsOutlookMail::IsAddressBookNameUnique(nsString& name, nsString& list) {
+ nsString usedName;
+ usedName.Append('[');
+ usedName.Append(name);
+ usedName.AppendLiteral("],");
+
+ return list.Find(usedName) == -1;
+}
+
+void nsOutlookMail::MakeAddressBookNameUnique(nsString& name, nsString& list) {
+ nsString newName;
+ int idx = 1;
+
+ newName = name;
+ while (!IsAddressBookNameUnique(newName, list)) {
+ newName = name;
+ newName.Append(char16_t(' '));
+ newName.AppendInt((int32_t)idx);
+ idx++;
+ }
+
+ name = newName;
+ list.Append('[');
+ list.Append(name);
+ list.AppendLiteral("],");
+}
+
+nsresult nsOutlookMail::GetAddressBooks(
+ nsTArray<RefPtr<nsIImportABDescriptor>>& books) {
+ books.Clear();
+ if (!m_haveMapi) {
+ IMPORT_LOG0("GetAddressBooks called before Mapi is initialized\n");
+ return NS_ERROR_FAILURE;
+ }
+ nsresult rv;
+ nsCOMPtr<nsIImportService> impSvc(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) return rv;
+
+ m_gotAddresses = true;
+
+ m_addressList.ClearAll();
+ m_mapi.Initialize();
+ m_mapi.LogOn();
+ if (m_storeList.GetSize() == 0) m_mapi.IterateStores(m_storeList);
+
+ int i = 0;
+ CMapiFolder* pFolder;
+ if (m_storeList.GetSize() > 1) {
+ while ((pFolder = m_storeList.GetItem(i))) {
+ CMapiFolder* pItem = new CMapiFolder(pFolder);
+ pItem->SetDepth(1);
+ m_addressList.AddItem(pItem);
+ if (!m_mapi.GetStoreAddressFolders(pItem->GetCBEntryID(),
+ pItem->GetEntryID(), m_addressList)) {
+ IMPORT_LOG1("GetStoreAddressFolders for index %d failed.\n", i);
+ }
+ i++;
+ }
+ } else {
+ if ((pFolder = m_storeList.GetItem(i))) {
+ if (!m_mapi.GetStoreAddressFolders(
+ pFolder->GetCBEntryID(), pFolder->GetEntryID(), m_addressList)) {
+ IMPORT_LOG1("GetStoreFolders for index %d failed.\n", i);
+ }
+ }
+ }
+
+ // Create the mailbox descriptors for the list of folders
+ nsCOMPtr<nsIImportABDescriptor> pID;
+ nsString name;
+ nsString list;
+
+ for (i = 0; i < m_addressList.GetSize(); i++) {
+ pFolder = m_addressList.GetItem(i);
+ if (!pFolder->IsStore()) {
+ rv = impSvc->CreateNewABDescriptor(getter_AddRefs(pID));
+ if (NS_SUCCEEDED(rv)) {
+ pID->SetIdentifier(i);
+ pFolder->GetDisplayName(name);
+ MakeAddressBookNameUnique(name, list);
+ pID->SetPreferredName(name);
+ pID->SetSize(100);
+ books.AppendElement(pID);
+ }
+ }
+ }
+ return NS_OK;
+}
+
+void nsOutlookMail::OpenMessageStore(CMapiFolder* pNextFolder) {
+ // Open the store specified
+ if (pNextFolder->IsStore()) {
+ if (!m_mapi.OpenStore(pNextFolder->GetCBEntryID(),
+ pNextFolder->GetEntryID(), &m_lpMdb)) {
+ m_lpMdb = NULL;
+ IMPORT_LOG0("CMapiApi::OpenStore failed\n");
+ }
+
+ return;
+ }
+
+ // Check to see if we should open the one and only store
+ if (!m_lpMdb) {
+ if (m_storeList.GetSize() == 1) {
+ CMapiFolder* pFolder = m_storeList.GetItem(0);
+ if (pFolder) {
+ if (!m_mapi.OpenStore(pFolder->GetCBEntryID(), pFolder->GetEntryID(),
+ &m_lpMdb)) {
+ m_lpMdb = NULL;
+ IMPORT_LOG0("CMapiApi::OpenStore failed\n");
+ }
+ } else {
+ IMPORT_LOG0("Error retrieving the one & only message store\n");
+ }
+ } else {
+ IMPORT_LOG0(
+ "*** Error importing a folder without a valid message store\n");
+ }
+ }
+}
+
+// Roles and responsibilities:
+// nsOutlookMail
+// - Connect to Outlook
+// - Enumerate the mailboxes
+// - Iterate the mailboxes
+// - For each mail, create one nsOutlookCompose object
+// - For each mail, create one CMapiMessage object
+//
+// nsOutlookCompose
+// - Establish a TB session
+// - Connect to all required services
+// - Perform the composition of the RC822 document from the data gathered by
+// CMapiMessage
+// - Save the composed message to the TB mailbox
+// - Ensure the proper cleanup
+//
+// CMapiMessage
+// - Encapsulate the MAPI message interface
+// - Gather the information required to (re)compose the message
+
+ImportMailboxRunnable::ImportMailboxRunnable(
+ uint32_t* pDoneSoFar, bool* pAbort, int32_t index, const char16_t* pName,
+ nsIMsgFolder* dstFolder, int32_t* pMsgCount, nsOutlookMail* aCaller)
+ : mozilla::Runnable("ImportMailboxRunnable"),
+ mResult(NS_OK),
+ mCaller(aCaller),
+ mDoneSoFar(pDoneSoFar),
+ mAbort(pAbort),
+ mIndex(index),
+ mName(pName),
+ mDstFolder(dstFolder),
+ mMsgCount(pMsgCount) {}
+NS_IMETHODIMP ImportMailboxRunnable::Run() {
+ if ((mIndex < 0) || (mIndex >= mCaller->m_folderList.GetSize())) {
+ IMPORT_LOG0("*** Bad mailbox identifier, unable to import\n");
+ *mAbort = true;
+ mResult = NS_ERROR_FAILURE;
+ return NS_OK; // Sync runnable must return OK.
+ }
+
+ int32_t dummyMsgCount = 0;
+ if (mMsgCount)
+ *mMsgCount = 0;
+ else
+ mMsgCount = &dummyMsgCount;
+
+ CMapiFolder* pFolder = mCaller->m_folderList.GetItem(mIndex);
+ mCaller->OpenMessageStore(pFolder);
+ if (!mCaller->m_lpMdb) {
+ IMPORT_LOG1("*** Unable to obtain mapi message store for mailbox: %S\n",
+ (const wchar_t*)mName);
+ mResult = NS_ERROR_FAILURE;
+ return NS_OK; // Sync runnable must return OK.
+ }
+
+ if (pFolder->IsStore()) return NS_OK;
+
+ // now what?
+ CMapiFolderContents contents(mCaller->m_lpMdb, pFolder->GetCBEntryID(),
+ pFolder->GetEntryID());
+
+ BOOL done = FALSE;
+ ULONG cbEid;
+ LPENTRYID lpEid;
+ ULONG oType;
+ LPMESSAGE lpMsg = nullptr;
+ ULONG totalCount;
+ double doneCalc;
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ nsCOMPtr<nsIMsgPluggableStore> msgStore;
+ nsresult rv = mDstFolder->GetMsgStore(getter_AddRefs(msgStore));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ while (!done) {
+ if (!contents.GetNext(&cbEid, &lpEid, &oType, &done)) {
+ IMPORT_LOG1("*** Error iterating mailbox: %S\n", (const wchar_t*)mName);
+ mResult = NS_ERROR_FAILURE;
+ return NS_OK; // Sync runnable must return OK.
+ }
+
+ nsCOMPtr<nsIMsgDBHdr> msgHdr;
+ rv = msgStore->GetNewMsgOutputStream(mDstFolder, getter_AddRefs(msgHdr),
+ getter_AddRefs(outputStream));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG1("*** Error getting nsIOutputStream of mailbox: %S\n",
+ (const wchar_t*)mName);
+ mResult = rv;
+ return NS_OK; // Sync runnable must return OK.
+ }
+ totalCount = contents.GetCount();
+ doneCalc = *mMsgCount;
+ doneCalc /= totalCount;
+ doneCalc *= 1000;
+ if (mDoneSoFar) {
+ *mDoneSoFar = (uint32_t)doneCalc;
+ if (*mDoneSoFar > 1000) *mDoneSoFar = 1000;
+ }
+
+ if (!done && (oType == MAPI_MESSAGE)) {
+ if (!mCaller->m_mapi.OpenMdbEntry(mCaller->m_lpMdb, cbEid, lpEid,
+ (LPUNKNOWN*)&lpMsg)) {
+ IMPORT_LOG1("*** Error opening messages in mailbox: %S\n",
+ (const wchar_t*)mName);
+ mResult = NS_ERROR_FAILURE;
+ return NS_OK; // Sync runnable must return OK.
+ }
+
+ // See if it's a drafts folder. Outlook doesn't allow drafts
+ // folder to be configured so it's ok to hard code it here.
+ nsAutoString folderName(mName);
+ nsMsgDeliverMode mode = nsIMsgSend::nsMsgDeliverNow;
+ mode = nsIMsgSend::nsMsgSaveAsDraft;
+ if (folderName.LowerCaseEqualsLiteral("drafts"))
+ mode = nsIMsgSend::nsMsgSaveAsDraft;
+
+ rv = ImportMessage(lpMsg, outputStream, mode);
+ if (NS_SUCCEEDED(rv)) { // No errors & really imported
+ (*mMsgCount)++;
+ msgStore->FinishNewMessage(outputStream, msgHdr);
+ outputStream = nullptr;
+ } else {
+ IMPORT_LOG1("*** Error reading message from mailbox: %S\n",
+ (const wchar_t*)mName);
+ msgStore->DiscardNewMessage(outputStream, msgHdr);
+ outputStream = nullptr;
+ }
+ }
+ }
+ return NS_OK;
+}
+
+nsresult ProxyImportMailbox(uint32_t* pDoneSoFar, bool* pAbort, int32_t index,
+ const char16_t* pName, nsIMsgFolder* dstFolder,
+ int32_t* pMsgCount, nsOutlookMail* aCaller) {
+ RefPtr<ImportMailboxRunnable> importMailbox = new ImportMailboxRunnable(
+ pDoneSoFar, pAbort, index, pName, dstFolder, pMsgCount, aCaller);
+ nsresult rv = NS_DispatchAndSpinEventLoopUntilComplete(
+ "ProxyImportMailbox"_ns, mozilla::GetMainThreadSerialEventTarget(),
+ do_AddRef(importMailbox));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return importMailbox->mResult;
+}
+
+nsresult nsOutlookMail::ImportMailbox(uint32_t* pDoneSoFar, bool* pAbort,
+ int32_t index, const char16_t* pName,
+ nsIMsgFolder* dstFolder,
+ int32_t* pMsgCount) {
+ return ProxyImportMailbox(pDoneSoFar, pAbort, index, pName, dstFolder,
+ pMsgCount, this);
+}
+
+nsresult ImportMailboxRunnable::ImportMessage(LPMESSAGE lpMsg,
+ nsIOutputStream* pDest,
+ nsMsgDeliverMode mode) {
+ CMapiMessage msg(lpMsg);
+ // If we wanted to skip messages that were downloaded in header only mode, we
+ // would return NS_ERROR_FAILURE if !msg.FullMessageDownloaded. However, we
+ // don't do this because it may cause seemingly wrong import results.
+ // A user will get less mails in his imported folder than were in the original
+ // folder, and this may make user feel like TB import is bad. In reality, the
+ // skipped messages are those that have not been downloaded yet, because they
+ // were downloaded in the "headers-only" mode. This is different from the case
+ // when the message is downloaded completely, but consists only of headers -
+ // in this case the message will be imported anyway.
+
+ if (!msg.ValidState()) return NS_ERROR_FAILURE;
+
+ // I have to create a composer for each message, since it turns out that if we
+ // create one composer for several messages, the Send Proxy object that is
+ // shared between those messages isn't reset properly (at least in the current
+ // implementation), which leads to crash. If there's a proper way to
+ // reinitialize the Send Proxy object, then we could slightly optimize the
+ // send process.
+ nsOutlookCompose compose;
+ nsresult rv = compose.ProcessMessage(mode, msg, pDest);
+
+ // Just for YUCKS, let's try an extra endline
+ nsOutlookMail::WriteData(pDest, "\x0D\x0A", 2);
+
+ return rv;
+}
+
+BOOL nsOutlookMail::WriteData(nsIOutputStream* pDest, const char* pData,
+ uint32_t len) {
+ uint32_t written;
+ nsresult rv = pDest->Write(pData, len, &written);
+ return NS_SUCCEEDED(rv) && written == len;
+}
+
+nsresult nsOutlookMail::ImportAddresses(uint32_t* pCount, uint32_t* pTotal,
+ const char16_t* pName, uint32_t id,
+ nsIAbDirectory* pDirectory,
+ nsString& errors) {
+ if (id >= (uint32_t)(m_addressList.GetSize())) {
+ IMPORT_LOG0("*** Bad address identifier, unable to import\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ uint32_t dummyCount = 0;
+ if (pCount)
+ *pCount = 0;
+ else
+ pCount = &dummyCount;
+
+ CMapiFolder* pFolder;
+ if (id > 0) {
+ int32_t idx = (int32_t)id;
+ idx--;
+ while (idx >= 0) {
+ pFolder = m_addressList.GetItem(idx);
+ if (pFolder->IsStore()) {
+ OpenMessageStore(pFolder);
+ break;
+ }
+ idx--;
+ }
+ }
+
+ pFolder = m_addressList.GetItem(id);
+ OpenMessageStore(pFolder);
+ if (!m_lpMdb) {
+ IMPORT_LOG1(
+ "*** Unable to obtain mapi message store for address book: %S\n",
+ (const wchar_t*)pName);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (pFolder->IsStore()) return NS_OK;
+
+ nsresult rv;
+
+ nsCOMPtr<nsIImportFieldMap> pFieldMap;
+
+ nsCOMPtr<nsIImportService> impSvc(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = impSvc->CreateNewFieldMap(getter_AddRefs(pFieldMap));
+ }
+
+ CMapiFolderContents contents(m_lpMdb, pFolder->GetCBEntryID(),
+ pFolder->GetEntryID());
+
+ BOOL done = FALSE;
+ ULONG cbEid;
+ LPENTRYID lpEid;
+ ULONG oType;
+ LPMESSAGE lpMsg;
+ nsCString type;
+ LPSPropValue pVal;
+ nsString subject;
+
+ while (!done) {
+ (*pCount)++;
+
+ if (!contents.GetNext(&cbEid, &lpEid, &oType, &done)) {
+ IMPORT_LOG1("*** Error iterating address book: %S\n",
+ (const wchar_t*)pName);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (pTotal && (*pTotal == 0)) *pTotal = contents.GetCount();
+
+ if (!done && (oType == MAPI_MESSAGE)) {
+ if (!m_mapi.OpenMdbEntry(m_lpMdb, cbEid, lpEid, (LPUNKNOWN*)&lpMsg)) {
+ IMPORT_LOG1("*** Error opening messages in mailbox: %S\n",
+ (const wchar_t*)pName);
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get the PR_MESSAGE_CLASS attribute,
+ // ensure that it is IPM.Contact
+ pVal = m_mapi.GetMapiProperty(lpMsg, PR_MESSAGE_CLASS);
+ if (pVal) {
+ type.Truncate();
+ m_mapi.GetStringFromProp(pVal, type);
+ if (type.EqualsLiteral("IPM.Contact")) {
+ // This is a contact, add it to the address book!
+ subject.Truncate();
+ pVal = m_mapi.GetMapiProperty(lpMsg, PR_SUBJECT);
+ if (pVal) m_mapi.GetStringFromProp(pVal, subject);
+
+ nsCOMPtr<nsIAbCard> newCard =
+ do_CreateInstance("@mozilla.org/addressbook/cardproperty;1", &rv);
+ if (newCard) {
+ if (BuildCard(subject.get(), pDirectory, newCard, lpMsg,
+ pFieldMap)) {
+ nsIAbCard* outCard;
+ pDirectory->AddCard(newCard, &outCard);
+ }
+ }
+ } else if (type.EqualsLiteral("IPM.DistList")) {
+ // This is a list/group, add it to the address book!
+ subject.Truncate();
+ pVal = m_mapi.GetMapiProperty(lpMsg, PR_SUBJECT);
+ if (pVal) m_mapi.GetStringFromProp(pVal, subject);
+ CreateList(subject, pDirectory, lpMsg, pFieldMap);
+ }
+ }
+
+ lpMsg->Release();
+ }
+ }
+
+ return rv;
+}
+nsresult nsOutlookMail::CreateList(const nsString& pName,
+ nsIAbDirectory* pDirectory,
+ LPMAPIPROP pUserList,
+ nsIImportFieldMap* pFieldMap) {
+ // If no name provided then we're done.
+ if (pName.IsEmpty()) return NS_OK;
+
+ nsresult rv = NS_ERROR_FAILURE;
+ // Make sure we have db to work with.
+ if (!pDirectory) return rv;
+
+ nsCOMPtr<nsIAbDirectory> newList =
+ do_CreateInstance("@mozilla.org/addressbook/directoryproperty;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ rv = newList->SetDirName(pName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ HRESULT hr;
+ LPSPropValue value = NULL;
+ ULONG valueCount = 0;
+
+ LPSPropTagArray properties = NULL;
+ m_mapi.MAPIAllocateBuffer(CbNewSPropTagArray(1), (void**)&properties);
+ properties->cValues = 1;
+ properties->aulPropTag[0] = m_mapi.GetEmailPropertyTag(pUserList, 0x8054);
+ hr = pUserList->GetProps(properties, 0, &valueCount, &value);
+ m_mapi.MAPIFreeBuffer(properties);
+ if (HR_FAILED(hr)) return NS_ERROR_FAILURE;
+ if (!value) return NS_ERROR_NOT_AVAILABLE;
+ // XXX from here out, value must be freed with MAPIFreeBuffer
+
+ SBinaryArray* sa = (SBinaryArray*)&value->Value.bin;
+ if (!sa || !sa->lpbin) {
+ m_mapi.MAPIFreeBuffer(value);
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ LPENTRYID lpEid;
+ ULONG cbEid;
+ ULONG idx;
+ LPMESSAGE lpMsg;
+ nsCString type;
+ LPSPropValue pVal;
+ nsString subject;
+ ULONG total;
+
+ total = sa->cValues;
+ for (idx = 0; idx < total; idx++) {
+ lpEid = (LPENTRYID)sa->lpbin[idx].lpb;
+ cbEid = sa->lpbin[idx].cb;
+
+ if (!m_mapi.OpenEntry(cbEid, lpEid, (LPUNKNOWN*)&lpMsg)) {
+ IMPORT_LOG1("*** Error opening messages in mailbox: %S\n",
+ static_cast<const wchar_t*>(pName.get()));
+ m_mapi.MAPIFreeBuffer(value);
+ return NS_ERROR_FAILURE;
+ }
+ // This is a contact, add it to the address book!
+ subject.Truncate();
+ pVal = m_mapi.GetMapiProperty(lpMsg, PR_SUBJECT);
+ if (pVal) m_mapi.GetStringFromProp(pVal, subject);
+
+ nsCOMPtr<nsIAbCard> newCard =
+ do_CreateInstance("@mozilla.org/addressbook/cardproperty;1", &rv);
+ if (newCard) {
+ if (BuildCard(subject.get(), pDirectory, newCard, lpMsg, pFieldMap)) {
+ nsIAbCard* outCard;
+ newList->AddCard(newCard, &outCard);
+ }
+ }
+ }
+ m_mapi.MAPIFreeBuffer(value);
+
+ nsIAbDirectory* outList;
+ rv = pDirectory->AddMailList(newList, &outList);
+ return rv;
+}
+
+void nsOutlookMail::SanitizeValue(nsString& val) {
+ val.ReplaceSubstring(u"\r\n"_ns, u", "_ns);
+ val.ReplaceChar(u"\r\n", u',');
+}
+
+void nsOutlookMail::SplitString(nsString& val1, nsString& val2) {
+ // Find the last line if there is more than one!
+ int32_t idx = val1.RFind(u"\x0D\x0A");
+ int32_t cnt = 2;
+ if (idx == -1) {
+ cnt = 1;
+ idx = val1.RFindChar(13);
+ }
+ if (idx == -1) idx = val1.RFindChar(10);
+ if (idx != -1) {
+ val2 = Substring(val1, idx + cnt);
+ val1.SetLength(idx);
+ SanitizeValue(val1);
+ }
+}
+
+bool nsOutlookMail::BuildCard(const char16_t* pName, nsIAbDirectory* pDirectory,
+ nsIAbCard* newCard, LPMAPIPROP pUser,
+ nsIImportFieldMap* pFieldMap) {
+ nsString lastName;
+ nsString firstName;
+ nsString eMail;
+ nsString nickName;
+ nsString middleName;
+ nsString secondEMail;
+ ULONG emailTag;
+
+ LPSPropValue pProp = m_mapi.GetMapiProperty(pUser, PR_EMAIL_ADDRESS);
+ if (!pProp) {
+ emailTag = m_mapi.GetEmailPropertyTag(pUser, OUTLOOK_EMAIL1_MAPI_ID1);
+ if (emailTag) {
+ pProp = m_mapi.GetMapiProperty(pUser, emailTag);
+ }
+ }
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, eMail);
+ SanitizeValue(eMail);
+ }
+
+ // for secondary email
+ emailTag = m_mapi.GetEmailPropertyTag(pUser, OUTLOOK_EMAIL2_MAPI_ID1);
+ if (emailTag) {
+ pProp = m_mapi.GetMapiProperty(pUser, emailTag);
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, secondEMail);
+ SanitizeValue(secondEMail);
+ }
+ }
+
+ pProp = m_mapi.GetMapiProperty(pUser, PR_GIVEN_NAME);
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, firstName);
+ SanitizeValue(firstName);
+ }
+ pProp = m_mapi.GetMapiProperty(pUser, PR_SURNAME);
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, lastName);
+ SanitizeValue(lastName);
+ }
+ pProp = m_mapi.GetMapiProperty(pUser, PR_MIDDLE_NAME);
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, middleName);
+ SanitizeValue(middleName);
+ }
+ pProp = m_mapi.GetMapiProperty(pUser, PR_NICKNAME);
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, nickName);
+ SanitizeValue(nickName);
+ }
+ if (firstName.IsEmpty() && lastName.IsEmpty()) {
+ firstName = pName;
+ }
+
+ nsString displayName;
+ pProp = m_mapi.GetMapiProperty(pUser, PR_DISPLAY_NAME);
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, displayName);
+ SanitizeValue(displayName);
+ }
+ if (displayName.IsEmpty()) {
+ if (firstName.IsEmpty())
+ displayName = pName;
+ else {
+ displayName = firstName;
+ if (!middleName.IsEmpty()) {
+ displayName.Append(char16_t(' '));
+ displayName.Append(middleName);
+ }
+ if (!lastName.IsEmpty()) {
+ displayName.Append(char16_t(' '));
+ displayName.Append(lastName);
+ }
+ }
+ }
+
+ // We now have the required fields
+ // write them out followed by any optional fields!
+ if (!displayName.IsEmpty()) {
+ newCard->SetDisplayName(displayName);
+ }
+ if (!firstName.IsEmpty()) {
+ newCard->SetFirstName(firstName);
+ }
+ if (!lastName.IsEmpty()) {
+ newCard->SetLastName(lastName);
+ }
+ if (!nickName.IsEmpty()) {
+ newCard->SetPropertyAsAString(kNicknameProperty, nickName);
+ }
+ if (!eMail.IsEmpty()) {
+ newCard->SetPrimaryEmail(eMail);
+ }
+ if (!secondEMail.IsEmpty()) {
+ newCard->SetPropertyAsAString(k2ndEmailProperty, secondEMail);
+ }
+
+ // Do all of the extra fields!
+
+ nsString value;
+ nsString line2;
+
+ if (pFieldMap) {
+ int max = sizeof(gMapiFields) / sizeof(MAPIFields);
+ for (int i = 0; i < max; i++) {
+ pProp = m_mapi.GetMapiProperty(pUser, gMapiFields[i].mapiTag);
+ if (pProp) {
+ m_mapi.GetStringFromProp(pProp, value);
+ if (!value.IsEmpty()) {
+ if (gMapiFields[i].multiLine == kNoMultiLine) {
+ SanitizeValue(value);
+ pFieldMap->SetFieldValue(pDirectory, newCard,
+ gMapiFields[i].mozField, value);
+ } else if (gMapiFields[i].multiLine == kIsMultiLine) {
+ pFieldMap->SetFieldValue(pDirectory, newCard,
+ gMapiFields[i].mozField, value);
+ } else {
+ line2.Truncate();
+ SplitString(value, line2);
+ if (!value.IsEmpty())
+ pFieldMap->SetFieldValue(pDirectory, newCard,
+ gMapiFields[i].mozField, value);
+ if (!line2.IsEmpty())
+ pFieldMap->SetFieldValue(pDirectory, newCard,
+ gMapiFields[i].multiLine, line2);
+ }
+ }
+ }
+ }
+ }
+
+ return true;
+}
diff --git a/comm/mailnews/import/src/nsOutlookMail.h b/comm/mailnews/import/src/nsOutlookMail.h
new file mode 100644
index 0000000000..8f28d6425d
--- /dev/null
+++ b/comm/mailnews/import/src/nsOutlookMail.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 4; 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 nsOutlookMail_h___
+#define nsOutlookMail_h___
+
+#include "nsIArray.h"
+#include "nsTArray.h"
+#include "nsString.h"
+#include "nsOutlookCompose.h"
+#include "nsIFile.h"
+#include "MapiApi.h"
+#include "MapiMessage.h"
+#include "nsIAbDirectory.h"
+#include "nsThreadUtils.h"
+
+class nsIImportFieldMap;
+
+class nsOutlookMail {
+ public:
+ nsOutlookMail();
+ ~nsOutlookMail();
+
+ nsresult GetMailFolders(nsTArray<RefPtr<nsIImportMailboxDescriptor>>& boxes);
+ nsresult GetAddressBooks(nsTArray<RefPtr<nsIImportABDescriptor>>& books);
+ nsresult ImportMailbox(uint32_t* pDoneSoFar, bool* pAbort, int32_t index,
+ const char16_t* pName, nsIMsgFolder* pDest,
+ int32_t* pMsgCount);
+ nsresult ImportAddresses(uint32_t* pCount, uint32_t* pTotal,
+ const char16_t* pName, uint32_t id,
+ nsIAbDirectory* pDirectory, nsString& errors);
+ void OpenMessageStore(CMapiFolder* pNextFolder);
+ static BOOL WriteData(nsIOutputStream* pDest, const char* pData,
+ uint32_t len);
+
+ private:
+ bool IsAddressBookNameUnique(nsString& name, nsString& list);
+ void MakeAddressBookNameUnique(nsString& name, nsString& list);
+ void SanitizeValue(nsString& val);
+ void SplitString(nsString& val1, nsString& val2);
+ bool BuildCard(const char16_t* pName, nsIAbDirectory* pDirectory,
+ nsIAbCard* newCard, LPMAPIPROP pUser,
+ nsIImportFieldMap* pFieldMap);
+ nsresult CreateList(const nsString& pName, nsIAbDirectory* pDirectory,
+ LPMAPIPROP pUserList, nsIImportFieldMap* pFieldMap);
+
+ private:
+ bool m_gotFolders;
+ bool m_gotAddresses;
+ bool m_haveMapi;
+ CMapiFolderList m_addressList;
+ CMapiFolderList m_storeList;
+
+ public:
+ // Needed for the proxy class.
+ CMapiApi m_mapi;
+ CMapiFolderList m_folderList;
+ LPMDB m_lpMdb;
+};
+
+class ImportMailboxRunnable : public mozilla::Runnable {
+ public:
+ ImportMailboxRunnable(uint32_t* pDoneSoFar, bool* pAbort, int32_t index,
+ const char16_t* pName, nsIMsgFolder* dstFolder,
+ int32_t* pMsgCount, nsOutlookMail* aCaller);
+ NS_DECL_NSIRUNNABLE
+ static nsresult ImportMessage(LPMESSAGE lpMsg, nsIOutputStream* pDest,
+ nsMsgDeliverMode mode);
+ nsresult mResult;
+
+ private:
+ nsOutlookMail* mCaller;
+ uint32_t* mDoneSoFar;
+ bool* mAbort;
+ int32_t mIndex;
+ const char16_t* mName;
+ nsCOMPtr<nsIFile> mMessageFile;
+ nsCOMPtr<nsIMsgFolder> mDstFolder;
+ int32_t* mMsgCount;
+};
+
+#endif /* nsOutlookMail_h___ */
diff --git a/comm/mailnews/import/src/nsOutlookSettings.cpp b/comm/mailnews/import/src/nsOutlookSettings.cpp
new file mode 100644
index 0000000000..2e202a5600
--- /dev/null
+++ b/comm/mailnews/import/src/nsOutlookSettings.cpp
@@ -0,0 +1,500 @@
+/* -*- 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/. */
+
+/*
+ Outlook (Win32) settings
+*/
+
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsMsgUtils.h"
+#include "nsOutlookImport.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgAccount.h"
+#include "nsIImportSettings.h"
+#include "nsOutlookSettings.h"
+#include "nsISmtpService.h"
+#include "nsISmtpServer.h"
+#include "nsOutlookStringBundle.h"
+#include "ImportDebug.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsMsgI18N.h"
+#include <windows.h>
+#include "nsIWindowsRegKey.h"
+#include "nsComponentManagerUtils.h"
+#include "nsNativeCharsetUtils.h"
+
+class OutlookSettings {
+ public:
+ static nsresult FindAccountsKey(nsIWindowsRegKey** aKey);
+ static nsresult QueryAccountSubKey(nsIWindowsRegKey** aKey);
+ static nsresult GetDefaultMailAccountName(nsAString& aName);
+
+ static bool DoImport(nsIMsgAccount** aAccount);
+
+ static bool DoIMAPServer(nsIMsgAccountManager* aMgr, nsIWindowsRegKey* aKey,
+ const nsString& aServerName,
+ nsIMsgAccount** aAccount);
+ static bool DoPOP3Server(nsIMsgAccountManager* aMgr, nsIWindowsRegKey* aKey,
+ const nsString& aServerName,
+ nsIMsgAccount** aAccount);
+
+ static void SetIdentities(nsIMsgAccountManager* pMgr, nsIMsgAccount* pAcc,
+ nsIWindowsRegKey* aKey);
+
+ static nsresult SetSmtpServer(nsIMsgAccountManager* aMgr, nsIMsgAccount* aAcc,
+ nsIMsgIdentity* aId, const nsString& aServer,
+ const nsString& aUser);
+ static nsresult SetSmtpServerKey(nsIMsgIdentity* aId, nsISmtpServer* aServer);
+ static nsresult GetAccountName(nsIWindowsRegKey* aKey,
+ const nsString& aDefaultName,
+ nsAString& aAccountName);
+};
+
+#define OUTLOOK2003_REGISTRY_KEY \
+ "Software\\Microsoft\\Office\\Outlook\\OMI Account Manager"
+#define OUTLOOK98_REGISTRY_KEY \
+ "Software\\Microsoft\\Office\\8.0\\Outlook\\OMI Account Manager"
+
+////////////////////////////////////////////////////////////////////////
+nsresult nsOutlookSettings::Create(nsIImportSettings** aImport) {
+ NS_ENSURE_ARG_POINTER(aImport);
+ NS_ADDREF(*aImport = new nsOutlookSettings());
+ return NS_OK;
+}
+
+nsOutlookSettings::nsOutlookSettings() {}
+
+nsOutlookSettings::~nsOutlookSettings() {}
+
+NS_IMPL_ISUPPORTS(nsOutlookSettings, nsIImportSettings)
+
+NS_IMETHODIMP nsOutlookSettings::AutoLocate(char16_t** description,
+ nsIFile** location, bool* _retval) {
+ NS_ASSERTION(description != nullptr, "null ptr");
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!description || !_retval) return NS_ERROR_NULL_POINTER;
+
+ *description = nsOutlookStringBundle::GetStringByID(OUTLOOKIMPORT_NAME);
+ *_retval = false;
+
+ if (location) *location = nullptr;
+
+ // look for the registry key for the accounts
+ nsCOMPtr<nsIWindowsRegKey> key;
+ *_retval =
+ NS_SUCCEEDED(OutlookSettings::FindAccountsKey(getter_AddRefs(key)));
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOutlookSettings::SetLocation(nsIFile* location) {
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsOutlookSettings::Import(nsIMsgAccount** localMailAccount,
+ bool* _retval) {
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+
+ if (OutlookSettings::DoImport(localMailAccount)) {
+ *_retval = true;
+ IMPORT_LOG0("Settings import appears successful\n");
+ } else {
+ *_retval = false;
+ IMPORT_LOG0("Settings import returned FALSE\n");
+ }
+
+ return NS_OK;
+}
+
+nsresult OutlookSettings::FindAccountsKey(nsIWindowsRegKey** aKey) {
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> key =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING_FROM_CSTRING(OUTLOOK2003_REGISTRY_KEY),
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE |
+ nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS);
+
+ if (NS_FAILED(rv)) {
+ rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING_FROM_CSTRING(OUTLOOK98_REGISTRY_KEY),
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE |
+ nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS);
+ }
+
+ if (NS_SUCCEEDED(rv)) key.forget(aKey);
+
+ return rv;
+}
+
+nsresult OutlookSettings::QueryAccountSubKey(nsIWindowsRegKey** aKey) {
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> key =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING_FROM_CSTRING(OUTLOOK2003_REGISTRY_KEY),
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE |
+ nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS);
+ if (NS_SUCCEEDED(rv)) {
+ key.forget(aKey);
+ return rv;
+ }
+
+ rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ NS_LITERAL_STRING_FROM_CSTRING(OUTLOOK98_REGISTRY_KEY),
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE |
+ nsIWindowsRegKey::ACCESS_ENUMERATE_SUB_KEYS);
+ if (NS_SUCCEEDED(rv)) {
+ key.forget(aKey);
+ return rv;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult OutlookSettings::GetDefaultMailAccountName(nsAString& aName) {
+ nsCOMPtr<nsIWindowsRegKey> key;
+ nsresult rv = QueryAccountSubKey(getter_AddRefs(key));
+ if (NS_FAILED(rv)) return rv;
+
+ return key->ReadStringValue(u"Default Mail Account"_ns, aName);
+}
+
+bool OutlookSettings::DoImport(nsIMsgAccount** aAccount) {
+ nsCOMPtr<nsIWindowsRegKey> key;
+ nsresult rv = OutlookSettings::FindAccountsKey(getter_AddRefs(key));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error finding Outlook registry account keys\n");
+ return false;
+ }
+
+ nsCOMPtr<nsIMsgAccountManager> accMgr =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to create a account manager!\n");
+ return false;
+ }
+
+ nsAutoString defMailName;
+ rv = GetDefaultMailAccountName(defMailName);
+
+ uint32_t childCount;
+ key->GetChildCount(&childCount);
+
+ uint32_t accounts = 0;
+ uint32_t popCount = 0;
+ for (uint32_t i = 0; i < childCount; i++) {
+ nsAutoString keyName;
+ key->GetChildName(i, keyName);
+ nsCOMPtr<nsIWindowsRegKey> subKey;
+ rv = key->OpenChild(keyName, nsIWindowsRegKey::ACCESS_QUERY_VALUE,
+ getter_AddRefs(subKey));
+ if (NS_FAILED(rv)) continue;
+
+ // Get the values for this account.
+ nsAutoCString nativeKeyName;
+ NS_CopyUnicodeToNative(keyName, nativeKeyName);
+ IMPORT_LOG1("Opened Outlook account: %s\n", nativeKeyName.get());
+
+ nsCOMPtr<nsIMsgAccount> account;
+ nsAutoString value;
+ rv = subKey->ReadStringValue(u"IMAP Server"_ns, value);
+ if (NS_SUCCEEDED(rv) &&
+ DoIMAPServer(accMgr, subKey, value, getter_AddRefs(account)))
+ accounts++;
+
+ rv = subKey->ReadStringValue(u"POP3 Server"_ns, value);
+ if (NS_SUCCEEDED(rv) &&
+ DoPOP3Server(accMgr, subKey, value, getter_AddRefs(account))) {
+ popCount++;
+ accounts++;
+ if (aAccount && account) {
+ // If we created a mail account, get rid of it since
+ // we have 2 POP accounts!
+ if (popCount > 1)
+ NS_RELEASE(*aAccount);
+ else
+ NS_ADDREF(*aAccount = account);
+ }
+ }
+
+ // Is this the default account?
+ if (account && keyName.Equals(defMailName))
+ accMgr->SetDefaultAccount(account);
+ }
+
+ // Now save the new acct info to pref file.
+ rv = accMgr->SaveAccountInfo();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Can't save account info to pref file");
+
+ return accounts != 0;
+}
+
+nsresult OutlookSettings::GetAccountName(nsIWindowsRegKey* aKey,
+ const nsString& aDefaultName,
+ nsAString& aAccountName) {
+ nsresult rv;
+ rv = aKey->ReadStringValue(u"Account Name"_ns, aAccountName);
+ if (NS_FAILED(rv)) aAccountName.Assign(aDefaultName);
+
+ return NS_OK;
+}
+
+bool OutlookSettings::DoIMAPServer(nsIMsgAccountManager* aMgr,
+ nsIWindowsRegKey* aKey,
+ const nsString& aServerName,
+ nsIMsgAccount** aAccount) {
+ nsAutoString userName;
+ nsresult rv;
+ rv = aKey->ReadStringValue(u"IMAP User Name"_ns, userName);
+ if (NS_FAILED(rv)) return false;
+
+ bool result = false;
+
+ // I now have a user name/server name pair, find out if it already exists?
+ nsAutoCString nativeUserName;
+ NS_CopyUnicodeToNative(userName, nativeUserName);
+ nsAutoCString nativeServerName;
+ NS_CopyUnicodeToNative(aServerName, nativeServerName);
+ nsCOMPtr<nsIMsgIncomingServer> in;
+ aMgr->FindServer(nativeUserName, nativeServerName, "imap"_ns, 0,
+ getter_AddRefs(in));
+ if (!in) {
+ // Create the incoming server and an account for it?
+ rv = aMgr->CreateIncomingServer(nativeUserName, nativeServerName, "imap"_ns,
+ getter_AddRefs(in));
+ if (NS_SUCCEEDED(rv) && in) {
+ rv = in->SetType("imap"_ns);
+ // TODO SSL, auth method
+
+ IMPORT_LOG2("Created IMAP server named: %s, userName: %s\n",
+ nativeServerName.get(), nativeUserName.get());
+
+ nsAutoString prettyName;
+ if (NS_SUCCEEDED(GetAccountName(aKey, aServerName, prettyName)))
+ rv = in->SetPrettyName(prettyName);
+ // We have a server, create an account.
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = aMgr->CreateAccount(getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account) {
+ rv = account->SetIncomingServer(in);
+
+ IMPORT_LOG0(
+ "Created an account and set the IMAP server as the incoming "
+ "server\n");
+
+ // Fiddle with the identities
+ SetIdentities(aMgr, account, aKey);
+ result = true;
+ if (aAccount) account.forget(aAccount);
+ }
+ }
+ } else
+ result = true;
+
+ return result;
+}
+
+bool OutlookSettings::DoPOP3Server(nsIMsgAccountManager* aMgr,
+ nsIWindowsRegKey* aKey,
+ const nsString& aServerName,
+ nsIMsgAccount** aAccount) {
+ nsAutoString userName;
+ nsresult rv;
+ rv = aKey->ReadStringValue(u"POP3 User Name"_ns, userName);
+ if (NS_FAILED(rv)) return false;
+
+ // I now have a user name/server name pair, find out if it already exists?
+ nsAutoCString nativeUserName;
+ NS_CopyUnicodeToNative(userName, nativeUserName);
+ nsAutoCString nativeServerName;
+ NS_CopyUnicodeToNative(aServerName, nativeServerName);
+ nsCOMPtr<nsIMsgIncomingServer> in;
+ aMgr->FindServer(nativeUserName, nativeServerName, "pop3"_ns, 0,
+ getter_AddRefs(in));
+ if (in) return true;
+
+ // Create the incoming server and an account for it?
+ rv = aMgr->CreateIncomingServer(nativeUserName, nativeServerName, "pop3"_ns,
+ getter_AddRefs(in));
+ rv = in->SetType("pop3"_ns);
+
+ // TODO SSL, auth method
+
+ nsCOMPtr<nsIPop3IncomingServer> pop3Server = do_QueryInterface(in);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // set local folders as the Inbox to use for this POP3 server
+ nsCOMPtr<nsIMsgIncomingServer> localFoldersServer;
+ aMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer));
+
+ if (!localFoldersServer) {
+ // XXX: We may need to move this local folder creation code to the generic
+ // nsImportSettings code if the other import modules end up needing to do
+ // this too. if Local Folders does not exist already, create it
+ rv = aMgr->CreateLocalMailAccount();
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to create Local Folders!\n");
+ return false;
+ }
+ aMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer));
+ }
+
+ // now get the account for this server
+ nsCOMPtr<nsIMsgAccount> localFoldersAccount;
+ aMgr->FindAccountForServer(localFoldersServer,
+ getter_AddRefs(localFoldersAccount));
+ if (localFoldersAccount) {
+ nsCString localFoldersAcctKey;
+ localFoldersAccount->GetKey(localFoldersAcctKey);
+ pop3Server->SetDeferredToAccount(localFoldersAcctKey);
+ pop3Server->SetDeferGetNewMail(true);
+ }
+
+ IMPORT_LOG2("Created POP3 server named: %s, userName: %s\n",
+ nativeServerName.get(), nativeUserName.get());
+
+ nsString prettyName;
+ rv = GetAccountName(aKey, aServerName, prettyName);
+ if (NS_FAILED(rv)) return false;
+
+ rv = in->SetPrettyName(prettyName);
+ // We have a server, create an account.
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = aMgr->CreateAccount(getter_AddRefs(account));
+ if (NS_FAILED(rv)) return false;
+
+ rv = account->SetIncomingServer(in);
+
+ IMPORT_LOG0(
+ "Created a new account and set the incoming server to the POP3 "
+ "server.\n");
+
+ uint32_t leaveOnServer;
+ rv = aKey->ReadIntValue(u"Leave Mail On Server"_ns, &leaveOnServer);
+ if (NS_SUCCEEDED(rv))
+ pop3Server->SetLeaveMessagesOnServer(leaveOnServer == 1 ? true : false);
+
+ // Fiddle with the identities
+ SetIdentities(aMgr, account, aKey);
+
+ if (aAccount) account.forget(aAccount);
+
+ return true;
+}
+
+void OutlookSettings::SetIdentities(nsIMsgAccountManager* aMgr,
+ nsIMsgAccount* aAcc,
+ nsIWindowsRegKey* aKey) {
+ // Get the relevant information for an identity
+ nsAutoString name;
+ aKey->ReadStringValue(u"SMTP Display Name"_ns, name);
+
+ nsAutoString server;
+ aKey->ReadStringValue(u"SMTP Server"_ns, server);
+
+ nsAutoString email;
+ aKey->ReadStringValue(u"SMTP Email Address"_ns, email);
+
+ nsAutoString reply;
+ aKey->ReadStringValue(u"SMTP Reply To Email Address"_ns, reply);
+
+ nsAutoString userName;
+ aKey->ReadStringValue(u"SMTP User Name"_ns, userName);
+
+ nsAutoString orgName;
+ aKey->ReadStringValue(u"SMTP Organization Name"_ns, orgName);
+
+ nsresult rv;
+ nsCOMPtr<nsIMsgIdentity> id;
+ if (!email.IsEmpty() && !name.IsEmpty() && !server.IsEmpty()) {
+ // The default identity, nor any other identities matched,
+ // create a new one and add it to the account.
+ rv = aMgr->CreateIdentity(getter_AddRefs(id));
+ if (id) {
+ id->SetFullName(name);
+ id->SetOrganization(orgName);
+
+ nsAutoCString nativeEmail;
+ NS_CopyUnicodeToNative(email, nativeEmail);
+ id->SetEmail(nativeEmail);
+ if (!reply.IsEmpty()) {
+ nsAutoCString nativeReply;
+ NS_CopyUnicodeToNative(reply, nativeReply);
+ id->SetReplyTo(nativeReply);
+ }
+ aAcc->AddIdentity(id);
+
+ nsAutoCString nativeName;
+ NS_CopyUnicodeToNative(name, nativeName);
+ IMPORT_LOG0("Created identity and added to the account\n");
+ IMPORT_LOG1("\tname: %s\n", nativeName.get());
+ IMPORT_LOG1("\temail: %s\n", nativeEmail.get());
+ }
+ }
+
+ if (userName.IsEmpty()) {
+ nsCOMPtr<nsIMsgIncomingServer> incomingServer;
+ rv = aAcc->GetIncomingServer(getter_AddRefs(incomingServer));
+ if (NS_SUCCEEDED(rv) && incomingServer) {
+ nsAutoCString nativeUserName;
+ rv = incomingServer->GetUsername(nativeUserName);
+ NS_ASSERTION(NS_SUCCEEDED(rv),
+ "Unable to get UserName from incomingServer");
+ NS_CopyNativeToUnicode(nativeUserName, userName);
+ }
+ }
+
+ SetSmtpServer(aMgr, aAcc, id, server, userName);
+}
+
+nsresult OutlookSettings::SetSmtpServerKey(nsIMsgIdentity* aId,
+ nsISmtpServer* aServer) {
+ nsAutoCString smtpServerKey;
+ aServer->GetKey(getter_Copies(smtpServerKey));
+ return aId->SetSmtpServerKey(smtpServerKey);
+}
+
+nsresult OutlookSettings::SetSmtpServer(nsIMsgAccountManager* aMgr,
+ nsIMsgAccount* aAcc,
+ nsIMsgIdentity* aId,
+ const nsString& aServer,
+ const nsString& aUser) {
+ nsresult rv;
+ nsCOMPtr<nsISmtpService> smtpService(
+ do_GetService("@mozilla.org/messengercompose/smtp;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoCString nativeUserName;
+ NS_CopyUnicodeToNative(aUser, nativeUserName);
+ nsAutoCString nativeServerName;
+ NS_CopyUnicodeToNative(aServer, nativeServerName);
+ nsCOMPtr<nsISmtpServer> foundServer;
+ rv = smtpService->FindServer(nativeUserName.get(), nativeServerName.get(),
+ getter_AddRefs(foundServer));
+ if (NS_SUCCEEDED(rv) && foundServer) {
+ if (aId) SetSmtpServerKey(aId, foundServer);
+ IMPORT_LOG1("SMTP server already exists: %s\n", nativeServerName.get());
+ return rv;
+ }
+
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ rv = smtpService->CreateServer(getter_AddRefs(smtpServer));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ smtpServer->SetHostname(nativeServerName);
+ if (!aUser.IsEmpty()) smtpServer->SetUsername(nativeUserName);
+
+ if (aId) SetSmtpServerKey(aId, smtpServer);
+
+ // TODO SSL, auth method
+ IMPORT_LOG1("Created new SMTP server: %s\n", nativeServerName.get());
+ return NS_OK;
+}
diff --git a/comm/mailnews/import/src/nsOutlookSettings.h b/comm/mailnews/import/src/nsOutlookSettings.h
new file mode 100644
index 0000000000..7410200c3d
--- /dev/null
+++ b/comm/mailnews/import/src/nsOutlookSettings.h
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 4; 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 nsOutlookSettings_h___
+#define nsOutlookSettings_h___
+
+#include "nsIImportSettings.h"
+
+class nsOutlookSettings : public nsIImportSettings {
+ public:
+ nsOutlookSettings();
+
+ static nsresult Create(nsIImportSettings** aImport);
+
+ // nsISupports interface
+ NS_DECL_ISUPPORTS
+
+ // nsIImportSettings interface
+ NS_DECL_NSIIMPORTSETTINGS
+
+ private:
+ virtual ~nsOutlookSettings();
+};
+
+#endif /* nsOutlookSettings_h___ */
diff --git a/comm/mailnews/import/src/nsOutlookStringBundle.cpp b/comm/mailnews/import/src/nsOutlookStringBundle.cpp
new file mode 100644
index 0000000000..140eb9a541
--- /dev/null
+++ b/comm/mailnews/import/src/nsOutlookStringBundle.cpp
@@ -0,0 +1,53 @@
+/* -*- 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 "prprf.h"
+#include "prmem.h"
+#include "nsCOMPtr.h"
+#include "nsMsgUtils.h"
+#include "nsIStringBundle.h"
+#include "nsOutlookStringBundle.h"
+#include "mozilla/Components.h"
+
+#define OUTLOOK_MSGS_URL \
+ "chrome://messenger/locale/outlookImportMsgs.properties"
+
+nsCOMPtr<nsIStringBundle> nsOutlookStringBundle::m_pBundle = nullptr;
+
+void nsOutlookStringBundle::GetStringBundle(void) {
+ if (m_pBundle) return;
+
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::components::StringBundle::Service();
+ if (sBundleService) {
+ sBundleService->CreateBundle(OUTLOOK_MSGS_URL, getter_AddRefs(m_pBundle));
+ }
+}
+
+void nsOutlookStringBundle::GetStringByID(int32_t stringID, nsString& result) {
+ char16_t* ptrv = GetStringByID(stringID);
+ result = ptrv;
+ FreeString(ptrv);
+}
+
+char16_t* nsOutlookStringBundle::GetStringByID(int32_t stringID) {
+ if (m_pBundle) GetStringBundle();
+
+ if (m_pBundle) {
+ nsAutoString str;
+ nsresult rv = m_pBundle->GetStringFromID(stringID, str);
+
+ if (NS_SUCCEEDED(rv)) return ToNewUnicode(str);
+ }
+
+ nsString resultString;
+ resultString.AppendLiteral("[StringID ");
+ resultString.AppendInt(stringID);
+ resultString.AppendLiteral("?]");
+
+ return ToNewUnicode(resultString);
+}
+
+void nsOutlookStringBundle::Cleanup(void) { m_pBundle = nullptr; }
diff --git a/comm/mailnews/import/src/nsOutlookStringBundle.h b/comm/mailnews/import/src/nsOutlookStringBundle.h
new file mode 100644
index 0000000000..491bbfaa64
--- /dev/null
+++ b/comm/mailnews/import/src/nsOutlookStringBundle.h
@@ -0,0 +1,36 @@
+/* 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 _nsOutlookStringBundle_H__
+#define _nsOutlookStringBundle_H__
+
+#include "nsCRTGlue.h"
+#include "nsString.h"
+
+class nsIStringBundle;
+
+class nsOutlookStringBundle {
+ public:
+ static char16_t* GetStringByID(int32_t stringID);
+ static void GetStringByID(int32_t stringID, nsString& result);
+ static void GetStringBundle(void);
+ static void FreeString(char16_t* pStr) { free(pStr); }
+ static void Cleanup(void);
+
+ private:
+ static nsCOMPtr<nsIStringBundle> m_pBundle;
+};
+
+#define OUTLOOKIMPORT_NAME 2000
+#define OUTLOOKIMPORT_DESCRIPTION 2010
+#define OUTLOOKIMPORT_MAILBOX_SUCCESS 2002
+#define OUTLOOKIMPORT_MAILBOX_BADPARAM 2003
+#define OUTLOOKIMPORT_MAILBOX_CONVERTERROR 2004
+#define OUTLOOKIMPORT_ADDRNAME 2005
+#define OUTLOOKIMPORT_ADDRESS_SUCCESS 2006
+#define OUTLOOKIMPORT_ADDRESS_BADPARAM 2007
+#define OUTLOOKIMPORT_ADDRESS_BADSOURCEFILE 2008
+#define OUTLOOKIMPORT_ADDRESS_CONVERTERROR 2009
+
+#endif /* _nsOutlookStringBundle_H__ */
diff --git a/comm/mailnews/import/src/nsTextAddress.cpp b/comm/mailnews/import/src/nsTextAddress.cpp
new file mode 100644
index 0000000000..497bd5b398
--- /dev/null
+++ b/comm/mailnews/import/src/nsTextAddress.cpp
@@ -0,0 +1,426 @@
+/* -*- 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 "nsTextAddress.h"
+#include "nsIAbCard.h"
+#include "nsIAbDirectory.h"
+#include "nsNativeCharsetUtils.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsNetUtil.h"
+#include "nsMsgI18N.h"
+#include "nsMsgUtils.h"
+#include "nsIConverterInputStream.h"
+#include "nsIUnicharLineInputStream.h"
+#include "nsMsgUtils.h"
+
+#include "ImportDebug.h"
+#include "plstr.h"
+#include "msgCore.h"
+
+#define kWhitespace " \t\b\r\n"
+
+nsTextAddress::nsTextAddress() {
+ m_LFCount = 0;
+ m_CRCount = 0;
+}
+
+nsTextAddress::~nsTextAddress() {}
+
+nsresult nsTextAddress::GetUnicharLineStreamForFile(
+ nsIFile* aFile, nsIInputStream* aInputStream,
+ nsIUnicharLineInputStream** aStream) {
+ nsAutoCString charset;
+ nsresult rv = MsgDetectCharsetFromFile(aFile, charset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIConverterInputStream> converterStream =
+ do_CreateInstance("@mozilla.org/intl/converter-input-stream;1", &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = converterStream->Init(
+ aInputStream, charset.get(), 8192,
+ nsIConverterInputStream::DEFAULT_REPLACEMENT_CHARACTER);
+ }
+
+ return CallQueryInterface(converterStream, aStream);
+}
+
+nsresult nsTextAddress::ImportAddresses(bool* pAbort, const char16_t* pName,
+ nsIFile* pSrc,
+ nsIAbDirectory* pDirectory,
+ nsIImportFieldMap* fieldMap,
+ nsString& errors, uint32_t* pProgress) {
+ // Open the source file for reading, read each line and process it!
+ m_directory = pDirectory;
+ m_fieldMap = fieldMap;
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), pSrc);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening address file for reading\n");
+ return rv;
+ }
+
+ // Here we use this to work out the size of the file, so we can update
+ // an integer as we go through the file which will update a progress
+ // bar if required by the caller.
+ uint64_t bytesLeft = 0;
+ rv = inputStream->Available(&bytesLeft);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error checking address file for size\n");
+ inputStream->Close();
+ return rv;
+ }
+
+ uint64_t totalBytes = bytesLeft;
+ bool skipRecord = false;
+
+ rv = m_fieldMap->GetSkipFirstRecord(&skipRecord);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0(
+ "*** Error checking to see if we should skip the first record\n");
+ return rv;
+ }
+
+ nsCOMPtr<nsIUnicharLineInputStream> lineStream;
+ rv = GetUnicharLineStreamForFile(pSrc, inputStream,
+ getter_AddRefs(lineStream));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening converter stream for importer\n");
+ return rv;
+ }
+
+ bool more = true;
+ nsAutoString line;
+
+ // Skip the first record if the user has requested it.
+ if (skipRecord) rv = ReadRecord(lineStream, line, &more);
+
+ while (!(*pAbort) && more && NS_SUCCEEDED(rv)) {
+ // Read the line in
+ rv = ReadRecord(lineStream, line, &more);
+ if (NS_SUCCEEDED(rv)) {
+ // Now process it to add it to the database
+ rv = ProcessLine(line, errors);
+
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error processing text record.\n");
+ }
+ }
+ if (NS_SUCCEEDED(rv) && pProgress) {
+ // This won't be totally accurate, but its the best we can do
+ // considering that lineStream won't give us how many bytes
+ // are actually left.
+ bytesLeft -= line.Length();
+ *pProgress = std::min(totalBytes - bytesLeft, uint64_t(PR_UINT32_MAX));
+ }
+ }
+
+ inputStream->Close();
+
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0(
+ "*** Error reading the address book - probably incorrect ending\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsTextAddress::ReadRecord(nsIUnicharLineInputStream* aLineStream,
+ nsAString& aLine, bool* aMore) {
+ bool more = true;
+ uint32_t numQuotes = 0;
+ nsresult rv;
+ nsAutoString line;
+
+ // ensure aLine is empty
+ aLine.Truncate();
+
+ do {
+ if (!more) {
+ // No more, so we must have an incorrect file.
+ rv = NS_ERROR_FAILURE;
+ } else {
+ // Read the line and append it
+ rv = aLineStream->ReadLine(line, &more);
+ if (NS_SUCCEEDED(rv)) {
+ if (!aLine.IsEmpty()) aLine.AppendLiteral(MSG_LINEBREAK);
+ aLine.Append(line);
+
+ numQuotes += line.CountChar(char16_t('"'));
+ }
+ }
+ // Continue whilst everything is ok, and we have an odd number of quotes.
+ } while (NS_SUCCEEDED(rv) && (numQuotes % 2 != 0));
+
+ *aMore = more;
+ return rv;
+}
+
+nsresult nsTextAddress::ReadRecordNumber(nsIFile* aSrc, nsAString& aLine,
+ int32_t rNum) {
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aSrc);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening address file for reading\n");
+ return rv;
+ }
+
+ int32_t rIndex = 0;
+ uint64_t bytesLeft = 0;
+
+ rv = inputStream->Available(&bytesLeft);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error checking address file for eof\n");
+ inputStream->Close();
+ return rv;
+ }
+
+ nsCOMPtr<nsIUnicharLineInputStream> lineStream;
+ rv = GetUnicharLineStreamForFile(aSrc, inputStream,
+ getter_AddRefs(lineStream));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening converter stream for importer\n");
+ return rv;
+ }
+
+ bool more = true;
+
+ while (more && (rIndex <= rNum)) {
+ rv = ReadRecord(lineStream, aLine, &more);
+ if (NS_FAILED(rv)) {
+ inputStream->Close();
+ return rv;
+ }
+ if (rIndex == rNum) {
+ inputStream->Close();
+ return NS_OK;
+ }
+
+ rIndex++;
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+int32_t nsTextAddress::CountFields(const nsAString& aLine, char16_t delim) {
+ int32_t pos = 0;
+ int32_t maxLen = aLine.Length();
+ int32_t count = 0;
+ char16_t tab = char16_t('\t');
+ char16_t doubleQuote = char16_t('"');
+
+ if (delim == tab) tab = char16_t('\0');
+
+ while (pos < maxLen) {
+ while (((aLine[pos] == char16_t(' ')) || (aLine[pos] == tab)) &&
+ (pos < maxLen)) {
+ pos++;
+ }
+ if ((pos < maxLen) && (aLine[pos] == doubleQuote)) {
+ pos++;
+ while ((pos < maxLen) && (aLine[pos] != doubleQuote)) {
+ pos++;
+ if (((pos + 1) < maxLen) && (aLine[pos] == doubleQuote) &&
+ (aLine[pos + 1] == doubleQuote)) {
+ pos += 2;
+ }
+ }
+ if (pos < maxLen) pos++;
+ }
+ while ((pos < maxLen) && (aLine[pos] != delim)) pos++;
+
+ count++;
+ pos++;
+ }
+
+ return count;
+}
+
+bool nsTextAddress::GetField(const nsAString& aLine, int32_t index,
+ nsString& field, char16_t delim) {
+ bool result = false;
+ int32_t pos = 0;
+ int32_t maxLen = aLine.Length();
+ char16_t tab = char16_t('\t');
+ char16_t doubleQuote = char16_t('"');
+
+ field.Truncate();
+
+ if (delim == tab) tab = 0;
+
+ while (index && (pos < maxLen)) {
+ while (((aLine[pos] == char16_t(' ')) || (aLine[pos] == tab)) &&
+ (pos < maxLen)) {
+ pos++;
+ }
+ if (pos >= maxLen) break;
+ if (aLine[pos] == doubleQuote) {
+ do {
+ pos++;
+ if (((pos + 1) < maxLen) && (aLine[pos] == doubleQuote) &&
+ (aLine[pos + 1] == doubleQuote)) {
+ pos += 2;
+ }
+ } while ((pos < maxLen) && (aLine[pos] != doubleQuote));
+ if (pos < maxLen) pos++;
+ }
+ if (pos >= maxLen) break;
+
+ while ((pos < maxLen) && (aLine[pos] != delim)) pos++;
+
+ if (pos >= maxLen) break;
+
+ index--;
+ pos++;
+ }
+
+ if (pos >= maxLen) return result;
+
+ result = true;
+
+ while ((pos < maxLen) && ((aLine[pos] == ' ') || (aLine[pos] == tab))) pos++;
+
+ int32_t fLen = 0;
+ int32_t startPos = pos;
+ bool quoted = false;
+ if (aLine[pos] == '"') {
+ startPos++;
+ fLen = -1;
+ do {
+ pos++;
+ fLen++;
+ if (((pos + 1) < maxLen) && (aLine[pos] == doubleQuote) &&
+ (aLine[pos + 1] == doubleQuote)) {
+ quoted = true;
+ pos += 2;
+ fLen += 2;
+ }
+ } while ((pos < maxLen) && (aLine[pos] != doubleQuote));
+ } else {
+ while ((pos < maxLen) && (aLine[pos] != delim)) {
+ pos++;
+ fLen++;
+ }
+ }
+
+ if (!fLen) {
+ return result;
+ }
+
+ field.Append(nsDependentSubstring(aLine, startPos, fLen));
+ field.Trim(kWhitespace);
+
+ if (quoted) {
+ int32_t offset = field.Find(u"\"\"");
+ while (offset != -1) {
+ field.Cut(offset, 1);
+ offset = field.Find(u"\"\"", offset + 1);
+ }
+ }
+
+ return result;
+}
+
+nsresult nsTextAddress::DetermineDelim(nsIFile* aSrc) {
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), aSrc);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening address file for reading\n");
+ return rv;
+ }
+
+ int32_t lineCount = 0;
+ int32_t tabCount = 0;
+ int32_t commaCount = 0;
+ int32_t tabLines = 0;
+ int32_t commaLines = 0;
+ nsAutoString line;
+ bool more = true;
+
+ nsCOMPtr<nsIUnicharLineInputStream> lineStream;
+ rv = GetUnicharLineStreamForFile(aSrc, inputStream,
+ getter_AddRefs(lineStream));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening converter stream for importer\n");
+ return rv;
+ }
+
+ while (more && NS_SUCCEEDED(rv) && (lineCount < 100)) {
+ rv = lineStream->ReadLine(line, &more);
+ if (NS_SUCCEEDED(rv)) {
+ tabCount = CountFields(line, char16_t('\t'));
+ commaCount = CountFields(line, char16_t(','));
+ if (tabCount > commaCount)
+ tabLines++;
+ else if (commaCount)
+ commaLines++;
+ }
+ lineCount++;
+ }
+
+ rv = inputStream->Close();
+
+ if (tabLines > commaLines)
+ m_delim = char16_t('\t');
+ else
+ m_delim = char16_t(',');
+
+ IMPORT_LOG2("Tab count = %d, Comma count = %d\n", tabLines, commaLines);
+
+ return rv;
+}
+
+/*
+ This is where the real work happens!
+ Go through the field map and set the data in a new database row
+*/
+nsresult nsTextAddress::ProcessLine(const nsAString& aLine, nsString& errors) {
+ if (!m_fieldMap) {
+ IMPORT_LOG0("*** Error, text import needs a field map\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+
+ // Wait until we get our first non-empty field, then create a new row,
+ // fill in the data, then add the row to the database.
+ nsCOMPtr<nsIAbCard> newCard;
+ nsAutoString fieldVal;
+ int32_t fieldNum;
+ int32_t numFields = 0;
+ bool active;
+ rv = m_fieldMap->GetMapSize(&numFields);
+ for (int32_t i = 0; (i < numFields) && NS_SUCCEEDED(rv); i++) {
+ active = false;
+ rv = m_fieldMap->GetFieldMap(i, &fieldNum);
+ if (NS_SUCCEEDED(rv)) rv = m_fieldMap->GetFieldActive(i, &active);
+ if (NS_SUCCEEDED(rv) && active) {
+ if (GetField(aLine, i, fieldVal, m_delim)) {
+ if (!fieldVal.IsEmpty()) {
+ if (!newCard) {
+ newCard = do_CreateInstance(
+ "@mozilla.org/addressbook/cardproperty;1", &rv);
+ }
+ if (newCard) {
+ rv = m_fieldMap->SetFieldValue(m_directory, newCard, fieldNum,
+ fieldVal);
+ }
+ }
+ } else {
+ break;
+ }
+ } else if (active) {
+ IMPORT_LOG1("*** Error getting field map for index %ld\n", (long)i);
+ }
+ }
+
+ nsIAbCard* outCard;
+ rv = m_directory->AddCard(newCard, &outCard);
+
+ return rv;
+}
diff --git a/comm/mailnews/import/src/nsTextAddress.h b/comm/mailnews/import/src/nsTextAddress.h
new file mode 100644
index 0000000000..58b37755b3
--- /dev/null
+++ b/comm/mailnews/import/src/nsTextAddress.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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 nsTextAddress_h__
+#define nsTextAddress_h__
+
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsIImportFieldMap.h"
+#include "nsIImportService.h"
+
+class nsIAbDirectory;
+class nsIFile;
+class nsIInputStream;
+class nsIUnicharLineInputStream;
+
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+/////////////////////////////////////////////////////////////////////////////////////
+
+class nsTextAddress {
+ public:
+ nsTextAddress();
+ virtual ~nsTextAddress();
+
+ nsresult ImportAddresses(bool* pAbort, const char16_t* pName, nsIFile* pSrc,
+ nsIAbDirectory* pDirectory,
+ nsIImportFieldMap* fieldMap, nsString& errors,
+ uint32_t* pProgress);
+
+ nsresult DetermineDelim(nsIFile* pSrc);
+ char16_t GetDelim(void) { return m_delim; }
+
+ static nsresult ReadRecordNumber(nsIFile* pSrc, nsAString& aLine,
+ int32_t rNum);
+ static bool GetField(const nsAString& aLine, int32_t index, nsString& field,
+ char16_t delim);
+
+ private:
+ nsresult ProcessLine(const nsAString& aLine, nsString& errors);
+
+ static int32_t CountFields(const nsAString& aLine, char16_t delim);
+ static nsresult ReadRecord(nsIUnicharLineInputStream* pSrc, nsAString& aLine,
+ bool* aMore);
+ static nsresult GetUnicharLineStreamForFile(
+ nsIFile* aFile, nsIInputStream* aInputStream,
+ nsIUnicharLineInputStream** aStream);
+
+ char16_t m_delim;
+ int32_t m_LFCount;
+ int32_t m_CRCount;
+ nsCOMPtr<nsIAbDirectory> m_directory;
+ nsCOMPtr<nsIImportFieldMap> m_fieldMap;
+ nsCOMPtr<nsIImportService> m_pService;
+};
+
+#endif /* nsTextAddress_h__ */
diff --git a/comm/mailnews/import/src/nsTextImport.cpp b/comm/mailnews/import/src/nsTextImport.cpp
new file mode 100644
index 0000000000..961f1631ae
--- /dev/null
+++ b/comm/mailnews/import/src/nsTextImport.cpp
@@ -0,0 +1,646 @@
+/* -*- 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/. */
+
+/*
+ * Text import addressbook interfaces
+ */
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsIImportService.h"
+#include "nsMsgI18N.h"
+#include "nsTextImport.h"
+#include "nsIImportGeneric.h"
+#include "nsIImportAddressBooks.h"
+#include "nsIImportABDescriptor.h"
+#include "nsIImportFieldMap.h"
+#include "nsIAbLDIFService.h"
+#include "nsTextFormatter.h"
+#include "nsImportStringBundle.h"
+#include "nsTextAddress.h"
+#include "nsIPrefService.h"
+#include "nsIPrefBranch.h"
+#include "ImportDebug.h"
+#include "nsNetUtil.h"
+#include "nsMsgUtils.h"
+
+#define TEXT_MSGS_URL "chrome://messenger/locale/textImportMsgs.properties"
+#define TEXTIMPORT_NAME 2000
+#define TEXTIMPORT_DESCRIPTION 2001
+#define TEXTIMPORT_ADDRESS_NAME 2002
+#define TEXTIMPORT_ADDRESS_SUCCESS 2003
+#define TEXTIMPORT_ADDRESS_BADPARAM 2004
+#define TEXTIMPORT_ADDRESS_BADSOURCEFILE 2005
+#define TEXTIMPORT_ADDRESS_CONVERTERROR 2006
+
+class ImportAddressImpl final : public nsIImportAddressBooks {
+ public:
+ explicit ImportAddressImpl(nsIStringBundle* aStringBundle);
+
+ static nsresult Create(nsIImportAddressBooks** aImport,
+ nsIStringBundle* aStringBundle);
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIImportAddressBooks interface
+
+ NS_IMETHOD GetSupportsMultiple(bool* _retval) override {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetAutoFind(char16_t** description, bool* _retval) override;
+
+ NS_IMETHOD GetNeedsFieldMap(nsIFile* location, bool* _retval) override;
+
+ NS_IMETHOD GetDefaultLocation(nsIFile** location, bool* found,
+ bool* userVerify) override;
+
+ NS_IMETHOD FindAddressBooks(
+ nsIFile* location,
+ nsTArray<RefPtr<nsIImportABDescriptor>>& books) override;
+
+ NS_IMETHOD InitFieldMap(nsIImportFieldMap* fieldMap) override;
+
+ NS_IMETHOD ImportAddressBook(nsIImportABDescriptor* source,
+ nsIAbDirectory* destination,
+ nsIImportFieldMap* fieldMap,
+ nsISupports* aSupportService,
+ char16_t** errorLog, char16_t** successLog,
+ bool* fatalError) override;
+
+ NS_IMETHOD GetImportProgress(uint32_t* _retval) override;
+
+ NS_IMETHOD GetSampleData(int32_t index, bool* pFound,
+ char16_t** pStr) override;
+
+ NS_IMETHOD SetSampleLocation(nsIFile*) override;
+
+ private:
+ void ClearSampleFile(void);
+ void SaveFieldMap(nsIImportFieldMap* pMap);
+
+ static void ReportSuccess(nsString& name, nsString* pStream,
+ nsIStringBundle* pBundle);
+ static void SetLogs(nsString& success, nsString& error, char16_t** pError,
+ char16_t** pSuccess);
+ static void ReportError(int32_t errorNum, nsString& name, nsString* pStream,
+ nsIStringBundle* pBundle);
+ static void SanitizeSampleData(nsString& val);
+
+ private:
+ ~ImportAddressImpl() {}
+ nsTextAddress m_text;
+ bool m_haveDelim;
+ nsCOMPtr<nsIFile> m_fileLoc;
+ nsCOMPtr<nsIStringBundle> m_notProxyBundle;
+ char16_t m_delim;
+ uint32_t m_bytesImported;
+};
+
+////////////////////////////////////////////////////////////////////////
+////////////////////////////////////////////////////////////////////////
+
+nsTextImport::nsTextImport() {
+ IMPORT_LOG0("nsTextImport Module Created\n");
+
+ nsImportStringBundle::GetStringBundle(TEXT_MSGS_URL,
+ getter_AddRefs(m_stringBundle));
+}
+
+nsTextImport::~nsTextImport() { IMPORT_LOG0("nsTextImport Module Deleted\n"); }
+
+NS_IMPL_ISUPPORTS(nsTextImport, nsIImportModule)
+
+NS_IMETHODIMP nsTextImport::GetName(char16_t** name) {
+ NS_ENSURE_ARG_POINTER(name);
+ *name = nsImportStringBundle::GetStringByID(TEXTIMPORT_NAME, m_stringBundle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTextImport::GetDescription(char16_t** name) {
+ NS_ENSURE_ARG_POINTER(name);
+ *name = nsImportStringBundle::GetStringByID(TEXTIMPORT_DESCRIPTION,
+ m_stringBundle);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTextImport::GetSupports(char** supports) {
+ NS_ENSURE_ARG_POINTER(supports);
+ *supports = strdup(kTextSupportsString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTextImport::GetSupportsUpgrade(bool* pUpgrade) {
+ NS_ASSERTION(pUpgrade != nullptr, "null ptr");
+ if (!pUpgrade) return NS_ERROR_NULL_POINTER;
+
+ *pUpgrade = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsTextImport::GetImportInterface(const char* pImportType,
+ nsISupports** ppInterface) {
+ NS_ENSURE_ARG_POINTER(pImportType);
+ NS_ENSURE_ARG_POINTER(ppInterface);
+
+ *ppInterface = nullptr;
+ nsresult rv;
+
+ if (!strcmp(pImportType, "addressbook")) {
+ // create the nsIImportMail interface and return it!
+ nsCOMPtr<nsIImportAddressBooks> pAddress;
+ nsCOMPtr<nsIImportGeneric> pGeneric;
+ rv = ImportAddressImpl::Create(getter_AddRefs(pAddress), m_stringBundle);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImportService> impSvc(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = impSvc->CreateNewGenericAddressBooks(getter_AddRefs(pGeneric));
+ if (NS_SUCCEEDED(rv)) {
+ pGeneric->SetData("addressInterface", pAddress);
+ nsCOMPtr<nsISupports> pInterface(do_QueryInterface(pGeneric));
+ pInterface.forget(ppInterface);
+ }
+ }
+ }
+ return rv;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+
+nsresult ImportAddressImpl::Create(nsIImportAddressBooks** aImport,
+ nsIStringBundle* aStringBundle) {
+ NS_ENSURE_ARG_POINTER(aImport);
+ NS_ADDREF(*aImport = new ImportAddressImpl(aStringBundle));
+ return NS_OK;
+}
+
+ImportAddressImpl::ImportAddressImpl(nsIStringBundle* aStringBundle)
+ : m_notProxyBundle(aStringBundle) {
+ m_haveDelim = false;
+}
+
+NS_IMPL_ISUPPORTS(ImportAddressImpl, nsIImportAddressBooks)
+
+NS_IMETHODIMP ImportAddressImpl::GetAutoFind(char16_t** addrDescription,
+ bool* _retval) {
+ NS_ASSERTION(addrDescription != nullptr, "null ptr");
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!addrDescription || !_retval) return NS_ERROR_NULL_POINTER;
+
+ nsString str;
+ *_retval = false;
+
+ if (!m_notProxyBundle) return NS_ERROR_FAILURE;
+
+ nsImportStringBundle::GetStringByID(TEXTIMPORT_ADDRESS_NAME, m_notProxyBundle,
+ str);
+ *addrDescription = ToNewUnicode(str);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ImportAddressImpl::GetDefaultLocation(nsIFile** ppLoc,
+ bool* found,
+ bool* userVerify) {
+ NS_ASSERTION(found != nullptr, "null ptr");
+ NS_ASSERTION(ppLoc != nullptr, "null ptr");
+ NS_ASSERTION(userVerify != nullptr, "null ptr");
+ if (!found || !userVerify || !ppLoc) return NS_ERROR_NULL_POINTER;
+
+ *ppLoc = nullptr;
+ *found = false;
+ *userVerify = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ImportAddressImpl::FindAddressBooks(
+ nsIFile* pLoc, nsTArray<RefPtr<nsIImportABDescriptor>>& books) {
+ NS_ASSERTION(pLoc != nullptr, "null ptr");
+ if (!pLoc) return NS_ERROR_NULL_POINTER;
+
+ books.Clear();
+ ClearSampleFile();
+
+ bool exists = false;
+ nsresult rv = pLoc->Exists(&exists);
+ if (NS_FAILED(rv) || !exists) return NS_ERROR_FAILURE;
+
+ bool isFile = false;
+ rv = pLoc->IsFile(&isFile);
+ if (NS_FAILED(rv) || !isFile) return NS_ERROR_FAILURE;
+
+ rv = m_text.DetermineDelim(pLoc);
+
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error determining delimitter\n");
+ return rv;
+ }
+ m_haveDelim = true;
+ m_delim = m_text.GetDelim();
+
+ m_fileLoc = pLoc;
+
+ /* Build an address book descriptor based on the file passed in! */
+ nsString name;
+ m_fileLoc->GetLeafName(name);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed getting leaf name of file\n");
+ return rv;
+ }
+
+ int32_t idx = name.RFindChar('.');
+ if ((idx != -1) && (idx > 0) && ((name.Length() - idx - 1) < 5)) {
+ name.SetLength(idx);
+ }
+
+ nsCOMPtr<nsIImportABDescriptor> desc;
+
+ nsCOMPtr<nsIImportService> impSvc(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to obtain the import service\n");
+ return rv;
+ }
+
+ rv = impSvc->CreateNewABDescriptor(getter_AddRefs(desc));
+ if (NS_SUCCEEDED(rv)) {
+ int64_t sz = 0;
+ pLoc->GetFileSize(&sz);
+ desc->SetPreferredName(name);
+ desc->SetSize((uint32_t)sz);
+ desc->SetAbFile(m_fileLoc);
+ books.AppendElement(desc);
+ }
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error creating address book descriptor for text import\n");
+ return rv;
+ }
+ return NS_OK;
+}
+
+void ImportAddressImpl::ReportSuccess(nsString& name, nsString* pStream,
+ nsIStringBundle* pBundle) {
+ if (!pStream) return;
+
+ // load the success string
+ char16_t* pFmt =
+ nsImportStringBundle::GetStringByID(TEXTIMPORT_ADDRESS_SUCCESS, pBundle);
+
+ nsString pText;
+ nsTextFormatter::ssprintf(pText, pFmt, name.get());
+ pStream->Append(pText);
+ free(pFmt);
+ pStream->Append(char16_t('\n'));
+}
+
+void ImportAddressImpl::ReportError(int32_t errorNum, nsString& name,
+ nsString* pStream,
+ nsIStringBundle* pBundle) {
+ if (!pStream) return;
+
+ // load the error string
+ char16_t* pFmt = nsImportStringBundle::GetStringByID(errorNum, pBundle);
+ nsString pText;
+ nsTextFormatter::ssprintf(pText, pFmt, name.get());
+ pStream->Append(pText);
+ free(pFmt);
+ pStream->Append(char16_t('\n'));
+}
+
+void ImportAddressImpl::SetLogs(nsString& success, nsString& error,
+ char16_t** pError, char16_t** pSuccess) {
+ if (pError) *pError = ToNewUnicode(error);
+ if (pSuccess) *pSuccess = ToNewUnicode(success);
+}
+
+NS_IMETHODIMP
+ImportAddressImpl::ImportAddressBook(nsIImportABDescriptor* pSource,
+ nsIAbDirectory* pDestination,
+ nsIImportFieldMap* fieldMap,
+ nsISupports* aSupportService,
+ char16_t** pErrorLog,
+ char16_t** pSuccessLog, bool* fatalError) {
+ NS_ASSERTION(pSource != nullptr, "null ptr");
+ NS_ASSERTION(pDestination != nullptr, "null ptr");
+ NS_ASSERTION(fatalError != nullptr, "null ptr");
+
+ m_bytesImported = 0;
+
+ nsString success, error;
+ if (!pSource || !pDestination || !fatalError) {
+ IMPORT_LOG0("*** Bad param passed to text address import\n");
+ nsImportStringBundle::GetStringByID(TEXTIMPORT_ADDRESS_BADPARAM,
+ m_notProxyBundle, error);
+
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+
+ if (fatalError) *fatalError = true;
+
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ ClearSampleFile();
+
+ bool addrAbort = false;
+ nsString name;
+ pSource->GetPreferredName(name);
+
+ uint32_t addressSize = 0;
+ pSource->GetSize(&addressSize);
+ if (addressSize == 0) {
+ IMPORT_LOG0("Address book size is 0, skipping import.\n");
+ ReportSuccess(name, &success, m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> inFile;
+ if (NS_FAILED(pSource->GetAbFile(getter_AddRefs(inFile)))) {
+ ReportError(TEXTIMPORT_ADDRESS_BADSOURCEFILE, name, &error,
+ m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aSupportService) {
+ IMPORT_LOG0("Missing support service to import call");
+ return NS_ERROR_FAILURE;
+ }
+
+ bool isLDIF = false;
+ nsresult rv;
+ nsCOMPtr<nsIAbLDIFService> ldifService(
+ do_QueryInterface(aSupportService, &rv));
+
+ if (NS_SUCCEEDED(rv)) {
+ rv = ldifService->IsLDIFFile(inFile, &isLDIF);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error reading address file\n");
+ }
+ }
+
+ if (NS_FAILED(rv)) {
+ ReportError(TEXTIMPORT_ADDRESS_CONVERTERROR, name, &error,
+ m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ return rv;
+ }
+
+ if (isLDIF) {
+ if (ldifService)
+ rv = ldifService->ImportLDIFFile(pDestination, inFile, false,
+ &m_bytesImported);
+ else
+ return NS_ERROR_FAILURE;
+ } else {
+ rv = m_text.ImportAddresses(&addrAbort, name.get(), inFile, pDestination,
+ fieldMap, error, &m_bytesImported);
+ SaveFieldMap(fieldMap);
+ }
+
+ if (NS_SUCCEEDED(rv) && error.IsEmpty()) {
+ ReportSuccess(name, &success, m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ } else {
+ ReportError(TEXTIMPORT_ADDRESS_CONVERTERROR, name, &error,
+ m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ }
+
+ IMPORT_LOG0("*** Text address import done\n");
+ return rv;
+}
+
+NS_IMETHODIMP ImportAddressImpl::GetImportProgress(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = m_bytesImported;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ImportAddressImpl::GetNeedsFieldMap(nsIFile* aLocation,
+ bool* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ NS_ENSURE_ARG_POINTER(aLocation);
+
+ *_retval = true;
+ bool exists = false;
+ bool isFile = false;
+
+ nsresult rv = aLocation->Exists(&exists);
+ rv = aLocation->IsFile(&isFile);
+
+ if (!exists || !isFile) return NS_ERROR_FAILURE;
+
+ bool isLDIF = false;
+ nsCOMPtr<nsIAbLDIFService> ldifService =
+ do_GetService("@mozilla.org/addressbook/abldifservice;1", &rv);
+
+ if (NS_SUCCEEDED(rv)) rv = ldifService->IsLDIFFile(aLocation, &isLDIF);
+
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error determining if file is of type LDIF\n");
+ return rv;
+ }
+
+ if (isLDIF) *_retval = false;
+
+ return NS_OK;
+}
+
+void ImportAddressImpl::SanitizeSampleData(nsString& val) {
+ // remove any line-feeds...
+ int32_t offset = val.Find(u"\x0D\x0A"_ns);
+ while (offset != -1) {
+ val.Replace(offset, 2, u", "_ns);
+ offset = val.Find(u"\x0D\x0A"_ns, offset + 2);
+ }
+ offset = val.FindChar(13);
+ while (offset != -1) {
+ val.Replace(offset, 1, ',');
+ offset = val.FindChar(13, offset + 2);
+ }
+ offset = val.FindChar(10);
+ while (offset != -1) {
+ val.Replace(offset, 1, ',');
+ offset = val.FindChar(10, offset + 2);
+ }
+}
+
+NS_IMETHODIMP ImportAddressImpl::GetSampleData(int32_t index, bool* pFound,
+ char16_t** pStr) {
+ NS_ASSERTION(pFound != nullptr, "null ptr");
+ NS_ASSERTION(pStr != nullptr, "null ptr");
+ if (!pFound || !pStr) return NS_ERROR_NULL_POINTER;
+
+ if (!m_fileLoc) {
+ IMPORT_LOG0("*** Error, called GetSampleData before SetSampleLocation\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv;
+ *pStr = nullptr;
+ char16_t term = 0;
+
+ if (!m_haveDelim) {
+ rv = m_text.DetermineDelim(m_fileLoc);
+ NS_ENSURE_SUCCESS(rv, rv);
+ m_haveDelim = true;
+ m_delim = m_text.GetDelim();
+ }
+
+ bool fileExists;
+ rv = m_fileLoc->Exists(&fileExists);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (!fileExists) {
+ *pFound = false;
+ *pStr = NS_xstrdup(&term);
+ return NS_OK;
+ }
+
+ nsAutoString line;
+ rv = nsTextAddress::ReadRecordNumber(m_fileLoc, line, index);
+ if (NS_SUCCEEDED(rv)) {
+ nsString str;
+ nsString field;
+ int32_t fNum = 0;
+ while (nsTextAddress::GetField(line, fNum, field, m_delim)) {
+ if (fNum) str.Append(char16_t('\n'));
+ SanitizeSampleData(field);
+ str.Append(field);
+ fNum++;
+ field.Truncate();
+ }
+
+ *pStr = ToNewUnicode(str);
+ *pFound = true;
+
+ /* IMPORT_LOG1("Sample data: %S\n", str.get()); */
+ } else {
+ *pFound = false;
+ *pStr = NS_xstrdup(&term);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP ImportAddressImpl::SetSampleLocation(nsIFile* pLocation) {
+ NS_ENSURE_ARG_POINTER(pLocation);
+
+ m_fileLoc = pLocation;
+ m_haveDelim = false;
+ return NS_OK;
+}
+
+void ImportAddressImpl::ClearSampleFile(void) {
+ m_fileLoc = nullptr;
+ m_haveDelim = false;
+}
+
+NS_IMETHODIMP ImportAddressImpl::InitFieldMap(nsIImportFieldMap* fieldMap) {
+ // Let's remember the last one the user used!
+ // This should be normal for someone importing multiple times, it's usually
+ // from the same file format.
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ nsCString prefStr;
+ rv = prefs->GetCharPref("mailnews.import.text.fieldmap", prefStr);
+ if (NS_SUCCEEDED(rv)) {
+ const char* pStr = prefStr.get();
+ if (pStr) {
+ fieldMap->SetFieldMapSize(0);
+ long fNum;
+ bool active;
+ long fIndex = 0;
+ while (*pStr) {
+ while (*pStr && (*pStr != '+') && (*pStr != '-')) pStr++;
+ if (*pStr == '+')
+ active = true;
+ else if (*pStr == '-')
+ active = false;
+ else
+ break;
+ fNum = 0;
+ while (*pStr && ((*pStr < '0') || (*pStr > '9'))) pStr++;
+ if (!(*pStr)) break;
+ while (*pStr && (*pStr >= '0') && (*pStr <= '9')) {
+ fNum *= 10;
+ fNum += (*pStr - '0');
+ pStr++;
+ }
+ while (*pStr && (*pStr != ',')) pStr++;
+ if (*pStr == ',') pStr++;
+ if (!active) {
+ fNum *= -1; // Re-add the stripped minus sign.
+ }
+ fieldMap->SetFieldMap(-1, fNum);
+ fieldMap->SetFieldActive(fIndex, active);
+ fIndex++;
+ }
+ if (!fIndex) {
+ int num;
+ fieldMap->GetNumMozFields(&num);
+ fieldMap->DefaultFieldMap(num);
+ }
+ }
+ }
+
+ // Now also get the last used skip first record value.
+ bool skipFirstRecord = false;
+ rv = prefs->GetBoolPref("mailnews.import.text.skipfirstrecord",
+ &skipFirstRecord);
+ if (NS_SUCCEEDED(rv)) fieldMap->SetSkipFirstRecord(skipFirstRecord);
+ }
+
+ return NS_OK;
+}
+
+void ImportAddressImpl::SaveFieldMap(nsIImportFieldMap* pMap) {
+ if (!pMap) return;
+
+ int size;
+ int index;
+ bool active;
+ nsCString str;
+
+ pMap->GetMapSize(&size);
+ for (long i = 0; i < size; i++) {
+ index = i;
+ active = false;
+ pMap->GetFieldMap(i, &index);
+ pMap->GetFieldActive(i, &active);
+ if (active)
+ str.Append('+');
+ else
+ str.Append('-');
+
+ str.AppendInt(index);
+ str.Append(',');
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsIPrefBranch> prefs(do_GetService(NS_PREFSERVICE_CONTRACTID, &rv));
+
+ if (NS_SUCCEEDED(rv)) {
+ nsCString prefStr;
+ rv = prefs->GetCharPref("mailnews.import.text.fieldmap", prefStr);
+ if (NS_FAILED(rv) || !str.Equals(prefStr))
+ rv = prefs->SetCharPref("mailnews.import.text.fieldmap", str);
+ }
+
+ // Now also save last used skip first record value.
+ bool skipFirstRecord = false;
+ rv = pMap->GetSkipFirstRecord(&skipFirstRecord);
+ if (NS_SUCCEEDED(rv))
+ prefs->SetBoolPref("mailnews.import.text.skipfirstrecord", skipFirstRecord);
+}
diff --git a/comm/mailnews/import/src/nsTextImport.h b/comm/mailnews/import/src/nsTextImport.h
new file mode 100644
index 0000000000..1106a0d098
--- /dev/null
+++ b/comm/mailnews/import/src/nsTextImport.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 nsTextImport_h___
+#define nsTextImport_h___
+
+#include "nsIImportModule.h"
+#include "nsCOMPtr.h"
+#include "nsIStringBundle.h"
+
+#define NS_TEXTIMPORT_CID \
+ { /* A5991D01-ADA7-11d3-A9C2-00A0CC26DA63 */ \
+ 0xa5991d01, 0xada7, 0x11d3, { \
+ 0xa9, 0xc2, 0x0, 0xa0, 0xcc, 0x26, 0xda, 0x63 \
+ } \
+ }
+
+#define kTextSupportsString NS_IMPORT_ADDRESS_STR
+
+class nsTextImport : public nsIImportModule {
+ public:
+ nsTextImport();
+
+ NS_DECL_ISUPPORTS
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // we support the nsIImportModule interface
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ NS_DECL_NSIIMPORTMODULE
+
+ protected:
+ virtual ~nsTextImport();
+ nsCOMPtr<nsIStringBundle> m_stringBundle;
+};
+
+#endif /* nsTextImport_h___ */
diff --git a/comm/mailnews/import/src/nsVCardAddress.cpp b/comm/mailnews/import/src/nsVCardAddress.cpp
new file mode 100644
index 0000000000..2ced1d7c0b
--- /dev/null
+++ b/comm/mailnews/import/src/nsVCardAddress.cpp
@@ -0,0 +1,151 @@
+/* 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 "nsNativeCharsetUtils.h"
+#include "nsNetUtil.h"
+#include "nsVCardAddress.h"
+
+#include "nsIAbCard.h"
+#include "nsIAbDirectory.h"
+#include "nsIFile.h"
+#include "nsIInputStream.h"
+#include "nsIUnicharLineInputStream.h"
+#include "nsIConverterInputStream.h"
+#include "nsIMsgVCardService.h"
+
+#include "plstr.h"
+#include "msgCore.h"
+#include "nsMsgUtils.h"
+
+nsVCardAddress::nsVCardAddress() {}
+
+nsVCardAddress::~nsVCardAddress() {}
+
+nsresult nsVCardAddress::ImportAddresses(bool* pAbort, const char16_t* pName,
+ nsIFile* pSrc,
+ nsIAbDirectory* pDirectory,
+ nsString& errors,
+ uint32_t* pProgress) {
+ // Open the source file for reading, read each line and process it!
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), pSrc);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error opening address file for reading\n");
+ return rv;
+ }
+
+ // Open the source file for reading, read each line and process it!
+ // Here we use this to work out the size of the file, so we can update
+ // an integer as we go through the file which will update a progress
+ // bar if required by the caller.
+ uint64_t bytesLeft = 0;
+ rv = inputStream->Available(&bytesLeft);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error checking address file for size\n");
+ inputStream->Close();
+ return rv;
+ }
+ uint64_t totalBytes = bytesLeft;
+
+ // Try to detect the character set and decode. Only UTF-8 is valid from
+ // vCard 4.0, but we support older versions, so other charsets are possible.
+
+ nsAutoCString sourceCharset;
+ rv = MsgDetectCharsetFromFile(pSrc, sourceCharset);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIConverterInputStream> converterStream =
+ do_CreateInstance("@mozilla.org/intl/converter-input-stream;1");
+ NS_ENSURE_TRUE(converterStream, NS_ERROR_FAILURE);
+
+ rv = converterStream->Init(
+ inputStream, sourceCharset.get(), 8192,
+ nsIConverterInputStream::DEFAULT_REPLACEMENT_CHARACTER);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIUnicharLineInputStream> lineStream(
+ do_QueryInterface(converterStream, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIMsgVCardService> vCardService =
+ do_GetService("@mozilla.org/addressbook/msgvcardservice;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool more = true;
+ nsAutoString record;
+ while (!(*pAbort) && more && NS_SUCCEEDED(rv)) {
+ rv = ReadRecord(lineStream, record, &more);
+ if (NS_SUCCEEDED(rv) && !record.IsEmpty()) {
+ // Parse the vCard and build an nsIAbCard from it
+ nsCOMPtr<nsIAbCard> cardFromVCard;
+ rv = vCardService->VCardToAbCard(record, getter_AddRefs(cardFromVCard));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsIAbCard* outCard;
+ rv = pDirectory->AddCard(cardFromVCard, &outCard);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Error processing vCard record.\n");
+ }
+ }
+ if (NS_SUCCEEDED(rv) && pProgress) {
+ // This won't be totally accurate, but its the best we can do
+ // considering that converterStream won't give us how many bytes
+ // are actually left.
+ bytesLeft -= record.Length();
+ *pProgress = totalBytes - bytesLeft;
+ }
+ }
+ inputStream->Close();
+
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0(
+ "*** Error reading the address book - probably incorrect ending\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult nsVCardAddress::ReadRecord(nsIUnicharLineInputStream* aLineStream,
+ nsString& aRecord, bool* aMore) {
+ bool more = true;
+ nsresult rv;
+ nsAutoString line;
+
+ aRecord.Truncate();
+
+ // remove the empty lines.
+ do {
+ rv = aLineStream->ReadLine(line, aMore);
+ } while (line.IsEmpty() && *aMore);
+ if (!*aMore) return rv;
+
+ // read BEGIN:VCARD
+ if (!line.LowerCaseEqualsLiteral("begin:vcard")) {
+ IMPORT_LOG0(
+ "*** Expected case-insensitive BEGIN:VCARD at start of vCard\n");
+ rv = NS_ERROR_FAILURE;
+ *aMore = more;
+ return rv;
+ }
+ aRecord.Append(line);
+
+ // read until END:VCARD
+ do {
+ if (!more) {
+ IMPORT_LOG0(
+ "*** Expected case-insensitive END:VCARD at start of vCard\n");
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ rv = aLineStream->ReadLine(line, &more);
+ aRecord.AppendLiteral(MSG_LINEBREAK);
+ aRecord.Append(line);
+ } while (!line.LowerCaseEqualsLiteral("end:vcard"));
+
+ *aMore = more;
+ return rv;
+}
diff --git a/comm/mailnews/import/src/nsVCardAddress.h b/comm/mailnews/import/src/nsVCardAddress.h
new file mode 100644
index 0000000000..4cdb4de2b4
--- /dev/null
+++ b/comm/mailnews/import/src/nsVCardAddress.h
@@ -0,0 +1,28 @@
+/* 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 nsVCardAddress_h__
+#define nsVCardAddress_h__
+
+#include "ImportDebug.h"
+
+class nsIAbDirectory;
+class nsIFile;
+class nsIUnicharLineInputStream;
+
+class nsVCardAddress {
+ public:
+ nsVCardAddress();
+ virtual ~nsVCardAddress();
+
+ nsresult ImportAddresses(bool* pAbort, const char16_t* pName, nsIFile* pSrc,
+ nsIAbDirectory* pDirectory, nsString& errors,
+ uint32_t* pProgress);
+
+ private:
+ static nsresult ReadRecord(nsIUnicharLineInputStream* aLineStream,
+ nsString& aRecord, bool* aMore);
+};
+
+#endif /* nsVCardAddress_h__ */
diff --git a/comm/mailnews/import/src/nsVCardImport.cpp b/comm/mailnews/import/src/nsVCardImport.cpp
new file mode 100644
index 0000000000..98f7482ddf
--- /dev/null
+++ b/comm/mailnews/import/src/nsVCardImport.cpp
@@ -0,0 +1,352 @@
+/* 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/. */
+
+/*
+ VCard import addressbook interfaces
+*/
+#include "nscore.h"
+#include "nsIFile.h"
+#include "nsIImportABDescriptor.h"
+#include "nsIImportAddressBooks.h"
+#include "nsIImportFieldMap.h"
+#include "nsIImportGeneric.h"
+#include "nsCOMPtr.h"
+#include "nsIImportService.h"
+#include "nsIFile.h"
+#include "nsImportStringBundle.h"
+#include "nsMsgUtils.h"
+#include "nsComponentManagerUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsTextFormatter.h"
+#include "nsVCardAddress.h"
+#include "nsVCardImport.h"
+
+class ImportVCardAddressImpl : public nsIImportAddressBooks {
+ public:
+ explicit ImportVCardAddressImpl(nsIStringBundle* aStringBundle);
+
+ static nsresult Create(nsIImportAddressBooks** aImport,
+ nsIStringBundle* aStringBundle);
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIImportAddressBooks interface
+
+ // TODO: support multiple vCard files in future - shouldn't be too hard,
+ // since you just import each file in turn.
+ NS_IMETHOD GetSupportsMultiple(bool* _retval) override {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetAutoFind(char16_t** description, bool* _retval) override;
+
+ NS_IMETHOD GetNeedsFieldMap(nsIFile* location, bool* _retval) override {
+ *_retval = false;
+ return NS_OK;
+ }
+
+ NS_IMETHOD GetDefaultLocation(nsIFile** location, bool* found,
+ bool* userVerify) override;
+
+ NS_IMETHOD FindAddressBooks(
+ nsIFile* location,
+ nsTArray<RefPtr<nsIImportABDescriptor>>& books) override;
+
+ NS_IMETHOD InitFieldMap(nsIImportFieldMap* fieldMap) override {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_IMETHOD ImportAddressBook(nsIImportABDescriptor* source,
+ nsIAbDirectory* destination,
+ nsIImportFieldMap* fieldMap,
+ nsISupports* aSupportService,
+ char16_t** errorLog, char16_t** successLog,
+ bool* fatalError) override;
+
+ NS_IMETHOD GetImportProgress(uint32_t* _retval) override;
+
+ NS_IMETHOD GetSampleData(int32_t index, bool* pFound,
+ char16_t** pStr) override {
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_IMETHOD SetSampleLocation(nsIFile*) override { return NS_ERROR_FAILURE; }
+
+ private:
+ virtual ~ImportVCardAddressImpl();
+ static void ReportSuccess(nsString& name, nsString* pStream,
+ nsIStringBundle* pBundle);
+ static void SetLogs(nsString& success, nsString& error, char16_t** pError,
+ char16_t** pSuccess);
+ static void ReportError(const char* errorName, nsString& name,
+ nsString* pStream, nsIStringBundle* pBundle);
+
+ private:
+ nsVCardAddress m_vCard;
+ nsCOMPtr<nsIFile> m_fileLoc;
+ uint32_t m_bytesImported;
+ nsCOMPtr<nsIStringBundle> m_notProxyBundle;
+};
+
+nsVCardImport::nsVCardImport() {
+ nsImportStringBundle::GetStringBundle(VCARDIMPORT_MSGS_URL,
+ getter_AddRefs(m_stringBundle));
+
+ IMPORT_LOG0("nsVCardImport Module Created\n");
+}
+
+nsVCardImport::~nsVCardImport() {
+ IMPORT_LOG0("nsVCardImport Module Deleted\n");
+}
+
+NS_IMPL_ISUPPORTS(nsVCardImport, nsIImportModule)
+
+NS_IMETHODIMP nsVCardImport::GetName(char16_t** name) {
+ NS_ENSURE_ARG_POINTER(name);
+ *name =
+ nsImportStringBundle::GetStringByName("vCardImportName", m_stringBundle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVCardImport::GetDescription(char16_t** name) {
+ NS_ENSURE_ARG_POINTER(name);
+ *name = nsImportStringBundle::GetStringByName("vCardImportDescription",
+ m_stringBundle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVCardImport::GetSupports(char** supports) {
+ NS_ENSURE_ARG_POINTER(supports);
+ *supports = strdup(NS_IMPORT_ADDRESS_STR);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVCardImport::GetSupportsUpgrade(bool* pUpgrade) {
+ NS_ENSURE_ARG_POINTER(pUpgrade);
+ *pUpgrade = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsVCardImport::GetImportInterface(const char* pImportType,
+ nsISupports** ppInterface) {
+ NS_ENSURE_ARG_POINTER(pImportType);
+ NS_ENSURE_ARG_POINTER(ppInterface);
+ *ppInterface = nullptr;
+ if (!strcmp(pImportType, "addressbook")) {
+ nsresult rv;
+ // create the nsIImportMail interface and return it!
+ nsCOMPtr<nsIImportAddressBooks> pAddress;
+ nsCOMPtr<nsIImportGeneric> pGeneric;
+ rv = ImportVCardAddressImpl::Create(getter_AddRefs(pAddress),
+ m_stringBundle);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIImportService> impSvc(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_SUCCEEDED(rv)) {
+ rv = impSvc->CreateNewGenericAddressBooks(getter_AddRefs(pGeneric));
+ if (NS_SUCCEEDED(rv)) {
+ pGeneric->SetData("addressInterface", pAddress);
+ nsCOMPtr<nsISupports> pInterface(do_QueryInterface(pGeneric));
+ pInterface.forget(ppInterface);
+ }
+ }
+ }
+ return rv;
+ }
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+nsresult ImportVCardAddressImpl::Create(nsIImportAddressBooks** aImport,
+ nsIStringBundle* aStringBundle) {
+ NS_ENSURE_ARG_POINTER(aImport);
+ NS_ADDREF(*aImport = new ImportVCardAddressImpl(aStringBundle));
+ return NS_OK;
+}
+
+ImportVCardAddressImpl::ImportVCardAddressImpl(nsIStringBundle* aStringBundle)
+ : m_notProxyBundle(aStringBundle) {}
+
+ImportVCardAddressImpl::~ImportVCardAddressImpl() {}
+
+NS_IMPL_ISUPPORTS(ImportVCardAddressImpl, nsIImportAddressBooks)
+
+NS_IMETHODIMP ImportVCardAddressImpl::GetAutoFind(char16_t** addrDescription,
+ bool* _retval) {
+ NS_ENSURE_ARG_POINTER(addrDescription);
+ NS_ENSURE_ARG_POINTER(_retval);
+
+ nsString str;
+ *_retval = false;
+
+ if (!m_notProxyBundle) return NS_ERROR_FAILURE;
+
+ nsImportStringBundle::GetStringByName("vCardImportAddressName",
+ m_notProxyBundle, str);
+ *addrDescription = ToNewUnicode(str);
+ return NS_OK;
+}
+
+NS_IMETHODIMP ImportVCardAddressImpl::GetDefaultLocation(nsIFile** ppLoc,
+ bool* found,
+ bool* userVerify) {
+ NS_ENSURE_ARG_POINTER(found);
+ NS_ENSURE_ARG_POINTER(ppLoc);
+ NS_ENSURE_ARG_POINTER(userVerify);
+
+ *ppLoc = nullptr;
+ *found = false;
+ *userVerify = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP ImportVCardAddressImpl::FindAddressBooks(
+ nsIFile* pLoc, nsTArray<RefPtr<nsIImportABDescriptor>>& books) {
+ NS_ENSURE_ARG_POINTER(pLoc);
+
+ books.Clear();
+ bool exists = false;
+ nsresult rv = pLoc->Exists(&exists);
+ if (NS_FAILED(rv) || !exists) return NS_ERROR_FAILURE;
+
+ bool isFile = false;
+ rv = pLoc->IsFile(&isFile);
+ if (NS_FAILED(rv) || !isFile) return NS_ERROR_FAILURE;
+
+ m_fileLoc = pLoc;
+
+ /* Build an address book descriptor based on the file passed in! */
+ nsString name;
+ m_fileLoc->GetLeafName(name);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed getting leaf name of file\n");
+ return rv;
+ }
+
+ int32_t idx = name.RFindChar('.');
+ if ((idx != -1) && (idx > 0) && ((name.Length() - idx - 1) < 5)) {
+ name.SetLength(idx);
+ }
+
+ nsCOMPtr<nsIImportABDescriptor> desc;
+ nsCOMPtr<nsIImportService> impSvc(
+ do_GetService(NS_IMPORTSERVICE_CONTRACTID, &rv));
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to obtain the import service\n");
+ return rv;
+ }
+
+ rv = impSvc->CreateNewABDescriptor(getter_AddRefs(desc));
+ if (NS_SUCCEEDED(rv)) {
+ int64_t sz = 0;
+ pLoc->GetFileSize(&sz);
+ desc->SetPreferredName(name);
+ desc->SetSize((uint32_t)sz);
+ desc->SetAbFile(m_fileLoc);
+ books.AppendElement(desc);
+ }
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0(
+ "*** Error creating address book descriptor for vCard import\n");
+ return rv;
+ }
+
+ return NS_OK;
+}
+
+void ImportVCardAddressImpl::ReportSuccess(nsString& name, nsString* pStream,
+ nsIStringBundle* pBundle) {
+ if (!pStream) return;
+
+ // load the success string
+ char16_t* pFmt = nsImportStringBundle::GetStringByName(
+ "vCardImportAddressSuccess", pBundle);
+
+ nsString pText;
+ nsTextFormatter::ssprintf(pText, pFmt, name.get());
+ pStream->Append(pText);
+ free(pFmt);
+ pStream->Append(char16_t('\n'));
+}
+
+void ImportVCardAddressImpl::ReportError(const char* errorName, nsString& name,
+ nsString* pStream,
+ nsIStringBundle* pBundle) {
+ if (!pStream) return;
+
+ // load the error string
+ char16_t* pFmt = nsImportStringBundle::GetStringByName(errorName, pBundle);
+ nsString pText;
+ nsTextFormatter::ssprintf(pText, pFmt, name.get());
+ pStream->Append(pText);
+ free(pFmt);
+ pStream->Append(char16_t('\n'));
+}
+
+void ImportVCardAddressImpl::SetLogs(nsString& success, nsString& error,
+ char16_t** pError, char16_t** pSuccess) {
+ if (pError) *pError = ToNewUnicode(error);
+ if (pSuccess) *pSuccess = ToNewUnicode(success);
+}
+
+NS_IMETHODIMP ImportVCardAddressImpl::ImportAddressBook(
+ nsIImportABDescriptor* pSource, nsIAbDirectory* pDestination,
+ nsIImportFieldMap* fieldMap, nsISupports* aSupportService,
+ char16_t** pErrorLog, char16_t** pSuccessLog, bool* fatalError) {
+ NS_ENSURE_ARG_POINTER(pSource);
+ NS_ENSURE_ARG_POINTER(pDestination);
+ NS_ENSURE_ARG_POINTER(fatalError);
+
+ if (!m_notProxyBundle) return NS_ERROR_FAILURE;
+
+ m_bytesImported = 0;
+ nsString success, error;
+ bool addrAbort = false;
+ nsString name;
+ pSource->GetPreferredName(name);
+
+ uint32_t addressSize = 0;
+ pSource->GetSize(&addressSize);
+ if (addressSize == 0) {
+ IMPORT_LOG0("Address book size is 0, skipping import.\n");
+ ReportSuccess(name, &success, m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> inFile;
+ if (NS_FAILED(pSource->GetAbFile(getter_AddRefs(inFile)))) {
+ ReportError("vCardImportAddressBadSourceFile", name, &error,
+ m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!aSupportService) {
+ IMPORT_LOG0("Missing support service to import call\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsresult rv = m_vCard.ImportAddresses(&addrAbort, name.get(), inFile,
+ pDestination, error, &m_bytesImported);
+
+ if (NS_SUCCEEDED(rv) && error.IsEmpty()) {
+ ReportSuccess(name, &success, m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ } else {
+ ReportError("vCardImportAddressConvertError", name, &error,
+ m_notProxyBundle);
+ SetLogs(success, error, pErrorLog, pSuccessLog);
+ }
+
+ IMPORT_LOG0("*** VCard address import done\n");
+ return rv;
+}
+
+NS_IMETHODIMP ImportVCardAddressImpl::GetImportProgress(uint32_t* _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = m_bytesImported;
+ return NS_OK;
+}
diff --git a/comm/mailnews/import/src/nsVCardImport.h b/comm/mailnews/import/src/nsVCardImport.h
new file mode 100644
index 0000000000..f35e5eb3d3
--- /dev/null
+++ b/comm/mailnews/import/src/nsVCardImport.h
@@ -0,0 +1,39 @@
+/* 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 nsVCardImport_h___
+#define nsVCardImport_h___
+
+#include "nsIImportModule.h"
+#include "nsIStringBundle.h"
+#include "nsCOMPtr.h"
+
+#define NS_VCARDIMPORT_CID \
+ { /* 0EB034A3-964A-4E2F-92EBCC55D9AE9DD2 */ \
+ 0x0eb034a3, 0x964a, 0x4e2f, { \
+ 0x92, 0xeb, 0xcc, 0x55, 0xd9, 0xae, 0x9d, 0xd2 \
+ } \
+ }
+
+#define VCARDIMPORT_MSGS_URL \
+ "chrome://messenger/locale/vCardImportMsgs.properties"
+
+class nsVCardImport : public nsIImportModule {
+ public:
+ nsVCardImport();
+
+ NS_DECL_ISUPPORTS
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // we support the nsIImportModule interface
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ NS_DECL_NSIIMPORTMODULE
+
+ protected:
+ virtual ~nsVCardImport();
+ nsCOMPtr<nsIStringBundle> m_stringBundle;
+};
+
+#endif /* nsVCardImport_h___ */
diff --git a/comm/mailnews/import/src/nsWMImport.cpp b/comm/mailnews/import/src/nsWMImport.cpp
new file mode 100644
index 0000000000..a3b5dd45b2
--- /dev/null
+++ b/comm/mailnews/import/src/nsWMImport.cpp
@@ -0,0 +1,199 @@
+/* -*- 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/. */
+
+/*
+ * Windows Live Mail (Win32) import mail and addressbook interfaces
+ */
+
+#include "nscore.h"
+#include "nsString.h"
+#include "nsMsgUtils.h"
+#include "nsWMImport.h"
+#include "nsIImportMail.h"
+#include "nsIImportMailboxDescriptor.h"
+#include "nsXPCOM.h"
+#include "nsWMSettings.h"
+#include "nsTextFormatter.h"
+#include "nsWMStringBundle.h"
+#include "nsUnicharUtils.h"
+
+#include "ImportDebug.h"
+
+class ImportWMMailImpl : public nsIImportMail {
+ public:
+ ImportWMMailImpl();
+
+ static nsresult Create(nsIImportMail** aImport);
+
+ // nsISupports interface
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ // nsIImportmail interface
+
+ /* void GetDefaultLocation (out nsIFile location, out boolean found, out
+ * boolean userVerify); */
+ NS_IMETHOD GetDefaultLocation(nsIFile** location, bool* found,
+ bool* userVerify);
+
+ /* nsIArray FindMailboxes (in nsIFile location); */
+ NS_IMETHOD FindMailboxes(nsIFile* location,
+ nsTArray<RefPtr<nsIImportMailboxDescriptor>>& boxes);
+
+ NS_IMETHOD ImportMailbox(nsIImportMailboxDescriptor* source,
+ nsIMsgFolder* dstFolder, char16_t** pErrorLog,
+ char16_t** pSuccessLog, bool* fatalError);
+
+ /* unsigned long GetImportProgress (); */
+ NS_IMETHOD GetImportProgress(uint32_t* _retval);
+
+ NS_IMETHOD TranslateFolderName(const nsAString& aFolderName,
+ nsAString& _retval);
+
+ public:
+ static void ReportSuccess(nsString& name, int32_t count, nsString* pStream);
+ static void ReportError(int32_t errorNum, nsString& name, nsString* pStream);
+ static void AddLinebreak(nsString* pStream);
+ static void SetLogs(nsString& success, nsString& error, char16_t** pError,
+ char16_t** pSuccess);
+
+ private:
+ virtual ~ImportWMMailImpl();
+};
+
+nsWMImport::nsWMImport() {
+ IMPORT_LOG0("nsWMImport Module Created\n");
+ nsWMStringBundle::GetStringBundle();
+}
+
+nsWMImport::~nsWMImport() { IMPORT_LOG0("nsWMImport Module Deleted\n"); }
+
+NS_IMPL_ISUPPORTS(nsWMImport, nsIImportModule)
+
+NS_IMETHODIMP nsWMImport::GetName(char16_t** name) {
+ NS_ENSURE_ARG_POINTER(name);
+ // nsString title = "Windows Live Mail";
+ // *name = ToNewUnicode(title);
+ *name = nsWMStringBundle::GetStringByID(WMIMPORT_NAME);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWMImport::GetDescription(char16_t** name) {
+ NS_ENSURE_ARG_POINTER(name);
+
+ // nsString desc = "Windows Live Mail mail and address books";
+ // *name = ToNewUnicode(desc);
+ *name = nsWMStringBundle::GetStringByID(WMIMPORT_DESCRIPTION);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWMImport::GetSupports(char** supports) {
+ NS_ASSERTION(supports != nullptr, "null ptr");
+ if (!supports) return NS_ERROR_NULL_POINTER;
+
+ *supports = strdup(kWMSupportsString);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWMImport::GetSupportsUpgrade(bool* pUpgrade) {
+ NS_ASSERTION(pUpgrade != nullptr, "null ptr");
+ if (!pUpgrade) return NS_ERROR_NULL_POINTER;
+
+ *pUpgrade = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWMImport::GetImportInterface(const char* pImportType,
+ nsISupports** ppInterface) {
+ NS_ENSURE_ARG_POINTER(pImportType);
+ NS_ENSURE_ARG_POINTER(ppInterface);
+
+ *ppInterface = nullptr;
+ nsresult rv;
+
+ if (!strcmp(pImportType, "settings")) {
+ nsCOMPtr<nsIImportSettings> pSettings;
+ rv = nsWMSettings::Create(getter_AddRefs(pSettings));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsISupports> pInterface(do_QueryInterface(pSettings));
+ pInterface.forget(ppInterface);
+ }
+ return rv;
+ }
+
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+/////////////////////////////////////////////////////////////////////////////////
+nsresult ImportWMMailImpl::Create(nsIImportMail** aImport) {
+ NS_ENSURE_ARG_POINTER(aImport);
+ NS_ADDREF(*aImport = new ImportWMMailImpl());
+ return NS_OK;
+}
+
+ImportWMMailImpl::ImportWMMailImpl() {}
+
+ImportWMMailImpl::~ImportWMMailImpl() {}
+
+NS_IMPL_ISUPPORTS(ImportWMMailImpl, nsIImportMail)
+
+NS_IMETHODIMP ImportWMMailImpl::TranslateFolderName(
+ const nsAString& aFolderName, nsAString& _retval) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ImportWMMailImpl::GetDefaultLocation(nsIFile** ppLoc, bool* found,
+ bool* userVerify) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ImportWMMailImpl::FindMailboxes(
+ nsIFile* pLoc, nsTArray<RefPtr<nsIImportMailboxDescriptor>>& boxes) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void ImportWMMailImpl::AddLinebreak(nsString* pStream) {
+ if (pStream) pStream->Append(char16_t('\n'));
+}
+
+void ImportWMMailImpl::ReportSuccess(nsString& name, int32_t count,
+ nsString* pStream) {
+ if (!pStream) return;
+ // load the success string
+ char16_t* pFmt = nsWMStringBundle::GetStringByID(WMIMPORT_MAILBOX_SUCCESS);
+ nsString pText;
+ nsTextFormatter::ssprintf(pText, pFmt, name.get(), count);
+ pStream->Append(pText);
+ nsWMStringBundle::FreeString(pFmt);
+ AddLinebreak(pStream);
+}
+
+void ImportWMMailImpl::ReportError(int32_t errorNum, nsString& name,
+ nsString* pStream) {
+ if (!pStream) return;
+ // load the error string
+ char16_t* pFmt = nsWMStringBundle::GetStringByID(errorNum);
+ nsString pText;
+ nsTextFormatter::ssprintf(pText, pFmt, name.get());
+ pStream->Append(pText);
+ nsWMStringBundle::FreeString(pFmt);
+ AddLinebreak(pStream);
+}
+
+void ImportWMMailImpl::SetLogs(nsString& success, nsString& error,
+ char16_t** pError, char16_t** pSuccess) {
+ if (pError) *pError = ToNewUnicode(error);
+ if (pSuccess) *pSuccess = ToNewUnicode(success);
+}
+
+NS_IMETHODIMP ImportWMMailImpl::ImportMailbox(
+ nsIImportMailboxDescriptor* pSource, nsIMsgFolder* pDstFolder,
+ char16_t** pErrorLog, char16_t** pSuccessLog, bool* fatalError) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP ImportWMMailImpl::GetImportProgress(uint32_t* pDoneSoFar) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/comm/mailnews/import/src/nsWMImport.h b/comm/mailnews/import/src/nsWMImport.h
new file mode 100644
index 0000000000..1f14b1331f
--- /dev/null
+++ b/comm/mailnews/import/src/nsWMImport.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 4; 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 nsWMImport_h___
+#define nsWMImport_h___
+
+#include "nsIImportModule.h"
+#include "nsCOMPtr.h"
+
+#define NS_WMIMPORT_CID \
+ { /* 42bc82bc-8e9f-4597-8b6e-e529daaf3af1 */ \
+ 0x42bc82bc, 0x8e9f, 0x4597, { \
+ 0x8b, 0x6e, 0xe5, 0x29, 0xda, 0xaf, 0x3a, 0xf1 \
+ } \
+ }
+
+// currently only support setting import
+#define kWMSupportsString NS_IMPORT_SETTINGS_STR
+
+class nsWMImport : public nsIImportModule {
+ public:
+ nsWMImport();
+
+ NS_DECL_ISUPPORTS
+
+ ////////////////////////////////////////////////////////////////////////////////////////
+ // we support the nsIImportModule interface
+ ////////////////////////////////////////////////////////////////////////////////////////
+
+ NS_DECL_NSIIMPORTMODULE
+
+ protected:
+ virtual ~nsWMImport();
+};
+
+#endif /* nsWMImport_h___ */
diff --git a/comm/mailnews/import/src/nsWMSettings.cpp b/comm/mailnews/import/src/nsWMSettings.cpp
new file mode 100644
index 0000000000..ee741fa053
--- /dev/null
+++ b/comm/mailnews/import/src/nsWMSettings.cpp
@@ -0,0 +1,679 @@
+/* -*- 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/. */
+
+/*
+ Windows Live Mail (Win32) settings
+*/
+
+#include "nsCOMPtr.h"
+#include "nscore.h"
+#include "nsString.h"
+#include "nsMsgUtils.h"
+#include "nsWMImport.h"
+#include "nsIMsgAccountManager.h"
+#include "nsIMsgAccount.h"
+#include "nsIImportSettings.h"
+#include "nsWMSettings.h"
+#include "nsMsgI18N.h"
+#include "nsISmtpService.h"
+#include "nsISmtpServer.h"
+#include "nsWMStringBundle.h"
+#include "ImportDebug.h"
+#include "nsIPop3IncomingServer.h"
+#include "nsIImapIncomingServer.h"
+#include "nsINntpIncomingServer.h"
+#include "stdlib.h"
+#include "mozilla/dom/Document.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsTArray.h"
+#include <windows.h>
+#include "nsIWindowsRegKey.h"
+#include "nsCOMArray.h"
+#include "nsWMUtils.h"
+
+class WMSettings {
+ public:
+ static bool DoImport(nsIMsgAccount** ppAccount);
+ static bool DoIMAPServer(nsIMsgAccountManager* pMgr,
+ mozilla::dom::Document* xmlDoc,
+ const nsString& serverName,
+ nsIMsgAccount** ppAccount);
+ static bool DoPOP3Server(nsIMsgAccountManager* pMgr,
+ mozilla::dom::Document* xmlDoc,
+ const nsString& serverName,
+ nsIMsgAccount** ppAccount);
+ static bool DoNNTPServer(nsIMsgAccountManager* pMgr,
+ mozilla::dom::Document* xmlDoc,
+ const nsString& serverName,
+ nsIMsgAccount** ppAccount);
+ static void SetIdentities(nsIMsgAccountManager* pMgr, nsIMsgAccount* pAcc,
+ mozilla::dom::Document* xmlDoc,
+ nsAutoString& userName, int32_t authMethodIncoming,
+ bool isNNTP);
+ static void SetSmtpServer(mozilla::dom::Document* xmlDoc, nsIMsgIdentity* id,
+ nsAutoString& inUserName,
+ int32_t authMethodIncoming);
+};
+
+static int32_t checkNewMailTime; // WM global setting, let's default to 30
+static bool checkNewMail; // WM global setting, let's default to false
+ // This won't cause unwanted autodownloads-
+ // user can set prefs after import
+
+////////////////////////////////////////////////////////////////////////
+nsresult nsWMSettings::Create(nsIImportSettings** aImport) {
+ NS_ENSURE_ARG_POINTER(aImport);
+ NS_ADDREF(*aImport = new nsWMSettings());
+ return NS_OK;
+}
+
+nsWMSettings::nsWMSettings() {}
+
+nsWMSettings::~nsWMSettings() {}
+
+NS_IMPL_ISUPPORTS(nsWMSettings, nsIImportSettings)
+
+NS_IMETHODIMP nsWMSettings::AutoLocate(char16_t** description,
+ nsIFile** location, bool* _retval) {
+ NS_ASSERTION(description != nullptr, "null ptr");
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+ if (!description || !_retval) return NS_ERROR_NULL_POINTER;
+
+ *description = nsWMStringBundle::GetStringByID(WMIMPORT_NAME);
+ *_retval = false;
+
+ if (location) *location = nullptr;
+ nsCOMPtr<nsIWindowsRegKey> key;
+ if (NS_SUCCEEDED(nsWMUtils::FindWMKey(getter_AddRefs(key)))) *_retval = true;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsWMSettings::SetLocation(nsIFile* location) { return NS_OK; }
+
+NS_IMETHODIMP nsWMSettings::Import(nsIMsgAccount** localMailAccount,
+ bool* _retval) {
+ NS_ASSERTION(_retval != nullptr, "null ptr");
+
+ if (WMSettings::DoImport(localMailAccount)) {
+ *_retval = true;
+ IMPORT_LOG0("Settings import appears successful\n");
+ } else {
+ *_retval = false;
+ IMPORT_LOG0("Settings import returned FALSE\n");
+ }
+
+ return NS_OK;
+}
+
+bool WMSettings::DoImport(nsIMsgAccount** ppAccount) {
+ // do the windows registry stuff first
+ nsCOMPtr<nsIWindowsRegKey> key;
+ if (NS_FAILED(nsWMUtils::FindWMKey(getter_AddRefs(key)))) {
+ IMPORT_LOG0("*** Error finding Windows Live Mail registry account keys\n");
+ return false;
+ }
+ // 'poll for messages' setting in WM is a global setting-Like OE
+ // for all accounts dword ==0xffffffff for don't poll else 1/60000 = minutes
+ checkNewMailTime = 30;
+ checkNewMail = false;
+
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> subKey;
+ if (NS_SUCCEEDED(key->OpenChild(u"mail"_ns,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE,
+ getter_AddRefs(subKey)))) {
+ uint32_t dwordResult = 0xffffffff;
+ rv = subKey->ReadIntValue(u"Poll For Mail"_ns,
+ &dwordResult); // reg_dword
+ subKey->Close();
+ if (NS_SUCCEEDED(rv) && dwordResult != 0xffffffff) {
+ checkNewMail = true;
+ checkNewMailTime = dwordResult / 60000;
+ }
+ }
+ // these are in main windowsmail key and if they don't exist-not to worry
+ // (less than 64 chars) e.g.
+ // account{4A18B81E-83CA-472A-8D7F-5301C0B97B8D}.oeaccount
+ nsAutoString defMailAcct, defNewsAcct;
+ key->ReadStringValue(u"Default Mail Account"_ns,
+ defMailAcct); // ref_sz
+ key->ReadStringValue(u"Default News Account"_ns,
+ defNewsAcct); // ref_sz
+
+ nsCOMPtr<nsIMsgAccountManager> accMgr =
+ do_GetService("@mozilla.org/messenger/account-manager;1", &rv);
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to create an account manager!\n");
+ return false;
+ }
+
+ nsCOMArray<nsIFile> fileArray;
+ if (NS_FAILED(nsWMUtils::GetOEAccountFiles(fileArray))) {
+ IMPORT_LOG0("*** Failed to get .oeaccount file!\n");
+ return false;
+ }
+
+ // Loop through *.oeaccounts files looking for POP3 & IMAP & NNTP accounts
+ // Ignore LDAP for now!
+ int accounts = 0;
+ nsCOMPtr<mozilla::dom::Document> xmlDoc;
+
+ for (int32_t i = fileArray.Count() - 1; i >= 0; i--) {
+ nsWMUtils::MakeXMLdoc(getter_AddRefs(xmlDoc), fileArray[i]);
+
+ nsAutoCString name;
+ fileArray[i]->GetNativeLeafName(name);
+ nsAutoString value;
+ nsCOMPtr<nsIMsgAccount> anAccount;
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "IMAP_Server", value)))
+ if (DoIMAPServer(accMgr, xmlDoc, value, getter_AddRefs(anAccount)))
+ accounts++;
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "NNTP_Server", value)))
+ if (DoNNTPServer(accMgr, xmlDoc, value, getter_AddRefs(anAccount)))
+ accounts++;
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "POP3_Server", value)))
+ if (DoPOP3Server(accMgr, xmlDoc, value, getter_AddRefs(anAccount)))
+ accounts++;
+
+ if (anAccount) {
+ nsString name;
+ // Is this the default account?
+ fileArray[i]->GetLeafName(name);
+ if (defMailAcct.Equals(name)) accMgr->SetDefaultAccount(anAccount);
+ }
+ }
+
+ // Now save the new acct info to pref file.
+ rv = accMgr->SaveAccountInfo();
+ NS_ASSERTION(NS_SUCCEEDED(rv), "Can't save account info to pref file");
+
+ return accounts != 0;
+}
+
+bool WMSettings::DoIMAPServer(nsIMsgAccountManager* pMgr,
+ mozilla::dom::Document* xmlDoc,
+ const nsString& serverName,
+ nsIMsgAccount** ppAccount) {
+ int32_t authMethod; // Secure Password Authentication (SPA)
+ nsresult errorCode;
+ if (ppAccount) *ppAccount = nullptr;
+
+ nsAutoString userName, value;
+ if (NS_FAILED(nsWMUtils::GetValueForTag(xmlDoc, "IMAP_User_Name", userName)))
+ return false;
+ bool result = false;
+ // I now have a user name/server name pair, find out if it already exists?
+ nsCOMPtr<nsIMsgIncomingServer> in;
+ nsresult rv = pMgr->FindServer(NS_ConvertUTF16toUTF8(userName),
+ NS_ConvertUTF16toUTF8(serverName), "imap"_ns,
+ 0, getter_AddRefs(in));
+ if (NS_FAILED(rv) || (in == nullptr)) {
+ // Create the incoming server and an account for it?
+ rv = pMgr->CreateIncomingServer(NS_ConvertUTF16toUTF8(userName),
+ NS_ConvertUTF16toUTF8(serverName),
+ "imap"_ns, getter_AddRefs(in));
+ if (NS_SUCCEEDED(rv) && in) {
+ nsCOMPtr<nsIImapIncomingServer> imapServer = do_QueryInterface(in);
+ if (!imapServer) {
+ IMPORT_LOG1("*** Failed to create nsIImapIncomingServer for %S!\n",
+ static_cast<const wchar_t*>(serverName.get()));
+ return false;
+ }
+ if (NS_SUCCEEDED(
+ nsWMUtils::GetValueForTag(xmlDoc, "IMAP_Root_Folder", value))) {
+ imapServer->SetServerDirectory(NS_ConvertUTF16toUTF8(value));
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(
+ xmlDoc, "IMAP_Secure_Connection", value))) {
+ if (value.ToInteger(&errorCode, 16))
+ in->SetSocketType(nsMsgSocketType::SSL);
+ }
+ if (NS_SUCCEEDED(
+ nsWMUtils::GetValueForTag(xmlDoc, "IMAP_Use_Sicily", value))) {
+ bool secAuth = (bool)value.ToInteger(&errorCode, 16);
+ authMethod = secAuth ? nsMsgAuthMethod::secure
+ : nsMsgAuthMethod::passwordCleartext;
+ in->SetAuthMethod(authMethod);
+ }
+
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "IMAP_Port", value))) {
+ in->SetPort(value.ToInteger(&errorCode, 16));
+ }
+ if (NS_SUCCEEDED(
+ nsWMUtils::GetValueForTag(xmlDoc, "Account_Name", value))) {
+ rv = in->SetPrettyName(value);
+ }
+ in->SetDoBiff(checkNewMail);
+ in->SetBiffMinutes(checkNewMailTime);
+
+ IMPORT_LOG2("Created IMAP server named: %S, userName: %S\n",
+ static_cast<const wchar_t*>(serverName.get()),
+ static_cast<const wchar_t*>(userName.get()));
+
+ // We have a server, create an account.
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = pMgr->CreateAccount(getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account) {
+ rv = account->SetIncomingServer(in);
+
+ IMPORT_LOG0(
+ "Created an account and set the IMAP server "
+ "as the incoming server\n");
+
+ // Fiddle with the identities
+ SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false);
+ result = true;
+ if (ppAccount) account.forget(ppAccount);
+ }
+ }
+ } else if (NS_SUCCEEDED(rv) && in) {
+ // for an existing server we create another identity,
+ // TB lists under 'manage identities'
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = pMgr->FindAccountForServer(in, getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account) {
+ IMPORT_LOG0(
+ "Created an identity and added to existing "
+ "IMAP incoming server\n");
+ // Fiddle with the identities
+ in->GetAuthMethod(&authMethod);
+ SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false);
+ result = true;
+ if (ppAccount) account.forget(ppAccount);
+ }
+ } else
+ result = true;
+ return result;
+}
+
+bool WMSettings::DoPOP3Server(nsIMsgAccountManager* pMgr,
+ mozilla::dom::Document* xmlDoc,
+ const nsString& serverName,
+ nsIMsgAccount** ppAccount) {
+ int32_t authMethod; // Secure Password Authentication (SPA)
+ nsresult errorCode;
+ if (ppAccount) *ppAccount = nullptr;
+
+ nsAutoString userName, value;
+ if (NS_FAILED(nsWMUtils::GetValueForTag(xmlDoc, "POP3_User_Name", userName)))
+ return false;
+ bool result = false;
+ // I now have a user name/server name pair, find out if it already exists?
+ nsCOMPtr<nsIMsgIncomingServer> in;
+ nsresult rv = pMgr->FindServer(NS_ConvertUTF16toUTF8(userName),
+ NS_ConvertUTF16toUTF8(serverName), "pop3"_ns,
+ 0, getter_AddRefs(in));
+ if (NS_FAILED(rv) || (in == nullptr)) {
+ // Create the incoming server and an account for it?
+ rv = pMgr->CreateIncomingServer(NS_ConvertUTF16toUTF8(userName),
+ NS_ConvertUTF16toUTF8(serverName),
+ "pop3"_ns, getter_AddRefs(in));
+ if (NS_SUCCEEDED(rv) && in) {
+ nsCOMPtr<nsIPop3IncomingServer> pop3Server = do_QueryInterface(in);
+ if (!pop3Server) {
+ IMPORT_LOG1("*** Failed to create nsIPop3IncomingServer for %S!\n",
+ static_cast<const wchar_t*>(serverName.get()));
+ return false;
+ }
+
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(
+ xmlDoc, "POP3_Secure_Connection", value)) &&
+ value.ToInteger(&errorCode, 16)) {
+ in->SetSocketType(nsMsgSocketType::SSL);
+ }
+ if (NS_SUCCEEDED(
+ nsWMUtils::GetValueForTag(xmlDoc, "POP3_Use_Sicily", value))) {
+ bool secAuth = (bool)value.ToInteger(&errorCode, 16);
+ authMethod = secAuth ? nsMsgAuthMethod::secure
+ : nsMsgAuthMethod::passwordCleartext;
+ in->SetAuthMethod(authMethod);
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "POP3_Port", value))) {
+ in->SetPort(value.ToInteger(&errorCode, 16));
+ }
+ if (NS_SUCCEEDED(
+ nsWMUtils::GetValueForTag(xmlDoc, "POP3_Skip_Account", value))) {
+ if (!value.IsEmpty())
+ // OE:0=='Include this account when receiving mail or synchronizing'==
+ // TB:1==ActMgr:Server:advanced:Include this server when getting new
+ // mail
+ pop3Server->SetDeferGetNewMail(value.ToInteger(&errorCode, 16) == 0);
+ else
+ pop3Server->SetDeferGetNewMail(false);
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "Leave_Mail_On_Server",
+ value))) {
+ pop3Server->SetLeaveMessagesOnServer(
+ (bool)value.ToInteger(&errorCode, 16));
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "Remove_When_Deleted",
+ value))) {
+ pop3Server->SetDeleteMailLeftOnServer(
+ (bool)value.ToInteger(&errorCode, 16));
+ }
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "Remove_When_Expired",
+ value))) {
+ pop3Server->SetDeleteByAgeFromServer(
+ (bool)value.ToInteger(&errorCode, 16));
+ }
+ if (NS_SUCCEEDED(
+ nsWMUtils::GetValueForTag(xmlDoc, "Expire_Days", value))) {
+ pop3Server->SetNumDaysToLeaveOnServer(value.ToInteger(&errorCode, 16));
+ }
+ if (NS_SUCCEEDED(
+ nsWMUtils::GetValueForTag(xmlDoc, "Account_Name", value))) {
+ rv = in->SetPrettyName(value);
+ }
+
+ in->SetDoBiff(checkNewMail);
+ in->SetBiffMinutes(checkNewMailTime);
+
+ // set local folders as the Inbox to use for this POP3 server
+ nsCOMPtr<nsIMsgIncomingServer> localFoldersServer;
+ pMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer));
+ if (!localFoldersServer) {
+ // XXX: We may need to move this local folder creation
+ // code to the generic nsImportSettings code
+ // if the other import modules end up needing to do this too.
+ // if Local Folders does not exist already, create it
+ rv = pMgr->CreateLocalMailAccount();
+ if (NS_FAILED(rv)) {
+ IMPORT_LOG0("*** Failed to create Local Folders!\n");
+ return false;
+ }
+ pMgr->GetLocalFoldersServer(getter_AddRefs(localFoldersServer));
+ }
+
+ // now get the account for this server
+ nsCOMPtr<nsIMsgAccount> localFoldersAccount;
+ pMgr->FindAccountForServer(localFoldersServer,
+ getter_AddRefs(localFoldersAccount));
+ if (localFoldersAccount) {
+ nsCString localFoldersAcctKey;
+ localFoldersAccount->GetKey(localFoldersAcctKey);
+ pop3Server->SetDeferredToAccount(localFoldersAcctKey);
+ }
+
+ IMPORT_LOG2("Created POP3 server named: %S, userName: %S\n",
+ static_cast<const wchar_t*>(serverName.get()),
+ static_cast<const wchar_t*>(userName.get()));
+
+ // We have a server, create an account.
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = pMgr->CreateAccount(getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account) {
+ rv = account->SetIncomingServer(in);
+ IMPORT_LOG0(
+ "Created a new account and set the incoming "
+ "server to the POP3 server.\n");
+
+ // Fiddle with the identities
+ SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false);
+ result = true;
+ if (ppAccount) account.forget(ppAccount);
+ }
+ }
+ } else if (NS_SUCCEEDED(rv) && in) {
+ IMPORT_LOG2("Existing POP3 server named: %S, userName: %S\n",
+ static_cast<const wchar_t*>(serverName.get()),
+ static_cast<const wchar_t*>(userName.get()));
+ // for an existing server we create another identity,
+ // TB listed under 'manage identities'
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = pMgr->FindAccountForServer(in, getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account) {
+ IMPORT_LOG0(
+ "Created identity and added to existing POP3 incoming server.\n");
+ // Fiddle with the identities
+ in->GetAuthMethod(&authMethod);
+ SetIdentities(pMgr, account, xmlDoc, userName, authMethod, false);
+ result = true;
+ if (ppAccount) account.forget(ppAccount);
+ }
+ } else
+ result = true;
+ return result;
+}
+
+bool WMSettings::DoNNTPServer(nsIMsgAccountManager* pMgr,
+ mozilla::dom::Document* xmlDoc,
+ const nsString& serverName,
+ nsIMsgAccount** ppAccount) {
+ int32_t authMethod;
+ nsresult errorCode;
+ if (ppAccount) *ppAccount = nullptr;
+
+ nsAutoString userName, value;
+ // this only exists if NNTP server requires it or not, anonymous login
+ nsWMUtils::GetValueForTag(xmlDoc, "NNTP_User_Name", userName);
+ bool result = false;
+
+ // I now have a user name/server name pair, find out if it already exists?
+ // NNTP can have empty user name. This is wild card in findserver
+ nsCOMPtr<nsIMsgIncomingServer> in;
+ nsresult rv =
+ pMgr->FindServer(EmptyCString(), NS_ConvertUTF16toUTF8(serverName),
+ "nntp"_ns, 0, getter_AddRefs(in));
+ if (NS_FAILED(rv) || (in == nullptr)) {
+ // Create the incoming server and an account for it?
+ rv = pMgr->CreateIncomingServer(EmptyCString(),
+ NS_ConvertUTF16toUTF8(serverName),
+ "nntp"_ns, getter_AddRefs(in));
+ if (NS_SUCCEEDED(rv) && in) {
+ nsCOMPtr<nsINntpIncomingServer> nntpServer = do_QueryInterface(in);
+ if (!nntpServer) {
+ IMPORT_LOG1("*** Failed to create nsINnntpIncomingServer for %S!\n",
+ static_cast<const wchar_t*>(serverName.get()));
+ return false;
+ }
+ if (!userName.IsEmpty()) { // if username req'd then auth req'd
+ nntpServer->SetPushAuth(true);
+ in->SetUsername(NS_ConvertUTF16toUTF8(userName));
+ }
+
+ nsAutoString value;
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(xmlDoc, "NNTP_Port", value))) {
+ in->SetPort(value.ToInteger(&errorCode, 16));
+ }
+
+ if (NS_SUCCEEDED(
+ nsWMUtils::GetValueForTag(xmlDoc, "Account_Name", value))) {
+ in->SetPrettyName(value);
+ }
+
+ if (NS_SUCCEEDED(
+ nsWMUtils::GetValueForTag(xmlDoc, "NNTP_Use_Sicily", value))) {
+ bool secAuth = (bool)value.ToInteger(&errorCode, 16);
+ authMethod = secAuth ? nsMsgAuthMethod::secure
+ : nsMsgAuthMethod::passwordCleartext;
+ in->SetAuthMethod(authMethod);
+ }
+
+ IMPORT_LOG2("Created NNTP server named: %S, userName: %S\n",
+ static_cast<const wchar_t*>(serverName.get()),
+ static_cast<const wchar_t*>(userName.get()));
+
+ // We have a server, create an account.
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = pMgr->CreateAccount(getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account) {
+ rv = account->SetIncomingServer(in);
+
+ IMPORT_LOG0(
+ "Created an account and set the NNTP server "
+ "as the incoming server\n");
+
+ // Fiddle with the identities
+ SetIdentities(pMgr, account, xmlDoc, userName, authMethod, true);
+ result = true;
+ if (ppAccount) account.forget(ppAccount);
+ }
+ }
+ } else if (NS_SUCCEEDED(rv) && in) {
+ // for the existing server...
+ nsCOMPtr<nsIMsgAccount> account;
+ rv = pMgr->FindAccountForServer(in, getter_AddRefs(account));
+ if (NS_SUCCEEDED(rv) && account) {
+ IMPORT_LOG0(
+ "Using existing account and set the "
+ "NNTP server as the incoming server\n");
+ // Fiddle with the identities
+ in->GetAuthMethod(&authMethod);
+ SetIdentities(pMgr, account, xmlDoc, userName, authMethod, true);
+ result = true;
+ if (ppAccount) account.forget(ppAccount);
+ }
+ } else
+ result = true;
+ return result;
+}
+
+void WMSettings::SetIdentities(nsIMsgAccountManager* pMgr, nsIMsgAccount* pAcc,
+ mozilla::dom::Document* xmlDoc,
+ nsAutoString& inUserName,
+ int32_t authMethodIncoming, bool isNNTP) {
+ // Get the relevant information for an identity
+ nsAutoString value;
+
+ nsCOMPtr<nsIMsgIdentity> id;
+ pMgr->CreateIdentity(getter_AddRefs(id));
+ if (id) {
+ IMPORT_LOG0("Created identity and added to the account\n");
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(
+ xmlDoc, isNNTP ? "NNTP_Display_Name" : "SMTP_Display_Name",
+ value))) {
+ id->SetFullName(value);
+ IMPORT_LOG1("\tname: %S\n", static_cast<const wchar_t*>(value.get()));
+ }
+
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(
+ xmlDoc,
+ isNNTP ? "NNTP_Organization_Name" : "SMTP_Organization_Name",
+ value))) {
+ id->SetOrganization(value);
+ }
+
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(
+ xmlDoc, isNNTP ? "NNTP_Email_Address" : "SMTP_Email_Address",
+ value))) {
+ id->SetEmail(NS_ConvertUTF16toUTF8(value));
+ IMPORT_LOG1("\temail: %S\n", static_cast<const wchar_t*>(value.get()));
+ }
+
+ if (NS_SUCCEEDED(
+ nsWMUtils::GetValueForTag(xmlDoc,
+ isNNTP ? "NNTP_Reply_To_Email_Address"
+ : "SMTP_Reply_To_Email_Address",
+ value))) {
+ id->SetReplyTo(NS_ConvertUTF16toUTF8(value));
+ }
+
+ // Windows users are used to top style quoting.
+ id->SetReplyOnTop(isNNTP ? 0 : 1);
+ pAcc->AddIdentity(id);
+ }
+
+ if (!isNNTP) // NNTP does not use SMTP in OE or TB
+ SetSmtpServer(xmlDoc, id, inUserName, authMethodIncoming);
+}
+
+void WMSettings::SetSmtpServer(mozilla::dom::Document* xmlDoc,
+ nsIMsgIdentity* id, nsAutoString& inUserName,
+ int32_t authMethodIncoming) {
+ nsresult errorCode;
+
+ // set the id.smtpserver accordingly
+ if (!id) return;
+ nsCString smtpServerKey, userName;
+ nsAutoString value, smtpName;
+ if (NS_FAILED(nsWMUtils::GetValueForTag(xmlDoc, "SMTP_Server", smtpName)))
+ return;
+
+ // first we have to calculate the smtp user name which is based on sicily
+ // smtp user name depends on sicily which may or not exist
+ int32_t useSicily = 0;
+ if (NS_SUCCEEDED(
+ nsWMUtils::GetValueForTag(xmlDoc, "SMTP_Use_Sicily", value))) {
+ useSicily = (int32_t)value.ToInteger(&errorCode, 16);
+ }
+ switch (useSicily) {
+ case 1:
+ case 3:
+ if (NS_SUCCEEDED(
+ nsWMUtils::GetValueForTag(xmlDoc, "SMTP_User_Name", value))) {
+ CopyUTF16toUTF8(value, userName);
+ } else {
+ CopyUTF16toUTF8(inUserName, userName);
+ }
+ break;
+ case 2:
+ CopyUTF16toUTF8(inUserName, userName);
+ break;
+ default:
+ break; // initial userName == ""
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISmtpService> smtpService(
+ do_GetService("@mozilla.org/messengercompose/smtp;1", &rv));
+ if (NS_SUCCEEDED(rv) && smtpService) {
+ nsCOMPtr<nsISmtpServer> extgServer;
+ // don't try to make another server
+ // regardless if username doesn't match
+ rv = smtpService->FindServer(userName.get(),
+ NS_ConvertUTF16toUTF8(smtpName).get(),
+ getter_AddRefs(extgServer));
+ if (NS_SUCCEEDED(rv) && extgServer) {
+ // set our account keyed to this smptserver key
+ extgServer->GetKey(getter_Copies(smtpServerKey));
+ id->SetSmtpServerKey(smtpServerKey);
+
+ IMPORT_LOG1("SMTP server already exists: %s\n",
+ NS_ConvertUTF16toUTF8(smtpName).get());
+ } else {
+ nsCOMPtr<nsISmtpServer> smtpServer;
+ rv = smtpService->CreateServer(getter_AddRefs(smtpServer));
+ if (NS_SUCCEEDED(rv) && smtpServer) {
+ if (NS_SUCCEEDED(
+ nsWMUtils::GetValueForTag(xmlDoc, "SMTP_Port", value))) {
+ smtpServer->SetPort(value.ToInteger(&errorCode, 16));
+ }
+
+ if (NS_SUCCEEDED(nsWMUtils::GetValueForTag(
+ xmlDoc, "SMTP_Secure_Connection", value))) {
+ if (value.ToInteger(&errorCode, 16) == 1)
+ smtpServer->SetSocketType(nsMsgSocketType::SSL);
+ else
+ smtpServer->SetSocketType(nsMsgSocketType::plain);
+ }
+ smtpServer->SetUsername(userName);
+ switch (useSicily) {
+ case 1:
+ smtpServer->SetAuthMethod(nsMsgAuthMethod::secure);
+ break;
+ case 2: // requires SMTP authentication to use the incoming server
+ // settings
+ smtpServer->SetAuthMethod(authMethodIncoming);
+ break;
+ case 3:
+ smtpServer->SetAuthMethod(nsMsgAuthMethod::passwordCleartext);
+ break;
+ default:
+ smtpServer->SetAuthMethod(nsMsgAuthMethod::none);
+ }
+
+ smtpServer->SetHostname(NS_ConvertUTF16toUTF8(smtpName));
+
+ smtpServer->GetKey(getter_Copies(smtpServerKey));
+ id->SetSmtpServerKey(smtpServerKey);
+
+ IMPORT_LOG1("Created new SMTP server: %s\n",
+ NS_ConvertUTF16toUTF8(smtpName).get());
+ }
+ }
+ }
+}
diff --git a/comm/mailnews/import/src/nsWMSettings.h b/comm/mailnews/import/src/nsWMSettings.h
new file mode 100644
index 0000000000..d1b770d0a1
--- /dev/null
+++ b/comm/mailnews/import/src/nsWMSettings.h
@@ -0,0 +1,22 @@
+/* -*- Mode: C++; tab-width: 4; 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 nsWMSettings_h___
+#define nsWMSettings_h___
+
+#include "nsIImportSettings.h"
+
+class nsWMSettings : public nsIImportSettings {
+ public:
+ nsWMSettings();
+ static nsresult Create(nsIImportSettings** aImport);
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIIMPORTSETTINGS
+
+ private:
+ virtual ~nsWMSettings();
+};
+
+#endif /* nsWMSettings_h___ */
diff --git a/comm/mailnews/import/src/nsWMStringBundle.cpp b/comm/mailnews/import/src/nsWMStringBundle.cpp
new file mode 100644
index 0000000000..bd60597cbc
--- /dev/null
+++ b/comm/mailnews/import/src/nsWMStringBundle.cpp
@@ -0,0 +1,52 @@
+/* -*- 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 "prprf.h"
+#include "prmem.h"
+#include "nsCOMPtr.h"
+#include "nsMsgUtils.h"
+#include "nsIStringBundle.h"
+#include "nsWMStringBundle.h"
+#include "mozilla/Components.h"
+
+#define WM_MSGS_URL "chrome://messenger/locale/wmImportMsgs.properties"
+
+nsCOMPtr<nsIStringBundle> nsWMStringBundle::m_pBundle = nullptr;
+
+void nsWMStringBundle::GetStringBundle(void) {
+ if (m_pBundle) return;
+
+ nsCOMPtr<nsIStringBundleService> sBundleService =
+ mozilla::components::StringBundle::Service();
+ if (sBundleService) {
+ sBundleService->CreateBundle(WM_MSGS_URL, getter_AddRefs(m_pBundle));
+ }
+}
+
+void nsWMStringBundle::GetStringByID(int32_t stringID, nsString& result) {
+ char16_t* ptrv = GetStringByID(stringID);
+ result = ptrv;
+ FreeString(ptrv);
+}
+
+char16_t* nsWMStringBundle::GetStringByID(int32_t stringID) {
+ if (!m_pBundle) GetStringBundle();
+
+ if (m_pBundle) {
+ nsAutoString str;
+ nsresult rv = m_pBundle->GetStringFromID(stringID, str);
+
+ if (NS_SUCCEEDED(rv)) return ToNewUnicode(str);
+ }
+
+ nsString resultString;
+ resultString.AppendLiteral("[StringID ");
+ resultString.AppendInt(stringID);
+ resultString.AppendLiteral("?]");
+
+ return ToNewUnicode(resultString);
+}
+
+void nsWMStringBundle::Cleanup(void) { m_pBundle = nullptr; }
diff --git a/comm/mailnews/import/src/nsWMStringBundle.h b/comm/mailnews/import/src/nsWMStringBundle.h
new file mode 100644
index 0000000000..45c92f75d6
--- /dev/null
+++ b/comm/mailnews/import/src/nsWMStringBundle.h
@@ -0,0 +1,36 @@
+/* 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 _nsWMStringBundle_H__
+#define _nsWMStringBundle_H__
+
+#include "nsString.h"
+
+class nsIStringBundle;
+
+class nsWMStringBundle {
+ public:
+ static char16_t* GetStringByID(int32_t stringID);
+ static void GetStringByID(int32_t stringID, nsString& result);
+ static void GetStringBundle(void);
+ static void FreeString(char16_t* pStr) { free(pStr); }
+ static void Cleanup(void);
+
+ private:
+ static nsCOMPtr<nsIStringBundle> m_pBundle;
+};
+
+#define WMIMPORT_NAME 2000
+#define WMIMPORT_DESCRIPTION 2001
+#define WMIMPORT_MAILBOX_SUCCESS 2002
+#define WMIMPORT_MAILBOX_BADPARAM 2003
+#define WMIMPORT_MAILBOX_BADSOURCEFILE 2004
+#define WMIMPORT_MAILBOX_CONVERTERROR 2005
+#define WMIMPORT_DEFAULT_NAME 2006
+#define WMIMPORT_AUTOFIND 2007
+#define WMIMPORT_ADDRESS_SUCCESS 2008
+#define WMIMPORT_ADDRESS_CONVERTERROR 2009
+#define WMIMPORT_ADDRESS_BADPARAM 2010
+
+#endif /* _nsWMStringBundle_H__ */
diff --git a/comm/mailnews/import/src/nsWMUtils.cpp b/comm/mailnews/import/src/nsWMUtils.cpp
new file mode 100644
index 0000000000..0172a30a82
--- /dev/null
+++ b/comm/mailnews/import/src/nsWMUtils.cpp
@@ -0,0 +1,153 @@
+/* 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 "nsCOMArray.h"
+#include "nsCOMPtr.h"
+#include "nsComponentManagerUtils.h"
+#include "nsNetCID.h"
+#include "nsString.h"
+#include "mozilla/dom/Document.h"
+#include "nsWMUtils.h"
+#include "nsINodeList.h"
+#include "nsContentList.h"
+#include "nsINode.h"
+#include "nsIFileStreams.h"
+#include "nsIFile.h"
+#include "nsIDirectoryEnumerator.h"
+#include "ImportDebug.h"
+#include "prio.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/DOMParser.h"
+
+nsresult nsWMUtils::FindWMKey(nsIWindowsRegKey** aKey) {
+ nsresult rv;
+ nsCOMPtr<nsIWindowsRegKey> key =
+ do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ u"Software\\Microsoft\\Windows Live Mail"_ns,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ if (NS_SUCCEEDED(rv)) {
+ key.forget(aKey);
+ return rv;
+ }
+
+ rv = key->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER,
+ u"Software\\Microsoft\\Windows Mail"_ns,
+ nsIWindowsRegKey::ACCESS_QUERY_VALUE);
+ key.forget(aKey);
+ return rv;
+}
+
+nsresult nsWMUtils::GetRootFolder(nsIFile** aRootFolder) {
+ nsCOMPtr<nsIWindowsRegKey> key;
+ if (NS_FAILED(nsWMUtils::FindWMKey(getter_AddRefs(key)))) {
+ IMPORT_LOG0("*** Error finding Windows Live Mail registry account keys\n");
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ // This is essential to proceed; it is the location on disk of xml-type
+ // account files; it is in reg_expand_sz so it will need expanding to absolute
+ // path.
+ nsString storeRoot;
+ nsresult rv = key->ReadStringValue(u"Store Root"_ns, storeRoot);
+ key->Close(); // Finished with windows registry key. We do not want to return
+ // before this closing
+ if (NS_FAILED(rv) || storeRoot.IsEmpty()) {
+ IMPORT_LOG0("*** Error finding Windows Live Mail Store Root\n");
+ return rv;
+ }
+
+ uint32_t size =
+ ::ExpandEnvironmentStringsW((LPCWSTR)storeRoot.get(), nullptr, 0);
+ nsString expandedStoreRoot;
+ expandedStoreRoot.SetLength(size - 1);
+ if (expandedStoreRoot.Length() != size - 1) return NS_ERROR_FAILURE;
+ ::ExpandEnvironmentStringsW((LPCWSTR)storeRoot.get(),
+ (LPWSTR)expandedStoreRoot.BeginWriting(), size);
+ storeRoot = expandedStoreRoot;
+
+ nsCOMPtr<nsIFile> rootFolder(
+ do_CreateInstance(NS_LOCAL_FILE_CONTRACTID, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = rootFolder->InitWithPath(storeRoot);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rootFolder.forget(aRootFolder);
+
+ return NS_OK;
+}
+
+nsresult nsWMUtils::GetOEAccountFiles(nsCOMArray<nsIFile>& aFileArray) {
+ nsCOMPtr<nsIFile> rootFolder;
+
+ nsresult rv = GetRootFolder(getter_AddRefs(rootFolder));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ return GetOEAccountFilesInFolder(rootFolder, aFileArray);
+}
+
+nsresult nsWMUtils::GetOEAccountFilesInFolder(nsIFile* aFolder,
+ nsCOMArray<nsIFile>& aFileArray) {
+ nsCOMPtr<nsIDirectoryEnumerator> entries;
+ nsresult rv = aFolder->GetDirectoryEntries(getter_AddRefs(entries));
+ if (NS_FAILED(rv) || !entries) return NS_ERROR_FAILURE;
+
+ bool hasMore;
+ while (NS_SUCCEEDED(entries->HasMoreElements(&hasMore)) && hasMore) {
+ nsCOMPtr<nsIFile> file;
+ rv = entries->GetNextFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ bool isDirectory;
+ rv = file->IsDirectory(&isDirectory);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (isDirectory) {
+ GetOEAccountFilesInFolder(file, aFileArray);
+ } else {
+ nsString name;
+ rv = file->GetLeafName(name);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (StringEndsWith(name, u".oeaccount"_ns)) aFileArray.AppendObject(file);
+ }
+ }
+ return NS_OK;
+}
+
+nsresult nsWMUtils::MakeXMLdoc(mozilla::dom::Document** aXmlDoc,
+ nsIFile* aFile) {
+ nsresult rv;
+ nsCOMPtr<nsIFileInputStream> stream =
+ do_CreateInstance(NS_LOCALFILEINPUTSTREAM_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = stream->Init(aFile, PR_RDONLY, -1, 0);
+ mozilla::ErrorResult rv2;
+ RefPtr<mozilla::dom::DOMParser> parser =
+ mozilla::dom::DOMParser::CreateWithoutGlobal(rv2);
+ if (rv2.Failed()) {
+ return rv2.StealNSResult();
+ }
+ int64_t filesize;
+ aFile->GetFileSize(&filesize);
+ nsCOMPtr<mozilla::dom::Document> xmldoc = parser->ParseFromStream(
+ stream, EmptyString(), int32_t(filesize),
+ mozilla::dom::SupportedType::Application_xml, rv2);
+ xmldoc.forget(aXmlDoc);
+ return rv2.StealNSResult();
+}
+
+nsresult nsWMUtils::GetValueForTag(mozilla::dom::Document* aXmlDoc,
+ const char* aTagName, nsAString& aValue) {
+ nsAutoString tagName;
+ tagName.AssignASCII(aTagName);
+ nsCOMPtr<nsINodeList> list = aXmlDoc->GetElementsByTagName(tagName);
+ nsCOMPtr<nsINode> node = list->Item(0);
+ if (!node) return NS_ERROR_FAILURE;
+ mozilla::ErrorResult rv2;
+ node->GetTextContent(aValue, rv2);
+ return rv2.StealNSResult();
+}
diff --git a/comm/mailnews/import/src/nsWMUtils.h b/comm/mailnews/import/src/nsWMUtils.h
new file mode 100644
index 0000000000..02f15c6379
--- /dev/null
+++ b/comm/mailnews/import/src/nsWMUtils.h
@@ -0,0 +1,23 @@
+/* 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 nsWMUtils_h___
+#define nsWMUtils_h___
+
+#include <windows.h>
+#include "nsIWindowsRegKey.h"
+
+class nsWMUtils {
+ public:
+ static nsresult FindWMKey(nsIWindowsRegKey** aKey);
+ static nsresult GetRootFolder(nsIFile** aRootFolder);
+ static nsresult GetOEAccountFiles(nsCOMArray<nsIFile>& aFileArray);
+ static nsresult GetOEAccountFilesInFolder(nsIFile* aFolder,
+ nsCOMArray<nsIFile>& aFileArray);
+ static nsresult MakeXMLdoc(mozilla::dom::Document** aXmlDoc, nsIFile* aFile);
+ static nsresult GetValueForTag(mozilla::dom::Document* aXmlDoc,
+ const char* aTagName, nsAString& aValue);
+};
+
+#endif /* nsWMUtils_h___ */
diff --git a/comm/mailnews/import/src/rtfDecoder.cpp b/comm/mailnews/import/src/rtfDecoder.cpp
new file mode 100644
index 0000000000..86a8151618
--- /dev/null
+++ b/comm/mailnews/import/src/rtfDecoder.cpp
@@ -0,0 +1,561 @@
+/* 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 <stack>
+#include <map>
+#include <sstream>
+#include "windows.h"
+#include "rtfDecoder.h"
+
+#define SIZEOF(x) (sizeof(x) / sizeof((x)[0]))
+#define IS_DIGIT(i) ((i) >= '0' && (i) <= '9')
+#define IS_ALPHA(VAL) \
+ (((VAL) >= 'a' && (VAL) <= 'z') || ((VAL) >= 'A' && (VAL) <= 'Z'))
+
+inline int HexToInt(char ch) {
+ switch (ch) {
+ case '0':
+ case '1':
+ case '2':
+ case '3':
+ case '4':
+ case '5':
+ case '6':
+ case '7':
+ case '8':
+ case '9':
+ return ch - '0';
+ case 'A':
+ case 'B':
+ case 'C':
+ case 'D':
+ case 'E':
+ case 'F':
+ return ch - 'A' + 10;
+ case 'a':
+ case 'b':
+ case 'c':
+ case 'd':
+ case 'e':
+ case 'f':
+ return ch - 'a' + 10;
+ default:
+ return 0;
+ }
+}
+
+inline int CharsetToCP(int charset) {
+ // We don't know the Code page for the commented out charsets.
+ switch (charset) {
+ case 0:
+ return 1252; // ANSI
+ case 1:
+ return 0; // Default
+ // case 2: return 42; // Symbol
+ case 2:
+ return 1252; // Symbol
+ case 77:
+ return 10000; // Mac Roman
+ case 78:
+ return 10001; // Mac Shift Jis
+ case 79:
+ return 10003; // Mac Hangul
+ case 80:
+ return 10008; // Mac GB2312
+ case 81:
+ return 10002; // Mac Big5
+ // case 82: Mac Johab (old)
+ case 83:
+ return 10005; // Mac Hebrew
+ case 84:
+ return 10004; // Mac Arabic
+ case 85:
+ return 10006; // Mac Greek
+ case 86:
+ return 10081; // Mac Turkish
+ case 87:
+ return 10021; // Mac Thai
+ case 88:
+ return 10029; // Mac East Europe
+ case 89:
+ return 10007; // Mac Russian
+ case 128:
+ return 932; // Shift JIS
+ case 129:
+ return 949; // Hangul
+ case 130:
+ return 1361; // Johab
+ case 134:
+ return 936; // GB2312
+ case 136:
+ return 950; // Big5
+ case 161:
+ return 1253; // Greek
+ case 162:
+ return 1254; // Turkish
+ case 163:
+ return 1258; // Vietnamese
+ case 177:
+ return 1255; // Hebrew
+ case 178:
+ return 1256; // Arabic
+ // case 179: Arabic Traditional (old)
+ // case 180: Arabic user (old)
+ // case 181: Hebrew user (old)
+ case 186:
+ return 1257; // Baltic
+ case 204:
+ return 1251; // Russian
+ case 222:
+ return 874; // Thai
+ case 238:
+ return 1250; // Eastern European
+ case 254:
+ return 437; // PC 437
+ case 255:
+ return 850; // OEM
+ default:
+ return CP_ACP;
+ }
+}
+
+struct FontInfo {
+ enum Options { has_fcharset = 0x0001, has_cpg = 0x0002 };
+ unsigned int options;
+ int fcharset;
+ unsigned int cpg;
+ FontInfo() : options(0), fcharset(0), cpg(0xFFFFFFFF) {}
+ unsigned int Codepage() {
+ if (options & has_cpg)
+ return cpg;
+ else if (options & has_fcharset)
+ return CharsetToCP(fcharset);
+ else
+ return 0xFFFFFFFF;
+ }
+};
+typedef std::map<int, FontInfo> Fonttbl;
+
+struct LocalState {
+ bool fonttbl; // When fonts are being defined
+ int f; // Index of the font being defined/used; defines the codepage if no
+ // \cpg
+ unsigned int uc; // ucN keyword value; its default is 1
+ unsigned int codepage; // defined by \cpg
+};
+typedef std::stack<LocalState> StateStack;
+
+struct GlobalState {
+ enum Pcdata_state { pcdsno, pcdsin, pcdsfinished };
+ std::istream& stream;
+ Fonttbl fonttbl;
+ StateStack stack;
+ unsigned int codepage; // defined by \ansi, \mac, \pc, \pca, and \ansicpgN
+ int deff;
+ std::stringstream pcdata_a;
+ unsigned int pcdata_a_codepage;
+ Pcdata_state pcdata_a_state;
+
+ explicit GlobalState(std::istream& s)
+ : stream(s), codepage(CP_ACP), deff(-1), pcdata_a_state(pcdsno) {
+ LocalState st;
+ st.fonttbl = false;
+ st.f = -1;
+ st.uc = 1;
+ st.codepage = 0xFFFFFFFF;
+ stack.push(st);
+ }
+ unsigned int GetCurrentCP() {
+ if (stack.top().codepage != 0xFFFFFFFF) // \cpg in use
+ return stack.top().codepage;
+ // \cpg not used; use font settings
+ int f = (stack.top().f != -1) ? stack.top().f : deff;
+ if (f != -1) {
+ Fonttbl::iterator iter = fonttbl.find(f);
+ if (iter != fonttbl.end()) {
+ unsigned int cp = iter->second.Codepage();
+ if (cp != 0xFFFFFFFF) return cp;
+ }
+ }
+ return codepage; // No overrides; use the top-level legacy setting
+ }
+};
+
+struct Keyword {
+ char name[33];
+ bool hasVal;
+ int val;
+};
+
+class Lexem {
+ public:
+ enum Type {
+ ltGroupBegin,
+ ltGroupEnd,
+ ltKeyword,
+ ltPCDATA_A,
+ ltPCDATA_W,
+ ltBDATA,
+ ltEOF,
+ ltError
+ };
+ explicit Lexem(Type t = ltError) : m_type(t) {}
+ Lexem(Lexem& from) {
+ switch (m_type = from.m_type) {
+ case ltKeyword:
+ m_keyword = from.m_keyword;
+ break;
+ case ltPCDATA_A:
+ m_pcdata_a = from.m_pcdata_a;
+ break;
+ case ltPCDATA_W:
+ m_pcdata_w = from.m_pcdata_w;
+ break;
+ case ltBDATA:
+ m_bdata = from.m_bdata; // Move pointers when copying.
+ from.m_type = ltError; // Invalidate the original. Not nice.
+ break;
+ }
+ }
+ ~Lexem() { Clear(); }
+ Lexem& operator=(Lexem& from) {
+ if (&from != this) {
+ Clear();
+ switch (m_type = from.m_type) {
+ case ltKeyword:
+ m_keyword = from.m_keyword;
+ break;
+ case ltPCDATA_A:
+ m_pcdata_a = from.m_pcdata_a;
+ break;
+ case ltPCDATA_W:
+ m_pcdata_w = from.m_pcdata_w;
+ break;
+ case ltBDATA:
+ m_bdata = from.m_bdata; // Move pointers when copying.
+ from.m_type = ltError; // Invalidate the original. Not nice.
+ break;
+ }
+ }
+ return *this;
+ }
+ Type type() const { return m_type; }
+ void SetPCDATA_A(char chdata) {
+ Clear();
+ m_pcdata_a = chdata;
+ m_type = ltPCDATA_A;
+ }
+ void SetPCDATA_W(wchar_t chdata) {
+ Clear();
+ m_pcdata_w = chdata;
+ m_type = ltPCDATA_W;
+ }
+ void SetBDATA(const char* data, int sz) {
+ char* tmp = new char[sz]; // to allow getting the data from itself
+ if (tmp) {
+ memcpy(tmp, data, sz);
+ Clear();
+ m_bdata.data = tmp;
+ m_bdata.sz = sz;
+ m_type = ltBDATA;
+ } else
+ m_type = ltError;
+ }
+ void SetKeyword(const Keyword& src) {
+ Clear();
+ m_type = ltKeyword;
+ m_keyword = src;
+ }
+ void SetKeyword(const char* name, bool hasVal = false, int val = 0) {
+ char tmp[SIZEOF(m_keyword.name)];
+ strncpy(tmp, name,
+ SIZEOF(m_keyword.name) - 1); // to allow copy drom itself
+ tmp[SIZEOF(m_keyword.name) - 1] = 0;
+ Clear();
+ m_type = ltKeyword;
+ memcpy(m_keyword.name, tmp, SIZEOF(m_keyword.name));
+ m_keyword.hasVal = hasVal;
+ m_keyword.val = val;
+ }
+ const char* KeywordName() const {
+ return (m_type == ltKeyword) ? m_keyword.name : 0;
+ }
+ const int* KeywordVal() const {
+ return ((m_type == ltKeyword) && m_keyword.hasVal) ? &m_keyword.val : 0;
+ }
+ char pcdata_a() const { return (m_type == ltPCDATA_A) ? m_pcdata_a : 0; }
+ wchar_t pcdata_w() const { return (m_type == ltPCDATA_W) ? m_pcdata_w : 0; }
+ const char* bdata() const { return (m_type == ltBDATA) ? m_bdata.data : 0; }
+ int bdata_sz() const { return (m_type == ltBDATA) ? m_bdata.sz : 0; }
+ static Lexem eof;
+ static Lexem groupBegin;
+ static Lexem groupEnd;
+ static Lexem error;
+
+ private:
+ struct BDATA {
+ size_t sz;
+ char* data;
+ };
+
+ Type m_type;
+ union {
+ Keyword m_keyword;
+ char m_pcdata_a;
+ wchar_t m_pcdata_w;
+ BDATA m_bdata;
+ };
+ // This function leaves the object in the broken state. Must be followed
+ // by a correct initialization.
+ void Clear() {
+ switch (m_type) {
+ case ltBDATA:
+ delete[] m_bdata.data;
+ break;
+ }
+ // m_type = ltError;
+ }
+};
+
+Lexem Lexem::eof(ltEOF);
+Lexem Lexem::groupBegin(ltGroupBegin);
+Lexem Lexem::groupEnd(ltGroupEnd);
+Lexem Lexem::error(ltError);
+
+// This function moves pos. When calling the function, pos must be next to the
+// backslash; pos must be in the same sequence and before end!
+Keyword GetKeyword(std::istream& stream) {
+ Keyword keyword = {"", false, 0};
+ char ch;
+ if (stream.get(ch).eof()) return keyword;
+ // Control word; maybe delimiter and value
+ if (IS_ALPHA(ch)) {
+ int i = 0;
+ do {
+ // We take up to 32 characters into account, skipping over extra
+ // characters (allowing for some non-conformant implementation).
+ if (i < 32) keyword.name[i++] = ch;
+ } while (!stream.get(ch).eof() && IS_ALPHA(ch));
+ keyword.name[i] = 0; // NULL-terminating
+ if (!stream.eof() && (IS_DIGIT(ch) || (ch == '-'))) { // Value begin
+ keyword.hasVal = true;
+ bool negative = (ch == '-');
+ if (negative) stream.get(ch);
+ i = 0;
+ while (!stream.eof() && IS_DIGIT(ch)) {
+ // We take into account only 10 digits, skip other. Older specs stated
+ // that we must be ready for an arbitrary number of digits.
+ if (i++ < 10) keyword.val = keyword.val * 10 + (ch - '0');
+ stream.get(ch);
+ }
+ if (negative) keyword.val = -keyword.val;
+ }
+ // End of control word; the space is just a delimiter - skip it
+ if (!stream.eof() && !(ch == ' ')) stream.unget();
+ } else { // Control symbol
+ keyword.name[0] = ch;
+ keyword.name[1] = 0;
+ }
+ return keyword;
+}
+
+void GetLexem(std::istream& stream, Lexem& result) {
+ // We always stay at the beginning of the next lexem or a crlf
+ // If it's a brace then it's group begin/end
+ // If it's a backslash -> Preprocess
+ // - if it's a \u or \' -> make UTF16 character
+ // - else it's a keyword -> Process (e.g., remember the codepage)
+ // - (if the keyword is \bin then the following is #BDATA)
+ // If it's some other character -> Preprocess
+ // - if it's 0x09 -> it's the keyword \tab
+ // - else it's a PCDATA
+ char ch;
+ while (!stream.get(ch).eof() && ((ch == '\n') || (ch == '\r')))
+ ; // Skip crlf
+ if (stream.eof())
+ result = Lexem::eof;
+ else {
+ switch (ch) {
+ case '{': // Group begin
+ case '}': // Group end
+ result = (ch == '{') ? Lexem::groupBegin : Lexem::groupEnd;
+ break;
+ case '\\': // Keyword
+ result.SetKeyword(GetKeyword(stream));
+ break;
+ case '\t': // tab
+ result.SetKeyword("tab");
+ break;
+ default: // PSDATA?
+ result.SetPCDATA_A(ch);
+ break;
+ }
+ }
+}
+
+void PreprocessLexem(/*inout*/ Lexem& lexem, std::istream& stream, int uc) {
+ if (lexem.type() == Lexem::ltKeyword) {
+ if (lexem.KeywordName()[0] == 0) // Empty keyword - maybe eof?
+ lexem = Lexem::error;
+ else if (eq(lexem.KeywordName(), "u")) {
+ // Unicode character - get the UTF16 and skip the uc characters
+ if (const int* val = lexem.KeywordVal()) {
+ lexem.SetPCDATA_W(*val);
+ stream.ignore(uc);
+ } else
+ lexem = Lexem::error;
+ } else if (eq(lexem.KeywordName(), "'")) {
+ // 8-bit character (\'hh) -> use current codepage
+ char ch = 0, ch1 = 0;
+ if (!stream.get(ch).eof()) ch1 = HexToInt(ch);
+ if (!stream.get(ch).eof()) (ch1 <<= 4) += HexToInt(ch);
+ lexem.SetPCDATA_A(ch1);
+ } else if (eq(lexem.KeywordName(), "\\") || eq(lexem.KeywordName(), "{") ||
+ eq(lexem.KeywordName(), "}")) // escaped characters
+ lexem.SetPCDATA_A(lexem.KeywordName()[0]);
+ else if (eq(lexem.KeywordName(), "bin")) {
+ if (const int* i = lexem.KeywordVal()) {
+ char* data = new char[*i];
+ if (data) {
+ stream.read(data, *i);
+ if (stream.fail())
+ lexem = Lexem::error;
+ else
+ lexem.SetBDATA(data, *i);
+ delete[] data;
+ } else
+ lexem = Lexem::error;
+ } else
+ lexem = Lexem::error;
+ } else if (eq(lexem.KeywordName(), "\n") || eq(lexem.KeywordName(), "\r")) {
+ // escaped cr or lf
+ lexem.SetKeyword("par");
+ }
+ }
+}
+
+void UpdateState(const Lexem& lexem, /*inout*/ GlobalState& globalState) {
+ switch (globalState.pcdata_a_state) {
+ case GlobalState::pcdsfinished: // Last time we finished the pcdata
+ globalState.pcdata_a_state = GlobalState::pcdsno;
+ break;
+ case GlobalState::pcdsin:
+ // to be reset later if still in the pcdata
+ globalState.pcdata_a_state = GlobalState::pcdsfinished;
+ break;
+ }
+
+ switch (lexem.type()) {
+ case Lexem::ltGroupBegin:
+ globalState.stack.push(globalState.stack.top());
+ break;
+ case Lexem::ltGroupEnd:
+ globalState.stack.pop();
+ break;
+ case Lexem::ltKeyword: {
+ const int* val = lexem.KeywordVal();
+ if (eq(lexem.KeywordName(), "ansi"))
+ globalState.codepage = CP_ACP;
+ else if (eq(lexem.KeywordName(), "mac"))
+ globalState.codepage = CP_MACCP;
+ else if (eq(lexem.KeywordName(), "pc"))
+ globalState.codepage = 437;
+ else if (eq(lexem.KeywordName(), "pca"))
+ globalState.codepage = 850;
+ else if (eq(lexem.KeywordName(), "ansicpg") && val)
+ globalState.codepage = static_cast<unsigned int>(*val);
+ else if (eq(lexem.KeywordName(), "deff") && val)
+ globalState.deff = *val;
+ else if (eq(lexem.KeywordName(), "fonttbl"))
+ globalState.stack.top().fonttbl = true;
+ else if (eq(lexem.KeywordName(), "f") && val) {
+ globalState.stack.top().f = *val;
+ } else if (eq(lexem.KeywordName(), "fcharset") &&
+ globalState.stack.top().fonttbl &&
+ (globalState.stack.top().f != -1) && val) {
+ FontInfo& f = globalState.fonttbl[globalState.stack.top().f];
+ f.options |= FontInfo::has_fcharset;
+ f.fcharset = *val;
+ } else if (eq(lexem.KeywordName(), "cpg") && val) {
+ if (globalState.stack.top().fonttbl &&
+ (globalState.stack.top().f != -1)) { // Defining a font
+ FontInfo& f = globalState.fonttbl[globalState.stack.top().f];
+ f.options |= FontInfo::has_cpg;
+ f.cpg = *val;
+ } else { // Overriding the codepage for the block - may be in filenames
+ globalState.stack.top().codepage = *val;
+ }
+ } else if (eq(lexem.KeywordName(), "plain"))
+ globalState.stack.top().f = -1;
+ else if (eq(lexem.KeywordName(), "uc") && val)
+ globalState.stack.top().uc = *val;
+ } break;
+ case Lexem::ltPCDATA_A:
+ if (globalState.pcdata_a_state ==
+ GlobalState::pcdsno) // Beginning of the pcdata
+ globalState.pcdata_a_codepage =
+ globalState.GetCurrentCP(); // to use later to convert to utf16
+ globalState.pcdata_a_state = GlobalState::pcdsin;
+ globalState.pcdata_a << lexem.pcdata_a();
+ break;
+ }
+}
+
+void DecodeRTF(std::istream& rtf, CRTFDecoder& decoder) {
+ // Check if this is the rtf
+ Lexem lexem;
+ GetLexem(rtf, lexem);
+ if (lexem.type() != Lexem::ltGroupBegin) return;
+ decoder.BeginGroup();
+ GetLexem(rtf, lexem);
+ if ((lexem.type() != Lexem::ltKeyword) || !eq(lexem.KeywordName(), "rtf") ||
+ !lexem.KeywordVal() || (*lexem.KeywordVal() != 1))
+ return;
+ decoder.Keyword(lexem.KeywordName(), lexem.KeywordVal());
+
+ GlobalState state(rtf);
+ // Level is the count of elements in the stack
+
+ while (!state.stream.eof() &&
+ (state.stack.size() > 0)) { // Don't go past the global group
+ GetLexem(state.stream, lexem);
+ PreprocessLexem(lexem, state.stream, state.stack.top().uc);
+ UpdateState(lexem, state);
+
+ if (state.pcdata_a_state == GlobalState::pcdsfinished) {
+ std::string s = state.pcdata_a.str();
+ int sz = ::MultiByteToWideChar(state.pcdata_a_codepage, 0, s.c_str(),
+ s.size(), 0, 0);
+ if (sz) {
+ wchar_t* data = new wchar_t[sz];
+ ::MultiByteToWideChar(state.pcdata_a_codepage, 0, s.c_str(), s.size(),
+ data, sz);
+ decoder.PCDATA(data, sz);
+ delete[] data;
+ }
+ state.pcdata_a.str(""); // reset
+ }
+
+ switch (lexem.type()) {
+ case Lexem::ltGroupBegin:
+ decoder.BeginGroup();
+ break;
+ case Lexem::ltGroupEnd:
+ decoder.EndGroup();
+ break;
+ case Lexem::ltKeyword:
+ decoder.Keyword(lexem.KeywordName(), lexem.KeywordVal());
+ break;
+ case Lexem::ltPCDATA_W: {
+ wchar_t ch = lexem.pcdata_w();
+ decoder.PCDATA(&ch, 1);
+ } break;
+ case Lexem::ltBDATA:
+ decoder.BDATA(lexem.bdata(), lexem.bdata_sz());
+ break;
+ case Lexem::ltError:
+ break; // Just silently skip the erroneous data - basic error recovery
+ }
+ } // while
+} // DecodeRTF
diff --git a/comm/mailnews/import/src/rtfDecoder.h b/comm/mailnews/import/src/rtfDecoder.h
new file mode 100644
index 0000000000..1b547c77b8
--- /dev/null
+++ b/comm/mailnews/import/src/rtfDecoder.h
@@ -0,0 +1,21 @@
+/* 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 <istream>
+
+template <size_t len>
+inline bool eq(const char* str1, const char (&str2)[len]) {
+ return ::strncmp(str1, str2, len) == 0;
+};
+
+class CRTFDecoder {
+ public:
+ virtual void BeginGroup() = 0;
+ virtual void EndGroup() = 0;
+ virtual void Keyword(const char* name, const int* Val) = 0;
+ virtual void PCDATA(const wchar_t* data, size_t cch) = 0;
+ virtual void BDATA(const char* data, size_t sz) = 0;
+};
+
+void DecodeRTF(std::istream& rtf, CRTFDecoder& decoder);
diff --git a/comm/mailnews/import/src/rtfMailDecoder.cpp b/comm/mailnews/import/src/rtfMailDecoder.cpp
new file mode 100644
index 0000000000..c5a234320e
--- /dev/null
+++ b/comm/mailnews/import/src/rtfMailDecoder.cpp
@@ -0,0 +1,71 @@
+/* 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 "rtfMailDecoder.h"
+
+void CRTFMailDecoder::BeginGroup() {
+ ClearState(sAsterisk);
+ SetState(sBeginGroup);
+ if (m_skipLevel) ++m_skipLevel;
+}
+
+void CRTFMailDecoder::EndGroup() {
+ ClearState(sAsterisk | sBeginGroup);
+ if (m_skipLevel) --m_skipLevel;
+}
+
+void CRTFMailDecoder::AddText(const wchar_t* txt, size_t cch) {
+ if (!IsHtmlRtf()) {
+ if (cch == static_cast<size_t>(-1))
+ m_text += txt;
+ else
+ m_text.append(txt, cch);
+ }
+}
+
+void CRTFMailDecoder::Keyword(const char* name, const int* Val) {
+ bool asterisk = IsAsterisk();
+ ClearState(sAsterisk); // for inside use only
+ bool beginGroup = IsBeginGroup();
+ ClearState(sBeginGroup); // for inside use only
+ if (!m_skipLevel) {
+ if (eq(name, "*") && beginGroup)
+ SetState(sAsterisk);
+ else if (asterisk) {
+ if (eq(name, "htmltag") &&
+ (m_mode ==
+ mHTML)) { // \*\htmltag -> don't ignore; include the following text
+ } else
+ ++m_skipLevel;
+ } else if (eq(name, "htmlrtf")) {
+ if (Val && (*Val == 0))
+ ClearState(sHtmlRtf);
+ else
+ SetState(sHtmlRtf);
+ } else if (eq(name, "par") || eq(name, "line")) {
+ AddText(L"\r\n");
+ } else if (eq(name, "tab")) {
+ AddText(L"\t");
+ } else if (eq(name, "rquote")) {
+ AddText(L"\x2019"); // Unicode right single quotation mark
+ } else if (eq(name, "fromtext") &&
+ (m_mode == mNone)) { // avoid double "fromX"
+ m_mode = mText;
+ } else if (eq(name, "fromhtml") &&
+ (m_mode == mNone)) { // avoid double "fromX"
+ m_mode = mHTML;
+ } else if (eq(name, "fonttbl") || eq(name, "colortbl") ||
+ eq(name, "stylesheet") || eq(name, "pntext"))
+ ++m_skipLevel;
+ }
+}
+
+void CRTFMailDecoder::PCDATA(const wchar_t* data, size_t cch) {
+ ClearState(sAsterisk | sBeginGroup);
+ if (!m_skipLevel) AddText(data, cch);
+}
+
+void CRTFMailDecoder::BDATA(const char* data, size_t sz) {
+ ClearState(sAsterisk | sBeginGroup);
+}
diff --git a/comm/mailnews/import/src/rtfMailDecoder.h b/comm/mailnews/import/src/rtfMailDecoder.h
new file mode 100644
index 0000000000..7f4063c5ae
--- /dev/null
+++ b/comm/mailnews/import/src/rtfMailDecoder.h
@@ -0,0 +1,44 @@
+/* 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 "mozilla/Attributes.h"
+#include <string>
+#include "rtfDecoder.h"
+
+class CRTFMailDecoder : public CRTFDecoder {
+ public:
+ enum Mode { mNone, mText, mHTML };
+ CRTFMailDecoder() : m_mode(mNone), m_state(sNormal), m_skipLevel(0) {}
+ void BeginGroup() override;
+ void EndGroup() override;
+ void Keyword(const char* name, const int* Val) override;
+ void PCDATA(const wchar_t* data, size_t cch) override;
+ void BDATA(const char* data, size_t sz) override;
+ const wchar_t* text() { return m_text.c_str(); }
+ std::wstring::size_type textSize() { return m_text.size(); }
+ Mode mode() { return m_mode; }
+
+ private:
+ enum State {
+ sNormal = 0x0000,
+ sBeginGroup = 0x0001,
+ sAsterisk = 0x0002,
+ sHtmlRtf = 0x0004
+ };
+
+ std::wstring m_text;
+ Mode m_mode;
+ unsigned int m_state; // bitmask of State
+ // bool m_beginGroup; // true just after the {
+ // bool m_asterisk; // true just after the {\*
+ int m_skipLevel; // if >0 then we ignore everything
+ // bool m_htmlrtf;
+ inline void SetState(unsigned int s) { m_state |= s; }
+ inline void ClearState(unsigned int s) { m_state &= ~s; }
+ inline bool CheckState(State s) { return (m_state & s) != 0; }
+ inline bool IsAsterisk() { return CheckState(sAsterisk); }
+ inline bool IsBeginGroup() { return CheckState(sBeginGroup); }
+ inline bool IsHtmlRtf() { return CheckState(sHtmlRtf); }
+ void AddText(const wchar_t* txt, size_t cch = static_cast<size_t>(-1));
+};
diff --git a/comm/mailnews/import/test/moz.build b/comm/mailnews/import/test/moz.build
new file mode 100644
index 0000000000..6b37fdbe09
--- /dev/null
+++ b/comm/mailnews/import/test/moz.build
@@ -0,0 +1,6 @@
+# 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/.
+
+XPCSHELL_TESTS_MANIFESTS += ["unit/xpcshell.ini"]
diff --git a/comm/mailnews/import/test/unit/head_import.js b/comm/mailnews/import/test/unit/head_import.js
new file mode 100644
index 0000000000..19be6a2662
--- /dev/null
+++ b/comm/mailnews/import/test/unit/head_import.js
@@ -0,0 +1,23 @@
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { XPCOMUtils } = ChromeUtils.importESModule(
+ "resource://gre/modules/XPCOMUtils.sys.mjs"
+);
+
+var CC = Components.Constructor;
+
+// Ensure the profile directory is set up
+do_get_profile();
+
+// Import the required setup scripts.
+/* import-globals-from ../../../test/resources/abSetup.js */
+load("../../../resources/abSetup.js");
+
+// Import the script with basic import functions
+/* import-globals-from resources/import_helper.js */
+load("resources/import_helper.js");
+
+registerCleanupFunction(function () {
+ load("../../../resources/mailShutdown.js");
+});
diff --git a/comm/mailnews/import/test/unit/resources/AB_README b/comm/mailnews/import/test/unit/resources/AB_README
new file mode 100644
index 0000000000..3cbeb09508
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/AB_README
@@ -0,0 +1,39 @@
+To test importing an address book, make a new file in the
+/import/test/unit directory with the prefix test_ in the filename
+(ex. test_ldif_import.js).
+
+It should have a function named run_test with no parameters. If you are using
+import_helper.js, which is already imported, you must at least get the file
+to import and make a new AbImportHelper object with at least the file and type
+of import. Call the beginImport method on the object when you are ready to
+start the import.
+
+If you would like the results of the import checked, make sure to update
+addressbook.json. This file is read by import_helper.js to compare the address
+book cards imported to an array of "cards" in in this file. When making a new
+import, first chose a name for the array (like basic_addressbook) to store the
+cards that should be in the newly-imported address book. The properties and
+values of each object in the array should identical to the properties and values
+of the newly-imported card(s) and the cards themselves need to be in the
+expected order. If a card to be imported does not have a property, do not
+include it in the JSON card. Multiple types of imports can be tested with one
+array, as only the supported attributes are checked.
+
+You will also need to give the AbImportHelper constructor two additional
+parameters: the name the imported address book will have (the filename without
+the extension) and the name you chose for the JSON object.
+
+Here is a sample LDIF unit test that doesn't check the results:
+function run_test()
+{
+ var file = do_get_file("resources/basic_ldif_addressbook.ldif");
+ new AbImportHelper(file, "Text file").beginImport();
+}
+
+Here is a sample CSV unit test that checks the results:
+function run_test()
+{
+ var file = do_get_file("resources/basic_csv_addressbook.csv");
+ new AbImportHelper(file, "Text file", "basic_csv_addressbook",
+ "basic_addressbook").beginImport();
+}
diff --git a/comm/mailnews/import/test/unit/resources/WindowsLiveMail/MicrosoftCommunities/account{2E23}.oeaccount b/comm/mailnews/import/test/unit/resources/WindowsLiveMail/MicrosoftCommunities/account{2E23}.oeaccount
new file mode 100644
index 0000000000..b1b278658a
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/WindowsLiveMail/MicrosoftCommunities/account{2E23}.oeaccount
Binary files differ
diff --git a/comm/mailnews/import/test/unit/resources/WindowsLiveMail/donhallimap/donhallimap{testimap}.oeaccount b/comm/mailnews/import/test/unit/resources/WindowsLiveMail/donhallimap/donhallimap{testimap}.oeaccount
new file mode 100644
index 0000000000..c23cf25d17
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/WindowsLiveMail/donhallimap/donhallimap{testimap}.oeaccount
Binary files differ
diff --git a/comm/mailnews/import/test/unit/resources/WindowsLiveMail/donhallnntp/donhallnntp{testnntp}.oeaccount b/comm/mailnews/import/test/unit/resources/WindowsLiveMail/donhallnntp/donhallnntp{testnntp}.oeaccount
new file mode 100644
index 0000000000..65872dcdbe
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/WindowsLiveMail/donhallnntp/donhallnntp{testnntp}.oeaccount
Binary files differ
diff --git a/comm/mailnews/import/test/unit/resources/WindowsLiveMail/news.mozilla.org/account{B3B3}.oeaccount b/comm/mailnews/import/test/unit/resources/WindowsLiveMail/news.mozilla.org/account{B3B3}.oeaccount
new file mode 100644
index 0000000000..f17de718ae
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/WindowsLiveMail/news.mozilla.org/account{B3B3}.oeaccount
Binary files differ
diff --git a/comm/mailnews/import/test/unit/resources/WindowsLiveMail/pop3.test.test/account{D244}.oeaccount b/comm/mailnews/import/test/unit/resources/WindowsLiveMail/pop3.test.test/account{D244}.oeaccount
new file mode 100644
index 0000000000..5ec08c4945
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/WindowsLiveMail/pop3.test.test/account{D244}.oeaccount
Binary files differ
diff --git a/comm/mailnews/import/test/unit/resources/addressbook.json b/comm/mailnews/import/test/unit/resources/addressbook.json
new file mode 100644
index 0000000000..c9545d3027
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/addressbook.json
@@ -0,0 +1,170 @@
+{
+ "basic_addressbook": [
+ {
+ "DisplayName": "Display Name",
+ "PrimaryEmail": "primaryemail@host.invalid",
+ "FirstName": "First",
+ "LastName": "Last",
+ "NickName": "Nickname",
+ "SecondEmail": "secondemail@host.invalid",
+ "_AimScreenName": "screenname",
+ "LastModifiedDate": 1213818826,
+ "_vCard": [
+ "VERSION:4.0",
+ "FN:Display Name",
+ "EMAIL;PREF=1:primaryemail@host.invalid",
+ "EMAIL:secondemail@host.invalid",
+ "NICKNAME:Nickname",
+ "NOTE:Notes line 1\\nNotes line 2\\nNotes line 3\\nNotes line 4",
+ "ORG:Organization Name;Department",
+ "TITLE:Job Title",
+ "BDAY;VALUE=DATE:19000102",
+ "N:Last;First;;;",
+ "ADR;TYPE=home:;Home Address Line 2;Home Address Line 1;Home City;Home State",
+ " ;Home Zip;Home Country",
+ "ADR;TYPE=work:;Work Address Line 2;Work Address Line 1;Work City;Work State",
+ " ;Work Zip;Work Country",
+ "TEL;TYPE=home;VALUE=TEXT:234-567-8901",
+ "TEL;TYPE=work;VALUE=TEXT:123-456-7890",
+ "TEL;TYPE=fax;VALUE=TEXT:345-678-9012",
+ "TEL;TYPE=pager;VALUE=TEXT:456-789-0123",
+ "TEL;TYPE=cell;VALUE=TEXT:567-890-1234",
+ "URL;TYPE=work;VALUE=URL:http://127.0.0.1",
+ "URL;TYPE=home;VALUE=URL:http://localhost",
+ "X-CUSTOM1;VALUE=TEXT:Custom Field 1",
+ "X-CUSTOM2;VALUE=TEXT:Custom Field 2",
+ "X-CUSTOM3;VALUE=TEXT:Custom Field 3",
+ "X-CUSTOM4;VALUE=TEXT:Custom Field 4",
+ "UID:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ ]
+ }
+ ],
+ "bug_263304": [
+ {
+ "DisplayName": "Display Name",
+ "PrimaryEmail": "primaryemail@host.invalid",
+ "_vCard": [
+ "VERSION:4.0",
+ "FN:Display Name",
+ "EMAIL;PREF=1:primaryemail@host.invalid",
+ "URL;TYPE=work;VALUE=URL:http://127.0.0.1",
+ "UID:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ ]
+ }
+ ],
+ "utf16_csv": [
+ {
+ "DisplayName": "John Doe",
+ "PrimaryEmail": "johndoe@host.invalid",
+ "FirstName": "John",
+ "LastName": "Doe"
+ }
+ ],
+ "shiftjis_csv": [
+ {
+ "DisplayName": "åç„¡ã—ã®æ¨©å…µè¡›",
+ "PrimaryEmail": "åç„¡ã—ã®æ¨©å…µè¡›@host.invalid"
+ }
+ ],
+ "quote_csv": [
+ {
+ "DisplayName": "Acer America",
+ "_vCard": [
+ "VERSION:4.0",
+ "FN:Acer America",
+ "ORG:Acer America;",
+ "TEL;TYPE=work;VALUE=TEXT:(800) 000-0000",
+ "UID:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ ]
+ }
+ ],
+ "vcard_import": [
+ {
+ "DisplayName": "John Doe",
+ "FirstName": "John",
+ "LastName": "Doe",
+ "PrimaryEmail": "john.doe@genericemail.invalid"
+ },
+ {
+ "DisplayName": "Jane Doe",
+ "FirstName": "Jane",
+ "LastName": "Doe",
+ "PrimaryEmail": "jane.doe@genericemail.invalid"
+ }
+ ],
+ "dos_vcard_import": [
+ {
+ "DisplayName": "Name Surname",
+ "FirstName": "Name",
+ "LastName": "Surname",
+ "PrimaryEmail": "example@gmail.com"
+ }
+ ],
+ "csv_import": [
+ {
+ "DisplayName": "John Doe",
+ "FirstName": "John",
+ "LastName": "Doe",
+ "PrimaryEmail": "john@doe.invalid"
+ },
+ {
+ "DisplayName": "Jane Doe",
+ "FirstName": "Jane",
+ "LastName": "Doe",
+ "PrimaryEmail": "jane@doe.invalid"
+ }
+ ],
+ "becky_addressbook": [
+ {
+ "DisplayName": "The first man",
+ "PrimaryEmail": "first@host.invalid",
+ "_vCard": [
+ "VERSION:3.0",
+ "FN:The first man",
+ "ORG:Organization;Post;",
+ "X-BECKY-IMAGE:0",
+ "N:The nick name of the first man",
+ "TEL;TYPE=HOME:11-1111-1111",
+ "TEL;TYPE=WORK:22-2222-2222",
+ "TEL;TYPE=CELL:333-3333-3333",
+ "EMAIL;TYPE=INTERNET:first@host.invalid",
+ "NOTE:This is a note.",
+ "UID:4E4D17E8.0043655C"
+ ]
+ },
+ {
+ "DisplayName": "The second man",
+ "PrimaryEmail": "second@host.invalid",
+ "_vCard": [
+ "VERSION:3.0",
+ "FN:The second man",
+ "ORG:Organization;post;",
+ "X-BECKY-IMAGE:0",
+ "N:The nick name of the second man",
+ "TEL;TYPE=HOME:44-4444-4444",
+ "TEL;TYPE=WORK:55-5555-5555",
+ "TEL;TYPE=CELL:666-6666-6666",
+ "EMAIL;TYPE=INTERNET:second@host.invalid",
+ "NOTE:This is a note.",
+ "UID:4EBBF6FE.00AC632B"
+ ]
+ },
+ {
+ "DisplayName": "The third man",
+ "PrimaryEmail": "third@host.invalid",
+ "_vCard": [
+ "VERSION:3.0",
+ "FN:The third man",
+ "ORG:Organization;post;",
+ "X-BECKY-IMAGE:0",
+ "N:The third man",
+ "TEL;TYPE=HOME:77-7777-7777",
+ "TEL;TYPE=WORK:88-8888-8888",
+ "TEL;TYPE=CELL:999-9999-9999",
+ "EMAIL;TYPE=INTERNET:third@host.invalid",
+ "NOTE:This is a note.",
+ "UID:4E57AB44.0001D53E"
+ ]
+ }
+ ]
+}
diff --git a/comm/mailnews/import/test/unit/resources/basic_addressbook.csv b/comm/mailnews/import/test/unit/resources/basic_addressbook.csv
new file mode 100644
index 0000000000..5d97d36f17
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/basic_addressbook.csv
@@ -0,0 +1,2 @@
+First Name,Last Name,Display Name,Nickname,Primary Email,Secondary Email,Work Phone,Home Phone,Fax Number,Pager Number,Mobile Number,Home Address,Home Address 2,Home City,Home State,Home ZipCode,Home Country,Work Address,Work Address 2,Work City,Work State,Work ZipCode,Work Country,Job Title,Department,Organization,Web Page 1,Web Page 2,Birth Year,Birth Month,Birth Day,Custom 1,Custom 2,Custom 3,Custom 4,Notes,Screen Name,
+John,Doe,John Doe,,johndoe@host.invalid,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
diff --git a/comm/mailnews/import/test/unit/resources/basic_csv_addressbook.csv b/comm/mailnews/import/test/unit/resources/basic_csv_addressbook.csv
new file mode 100644
index 0000000000..d4ecd1a308
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/basic_csv_addressbook.csv
@@ -0,0 +1,3 @@
+First Name,Last Name,Display Name,Nickname,Primary Email,Secondary Email,Work Phone,Home Phone,Fax Number,Pager Number,Mobile Number,Home Address,Home Address 2,Home City,Home State,Home ZipCode,Home Country,Work Address,Work Address 2,Work City,Work State,Work ZipCode,Work Country,Job Title,Department,Organization,Web Page 1,Web Page 2,Birth Year,Birth Month,Birth Day,Custom 1,Custom 2,Custom 3,Custom 4,Notes,Screen Name
+John,Doe,John Doe,,john@doe.invalid,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
+Jane,Doe,Jane Doe,,jane@doe.invalid,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
diff --git a/comm/mailnews/import/test/unit/resources/basic_ldif_addressbook.ldif b/comm/mailnews/import/test/unit/resources/basic_ldif_addressbook.ldif
new file mode 100644
index 0000000000..cf8ebab355
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/basic_ldif_addressbook.ldif
@@ -0,0 +1,45 @@
+dn: cn=Display Name,mail=primaryemail@host.invalid
+objectclass: top
+objectclass: person
+objectclass: organizationalPerson
+objectclass: inetOrgPerson
+objectclass: mozillaAbPersonAlpha
+givenName: First
+sn: Last
+cn: Display Name
+mozillaNickname: Nickname
+mail: primaryemail@host.invalid
+mozillaSecondEmail: secondemail@host.invalid
+nsAIMid: screenname
+mozillaUseHtmlMail: true
+modifytimestamp: 1213818826
+telephoneNumber: 123-456-7890
+homePhone: 234-567-8901
+fax: 345-678-9012
+pager: 456-789-0123
+mobile: 567-890-1234
+mozillaHomeStreet: Home Address Line 1
+mozillaHomeStreet2: Home Address Line 2
+mozillaHomeLocalityName: Home City
+mozillaHomeState: Home State
+mozillaHomePostalCode: Home Zip
+mozillaHomeCountryName: Home Country
+street: Work Address Line 1
+mozillaWorkStreet2: Work Address Line 2
+l: Work City
+st: Work State
+postalCode: Work Zip
+c: Work Country
+title: Job Title
+ou: Department
+o: Organization Name
+mozillaWorkUrl: http://127.0.0.1
+mozillaHomeUrl: http://localhost
+birthyear: 1900
+birthmonth: 1
+birthday: 2
+mozillaCustom1: Custom Field 1
+mozillaCustom2: Custom Field 2
+mozillaCustom3: Custom Field 3
+mozillaCustom4: Custom Field 4
+description:: Tm90ZXMgbGluZSAxCk5vdGVzIGxpbmUgMgpOb3RlcyBsaW5lIDMKTm90ZXMgbGluZSA0
diff --git a/comm/mailnews/import/test/unit/resources/basic_vcard_addressbook.vcf b/comm/mailnews/import/test/unit/resources/basic_vcard_addressbook.vcf
new file mode 100644
index 0000000000..7232bd6f23
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/basic_vcard_addressbook.vcf
@@ -0,0 +1,12 @@
+BEGIN:VCARD
+VERSION:2.1
+FN:John Doe
+N:Doe;John;;;
+EMAIL;TYPE=INTERNET:john.doe@genericemail.invalid
+END:VCARD
+BEGIN:VCARD
+VERSION:2.1
+FN:Jane Doe
+N:Doe;Jane;;;
+EMAIL;TYPE=INTERNET:jane.doe@genericemail.invalid
+END:VCARD
diff --git a/comm/mailnews/import/test/unit/resources/becky/addressbooks/4e4d186e.bab b/comm/mailnews/import/test/unit/resources/becky/addressbooks/4e4d186e.bab
new file mode 100644
index 0000000000..2b7e4de7e9
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/becky/addressbooks/4e4d186e.bab
@@ -0,0 +1,26 @@
+BEGIN:VCARD
+VERSION:3.0
+UID:4E4D17E8.0043655C
+FN:The first man
+ORG:Organization;Post;
+X-BECKY-IMAGE:0
+N:The nick name of the first man
+TEL;TYPE=HOME:11-1111-1111
+TEL;TYPE=WORK:22-2222-2222
+TEL;TYPE=CELL:333-3333-3333
+EMAIL;TYPE=INTERNET;PREF:first@host.invalid
+NOTE;ENCODING=QUOTED-PRINTABLE:This is a note.
+END:VCARD
+BEGIN:VCARD
+VERSION:3.0
+UID:4EBBF6FE.00AC632B
+FN:The second man
+ORG:Organization;post;
+X-BECKY-IMAGE:0
+N:The nick name of the second man
+TEL;TYPE=HOME:44-4444-4444
+TEL;TYPE=WORK:55-5555-5555
+TEL;TYPE=CELL:666-6666-6666
+EMAIL;TYPE=INTERNET;PREF:second@host.invalid
+NOTE;ENCODING=QUOTED-PRINTABLE:This is a note.
+END:VCARD
diff --git a/comm/mailnews/import/test/unit/resources/becky/addressbooks/4e4d186f.bab b/comm/mailnews/import/test/unit/resources/becky/addressbooks/4e4d186f.bab
new file mode 100644
index 0000000000..13df134ef8
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/becky/addressbooks/4e4d186f.bab
@@ -0,0 +1,13 @@
+BEGIN:VCARD
+VERSION:3.0
+UID:4E57AB44.0001D53E
+FN:The third man
+ORG:Organization;post;
+X-BECKY-IMAGE:0
+N:The third man
+TEL;TYPE=HOME:77-7777-7777
+TEL;TYPE=WORK:88-8888-8888
+TEL;TYPE=CELL:999-9999-9999
+EMAIL;TYPE=INTERNET;PREF:third@host.invalid
+NOTE;ENCODING=QUOTED-PRINTABLE:This is a note.
+END:VCARD
diff --git a/comm/mailnews/import/test/unit/resources/becky/addressbooks/do_not_import_this.nobab b/comm/mailnews/import/test/unit/resources/becky/addressbooks/do_not_import_this.nobab
new file mode 100644
index 0000000000..3a49cbfbf7
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/becky/addressbooks/do_not_import_this.nobab
@@ -0,0 +1,12 @@
+BEGIN:VCARD
+UID:4E4D17E8.0043655C
+FN:Nobody
+ORG:Organization;post;
+X-BECKY-IMAGE:0
+N:Nobody
+TEL;HOME:00-0000-0000
+TEL;WORK:11-1111-1111
+TEL;CELL:090-0000-0000
+EMAIL;PREF:nobody@example.com
+NOTE;ENCODING=QUOTED-PRINTABLE:This is a note.
+END:VCARD
diff --git a/comm/mailnews/import/test/unit/resources/becky/filters/IFilter.def b/comm/mailnews/import/test/unit/resources/becky/filters/IFilter.def
new file mode 100644
index 0000000000..0777e163c1
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/becky/filters/IFilter.def
@@ -0,0 +1,23 @@
+Version=1
+AutoSorting=1
+OnlyRead=0
+OnlyOneFolder=1
+:Begin ""
+!M:11111111.mb\!!!!Inbox\Sub11_OR_12\
+@0:Subject:Subject11 O I
+@0:Subject:Subject12 O I
+$O:Sort=1
+:End ""
+:Begin ""
+!M:11111111.mb\!!!!Inbox\From11_OR_12\
+@0:From:From11@example.com O I
+@0:From:From12@example.com O I
+$O:Sort=1
+:End ""
+:Begin ""
+!M:11111111.mb\!!!!Inbox\Sub12+From11\
+@0:Subject:Subject12 O I
+@1:From:From11@example.com O I
+$O:Sort=1
+$X:disabled
+:End ""
diff --git a/comm/mailnews/import/test/unit/resources/becky/filters/OFilter.def b/comm/mailnews/import/test/unit/resources/becky/filters/OFilter.def
new file mode 100644
index 0000000000..bf7d184ff5
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/becky/filters/OFilter.def
@@ -0,0 +1,22 @@
+Version=1
+AutoSorting=1
+OnlyRead=0
+OnlyOneFolder=1
+:Begin ""
+!M:11111111.mb\!!!!Outbox\!!!Sent\Sub21_OR_22\
+@0:Subject:Subject21 O IT
+@0:Subject:Subject22 O IT
+$O:Sort=1
+:End ""
+:Begin ""
+!M:11111111.mb\!!!!Outbox\!!!Sent\To21_OR_22\
+@0:To:To21@example.com O I
+@0:To:To22@example.com O I
+$O:Sort=1
+:End ""
+:Begin ""
+!M:11111111.mb\!!!!Outbox\!!!Sent\To21\
+@0:To:To21@example.com O I
+$O:Sort=1
+$X:disabled
+:End ""
diff --git a/comm/mailnews/import/test/unit/resources/bug_263304.ldif b/comm/mailnews/import/test/unit/resources/bug_263304.ldif
new file mode 100644
index 0000000000..3e4e34ee0f
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/bug_263304.ldif
@@ -0,0 +1,7 @@
+dn: cn=Display Name,mail=primaryemail@host.invalid
+objectclass: top
+objectclass: person
+objectclass: inetOrgPerson
+cn: Display Name
+mail: primaryemail@host.invalid
+labeledURI: http://127.0.0.1 label
diff --git a/comm/mailnews/import/test/unit/resources/csv_no_header.csv b/comm/mailnews/import/test/unit/resources/csv_no_header.csv
new file mode 100644
index 0000000000..f017ec3f7d
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/csv_no_header.csv
@@ -0,0 +1,2 @@
+John Doe,John,Doe,john@doe.invalid
+Jane Doe,Jane,Doe,jane@doe.invalid
diff --git a/comm/mailnews/import/test/unit/resources/csv_semicolon.csv b/comm/mailnews/import/test/unit/resources/csv_semicolon.csv
new file mode 100644
index 0000000000..6323d439ee
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/csv_semicolon.csv
@@ -0,0 +1,3 @@
+Display Name;First Name;Last Name;Primary Email
+John Doe;John;Doe;john@doe.invalid
+Jane Doe;Jane;Doe;jane@doe.invalid
diff --git a/comm/mailnews/import/test/unit/resources/dos_vcard_addressbook.vcf b/comm/mailnews/import/test/unit/resources/dos_vcard_addressbook.vcf
new file mode 100644
index 0000000000..722bebc137
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/dos_vcard_addressbook.vcf
@@ -0,0 +1,6 @@
+BEGIN:VCARD
+VERSION:3.0
+N:Surname;Name;;;
+FN:Name Surname
+EMAIL;type=INTERNET;type=HOME:example@gmail.com
+END:VCARD
diff --git a/comm/mailnews/import/test/unit/resources/emptylines_vcard_addressbook.vcf b/comm/mailnews/import/test/unit/resources/emptylines_vcard_addressbook.vcf
new file mode 100644
index 0000000000..2f67c61dfe
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/emptylines_vcard_addressbook.vcf
@@ -0,0 +1,18 @@
+
+
+BEGIN:VCARD
+VERSION:2.1
+FN:John Doe
+N:Doe;John;;;
+EMAIL;TYPE=INTERNET:john.doe@genericemail.invalid
+END:VCARD
+
+
+BEGIN:VCARD
+VERSION:2.1
+FN:Jane Doe
+N:Doe;Jane;;;
+EMAIL;TYPE=INTERNET:jane.doe@genericemail.invalid
+END:VCARD
+
+
diff --git a/comm/mailnews/import/test/unit/resources/import_helper.js b/comm/mailnews/import/test/unit/resources/import_helper.js
new file mode 100644
index 0000000000..e689547377
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/import_helper.js
@@ -0,0 +1,665 @@
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+// used by checkProgress to periodically check the progress of the import
+var gGenericImportHelper;
+/**
+ * GenericImportHelper
+ * The parent class of AbImportHelper, MailImportHelper, SettingsImportHelper
+ * and FiltersImportHelper.
+ *
+ * @param aModuleType The type of import module. Should be addressbook or mail.
+ * @param aModuleSearchString
+ * The string to search the module names for, such as
+ * "Text file" to find the import module for comma-separated
+ * value, LDIF, and tab-delimited files.
+ * @param aFile An instance of nsIFile to import.
+ *
+ * @class
+ * @class
+ */
+function GenericImportHelper(aModuleType, aModuleSearchString, aFile) {
+ gGenericImportHelper = null;
+ if (!["addressbook", "mail", "settings", "filters"].includes(aModuleType)) {
+ do_throw("Unexpected type passed to the GenericImportHelper constructor");
+ }
+ this.mModuleType = aModuleType;
+ this.mModuleSearchString = aModuleSearchString;
+ this.mInterface = this._findInterface();
+ Assert.ok(this.mInterface !== null);
+
+ this.mFile = aFile; // checked in the beginImport method
+}
+
+GenericImportHelper.prototype = {
+ interfaceType: Ci.nsIImportGeneric,
+ /**
+ * GenericImportHelper.beginImport
+ * Imports the given address book export or mail data and invoke
+ * checkProgress of child class to check the data,
+ */
+ beginImport() {
+ Assert.ok(this.mFile instanceof Ci.nsIFile && this.mFile.exists());
+
+ if (this.mModuleType == "addressbook") {
+ this.mInterface.SetData("addressLocation", this.mFile);
+ } else if (this.mModuleType == "mail") {
+ this.mInterface.SetData("mailLocation", this.mFile);
+ }
+
+ Assert.ok(this.mInterface.WantsProgress());
+ const error = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ Assert.ok(this.mInterface.BeginImport(null, error));
+ Assert.equal(error.data, "");
+ do_test_pending();
+ this.checkProgress();
+ },
+ /**
+ * GenericImportHelper.getInterface
+ *
+ * @returns An nsIImportGeneric import interface.
+ */
+ getInterface() {
+ return this.mInterface;
+ },
+
+ _findInterface() {
+ var importService = Cc["@mozilla.org/import/import-service;1"].getService(
+ Ci.nsIImportService
+ );
+ var count = importService.GetModuleCount(this.mModuleType);
+
+ // Iterate through each import module until the one being searched for is
+ // found and then return the ImportInterface of that module
+ for (var i = 0; i < count; i++) {
+ // Check if the current module fits the search string gets the interface
+ if (
+ importService
+ .GetModuleName(this.mModuleType, i)
+ .includes(this.mModuleSearchString)
+ ) {
+ return importService
+ .GetModule(this.mModuleType, i)
+ .GetImportInterface(this.mModuleType)
+ .QueryInterface(this.interfaceType);
+ }
+ }
+ return null; // it wasn't found
+ },
+ /**
+ * GenericImportHelper.checkProgress
+ * Checks the progress of an import every 200 milliseconds until it is
+ * complete. Checks the test results if there is an original address book,
+ * otherwise evaluates the optional command, or calls do_test_finished().
+ */
+ checkProgress() {
+ Assert.ok(
+ this.mInterface && this.mInterface instanceof Ci.nsIImportGeneric
+ );
+ Assert.ok(this.mInterface.ContinueImport());
+ // if the import isn't done, check again in 200 milliseconds.
+ if (this.mInterface.GetProgress() != 100) {
+ // use the helper object to check the progress of the import after 200 ms
+ gGenericImportHelper = this;
+ do_timeout(200, function () {
+ gGenericImportHelper.checkProgress();
+ });
+ } else {
+ // if it is done, check the results or finish the test.
+ this.checkResults();
+ do_test_finished();
+ }
+ },
+
+ /**
+ * GenericImportHelper.checkResults
+ * Checks the results of the import.
+ * Child class should implement this method.
+ */
+ checkResults() {},
+};
+
+/**
+ * AbImportHelper
+ * A helper for Address Book imports. To use, supply at least the file and type.
+ * If you would like the results checked, add a new array in the addressbook
+ * JSON file in the resources folder and supply aAbName and aJsonName.
+ * See AB_README for more information.
+ *
+ * @param aFile An instance of nsIAbFile to import.
+ * @param aModuleSearchString
+ * The string to search the module names for, such as
+ * "Text file" to find the import module for comma-separated
+ * value, LDIF, and tab-delimited files.
+ * Optional parameters: Include if you would like the import checked.
+ * @param aAbName The name the address book will have (the filename without
+ * the extension).
+ * @param aJsonName The name of the array in addressbook.json with the cards
+ * to compare with the imported cards.
+ * @class
+ * @class
+ */
+function AbImportHelper(aFile, aModuleSearchString, aAbName, aJsonName) {
+ GenericImportHelper.call(this, "addressbook", aModuleSearchString, aFile);
+
+ this.mAbName = aAbName;
+ /* Attribute notes: The attributes listed in the declaration below are
+ * supported by all three text export/import types.
+ * The following are not supported: anniversaryYear, anniversaryMonth,
+ * anniversaryDay, popularityIndex, isMailList, mailListURI, lastModifiedDate.
+ */
+ var supportedAttributes = [
+ "FirstName",
+ "LastName",
+ "DisplayName",
+ "NickName",
+ "PrimaryEmail",
+ "SecondEmail",
+ "WorkPhone",
+ "HomePhone",
+ "FaxNumber",
+ "PagerNumber",
+ "CellularNumber",
+ "HomeAddress",
+ "HomeAddress2",
+ "HomeCity",
+ "HomeState",
+ "HomeZipCode",
+ "HomeCountry",
+ "WorkAddress",
+ "WorkAddress2",
+ "WorkCity",
+ "WorkState",
+ "WorkZipCode",
+ "WorkCountry",
+ "JobTitle",
+ "Department",
+ "Company",
+ "BirthYear",
+ "BirthMonth",
+ "BirthDay",
+ "WebPage1",
+ "WebPage2",
+ "Custom1",
+ "Custom2",
+ "Custom3",
+ "Custom4",
+ "Notes",
+ "_AimScreenName",
+ "_vCard",
+ ];
+
+ // get the extra attributes supported for the given type of import
+ if (this.mFile.leafName.toLowerCase().endsWith(".ldif")) {
+ this.mSupportedAttributes = supportedAttributes;
+ } else if (this.mFile.leafName.toLowerCase().endsWith(".csv")) {
+ this.mSupportedAttributes = supportedAttributes;
+ this.setFieldMap(this.getDefaultFieldMap(true));
+ } else if (this.mFile.leafName.toLowerCase().endsWith(".vcf")) {
+ this.mSupportedAttributes = supportedAttributes;
+ }
+
+ // get the "cards" from the JSON file, if necessary
+ if (aJsonName) {
+ this.mJsonCards = this.getJsonCards(aJsonName);
+ }
+}
+
+AbImportHelper.prototype = {
+ __proto__: GenericImportHelper.prototype,
+ /**
+ * AbImportHelper.getDefaultFieldMap
+ * Returns the default field map.
+ *
+ * @param aSkipFirstRecord True if the first record of the text file should
+ * be skipped.
+ * @returns A default field map.
+ */
+ getDefaultFieldMap(aSkipFirstRecord) {
+ var importService = Cc["@mozilla.org/import/import-service;1"].getService(
+ Ci.nsIImportService
+ );
+ var fieldMap = importService.CreateNewFieldMap();
+
+ fieldMap.DefaultFieldMap(fieldMap.numMozFields);
+ this.mInterface
+ .GetData("addressInterface")
+ .QueryInterface(Ci.nsIImportAddressBooks)
+ .InitFieldMap(fieldMap);
+ fieldMap.skipFirstRecord = aSkipFirstRecord;
+
+ return fieldMap;
+ },
+
+ /**
+ * AbImportHelper.setFieldMap
+ * Set the field map.
+ *
+ * @param aFieldMap The field map used for address book import.
+ */
+ setFieldMap(aFieldMap) {
+ this.mInterface.SetData("fieldMap", aFieldMap);
+ },
+
+ /**
+ * AbImportHelper.setAddressLocation
+ * Set the the location of the address book.
+ *
+ * @param aLocation The location of the source address book.
+ */
+ setAddressBookLocation(aLocation) {
+ this.mInterface.SetData("addressLocation", aLocation);
+ },
+
+ /**
+ * AbImportHelper.setAddressDestination
+ * Set the the destination of the address book.
+ *
+ * @param aDestination URI of destination address book or null if
+ * new address books will be created.
+ */
+ setAddressDestination(aDestination) {
+ this.mInterface.SetData("addressDestination", aDestination);
+ },
+
+ /**
+ * AbImportHelper.checkResults
+ * Checks the results of the import.
+ * Ensures the an address book was created, then compares the supported
+ * attributes of each card with the card(s) in the JSON array.
+ * Calls do_test_finished() when done
+ */
+ checkResults() {
+ if (!this.mJsonCards) {
+ do_throw("The address book must be setup before checking results");
+ }
+ // When do_test_pending() was called and there is an error the test hangs.
+ // This try/catch block will catch any errors and call do_throw() with the
+ // error to throw the error and avoid the hang.
+ try {
+ // make sure an address book was created
+ var newAb = this.getAbByName(this.mAbName);
+ Assert.ok(newAb !== null);
+ Assert.ok(newAb.QueryInterface(Ci.nsIAbDirectory));
+ // get the imported card(s) and check each one
+ var count = 0;
+ for (let importedCard of newAb.childCards) {
+ this.compareCards(this.mJsonCards[count], importedCard);
+ count++;
+ }
+ // make sure there are the same number of cards in the address book and
+ // the JSON array
+ Assert.equal(count, this.mJsonCards.length);
+ do_test_finished();
+ } catch (e) {
+ do_throw(e);
+ }
+ },
+ /**
+ * AbImportHelper.getAbByName
+ * Returns the Address Book (if any) with the given name.
+ *
+ * @param aName The name of the Address Book to find.
+ * @returns An nsIAbDirectory, if found.
+ * null if the requested Address Book could not be found.
+ */
+ getAbByName(aName) {
+ Assert.ok(aName && aName.length > 0);
+
+ for (let data of MailServices.ab.directories) {
+ if (data.dirName == aName) {
+ return data;
+ }
+ }
+ return null;
+ },
+ /**
+ * AbImportHelper.compareCards
+ * Compares a JSON "card" with an imported card and throws an error if the
+ * values of a supported attribute are different.
+ *
+ * @param aJsonCard The object decoded from addressbook.json.
+ * @param aCard The imported card to compare with.
+ */
+ compareCards(aJsonCard, aCard) {
+ for (let [key, value] of Object.entries(aJsonCard)) {
+ if (!this.mSupportedAttributes.includes(key)) {
+ continue;
+ }
+ if (key == "_vCard") {
+ equal(
+ aCard
+ .getProperty(key, "")
+ .replace(
+ /UID:[a-f0-9-]{36}/i,
+ "UID:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ ),
+ `BEGIN:VCARD\r\n${value.join("\r\n")}\r\nEND:VCARD\r\n`,
+ "_vCard should be correct"
+ );
+ } else {
+ equal(aCard.getProperty(key, ""), value, `${key} should be correct`);
+ }
+ }
+ },
+ /**
+ * AbImportHelper.getJsonCards
+ * Gets an array of "cards" from the JSON file addressbook.json located in the
+ * mailnews/import/test/resources folder. The array should contain objects
+ * with the expected properties and values of the cards in the imported
+ * address book.
+ * See addressbook.json for an example and AB_README for more details.
+ *
+ * @param aName The name of the array in addressbook.json.
+ * @returns An array of "cards".
+ */
+ getJsonCards(aName) {
+ if (!aName) {
+ do_throw("Error - getJSONAb requires an address book name");
+ }
+ var file = do_get_file("resources/addressbook.json");
+ if (!file || !file.exists() || !file.isFile()) {
+ do_throw("Unable to get JSON file");
+ }
+
+ var fis = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
+ Ci.nsIFileInputStream
+ );
+ fis.init(file, 0x01, 0o444, 0);
+ var istream = Cc[
+ "@mozilla.org/intl/converter-input-stream;1"
+ ].createInstance(Ci.nsIConverterInputStream);
+ var replacementChar =
+ Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER;
+ istream.init(fis, "UTF-8", 1024, replacementChar);
+ var json = "";
+ var str = {};
+ // get the entire file into the json string
+ while (istream.readString(4096, str) != 0) {
+ json += str.value;
+ }
+ // close the input streams
+ istream.close();
+ fis.close();
+ // decode the JSON and get the array of cards
+ var arr = JSON.parse(json)[aName];
+ Assert.ok(arr && arr.length > 0);
+ return arr;
+ },
+
+ setSupportedAttributes(attributes) {
+ this.mSupportedAttributes = attributes;
+ },
+};
+
+/**
+ * MailImportHelper
+ * A helper for mail imports.
+ *
+ * @param aFile An instance of nsIFile to import.
+ * @param aModuleSearchString
+ * The string to search the module names for, such as
+ * "Outlook Express", etc.
+ * @param aExpected An instance of nsIFile to compare with the imported
+ * folders.
+ *
+ * @class
+ * @class
+ */
+function MailImportHelper(aFile, aModuleSearchString, aExpected) {
+ GenericImportHelper.call(this, "mail", aModuleSearchString, aFile);
+ this.mExpected = aExpected;
+}
+
+MailImportHelper.prototype = {
+ __proto__: GenericImportHelper.prototype,
+ interfaceType: Ci.nsIImportGeneric,
+ _checkEqualFolder(expectedFolder, actualFolder) {
+ Assert.equal(expectedFolder.leafName, actualFolder.name);
+
+ let expectedSubFolders = [];
+ for (let entry of expectedFolder.directoryEntries) {
+ if (entry.isDirectory()) {
+ expectedSubFolders.push(entry);
+ }
+ }
+ let actualSubFolders = actualFolder.subFolders;
+ Assert.equal(expectedSubFolders.length, actualSubFolders.length);
+ for (let i = 0; i < expectedSubFolders.length; i++) {
+ this._checkEqualFolder(expectedSubFolders[i], actualSubFolders[i]);
+ }
+ },
+
+ checkResults() {
+ let rootFolder = MailServices.accounts.localFoldersServer.rootFolder;
+ Assert.ok(rootFolder.containsChildNamed(this.mFile.leafName));
+ let importedFolder = rootFolder.getChildNamed(this.mFile.leafName);
+ Assert.notEqual(importedFolder, null);
+
+ this._checkEqualFolder(this.mExpected, importedFolder);
+ },
+};
+
+/**
+ * SettingsImportHelper
+ * A helper for settings imports.
+ *
+ * @param aFile An instance of nsIFile to import, can be null.
+ * @param aModuleSearchString
+ * The string to search the module names for, such as
+ * "Outlook Express", etc.
+ * @param aExpected An array of object which has incomingServer, identity
+ * and smtpSever to compare with imported nsIMsgAccount.
+ *
+ * @class
+ * @class
+ */
+function SettingsImportHelper(aFile, aModuleSearchString, aExpected) {
+ GenericImportHelper.call(this, "settings", aModuleSearchString, aFile);
+ this.mExpected = aExpected;
+}
+
+SettingsImportHelper.prototype = {
+ __proto__: GenericImportHelper.prototype,
+ interfaceType: Ci.nsIImportSettings,
+ /**
+ * SettingsImportHelper.beginImport
+ * Imports settings from a specific file or auto-located if the file is null,
+ * and compare the import results with the expected array.
+ */
+ beginImport() {
+ this._ensureNoAccounts();
+ if (this.mFile) {
+ this.mInterface.SetLocation(this.mFile);
+ } else {
+ Assert.equal(true, this.mInterface.AutoLocate({}, {}));
+ }
+ Assert.equal(true, this.mInterface.Import({}));
+ this.checkResults();
+ },
+
+ _ensureNoAccounts() {
+ for (let account of MailServices.accounts.accounts) {
+ MailServices.accounts.removeAccount(account);
+ }
+ },
+
+ _checkSmtpServer(expected, actual) {
+ Assert.equal(expected.port, actual.port);
+ Assert.equal(expected.username, actual.username);
+ Assert.equal(expected.authMethod, actual.authMethod);
+ Assert.equal(expected.socketType, actual.socketType);
+ },
+
+ _checkIdentity(expected, actual) {
+ Assert.equal(expected.fullName, actual.fullName);
+ Assert.equal(expected.email, actual.email);
+ Assert.equal(expected.replyTo, actual.replyTo);
+ Assert.equal(expected.organization, actual.organization);
+ },
+
+ _checkPop3IncomingServer(expected, actual) {
+ Assert.equal(expected.leaveMessagesOnServer, actual.leaveMessagesOnServer);
+ Assert.equal(
+ expected.deleteMailLeftOnServer,
+ actual.deleteMailLeftOnServer
+ );
+ Assert.equal(expected.deleteByAgeFromServer, actual.deleteByAgeFromServer);
+ Assert.equal(
+ expected.numDaysToLeaveOnServer,
+ actual.numDaysToLeaveOnServer
+ );
+ },
+
+ _checkIncomingServer(expected, actual) {
+ Assert.equal(expected.type, actual.type);
+ Assert.equal(expected.port, actual.port);
+ Assert.equal(expected.username, actual.username);
+ Assert.equal(expected.isSecure, actual.isSecure);
+ Assert.equal(expected.hostName, actual.hostName);
+ Assert.equal(expected.prettyName, actual.prettyName);
+ Assert.equal(expected.authMethod, actual.authMethod);
+ Assert.equal(expected.socketType, actual.socketType);
+ Assert.equal(expected.doBiff, actual.doBiff);
+ Assert.equal(expected.biffMinutes, actual.biffMinutes);
+
+ if (expected.type == "pop3") {
+ this._checkPop3IncomingServer(
+ expected,
+ actual.QueryInterface(Ci.nsIPop3IncomingServer)
+ );
+ }
+ },
+
+ _checkAccount(expected, actual) {
+ this._checkIncomingServer(expected.incomingServer, actual.incomingServer);
+
+ Assert.equal(1, actual.identities.length);
+ let actualIdentity = actual.identities[0];
+ this._checkIdentity(expected.identity, actualIdentity);
+
+ if (expected.incomingServer.type != "nntp") {
+ let actualSmtpServer = MailServices.smtp.getServerByKey(
+ actualIdentity.smtpServerKey
+ );
+ this._checkSmtpServer(expected.smtpServer, actualSmtpServer);
+ }
+ },
+
+ _isLocalMailAccount(account) {
+ return (
+ account.incomingServer.type == "none" &&
+ account.incomingServer.username == "nobody" &&
+ account.incomingServer.hostName == "Local Folders"
+ );
+ },
+
+ _findExpectedAccount(account) {
+ return this.mExpected.filter(function (expectedAccount) {
+ return (
+ expectedAccount.incomingServer.type == account.incomingServer.type &&
+ expectedAccount.incomingServer.username ==
+ account.incomingServer.username &&
+ expectedAccount.incomingServer.hostName ==
+ account.incomingServer.hostName
+ );
+ });
+ },
+
+ checkResults() {
+ for (let actualAccount of MailServices.accounts.accounts) {
+ if (this._isLocalMailAccount(actualAccount)) {
+ continue;
+ }
+ let expectedAccounts = this._findExpectedAccount(actualAccount);
+ Assert.notEqual(null, expectedAccounts);
+ Assert.equal(1, expectedAccounts.length);
+ this._checkAccount(expectedAccounts[0], actualAccount);
+ }
+ },
+};
+
+/**
+ * FiltersImportHelper
+ * A helper for filter imports.
+ *
+ * @param aFile An instance of nsIFile to import.
+ * @param aModuleSearchString
+ * The string to search the module names for, such as
+ * "Outlook Express", etc.
+ * @param aExpected The number of filters that should exist after import.
+ *
+ * @class
+ * @class
+ */
+function FiltersImportHelper(aFile, aModuleSearchString, aExpected) {
+ GenericImportHelper.call(this, "filters", aModuleSearchString, aFile);
+ this.mExpected = aExpected;
+}
+
+FiltersImportHelper.prototype = {
+ __proto__: GenericImportHelper.prototype,
+ interfaceType: Ci.nsIImportFilters,
+
+ /**
+ * FiltersImportHelper.beginImport
+ * Imports filters from a specific file/folder or auto-located if the file is null,
+ * and compare the import results with the expected array.
+ */
+ beginImport() {
+ if (this.mFile) {
+ this.mInterface.SetLocation(this.mFile);
+ } else {
+ Assert.equal(true, this.mInterface.AutoLocate({}, {}));
+ }
+ Assert.equal(true, this.mInterface.Import({}));
+ this.checkResults();
+ },
+
+ _loopOverFilters(aFilterList, aCondition) {
+ let result = 0;
+ for (let i = 0; i < aFilterList.filterCount; i++) {
+ let filter = aFilterList.getFilterAt(i);
+ if (aCondition(filter)) {
+ result++;
+ }
+ }
+ return result;
+ },
+
+ checkResults() {
+ let expected = this.mExpected;
+ let server = MailServices.accounts.localFoldersServer;
+ let filterList = server.getFilterList(null);
+ if ("count" in expected) {
+ Assert.equal(filterList.filterCount, expected.count);
+ }
+ if ("enabled" in expected) {
+ Assert.equal(
+ this._loopOverFilters(filterList, f => f.enabled),
+ expected.enabled
+ );
+ }
+ if ("incoming" in expected) {
+ Assert.equal(
+ this._loopOverFilters(
+ filterList,
+ f => f.filterType & Ci.nsMsgFilterType.InboxRule
+ ),
+ expected.incoming
+ );
+ }
+ if ("outgoing" in expected) {
+ Assert.equal(
+ this._loopOverFilters(
+ filterList,
+ f => f.filterType & Ci.nsMsgFilterType.PostOutgoing
+ ),
+ expected.outgoing
+ );
+ }
+ },
+};
diff --git a/comm/mailnews/import/test/unit/resources/mock_windows_reg_factory.js b/comm/mailnews/import/test/unit/resources/mock_windows_reg_factory.js
new file mode 100644
index 0000000000..19583d5007
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/mock_windows_reg_factory.js
@@ -0,0 +1,84 @@
+var { MockRegistrar } = ChromeUtils.importESModule(
+ "resource://testing-common/MockRegistrar.sys.mjs"
+);
+
+var gCid;
+
+function MockWindowsRegKey(registryData) {
+ this._registryData = registryData;
+}
+
+MockWindowsRegKey.prototype = {
+ QueryInterface: ChromeUtils.generateQI(["nsIWindowsRegKey"]),
+
+ open(aRootKey, aRelPath, aMode) {
+ if (!this._registryData[aRelPath]) {
+ throw Components.Exception("", Cr.NS_ERROR_FAILURE);
+ }
+ this._keyPath = aRelPath;
+ },
+
+ close() {},
+
+ openChild(aRelPath, aMode) {
+ if (
+ !this._registryData[this._keyPath] ||
+ !this._registryData[this._keyPath][aRelPath]
+ ) {
+ throw Components.Exception("", Cr.NS_ERROR_FAILURE);
+ }
+
+ let child = new MockWindowsRegKey({});
+ let newKeyPath = this._keyPath + "\\" + aRelPath;
+ child._keyPath = newKeyPath;
+ child._registryData[newKeyPath] =
+ this._registryData[this._keyPath][aRelPath];
+ return child;
+ },
+
+ get childCount() {
+ return Object.keys(this._registryData[this._keyPath]).length;
+ },
+
+ getChildName(aIndex) {
+ let keys = Object.keys(this._registryData[this._keyPath]);
+ let keyAtIndex = keys[aIndex];
+ if (!keyAtIndex) {
+ throw Components.Exception("", Cr.NS_ERROR_FAILURE);
+ }
+
+ return keyAtIndex;
+ },
+
+ _readValue(aName) {
+ if (
+ !this._registryData[this._keyPath] ||
+ !this._registryData[this._keyPath][aName]
+ ) {
+ throw Components.Exception("", Cr.NS_ERROR_FAILURE);
+ }
+
+ return this._registryData[this._keyPath][aName];
+ },
+
+ readIntValue(aName) {
+ return this._readValue(aName);
+ },
+
+ readStringValue(aName) {
+ return this._readValue(aName);
+ },
+};
+
+/* exported setup_mock_registry, teardown_mock_registry */
+function setup_mock_registry(mockRegistry) {
+ gCid = MockRegistrar.register(
+ "@mozilla.org/windows-registry-key;1",
+ MockWindowsRegKey,
+ [mockRegistry]
+ );
+}
+
+function teardown_mock_registry() {
+ MockRegistrar.unregister(gCid);
+}
diff --git a/comm/mailnews/import/test/unit/resources/quote.csv b/comm/mailnews/import/test/unit/resources/quote.csv
new file mode 100644
index 0000000000..d508eac33c
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/quote.csv
@@ -0,0 +1,2 @@
+First Name,Last Name,Display Name,Nickname,Primary Email,Secondary Email,Screen Name,Work Phone,Home Phone,Fax Number,Pager Number,Mobile Number,Home Address,Home Address 2,Home City,Home State,Home ZipCode,Home Country,Work Address,Work Address 2,Work City,Work State,Work ZipCode,Work Country,Job Title,Department,Organization,Web Page 1,Web Page 2,Birth Year,Birth Month,Birth Day,Custom 1,Custom 2,Custom 3,Custom 4,Notes
+,,"Acer America",,,,,"(800) 000-0000","","","","","","","","","","","","","","","","",,,"Acer America",,,,,,,,,,
diff --git a/comm/mailnews/import/test/unit/resources/shiftjis_addressbook.csv b/comm/mailnews/import/test/unit/resources/shiftjis_addressbook.csv
new file mode 100644
index 0000000000..31467dd42f
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/shiftjis_addressbook.csv
@@ -0,0 +1,2 @@
+First Name,Last Name,Display Name,Nickname,Primary Email,Secondary Email,Work Phone,Home Phone,Fax Number,Pager Number,Mobile Number,Home Address,Home Address 2,Home City,Home State,Home ZipCode,Home Country,Work Address,Work Address 2,Work City,Work State,Work ZipCode,Work Country,Job Title,Department,Organization,Web Page 1,Web Page 2,Birth Year,Birth Month,Birth Day,Custom 1,Custom 2,Custom 3,Custom 4,Notes,Screen Name,
+Œ •º‰q,–¼–³‚µ,–¼–³‚µ‚ÌŒ •º‰q,,–¼–³‚µ‚ÌŒ •º‰q@host.invalid,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,
diff --git a/comm/mailnews/import/test/unit/resources/tab_comma_mixed.csv b/comm/mailnews/import/test/unit/resources/tab_comma_mixed.csv
new file mode 100644
index 0000000000..804a330e22
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/tab_comma_mixed.csv
@@ -0,0 +1,4 @@
+1,1,1,1,1@host.invalid,,,,,,,,,,,,,,,,,,,,,
+2 2 2 2 2@host.invalid
+3 3 3 3 3@host.invalid
+4 4 4 4 4@host.invalid
diff --git a/comm/mailnews/import/test/unit/resources/utf16_addressbook.csv b/comm/mailnews/import/test/unit/resources/utf16_addressbook.csv
new file mode 100644
index 0000000000..e22a64b187
--- /dev/null
+++ b/comm/mailnews/import/test/unit/resources/utf16_addressbook.csv
Binary files differ
diff --git a/comm/mailnews/import/test/unit/test_AddrBookFileImporter.js b/comm/mailnews/import/test/unit/test_AddrBookFileImporter.js
new file mode 100644
index 0000000000..2ba696d1ed
--- /dev/null
+++ b/comm/mailnews/import/test/unit/test_AddrBookFileImporter.js
@@ -0,0 +1,130 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+var { AddrBookFileImporter } = ChromeUtils.import(
+ "resource:///modules/AddrBookFileImporter.jsm"
+);
+
+/**
+ * Create a temporary address book, import a source file into it, then test the
+ * cards are correct.
+ *
+ * @param {string} type - A source file type supported by AddrBookFileImporter.
+ * @param {string} filePath - The path of a source file.
+ * @param {string} refDataKey - The key of an object in addressbook.json.
+ * @param {string[]} [csvFieldMap] - Map of CSV fields to address book fields.
+ */
+async function test_importAbFile(type, filePath, refDataKey, csvFieldMap) {
+ // Create an address book and init the importer.
+ let dirId = MailServices.ab.newAddressBook(
+ `tmp-${type}`,
+ "",
+ Ci.nsIAbManager.JS_DIRECTORY_TYPE
+ );
+ let targetDir = MailServices.ab.getDirectoryFromId(dirId);
+ let importer = new AddrBookFileImporter(type);
+
+ // Start importing.
+ let sourceFile = do_get_file(filePath);
+ if (type == "csv") {
+ let unmatched = await importer.parseCsvFile(sourceFile);
+ if (unmatched.length) {
+ importer.setCsvFields(csvFieldMap);
+ }
+ }
+ await importer.startImport(sourceFile, targetDir);
+
+ // Read in the reference data.
+ let refFile = do_get_file("resources/addressbook.json");
+ let refData = JSON.parse(await IOUtils.readUTF8(refFile.path))[refDataKey];
+
+ // Compare with the reference data.
+ for (let i = 0; i < refData.length; i++) {
+ let card = targetDir.childCards[i];
+ for (let [key, value] of Object.entries(refData[i])) {
+ if (key == "LastModifiedDate") {
+ continue;
+ }
+ if (key == "_vCard") {
+ equal(
+ card
+ .getProperty(key, "")
+ .replace(
+ /UID:[a-f0-9-]{36}/i,
+ "UID:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
+ ),
+ `BEGIN:VCARD\r\n${value.join("\r\n")}\r\nEND:VCARD\r\n`,
+ "_vCard should be correct"
+ );
+ } else {
+ equal(card.getProperty(key, ""), value, `${key} should be correct`);
+ }
+ }
+ }
+}
+
+/** Test importing .csv file works. */
+add_task(async function test_importCsvFile() {
+ // A comma separated file.
+ await test_importAbFile(
+ "csv",
+ "resources/basic_csv_addressbook.csv",
+ "csv_import"
+ );
+
+ // A semicolon separated file.
+ await test_importAbFile("csv", "resources/csv_semicolon.csv", "csv_import");
+
+ // A comma separated file without header row.
+ Services.prefs.setBoolPref("mail.import.csv.skipfirstrow", false);
+ await test_importAbFile("csv", "resources/csv_no_header.csv", "csv_import", [
+ 2, // DisplayName
+ 0, // FirstName
+ 1, // LastName
+ 4, // PrimaryEmail
+ ]);
+ Services.prefs.clearUserPref("mail.import.csv.skipfirstrow");
+
+ // A comma separated file with some fields containing quotes.
+ await test_importAbFile("csv", "resources/quote.csv", "quote_csv");
+
+ // Non-UTF8 csv file.
+ await test_importAbFile(
+ "csv",
+ "resources/shiftjis_addressbook.csv",
+ "shiftjis_csv"
+ );
+ await test_importAbFile(
+ "csv",
+ "resources/utf16_addressbook.csv",
+ "utf16_csv"
+ );
+});
+
+/** Test importing .vcf file works. */
+add_task(async function test_importVCardFile() {
+ return test_importAbFile(
+ "vcard",
+ "resources/basic_vcard_addressbook.vcf",
+ "vcard_import"
+ );
+});
+
+/** Test importing .vcf file with \r\r\n as line breaks works. */
+add_task(async function test_importDosVCardFile() {
+ return test_importAbFile(
+ "vcard",
+ "resources/dos_vcard_addressbook.vcf",
+ "dos_vcard_import"
+ );
+});
+
+/** Test importing .ldif file works. */
+add_task(async function test_importLdifFile() {
+ return test_importAbFile(
+ "ldif",
+ "resources/basic_ldif_addressbook.ldif",
+ "basic_addressbook"
+ );
+});
diff --git a/comm/mailnews/import/test/unit/test_ThunderbirdProfileImporter.js b/comm/mailnews/import/test/unit/test_ThunderbirdProfileImporter.js
new file mode 100644
index 0000000000..f89a8ca2a3
--- /dev/null
+++ b/comm/mailnews/import/test/unit/test_ThunderbirdProfileImporter.js
@@ -0,0 +1,288 @@
+/*
+ * 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 https://mozilla.org/MPL/2.0/.
+ */
+
+// It is necessary to manually disable `xpc::IsInAutomation` since
+// `resetPrefs` will flip the preference to re-enable `once`-synced
+// preference change assertions, and also change the value of those
+// preferences.
+Services.prefs.setBoolPref(
+ "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer",
+ false
+);
+
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+var { ThunderbirdProfileImporter } = ChromeUtils.import(
+ "resource:///modules/ThunderbirdProfileImporter.jsm"
+);
+
+let tmpProfileDir;
+registerCleanupFunction(() => {
+ tmpProfileDir?.remove(true);
+});
+
+/**
+ * Create a temporary dir to use as the source profile dir. Write a prefs.js
+ * into it.
+ *
+ * @param {Array<[string, string]>} prefs - An array of tuples, each tuple is
+ * a pref represented as [prefName, prefValue].
+ */
+async function createTmpProfileWithPrefs(prefs) {
+ tmpProfileDir?.remove(true);
+
+ // Create a temporary dir.
+ tmpProfileDir = Services.dirsvc.get("TmpD", Ci.nsIFile);
+ tmpProfileDir.append("profile-tmp");
+ tmpProfileDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
+ info(`Created a temporary profile at ${tmpProfileDir.path}`);
+
+ // Write prefs to prefs.js.
+ let prefsFile = tmpProfileDir.clone();
+ prefsFile.append("prefs.js");
+ let prefsContent = prefs
+ .map(([name, value]) => {
+ let prefValue = typeof value == "string" ? `"${value}"` : value;
+ return `user_pref("${name}", ${prefValue});`;
+ })
+ .join("\n");
+ return IOUtils.writeUTF8(prefsFile.path, prefsContent);
+}
+
+/**
+ * Construct a temporary profile dir with prefs, import into the current
+ * profile, then check the values of prefs related to mail accounts.
+ */
+add_task(async function test_importAccountsIntoEmptyProfile() {
+ equal(
+ Services.prefs.getCharPref("mail.accountmanager.accounts"),
+ "",
+ "Should have no accounts at first"
+ );
+ let charPrefs = [
+ ["mail.smtpserver.smtp1.username", "smtp-user-1"],
+ ["mail.smtpserver.smtp3.username", "smtp-user-2"],
+ ["mail.smtpservers", "smtp1,smtp3"],
+ ["mail.identity.id1.smtpServer", "smtp3"],
+ ["mail.identity.id3.fullName", "id-name-2"],
+ ["mail.identity.id4.stmpServer", "smtp1"],
+ ["mail.server.server2.type", "none"],
+ ["mail.server.server6.type", "imap"],
+ ["mail.server.server7.type", "pop3"],
+ ["mail.account.account2.server", "server2"],
+ ["mail.account.account3.server", "server6"],
+ ["mail.account.account3.identities", "id1,id3"],
+ ["mail.account.account4.server", "server7"],
+ ["mail.accountmanager.accounts", "account3,account4,account2"],
+ ];
+ await createTmpProfileWithPrefs(charPrefs);
+
+ let importer = new ThunderbirdProfileImporter();
+
+ await importer.startImport(tmpProfileDir, importer.SUPPORTED_ITEMS);
+ // Server/identity/account keys should be changed and remapped correctly after
+ // import.
+ let expectedCharPrefs = [
+ ["mail.smtpserver.smtp1.username", "smtp-user-1"],
+ ["mail.smtpserver.smtp2.username", "smtp-user-2"],
+ ["mail.smtpservers", "smtp1,smtp2"],
+ ["mail.identity.id1.smtpServer", "smtp2"],
+ ["mail.identity.id2.fullName", "id-name-2"],
+ ["mail.identity.id3.stmpServer", "smtp1"],
+ ["mail.server.server1.type", "none"],
+ ["mail.server.server2.type", "imap"],
+ ["mail.server.server3.type", "pop3"],
+ ["mail.account.account1.server", "server1"],
+ ["mail.account.account2.server", "server2"],
+ ["mail.account.account2.identities", "id1,id2"],
+ ["mail.account.account3.server", "server3"],
+ ["mail.accountmanager.accounts", "account2,account3,account1"],
+ ];
+ for (let [name, value] of expectedCharPrefs) {
+ equal(
+ Services.prefs.getCharPref(name, ""),
+ value,
+ `${name} should be correct`
+ );
+ }
+
+ // Remove all the prefs to do the next test.
+ Services.prefs.resetPrefs();
+
+ equal(
+ Services.prefs.getCharPref("mail.accountmanager.accounts"),
+ "",
+ "Should have no accounts after resetPrefs"
+ );
+ await importer.startImport(tmpProfileDir, {
+ ...importer.SUPPORTED_ITEMS,
+ accounts: false,
+ mailMessages: false, // If true, Local Folders is created
+ });
+ equal(
+ Services.prefs.getCharPref("mail.accountmanager.accounts"),
+ "",
+ "Should still have no accounts without importing accounts"
+ );
+
+ Services.prefs.resetPrefs();
+});
+
+/**
+ * Test that importing a server without directory works. A server without
+ * directory can happen after clicking a news url.
+ */
+add_task(async function test_serverWithoutDirectory() {
+ let prefs = [
+ ["mail.server.server1.type", "nntp"],
+ ["mail.server.server1.hostname", "news.invalid"],
+ ];
+ await createTmpProfileWithPrefs(prefs);
+
+ let importer = new ThunderbirdProfileImporter();
+ await importer.startImport(tmpProfileDir, importer.SUPPORTED_ITEMS);
+ for (let [name, value] of prefs) {
+ equal(
+ Services.prefs.getCharPref(name, ""),
+ value,
+ `${name} should be correct`
+ );
+ }
+});
+
+/**
+ * Test that when the source profile and current profile each has Local Folders,
+ * the source Local Folders will be merged into the current Local Folders.
+ */
+add_task(async function test_mergeLocalFolders() {
+ let prefs = [
+ ["mail.smtpserver.smtp1.username", "smtp-user-1"],
+ ["mail.smtpservers", "smtp1"],
+ ["mail.identity.id1.smtpServer", "smtp1"],
+ ["mail.server.server2.type", "none"],
+ ["mail.server.server2.directory-rel", "[ProfD]Mail/Local Folders"],
+ ["mail.server.server2.hostname", "Local Folders"],
+ ["mail.server.server3.type", "imap"],
+ ["mail.account.account2.server", "server2"],
+ ["mail.account.account3.server", "server3"],
+ ["mail.account.account3.identities", "id1"],
+ ["mail.accountmanager.accounts", "account3,account2"],
+ ["mail.accountmanager.localfoldersserver", "server2"],
+ ];
+ await createTmpProfileWithPrefs(prefs);
+
+ // Create a physical file in tmpProfileDir.
+ let sourceLocalFolder = tmpProfileDir.clone();
+ sourceLocalFolder.append("Mail");
+ sourceLocalFolder.append("Local Folders");
+ sourceLocalFolder.append("folder-xpcshell");
+ sourceLocalFolder.create(Ci.nsIFile.NORMAL_FILE_TYPE, 0o644);
+
+ // Create Local Folders in the current profile.
+ MailServices.accounts.createLocalMailAccount();
+
+ let importer = new ThunderbirdProfileImporter();
+ await importer.startImport(tmpProfileDir, importer.SUPPORTED_ITEMS);
+
+ // Test that sub msg folders are created in the current Local Folders.
+ let localFolders = MailServices.accounts.localFoldersServer.rootMsgFolder;
+ ok(localFolders.containsChildNamed("Local Folders"));
+ let msgFolder = localFolders.getChildNamed("Local Folders");
+ ok(msgFolder.containsChildNamed("folder-xpcshell"));
+
+ // Test that folder-xpcshell is copied into current Local Folders.
+ let importedFolder = localFolders.filePath;
+ importedFolder.append("Local Folders.sbd");
+ importedFolder.append("folder-xpcshell");
+ ok(importedFolder.exists(), "Source Local Folders should be merged in.");
+});
+
+/**
+ * Test that calendars can be correctly imported.
+ */
+add_task(async function test_importCalendars() {
+ // Set sortOrder to contain a fake calendar id.
+ Services.prefs.setCharPref("calendar.list.sortOrder", "uuid-x");
+
+ let prefs = [
+ ["calendar.registry.uuid-1.name", "Home"],
+ ["calendar.registry.uuid-1.type", "Storage"],
+ ["calendar.registry.uuid-3.name", "cal1"],
+ ["calendar.registry.uuid-3.type", "caldav"],
+ ["calendar.list.sortOrder", "uuid-1 uuid-3"],
+ ];
+
+ await createTmpProfileWithPrefs(prefs);
+
+ let importer = new ThunderbirdProfileImporter();
+
+ await importer.startImport(tmpProfileDir, { calendars: true });
+
+ // Test calendar.registry.* are imported correctly.
+ for (let [name, value] of prefs.slice(0, -1)) {
+ equal(
+ Services.prefs.getCharPref(name, ""),
+ value,
+ `${name} should be correct`
+ );
+ }
+
+ // Test calendar.list.sortOrder has merged ids.
+ equal(
+ Services.prefs.getCharPref("calendar.list.sortOrder"),
+ "uuid-x uuid-1 uuid-3",
+ "calendar.list.sortOrder should be correct"
+ );
+
+ Services.prefs.resetPrefs();
+});
+
+/**
+ * Test that tags can be correctly imported.
+ */
+add_task(async function test_importTags() {
+ let prefs = [
+ ["mailnews.tags.$label1.color", "#CC0011"],
+ ["mailnews.tags.$label1.tag", "tag1"],
+ ["mailnews.tags.$label2.color", "#CC0022"],
+ ["mailnews.tags.$label2.tag", "tag2"],
+ ];
+ await createTmpProfileWithPrefs(prefs);
+
+ let importer = new ThunderbirdProfileImporter();
+ await importer.startImport(tmpProfileDir, importer.SUPPORTED_ITEMS);
+
+ // Test mailnews.tags.* are imported because existing tags are in default state.
+ for (let [name, value] of prefs) {
+ equal(
+ Services.prefs.getCharPref(name, ""),
+ value,
+ `${name} should be correct`
+ );
+ }
+
+ let prefs2 = [
+ ["mailnews.tags.$label1.color", "#DD0011"],
+ ["mailnews.tags.$label1.tag", "tag11"],
+ ["mailnews.tags.$label2.color", "#DD0022"],
+ ["mailnews.tags.$label2.tag", "tag22"],
+ ["mailnews.tags.$tag3.color", "#DD0033"],
+ ["mailnews.tags.$tag3.tag", "tag3"],
+ ];
+ await createTmpProfileWithPrefs(prefs2);
+
+ await importer.startImport(tmpProfileDir, importer.SUPPORTED_ITEMS);
+
+ // $label1 and $label2 should not be imported, only $tag3 should be imported.
+ for (let [name, value] of [...prefs, ...prefs2.slice(4)]) {
+ equal(
+ Services.prefs.getCharPref(name, ""),
+ value,
+ `${name} should be correct`
+ );
+ }
+});
diff --git a/comm/mailnews/import/test/unit/test_becky_addressbook.js b/comm/mailnews/import/test/unit/test_becky_addressbook.js
new file mode 100644
index 0000000000..c4d4528e32
--- /dev/null
+++ b/comm/mailnews/import/test/unit/test_becky_addressbook.js
@@ -0,0 +1,59 @@
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ // Due to the import code using nsIAbManager off the main thread, we need
+ // to ensure that it is initialized before we start the main test.
+ MailServices.ab;
+
+ let file = do_get_file("resources/becky/addressbooks");
+ let helper = new AbImportHelper(
+ file,
+ "Becky!",
+ "addressbooks",
+ "becky_addressbook"
+ );
+ let vcfSupportedAttributes = [
+ "FirstName",
+ "LastName",
+ "DisplayName",
+ "NickName",
+ "PrimaryEmail",
+ "SecondEmail",
+ "WorkPhone",
+ "HomePhone",
+ "FaxNumber",
+ "PagerNumber",
+ "CellularNumber",
+ "HomeAddress",
+ "HomeAddress2",
+ "HomeCity",
+ "HomeState",
+ "HomeZipCode",
+ "HomeCountry",
+ "WorkAddress",
+ "WorkAddress2",
+ "WorkCity",
+ "WorkState",
+ "WorkZipCode",
+ "WorkCountry",
+ "JobTitle",
+ "Department",
+ "Company",
+ "BirthYear",
+ "BirthMonth",
+ "BirthDay",
+ "WebPage1",
+ "WebPage2",
+ "Custom1",
+ "Custom2",
+ "Custom3",
+ "Custom4",
+ "Notes",
+ "_AimScreenName",
+ "_vCard",
+ ];
+ helper.setSupportedAttributes(vcfSupportedAttributes);
+ helper.beginImport();
+}
diff --git a/comm/mailnews/import/test/unit/test_becky_filters.js b/comm/mailnews/import/test/unit/test_becky_filters.js
new file mode 100644
index 0000000000..5af6483071
--- /dev/null
+++ b/comm/mailnews/import/test/unit/test_becky_filters.js
@@ -0,0 +1,41 @@
+const { localAccountUtils } = ChromeUtils.import(
+ "resource://testing-common/mailnews/LocalAccountUtils.jsm"
+);
+
+function run_test() {
+ localAccountUtils.loadLocalMailAccount();
+
+ // Due to the import code using nsIAbManager off the main thread, we need
+ // to ensure that it is initialized before we start the main test.
+ MailServices.ab;
+
+ // Import incoming filters.
+ let file = do_get_file("resources/becky/filters/IFilter.def");
+ let helper1 = new FiltersImportHelper(file, "Becky!", {
+ count: 3,
+ enabled: 2,
+ incoming: 3,
+ outgoing: 0,
+ });
+ helper1.beginImport();
+
+ // Import outgoing filters.
+ file = do_get_file("resources/becky/filters/OFilter.def");
+ let helper2 = new FiltersImportHelper(file, "Becky!", {
+ count: 6,
+ enabled: 4,
+ incoming: 3,
+ outgoing: 3,
+ });
+ helper2.beginImport();
+
+ // Import both filter types automatically.
+ file = do_get_file("resources/becky/filters");
+ let helper3 = new FiltersImportHelper(file, "Becky!", {
+ count: 12,
+ enabled: 8,
+ incoming: 6,
+ outgoing: 6,
+ });
+ helper3.beginImport();
+}
diff --git a/comm/mailnews/import/test/unit/test_bug_263304.js b/comm/mailnews/import/test/unit/test_bug_263304.js
new file mode 100644
index 0000000000..7089c0a4c2
--- /dev/null
+++ b/comm/mailnews/import/test/unit/test_bug_263304.js
@@ -0,0 +1,25 @@
+/**
+ * Tests importing an address book export in the LDAP data interchange format
+ * (LDIF) with a labeledURI and checks the accuracy of the imported contact.
+ *
+ * This test checks for the following bug:
+ * - Bug 264405: The Address Book doesn't show the LDAP-field "labeledURI"
+ * as Website
+ */
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ // Due to the import code using nsIAbManager off the main thread, we need
+ // to ensure that it is initialized before we start the main test.
+ MailServices.ab;
+
+ var file = do_get_file("resources/bug_263304.ldif");
+ new AbImportHelper(
+ file,
+ "Text file",
+ "bug_263304",
+ "bug_263304"
+ ).beginImport();
+}
diff --git a/comm/mailnews/import/test/unit/test_bug_437556.js b/comm/mailnews/import/test/unit/test_bug_437556.js
new file mode 100644
index 0000000000..284309a2dc
--- /dev/null
+++ b/comm/mailnews/import/test/unit/test_bug_437556.js
@@ -0,0 +1,23 @@
+/**
+ * Test for a regression of Bug 437556: mailnews crashes while importing an
+ * address book if a field map is required but not set.
+ */
+function run_test() {
+ var file = do_get_file("resources/basic_addressbook.csv");
+ var errorStr = Cc["@mozilla.org/supports-string;1"].createInstance(
+ Ci.nsISupportsString
+ );
+ // get the Address Book text import interface and make sure it succeeded
+ var helper = new AbImportHelper(file, "Text file");
+ helper.setFieldMap(null);
+ helper.setAddressBookLocation(file);
+
+ var abInterface = helper.getInterface();
+ Assert.notEqual(abInterface, null);
+ // prepare to start the import
+ Assert.ok(abInterface.WantsProgress());
+ // start the import
+ // BeginImport should return false and log an error if the fieldMap isn't set
+ Assert.ok(!abInterface.BeginImport(null, errorStr));
+ Assert.notEqual(errorStr, "");
+}
diff --git a/comm/mailnews/import/test/unit/test_csv_GetSample.js b/comm/mailnews/import/test/unit/test_csv_GetSample.js
new file mode 100644
index 0000000000..3c401391e0
--- /dev/null
+++ b/comm/mailnews/import/test/unit/test_csv_GetSample.js
@@ -0,0 +1,15 @@
+function run_test() {
+ var file = do_get_file("resources/tab_comma_mixed.csv");
+ var helper = new AbImportHelper(file, "Text file");
+ var genericInterface = helper.getInterface();
+ Assert.notEqual(genericInterface, null);
+ let abInterface = genericInterface
+ .GetData("addressInterface")
+ .QueryInterface(Ci.nsIImportAddressBooks);
+ abInterface.SetSampleLocation(file);
+ let recordExists = {};
+
+ let sampleData = abInterface.GetSampleData(3, recordExists);
+ Assert.ok(recordExists.value);
+ Assert.equal(sampleData, "4\n4\n4\n4\n4@host.invalid");
+}
diff --git a/comm/mailnews/import/test/unit/test_csv_import.js b/comm/mailnews/import/test/unit/test_csv_import.js
new file mode 100644
index 0000000000..df53525de9
--- /dev/null
+++ b/comm/mailnews/import/test/unit/test_csv_import.js
@@ -0,0 +1,23 @@
+/**
+ * Tests importing an address book export in the LDAP data interchange (LDIF)
+ * format and checks the accuracy of the imported address book's cards.
+ * The current export contains only one card with most fields full.
+ *
+ * This test also checks for the following bugs:
+ * -Bug 439819: LDIF import does not include mozillahomestreet.
+ * -Bug 182128: Edit Card, Notes on several lines appear on one after
+ * export/import in text format *(only tests the import).
+ */
+function run_test() {
+ // Due to the import code using nsIAbManager off the main thread, we need
+ // to ensure that it is initialized before we start the main test.
+ MailServices.ab;
+
+ let file = do_get_file("resources/basic_csv_addressbook.csv");
+ new AbImportHelper(
+ file,
+ "csv",
+ "basic_csv_addressbook",
+ "csv_import"
+ ).beginImport();
+}
diff --git a/comm/mailnews/import/test/unit/test_csv_import_quote.js b/comm/mailnews/import/test/unit/test_csv_import_quote.js
new file mode 100644
index 0000000000..67b469bac5
--- /dev/null
+++ b/comm/mailnews/import/test/unit/test_csv_import_quote.js
@@ -0,0 +1,11 @@
+/**
+ * Tests importing quoted csv address books.
+ */
+function run_test() {
+ // Due to the import code using nsIAbManager off the main thread, we need
+ // to ensure that it is initialized before we start the main test.
+ MailServices.ab;
+
+ let file = do_get_file("resources/quote.csv");
+ new AbImportHelper(file, "csv", "quote", "quote_csv").beginImport();
+}
diff --git a/comm/mailnews/import/test/unit/test_ldif_import.js b/comm/mailnews/import/test/unit/test_ldif_import.js
new file mode 100644
index 0000000000..48be83c27b
--- /dev/null
+++ b/comm/mailnews/import/test/unit/test_ldif_import.js
@@ -0,0 +1,27 @@
+/**
+ * Tests importing an address book export in the LDAP data interchange (LDIF)
+ * format and checks the accuracy of the imported address book's cards.
+ * The current export contains only one card with most fields full.
+ *
+ * This test also checks for the following bugs:
+ * -Bug 439819: LDIF import does not include mozillahomestreet.
+ * -Bug 182128: Edit Card, Notes on several lines appear on one after
+ * export/import in text format *(only tests the import).
+ */
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ // Due to the import code using nsIAbManager off the main thread, we need
+ // to ensure that it is initialized before we start the main test.
+ MailServices.ab;
+
+ var file = do_get_file("resources/basic_ldif_addressbook.ldif");
+ new AbImportHelper(
+ file,
+ "Text file",
+ "basic_ldif_addressbook",
+ "basic_addressbook"
+ ).beginImport();
+}
diff --git a/comm/mailnews/import/test/unit/test_outlook_settings.js b/comm/mailnews/import/test/unit/test_outlook_settings.js
new file mode 100644
index 0000000000..0f0313dcad
--- /dev/null
+++ b/comm/mailnews/import/test/unit/test_outlook_settings.js
@@ -0,0 +1,158 @@
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+/* import-globals-from resources/mock_windows_reg_factory.js */
+load("resources/mock_windows_reg_factory.js");
+
+function POP3Account() {}
+
+POP3Account.prototype = {
+ "Account Name": "POP3 Account Name",
+ "POP3 Server": "pop.host.invalid",
+ "POP3 User Name": "pop3user",
+ "Leave Mail On Server": 1,
+ "SMTP Server": "smtp.host.invalid",
+ "SMTP Display Name": "SMTP Display Name",
+ "SMTP Email Address": "pop3user@host.invalid",
+ "SMTP Reply To Email Address": "pop3user@host.invalid",
+ "SMTP Organization Name": "SMTP Organization Name",
+ "SMTP User Name": "smtpuser",
+};
+
+function IMAPAccount() {}
+
+IMAPAccount.prototype = {
+ "Account Name": "IMAP Account Name",
+ "IMAP Server": "imap.host.invalid",
+ "IMAP User Name": "imapuser",
+ "SMTP Server": "smtp.host.invalid",
+ "SMTP Display Name": "SMTP Display Name",
+ "SMTP Email Address": "imapuser@host.invalid",
+ "SMTP Reply To Email Address": "imapuser@host.invalid",
+ "SMTP Organization Name": "SMTP Organization Name",
+ "SMTP User Name": "smtpuser",
+};
+
+/* Outlook 98 */
+function Outlook98Registry(defaultAccount) {
+ this._defaultAccount = defaultAccount;
+}
+
+Outlook98Registry.prototype = {
+ get "Software\\Microsoft\\Office\\8.0\\Outlook\\OMI Account Manager"() {
+ return {
+ "Default Mail Account": "00000001",
+ "00000001": this._defaultAccount,
+ };
+ },
+};
+
+/* Outlook 2003 - */
+function Outlook2003Registry(defaultAccount) {
+ this._defaultAccount = defaultAccount;
+}
+
+Outlook2003Registry.prototype = {
+ get "Software\\Microsoft\\Office\\Outlook\\OMI Account Manager"() {
+ return {
+ "Default Mail Account": "00000001",
+ "00000001": this._defaultAccount,
+ };
+ },
+};
+
+var expectedPop3Account = {
+ incomingServer: {
+ prettyName: "POP3 Account Name",
+ type: "pop3",
+ hostName: "pop.host.invalid",
+ username: "pop3user",
+ leaveMessagesOnServer: true,
+
+ // These are account default values, not imported from Outlook.
+ // They should probably be omitted, but the check functions in
+ // import_helper.js expect to find them.
+ deleteMailLeftOnServer: false,
+ deleteByAgeFromServer: false,
+ numDaysToLeaveOnServer: 7,
+ port: 110,
+ isSecure: false,
+ authMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ socketType: Ci.nsMsgSocketType.plain,
+ doBiff: true,
+ biffMinutes: 10,
+ },
+ identity: {
+ fullName: "SMTP Display Name",
+ email: "pop3user@host.invalid",
+ replyTo: "pop3user@host.invalid",
+ organization: "SMTP Organization Name",
+ },
+ smtpServer: {
+ hostname: "smtp.host.invalid",
+ username: "smtpuser",
+ port: 0, // default port
+ authMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ socketType: Ci.nsMsgSocketType.plain,
+ },
+};
+
+var expectedImapAccount = {
+ incomingServer: {
+ prettyName: "IMAP Account Name",
+ type: "imap",
+ hostName: "imap.host.invalid",
+ username: "imapuser",
+
+ // These are account default values, not imported from Outlook.
+ // They should probably be omitted, but the check functions in
+ // import_helper.js expect to find them.
+ port: 143,
+ isSecure: false,
+ authMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ socketType: Ci.nsMsgSocketType.plain,
+ doBiff: true,
+ biffMinutes: 10,
+ },
+ identity: {
+ fullName: "SMTP Display Name",
+ email: "imapuser@host.invalid",
+ replyTo: "imapuser@host.invalid",
+ organization: "SMTP Organization Name",
+ },
+ smtpServer: {
+ hostname: "smtp.host.invalid",
+ username: "smtpuser",
+ port: 0, // default port
+ authMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ socketType: Ci.nsMsgSocketType.plain,
+ },
+};
+
+function teardown() {
+ for (let server of MailServices.smtp.servers) {
+ MailServices.smtp.deleteServer(server);
+ }
+
+ teardown_mock_registry();
+}
+
+function _test(registry, expectedAccount) {
+ try {
+ setup_mock_registry(registry);
+ new SettingsImportHelper(null, "Outlook", [expectedAccount]).beginImport();
+ } catch (e) {
+ teardown();
+ do_throw(e);
+ }
+ teardown();
+}
+
+function run_test() {
+ _test(new Outlook2003Registry(new POP3Account()), expectedPop3Account);
+ _test(new Outlook2003Registry(new IMAPAccount()), expectedImapAccount);
+
+ _test(new Outlook98Registry(new POP3Account()), expectedPop3Account);
+ _test(new Outlook98Registry(new IMAPAccount()), expectedImapAccount);
+}
diff --git a/comm/mailnews/import/test/unit/test_shiftjis_csv.js b/comm/mailnews/import/test/unit/test_shiftjis_csv.js
new file mode 100644
index 0000000000..bd4416a09a
--- /dev/null
+++ b/comm/mailnews/import/test/unit/test_shiftjis_csv.js
@@ -0,0 +1,20 @@
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ // Due to the import code using nsIAbManager off the main thread, we need
+ // to ensure that it is initialized before we start the main test.
+ MailServices.ab;
+
+ let file = do_get_file("resources/shiftjis_addressbook.csv");
+ let helper = new AbImportHelper(
+ file,
+ "csv",
+ "shiftjis_addressbook",
+ "shiftjis_csv"
+ );
+
+ helper.setFieldMap(helper.getDefaultFieldMap(true));
+ helper.beginImport();
+}
diff --git a/comm/mailnews/import/test/unit/test_utf16_csv.js b/comm/mailnews/import/test/unit/test_utf16_csv.js
new file mode 100644
index 0000000000..cf64e72c0a
--- /dev/null
+++ b/comm/mailnews/import/test/unit/test_utf16_csv.js
@@ -0,0 +1,20 @@
+var { MailServices } = ChromeUtils.import(
+ "resource:///modules/MailServices.jsm"
+);
+
+function run_test() {
+ // Due to the import code using nsIAbManager off the main thread, we need
+ // to ensure that it is initialized before we start the main test.
+ MailServices.ab;
+
+ let file = do_get_file("resources/utf16_addressbook.csv");
+ let helper = new AbImportHelper(
+ file,
+ "csv",
+ "utf16_addressbook",
+ "utf16_csv"
+ );
+
+ helper.setFieldMap(helper.getDefaultFieldMap(true));
+ helper.beginImport();
+}
diff --git a/comm/mailnews/import/test/unit/test_vcard_import.js b/comm/mailnews/import/test/unit/test_vcard_import.js
new file mode 100644
index 0000000000..9ba0c5511a
--- /dev/null
+++ b/comm/mailnews/import/test/unit/test_vcard_import.js
@@ -0,0 +1,34 @@
+/**
+ * Tests importing an address book export in the LDAP data interchange (LDIF)
+ * format and checks the accuracy of the imported address book's cards.
+ * The current export contains only one card with most fields full.
+ *
+ * This test also checks for the following bugs:
+ * -Bug 439819: LDIF import does not include mozillahomestreet.
+ * -Bug 182128: Edit Card, Notes on several lines appear on one after
+ * export/import in text format *(only tests the import).
+ */
+function run_test() {
+ // Due to the import code using nsIAbManager off the main thread, we need
+ // to ensure that it is initialized before we start the main test.
+ MailServices.ab;
+
+ // test regular import (e.g. from file exported by another mail client)
+ let file = do_get_file("resources/basic_vcard_addressbook.vcf");
+ new AbImportHelper(
+ file,
+ "vcf",
+ "basic_vcard_addressbook",
+ "vcard_import"
+ ).beginImport();
+
+ // test import against file with extra newlines (e.g. as copy-pasted by
+ // hand, a relatively unlikely but still reasonable use case to cover)
+ file = do_get_file("resources/emptylines_vcard_addressbook.vcf");
+ new AbImportHelper(
+ file,
+ "vcf",
+ "emptylines_vcard_addressbook",
+ "vcard_import"
+ ).beginImport();
+}
diff --git a/comm/mailnews/import/test/unit/test_winmail.js b/comm/mailnews/import/test/unit/test_winmail.js
new file mode 100644
index 0000000000..73de9a1451
--- /dev/null
+++ b/comm/mailnews/import/test/unit/test_winmail.js
@@ -0,0 +1,178 @@
+/**
+ * Basic tests for importing accounts of Windows Live Mail.
+ */
+
+/* import-globals-from resources/mock_windows_reg_factory.js */
+load("resources/mock_windows_reg_factory.js");
+
+var expectedPop3TestTestAccount = {
+ incomingServer: {
+ type: "pop3",
+ hostName: "pop3.test.test",
+ prettyName: "testpopaccountname",
+ port: 110,
+ socketType: 0,
+ doBiff: true,
+ biffMinutes: 2,
+ isSecure: false,
+ username: "testpopusername",
+ authMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ leaveMessagesOnServer: false,
+ deleteMailLeftOnServer: false,
+ deleteByAgeFromServer: false,
+ numDaysToLeaveOnServer: 7,
+ },
+ identity: {
+ fullName: "popdisplayname",
+ organization: "",
+ email: "testpop@invalid.invalid",
+ },
+ smtpServer: {
+ hostname: "smtp.pop.test",
+ port: 0, // default port
+ username: "",
+ authMethod: Ci.nsMsgAuthMethod.none,
+ socketType: 0,
+ },
+};
+
+var expectedNewsMozillaOrgAccount = {
+ incomingServer: {
+ type: "nntp",
+ hostName: "testnews.mozilla.org",
+ prettyName: "accountnamemozillanews",
+ port: 119,
+ username: "",
+ socketType: 0,
+ isSecure: false,
+ authMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ doBiff: false,
+ biffMinutes: 10, // default value
+ },
+ identity: {
+ fullName: "test",
+ organization: "",
+ email: "mozillanews@invalid.invalid",
+ },
+};
+
+var expectedMicrosoftCommunitiesAccount = {
+ incomingServer: {
+ type: "nntp",
+ hostName: "testmsnews.microsoft.invalid",
+ prettyName: "Microsoft Communities Test",
+ port: 119,
+ username: "",
+ socketType: 0,
+ isSecure: false,
+ authMethod: Ci.nsMsgAuthMethod.passwordCleartext,
+ doBiff: false,
+ biffMinutes: 10, // default value
+ },
+ identity: {
+ fullName: "",
+ organization: "",
+ },
+};
+
+var expectedDonHallNntpAccount = {
+ incomingServer: {
+ type: "nntp",
+ hostName: "news.wingtiptoys.invalid",
+ prettyName: "donhallnntp",
+ port: 563,
+ username: "don",
+ isSecure: false,
+ authMethod: Ci.nsMsgAuthMethod.secure,
+ socketType: 0,
+ doBiff: false,
+ biffMinutes: 10, // default value
+ },
+ identity: {
+ fullName: "Don Hall",
+ organization: "Wingtip Toys",
+ email: "don@wingtiptoys.invalid",
+ replyTo: "don@wingtiptoys.invalid",
+ },
+};
+
+var expectedDonHallImapAccount = {
+ incomingServer: {
+ type: "imap",
+ hostName: "mail.wingtiptoys.invalid",
+ prettyName: "donhallimap",
+ port: 993,
+ isSecure: true,
+ doBiff: true,
+ biffMinutes: 2,
+ username: "don",
+ authMethod: Ci.nsMsgAuthMethod.secure,
+ socketType: Ci.nsMsgSocketType.SSL,
+ },
+ identity: {
+ fullName: "Don Hall",
+ organization: "Wingtip Toys",
+ email: "don@wingtiptoys.invalid",
+ replyTo: "don@wingtiptoys.invalid",
+ },
+ smtpServer: {
+ hostname: "smtp.wingtiptoys.invalid",
+ username: "don",
+ port: 25,
+ socketType: Ci.nsMsgSocketType.SSL,
+ authMethod: Ci.nsMsgAuthMethod.secure,
+ },
+};
+
+var expectedAccounts = [
+ expectedPop3TestTestAccount,
+ expectedNewsMozillaOrgAccount,
+ expectedMicrosoftCommunitiesAccount,
+ expectedDonHallNntpAccount,
+ expectedDonHallImapAccount,
+];
+
+function WinLiveMailRegistry(rootPath) {
+ this._rootPath = rootPath;
+}
+
+WinLiveMailRegistry.prototype = {
+ get "Software\\Microsoft\\Windows Live Mail"() {
+ return {
+ "Default Mail Account": "fill in mail account",
+ "Default News Account": "fill in news account",
+ "Store Root": this._rootPath,
+ mail: {
+ "Poll For Mail": 120000,
+ },
+ };
+ },
+};
+
+function _test(registry) {
+ try {
+ setup_mock_registry(registry);
+ new SettingsImportHelper(
+ null,
+ "Windows Live Mail",
+ expectedAccounts
+ ).beginImport();
+ } catch (e) {
+ teardown();
+ do_throw(e);
+ }
+ teardown();
+}
+
+function teardown() {
+ for (let server of MailServices.smtp.servers) {
+ MailServices.smtp.deleteServer(server);
+ }
+
+ teardown_mock_registry();
+}
+
+function run_test() {
+ let root = do_get_file("resources/WindowsLiveMail");
+ _test(new WinLiveMailRegistry(root.path));
+}
diff --git a/comm/mailnews/import/test/unit/xpcshell.ini b/comm/mailnews/import/test/unit/xpcshell.ini
new file mode 100644
index 0000000000..891ba63d96
--- /dev/null
+++ b/comm/mailnews/import/test/unit/xpcshell.ini
@@ -0,0 +1,26 @@
+[DEFAULT]
+head = head_import.js
+tail =
+support-files = resources/*
+
+[test_AddrBookFileImporter.js]
+[test_bug_263304.js]
+[test_bug_437556.js]
+[test_csv_GetSample.js]
+[test_becky_addressbook.js]
+run-if = os == 'win'
+tags = vcard
+[test_becky_filters.js]
+run-if = os == 'win'
+[test_csv_import.js]
+[test_csv_import_quote.js]
+[test_ldif_import.js]
+[test_outlook_settings.js]
+run-if = os == 'win'
+[test_shiftjis_csv.js]
+[test_ThunderbirdProfileImporter.js]
+[test_utf16_csv.js]
+[test_vcard_import.js]
+tags = vcard
+[test_winmail.js]
+run-if = os == 'win'